diff --git a/README.md b/README.md index 0150318..9bced55 100644 --- a/README.md +++ b/README.md @@ -1 +1,51 @@ -# Blazor UI Components \ No newline at end of file +# Enciphered.Blazor.UIComponents + +A **pure static SSR** Blazor component library styled with **Tailwind CSS v4** and **shadcn/ui** design tokens. All interactivity is powered by vanilla JavaScript — no SignalR, no WebAssembly, no `InteractiveServer` render mode required. Form validation and submission are handled entirely through **htmx**. + +## Features + +- **Zero Blazor interactivity** — components work with `AddRazorComponents()` alone +- **Tailwind CSS v4** with oklch color tokens (light + dark mode) +- **htmx form validation** — per-field blur validation and full form submission with a fluent `FormValidator` API +- **Strongly-typed model binding** — bind submitted form data to POCOs automatically +- **Sidebar layout** — collapsible, responsive, cookie-persisted +- **Card components** — composable header/content/footer/image/action slots +- **Dark mode** — toggle with localStorage persistence, FOUC-free +- **Date/Time pickers** — calendar and time picker popovers with hidden native inputs + +## Quick Start + +```bash +dotnet add package Enciphered.Blazor.UIComponents +``` + +> See the full [Getting Started guide](docs/getting-started.md) for setup instructions. + +## Documentation + +| Document | Description | +|---|---| +| [Getting Started](docs/getting-started.md) | Installation, prerequisites, and first-app setup | +| [Sidebar](docs/components/sidebar.md) | Collapsible sidebar layout system | +| [Card](docs/components/card.md) | Composable card components | +| [Button](docs/components/button.md) | Button with variants and sizes | +| [Form Inputs](docs/components/form-inputs.md) | TextInput, NumberInput, DateInput, TimeInput, DateTimeInput | +| [Form Validation](docs/forms/validation.md) | htmx-powered validation with FormValidator | +| [Form Submission](docs/forms/submission.md) | Handling form submit with model binding | +| [Theme Toggle](docs/components/theme-toggle.md) | Dark/light mode toggle | + +## Architecture + +All interactive behavior (popovers, sidebar collapse, calendar navigation, number input steppers, form resets) is implemented via three vanilla JS modules that use `data-*` attribute selectors and event delegation: + +| Module | Purpose | +|---|---| +| `darkmode.js` | Theme toggle, localStorage persistence, SVG icon sync | +| `sidebar.js` | Expand/collapse, mobile responsiveness, cookie persistence | +| `forms.js` | Popover, calendar, time picker, number stepper, form reset | + +These modules survive Blazor enhanced navigation via `MutationObserver` patterns. + +## License + +ISC \ No newline at end of file diff --git a/docs/components/button.md b/docs/components/button.md new file mode 100644 index 0000000..0bf2474 --- /dev/null +++ b/docs/components/button.md @@ -0,0 +1,148 @@ +# Button + +A styled button component with variant and size presets built from Tailwind utility classes. + +--- + +## Basic Usage + +```razor + + + +``` + +--- + +## Variants + +Use the `Variant` parameter with constants from `ButtonVariant`: + +```razor + + + + + + +``` + +| 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 + +``` + +### 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 + + + +``` + +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 + +``` + +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 + + + + +``` + +| 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 + +``` + +--- + +## 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`. diff --git a/docs/components/card.md b/docs/components/card.md new file mode 100644 index 0000000..0787c77 --- /dev/null +++ b/docs/components/card.md @@ -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` | `

` 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 + + + Notifications + You have 3 unread messages. + + +

Here is the main content of the card.

+
+ + + +
+``` + +--- + +## Card with Action + +The `CardAction` renders at the trailing edge of the header using CSS grid: + +```razor + + + Team Members + Manage your team. + + + + + + + + +``` + +--- + +## Card with Image + +```razor + + + + Beautiful Scenery + A mountain landscape at sunset. + + +

The image fills the card width and crops via object-cover.

+
+
+``` + +--- + +## 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 `` | +| `WrapperClass` | `string?` | — | Additional classes on the wrapper `
` | + +### CardTitle, CardDescription, CardContent, CardFooter, CardAction + +All accept `ChildContent` and `Class` parameters. All support unmatched HTML attributes via `@attributes`. diff --git a/docs/components/form-inputs.md b/docs/components/form-inputs.md new file mode 100644 index 0000000..f5a905e --- /dev/null +++ b/docs/components/form-inputs.md @@ -0,0 +1,192 @@ +# Form Inputs + +All input components extend a shared `InputBase` 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 + + + + + + + + + + + +``` + +### 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 + + + + + + + +``` + +### 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` (`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 `` with a popover calendar. Users select dates through the calendar UI — the native date picker chrome is hidden. + +```razor + + + +``` + +### How it works + +1. A hidden `` 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`. + +--- + +## TimeInput + +A time picker that combines a hidden `` with a popover time picker. Features scrollable hour/minute columns and AM/PM toggle. + +```razor + + + +``` + +### 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`. + +--- + +## DateTimeInput + +Combines a date picker and time picker side by side for selecting both date and time. Internally manages a hidden `` plus two helper hidden inputs for the date and time parts. + +```razor + + + +``` + +### How it works + +1. A hidden `` 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`. + +--- + +## 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 + + + +``` + +### Parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `Label` | `string?` | — | Label text rendered as `