Added docs
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# Button
|
||||
|
||||
A styled button component with variant and size presets built from Tailwind utility classes.
|
||||
|
||||
---
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```razor
|
||||
<Button>Default Button</Button>
|
||||
<Button Type="submit">Submit</Button>
|
||||
<Button Disabled="true">Can't Click</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variants
|
||||
|
||||
Use the `Variant` parameter with constants from `ButtonVariant`:
|
||||
|
||||
```razor
|
||||
<Button Variant="@ButtonVariant.Default">Default</Button>
|
||||
<Button Variant="@ButtonVariant.Secondary">Secondary</Button>
|
||||
<Button Variant="@ButtonVariant.Destructive">Destructive</Button>
|
||||
<Button Variant="@ButtonVariant.Outline">Outline</Button>
|
||||
<Button Variant="@ButtonVariant.Ghost">Ghost</Button>
|
||||
<Button Variant="@ButtonVariant.Link">Link</Button>
|
||||
```
|
||||
|
||||
| Constant | Tailwind Classes |
|
||||
|---|---|
|
||||
| `ButtonVariant.Default` | `bg-primary text-primary-foreground shadow hover:bg-primary/90` |
|
||||
| `ButtonVariant.Destructive` | `bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90` |
|
||||
| `ButtonVariant.Outline` | `border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground` |
|
||||
| `ButtonVariant.Secondary` | `bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80` |
|
||||
| `ButtonVariant.Ghost` | `hover:bg-accent hover:text-accent-foreground` |
|
||||
| `ButtonVariant.Link` | `text-primary underline-offset-4 hover:underline` |
|
||||
|
||||
You can also pass any custom Tailwind class string directly:
|
||||
|
||||
```razor
|
||||
<Button Variant="bg-blue-600 text-white hover:bg-blue-700">Custom</Button>
|
||||
```
|
||||
|
||||
### Creating Custom Variants
|
||||
|
||||
Since `Variant` and `Size` are plain strings (not enums), you can create your own variant constants without modifying the library. Define a static class in your app with any Tailwind utility combinations you need:
|
||||
|
||||
```csharp
|
||||
public static class AppButtonVariant
|
||||
{
|
||||
public const string Success =
|
||||
"bg-green-600 text-white shadow-sm hover:bg-green-700";
|
||||
|
||||
public const string Warning =
|
||||
"bg-amber-500 text-white shadow-sm hover:bg-amber-600";
|
||||
|
||||
public const string Info =
|
||||
"bg-sky-500 text-white shadow-sm hover:bg-sky-600";
|
||||
|
||||
public const string OutlineDestructive =
|
||||
"border border-destructive text-destructive bg-transparent shadow-sm hover:bg-destructive/10";
|
||||
|
||||
public const string Gradient =
|
||||
"bg-gradient-to-r from-purple-500 to-pink-500 text-white shadow-sm hover:from-purple-600 hover:to-pink-600";
|
||||
}
|
||||
```
|
||||
|
||||
Then use them exactly like the built-in variants:
|
||||
|
||||
```razor
|
||||
<Button Variant="@AppButtonVariant.Success">Save Changes</Button>
|
||||
<Button Variant="@AppButtonVariant.Warning">Proceed with Caution</Button>
|
||||
<Button Variant="@AppButtonVariant.Gradient">Upgrade Plan</Button>
|
||||
```
|
||||
|
||||
You can do the same for custom sizes:
|
||||
|
||||
```csharp
|
||||
public static class AppButtonSize
|
||||
{
|
||||
public const string Xs = "h-7 rounded-md px-2 text-xs";
|
||||
public const string Xl = "h-12 rounded-lg px-10 text-base";
|
||||
public const string Wide = "h-9 px-12 py-2";
|
||||
}
|
||||
```
|
||||
|
||||
```razor
|
||||
<Button Size="@AppButtonSize.Xl" Variant="@AppButtonVariant.Gradient">
|
||||
Get Started
|
||||
</Button>
|
||||
```
|
||||
|
||||
This approach works because the `Button` component simply concatenates the variant and size strings into the element's `class` attribute — there is no closed set of allowed values.
|
||||
|
||||
---
|
||||
|
||||
## Sizes
|
||||
|
||||
Use the `Size` parameter with constants from `ButtonSize`:
|
||||
|
||||
```razor
|
||||
<Button Size="@ButtonSize.Sm">Small</Button>
|
||||
<Button Size="@ButtonSize.Default">Default</Button>
|
||||
<Button Size="@ButtonSize.Lg">Large</Button>
|
||||
<Button Size="@ButtonSize.Icon">🔔</Button>
|
||||
```
|
||||
|
||||
| Constant | Tailwind Classes |
|
||||
|---|---|
|
||||
| `ButtonSize.Default` | `h-9 px-4 py-2` |
|
||||
| `ButtonSize.Sm` | `h-8 rounded-md px-3 text-xs` |
|
||||
| `ButtonSize.Lg` | `h-10 rounded-md px-8` |
|
||||
| `ButtonSize.Icon` | `h-9 w-9` |
|
||||
|
||||
---
|
||||
|
||||
## Button with Icon
|
||||
|
||||
Use the `Icon` render fragment to prepend an icon:
|
||||
|
||||
```razor
|
||||
<Button>
|
||||
<Icon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14" /><path d="m12 5 7 7-7 7" />
|
||||
</svg>
|
||||
</Icon>
|
||||
<ChildContent>Continue</ChildContent>
|
||||
</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `ChildContent` | `RenderFragment?` | — | Button label content |
|
||||
| `Icon` | `RenderFragment?` | — | Icon slot rendered before the label |
|
||||
| `Type` | `string` | `"button"` | HTML button type (`button`, `submit`, `reset`) |
|
||||
| `Disabled` | `bool` | `false` | Disables the button |
|
||||
| `Variant` | `string` | `ButtonVariant.Default` | Visual style classes |
|
||||
| `Size` | `string` | `ButtonSize.Default` | Size classes |
|
||||
| `Class` | `string?` | — | Additional CSS classes appended |
|
||||
|
||||
All unmatched HTML attributes (`data-testid`, `aria-*`, etc.) are passed through via `@attributes`.
|
||||
@@ -0,0 +1,103 @@
|
||||
# Card
|
||||
|
||||
A composable card component suite for displaying grouped content in a bordered container with optional header, footer, image, and action slots.
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
| Component | Description |
|
||||
|---|---|
|
||||
| `Card` | Root container with border, shadow, and rounded corners |
|
||||
| `CardHeader` | Top section — contains title, description, and optional action |
|
||||
| `CardTitle` | `<h3>` heading styled for cards |
|
||||
| `CardDescription` | Muted paragraph below the title |
|
||||
| `CardAction` | Trailing action element (button/link) aligned to the header's right edge |
|
||||
| `CardContent` | Main body area |
|
||||
| `CardFooter` | Bottom area for buttons or metadata |
|
||||
| `CardImage` | Full-width image with optional wrapper styling |
|
||||
|
||||
---
|
||||
|
||||
## Basic Card
|
||||
|
||||
```razor
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Notifications</CardTitle>
|
||||
<CardDescription>You have 3 unread messages.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Here is the main content of the card.</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>View All</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Card with Action
|
||||
|
||||
The `CardAction` renders at the trailing edge of the header using CSS grid:
|
||||
|
||||
```razor
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Team Members</CardTitle>
|
||||
<CardDescription>Manage your team.</CardDescription>
|
||||
<CardAction>
|
||||
<Button Variant="@ButtonVariant.Outline" Size="@ButtonSize.Sm">
|
||||
Add Member
|
||||
</Button>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<!-- member list -->
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Card with Image
|
||||
|
||||
```razor
|
||||
<Card Class="max-w-sm">
|
||||
<CardImage Src="/images/hero.jpg" Alt="Hero image" WrapperClass="h-48" />
|
||||
<CardHeader>
|
||||
<CardTitle>Beautiful Scenery</CardTitle>
|
||||
<CardDescription>A mountain landscape at sunset.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>The image fills the card width and crops via object-cover.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
### Card
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `ChildContent` | `RenderFragment?` | — | Card inner content |
|
||||
| `Class` | `string?` | — | Additional CSS classes |
|
||||
|
||||
Base classes: `rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden`
|
||||
|
||||
### CardImage
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Src` | `string` | **required** | Image source URL |
|
||||
| `Alt` | `string` | `""` | Alt text for accessibility |
|
||||
| `Class` | `string?` | — | Additional classes on the `<img>` |
|
||||
| `WrapperClass` | `string?` | — | Additional classes on the wrapper `<div>` |
|
||||
|
||||
### CardTitle, CardDescription, CardContent, CardFooter, CardAction
|
||||
|
||||
All accept `ChildContent` and `Class` parameters. All support unmatched HTML attributes via `@attributes`.
|
||||
@@ -0,0 +1,192 @@
|
||||
# Form Inputs
|
||||
|
||||
All input components extend a shared `InputBase<T>` class that provides consistent styling, htmx validation integration, and parameter unification. When placed inside an `HtmxForm` with a `FormField`, inputs automatically attach `hx-post`, `hx-trigger="blur"`, and `hx-target` attributes for real-time per-field validation.
|
||||
|
||||
---
|
||||
|
||||
## TextInput
|
||||
|
||||
A standard text input for strings. Supports all HTML input types (`text`, `email`, `password`, `search`, etc.).
|
||||
|
||||
```razor
|
||||
<FormField Label="Full Name" For="name">
|
||||
<TextInput Id="name" Name="name" Placeholder="Jane Doe" />
|
||||
</FormField>
|
||||
|
||||
<FormField Label="Email" For="email">
|
||||
<TextInput Id="email" Name="email" Type="email" Placeholder="jane@example.com" />
|
||||
</FormField>
|
||||
|
||||
<FormField Label="Password" For="password">
|
||||
<TextInput Id="password" Name="password" Type="password" Placeholder="••••••••" />
|
||||
</FormField>
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Type` | `string` | `"text"` | HTML input type |
|
||||
| `Id` | `string?` | — | Input element ID |
|
||||
| `Name` | `string?` | — | Form field name (submitted to the server) |
|
||||
| `Value` | `string?` | — | Current value |
|
||||
| `Placeholder` | `string?` | — | Placeholder text |
|
||||
| `Disabled` | `bool` | `false` | Disables the input |
|
||||
| `ReadOnly` | `bool` | `false` | Makes the input read-only |
|
||||
| `Class` | `string?` | — | Additional CSS classes |
|
||||
|
||||
---
|
||||
|
||||
## NumberInput
|
||||
|
||||
A numeric input with built-in increment/decrement stepper buttons. Hides the browser's native spinner.
|
||||
|
||||
```razor
|
||||
<FormField Label="Age" For="age">
|
||||
<NumberInput Id="age" Name="age" Placeholder="25" Min="0" Max="150" />
|
||||
</FormField>
|
||||
|
||||
<FormField Label="Quantity" For="quantity">
|
||||
<NumberInput Id="quantity" Name="quantity" Value="1" Min="1" Max="99" Step="1" />
|
||||
</FormField>
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Min` | `string?` | — | Minimum allowed value |
|
||||
| `Max` | `string?` | — | Maximum allowed value |
|
||||
| `Step` | `string?` | — | Step increment |
|
||||
| `Value` | `double?` | — | Current numeric value |
|
||||
|
||||
Inherits all parameters from `InputBase<double?>` (`Id`, `Name`, `Placeholder`, `Disabled`, `ReadOnly`, `Class`).
|
||||
|
||||
The stepper buttons are disabled when the value reaches `Min` or `Max`. They are hidden when `Disabled` or `ReadOnly` is true.
|
||||
|
||||
---
|
||||
|
||||
## DateInput
|
||||
|
||||
A date picker that combines a hidden `<input type="date">` with a popover calendar. Users select dates through the calendar UI — the native date picker chrome is hidden.
|
||||
|
||||
```razor
|
||||
<FormField Label="Birth Date" For="birthdate">
|
||||
<DateInput Id="birthdate" Name="birthdate" Placeholder="Select your birth date" />
|
||||
</FormField>
|
||||
```
|
||||
|
||||
### How it works
|
||||
|
||||
1. A hidden `<input type="date">` holds the actual form value in `yyyy-MM-dd` format
|
||||
2. A styled button trigger shows the selected date (or placeholder)
|
||||
3. Clicking the trigger opens a `Popover` containing a `Calendar` component
|
||||
4. Selecting a day updates the hidden input and closes the popover
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Value` | `DateOnly?` | — | Currently selected date |
|
||||
| `Min` | `string?` | — | Minimum date |
|
||||
| `Max` | `string?` | — | Maximum date |
|
||||
| `Placeholder` | `string?` | `"Select date"` | Trigger button placeholder |
|
||||
|
||||
Inherits all parameters from `InputBase<DateOnly?>`.
|
||||
|
||||
---
|
||||
|
||||
## TimeInput
|
||||
|
||||
A time picker that combines a hidden `<input type="time">` with a popover time picker. Features scrollable hour/minute columns and AM/PM toggle.
|
||||
|
||||
```razor
|
||||
<FormField Label="Preferred Time" For="preferredtime">
|
||||
<TimeInput Id="preferredtime" Name="preferredtime" />
|
||||
</FormField>
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Value` | `TimeOnly?` | — | Currently selected time |
|
||||
| `Step` | `string?` | — | Minute step interval |
|
||||
| `Use12Hour` | `bool` | `true` | 12-hour format with AM/PM |
|
||||
| `Placeholder` | `string?` | `"Select time"` | Trigger button placeholder |
|
||||
|
||||
Inherits all parameters from `InputBase<TimeOnly?>`.
|
||||
|
||||
---
|
||||
|
||||
## DateTimeInput
|
||||
|
||||
Combines a date picker and time picker side by side for selecting both date and time. Internally manages a hidden `<input type="datetime-local">` plus two helper hidden inputs for the date and time parts.
|
||||
|
||||
```razor
|
||||
<FormField Label="Appointment" For="appointment">
|
||||
<DateTimeInput Id="appointment" Name="appointment" />
|
||||
</FormField>
|
||||
```
|
||||
|
||||
### How it works
|
||||
|
||||
1. A hidden `<input type="datetime-local">` holds the combined value in `yyyy-MM-ddTHH:mm` format
|
||||
2. Two helper hidden inputs hold the date-part and time-part separately
|
||||
3. Two popover triggers (calendar + time picker) are displayed side by side
|
||||
4. The `forms.js` module automatically combines the date-part and time-part values into the main hidden input
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Value` | `DateTime?` | — | Currently selected date and time |
|
||||
| `Min` | `string?` | — | Minimum datetime |
|
||||
| `Max` | `string?` | — | Maximum datetime |
|
||||
| `Step` | `string?` | — | Minute step interval |
|
||||
| `Placeholder` | `string?` | `"Select date"` | Date trigger placeholder |
|
||||
|
||||
Inherits all parameters from `InputBase<DateTime?>`.
|
||||
|
||||
---
|
||||
|
||||
## FormField
|
||||
|
||||
Wraps an input with a label, description, and error placeholder. The error element is used by htmx to display server-side validation messages.
|
||||
|
||||
```razor
|
||||
<FormField Label="Email" For="email" Description="We'll never share your email.">
|
||||
<TextInput Id="email" Name="email" Type="email" />
|
||||
</FormField>
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Label` | `string?` | — | Label text rendered as `<label>` |
|
||||
| `For` | `string?` | — | Links the label to the input, and identifies the field for validation errors |
|
||||
| `Description` | `string?` | — | Helper text below the input |
|
||||
| `Error` | `string?` | — | Pre-set error message (for server-rendered errors) |
|
||||
| `Class` | `string?` | — | Additional CSS classes on the wrapper |
|
||||
| `LabelClass` | `string?` | — | Additional CSS classes on the label |
|
||||
|
||||
### How validation errors are displayed
|
||||
|
||||
`FormField` renders a `<p data-field-error="fieldname">` element. When htmx receives a validation response, it swaps this element with the server's HTML fragment (which includes or hides the error message). This is the core mechanism for per-field validation.
|
||||
|
||||
---
|
||||
|
||||
## InputBase
|
||||
|
||||
All input components inherit from `InputBase<TValue>`, which provides:
|
||||
|
||||
- **Common parameters**: `Id`, `Name`, `Value`, `Placeholder`, `Disabled`, `ReadOnly`, `Class`
|
||||
- **Computed CSS**: A consistent input style using Tailwind classes
|
||||
- **htmx auto-wiring**: When `ValidationEndpoint` and `FieldName` cascading values are present (provided by `HtmxForm` and `FormField`), the input automatically gets:
|
||||
- `hx-post` pointing to the validation endpoint
|
||||
- `hx-trigger="blur"` for on-blur validation
|
||||
- `hx-target="next [data-field-error]"` to update the error element
|
||||
- `hx-swap="outerHTML"` for full element replacement
|
||||
- `hx-include="this"` to send only this input's value
|
||||
- `hx-vals='{"_field": "fieldname"}'` to identify which field is being validated
|
||||
@@ -0,0 +1,139 @@
|
||||
# Sidebar
|
||||
|
||||
A collapsible, responsive sidebar layout system. Persists expand/collapse state via cookies and adapts between desktop (collapsible rail) and mobile (overlay drawer) modes automatically.
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
| Component | Description |
|
||||
|---|---|
|
||||
| `SidebarProvider` | Root wrapper — provides collapse state context |
|
||||
| `Sidebar` | The sidebar panel itself |
|
||||
| `SidebarHeader` | Top area (logo, app name) — also acts as a collapse trigger |
|
||||
| `SidebarContent` | Scrollable middle area for navigation groups |
|
||||
| `SidebarFooter` | Bottom area (copyright, user info) |
|
||||
| `SidebarGroup` | Groups related menu items |
|
||||
| `SidebarGroupLabel` | Section heading within a group |
|
||||
| `SidebarGroupContent` | Container for menu items within a group |
|
||||
| `SidebarMenuItem` | Navigation link with icon support |
|
||||
| `SidebarInset` | Main content area adjacent to the sidebar |
|
||||
| `SidebarSeparator` | Horizontal divider line |
|
||||
| `SidebarTrigger` | Standalone toggle button (for mobile header bars) |
|
||||
|
||||
---
|
||||
|
||||
## Basic Layout
|
||||
|
||||
```razor
|
||||
<SidebarProvider DefaultOpen="true">
|
||||
<Sidebar>
|
||||
<SidebarHeader>
|
||||
<div class="flex items-center gap-2 px-1 py-1.5">
|
||||
<img src="logo.svg" alt="Logo" class="size-8" />
|
||||
<span class="font-semibold text-sm">My App</span>
|
||||
</div>
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel Label="Navigation" />
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenuItem Href="/" Tooltip="Home" IsActive="true">
|
||||
<Icon>
|
||||
<!-- SVG icon -->
|
||||
</Icon>
|
||||
<ChildContent>Home</ChildContent>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem Href="/about" Tooltip="About">
|
||||
<Icon>
|
||||
<!-- SVG icon -->
|
||||
</Icon>
|
||||
<ChildContent>About</ChildContent>
|
||||
</SidebarMenuItem>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
<SidebarSeparator />
|
||||
<div class="px-3 py-2 text-xs text-sidebar-foreground/50">
|
||||
© 2026 My Company
|
||||
</div>
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
|
||||
<SidebarInset>
|
||||
<header class="flex h-14 items-center gap-2 border-b border-border px-4">
|
||||
<SidebarTrigger Class="md:hidden" />
|
||||
<h1 class="text-sm font-medium">My App</h1>
|
||||
<div class="ml-auto">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 p-4 md:p-6">
|
||||
@Body
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
### SidebarProvider
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `DefaultOpen` | `bool` | `true` | Initial sidebar state on first visit |
|
||||
| `ChildContent` | `RenderFragment` | — | Must contain `Sidebar` + `SidebarInset` |
|
||||
|
||||
### SidebarMenuItem
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Href` | `string?` | — | Navigation URL |
|
||||
| `Tooltip` | `string?` | — | Tooltip text (shown on hover when collapsed) |
|
||||
| `IsActive` | `bool` | `false` | Highlights the item with accent styling |
|
||||
| `Icon` | `RenderFragment?` | — | Icon slot (typically an SVG) |
|
||||
| `Class` | `string?` | — | Additional CSS classes |
|
||||
|
||||
### SidebarGroupLabel
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Label` | `string?` | — | Label text (alternative to ChildContent) |
|
||||
| `ChildContent` | `RenderFragment?` | — | Custom label markup |
|
||||
| `Class` | `string?` | — | Additional CSS classes |
|
||||
|
||||
### All sidebar components
|
||||
|
||||
Every sidebar component accepts:
|
||||
- `ChildContent` — slot for nested content
|
||||
- `Class` — additional CSS classes to append
|
||||
|
||||
---
|
||||
|
||||
## Behavior
|
||||
|
||||
### Desktop (≥ 768px)
|
||||
- Clicking the **SidebarHeader** or **SidebarTrigger** toggles between expanded and collapsed (icon rail) states
|
||||
- Collapsed state shrinks to `var(--sidebar-width-icon)` (3rem) showing only icons
|
||||
- State persists via a `sidebar:state` cookie (1 year)
|
||||
|
||||
### Mobile (< 768px)
|
||||
- Sidebar renders as an off-screen drawer
|
||||
- Opens with a semi-transparent backdrop overlay
|
||||
- Clicking the overlay or trigger closes it
|
||||
- Navigation link clicks auto-close the sidebar
|
||||
|
||||
### CSS Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `--sidebar-width` | `16rem` | Expanded sidebar width |
|
||||
| `--sidebar-width-icon` | `3rem` | Collapsed (icon rail) width |
|
||||
|
||||
These can be overridden in your `@theme` block.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Theme Toggle
|
||||
|
||||
A dark/light mode toggle button that persists the user's preference to `localStorage` and applies it instantly without a page reload.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```razor
|
||||
<ThemeToggle />
|
||||
```
|
||||
|
||||
Place it anywhere in your layout — typically in a header or toolbar:
|
||||
|
||||
```razor
|
||||
<header class="flex h-14 items-center px-4">
|
||||
<h1 class="text-sm font-medium">My App</h1>
|
||||
<div class="ml-auto">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `Class` | `string?` | — | Additional CSS classes appended to the button |
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Toggle button** — renders a `<button>` with moon (light mode) and sun (dark mode) SVG icons
|
||||
2. **Click handler** — `darkmode.js` toggles the `dark` class on `<html>` and persists to `localStorage`
|
||||
3. **Icon sync** — shows the appropriate icon based on the current theme
|
||||
4. **FOUC prevention** — a synchronous inline script in `App.razor` checks `localStorage` before first paint
|
||||
|
||||
### Required Setup in `App.razor`
|
||||
|
||||
Add this script in the `<head>` before any stylesheets:
|
||||
|
||||
```html
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
if (localStorage.getItem('theme') === 'dark')
|
||||
document.documentElement.classList.add('dark');
|
||||
} catch (e) { }
|
||||
})();
|
||||
</script>
|
||||
```
|
||||
|
||||
And initialize the module in the `<body>`:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { init as initDarkMode } from '/_content/Enciphered.Blazor.UIComponents/js/darkmode.js';
|
||||
initDarkMode();
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS Custom Variant
|
||||
|
||||
The library uses Tailwind CSS v4's custom variant for dark mode:
|
||||
|
||||
```css
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
```
|
||||
|
||||
This means dark mode is class-based (`.dark` on `<html>`) rather than media-query-based, giving users manual control.
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens
|
||||
|
||||
All color tokens have light and dark variants defined in the library's `Styles/app.css`:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
/* ... */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
When the `dark` class is toggled, all components automatically switch to dark colors through these CSS custom properties.
|
||||
Reference in New Issue
Block a user