Added docs

This commit was merged in pull request #1.
This commit is contained in:
2026-04-13 18:57:47 +05:00
parent b323862e03
commit 5668cf20d9
9 changed files with 1421 additions and 1 deletions
+148
View File
@@ -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`.
+103
View File
@@ -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`.
+192
View File
@@ -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
+139
View File
@@ -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">
&copy; 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.
+97
View File
@@ -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.