refactor: migrate and consolidate UI templates to compile-time Askama component macros

This commit is contained in:
2026-05-30 12:28:47 +05:00
parent f42a5f05b2
commit 110fc61fa2
16 changed files with 697 additions and 598 deletions
+130
View File
@@ -0,0 +1,130 @@
# Detailed Project Architecture & Design System Reference Manual
This document provides a comprehensive technical overview of the **Stick** template, detailing its vertical structure, core dependencies, event delegation contracts, component reuse solutions, and our current implementation plan.
---
## 📁 1. Project Directory Layout & Vertical Architecture
Unlike traditional MVC applications that split code horizontally (e.g., separating all controllers, all models, and all views), Stick is organized by **vertical features (use-cases)**. Every business module contains its own Rust handler logic, database schemas, repository queries, and custom routing.
### Directory Mapping
```text
stick/
├── Cargo.toml # Rust dependency management
├── package.json # Node assets configuration (optional tooling)
├── src/
│ ├── main.rs # Core composition, shared state, and route merging
│ ├── common/ # Shared utilities & configurations
│ │ ├── config.rs # Configuration parsed from environment variables (.env)
│ │ ├── database.rs # MongoDB initialization and connections
│ │ └── errors.rs # AppError implementation wrapping custom responses
│ ├── auth/ # AUTH USE-CASE (Users, Passwords, Session cookies)
│ │ ├── extractors.rs # Native Axum extractors for authentication state
│ │ ├── handlers.rs # Login and registration endpoint handlers
│ │ ├── models.rs # User database schemas
│ │ └── repository.rs # MongoDB operations for authentication
│ ├── tasks/ # TASKS USE-CASE (CRUD tasks, dashboard view)
│ │ ├── handlers.rs
│ │ ├── models.rs
│ │ └── repository.rs
│ ├── developers/ # DEVELOPERS USE-CASE (HTMX-based assignee autocomplete query)
│ │ ├── handlers.rs
│ │ ├── models.rs
│ │ └── repository.rs
│ ├── main_view/ # STATIC VIEWS & GLOBAL ASSET ENDPOINTS
│ │ └── mod.rs # Serves index.html, static css, and static javascript files
│ ├── components/ # WIKI & INTERACTIVE DESIGN SYSTEM
│ │ ├── mod.rs # Wiki routing table
│ │ └── handlers.rs # Handlers serving reference pages
│ └── input.css # Tailwind CSS v4 custom theme mappings and custom scrollbars
├── static/ # Raw assets compiled/included in compilation
│ ├── tailwind.css # Output/input css rules
│ └── js/
│ ├── components.js # Main component event delegation system
│ └── combobox.js # HTMX-friendly autocomplete combobox scripts
└── templates/ # Raw HTML template layout files (Askama templates)
├── base.html # Global HTML layout wrapper (injects headers, navigation)
├── auth/ # Login / registration markup
├── tasks/ # Task management layouts
├── main_view/ # Landing page layout
└── components/ # Component Wiki pages (Buttons, Modals, Comboboxes, etc.)
```
### Route Composition
In [src/main.rs](file:///home/enciphered/Desktop/Code/stick/src/main.rs), routers from each vertical module are instantiated and merged using Axum's `.merge()` method:
```rust
let app = Router::new()
.merge(main_view::router())
.merge(components::router())
.merge(auth::router())
.merge(tasks::router())
.merge(developers::router())
.with_state(state);
```
---
## 🛠️ 2. Core Technical Dependencies
* **Web Server**: `axum = "0.8.9"` — Native asynchronous routing with type-safe extractor traits.
* **Database**: `mongodb = "3.7.0"` — Offical Rust driver, using `bson = "3.1.0"` with serde serialization.
* **Template Rendering**: `askama = "0.16.0"` — Compile-time type safety; views are compiled directly into the binary.
* **Styling Engine**: Tailwind CSS (v4) — Uses native CSS variables and `@import "tailwindcss"` rather than complex configuration JSONs.
* **Security & JWT**: `jsonwebtoken = "10.4.0"` (via `rust_crypto` backend) + `bcrypt = "0.19.1"` for password hashing. JWTs are stored in secure `HttpOnly` cookie wrappers.
---
## 🎨 3. JavaScript & HTML Event Delegation Contract
Rather than attaching individual event listeners to elements upon page load (which degrades performance and fails when elements are swapped dynamically via HTMX), Stick uses **document-level event delegation** via [static/js/components.js](file:///home/enciphered/Desktop/Code/stick/static/js/components.js) and [static/js/combobox.js](file:///home/enciphered/Desktop/Code/stick/static/js/combobox.js).
### Active Binding Attributes & Hooks
To make components function, developers must preserve specific HTML attributes and class structures that JavaScript expects:
| Component Type | JavaScript Trigger / Hook | Expected Target / Functional Structure |
| :--- | :--- | :--- |
| **Modal / Dialog** | `[data-modal-target="id"]` | `.modal-dialog` (wrapper), `.modal-backdrop`, `.modal-close` |
| **Sheets / Drawers** | `[data-sheet-target="id"]` | `.sheet-dialog` (wrapper), `.sheet-backdrop`, `.sheet-close` |
| **Custom Dropdowns** | `.dropdown-trigger` | `.dropdown-menu` (container), `.dropdown-content` |
| **Interactive Tabs**| `[data-tab-target="pane-id"]` | `[data-tab-group="group-name"]` on buttons & content wrappers |
| **Accordions** | `.accordion-trigger` | `.accordion-item` (container), `.accordion-content`, `.accordion-chevron` |
| **Custom Select** | `.select-trigger` | `.custom-select` (container), `.select-popover`, `.select-item`, `.select-value` |
| **Autocomplete Combobox**| `.combobox-input` | `.autocomplete-combobox` (container), `.combobox-results`, `.combobox-value` |
*Any customized styling classes (colors, borders, rounded corners) can be freely added or changed. However, the core class naming hierarchy listed above must remain intact to preserve interaction animations and keyboard controls.*
---
## 🧩 4. Proposed Scalability & Code Reuse Solutions
### Solution 1: Askama Macros (Selected Demo)
Consolidate UI components into a global macros registry template.
* *Pros*: Safe compilation (missing variables or typos are checked at build time), zero performance cost.
* *Cons*: Complex child structures (e.g. slots or dynamic SVG parameters) require passing strings/blocks, which can look verbose.
### Solution 2: CSS Component Classes
Define custom components in [src/input.css](file:///home/enciphered/Desktop/Code/stick/src/input.css) using Tailwind CSS v4's components layer (e.g. `@apply btn btn-primary`).
* *Pros*: Clear, descriptive markup syntax; easily consumable by pure front-end web developers.
* *Cons*: Partially moves styling definitions out of HTML files and back into stylesheet files.
### Solution 3: Rust Struct Components
Represent reusable buttons, grids, or select widgets as Rust structs that implement Askama's `Template` trait.
* *Pros*: Advanced data-driven structures and type constraints managed entirely in Rust code.
* *Cons*: Visual updates require recompiling Rust binaries, which limits swift CSS experimentation.
---
## 🚀 5. Action Plan: Askama Macros Implementation
We are implementing a demo showcasing Askama Macros in a new git branch:
1. **Branch Setup**: Create `demo/askama-macros`.
2. **Define Macros**: Create `templates/components/macros.html` to define reusable snippets for:
* **Button**: Primary, secondary, outline, destructive, status indicators.
* **Modal**: Accessibility markup, background overlays, closing controls.
* **Autocomplete Combobox**: Dynamic HTMX data queries and search hooks.
3. **Refactor Views**: Replace manual HTML blocks in [dashboard.html](file:///home/enciphered/Desktop/Code/stick/templates/tasks/dashboard.html) with:
```html
{% import "components/macros.html" as ui %}
{{ ui::button("Submit", "primary") }}
```
-1
View File
@@ -185,7 +185,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (input) { if (input) {
input.value = name; input.value = name;
input.focus(); input.focus();
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true }));
} }
+5 -12
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Sign In - Stick{% endblock %} {% block title %}Sign In - Stick{% endblock %}
@@ -22,20 +23,12 @@
{% endif %} {% endif %}
<form class="space-y-5" action="/auth/login" method="post"> <form class="space-y-5" action="/auth/login" method="post">
<div> {{ ui::text_input(id="username", name="username", label="Username", type="text", placeholder="Enter username", required=true) }}
<label for="username" class="block text-sm font-medium text-slate-400 mb-1.5">Username</label>
<input id="username" name="username" type="text" required class="appearance-none rounded-xl relative block w-full px-4 py-3 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm" placeholder="Enter username"> {{ ui::text_input(id="password", name="password", label="Password", type="password", placeholder="••••••••", required=true) }}
</div>
<div> <div>
<label for="password" class="block text-sm font-medium text-slate-400 mb-1.5">Password</label> {{ ui::button(label="Sign In", variant="indigo", type="submit", extra_class="w-full py-3.5 shadow-lg shadow-sky-500/10") }}
<input id="password" name="password" type="password" required class="appearance-none rounded-xl relative block w-full px-4 py-3 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm" placeholder="••••••••">
</div>
<div>
<button type="submit" class="group relative w-full flex justify-center py-3.5 px-4 border border-transparent text-sm font-semibold rounded-xl text-white bg-gradient-to-r from-sky-500 to-indigo-600 hover:opacity-95 transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500 focus:ring-offset-[#0f172a] shadow-lg shadow-sky-500/10">
Sign In
</button>
</div> </div>
</form> </form>
+5 -12
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Sign Up - Stick{% endblock %} {% block title %}Sign Up - Stick{% endblock %}
@@ -31,20 +32,12 @@
{% endif %} {% endif %}
<form class="space-y-5" action="/auth/register" method="post"> <form class="space-y-5" action="/auth/register" method="post">
<div> {{ ui::text_input(id="username", name="username", label="Username", type="text", placeholder="Choose a username", required=true) }}
<label for="username" class="block text-sm font-medium text-slate-400 mb-1.5">Username</label>
<input id="username" name="username" type="text" required class="appearance-none rounded-xl relative block w-full px-4 py-3 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 transition duration-200 text-sm" placeholder="Choose a username"> {{ ui::text_input(id="password", name="password", label="Password", type="password", placeholder="••••••••", required=true) }}
</div>
<div> <div>
<label for="password" class="block text-sm font-medium text-slate-400 mb-1.5">Password</label> {{ ui::button(label="Sign Up", variant="primary", type="submit", extra_class="w-full py-3.5 bg-gradient-to-r from-emerald-500 to-teal-600 hover:opacity-95 text-white shadow-lg shadow-emerald-500/10 focus:ring-emerald-500") }}
<input id="password" name="password" type="password" required class="appearance-none rounded-xl relative block w-full px-4 py-3 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 transition duration-200 text-sm" placeholder="••••••••">
</div>
<div>
<button type="submit" class="group relative w-full flex justify-center py-3.5 px-4 border border-transparent text-sm font-semibold rounded-xl text-white bg-gradient-to-r from-emerald-500 to-teal-600 hover:opacity-95 transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 focus:ring-offset-[#0f172a] shadow-lg shadow-emerald-500/10">
Sign Up
</button>
</div> </div>
</form> </form>
+19 -56
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Buttons - Design System Wiki{% endblock %} {% block title %}Buttons - Design System Wiki{% endblock %}
@@ -33,82 +34,44 @@
<!-- Demo Viewport --> <!-- Demo Viewport -->
<div id="btn-sandbox" class="wiki-pane flex flex-wrap gap-4 py-2"> <div id="btn-sandbox" class="wiki-pane flex flex-wrap gap-4 py-2">
<button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background bg-primary text-primary-foreground hover:opacity-90 px-4 py-2.5 shadow-md shadow-slate-950/20 active:scale-95"> {{ ui::button(label="Primary", variant="primary") }}
Primary {{ ui::button(label="Secondary", variant="secondary") }}
</button> {{ ui::button(label="Outline", variant="outline") }}
{{ ui::button(label="Destructive", variant="destructive") }}
<button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background bg-secondary text-secondary-foreground hover:bg-secondary/80 px-4 py-2.5 active:scale-95"> <!-- Icon Button using safe SVG markup inside label -->
Secondary {{ ui::button(label="<svg class='w-4 h-4' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'><path stroke-linecap='round' stroke-linejoin='round' d='M12 4v16m8-8H4'/></svg> Create Task", variant="indigo", extra_class="gap-2") }}
</button>
<button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background border border-border bg-transparent hover:bg-secondary text-slate-200 px-4 py-2.5 active:scale-95"> <!-- Loading / Disabled state -->
Outline {{ ui::button(label="<svg class='animate-spin h-3.5 w-3.5 text-slate-500' fill='none' viewBox='0 0 24 24'><circle class='opacity-25' cx='12' cy='12' r='10' stroke='currentColor' stroke-width='4'></circle><path class='opacity-75' fill='currentColor' d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'></path></svg> Processing...", variant="secondary", disabled=true, extra_class="gap-2 text-slate-500 cursor-not-allowed") }}
</button>
<button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 focus:ring-offset-background bg-destructive text-destructive-foreground hover:opacity-90 px-4 py-2.5 shadow-md active:scale-95">
Destructive
</button>
<!-- Icon Button -->
<button class="inline-flex items-center gap-2 justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2.5 active:scale-95">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"/>
</svg>
Create Task
</button>
<!-- Loading State -->
<button disabled class="inline-flex items-center gap-2 justify-center rounded-xl text-xs font-bold bg-secondary text-slate-500 cursor-not-allowed px-4 py-2.5">
<svg class="animate-spin h-3.5 w-3.5 text-slate-500" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing...
</button>
</div> </div>
<!-- Code Snippet Area --> <!-- Code Snippet Area -->
<div id="btn-code" class="wiki-pane hidden space-y-4"> <div id="btn-code" class="wiki-pane hidden space-y-4">
<div class="relative group"> <div class="relative group">
<button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-muted-foreground/90 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)"> <button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-muted-foreground/90 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code Copy Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- Primary Button --&gt; <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background bg-primary text-primary-foreground hover:opacity-90 px-4 py-2.5 shadow-md shadow-slate-950/20 active:scale-95"&gt;
Primary &lt;!-- Primary Button --&gt;
&lt;/button&gt; {{ "{{" }} ui::button(label="Primary", variant="primary") {{ "}}" }}
&lt;!-- Secondary Button --&gt; &lt;!-- Secondary Button --&gt;
&lt;button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 bg-secondary text-secondary-foreground hover:bg-secondary/80 px-4 py-2.5 active:scale-95"&gt; {{ "{{" }} ui::button(label="Secondary", variant="secondary") {{ "}}" }}
Secondary
&lt;/button&gt;
&lt;!-- Outline Button --&gt; &lt;!-- Outline Button --&gt;
&lt;button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 border border-border bg-transparent hover:bg-secondary text-slate-200 px-4 py-2.5 active:scale-95"&gt; {{ "{{" }} ui::button(label="Outline", variant="outline") {{ "}}" }}
Outline
&lt;/button&gt;
&lt;!-- Destructive Button --&gt; &lt;!-- Destructive Button --&gt;
&lt;button class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-rose-500 bg-destructive text-destructive-foreground hover:opacity-90 px-4 py-2.5 active:scale-95"&gt; {{ "{{" }} ui::button(label="Destructive", variant="destructive") {{ "}}" }}
Destructive
&lt;/button&gt;
&lt;!-- Create Button (Icon + Text) --&gt; &lt;!-- Create Button (Icon + Text) --&gt;
&lt;button class="inline-flex items-center gap-2 justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 bg-indigo-650 hover:bg-indigo-600 text-white px-4 py-2.5 active:scale-95"&gt; {{ "{{" }} ui::button(label="&lt;svg class='w-4 h-4' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'&gt;&lt;path stroke-linecap='round' stroke-linejoin='round' d='M12 4v16m8-8H4'/&gt;&lt;/svg&gt; Create Task", variant="indigo", extra_class="gap-2") {{ "}}" }}
&lt;svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"&gt;
&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"/&gt;
&lt;/svg&gt;
Create Task
&lt;/button&gt;
&lt;!-- Spinner Loading Button --&gt; &lt;!-- Spinner Loading Button --&gt;
&lt;button disabled class="inline-flex items-center gap-2 justify-center rounded-xl text-xs font-bold bg-secondary text-slate-500 cursor-not-allowed px-4 py-2.5"&gt; {{ "{{" }} ui::button(label="&lt;svg class='animate-spin h-3.5 w-3.5 text-slate-500' fill='none' viewBox='0 0 24 24'&gt;&lt;circle class='opacity-25' cx='12' cy='12' r='10' stroke='currentColor' stroke-width='4'&gt;&lt;/circle&gt;&lt;path class='opacity-75' fill='currentColor' d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'&gt;&lt;/path&gt;&lt;/svg&gt; Processing...", variant="secondary", disabled=true, extra_class="gap-2 text-slate-500 cursor-not-allowed") {{ "}}" }}</code></pre>
&lt;svg class="animate-spin h-3.5 w-3.5" fill="none" viewBox="0 0 24 24"&gt;
&lt;circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"&gt;&lt;/circle&gt;
&lt;path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"&gt;&lt;/svg&gt;
Processing...
&lt;/button&gt;</code></pre>
</div> </div>
</div> </div>
</div> </div>
+5 -20
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Autocomplete (Combobox) - Design System Wiki{% endblock %} {% block title %}Autocomplete (Combobox) - Design System Wiki{% endblock %}
@@ -56,14 +57,7 @@
<span class="text-[10px] font-bold text-emerald-400 uppercase tracking-wider block mb-1">Server-Side HTMX Search</span> <span class="text-[10px] font-bold text-emerald-400 uppercase tracking-wider block mb-1">Server-Side HTMX Search</span>
{% if authenticated %} {% if authenticated %}
<div class="autocomplete-combobox relative"> {{ ui::search_combobox(name="assignee_id", label="Live MongoDB Query", placeholder="Query developers database...", search_url="/developers/search", input_id="wiki-db-search", value_id="wiki-db-value") }}
<label class="block text-xs font-semibold text-muted-foreground mb-1.5">Live MongoDB Query</label>
<input type="hidden" name="assignee_id" class="combobox-value">
<input type="text" name="q" placeholder="Query developers database..." autocomplete="off"
class="combobox-input block w-full px-4 py-2 bg-background border border-border text-white rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-sky-500"
hx-get="/developers/search" hx-trigger="input changed delay:200ms" hx-target="next .combobox-results">
<div class="combobox-results absolute z-10 w-full mt-2 bg-popover border border-border rounded-xl p-1 shadow-xl hidden"></div>
</div>
{% else %} {% else %}
<div class="rounded-2xl border border-border bg-[#09090b]/40 p-4 space-y-2 text-xs"> <div class="rounded-2xl border border-border bg-[#09090b]/40 p-4 space-y-2 text-xs">
<div class="flex items-center gap-1.5 text-amber-500 font-bold"> <div class="flex items-center gap-1.5 text-amber-500 font-bold">
@@ -115,19 +109,10 @@
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code Copy Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- Server-Side Combobox Container (Asynchronous Query via HTMX) --&gt; <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;div class="autocomplete-combobox relative"&gt;
&lt;!-- Holds the final value submitted to forms --&gt;
&lt;input type="hidden" name="assignee_id" class="combobox-value"&gt;
&lt;!-- Input box triggers search query. Submits with parameter 'q' --&gt; &lt;!-- Server-Side Combobox using macro (Asynchronous Query via HTMX) --&gt;
&lt;input type="text" name="q" placeholder="Search developers..." autocomplete="off" {{ "{{" }} ui::search_combobox(name="assignee_id", label="Live MongoDB Query", placeholder="Query developers database...", search_url="/developers/search", input_id="wiki-db-search", value_id="wiki-db-value") {{ "}}" }}</code></pre>
class="combobox-input block w-full px-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-sky-500"
hx-get="/developers/search" hx-trigger="input changed delay:250ms" hx-target="next .combobox-results"&gt;
&lt;!-- Dropdown container receives swapped HTML markup from server --&gt;
&lt;div class="combobox-results absolute z-10 w-full mt-2 bg-popover border border-border rounded-xl p-1 shadow-xl hidden"&gt;&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div> </div>
</div> </div>
</div> </div>
+15 -155
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Date & Time Pickers - Design System Wiki{% endblock %} {% block title %}Date & Time Pickers - Design System Wiki{% endblock %}
@@ -33,183 +34,42 @@
<!-- Demo Viewport --> <!-- Demo Viewport -->
<div id="picker-sandbox" class="wiki-pane grid grid-cols-1 sm:grid-cols-2 gap-6 max-w-lg py-2"> <div id="picker-sandbox" class="wiki-pane grid grid-cols-1 sm:grid-cols-2 gap-6 max-w-lg py-2">
<!-- Date Picker --> <!-- Date Picker using macro -->
<div class="space-y-2"> {{ ui::datepicker(id="wiki-datepicker", name="wiki_date", label="Date Picker", value="2026-05-30", display_text="May 30, 2026", data_year="2026", data_month="4") }}
<label class="block text-xs font-semibold text-slate-405">Date Picker</label>
<div class="custom-datepicker relative inline-block w-full" id="wiki-datepicker" data-year="2026" data-month="4">
<input type="hidden" name="wiki_date" class="datepicker-value" value="2026-05-30">
<button type="button" class="datepicker-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200 hover:bg-secondary transition focus:outline-none focus:ring-2 focus:ring-sky-500">
<span class="datepicker-label flex items-center gap-2">
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
<span class="datepicker-text">May 30, 2026</span>
</span>
<svg class="h-4 w-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="datepicker-popover absolute left-0 z-20 mt-2 w-[270px] p-3 rounded-2xl border border-border bg-popover shadow-2xl animate-in fade-in slide-in-from-top-2 duration-200 hidden">
<div class="flex items-center justify-between mb-3.5">
<button type="button" class="datepicker-prev p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
</button>
<span class="datepicker-month-year text-xs font-bold text-slate-200">May 2026</span>
<button type="button" class="datepicker-next p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg>
</button>
</div>
<div class="grid grid-cols-7 gap-1 text-center text-[10px] font-bold text-slate-500 mb-2">
<span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span>
</div>
<div class="datepicker-days grid grid-cols-7 gap-1 text-center"></div>
</div>
</div>
</div>
<!-- Time Picker --> <!-- Time Picker using macro -->
<div class="space-y-2"> {{ ui::timepicker(id="wiki-timepicker", name="wiki_time", label="Time Picker", value="12:00 PM") }}
<label class="block text-xs font-semibold text-slate-405">Time Picker</label>
<div class="custom-timepicker relative inline-block w-full" id="wiki-timepicker">
<input type="hidden" name="wiki_time" class="timepicker-value" value="12:00 PM">
<button type="button" class="timepicker-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200 hover:bg-secondary transition focus:outline-none focus:ring-2 focus:ring-sky-500">
<span class="timepicker-label flex items-center gap-2">
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="timepicker-text">12:00 PM</span>
</span>
<svg class="h-4 w-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="timepicker-popover absolute right-0 sm:left-0 z-20 mt-2 w-[230px] p-3 rounded-2xl border border-border bg-popover shadow-2xl animate-in fade-in slide-in-from-top-2 duration-200 hidden">
<div class="flex gap-2 justify-center items-center">
<div class="flex flex-col items-center">
<span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider">Hr</span>
<div class="h-32 overflow-y-auto w-12 text-center rounded-lg border border-border bg-popover scrollbar-none timepicker-col-hours"></div>
</div>
<span class="text-slate-500 font-bold self-end mb-12">:</span>
<div class="flex flex-col items-center">
<span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider">Min</span>
<div class="h-32 overflow-y-auto w-12 text-center rounded-lg border border-border bg-popover scrollbar-none timepicker-col-minutes"></div>
</div>
<div class="flex flex-col items-center ml-1">
<span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider">Am/Pm</span>
<div class="flex flex-col gap-1 w-12">
<button type="button" class="timepicker-ampm-btn py-1.5 rounded-lg text-xs font-bold hover:bg-secondary transition text-muted-foreground">AM</button>
<button type="button" class="timepicker-ampm-btn py-1.5 rounded-lg text-xs font-bold hover:bg-secondary transition text-muted-foreground">PM</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<!-- Code Snippet Area --> <!-- Code Snippet Area -->
<div id="picker-code" class="wiki-pane hidden space-y-4"> <div id="picker-code" class="wiki-pane hidden space-y-4">
<!-- Date Picker Code --> <!-- Date Picker Macro -->
<div class="space-y-2"> <div class="space-y-2">
<span class="text-xs font-bold text-muted-foreground block">Date Picker HTML Structure</span> <span class="text-xs font-bold text-muted-foreground block">Date Picker Macro</span>
<div class="relative group"> <div class="relative group">
<button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-muted-foreground/90 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)"> <button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-muted-foreground/90 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Date Picker Code Copy Date Picker Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- Custom Date Picker Container <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
- class "custom-datepicker": required for JavaScript target binding
- data-year / data-month: initializes the calendar viewport (month is 0-indexed: 4 = May) --&gt;
&lt;div class="custom-datepicker relative inline-block w-full" id="unique-datepicker-id" data-year="2026" data-month="4"&gt;
&lt;!-- Hidden input that holds the actual selected date (YYYY-MM-DD) to submit with the form --&gt;
&lt;input type="hidden" name="date_value" class="datepicker-value" value="2026-05-30"&gt;
&lt;!-- Trigger Button: opens/closes the dropdown calendar popover --&gt; &lt;!-- Custom Date Picker using macro --&gt;
&lt;button type="button" class="datepicker-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200"&gt; {{ "{{" }} ui::datepicker(id="my-datepicker", name="my_date", label="Date Picker", value="2026-05-30", display_text="May 30, 2026", data_year="2026", data_month="4") {{ "}}" }}</code></pre>
&lt;span class="datepicker-label flex items-center gap-2"&gt;
&lt;svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"&gt;
&lt;path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/&gt;
&lt;/svg&gt;
&lt;!-- text-label: will be dynamically updated by components.js when a day is selected --&gt;
&lt;span class="datepicker-text"&gt;Pick a date&lt;/span&gt;
&lt;/span&gt;
&lt;svg class="h-4 w-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"&gt;&lt;polyline points="6 9 12 15 18 9"/&gt;&lt;/svg&gt;
&lt;/button&gt;
&lt;!-- Popover: holds month controls and calendar grids --&gt;
&lt;div class="datepicker-popover absolute left-0 z-20 mt-2 w-[270px] p-3 rounded-2xl border border-border bg-popover shadow-2xl hidden"&gt;
&lt;!-- Navigation Controls --&gt;
&lt;div class="flex items-center justify-between mb-3.5"&gt;
&lt;button type="button" class="datepicker-prev p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white"&gt;
&lt;svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"&gt;&lt;polyline points="15 18 9 12 15 6"/&gt;&lt;/svg&gt;
&lt;/button&gt;
&lt;!-- Current visible Month/Year label --&gt;
&lt;span class="datepicker-month-year text-xs font-bold text-slate-200"&gt;&lt;/span&gt;
&lt;button type="button" class="datepicker-next p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white"&gt;
&lt;svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"&gt;&lt;polyline points="9 18 15 12 9 6"/&gt;&lt;/svg&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;!-- Weekday column labels --&gt;
&lt;div class="grid grid-cols-7 gap-1 text-center text-[10px] font-bold text-slate-500 mb-2"&gt;
&lt;span&gt;Su&lt;/span&gt;&lt;span&gt;Mo&lt;/span&gt;&lt;span&gt;Tu&lt;/span&gt;&lt;span&gt;We&lt;/span&gt;&lt;span&gt;Th&lt;/span&gt;&lt;span&gt;Fr&lt;/span&gt;&lt;span&gt;Sa&lt;/span&gt;
&lt;/div&gt;
&lt;!-- Day Grid (filled dynamically with days by components.js on load) --&gt;
&lt;div class="datepicker-days grid grid-cols-7 gap-1 text-center"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div> </div>
</div> </div>
<!-- Time Picker Code --> <!-- Time Picker Macro -->
<div class="space-y-2"> <div class="space-y-2">
<span class="text-xs font-bold text-muted-foreground block">Time Picker HTML Structure</span> <span class="text-xs font-bold text-muted-foreground block">Time Picker Macro</span>
<div class="relative group"> <div class="relative group">
<button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-slate-455 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)"> <button class="absolute top-2 right-2 p-1.5 rounded-lg border border-border bg-popover/80 backdrop-blur text-[10px] font-semibold text-slate-455 hover:text-white hover:bg-secondary opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center gap-1.5" onclick="copyCodeSnippet(this)">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Time Picker Code Copy Time Picker Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- Custom Time Picker Container <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
- class "custom-timepicker": required for JavaScript target binding --&gt;
&lt;div class="custom-timepicker relative inline-block w-full" id="unique-timepicker-id"&gt;
&lt;!-- Hidden input holds selected value (e.g., "12:00 PM") for form submission --&gt;
&lt;input type="hidden" name="time_value" class="timepicker-value" value="12:00 PM"&gt;
&lt;!-- Trigger Button --&gt; &lt;!-- Custom Time Picker using macro --&gt;
&lt;button type="button" class="timepicker-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200"&gt; {{ "{{" }} ui::timepicker(id="my-timepicker", name="my_time", label="Time Picker", value="12:00 PM") {{ "}}" }}</code></pre>
&lt;span class="timepicker-label flex items-center gap-2"&gt;
&lt;svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"&gt;
&lt;path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/&gt;
&lt;/svg&gt;
&lt;!-- Time text label updated dynamically on pick --&gt;
&lt;span class="timepicker-text"&gt;12:00 PM&lt;/span&gt;
&lt;/span&gt;
&lt;svg class="h-4 w-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"&gt;&lt;polyline points="6 9 12 15 18 9"/&gt;&lt;/svg&gt;
&lt;/button&gt;
&lt;!-- Time Picker Dropdown Menu --&gt;
&lt;div class="timepicker-popover absolute left-0 z-20 mt-2 w-[230px] p-3 rounded-2xl border border-border bg-popover shadow-2xl hidden"&gt;
&lt;div class="flex gap-2 justify-center items-center"&gt;
&lt;!-- Hours Column (filled with &lt;button&gt;s 1 to 12 dynamically by components.js) --&gt;
&lt;div class="flex flex-col items-center"&gt;
&lt;span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider"&gt;Hr&lt;/span&gt;
&lt;div class="h-32 overflow-y-auto w-12 text-center rounded-lg border border-border bg-popover scrollbar-none timepicker-col-hours"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;span class="text-slate-500 font-bold self-end mb-12"&gt;:&lt;/span&gt;
&lt;!-- Minutes Column (filled with &lt;button&gt;s 00 to 55 in 5m steps dynamically) --&gt;
&lt;div class="flex flex-col items-center"&gt;
&lt;span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider"&gt;Min&lt;/span&gt;
&lt;div class="h-32 overflow-y-auto w-12 text-center rounded-lg border border-border bg-popover scrollbar-none timepicker-col-minutes"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;!-- AM/PM Selector --&gt;
&lt;div class="flex flex-col items-center ml-1"&gt;
&lt;span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider"&gt;Am/Pm&lt;/span&gt;
&lt;div class="flex flex-col gap-1 w-12"&gt;
&lt;button type="button" class="timepicker-ampm-btn py-1.5 rounded-lg text-xs font-bold hover:bg-secondary transition text-muted-foreground"&gt;AM&lt;/button&gt;
&lt;button type="button" class="timepicker-ampm-btn py-1.5 rounded-lg text-xs font-bold hover:bg-secondary transition text-muted-foreground"&gt;PM&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div> </div>
</div> </div>
</div> </div>
+28 -71
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Form Fields & Select - Design System Wiki{% endblock %} {% block title %}Form Fields & Select - Design System Wiki{% endblock %}
@@ -33,45 +34,22 @@
<!-- Demo Viewport --> <!-- Demo Viewport -->
<div id="input-sandbox" class="wiki-pane space-y-4 max-w-md py-2"> <div id="input-sandbox" class="wiki-pane space-y-4 max-w-md py-2">
<!-- Text field --> <!-- Text field using macro -->
<div class="space-y-2"> {{ ui::text_input(id="demo-username", name="username", label="Username Input", type="text", placeholder="e.g. dev_alice") }}
<label class="block text-xs font-semibold text-muted-foreground">Username Input</label>
<input type="text" placeholder="e.g. dev_alice" class="block h-10 w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white placeholder-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200">
</div>
<!-- Password field --> <!-- Password field using macro -->
<div class="space-y-2"> {{ ui::text_input(id="demo-password", name="password", label="Security Password", type="password", placeholder="••••••••") }}
<label class="block text-xs font-semibold text-muted-foreground">Security Password</label>
<input type="password" placeholder="••••••••" class="block h-10 w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white placeholder-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200">
</div>
<!-- Textarea --> <!-- Textarea using macro -->
<div class="space-y-2"> {{ ui::textarea(id="demo-details", name="details", label="Task Details", placeholder="Provide a detailed description of the task requirements...") }}
<label class="block text-xs font-semibold text-muted-foreground">Task Details</label>
<textarea rows="3" placeholder="Provide a detailed description of the task requirements..." class="block w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white placeholder-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200 resize-none"></textarea>
</div>
<!-- Custom Styled Select Dropdown --> <!-- Custom Styled Select Dropdown using paired macros -->
<div class="space-y-2"> {{ ui::select_open(name="specialization", label="Developer Specialization", current_value="Senior Rust Engineer", current_text="Senior Rust Engineer") }}
<label class="block text-xs font-semibold text-muted-foreground">Developer Specialization</label> {{ ui::select_item(value="Senior Rust Engineer", label="Senior Rust Engineer", is_selected=true) }}
<div class="custom-select relative inline-block w-full"> {{ ui::select_item(value="Frontend Architect", label="Frontend Architect") }}
<input type="hidden" name="specialization" class="select-value" value="Senior Rust Engineer"> {{ ui::select_item(value="DevOps Lead", label="DevOps Lead") }}
<button type="button" class="select-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200 hover:bg-secondary/50 transition focus:outline-none focus:ring-2 focus:ring-sky-500"> {{ ui::select_item(value="Database Administrator", label="Database Administrator") }}
<span class="select-text">Senior Rust Engineer</span> {{ ui::select_close() }}
<svg class="h-4 w-4 text-slate-500 transition-transform duration-200 select-chevron" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
<div class="select-popover absolute left-0 z-20 mt-2 w-full p-1 rounded-2xl border border-border bg-popover shadow-2xl hidden animate-in fade-in slide-in-from-top-2 duration-150">
<div class="max-h-60 overflow-y-auto p-0.5 space-y-0.5">
<button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs bg-accent text-accent-foreground font-semibold focus:bg-accent focus:text-accent-foreground focus:outline-none text-slate-205 cursor-pointer select-none text-left" data-value="Senior Rust Engineer">Senior Rust Engineer</button>
<button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none text-slate-205 cursor-pointer select-none text-left" data-value="Frontend Architect">Frontend Architect</button>
<button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none text-slate-205 cursor-pointer select-none text-left" data-value="DevOps Lead">DevOps Lead</button>
<button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none text-slate-205 cursor-pointer select-none text-left" data-value="Database Administrator">Database Administrator</button>
</div>
</div>
</div>
</div>
</div> </div>
<!-- Code Snippet Area --> <!-- Code Snippet Area -->
@@ -81,44 +59,23 @@
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code Copy Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- Styled Text Input --&gt; <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;div class="space-y-2"&gt;
&lt;label class="block text-xs font-semibold text-muted-foreground"&gt;Username Input&lt;/label&gt; &lt;!-- Styled Text Input --&gt;
&lt;input type="text" placeholder="e.g. dev_alice" {{ "{{" }} ui::text_input(id="demo-username", name="username", label="Username Input", type="text", placeholder="e.g. dev_alice") {{ "}}" }}
class="block h-10 w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200"&gt;
&lt;/div&gt; &lt;!-- Styled Password Input --&gt;
{{ "{{" }} ui::text_input(id="demo-password", name="password", label="Security Password", type="password", placeholder="••••••••") {{ "}}" }}
&lt;!-- Styled Textarea --&gt; &lt;!-- Styled Textarea --&gt;
&lt;div class="space-y-2"&gt; {{ "{{" }} ui::textarea(id="demo-details", name="details", label="Task Details", placeholder="Provide a detailed description...") {{ "}}" }}
&lt;label class="block text-xs font-semibold text-muted-foreground"&gt;Task Details&lt;/label&gt;
&lt;textarea rows="3" placeholder="Provide a detailed description..."
class="block w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200 resize-none"&gt;&lt;/textarea&gt;
&lt;/div&gt;
&lt;!-- Custom Styled Select Dropdown (Chevron and Popover managed globally in components.js) --&gt; &lt;!-- Custom Styled Select Dropdown --&gt;
&lt;div class="space-y-2"&gt; {{ "{{" }} ui::select_open(name="specialization", label="Developer Specialization", current_value="Senior Rust Engineer", current_text="Senior Rust Engineer") {{ "}}" }}
&lt;label class="block text-xs font-semibold text-muted-foreground"&gt;Developer Specialization&lt;/label&gt; {{ "{{" }} ui::select_item(value="Senior Rust Engineer", label="Senior Rust Engineer", is_selected=true) {{ "}}" }}
&lt;div class="custom-select relative inline-block w-full"&gt; {{ "{{" }} ui::select_item(value="Frontend Architect", label="Frontend Architect") {{ "}}" }}
&lt;!-- Hidden input holds the actual value for form submissions --&gt; {{ "{{" }} ui::select_item(value="DevOps Lead", label="DevOps Lead") {{ "}}" }}
&lt;input type="hidden" name="specialization" class="select-value" value="Senior Rust Engineer"&gt; {{ "{{" }} ui::select_close() {{ "}}" }}</code></pre>
&lt;!-- Toggle trigger button --&gt;
&lt;button type="button" class="select-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200"&gt;
&lt;span class="select-text"&gt;Senior Rust Engineer&lt;/span&gt;
&lt;svg class="h-4 w-4 text-slate-500 transition-transform duration-200 select-chevron" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"&gt;
&lt;polyline points="6 9 12 15 18 9"/&gt;
&lt;/svg&gt;
&lt;/button&gt;
&lt;!-- Options Popover --&gt;
&lt;div class="select-popover absolute left-0 z-20 mt-2 w-full p-1 rounded-2xl border border-border bg-popover shadow-2xl hidden"&gt;
&lt;div class="max-h-60 overflow-y-auto p-0.5 space-y-0.5"&gt;
&lt;button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs bg-accent text-accent-foreground font-semibold text-slate-200 text-left" data-value="Senior Rust Engineer"&gt;Senior Rust Engineer&lt;/button&gt;
&lt;button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs hover:bg-accent hover:text-accent-foreground text-slate-200 text-left" data-value="Frontend Architect"&gt;Frontend Architect&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div> </div>
</div> </div>
</div> </div>
+383
View File
@@ -0,0 +1,383 @@
{% macro button(label, variant="primary", type="button", extra_class="", disabled=false) %}
<button
type="{{ type }}"
{% if disabled %}disabled{% endif %}
class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background px-4 py-2.5 active:scale-95
{% if variant == "primary" %}
bg-primary text-primary-foreground hover:opacity-90 shadow-md shadow-slate-950/20
{% elif variant == "secondary" %}
bg-secondary text-secondary-foreground hover:bg-secondary/80
{% elif variant == "outline" %}
border border-border bg-transparent hover:bg-secondary text-slate-200
{% elif variant == "destructive" %}
bg-destructive text-destructive-foreground hover:opacity-90 shadow-md
{% elif variant == "indigo" %}
bg-indigo-600 hover:bg-indigo-500 text-white shadow-md
{% endif %}
{% if disabled %}opacity-50 cursor-not-allowed active:scale-100{% endif %}
{{ extra_class }}"
>
{{ label|safe }}
</button>
{% endmacro %}
{% macro modal_trigger(target_id, label, variant="primary", extra_class="") %}
<button
data-modal-target="{{ target_id }}"
class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background px-4 py-2.5 active:scale-95
{% if variant == "primary" %}
bg-primary text-primary-foreground hover:opacity-90 shadow-md shadow-slate-950/20
{% elif variant == "secondary" %}
bg-secondary text-secondary-foreground hover:bg-secondary/80
{% elif variant == "outline" %}
border border-border bg-transparent hover:bg-secondary text-slate-200
{% elif variant == "destructive" %}
bg-destructive text-destructive-foreground hover:opacity-90 shadow-md
{% elif variant == "indigo" %}
bg-indigo-600 hover:bg-indigo-500 text-white shadow-md
{% endif %}
{{ extra_class }}"
>
{{ label|safe }}
</button>
{% endmacro %}
{% macro sheet_trigger(target_id, label, variant="primary", extra_class="") %}
<button
data-sheet-target="{{ target_id }}"
class="inline-flex items-center justify-center rounded-xl text-xs font-bold transition-all focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-background px-4 py-2.5 active:scale-95
{% if variant == "primary" %}
bg-primary text-primary-foreground hover:opacity-90 shadow-md shadow-slate-950/20
{% elif variant == "secondary" %}
bg-secondary text-secondary-foreground hover:bg-secondary/80
{% elif variant == "outline" %}
border border-border bg-transparent hover:bg-secondary text-slate-200
{% elif variant == "destructive" %}
bg-destructive text-destructive-foreground hover:opacity-90 shadow-md
{% elif variant == "indigo" %}
bg-indigo-600 hover:bg-indigo-500 text-white shadow-md
{% endif %}
{{ extra_class }}"
>
{{ label|safe }}
</button>
{% endmacro %}
{% macro modal(id, title, content, close_label="Close") %}
<div id="{{ id }}" class="modal-dialog fixed inset-0 z-50 flex items-center justify-center hidden" role="dialog" aria-modal="true">
<div class="modal-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm transition-opacity duration-300"></div>
<div class="modal-content relative z-10 w-full max-w-sm scale-95 opacity-0 transition-all duration-300 border border-border bg-popover/95 backdrop-blur-xl p-6 shadow-2xl rounded-3xl text-center">
<div class="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 mb-3">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<h3 class="text-sm font-bold text-slate-100">{{ title }}</h3>
<div class="text-xs text-slate-400 mt-2 leading-relaxed">{{ content|safe }}</div>
<button class="modal-close mt-4 w-full py-2 rounded-xl bg-secondary border border-border hover:bg-secondary transition text-xs font-semibold text-slate-200">{{ close_label }}</button>
</div>
</div>
{% endmacro %}
{% macro modal_open(id, title) %}
<div id="{{ id }}" class="modal-dialog fixed inset-0 z-50 flex items-center justify-center hidden" role="dialog" aria-modal="true">
<div class="modal-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm transition-opacity duration-300"></div>
<div class="modal-content relative z-10 w-full max-w-sm scale-95 opacity-0 transition-all duration-300 border border-border bg-popover/95 backdrop-blur-xl p-6 shadow-2xl rounded-3xl text-center">
<div class="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 mb-3">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<h3 class="text-sm font-bold text-slate-100 mb-2">{{ title }}</h3>
{% endmacro %}
{% macro modal_close(close_label="Dismiss Modal") %}
<button class="modal-close mt-4 w-full py-2 rounded-xl bg-secondary border border-border hover:bg-secondary transition text-xs font-semibold text-slate-200">{{ close_label }}</button>
</div>
</div>
{% endmacro %}
{% macro sheet_open(id, title, max_width_class="max-w-sm") %}
<div id="{{ id }}" class="sheet-dialog fixed inset-0 z-50 overflow-hidden hidden" role="dialog" aria-modal="true">
<div class="sheet-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm opacity-0 transition-opacity duration-300 animate-fade-in"></div>
<div class="absolute inset-y-0 right-0 max-w-full flex pl-10">
<div class="sheet-content w-screen {{ max_width_class }} translate-x-full transition-transform duration-300 ease-in-out border-l border-border bg-popover/95 backdrop-blur-xl p-6 shadow-2xl flex flex-col justify-between">
<div class="space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-border">
<h3 class="text-sm font-bold text-slate-100">{{ title }}</h3>
<button class="sheet-close text-slate-500 hover:text-white rounded-lg p-1.5 hover:bg-secondary transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
{% endmacro %}
{% macro sheet_close(save_label="Save") %}
</div>
{% if !save_label.is_empty() %}
<button class="sheet-close w-full py-2.5 rounded-xl bg-indigo-650 hover:bg-indigo-600 transition text-xs font-bold text-white shadow-lg shadow-indigo-650/10">{{ save_label }}</button>
{% endif %}
</div>
</div>
</div>
{% endmacro %}
{% macro search_combobox(name, label, placeholder="Search...", search_url="/developers/search", input_id="combobox-search", value_id="combobox-value") %}
<div class="autocomplete-combobox relative">
{% if !label.is_empty() %}
<label class="block text-xs font-semibold text-slate-400 mb-1.5">{{ label }}</label>
{% endif %}
<!-- Hidden input holding the actual selected ID to submit in the form -->
<input type="hidden" id="{{ value_id }}" name="{{ name }}" class="combobox-value">
<div class="relative">
<input type="text"
id="{{ input_id }}"
name="q"
placeholder="{{ placeholder }}"
autocomplete="off"
hx-get="{{ search_url }}"
hx-trigger="input changed delay:250ms, search"
hx-target="next .combobox-results"
hx-indicator="next .combobox-indicator"
class="combobox-input appearance-none rounded-xl relative block w-full pl-9 pr-4 py-2.5 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm">
<!-- Search Icon & Loading Indicator -->
<div class="absolute left-3 top-3 text-slate-500">
<svg class="combobox-indicator htmx-indicator animate-spin h-4 w-4 text-sky-500 hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
<!-- Search Results Dropdown Popover -->
<div class="combobox-results absolute z-10 w-full mt-1.5 bg-slate-900 border border-slate-800 rounded-xl shadow-2xl overflow-hidden hidden">
</div>
</div>
{% endmacro %}
{% macro text_input(id, name, label, type="text", placeholder="", value="", required=false, extra_class="") %}
<div class="space-y-2 {{ extra_class }}">
{% if !label.is_empty() %}
<label for="{{ id }}" class="block text-xs font-semibold text-muted-foreground">{{ label }}</label>
{% endif %}
<input
id="{{ id }}"
name="{{ name }}"
type="{{ type }}"
value="{{ value }}"
placeholder="{{ placeholder }}"
{% if required %}required{% endif %}
class="block h-10 w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white placeholder-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200"
>
</div>
{% endmacro %}
{% macro textarea(id, name, label, placeholder="", value="", rows="3", required=false, extra_class="") %}
<div class="space-y-2 {{ extra_class }}">
{% if !label.is_empty() %}
<label for="{{ id }}" class="block text-xs font-semibold text-muted-foreground">{{ label }}</label>
{% endif %}
<textarea
id="{{ id }}"
name="{{ name }}"
rows="{{ rows }}"
placeholder="{{ placeholder }}"
{% if required %}required{% endif %}
class="block w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white placeholder-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200 resize-none"
>{{ value }}</textarea>
</div>
{% endmacro %}
{% macro select_open(name, label, current_value, current_text) %}
<div class="space-y-2">
{% if !label.is_empty() %}
<label class="block text-xs font-semibold text-muted-foreground">{{ label }}</label>
{% endif %}
<div class="custom-select relative inline-block w-full">
<input type="hidden" name="{{ name }}" class="select-value" value="{{ current_value }}">
<button type="button" class="select-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200 hover:bg-secondary/50 transition focus:outline-none focus:ring-2 focus:ring-sky-500">
<span class="select-text">{{ current_text }}</span>
<svg class="h-4 w-4 text-slate-500 transition-transform duration-200 select-chevron" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
<div class="select-popover absolute left-0 z-20 mt-2 w-full p-1 rounded-2xl border border-border bg-popover shadow-2xl hidden animate-in fade-in slide-in-from-top-2 duration-150">
<div class="max-h-60 overflow-y-auto p-0.5 space-y-0.5">
{% endmacro %}
{% macro select_item(value, label, is_selected=false) %}
<button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs {% if is_selected %}bg-accent text-accent-foreground font-semibold{% else %}hover:bg-accent hover:text-accent-foreground{% endif %} focus:bg-accent focus:text-accent-foreground focus:outline-none text-slate-200 cursor-pointer select-none text-left" data-value="{{ value }}">{{ label }}</button>
{% endmacro %}
{% macro select_close() %}
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro toggle_switch(name, label, checked=false, extra_class="") %}
<div class="flex items-center justify-between {{ extra_class }}">
{% if !label.is_empty() %}
<span class="text-xs text-muted-foreground font-medium">{{ label }}</span>
{% endif %}
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" name="{{ name }}" class="sr-only peer" {% if checked %}checked{% endif %}>
<div class="w-9 h-5 bg-secondary rounded-full border border-border peer-checked:bg-indigo-600 peer-checked:border-indigo-500 transition-all duration-300 relative after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-slate-400 after:rounded-full after:h-[14px] after:w-[14px] after:transition-all peer-checked:after:translate-x-4 peer-checked:after:bg-white"></div>
</label>
</div>
{% endmacro %}
{% macro checkbox(name, label, checked=false, extra_class="") %}
<label class="flex items-center gap-3 cursor-pointer group {{ extra_class }}">
<input type="checkbox" name="{{ name }}" class="sr-only peer" {% if checked %}checked{% endif %}>
<div class="w-4 h-4 rounded bg-popover border border-border flex items-center justify-center peer-checked:bg-indigo-600 peer-checked:border-indigo-500 peer-checked:[&_svg]:opacity-100 transition">
<svg class="w-2.5 h-2.5 text-white opacity-0 transition-opacity" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="4"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
</div>
{% if !label.is_empty() %}
<span class="text-xs text-muted-foreground peer-checked:text-slate-200 select-none">{{ label }}</span>
{% endif %}
</label>
{% endmacro %}
{% macro range_slider(name, label, min="0", max="100", value="50", extra_class="") %}
<div class="space-y-2 {{ extra_class }}">
{% if !label.is_empty() %}
<label class="block text-xs font-semibold text-muted-foreground">{{ label }}</label>
{% endif %}
<div class="flex items-center gap-4">
<input type="range" name="{{ name }}" min="{{ min }}" max="{{ max }}" value="{{ value }}" class="grow h-1 bg-secondary rounded-lg appearance-none cursor-pointer accent-indigo-600" oninput="this.nextElementSibling.textContent = this.value + '%'">
<span class="text-xs font-mono font-bold text-sky-400 w-10 text-right">{{ value }}%</span>
</div>
</div>
{% endmacro %}
{% macro datepicker(id, name, label, value="2026-05-30", display_text="May 30, 2026", data_year="2026", data_month="4", extra_class="") %}
<div class="space-y-2 {{ extra_class }}">
{% if !label.is_empty() %}
<label class="block text-xs font-semibold text-slate-400">{{ label }}</label>
{% endif %}
<div class="custom-datepicker relative inline-block w-full" id="{{ id }}" data-year="{{ data_year }}" data-month="{{ data_month }}">
<input type="hidden" name="{{ name }}" class="datepicker-value" value="{{ value }}">
<button type="button" class="datepicker-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200 hover:bg-secondary transition focus:outline-none focus:ring-2 focus:ring-sky-500">
<span class="datepicker-label flex items-center gap-2">
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
<span class="datepicker-text">{{ display_text }}</span>
</span>
<svg class="h-4 w-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="datepicker-popover absolute left-0 z-20 mt-2 w-[270px] p-3 rounded-2xl border border-border bg-popover shadow-2xl animate-in fade-in slide-in-from-top-2 duration-200 hidden">
<div class="flex items-center justify-between mb-3.5">
<button type="button" class="datepicker-prev p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
</button>
<span class="datepicker-month-year text-xs font-bold text-slate-200"></span>
<button type="button" class="datepicker-next p-1.5 rounded-lg hover:bg-secondary text-muted-foreground/90 hover:text-white transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg>
</button>
</div>
<div class="grid grid-cols-7 gap-1 text-center text-[10px] font-bold text-slate-500 mb-2">
<span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span>
</div>
<div class="datepicker-days grid grid-cols-7 gap-1 text-center"></div>
</div>
</div>
</div>
{% endmacro %}
{% macro timepicker(id, name, label, value="12:00 PM", extra_class="") %}
<div class="space-y-2 {{ extra_class }}">
{% if !label.is_empty() %}
<label class="block text-xs font-semibold text-slate-405">{{ label }}</label>
{% endif %}
<div class="custom-timepicker relative inline-block w-full" id="{{ id }}">
<input type="hidden" name="{{ name }}" class="timepicker-value" value="{{ value }}">
<button type="button" class="timepicker-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200 hover:bg-secondary transition focus:outline-none focus:ring-2 focus:ring-sky-500">
<span class="timepicker-label flex items-center gap-2">
<svg class="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span class="timepicker-text">{{ value }}</span>
</span>
<svg class="h-4 w-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="timepicker-popover absolute right-0 sm:left-0 z-20 mt-2 w-[230px] p-3 rounded-2xl border border-border bg-popover shadow-2xl animate-in fade-in slide-in-from-top-2 duration-200 hidden">
<div class="flex gap-2 justify-center items-center">
<div class="flex flex-col items-center">
<span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider">Hr</span>
<div class="h-32 overflow-y-auto w-12 text-center rounded-lg border border-border bg-popover scrollbar-none timepicker-col-hours"></div>
</div>
<span class="text-slate-500 font-bold self-end mb-12">:</span>
<div class="flex flex-col items-center">
<span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider">Min</span>
<div class="h-32 overflow-y-auto w-12 text-center rounded-lg border border-border bg-popover scrollbar-none timepicker-col-minutes"></div>
</div>
<div class="flex flex-col items-center ml-1">
<span class="text-[9px] font-bold text-slate-500 mb-1.5 uppercase tracking-wider">Am/Pm</span>
<div class="flex flex-col gap-1 w-12">
<button type="button" class="timepicker-ampm-btn py-1.5 rounded-lg text-xs font-bold hover:bg-secondary transition text-muted-foreground">AM</button>
<button type="button" class="timepicker-ampm-btn py-1.5 rounded-lg text-xs font-bold hover:bg-secondary transition text-muted-foreground">PM</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro tabs_header_open() %}
<div class="flex border-b border-border">
{% endmacro %}
{% macro tab_trigger(group, target_id, label, is_active=false) %}
<button
type="button"
data-tab-group="{{ group }}"
data-tab-target="{{ target_id }}"
class="px-3 py-1.5 text-xs font-semibold border-b-2 focus:outline-none transition-all
{% if is_active %}border-sky-500 text-sky-400{% else %}border-transparent text-muted-foreground hover:text-slate-200{% endif %}"
>
{{ label }}
</button>
{% endmacro %}
{% macro tabs_header_close() %}
</div>
{% endmacro %}
{% macro tabs_content_open() %}
<div class="p-4 bg-card/50 rounded-xl border border-border text-xs text-muted-foreground min-h-[5rem]">
{% endmacro %}
{% macro tab_pane_open(group, id, is_active=true) %}
<div id="{{ id }}" data-tab-content-group="{{ group }}" {% if !is_active %}class="hidden"{% endif %}>
{% endmacro %}
{% macro tab_pane_close() %}
</div>
{% endmacro %}
{% macro tabs_content_close() %}
</div>
{% endmacro %}
{% macro accordion_open(title, is_open=false) %}
<div class="accordion-item border border-border rounded-xl overflow-hidden bg-card/30">
<button type="button" class="accordion-trigger w-full flex items-center justify-between px-4 py-3 text-xs font-bold text-slate-200 hover:bg-secondary/50 transition focus:outline-none">
<span>{{ title }}</span>
<svg class="accordion-chevron h-3 w-3 text-slate-500 transition-transform duration-200 {% if is_open %}rotate-180{% endif %}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="accordion-content px-4 pb-3 pt-1 text-xs text-muted-foreground {% if !is_open %}hidden{% endif %} border-t border-border leading-relaxed">
{% endmacro %}
{% macro accordion_close() %}
</div>
</div>
{% endmacro %}
+15 -35
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Dialog Modals - Design System Wiki{% endblock %} {% block title %}Dialog Modals - Design System Wiki{% endblock %}
@@ -33,9 +34,7 @@
<!-- Demo Viewport --> <!-- Demo Viewport -->
<div id="modal-sandbox" class="wiki-pane py-2"> <div id="modal-sandbox" class="wiki-pane py-2">
<button data-modal-target="wiki-demo-modal" class="inline-flex items-center justify-center rounded-xl text-xs font-bold bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2.5 transition active:scale-95"> {{ ui::modal_trigger(target_id="wiki-demo-modal", label="Open Modal Dialog", variant="indigo") }}
Open Modal Dialog
</button>
</div> </div>
<!-- Code Snippet Area --> <!-- Code Snippet Area -->
@@ -45,27 +44,18 @@
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code Copy Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- Trigger Button (points to modal element ID) --&gt; <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;button data-modal-target="my-modal-id" class="px-4 py-2 bg-indigo-600 text-white text-xs font-bold rounded-xl"&gt;
Open Modal
&lt;/button&gt;
&lt;!-- Modal Overlay Element --&gt; &lt;!-- Trigger Button (points to modal element ID) --&gt;
&lt;div id="my-modal-id" class="modal-dialog fixed inset-0 z-50 flex items-center justify-center hidden" role="dialog" aria-modal="true"&gt; {{ "{{" }} ui::modal_trigger(target_id="my-modal-id", label="Open Modal Dialog", variant="indigo") {{ "}}" }}
&lt;!-- Backdrop shadow with blur --&gt;
&lt;div class="modal-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm transition-opacity duration-300"&gt;&lt;/div&gt;
&lt;!-- Modal Panel with scale / opacity transition --&gt; &lt;!-- Option A: Simple Modal (Self-contained with text body) --&gt;
&lt;div class="modal-content relative z-10 w-full max-w-sm scale-95 opacity-0 transition-all duration-300 border border-border bg-popover/95 backdrop-blur-xl p-6 shadow-2xl rounded-3xl"&gt; {{ "{{" }} ui::modal(id="my-modal-id", title="Confirmation Title", content="Are you sure you want to proceed?", close_label="Cancel") {{ "}}" }}
&lt;h3 class="text-sm font-bold text-slate-100"&gt;Confirmation Title&lt;/h3&gt;
&lt;p class="text-xs text-muted-foreground mt-2 leading-relaxed"&gt;Are you sure you want to proceed?&lt;/p&gt;
&lt;!-- Close triggers require class 'modal-close' --&gt; &lt;!-- Option B: Paired Macros (For custom markup, inputs, or headers) --&gt;
&lt;button class="modal-close mt-4 w-full py-2 rounded-xl bg-secondary border border-border text-slate-200 text-xs font-semibold"&gt; {{ "{{" }} ui::modal_open(id="my-modal-id", title="Confirmation Title") {{ "}}" }}
Cancel &lt;p class="text-xs text-muted-foreground mt-2 leading-relaxed"&gt;Are you sure you want to proceed?&lt;/p&gt;
&lt;/button&gt; {{ "{{" }} ui::modal_close(close_label="Cancel") {{ "}}" }}</code></pre>
&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div> </div>
</div> </div>
</div> </div>
@@ -136,20 +126,10 @@
</div> </div>
</div> </div>
<!-- Interactive Modal Sandbox Element --> <!-- Interactive Modal Sandbox Element using paired macros -->
<div id="wiki-demo-modal" class="modal-dialog fixed inset-0 z-50 flex items-center justify-center hidden" role="dialog" aria-modal="true"> {{ ui::modal_open(id="wiki-demo-modal", title="Wiki Sandbox Modal") }}
<div class="modal-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm transition-opacity duration-300"></div> <p class="text-xs text-slate-405 mt-2 leading-relaxed">This modal is animated and handled globally by <code>components.js</code>. Pressing escape or clicking outside closes it instantly.</p>
<div class="modal-content relative z-10 w-full max-w-sm scale-95 opacity-0 transition-all duration-300 border border-border bg-popover/95 backdrop-blur-xl p-6 shadow-2xl rounded-3xl text-center"> {{ ui::modal_close(close_label="Dismiss Modal") }}
<div class="mx-auto flex h-10 w-10 items-center justify-center rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 mb-3">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<h3 class="text-sm font-bold text-slate-100">Wiki Sandbox Modal</h3>
<p class="text-xs text-slate-405 mt-2 leading-relaxed">This modal is animated and handled globally by <code>components.js</code>. Pressing escape or clicking outside closes it instantly.</p>
<button class="modal-close mt-4 w-full py-2 rounded-xl bg-secondary border border-border hover:bg-secondary transition text-xs font-semibold text-slate-200">Dismiss Modal</button>
</div>
</div>
<script> <script>
function toggleWikiTabs(btn, paneId) { function toggleWikiTabs(btn, paneId) {
+13 -47
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Slide-over Drawers (Sheets) - Design System Wiki{% endblock %} {% block title %}Slide-over Drawers (Sheets) - Design System Wiki{% endblock %}
@@ -33,9 +34,7 @@
<!-- Demo Viewport --> <!-- Demo Viewport -->
<div id="sheet-sandbox" class="wiki-pane py-2"> <div id="sheet-sandbox" class="wiki-pane py-2">
<button data-sheet-target="wiki-demo-sheet" class="inline-flex items-center justify-center rounded-xl text-xs font-bold bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2.5 transition active:scale-95"> {{ ui::sheet_trigger(target_id="wiki-demo-sheet", label="Open Right Drawer", variant="indigo") }}
Open Right Drawer
</button>
</div> </div>
<!-- Code Snippet Area --> <!-- Code Snippet Area -->
@@ -45,34 +44,15 @@
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code Copy Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- Trigger Button (points to sheet element ID) --&gt; <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;button data-sheet-target="my-sheet-id" class="px-4 py-2 bg-indigo-650 text-white text-xs font-bold rounded-xl"&gt;
Open Drawer
&lt;/button&gt;
&lt;!-- Slide Drawer Sheet Element --&gt; &lt;!-- Trigger Button (points to sheet element ID) --&gt;
&lt;div id="my-sheet-id" class="sheet-dialog fixed inset-0 z-50 overflow-hidden hidden" role="dialog" aria-modal="true"&gt; {{ "{{" }} ui::sheet_trigger(target_id="my-sheet-id", label="Open Drawer", variant="indigo") {{ "}}" }}
&lt;!-- Backdrop --&gt;
&lt;div class="sheet-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm opacity-0 transition-opacity duration-300"&gt;&lt;/div&gt;
&lt;div class="absolute inset-y-0 right-0 max-w-full flex pl-10"&gt; &lt;!-- Slide Drawer Sheet Element using paired macros --&gt;
&lt;!-- Panel with transition translate-x-full --&gt; {{ "{{" }} ui::sheet_open(id="my-sheet-id", title="Settings", max_width_class="max-w-sm") {{ "}}" }}
&lt;div class="sheet-content w-screen max-w-sm translate-x-full transition-transform duration-300 bg-popover/95 border-l border-border p-6 flex flex-col justify-between"&gt; &lt;p class="text-xs text-muted-foreground"&gt;Drawer Body Content&lt;/p&gt;
&lt;div class="space-y-4"&gt; {{ "{{" }} ui::sheet_close(save_label="Save") {{ "}}" }}</code></pre>
&lt;div class="flex items-center justify-between pb-3 border-b border-border"&gt;
&lt;h3 class="text-sm font-bold text-slate-100"&gt;Settings&lt;/h3&gt;
&lt;button class="sheet-close text-slate-500 hover:text-white"&gt;
&lt;svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"&gt;&lt;path d="M6 18L18 6M6 6l12 12"/&gt;&lt;/svg&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;p class="text-xs text-muted-foreground"&gt;Drawer Body Content&lt;/p&gt;
&lt;/div&gt;
&lt;button class="sheet-close w-full py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold rounded-xl"&gt;
Save
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre>
</div> </div>
</div> </div>
</div> </div>
@@ -143,24 +123,10 @@
</div> </div>
</div> </div>
<!-- Interactive Sheet Sandbox Element --> <!-- Interactive Sheet Sandbox Element using paired macros -->
<div id="wiki-demo-sheet" class="sheet-dialog fixed inset-0 z-50 overflow-hidden hidden" role="dialog" aria-modal="true"> {{ ui::sheet_open(id="wiki-demo-sheet", title="Drawer Panel Properties", max_width_class="max-w-sm") }}
<div class="sheet-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm opacity-0 transition-opacity duration-300 animate-fade-in"></div> <p class="text-xs text-slate-405 leading-relaxed">This slide-over panel demonstrates real-time sidebar parameter adjustments, sliding in from the right edge with hardware transitions.</p>
<div class="absolute inset-y-0 right-0 max-w-full flex pl-10"> {{ ui::sheet_close(save_label="Save Properties") }}
<div class="sheet-content w-screen max-w-sm translate-x-full transition-transform duration-300 ease-in-out border-l border-border bg-popover/95 backdrop-blur-xl p-6 shadow-2xl flex flex-col justify-between">
<div class="space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-border">
<h3 class="text-sm font-bold text-slate-100">Drawer Panel Properties</h3>
<button class="sheet-close text-slate-500 hover:text-white rounded-lg p-1.5 hover:bg-secondary transition">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<p class="text-xs text-slate-405 leading-relaxed">This slide-over panel demonstrates real-time sidebar parameter adjustments, sliding in from the right edge with hardware transitions.</p>
</div>
<button class="sheet-close w-full py-2.5 rounded-xl bg-indigo-650 hover:bg-indigo-600 transition text-xs font-bold text-white shadow-lg shadow-indigo-650/10">Save Properties</button>
</div>
</div>
</div>
<script> <script>
function toggleWikiTabs(btn, paneId) { function toggleWikiTabs(btn, paneId) {
+37 -57
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Tabs & Accordions - Design System Wiki{% endblock %} {% block title %}Tabs & Accordions - Design System Wiki{% endblock %}
@@ -33,36 +34,32 @@
<!-- Demo Viewport --> <!-- Demo Viewport -->
<div id="tabs-sandbox" class="wiki-pane space-y-6 max-w-md py-2"> <div id="tabs-sandbox" class="wiki-pane space-y-6 max-w-md py-2">
<!-- Tabs Showcase --> <!-- Tabs Showcase using macros -->
<div class="space-y-2"> <div class="space-y-2">
<span class="block text-xs font-semibold text-muted-foreground">Switch Tabs</span> <span class="block text-xs font-semibold text-muted-foreground">Switch Tabs</span>
<div class="flex border-b border-border"> {{ ui::tabs_header_open() }}
<button type="button" data-tab-group="wiki-tabs" data-tab-target="wiki-pane-1" class="px-4 py-2 text-xs font-semibold border-b-2 border-sky-500 text-sky-400 focus:outline-none">Overview</button> {{ ui::tab_trigger(group="wiki-tabs", target_id="wiki-pane-1", label="Overview", is_active=true) }}
<button type="button" data-tab-group="wiki-tabs" data-tab-target="wiki-pane-2" class="px-4 py-2 text-xs font-semibold border-b-2 border-transparent text-muted-foreground hover:text-slate-250 focus:outline-none">Config Settings</button> {{ ui::tab_trigger(group="wiki-tabs", target_id="wiki-pane-2", label="Config Settings") }}
</div> {{ ui::tabs_header_close() }}
<div class="p-4 bg-card/50 rounded-xl border border-border text-xs text-muted-foreground min-h-[5rem]">
<div id="wiki-pane-1" data-tab-content-group="wiki-tabs"> {{ ui::tabs_content_open() }}
{{ ui::tab_pane_open(group="wiki-tabs", id="wiki-pane-1", is_active=true) }}
Overview parameters content pane. You can place statistics, graphs, or summary tables here. Overview parameters content pane. You can place statistics, graphs, or summary tables here.
</div> {{ ui::tab_pane_close() }}
<div id="wiki-pane-2" data-tab-content-group="wiki-tabs" class="hidden">
{{ ui::tab_pane_open(group="wiki-tabs", id="wiki-pane-2", is_active=false) }}
Settings updates pane. Configuration toggles, environment variables, or webhook URLs live here. Settings updates pane. Configuration toggles, environment variables, or webhook URLs live here.
</div> {{ ui::tab_pane_close() }}
</div> {{ ui::tabs_content_close() }}
</div> </div>
<!-- Accordion Showcase --> <!-- Accordion Showcase using paired macros -->
<div class="space-y-2"> <div class="space-y-2">
<span class="block text-xs font-semibold text-muted-foreground">Accordion Collapsible</span> <span class="block text-xs font-semibold text-muted-foreground">Accordion Collapsible</span>
<div class="space-y-2"> <div class="space-y-2">
<div class="accordion-item border border-border rounded-xl overflow-hidden bg-card/30"> {{ ui::accordion_open(title="Is this library dependency-free?", is_open=false) }}
<button type="button" class="accordion-trigger w-full flex items-center justify-between px-4 py-3 text-xs font-bold text-slate-200 hover:bg-secondary/50 transition focus:outline-none"> Yes! The template does not rely on Alpine.js or React. JavaScript toggles run via vanilla click listeners listening on specific class selectors.
<span>Is this library dependency-free?</span> {{ ui::accordion_close() }}
<svg class="accordion-chevron h-3 w-3 text-slate-500 transition-transform duration-200" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="accordion-content px-4 pb-3 pt-1 text-xs text-muted-foreground hidden border-t border-border leading-relaxed">
Yes! The template does not rely on Alpine.js or React. JavaScript toggles run via vanilla click listeners listening on specific class selectors.
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -74,45 +71,28 @@
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code Copy Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- 1. Tabs Layout Markup --&gt; <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;div class="space-y-2"&gt;
&lt;!-- Tabs Trigger Header --&gt;
&lt;div class="flex border-b border-border"&gt;
&lt;!-- Include class 'border-sky-500 text-sky-400' on the active trigger --&gt;
&lt;button data-tab-group="my-tabs-group" data-tab-target="my-pane-1" class="px-4 py-2 border-b-2 border-sky-500 text-sky-400 text-xs font-semibold"&gt;
Tab 1
&lt;/button&gt;
&lt;button data-tab-group="my-tabs-group" data-tab-target="my-pane-2" class="px-4 py-2 border-b-2 border-transparent text-muted-foreground hover:text-slate-200 text-xs font-semibold"&gt;
Tab 2
&lt;/button&gt;
&lt;/div&gt;
&lt;!-- Content Panes --&gt; &lt;!-- 1. Tabs Layout using paired macros --&gt;
&lt;div class="p-4 bg-card/50 rounded-xl border border-border text-xs"&gt; {{ "{{" }} ui::tabs_header_open() {{ "}}" }}
&lt;div id="my-pane-1" data-tab-content-group="my-tabs-group"&gt; {{ "{{" }} ui::tab_trigger(group="my-tabs-group", target_id="my-pane-1", label="Tab 1", is_active=true) {{ "}}" }}
Overview content... {{ "{{" }} ui::tab_trigger(group="my-tabs-group", target_id="my-pane-2", label="Tab 2") {{ "}}" }}
&lt;/div&gt; {{ "{{" }} ui::tabs_header_close() {{ "}}" }}
&lt;div id="my-pane-2" data-tab-content-group="my-tabs-group" class="hidden"&gt;
Settings content...
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;!-- 2. Accordion Markup --&gt; {{ "{{" }} ui::tabs_content_open() {{ "}}" }}
&lt;div class="accordion-item border border-border rounded-xl bg-card/30 overflow-hidden"&gt; {{ "{{" }} ui::tab_pane_open(group="my-tabs-group", id="my-pane-1", is_active=true) {{ "}}" }}
&lt;!-- Accordion Button Trigger --&gt; Overview content...
&lt;button class="accordion-trigger w-full flex items-center justify-between px-4 py-3 text-xs font-bold text-slate-200 hover:bg-secondary/50"&gt; {{ "{{" }} ui::tab_pane_close() {{ "}}" }}
&lt;span&gt;Section Title&lt;/span&gt;
&lt;svg class="accordion-chevron h-3 w-3 text-slate-500 transition-transform duration-200" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"&gt;
&lt;polyline points="6 9 12 15 18 9"/&gt;
&lt;/svg&gt;
&lt;/button&gt;
&lt;!-- Content Panel (Hidden by default) --&gt; {{ "{{" }} ui::tab_pane_open(group="my-tabs-group", id="my-pane-2", is_active=false) {{ "}}" }}
&lt;div class="accordion-content px-4 pb-3 pt-1 text-xs text-muted-foreground hidden border-t border-border"&gt; Settings content...
Collapsible description content. {{ "{{" }} ui::tab_pane_close() {{ "}}" }}
&lt;/div&gt; {{ "{{" }} ui::tabs_content_close() {{ "}}" }}
&lt;/div&gt;</code></pre>
&lt;!-- 2. Accordion using paired macros --&gt;
{{ "{{" }} ui::accordion_open(title="Section Title", is_open=false) {{ "}}" }}
Collapsible description content.
{{ "{{" }} ui::accordion_close() {{ "}}" }}</code></pre>
</div> </div>
</div> </div>
</div> </div>
+14 -49
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Switches & Checkboxes - Design System Wiki{% endblock %} {% block title %}Switches & Checkboxes - Design System Wiki{% endblock %}
@@ -33,34 +34,16 @@
<!-- Demo Viewport --> <!-- Demo Viewport -->
<div id="toggle-sandbox" class="wiki-pane space-y-5 max-w-xs py-2"> <div id="toggle-sandbox" class="wiki-pane space-y-5 max-w-xs py-2">
<!-- Toggle Switch --> <!-- Toggle Switch using macro -->
<div class="flex items-center justify-between"> {{ ui::toggle_switch(name="demo_toggle", label="Toggle Status", checked=false) }}
<span class="text-xs text-muted-foreground font-medium">Toggle Status</span>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" class="sr-only peer">
<div class="w-9 h-5 bg-secondary rounded-full border border-border peer-checked:bg-indigo-600 peer-checked:border-indigo-500 transition-all duration-300 relative after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-slate-400 after:rounded-full after:h-[14px] after:w-[14px] after:transition-all peer-checked:after:translate-x-4 peer-checked:after:bg-white"></div>
</label>
</div>
<!-- Custom Checkbox --> <!-- Custom Checkbox using macro -->
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<label class="flex items-center gap-3 cursor-pointer group"> {{ ui::checkbox(name="demo_checkbox", label="Enable Email notifications", checked=true) }}
<input type="checkbox" class="sr-only peer" checked>
<div class="w-4 h-4 rounded bg-popover border border-border flex items-center justify-center peer-checked:bg-indigo-600 peer-checked:border-indigo-500 peer-checked:[&_svg]:opacity-100 transition">
<svg class="w-2.5 h-2.5 text-white opacity-0 transition-opacity" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="4"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
</div>
<span class="text-xs text-muted-foreground peer-checked:text-slate-200">Enable Email notifications</span>
</label>
</div> </div>
<!-- Range Slider --> <!-- Range Slider using macro -->
<div class="space-y-2"> {{ ui::range_slider(name="demo_slider", label="Range Slider (0-100%)", min="0", max="100", value="50") }}
<label class="block text-xs font-semibold text-muted-foreground">Range Slider (0-100%)</label>
<div class="flex items-center gap-4">
<input type="range" min="0" max="100" value="50" class="grow h-1 bg-secondary rounded-lg appearance-none cursor-pointer accent-indigo-600" oninput="this.nextElementSibling.textContent = this.value + '%'">
<span class="text-xs font-mono font-bold text-sky-400 w-10 text-right">50%</span>
</div>
</div>
</div> </div>
<!-- Code Snippet Area --> <!-- Code Snippet Area -->
@@ -70,34 +53,16 @@
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg> <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 002 2h2a2 2 0 002-2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>
Copy Code Copy Code
</button> </button>
<pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>&lt;!-- 1. Toggle Switch Variant --&gt; <pre class="bg-popover p-4 rounded-xl border border-border overflow-x-auto text-[10px] text-sky-400 font-mono"><code>{{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
&lt;label class="relative inline-flex items-center cursor-pointer"&gt;
&lt;!-- sr-only: visually hides native input; peer: lets siblings styled with 'peer-checked:' react to its state --&gt; &lt;!-- 1. Toggle Switch Variant --&gt;
&lt;input type="checkbox" class="sr-only peer"&gt; {{ "{{" }} ui::toggle_switch(name="demo_toggle", label="Toggle Status", checked=false) {{ "}}" }}
&lt;!-- bg-secondary: base slider bg; peer-checked:bg-indigo-600: checked slider bg;
after: absolute round knob dot; peer-checked:after:translate-x-4: moves knob when checked --&gt;
&lt;div class="w-9 h-5 bg-secondary rounded-full border border-border peer-checked:bg-indigo-600 peer-checked:border-indigo-500 transition-all duration-300 relative after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-slate-400 after:rounded-full after:h-[14px] after:w-[14px] after:transition-all peer-checked:after:translate-x-4 peer-checked:after:bg-white"&gt;&lt;/div&gt;
&lt;/label&gt;
&lt;!-- 2. Custom Checkbox --&gt; &lt;!-- 2. Custom Checkbox --&gt;
&lt;label class="flex items-center gap-3 cursor-pointer group"&gt; {{ "{{" }} ui::checkbox(name="demo_checkbox", label="Enable Email notifications", checked=true) {{ "}}" }}
&lt;!-- sr-only peer hides checkbox input globally but exposes its state --&gt;
&lt;input type="checkbox" class="sr-only peer"&gt;
&lt;!-- peer-checked:[&amp;_svg]:opacity-100: uses a descendant selector to show the checkmark when checked --&gt;
&lt;div class="w-4 h-4 rounded bg-popover border border-border flex items-center justify-center peer-checked:bg-indigo-600 peer-checked:border-indigo-500 peer-checked:[&amp;_svg]:opacity-100 transition"&gt;
&lt;svg class="w-2.5 h-2.5 text-white opacity-0 transition-opacity" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="4"&gt;
&lt;path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;span class="text-xs text-muted-foreground peer-checked:text-slate-200 select-none"&gt;Label Option&lt;/span&gt;
&lt;/label&gt;
&lt;!-- 3. Custom Range Slider (accent-indigo-600 styles input thumb in modern browsers) --&gt; &lt;!-- 3. Custom Range Slider --&gt;
&lt;div class="flex items-center gap-4"&gt; {{ "{{" }} ui::range_slider(name="demo_slider", label="Range Slider (0-100%)", min="0", max="100", value="50") {{ "}}" }}</code></pre>
&lt;!-- oninput: updates text element sibling content to display slider value --&gt;
&lt;input type="range" min="0" max="100" value="50" class="grow h-1 bg-secondary rounded-lg appearance-none cursor-pointer accent-indigo-600" oninput="this.nextElementSibling.textContent = this.value + '%'"&gt;
&lt;span class="text-xs font-mono font-bold text-sky-400 w-10 text-right"&gt;50%&lt;/span&gt;
&lt;/div&gt;</code></pre>
</div> </div>
</div> </div>
</div> </div>
+5 -15
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Edit Developer - Stick{% endblock %} {% block title %}Edit Developer - Stick{% endblock %}
@@ -13,28 +14,17 @@
</div> </div>
<form action="/developers/{{ developer.id.unwrap().to_hex() }}/edit" method="post" class="space-y-5"> <form action="/developers/{{ developer.id.unwrap().to_hex() }}/edit" method="post" class="space-y-5">
<div> {{ ui::text_input(id="name", name="name", label="Name", type="text", placeholder="Name", value=developer.name, required=true) }}
<label for="name" class="block text-sm font-medium text-slate-400 mb-1.5">Name</label>
<input id="name" name="name" type="text" value="{{ developer.name }}" required class="appearance-none rounded-xl relative block w-full px-4 py-3 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm">
</div>
<div> {{ ui::text_input(id="email", name="email", label="Email", type="email", placeholder="Email", value=developer.email, required=true) }}
<label for="email" class="block text-sm font-medium text-slate-400 mb-1.5">Email</label>
<input id="email" name="email" type="email" value="{{ developer.email }}" required class="appearance-none rounded-xl relative block w-full px-4 py-3 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm">
</div>
<div> {{ ui::text_input(id="skills", name="skills", label="Skills (Comma-separated)", type="text", placeholder="Skills", value=developer.skills.join(", ")) }}
<label for="skills" class="block text-sm font-medium text-slate-400 mb-1.5">Skills (Comma-separated)</label>
<input id="skills" name="skills" type="text" value='{{ developer.skills.join(", ") }}' class="appearance-none rounded-xl relative block w-full px-4 py-3 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm">
</div>
<div class="flex gap-4 pt-2"> <div class="flex gap-4 pt-2">
<a href="/developers" class="flex-1 py-3 px-4 text-center text-sm font-semibold rounded-xl text-slate-300 bg-slate-900 border border-slate-800 hover:border-slate-700 transition"> <a href="/developers" class="flex-1 py-3 px-4 text-center text-sm font-semibold rounded-xl text-slate-300 bg-slate-900 border border-slate-800 hover:border-slate-700 transition">
Cancel Cancel
</a> </a>
<button type="submit" class="flex-1 py-3 px-4 text-sm font-semibold rounded-xl text-white bg-gradient-to-r from-sky-500 to-indigo-600 hover:opacity-95 transition shadow-lg shadow-sky-500/10"> {{ ui::button(label="Save Changes", variant="indigo", type="submit", extra_class="flex-1 py-3 bg-gradient-to-r from-sky-500 to-indigo-600 hover:opacity-95 transition shadow-lg shadow-sky-500/10") }}
Save Changes
</button>
</div> </div>
</form> </form>
</div> </div>
+5 -15
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Developers - Stick{% endblock %} {% block title %}Developers - Stick{% endblock %}
@@ -30,24 +31,13 @@
Add Developer Add Developer
</h3> </h3>
<form action="/developers" method="post" class="space-y-4"> <form action="/developers" method="post" class="space-y-4">
<div> {{ ui::text_input(id="name", name="name", label="Name", type="text", placeholder="e.g. Alice Smith", required=true) }}
<label for="name" class="block text-xs font-semibold text-slate-400 mb-1.5">Name</label>
<input id="name" name="name" type="text" required class="appearance-none rounded-xl relative block w-full px-4 py-2.5 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm" placeholder="e.g. Alice Smith">
</div>
<div> {{ ui::text_input(id="email", name="email", label="Email", type="email", placeholder="e.g. alice@company.com", required=true) }}
<label for="email" class="block text-xs font-semibold text-slate-400 mb-1.5">Email</label>
<input id="email" name="email" type="email" required class="appearance-none rounded-xl relative block w-full px-4 py-2.5 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm" placeholder="e.g. alice@company.com">
</div>
<div> {{ ui::text_input(id="skills", name="skills", label="Skills (Comma-separated)", type="text", placeholder="e.g. Rust, Axum, MongoDB") }}
<label for="skills" class="block text-xs font-semibold text-slate-400 mb-1.5">Skills (Comma-separated)</label>
<input id="skills" name="skills" type="text" class="appearance-none rounded-xl relative block w-full px-4 py-2.5 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm" placeholder="e.g. Rust, Axum, MongoDB">
</div>
<button type="submit" class="w-full py-3 px-4 text-sm font-semibold rounded-xl text-white bg-gradient-to-r from-sky-500 to-indigo-600 hover:opacity-95 transition shadow-md shadow-sky-500/10"> {{ ui::button(label="Create Developer", variant="indigo", type="submit", extra_class="w-full py-3 bg-gradient-to-r from-sky-500 to-indigo-600 hover:opacity-95 transition shadow-md shadow-sky-500/10") }}
Create Developer
</button>
</form> </form>
</div> </div>
</div> </div>
+14 -49
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "components/macros.html" as ui %}
{% block title %}Tasks Dashboard - Stick{% endblock %} {% block title %}Tasks Dashboard - Stick{% endblock %}
@@ -15,6 +16,8 @@
<span class="text-xs font-semibold px-3 py-1.5 rounded-xl bg-slate-900 border border-slate-800 text-slate-300"> <span class="text-xs font-semibold px-3 py-1.5 rounded-xl bg-slate-900 border border-slate-800 text-slate-300">
Total Tasks: {{ tasks.len() }} Total Tasks: {{ tasks.len() }}
</span> </span>
<!-- Info trigger button -->
{{ ui::modal_trigger(target_id="task-info-modal", label="<svg class='w-4 h-4' fill='none' stroke='currentColor' viewBox='0 0 24 24'><path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z'/></svg>", variant="outline", extra_class="px-2.5 py-2.5") }}
</div> </div>
</div> </div>
@@ -30,58 +33,17 @@
New Task New Task
</h3> </h3>
<form action="/tasks/create" method="post" class="space-y-4"> <form action="/tasks/create" method="post" class="space-y-4">
<div> <!-- Task Title Input using macro -->
<label for="title" class="block text-xs font-semibold text-slate-400 mb-1.5">Title</label> {{ ui::text_input(id="title", name="title", label="Title", type="text", placeholder="Task name", required=true) }}
<input id="title" name="title" type="text" required class="appearance-none rounded-xl relative block w-full px-4 py-2.5 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm" placeholder="Task name">
</div>
<div> <!-- Task Description TextArea using macro -->
<label for="description" class="block text-xs font-semibold text-slate-400 mb-1.5">Description (Optional)</label> {{ ui::textarea(id="description", name="description", label="Description (Optional)", placeholder="Add some context...") }}
<textarea id="description" name="description" rows="3" class="appearance-none rounded-xl relative block w-full px-4 py-2.5 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm" placeholder="Add some context..."></textarea>
</div>
<!-- Interactive Assignee Search --> <!-- Interactive Assignee Search using macro -->
<div class="autocomplete-combobox relative"> {{ ui::search_combobox(name="assignee_id", label="Assignee (Optional)", placeholder="Search developer...", search_url="/developers/search", input_id="assignee-search", value_id="assignee-id") }}
<label class="block text-xs font-semibold text-slate-400 mb-1.5">Assignee (Optional)</label>
<!-- Hidden input holding the actual developer ID to submit --> <!-- Create Task submit button using macro -->
<input type="hidden" id="assignee-id" name="assignee_id" class="combobox-value"> {{ ui::button(label="Create Task", variant="indigo", type="submit", extra_class="w-full py-3 bg-gradient-to-r from-sky-500 to-indigo-600 hover:opacity-95 transition shadow-md shadow-sky-500/10") }}
<div class="relative">
<input type="text"
id="assignee-search"
name="q"
placeholder="Search developer..."
autocomplete="off"
hx-get="/developers/search"
hx-trigger="input changed delay:250ms, search"
hx-target="next .combobox-results"
hx-indicator="next .combobox-indicator"
class="combobox-input appearance-none rounded-xl relative block w-full pl-9 pr-4 py-2.5 bg-[#0f172a]/80 border border-slate-800 placeholder-slate-500 text-white focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition duration-200 text-sm">
<!-- Search Icon & Loading Indicator -->
<div class="absolute left-3 top-3 text-slate-500">
<svg class="combobox-indicator htmx-indicator animate-spin h-4 w-4 text-sky-500 hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
<!-- Search Results Dropdown Popover -->
<div id="search-results"
class="combobox-results absolute z-10 w-full mt-1.5 bg-slate-900 border border-slate-800 rounded-xl shadow-2xl overflow-hidden hidden">
</div>
</div>
<button type="submit" class="w-full py-3 px-4 text-sm font-semibold rounded-xl text-white bg-gradient-to-r from-sky-500 to-indigo-600 hover:opacity-95 transition shadow-md shadow-sky-500/10">
Create Task
</button>
</form> </form>
</div> </div>
</div> </div>
@@ -166,4 +128,7 @@
</div> </div>
</div> </div>
<!-- Reusable Task Info Modal defined via macro -->
{{ ui::modal(id="task-info-modal", title="Tasks Guide", content="Welcome to the task board! You can add new tasks, assign them to registered developers, mark them complete, or delete them. This board is dynamic and updates automatically.", close_label="Got it") }}
{% endblock %} {% endblock %}