@zerotal/arch 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/api-surface.md +283 -0
- package/docs/about.md +676 -0
- package/docs/admin/actions.md +183 -0
- package/docs/admin/auth.md +89 -0
- package/docs/admin/dashboard.md +155 -0
- package/docs/admin/extending-ui.md +229 -0
- package/docs/admin/extending.md +205 -0
- package/docs/admin/forms.md +155 -0
- package/docs/admin/index.md +144 -0
- package/docs/admin/operations.md +224 -0
- package/docs/admin/references.md +174 -0
- package/docs/admin/resources.md +132 -0
- package/docs/admin/structure.md +158 -0
- package/docs/admin/tables.md +185 -0
- package/docs/admin/testing.md +138 -0
- package/docs/ai.md +373 -0
- package/docs/application.md +435 -0
- package/docs/arch.md +263 -0
- package/docs/assets.md +755 -0
- package/docs/audit.md +367 -0
- package/docs/authentication.md +1000 -0
- package/docs/authorization.md +529 -0
- package/docs/broadcasting/channels.md +170 -0
- package/docs/broadcasting/client.md +108 -0
- package/docs/broadcasting/events.md +190 -0
- package/docs/broadcasting/index.md +163 -0
- package/docs/broadcasting/references.md +84 -0
- package/docs/broadcasting/testing.md +132 -0
- package/docs/cache.md +387 -0
- package/docs/carbon.md +830 -0
- package/docs/changelog.md +465 -0
- package/docs/client/auth.md +113 -0
- package/docs/client/errors.md +139 -0
- package/docs/client/files.md +118 -0
- package/docs/client/index.md +128 -0
- package/docs/client/references.md +58 -0
- package/docs/client/requests.md +131 -0
- package/docs/client/resilience.md +141 -0
- package/docs/client/testing.md +146 -0
- package/docs/commands.md +450 -0
- package/docs/components.md +2427 -0
- package/docs/config-system.md +258 -0
- package/docs/container.md +596 -0
- package/docs/context.md +579 -0
- package/docs/contributing.md +164 -0
- package/docs/controllers.md +354 -0
- package/docs/conventions.md +466 -0
- package/docs/cookies.md +101 -0
- package/docs/csrf.md +206 -0
- package/docs/database.md +476 -0
- package/docs/deployment.md +434 -0
- package/docs/devtools.md +1013 -0
- package/docs/email-verification.md +320 -0
- package/docs/encryption.md +380 -0
- package/docs/errors.md +317 -0
- package/docs/events.md +420 -0
- package/docs/flow/components.md +846 -0
- package/docs/flow/decorators.md +458 -0
- package/docs/flow/events.md +420 -0
- package/docs/flow/forms.md +551 -0
- package/docs/flow/index.md +568 -0
- package/docs/flow/layouts.md +793 -0
- package/docs/flow/lifecycle.md +336 -0
- package/docs/flow/pagination.md +402 -0
- package/docs/flow/performance.md +262 -0
- package/docs/flow/references.md +317 -0
- package/docs/flow/routing.md +550 -0
- package/docs/flow/testing.md +447 -0
- package/docs/getting-started.md +286 -0
- package/docs/health.md +269 -0
- package/docs/helpers.md +423 -0
- package/docs/i18n.md +414 -0
- package/docs/index.md +65 -0
- package/docs/inertia/build.md +95 -0
- package/docs/inertia/devtools.md +197 -0
- package/docs/inertia/index.md +267 -0
- package/docs/inertia/middleware.md +81 -0
- package/docs/inertia/props.md +507 -0
- package/docs/inertia/references.md +144 -0
- package/docs/inertia/rendering.md +189 -0
- package/docs/inertia/ssr.md +102 -0
- package/docs/inspirations.md +257 -0
- package/docs/lifecycle.md +331 -0
- package/docs/lock.md +454 -0
- package/docs/logger.md +517 -0
- package/docs/media.md +582 -0
- package/docs/middleware.md +509 -0
- package/docs/migrations.md +529 -0
- package/docs/monitor.md +377 -0
- package/docs/notifications.md +848 -0
- package/docs/orm/casts.md +365 -0
- package/docs/orm/factories.md +231 -0
- package/docs/orm/index.md +663 -0
- package/docs/orm/lifecycle.md +421 -0
- package/docs/orm/queries.md +692 -0
- package/docs/orm/relationships.md +440 -0
- package/docs/orm/serialization.md +251 -0
- package/docs/package-development.md +536 -0
- package/docs/pagination.md +340 -0
- package/docs/password-reset.md +335 -0
- package/docs/providers.md +447 -0
- package/docs/query-builder.md +465 -0
- package/docs/queue.md +549 -0
- package/docs/rate-limiting.md +253 -0
- package/docs/responses.md +336 -0
- package/docs/roles-and-2fa.md +418 -0
- package/docs/routing.md +1008 -0
- package/docs/scaffolding.md +335 -0
- package/docs/scheduler.md +533 -0
- package/docs/seeding.md +229 -0
- package/docs/session.md +451 -0
- package/docs/social.md +524 -0
- package/docs/storage.md +622 -0
- package/docs/structure.md +230 -0
- package/docs/support-policy.md +114 -0
- package/docs/telemetry.md +505 -0
- package/docs/tenancy.md +608 -0
- package/docs/testing/browser.md +153 -0
- package/docs/testing/console.md +130 -0
- package/docs/testing/database.md +238 -0
- package/docs/testing/flow-browser.md +216 -0
- package/docs/testing/http.md +441 -0
- package/docs/testing/index.md +240 -0
- package/docs/testing/mocking.md +410 -0
- package/docs/upgrade.md +192 -0
- package/docs/validator.md +549 -0
- package/docs/view.md +453 -0
- package/package.json +60 -0
- package/src/bin/mcp.ts +73 -0
- package/src/config.ts +63 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +56 -0
- package/src/install/ArchInstallCommand.ts +189 -0
- package/src/install/detect.ts +98 -0
- package/src/install/guidelines.ts +290 -0
- package/src/install/markers.ts +77 -0
- package/src/install/mcpConfig.ts +81 -0
- package/src/mcp/index.ts +27 -0
- package/src/mcp/jsonrpc.ts +133 -0
- package/src/mcp/server.ts +307 -0
- package/src/mcp/stdio.ts +111 -0
- package/src/mcp/types.ts +146 -0
- package/src/probe/ArchProbeCommand.ts +48 -0
- package/src/probe/sentinel.ts +13 -0
- package/src/probe/topics.ts +291 -0
- package/src/provider/ArchProvider.ts +101 -0
- package/src/tools/_probe.ts +164 -0
- package/src/tools/apiSurface.ts +241 -0
- package/src/tools/appInfo.ts +88 -0
- package/src/tools/baselines.ts +210 -0
- package/src/tools/context.ts +37 -0
- package/src/tools/doctor.ts +93 -0
- package/src/tools/index.ts +50 -0
- package/src/tools/logs.ts +265 -0
- package/src/tools/routes.ts +133 -0
- package/src/tools/schema.ts +119 -0
- package/src/tools/searchDocs.ts +345 -0
|
@@ -0,0 +1,2427 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Components
|
|
3
|
+
description: Themeable UI components for Flow that you install via the CLI or import from the package.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Components
|
|
7
|
+
|
|
8
|
+
`@zerotal/flow-ui` ships 53 themeable components for [Flow](/docs/flow). They are
|
|
9
|
+
built on accessible headless primitives and design tokens, so they follow your theme
|
|
10
|
+
(light / dark) out of the box. Add them with the CLI (copy the source into your app) or
|
|
11
|
+
import them straight from the package.
|
|
12
|
+
|
|
13
|
+
The catalogue covers what an application actually needs rather than only the obvious
|
|
14
|
+
primitives:
|
|
15
|
+
|
|
16
|
+
| Group | Components |
|
|
17
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| Forms | `Field`, `Input`, `InputGroup`, `InputOTP`, `Textarea`, `Select`, `Combobox`, `Checkbox`, `RadioGroup`, `Switch`, `Slider`, `Toggle`, `ToggleGroup`, `Label`, `Calendar`, `DatePicker` |
|
|
19
|
+
| Buttons & actions | `Button`, `ButtonGroup`, `DropdownMenu`, `ContextMenu`, `Menubar` |
|
|
20
|
+
| Overlays | `Dialog`, `AlertDialog`, `Sheet`, `Popover`, `HoverCard`, `Tooltip`, `Command` |
|
|
21
|
+
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Pagination`, `Tabs` |
|
|
22
|
+
| Data | `Table`, `Chart`, `Item`, `Avatar`, `Badge` |
|
|
23
|
+
| Feedback | `Toaster`, `Alert`, `Progress`, `Spinner`, `Skeleton`, `Empty` |
|
|
24
|
+
| Layout & content | `Card`, `Accordion`, `Collapsible`, `ScrollArea`, `Resizable`, `Carousel`, `AspectRatio`, `Separator`, `Prose`, `Kbd` |
|
|
25
|
+
|
|
26
|
+
## Where the work happens
|
|
27
|
+
|
|
28
|
+
The components follow one rule, and it is worth stating before the API: **the
|
|
29
|
+
server renders, the client reacts.**
|
|
30
|
+
|
|
31
|
+
Anything that answers to a pointer or a keystroke — dragging a slider, paging a
|
|
32
|
+
calendar, filtering a palette, pressing a toggle, hovering a chart — runs entirely
|
|
33
|
+
in the browser through Alpine. Those interactions have to land inside a frame, and
|
|
34
|
+
a network round-trip cannot make that promise. Putting them on the server produces
|
|
35
|
+
the specific ugliness of a control that lags its own input: a slider whose readout
|
|
36
|
+
does not move while you drag it, a segmented button that stays unpressed until the
|
|
37
|
+
reply arrives.
|
|
38
|
+
|
|
39
|
+
The server's job is the first paint and the data. It renders the initial state so
|
|
40
|
+
the page is correct and complete before any script runs, and it hears about a
|
|
41
|
+
change once — when there is something to persist. A date picker syncs the day you
|
|
42
|
+
chose, not the six months you browsed to find it.
|
|
43
|
+
|
|
44
|
+
In practice that means most components bind rather than call:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<Slider bind={this.volume} showValue /> {/* live while dragging, synced on release */}
|
|
48
|
+
<ToggleGroup bind={this.view} options={…} /> {/* presses instantly, syncs after */}
|
|
49
|
+
<DatePicker bind={this.due} /> {/* months page client-side, day syncs once */}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Where a component genuinely needs the server — a calendar laying out records, a
|
|
53
|
+
table of rows — it says so, and paging is a real navigation because the next page
|
|
54
|
+
means different data.
|
|
55
|
+
|
|
56
|
+
## Getting Started
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# in your project root
|
|
60
|
+
bun add @zerotal/flow-ui
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Register the provider
|
|
64
|
+
|
|
65
|
+
`FlowUiProvider` is a scaffolding-only provider: it registers the `flow:*` CLI commands. The components themselves are plain functions with no runtime service, so you only need the provider to use the CLI. Add it to the providers array in `bootstrap/providers.ts`:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// bootstrap/providers.ts
|
|
69
|
+
import { FlowUiProvider } from "@zerotal/flow-ui";
|
|
70
|
+
|
|
71
|
+
const providers = [
|
|
72
|
+
// …your other providers
|
|
73
|
+
FlowUiProvider,
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
export default providers;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Registering the provider switches on the following:
|
|
80
|
+
|
|
81
|
+
- `onBooted` — registers the `flow:list`, `flow:add`, and `flow:init` console commands (only when the `commands` binding is present, so web/test boots stay unaffected).
|
|
82
|
+
|
|
83
|
+
## Set up the theme
|
|
84
|
+
|
|
85
|
+
Run `flow:init` once per app. It drops the shared `cn` / `gva` utilities into `app/flow/components/ui/lib/` and wires the design-token theme into your Tailwind entry CSS:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# in your project root
|
|
89
|
+
bun zt flow:init
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> **Note** — `flow:init` is idempotent: it skips files that already exist and only adds the `@import "@zerotal/flow-ui/theme.css";` line if it is missing. Pass `--css <path>` to target a Tailwind entry other than `resources/css/app.css`.
|
|
93
|
+
|
|
94
|
+
### Without a build step
|
|
95
|
+
|
|
96
|
+
Components are written against design tokens — `bg-card`, `text-muted-foreground`, `border-input`, `bg-primary` — so _defining_ those tokens themes the whole kit at once. `theme.css` does that for a real Tailwind build. When you have no build step, `flowUiHead()` does the same job as a `<head>` payload: it loads the Tailwind Play CDN, configures it with the identical token mapping, and emits the palette inline.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { Layout, flowUiHead } from "@zerotal/flow-ui";
|
|
100
|
+
|
|
101
|
+
export class AppLayout extends Layout {
|
|
102
|
+
static override get head() {
|
|
103
|
+
return flowUiHead("Acme");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Re-brand by passing `tokensCss`; it is appended after the defaults, so overriding one variable recolours everything that reads it:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
flowUiHead("Acme", {
|
|
112
|
+
tokensCss: `
|
|
113
|
+
:root { --primary: 21 90% 48%; --ring: 21 90% 48%; }
|
|
114
|
+
.dark { --primary: 25 95% 58%; --ring: 25 95% 58%; }
|
|
115
|
+
`,
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This is the path both panels Zerotal ships take. The [admin](/docs/admin) uses it through `adminHead()`, and the [monitor](/docs/monitor) uses it directly with an orange `--primary` — which is the whole reason the two look like the same product rather than two.
|
|
120
|
+
|
|
121
|
+
### The token set
|
|
122
|
+
|
|
123
|
+
Alongside the base tokens (`background`, `card`, `primary`, `muted`, `accent`, `destructive`, `border`, `input`, `ring`) the theme adds:
|
|
124
|
+
|
|
125
|
+
| Token | For |
|
|
126
|
+
| ------------------------------------------- | ------------------------------------------------------------------ |
|
|
127
|
+
| `success` / `warning` | The states `destructive` doesn't cover — "healthy" and "degraded". |
|
|
128
|
+
| `flow-toast-success` / `flow-toast-warning` | Status accents the toast host reads at runtime. |
|
|
129
|
+
| `chart-1` … `chart-7` | A categorical series palette for charts. |
|
|
130
|
+
|
|
131
|
+
The chart tokens are deliberately _not_ semantic: "the second line" isn't good or bad, it just has to stay distinguishable from the first. Because SVG `stroke` and `fill` can't take Tailwind classes, reference them as `hsl(var(--chart-2))`.
|
|
132
|
+
|
|
133
|
+
## Adding components
|
|
134
|
+
|
|
135
|
+
`flow:add` copies component source into `app/flow/components/ui/`, so you own the code
|
|
136
|
+
outright. The shared `cn` / `gva` utils land in `ui/lib/`, and util imports are rewritten
|
|
137
|
+
to the local `./lib/*` paths on the way in.
|
|
138
|
+
|
|
139
|
+
A component built from others brings them along. Asking for `date-picker` also copies
|
|
140
|
+
`popover` and `calendar`, because a file importing a `Calendar` that was never copied is
|
|
141
|
+
not a working component.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# in your project root
|
|
145
|
+
bun zt flow:add button # one component
|
|
146
|
+
bun zt flow:add button,card,dialog # several, comma-separated
|
|
147
|
+
bun zt flow:add --all # everything
|
|
148
|
+
bun zt flow:add button --force # overwrite if it exists
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Browse everything available with `flow:list`:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# in your project root
|
|
155
|
+
bun zt flow:list
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
> **Tip** — The copied `cn` util needs `clsx` and `tailwind-merge` in your app. `flow:add` and `flow:init` warn you with the exact `bun add` command if they are missing.
|
|
159
|
+
|
|
160
|
+
## Copy-in vs import — which should I use?
|
|
161
|
+
|
|
162
|
+
Both give you the same components; they differ in ownership:
|
|
163
|
+
|
|
164
|
+
- **Copy in with `flow:add`** when you want to _own and tweak_ the source — change variants, restyle, or extend a component. The code lives in your repo and won't change under you.
|
|
165
|
+
- **Import from `@zerotal/flow-ui`** when you just want the component as-is and prefer to track upstream updates. Every component is exported from the package root:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
// in a Flow component
|
|
169
|
+
import { Button, Card, Dialog } from "@zerotal/flow-ui";
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The component API is identical either way — the sections below apply to both.
|
|
173
|
+
|
|
174
|
+
Props not listed for a component pass straight through to the underlying element —
|
|
175
|
+
`onClick`, `type`, `disabled`, and any `flow:*` directive behave exactly as they would
|
|
176
|
+
on the raw tag, so a component never gets in the way of Flow's own bindings.
|
|
177
|
+
|
|
178
|
+
<!-- BEGIN GENERATED COMPONENTS — edit the spec in packages/flow-ui/src/docs/, not this block. -->
|
|
179
|
+
|
|
180
|
+
### All 53 components
|
|
181
|
+
|
|
182
|
+
- [Button](#components-button) — Clickable button with variants + sizes
|
|
183
|
+
- [Badge](#components-badge) — Small status pill with variants
|
|
184
|
+
- [Card](#components-card) — Surface container (+ header/title/content/footer)
|
|
185
|
+
- [Input](#components-input) — Themed text input (two-way bindable)
|
|
186
|
+
- [Textarea](#components-textarea) — Themed multi-line input
|
|
187
|
+
- [Label](#components-label) — Form label (wraps the headless Label)
|
|
188
|
+
- [Separator](#components-separator) — Horizontal/vertical divider
|
|
189
|
+
- [Skeleton](#components-skeleton) — Pulsing loading placeholder
|
|
190
|
+
- [Avatar](#components-avatar) — Circular avatar with image + fallback
|
|
191
|
+
- [Switch](#components-switch) — On/off toggle bound to a boolean
|
|
192
|
+
- [Checkbox](#components-checkbox) — Checkbox bound to a boolean
|
|
193
|
+
- [Select](#components-select) — Native select bound to a value
|
|
194
|
+
- [RadioGroup](#components-radio-group) — Segmented radio set bound to a value
|
|
195
|
+
- [Dialog](#components-dialog) — Modal dialog (focus-trapped)
|
|
196
|
+
- [Sheet](#components-sheet) — Edge-anchored slide-over panel
|
|
197
|
+
- [DropdownMenu](#components-dropdown-menu) — Keyboard-navigable menu (+ item/label/separator)
|
|
198
|
+
- [Tabs](#components-tabs) — Tabbed panels with a pill tablist
|
|
199
|
+
- [Alert](#components-alert) — Inline alert (+ title/description)
|
|
200
|
+
- [Tooltip](#components-tooltip) — Hover/focus tooltip
|
|
201
|
+
- [Table](#components-table) — URL-sortable data table
|
|
202
|
+
- [Popover](#components-popover) — Floating panel anchored to a trigger
|
|
203
|
+
- [HoverCard](#components-hover-card) — Preview panel shown on hover
|
|
204
|
+
- [AlertDialog](#components-alert-dialog) — Confirm before something irreversible
|
|
205
|
+
- [Command](#components-command) — Searchable command menu (⌘K palette)
|
|
206
|
+
- [ContextMenu](#components-context-menu) — Right-click menu
|
|
207
|
+
- [Menubar](#components-menubar) — Application menu bar
|
|
208
|
+
- [NavigationMenu](#components-navigation-menu) — Site nav with dropdown panels
|
|
209
|
+
- [Sidebar](#components-sidebar) — App shell nav rail with a mobile drawer
|
|
210
|
+
- [Breadcrumb](#components-breadcrumb) — Trail showing where a page sits
|
|
211
|
+
- [Pagination](#components-pagination) — Page links with a windowed number range
|
|
212
|
+
- [Field](#components-field) — Label + control + description + error
|
|
213
|
+
- [InputGroup](#components-input-group) — Input with affixes or addons
|
|
214
|
+
- [InputOTP](#components-input-otp) — One-time-code input
|
|
215
|
+
- [Combobox](#components-combobox) — Autocomplete over many options
|
|
216
|
+
- [Slider](#components-slider) — Value chosen from a range
|
|
217
|
+
- [Toggle](#components-toggle) — Pressed-state button (+ toggle group)
|
|
218
|
+
- [ButtonGroup](#components-button-group) — Buttons joined into one control
|
|
219
|
+
- [Calendar](#components-calendar) — Month grid for picking or laying out dates
|
|
220
|
+
- [DatePicker](#components-date-picker) — Calendar in a popover
|
|
221
|
+
- [Toaster](#components-toast) — Host for transient flash messages
|
|
222
|
+
- [Progress](#components-progress) — Determinate progress bar
|
|
223
|
+
- [Spinner](#components-spinner) — Indeterminate loading indicator
|
|
224
|
+
- [Empty](#components-empty) — Empty-state block with an action
|
|
225
|
+
- [Kbd](#components-kbd) — Keyboard key (+ platform modifier)
|
|
226
|
+
- [Accordion](#components-accordion) — Stacked collapsible sections
|
|
227
|
+
- [Collapsible](#components-collapsible) — One section that opens and closes
|
|
228
|
+
- [ScrollArea](#components-scroll-area) — Scrollable region with a styled bar
|
|
229
|
+
- [Resizable](#components-resizable) — Two panes with a draggable handle
|
|
230
|
+
- [Carousel](#components-carousel) — Snap-scrolling strip with controls
|
|
231
|
+
- [AspectRatio](#components-aspect-ratio) — Fixed width-to-height box
|
|
232
|
+
- [Item](#components-item) — Icon + title + description + action row
|
|
233
|
+
- [Chart](#components-chart) — SVG line, area, bar and donut charts
|
|
234
|
+
- [Prose](#components-typography) — Prose wrapper + heading helpers
|
|
235
|
+
|
|
236
|
+
<a id="components-button"></a>
|
|
237
|
+
|
|
238
|
+
## Button
|
|
239
|
+
|
|
240
|
+
Clickable button with variants + sizes.
|
|
241
|
+
|
|
242
|
+
### Button installation
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
bun zt flow:add button
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Or import directly from the package: `import { Button } from "@zerotal/flow-ui";`
|
|
249
|
+
|
|
250
|
+
### Button preview
|
|
251
|
+
|
|
252
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
253
|
+
<div class="flex flex-wrap items-center gap-3"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 py-2">Default</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground hover:bg-secondary/80 h-9 px-4 py-2">Secondary</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground hover:bg-destructive/90 h-9 px-4 py-2">Destructive</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Outline</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Ghost</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 text-primary underline-offset-4 hover:underline h-9 px-4 py-2">Link</button></div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
### Button usage
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
<Button onClick={this.save}>Save</Button>
|
|
260
|
+
<Button variant="secondary">Secondary</Button>
|
|
261
|
+
<Button variant="destructive">Delete</Button>
|
|
262
|
+
<Button variant="outline">Outline</Button>
|
|
263
|
+
<Button variant="ghost">Ghost</Button>
|
|
264
|
+
<Button variant="link">Link</Button>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Button props
|
|
268
|
+
|
|
269
|
+
<table>
|
|
270
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
271
|
+
<tbody>
|
|
272
|
+
<tr><td><code>variant</code></td><td><code>"default" | "secondary" | "destructive" | "outline" | "ghost" | "link"</code></td><td><code>"default"</code></td><td>Visual style.</td></tr>
|
|
273
|
+
<tr><td><code>size</code></td><td><code>"default" | "sm" | "lg" | "icon"</code></td><td><code>"default"</code></td><td>Sizing.</td></tr>
|
|
274
|
+
<tr><td><code>onClick</code></td><td><code>handler</code></td><td>—</td><td>Server action or client expression (standard Flow).</td></tr>
|
|
275
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
276
|
+
</tbody>
|
|
277
|
+
</table>
|
|
278
|
+
|
|
279
|
+
<a id="components-badge"></a>
|
|
280
|
+
|
|
281
|
+
## Badge
|
|
282
|
+
|
|
283
|
+
Small status pill with variants.
|
|
284
|
+
|
|
285
|
+
### Badge installation
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
bun zt flow:add badge
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Or import directly from the package: `import { Badge } from "@zerotal/flow-ui";`
|
|
292
|
+
|
|
293
|
+
### Badge preview
|
|
294
|
+
|
|
295
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
296
|
+
<div class="flex flex-wrap items-center gap-3"><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80">Default</span><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">Secondary</span><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80">Destructive</span><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground">Outline</span></div>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
### Badge usage
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
<Badge>New</Badge>
|
|
303
|
+
<Badge variant="secondary">Beta</Badge>
|
|
304
|
+
<Badge variant="destructive">Overdue</Badge>
|
|
305
|
+
<Badge variant="outline">Draft</Badge>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Badge props
|
|
309
|
+
|
|
310
|
+
<table>
|
|
311
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
312
|
+
<tbody>
|
|
313
|
+
<tr><td><code>variant</code></td><td><code>"default" | "secondary" | "destructive" | "outline"</code></td><td><code>"default"</code></td><td>Visual style.</td></tr>
|
|
314
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
|
|
315
|
+
</tbody>
|
|
316
|
+
</table>
|
|
317
|
+
|
|
318
|
+
<a id="components-card"></a>
|
|
319
|
+
|
|
320
|
+
## Card
|
|
321
|
+
|
|
322
|
+
Surface container (+ header/title/content/footer).
|
|
323
|
+
|
|
324
|
+
### Card installation
|
|
325
|
+
|
|
326
|
+
```sh
|
|
327
|
+
bun zt flow:add card
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Or import directly from the package: `import { Card } from "@zerotal/flow-ui";`
|
|
331
|
+
|
|
332
|
+
### Card preview
|
|
333
|
+
|
|
334
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
335
|
+
<div class="rounded-xl border bg-card text-card-foreground shadow w-80"><div class="flex flex-col space-y-1.5 p-6"><h3 class="font-semibold leading-none tracking-tight">Create project</h3><p class="text-sm text-muted-foreground">Deploy your new project in one click.</p></div><div class="p-6 pt-0"><p class="text-sm text-muted-foreground">Project settings go here.</p></div><div class="flex items-center p-6 pt-0"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-4 py-2">Deploy</button></div></div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
### Card usage
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
<Card>
|
|
342
|
+
<CardHeader>
|
|
343
|
+
<CardTitle>Create project</CardTitle>
|
|
344
|
+
<CardDescription>Deploy your new project in one click.</CardDescription>
|
|
345
|
+
</CardHeader>
|
|
346
|
+
<CardContent>…</CardContent>
|
|
347
|
+
<CardFooter>
|
|
348
|
+
<Button>Deploy</Button>
|
|
349
|
+
</CardFooter>
|
|
350
|
+
</Card>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Card props
|
|
354
|
+
|
|
355
|
+
<table>
|
|
356
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
357
|
+
<tbody>
|
|
358
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes on the surface.</td></tr>
|
|
359
|
+
<tr><td><code>children</code></td><td><code>node</code></td><td>—</td><td>Compose with CardHeader / CardTitle / CardDescription / CardContent / CardFooter.</td></tr>
|
|
360
|
+
</tbody>
|
|
361
|
+
</table>
|
|
362
|
+
|
|
363
|
+
<a id="components-input"></a>
|
|
364
|
+
|
|
365
|
+
## Input
|
|
366
|
+
|
|
367
|
+
Themed text input (two-way bindable).
|
|
368
|
+
|
|
369
|
+
### Input installation
|
|
370
|
+
|
|
371
|
+
```sh
|
|
372
|
+
bun zt flow:add input
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Or import directly from the package: `import { Input } from "@zerotal/flow-ui";`
|
|
376
|
+
|
|
377
|
+
### Input preview
|
|
378
|
+
|
|
379
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
380
|
+
<div class="flex w-72 flex-col gap-1.5"><label class="flow-label text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">Email</label><input type="text" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" placeholder="you@example.com"></div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
### Input usage
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
<Field label="Email">
|
|
387
|
+
<Input value={this.form.email} placeholder="you@example.com" />
|
|
388
|
+
</Field>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Input props
|
|
392
|
+
|
|
393
|
+
<table>
|
|
394
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
395
|
+
<tbody>
|
|
396
|
+
<tr><td><code>value</code></td><td><code>bound state</code></td><td>—</td><td>Two-way bind to an @expose / form field (emits flow:model).</td></tr>
|
|
397
|
+
<tr><td><code>type</code></td><td><code>string</code></td><td><code>"text"</code></td><td>Native input type.</td></tr>
|
|
398
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
|
|
399
|
+
</tbody>
|
|
400
|
+
</table>
|
|
401
|
+
|
|
402
|
+
<a id="components-textarea"></a>
|
|
403
|
+
|
|
404
|
+
## Textarea
|
|
405
|
+
|
|
406
|
+
Themed multi-line input.
|
|
407
|
+
|
|
408
|
+
### Textarea installation
|
|
409
|
+
|
|
410
|
+
```sh
|
|
411
|
+
bun zt flow:add textarea
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Or import directly from the package: `import { Textarea } from "@zerotal/flow-ui";`
|
|
415
|
+
|
|
416
|
+
### Textarea preview
|
|
417
|
+
|
|
418
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
419
|
+
<div class="w-72"><textarea class="flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" placeholder="Tell us about yourself" rows="4"></textarea></div>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
### Textarea usage
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
<Textarea value={this.form.bio} placeholder="Tell us about yourself" rows={4} />
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Textarea props
|
|
429
|
+
|
|
430
|
+
<table>
|
|
431
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
432
|
+
<tbody>
|
|
433
|
+
<tr><td><code>value</code></td><td><code>bound state</code></td><td>—</td><td>Two-way bind to an @expose / form field.</td></tr>
|
|
434
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
|
|
435
|
+
</tbody>
|
|
436
|
+
</table>
|
|
437
|
+
|
|
438
|
+
<a id="components-label"></a>
|
|
439
|
+
|
|
440
|
+
## Label
|
|
441
|
+
|
|
442
|
+
Form label (wraps the headless Label).
|
|
443
|
+
|
|
444
|
+
### Label installation
|
|
445
|
+
|
|
446
|
+
```sh
|
|
447
|
+
bun zt flow:add label
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Or import directly from the package: `import { Label } from "@zerotal/flow-ui";`
|
|
451
|
+
|
|
452
|
+
### Label preview
|
|
453
|
+
|
|
454
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
455
|
+
<div class="flex flex-col gap-1.5"><label for="email" class="flow-label text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">Email address</label><input type="text" data-slot="input" class="flex h-9 min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 w-64" id="email" placeholder="you@example.com"></div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
### Label usage
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
<Label for="email">Email</Label>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Label props
|
|
465
|
+
|
|
466
|
+
<table>
|
|
467
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
468
|
+
<tbody>
|
|
469
|
+
<tr><td><code>for</code></td><td><code>string</code></td><td>—</td><td>Associated control id.</td></tr>
|
|
470
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
|
|
471
|
+
</tbody>
|
|
472
|
+
</table>
|
|
473
|
+
|
|
474
|
+
<a id="components-separator"></a>
|
|
475
|
+
|
|
476
|
+
## Separator
|
|
477
|
+
|
|
478
|
+
Horizontal/vertical divider.
|
|
479
|
+
|
|
480
|
+
### Separator installation
|
|
481
|
+
|
|
482
|
+
```sh
|
|
483
|
+
bun zt flow:add separator
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Or import directly from the package: `import { Separator } from "@zerotal/flow-ui";`
|
|
487
|
+
|
|
488
|
+
### Separator preview
|
|
489
|
+
|
|
490
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
491
|
+
<div class="w-72"><p class="text-sm font-medium text-foreground">Radix Primitives</p><p class="text-sm text-muted-foreground">An open-source UI component library.</p><div role="none" class="shrink-0 bg-border h-px w-full my-3"></div><div class="flex h-5 items-center gap-3 text-sm"><span>Blog</span><div role="none" class="shrink-0 bg-border h-full w-px"></div><span>Docs</span><div role="none" class="shrink-0 bg-border h-full w-px"></div><span>Source</span></div></div>
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
### Separator usage
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
<Separator />
|
|
498
|
+
<Separator orientation="vertical" class="h-6" />
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Separator props
|
|
502
|
+
|
|
503
|
+
<table>
|
|
504
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
505
|
+
<tbody>
|
|
506
|
+
<tr><td><code>orientation</code></td><td><code>"horizontal" | "vertical"</code></td><td><code>"horizontal"</code></td><td>Divider direction.</td></tr>
|
|
507
|
+
<tr><td><code>decorative</code></td><td><code>boolean</code></td><td><code>true</code></td><td>ARIA-hidden when decorative; semantic separator otherwise.</td></tr>
|
|
508
|
+
</tbody>
|
|
509
|
+
</table>
|
|
510
|
+
|
|
511
|
+
<a id="components-skeleton"></a>
|
|
512
|
+
|
|
513
|
+
## Skeleton
|
|
514
|
+
|
|
515
|
+
Pulsing loading placeholder.
|
|
516
|
+
|
|
517
|
+
### Skeleton installation
|
|
518
|
+
|
|
519
|
+
```sh
|
|
520
|
+
bun zt flow:add skeleton
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Or import directly from the package: `import { Skeleton } from "@zerotal/flow-ui";`
|
|
524
|
+
|
|
525
|
+
### Skeleton preview
|
|
526
|
+
|
|
527
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
528
|
+
<div class="flex items-center gap-3"><div class="animate-pulse bg-muted h-12 w-12 rounded-full"></div><div class="flex flex-col gap-2"><div class="animate-pulse rounded-md bg-muted h-4 w-40"></div><div class="animate-pulse rounded-md bg-muted h-4 w-28"></div></div></div>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
### Skeleton usage
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
<Skeleton class="h-12 w-12 rounded-full" />
|
|
535
|
+
<Skeleton class="h-4 w-48" />
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Skeleton props
|
|
539
|
+
|
|
540
|
+
<table>
|
|
541
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
542
|
+
<tbody>
|
|
543
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Size + shape via utilities.</td></tr>
|
|
544
|
+
</tbody>
|
|
545
|
+
</table>
|
|
546
|
+
|
|
547
|
+
<a id="components-avatar"></a>
|
|
548
|
+
|
|
549
|
+
## Avatar
|
|
550
|
+
|
|
551
|
+
Circular avatar with image + fallback.
|
|
552
|
+
|
|
553
|
+
### Avatar installation
|
|
554
|
+
|
|
555
|
+
```sh
|
|
556
|
+
bun zt flow:add avatar
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Or import directly from the package: `import { Avatar } from "@zerotal/flow-ui";`
|
|
560
|
+
|
|
561
|
+
### Avatar preview
|
|
562
|
+
|
|
563
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
564
|
+
<div class="flex flex-wrap items-center gap-3"><span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"><img src="https://i.pravatar.cc/64?img=11" alt="Ada Mokoena" class="aspect-square h-full w-full object-cover"></span><span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"><span class="flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">AL</span></span><span class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full bg-primary text-primary-foreground"><span class="flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">GH</span></span></div>
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
### Avatar usage
|
|
568
|
+
|
|
569
|
+
```tsx
|
|
570
|
+
<Avatar src={user.avatarUrl} alt={user.name} fallback="AL" />
|
|
571
|
+
<Avatar fallback="GH" />
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Avatar props
|
|
575
|
+
|
|
576
|
+
<table>
|
|
577
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
578
|
+
<tbody>
|
|
579
|
+
<tr><td><code>src</code></td><td><code>string | null</code></td><td>—</td><td>Image URL; falls back to `fallback` when absent.</td></tr>
|
|
580
|
+
<tr><td><code>fallback</code></td><td><code>node</code></td><td>—</td><td>Shown when there's no image (e.g. initials).</td></tr>
|
|
581
|
+
<tr><td><code>alt</code></td><td><code>string</code></td><td>—</td><td>Image alt text.</td></tr>
|
|
582
|
+
</tbody>
|
|
583
|
+
</table>
|
|
584
|
+
|
|
585
|
+
<a id="components-switch"></a>
|
|
586
|
+
|
|
587
|
+
## Switch
|
|
588
|
+
|
|
589
|
+
On/off toggle bound to a boolean.
|
|
590
|
+
|
|
591
|
+
### Switch installation
|
|
592
|
+
|
|
593
|
+
```sh
|
|
594
|
+
bun zt flow:add switch
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Or import directly from the package: `import { Switch } from "@zerotal/flow-ui";`
|
|
598
|
+
|
|
599
|
+
### Switch preview
|
|
600
|
+
|
|
601
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
602
|
+
<label class="flex items-center gap-2 text-sm"><button type="button" role="switch" tabindex="0" aria-checked="true" class="flow-switch group inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background bg-input data-checked:bg-primary" data-checked=""><span class="pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform translate-x-0 group-data-checked:translate-x-4"></span></button>Airplane mode</label>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
### Switch usage
|
|
606
|
+
|
|
607
|
+
```tsx
|
|
608
|
+
<Switch bind={this.notifications} />
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Switch props
|
|
612
|
+
|
|
613
|
+
<table>
|
|
614
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
615
|
+
<tbody>
|
|
616
|
+
<tr><td><code>bind</code></td><td><code>@expose boolean</code></td><td>—</td><td>Two-way bound boolean (server-synced).</td></tr>
|
|
617
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes on the track.</td></tr>
|
|
618
|
+
</tbody>
|
|
619
|
+
</table>
|
|
620
|
+
|
|
621
|
+
<a id="components-checkbox"></a>
|
|
622
|
+
|
|
623
|
+
## Checkbox
|
|
624
|
+
|
|
625
|
+
Checkbox bound to a boolean.
|
|
626
|
+
|
|
627
|
+
### Checkbox installation
|
|
628
|
+
|
|
629
|
+
```sh
|
|
630
|
+
bun zt flow:add checkbox
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Or import directly from the package: `import { Checkbox } from "@zerotal/flow-ui";`
|
|
634
|
+
|
|
635
|
+
### Checkbox preview
|
|
636
|
+
|
|
637
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
638
|
+
<label class="flex items-center gap-2 text-sm"><button type="button" role="checkbox" tabindex="0" aria-checked="true" class="flow-checkbox grid h-4 w-4 shrink-0 place-items-center rounded-sm border border-primary text-transparent shadow outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-checked:bg-primary data-checked:text-primary-foreground" data-checked=""><svg viewBox="0 0 16 16" fill="none" class="h-3.5 w-3.5" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-7"></path></svg></button>Accept terms and conditions</label>
|
|
639
|
+
</div>
|
|
640
|
+
|
|
641
|
+
### Checkbox usage
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
<Checkbox bind={this.agree} />
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Checkbox props
|
|
648
|
+
|
|
649
|
+
<table>
|
|
650
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
651
|
+
<tbody>
|
|
652
|
+
<tr><td><code>bind</code></td><td><code>@expose boolean</code></td><td>—</td><td>Two-way bound boolean.</td></tr>
|
|
653
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
|
|
654
|
+
</tbody>
|
|
655
|
+
</table>
|
|
656
|
+
|
|
657
|
+
<a id="components-select"></a>
|
|
658
|
+
|
|
659
|
+
## Select
|
|
660
|
+
|
|
661
|
+
Native select bound to a value.
|
|
662
|
+
|
|
663
|
+
### Select installation
|
|
664
|
+
|
|
665
|
+
```sh
|
|
666
|
+
bun zt flow:add select
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Or import directly from the package: `import { Select } from "@zerotal/flow-ui";`
|
|
670
|
+
|
|
671
|
+
### Select preview
|
|
672
|
+
|
|
673
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
674
|
+
<div class="w-56"><select class="flow-select flex h-9 w-full items-center rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"><option value="ca" selected>Canada</option><option value="br">Brazil</option><option value="jp">Japan</option></select></div>
|
|
675
|
+
</div>
|
|
676
|
+
|
|
677
|
+
### Select usage
|
|
678
|
+
|
|
679
|
+
```tsx
|
|
680
|
+
<Select bind={this.country} options={[{ label: "Canada", value: "ca" }]} />
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Select props
|
|
684
|
+
|
|
685
|
+
<table>
|
|
686
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
687
|
+
<tbody>
|
|
688
|
+
<tr><td><code>bind</code></td><td><code>@expose value</code></td><td>—</td><td>Two-way bound value (flow:model).</td></tr>
|
|
689
|
+
<tr><td><code>options</code></td><td><code>{ label, value }[]</code></td><td>—</td><td>Option list.</td></tr>
|
|
690
|
+
<tr><td><code>placeholder</code></td><td><code>string</code></td><td>—</td><td>Optional empty first option.</td></tr>
|
|
691
|
+
</tbody>
|
|
692
|
+
</table>
|
|
693
|
+
|
|
694
|
+
<a id="components-radio-group"></a>
|
|
695
|
+
|
|
696
|
+
## RadioGroup
|
|
697
|
+
|
|
698
|
+
Segmented radio set bound to a value.
|
|
699
|
+
|
|
700
|
+
### RadioGroup installation
|
|
701
|
+
|
|
702
|
+
```sh
|
|
703
|
+
bun zt flow:add radio-group
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Or import directly from the package: `import { RadioGroup } from "@zerotal/flow-ui";`
|
|
707
|
+
|
|
708
|
+
### RadioGroup preview
|
|
709
|
+
|
|
710
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
711
|
+
<div role="radiogroup" x-data="{}" class="flow-radiogroup grid gap-2 w-56"><div role="radio" aria-checked="false" class="flow-radio flex cursor-pointer items-center justify-center rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground" tabindex="-1">Free</div><div role="radio" aria-checked="true" class="flow-radio flex cursor-pointer items-center justify-center rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground" data-checked="" tabindex="0">Pro</div><div role="radio" aria-checked="false" class="flow-radio flex cursor-pointer items-center justify-center rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring data-[checked]:border-primary data-[checked]:bg-primary data-[checked]:text-primary-foreground" tabindex="-1">Enterprise</div></div>
|
|
712
|
+
</div>
|
|
713
|
+
|
|
714
|
+
### RadioGroup usage
|
|
715
|
+
|
|
716
|
+
```tsx
|
|
717
|
+
<RadioGroup bind={this.plan} options={[{ label: "Pro", value: "pro" }]} />
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### RadioGroup props
|
|
721
|
+
|
|
722
|
+
<table>
|
|
723
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
724
|
+
<tbody>
|
|
725
|
+
<tr><td><code>bind</code></td><td><code>@expose value</code></td><td>—</td><td>Two-way bound value.</td></tr>
|
|
726
|
+
<tr><td><code>options</code></td><td><code>{ label, value }[]</code></td><td>—</td><td>Option list.</td></tr>
|
|
727
|
+
<tr><td><code>optionClass</code></td><td><code>string</code></td><td>—</td><td>Per-option classes.</td></tr>
|
|
728
|
+
</tbody>
|
|
729
|
+
</table>
|
|
730
|
+
|
|
731
|
+
<a id="components-dialog"></a>
|
|
732
|
+
|
|
733
|
+
## Dialog
|
|
734
|
+
|
|
735
|
+
Modal dialog (focus-trapped).
|
|
736
|
+
|
|
737
|
+
### Dialog installation
|
|
738
|
+
|
|
739
|
+
```sh
|
|
740
|
+
bun zt flow:add dialog
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Or import directly from the package: `import { Dialog } from "@zerotal/flow-ui";`
|
|
744
|
+
|
|
745
|
+
### Dialog preview
|
|
746
|
+
|
|
747
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
748
|
+
<button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Edit profile</button>
|
|
749
|
+
</div>
|
|
750
|
+
|
|
751
|
+
### Dialog usage
|
|
752
|
+
|
|
753
|
+
```tsx
|
|
754
|
+
<Button onClick={() => (this.open = true)}>Edit profile</Button>
|
|
755
|
+
|
|
756
|
+
<Dialog show={this.open} title="Edit profile" description="Make changes here.">
|
|
757
|
+
<form onSubmit={this.save} class="flex flex-col gap-3">
|
|
758
|
+
<Field label="Name"><Input value={this.form.name} /></Field>
|
|
759
|
+
<Button type="submit">Save</Button>
|
|
760
|
+
</form>
|
|
761
|
+
</Dialog>
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### Dialog props
|
|
765
|
+
|
|
766
|
+
<table>
|
|
767
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
768
|
+
<tbody>
|
|
769
|
+
<tr><td><code>show</code></td><td><code>@expose boolean</code></td><td>—</td><td>Visibility (focus-trapped while open).</td></tr>
|
|
770
|
+
<tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>Dialog title (wires aria-labelledby).</td></tr>
|
|
771
|
+
<tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>Supporting text (aria-describedby).</td></tr>
|
|
772
|
+
<tr><td><code>closable</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Show the × + allow backdrop/Escape close.</td></tr>
|
|
773
|
+
</tbody>
|
|
774
|
+
</table>
|
|
775
|
+
|
|
776
|
+
<a id="components-sheet"></a>
|
|
777
|
+
|
|
778
|
+
## Sheet
|
|
779
|
+
|
|
780
|
+
Edge-anchored slide-over panel.
|
|
781
|
+
|
|
782
|
+
### Sheet installation
|
|
783
|
+
|
|
784
|
+
```sh
|
|
785
|
+
bun zt flow:add sheet
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
Or import directly from the package: `import { Sheet } from "@zerotal/flow-ui";`
|
|
789
|
+
|
|
790
|
+
### Sheet preview
|
|
791
|
+
|
|
792
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
793
|
+
<button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Open sheet</button>
|
|
794
|
+
</div>
|
|
795
|
+
|
|
796
|
+
### Sheet usage
|
|
797
|
+
|
|
798
|
+
```tsx
|
|
799
|
+
<Button onClick={() => (this.open = true)}>Open</Button>
|
|
800
|
+
|
|
801
|
+
<Sheet show={this.open} side="right" title="Edit profile">…</Sheet>
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### Sheet props
|
|
805
|
+
|
|
806
|
+
<table>
|
|
807
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
808
|
+
<tbody>
|
|
809
|
+
<tr><td><code>show</code></td><td><code>@expose boolean</code></td><td>—</td><td>Visibility (focus-trapped while open).</td></tr>
|
|
810
|
+
<tr><td><code>side</code></td><td><code>"left" | "right" | "top" | "bottom"</code></td><td><code>"right"</code></td><td>Edge to slide from.</td></tr>
|
|
811
|
+
<tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>Header title.</td></tr>
|
|
812
|
+
</tbody>
|
|
813
|
+
</table>
|
|
814
|
+
|
|
815
|
+
<a id="components-dropdown-menu"></a>
|
|
816
|
+
|
|
817
|
+
## DropdownMenu
|
|
818
|
+
|
|
819
|
+
Keyboard-navigable menu (+ item/label/separator).
|
|
820
|
+
|
|
821
|
+
### DropdownMenu installation
|
|
822
|
+
|
|
823
|
+
```sh
|
|
824
|
+
bun zt flow:add dropdown-menu
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Or import directly from the package: `import { DropdownMenu } from "@zerotal/flow-ui";`
|
|
828
|
+
|
|
829
|
+
### DropdownMenu preview
|
|
830
|
+
|
|
831
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
832
|
+
<button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Options ▾</button>
|
|
833
|
+
</div>
|
|
834
|
+
|
|
835
|
+
### DropdownMenu usage
|
|
836
|
+
|
|
837
|
+
```tsx
|
|
838
|
+
<DropdownMenu label="Options">
|
|
839
|
+
<DropdownMenuLabel>My account</DropdownMenuLabel>
|
|
840
|
+
<DropdownMenuItem onClick={this.profile}>Profile</DropdownMenuItem>
|
|
841
|
+
<DropdownMenuSeparator />
|
|
842
|
+
<DropdownMenuItem variant="destructive" onClick={this.signOut}>
|
|
843
|
+
Sign out
|
|
844
|
+
</DropdownMenuItem>
|
|
845
|
+
</DropdownMenu>
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### DropdownMenu props
|
|
849
|
+
|
|
850
|
+
<table>
|
|
851
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
852
|
+
<tbody>
|
|
853
|
+
<tr><td><code>label</code></td><td><code>node</code></td><td>—</td><td>Default trigger label (or pass `trigger`).</td></tr>
|
|
854
|
+
<tr><td><code>align</code></td><td><code>"left" | "right"</code></td><td><code>"left"</code></td><td>Panel alignment.</td></tr>
|
|
855
|
+
</tbody>
|
|
856
|
+
</table>
|
|
857
|
+
|
|
858
|
+
<a id="components-tabs"></a>
|
|
859
|
+
|
|
860
|
+
## Tabs
|
|
861
|
+
|
|
862
|
+
Tabbed panels with a pill tablist.
|
|
863
|
+
|
|
864
|
+
### Tabs installation
|
|
865
|
+
|
|
866
|
+
```sh
|
|
867
|
+
bun zt flow:add tabs
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
Or import directly from the package: `import { Tabs } from "@zerotal/flow-ui";`
|
|
871
|
+
|
|
872
|
+
### Tabs preview
|
|
873
|
+
|
|
874
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
875
|
+
<div class="w-80"><div x-data="flowTabs({ tab: "0" })" class="flex flex-col"><div role="tablist" x-on:keydown="onKey($event)" class="inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground"><button type="button" role="tab" id="flow-tab-0" aria-controls="flow-tabpanel-0" :aria-selected="tab === "0"" :tabindex="tab === "0" ? 0 : -1" x-on:click="tab = "0"" :class="tab === "0" ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium outline-none transition-all focus-visible:ring-2 focus-visible:ring-ring">Account</button><button type="button" role="tab" id="flow-tab-1" aria-controls="flow-tabpanel-1" :aria-selected="tab === "1"" :tabindex="tab === "1" ? 0 : -1" x-on:click="tab = "1"" :class="tab === "1" ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium outline-none transition-all focus-visible:ring-2 focus-visible:ring-ring">Password</button></div><div role="tabpanel" id="flow-tabpanel-0" aria-labelledby="flow-tab-0" tabindex="0" x-show="tab === "0"" x-cloak class="mt-2 outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md"><p class="text-sm text-muted-foreground">Account settings.</p></div><div role="tabpanel" id="flow-tabpanel-1" aria-labelledby="flow-tab-1" tabindex="0" x-show="tab === "1"" x-cloak class="mt-2 outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-md"><p class="text-sm text-muted-foreground">Change your password.</p></div></div></div>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
878
|
+
### Tabs usage
|
|
879
|
+
|
|
880
|
+
```tsx
|
|
881
|
+
<Tabs
|
|
882
|
+
items={[
|
|
883
|
+
{ label: "Account", content: <AccountForm /> },
|
|
884
|
+
{ label: "Password", content: <PasswordForm /> },
|
|
885
|
+
]}
|
|
886
|
+
/>
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### Tabs props
|
|
890
|
+
|
|
891
|
+
<table>
|
|
892
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
893
|
+
<tbody>
|
|
894
|
+
<tr><td><code>items</code></td><td><code>{ label, content, name? }[]</code></td><td>—</td><td>Tabs + their panels.</td></tr>
|
|
895
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes.</td></tr>
|
|
896
|
+
</tbody>
|
|
897
|
+
</table>
|
|
898
|
+
|
|
899
|
+
<a id="components-alert"></a>
|
|
900
|
+
|
|
901
|
+
## Alert
|
|
902
|
+
|
|
903
|
+
Inline alert (+ title/description).
|
|
904
|
+
|
|
905
|
+
### Alert installation
|
|
906
|
+
|
|
907
|
+
```sh
|
|
908
|
+
bun zt flow:add alert
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
Or import directly from the package: `import { Alert } from "@zerotal/flow-ui";`
|
|
912
|
+
|
|
913
|
+
### Alert preview
|
|
914
|
+
|
|
915
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
916
|
+
<div class="flex w-96 flex-col gap-3"><div role="status" x-data="{ shown: true }" x-show="shown" class="relative w-full rounded-lg border px-4 py-3 text-sm bg-background text-foreground flex items-start gap-3"><div class="min-w-0 flex-1"><div class="mb-1 font-medium leading-none tracking-tight">Heads up!</div><div class="text-sm [&_p]:leading-relaxed">You can add components to your app using the CLI.</div></div></div><div role="alert" x-data="{ shown: true }" x-show="shown" class="relative w-full rounded-lg border px-4 py-3 text-sm border-destructive/50 text-destructive flex items-start gap-3"><div class="min-w-0 flex-1"><div class="mb-1 font-medium leading-none tracking-tight">Error</div><div class="text-sm [&_p]:leading-relaxed">Your session has expired.</div></div></div></div>
|
|
917
|
+
</div>
|
|
918
|
+
|
|
919
|
+
### Alert usage
|
|
920
|
+
|
|
921
|
+
```tsx
|
|
922
|
+
<Alert title="Heads up!">You can add components to your app.</Alert>
|
|
923
|
+
<Alert variant="destructive" title="Error">Something went wrong.</Alert>
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
### Alert props
|
|
927
|
+
|
|
928
|
+
<table>
|
|
929
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
930
|
+
<tbody>
|
|
931
|
+
<tr><td><code>variant</code></td><td><code>"default" | "destructive"</code></td><td><code>"default"</code></td><td>Visual style + ARIA role.</td></tr>
|
|
932
|
+
<tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>Bold title line.</td></tr>
|
|
933
|
+
<tr><td><code>dismissible</code></td><td><code>boolean</code></td><td><code>false</code></td><td>Show a client-only dismiss button.</td></tr>
|
|
934
|
+
</tbody>
|
|
935
|
+
</table>
|
|
936
|
+
|
|
937
|
+
<a id="components-tooltip"></a>
|
|
938
|
+
|
|
939
|
+
## Tooltip
|
|
940
|
+
|
|
941
|
+
Hover/focus tooltip.
|
|
942
|
+
|
|
943
|
+
### Tooltip installation
|
|
944
|
+
|
|
945
|
+
```sh
|
|
946
|
+
bun zt flow:add tooltip
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
Or import directly from the package: `import { Tooltip } from "@zerotal/flow-ui";`
|
|
950
|
+
|
|
951
|
+
### Tooltip preview
|
|
952
|
+
|
|
953
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
954
|
+
<button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 w-9">+</button>
|
|
955
|
+
</div>
|
|
956
|
+
|
|
957
|
+
### Tooltip usage
|
|
958
|
+
|
|
959
|
+
```tsx
|
|
960
|
+
<Tooltip content="Add to library">
|
|
961
|
+
<Button size="icon">+</Button>
|
|
962
|
+
</Tooltip>
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
### Tooltip props
|
|
966
|
+
|
|
967
|
+
<table>
|
|
968
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
969
|
+
<tbody>
|
|
970
|
+
<tr><td><code>content</code></td><td><code>node</code></td><td>—</td><td>Tooltip text.</td></tr>
|
|
971
|
+
<tr><td><code>placement</code></td><td><code>"top" | "bottom"</code></td><td><code>"top"</code></td><td>Bubble position.</td></tr>
|
|
972
|
+
</tbody>
|
|
973
|
+
</table>
|
|
974
|
+
|
|
975
|
+
<a id="components-table"></a>
|
|
976
|
+
|
|
977
|
+
## Table
|
|
978
|
+
|
|
979
|
+
URL-sortable data table.
|
|
980
|
+
|
|
981
|
+
### Table installation
|
|
982
|
+
|
|
983
|
+
```sh
|
|
984
|
+
bun zt flow:add table
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
Or import directly from the package: `import { Table } from "@zerotal/flow-ui";`
|
|
988
|
+
|
|
989
|
+
### Table preview
|
|
990
|
+
|
|
991
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
992
|
+
<div class="w-96"><table class="w-full caption-bottom border-collapse text-sm"><thead class="[&_tr]:border-b [&_tr]:border-border"><tr><th scope="col" class="h-10 px-2 text-left align-middle font-medium text-muted-foreground">Name</th><th scope="col" class="h-10 px-2 text-left align-middle font-medium text-muted-foreground">Role</th></tr></thead><tbody><tr flow:key="Ada Lovelace" class="border-b border-border transition-colors hover:bg-muted/50"><td class="p-2 align-middle">Ada Lovelace</td><td class="p-2 align-middle">Engineer</td></tr><tr flow:key="Alan Turing" class="border-b border-border transition-colors hover:bg-muted/50"><td class="p-2 align-middle">Alan Turing</td><td class="p-2 align-middle">Researcher</td></tr></tbody></table></div>
|
|
993
|
+
</div>
|
|
994
|
+
|
|
995
|
+
### Table usage
|
|
996
|
+
|
|
997
|
+
```tsx
|
|
998
|
+
<Table
|
|
999
|
+
columns={[
|
|
1000
|
+
{ key: "name", label: "Name", sortable: true },
|
|
1001
|
+
{ key: "role", label: "Role" },
|
|
1002
|
+
]}
|
|
1003
|
+
rows={people}
|
|
1004
|
+
sortBy={this.sortBy}
|
|
1005
|
+
sortDir={this.sortDir}
|
|
1006
|
+
hover
|
|
1007
|
+
/>
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
### Table props
|
|
1011
|
+
|
|
1012
|
+
<table>
|
|
1013
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1014
|
+
<tbody>
|
|
1015
|
+
<tr><td><code>columns</code></td><td><code>TableColumn[]</code></td><td>—</td><td>Column defs (key, label, sortable?, render?).</td></tr>
|
|
1016
|
+
<tr><td><code>rows</code></td><td><code>T[]</code></td><td>—</td><td>Row data.</td></tr>
|
|
1017
|
+
<tr><td><code>sortBy / sortDir</code></td><td><code>@url state</code></td><td>—</td><td>Bind to URL sort state for sortable headers.</td></tr>
|
|
1018
|
+
</tbody>
|
|
1019
|
+
</table>
|
|
1020
|
+
|
|
1021
|
+
<a id="components-popover"></a>
|
|
1022
|
+
|
|
1023
|
+
## Popover
|
|
1024
|
+
|
|
1025
|
+
Floating panel anchored to a trigger.
|
|
1026
|
+
|
|
1027
|
+
### Popover installation
|
|
1028
|
+
|
|
1029
|
+
```sh
|
|
1030
|
+
bun zt flow:add popover
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
Or import directly from the package: `import { Popover } from "@zerotal/flow-ui";`
|
|
1034
|
+
|
|
1035
|
+
### Popover preview
|
|
1036
|
+
|
|
1037
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1038
|
+
<div x-data="{ open: false }" class="flow-popover relative inline-block"><span x-on:click="open = !open" :aria-expanded="open" :data-open="open ? '' : null" role="button" tabindex="0" class="flow-popover-button cursor-pointer outline-none"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Options</button></span><div x-show="open" x-cloak x-transition x-on:click.outside="open = false" x-on:keydown.escape.window="open = false" :data-open="open ? '' : null" class="flow-popover-panel absolute top-full mt-2 left-0 z-50 min-w-[8rem] rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none"><p class="text-sm">Anything at all.</p></div></div>
|
|
1039
|
+
</div>
|
|
1040
|
+
|
|
1041
|
+
### Popover usage
|
|
1042
|
+
|
|
1043
|
+
```tsx
|
|
1044
|
+
<Popover trigger={<Button variant="outline">Options</Button>}>
|
|
1045
|
+
<p class="text-sm">Anything at all.</p>
|
|
1046
|
+
</Popover>
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
### Popover props
|
|
1050
|
+
|
|
1051
|
+
<table>
|
|
1052
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1053
|
+
<tbody>
|
|
1054
|
+
<tr><td><code>trigger</code></td><td><code>node</code></td><td>—</td><td>The element that opens the panel.</td></tr>
|
|
1055
|
+
<tr><td><code>side</code></td><td><code>"top" | "right" | "bottom" | "left"</code></td><td><code>"bottom"</code></td><td>Which edge the panel sits on.</td></tr>
|
|
1056
|
+
<tr><td><code>align</code></td><td><code>"start" | "center" | "end"</code></td><td><code>"start"</code></td><td>How the panel lines up along that edge.</td></tr>
|
|
1057
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
1058
|
+
</tbody>
|
|
1059
|
+
</table>
|
|
1060
|
+
|
|
1061
|
+
<a id="components-hover-card"></a>
|
|
1062
|
+
|
|
1063
|
+
## HoverCard
|
|
1064
|
+
|
|
1065
|
+
Preview panel shown on hover.
|
|
1066
|
+
|
|
1067
|
+
### HoverCard installation
|
|
1068
|
+
|
|
1069
|
+
```sh
|
|
1070
|
+
bun zt flow:add hover-card
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
Or import directly from the package: `import { HoverCard } from "@zerotal/flow-ui";`
|
|
1074
|
+
|
|
1075
|
+
### HoverCard preview
|
|
1076
|
+
|
|
1077
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1078
|
+
<div x-data="{
|
|
1079
|
+
open: false,
|
|
1080
|
+
t: null,
|
|
1081
|
+
show() { clearTimeout(this.t); this.t = setTimeout(() => (this.open = true), 300); },
|
|
1082
|
+
hide() { clearTimeout(this.t); this.t = setTimeout(() => (this.open = false), 150); }
|
|
1083
|
+
}" class="relative inline-block" x-on:mouseenter="show()" x-on:mouseleave="hide()"><span tabindex="0" x-on:focus="open = true" x-on:blur="hide()" class="cursor-default outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:rounded-sm"><span class="underline">@ada</span></span><div x-show="open" x-cloak x-transition class="absolute z-50 w-64 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md top-full mt-2 left-0"><p class="text-sm font-medium">Ada Mokoena</p><p class="text-xs text-muted-foreground">Joined in 2024</p></div></div>
|
|
1084
|
+
</div>
|
|
1085
|
+
|
|
1086
|
+
### HoverCard usage
|
|
1087
|
+
|
|
1088
|
+
```tsx
|
|
1089
|
+
<HoverCard trigger={<a href="/users/1">@ada</a>}>
|
|
1090
|
+
<p class="text-sm font-medium">Ada Mokoena</p>
|
|
1091
|
+
</HoverCard>
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
### HoverCard props
|
|
1095
|
+
|
|
1096
|
+
<table>
|
|
1097
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1098
|
+
<tbody>
|
|
1099
|
+
<tr><td><code>trigger</code></td><td><code>node</code></td><td>—</td><td>What is hovered.</td></tr>
|
|
1100
|
+
<tr><td><code>openDelay</code></td><td><code>number</code></td><td><code>300</code></td><td>Milliseconds before it opens.</td></tr>
|
|
1101
|
+
<tr><td><code>closeDelay</code></td><td><code>number</code></td><td><code>150</code></td><td>Grace period so the pointer can reach the panel.</td></tr>
|
|
1102
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
1103
|
+
</tbody>
|
|
1104
|
+
</table>
|
|
1105
|
+
|
|
1106
|
+
<a id="components-alert-dialog"></a>
|
|
1107
|
+
|
|
1108
|
+
## AlertDialog
|
|
1109
|
+
|
|
1110
|
+
Confirm before something irreversible.
|
|
1111
|
+
|
|
1112
|
+
### AlertDialog installation
|
|
1113
|
+
|
|
1114
|
+
```sh
|
|
1115
|
+
bun zt flow:add alert-dialog
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
Or import directly from the package: `import { AlertDialog } from "@zerotal/flow-ui";`
|
|
1119
|
+
|
|
1120
|
+
### AlertDialog preview
|
|
1121
|
+
|
|
1122
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1123
|
+
<div role="dialog" aria-modal="true" class="fixed inset-0 z-50 flex items-center justify-center p-4"><div class="absolute inset-0 bg-black/50"></div><div class="relative z-10 w-full max-w-lg rounded-lg border border-border bg-background p-6 text-foreground shadow-lg" flow:transition><div class="mb-4 flex flex-col gap-1.5 pr-6"><h2 class="text-lg font-semibold leading-none tracking-tight">Delete this product?</h2><p class="text-sm text-muted-foreground">It will be removed from every order. This cannot be undone.</p></div><div class="mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Cancel</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground hover:bg-destructive/90 h-9 px-4 py-2">Delete</button></div></div></div>
|
|
1124
|
+
</div>
|
|
1125
|
+
|
|
1126
|
+
### AlertDialog usage
|
|
1127
|
+
|
|
1128
|
+
```tsx
|
|
1129
|
+
<AlertDialog
|
|
1130
|
+
show={this.confirming}
|
|
1131
|
+
title="Delete this product?"
|
|
1132
|
+
description="It will be removed from every order. This cannot be undone."
|
|
1133
|
+
confirmLabel="Delete"
|
|
1134
|
+
onConfirm={this.destroy}
|
|
1135
|
+
/>
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
### AlertDialog props
|
|
1139
|
+
|
|
1140
|
+
<table>
|
|
1141
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1142
|
+
<tbody>
|
|
1143
|
+
<tr><td><code>show</code></td><td><code>boolean</code></td><td>—</td><td>Bound @expose boolean controlling visibility.</td></tr>
|
|
1144
|
+
<tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>The question.</td></tr>
|
|
1145
|
+
<tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>What will happen. Worth a full sentence.</td></tr>
|
|
1146
|
+
<tr><td><code>onConfirm</code></td><td><code>handler</code></td><td>—</td><td>Server action for the confirming choice.</td></tr>
|
|
1147
|
+
<tr><td><code>destructive</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Style the confirm button as destructive.</td></tr>
|
|
1148
|
+
</tbody>
|
|
1149
|
+
</table>
|
|
1150
|
+
|
|
1151
|
+
<a id="components-command"></a>
|
|
1152
|
+
|
|
1153
|
+
## Command
|
|
1154
|
+
|
|
1155
|
+
Searchable command menu (⌘K palette).
|
|
1156
|
+
|
|
1157
|
+
### Command installation
|
|
1158
|
+
|
|
1159
|
+
```sh
|
|
1160
|
+
bun zt flow:add command
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
Or import directly from the package: `import { Command } from "@zerotal/flow-ui";`
|
|
1164
|
+
|
|
1165
|
+
### Command preview
|
|
1166
|
+
|
|
1167
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1168
|
+
<p class="text-sm text-muted-foreground">Mounted hidden; press <kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground" x-data="{ mac: false }" x-init="mac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)" x-text="mac ? '⌘' : 'Ctrl'">Ctrl</kbd> <kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">K</kbd> to open it.</p><div x-data="flowCommand({"items":[{"label":"Products","href":"#","group":"Go to"},{"label":"Orders","href":"#","group":"Go to"},{"label":"New order","href":"#","group":"Create"}],"hotkey":"k"})" x-show="open" x-cloak x-transition.opacity x-on:click="if ($event.target === $el) hide()" x-on:keydown.escape.window="hide()" class="fixed inset-0 z-50 flex items-start justify-center bg-black/50 p-4 pt-[12vh]"><div role="dialog" aria-modal="true" aria-label="Command menu" class="w-full max-w-lg overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-2xl"><div class="flex items-center gap-2 border-b border-border px-3"><svg class="h-4 w-4 shrink-0 text-muted-foreground" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg><input x-ref="field" type="text" role="combobox" aria-expanded="true" aria-controls="flow-command-list" autocomplete="off" placeholder="Search…" x-model="query" x-on:input="active = 0" x-on:keydown.arrow-down.prevent="move(1)" x-on:keydown.arrow-up.prevent="move(-1)" x-on:keydown.enter.prevent="choose()" class="h-12 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"></div><div id="flow-command-list" role="listbox" class="max-h-80 overflow-y-auto p-2"><template x-for="(item, i) in results()" :key="item.label + i"><div><div x-show="startsGroup(i)" x-text="item.group" class="px-2 pb-1 pt-3 text-[11px] font-medium uppercase tracking-wide text-muted-foreground"></div><div role="option" :aria-selected="active === i" x-on:click="choose(i)" x-on:mousemove="active = i" :class="active === i && 'bg-accent text-accent-foreground'" class="flex cursor-pointer items-center gap-2 rounded-md px-2 py-2 text-sm"><span class="flex-1 truncate" x-text="item.label"></span><span x-show="item.shortcut" x-text="item.shortcut" class="text-[11px] text-muted-foreground"></span></div></div></template><p x-show="results().length === 0" class="px-4 py-8 text-center text-sm text-muted-foreground">Nothing found.</p></div></div></div>
|
|
1169
|
+
</div>
|
|
1170
|
+
|
|
1171
|
+
### Command usage
|
|
1172
|
+
|
|
1173
|
+
```tsx
|
|
1174
|
+
<Command
|
|
1175
|
+
items={[
|
|
1176
|
+
{ label: "Products", href: "/admin/products", group: "Go to" },
|
|
1177
|
+
{ label: "New order", href: "/admin/orders/create", group: "Create" },
|
|
1178
|
+
]}
|
|
1179
|
+
/>
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
### Command props
|
|
1183
|
+
|
|
1184
|
+
<table>
|
|
1185
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1186
|
+
<tbody>
|
|
1187
|
+
<tr><td><code>items</code></td><td><code>CommandItem[]</code></td><td>—</td><td>Destinations and actions, each with an optional group.</td></tr>
|
|
1188
|
+
<tr><td><code>hotkey</code></td><td><code>string | null</code></td><td><code>"k"</code></td><td>Key that opens it with the platform modifier.</td></tr>
|
|
1189
|
+
<tr><td><code>placeholder</code></td><td><code>string</code></td><td><code>"Search…"</code></td><td>Placeholder in the search box.</td></tr>
|
|
1190
|
+
<tr><td><code>emptyMessage</code></td><td><code>string</code></td><td><code>"Nothing found."</code></td><td>Shown when nothing matches.</td></tr>
|
|
1191
|
+
</tbody>
|
|
1192
|
+
</table>
|
|
1193
|
+
|
|
1194
|
+
<a id="components-context-menu"></a>
|
|
1195
|
+
|
|
1196
|
+
## ContextMenu
|
|
1197
|
+
|
|
1198
|
+
Right-click menu.
|
|
1199
|
+
|
|
1200
|
+
### ContextMenu installation
|
|
1201
|
+
|
|
1202
|
+
```sh
|
|
1203
|
+
bun zt flow:add context-menu
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
Or import directly from the package: `import { ContextMenu } from "@zerotal/flow-ui";`
|
|
1207
|
+
|
|
1208
|
+
### ContextMenu preview
|
|
1209
|
+
|
|
1210
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1211
|
+
<div x-data="{ open: false, x: 0, y: 0 }" x-on:contextmenu=" $event.preventDefault(); open = true; $nextTick(() => { const menu = $refs.menu; const w = menu.offsetWidth, h = menu.offsetHeight; x = Math.min($event.clientX, window.innerWidth - w - 8); y = Math.min($event.clientY, window.innerHeight - h - 8); }); " class="contents"><div class="rounded-md border border-dashed border-border px-4 py-6 text-center text-sm text-muted-foreground">Right-click me</div><div x-ref="menu" x-show="open" x-cloak x-on:click.outside="open = false" x-on:keydown.escape.window="open = false" x-bind:style="`position:fixed;left:${x}px;top:${y}px`" role="menu" class="z-50 min-w-44 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Open</span><span class="text-xs text-muted-foreground">↵</span></button><div role="separator" class="-mx-1 my-1 h-px bg-border"></div><button type="button" role="menuitem" class="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 text-destructive hover:bg-destructive/10"><span class="flex-1">Delete</span></button></div></div>
|
|
1212
|
+
</div>
|
|
1213
|
+
|
|
1214
|
+
### ContextMenu usage
|
|
1215
|
+
|
|
1216
|
+
```tsx
|
|
1217
|
+
<ContextMenu
|
|
1218
|
+
items={[
|
|
1219
|
+
{ label: "Open", action: "$flow.open(id)" },
|
|
1220
|
+
{ separator: true },
|
|
1221
|
+
{ label: "Delete", action: "$flow.remove(id)", danger: true },
|
|
1222
|
+
]}
|
|
1223
|
+
>
|
|
1224
|
+
<div>Right-click me</div>
|
|
1225
|
+
</ContextMenu>
|
|
1226
|
+
```
|
|
1227
|
+
|
|
1228
|
+
### ContextMenu props
|
|
1229
|
+
|
|
1230
|
+
<table>
|
|
1231
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1232
|
+
<tbody>
|
|
1233
|
+
<tr><td><code>items</code></td><td><code>ContextMenuItem[]</code></td><td>—</td><td>Entries, dividers and destructive actions.</td></tr>
|
|
1234
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
1235
|
+
</tbody>
|
|
1236
|
+
</table>
|
|
1237
|
+
|
|
1238
|
+
<a id="components-menubar"></a>
|
|
1239
|
+
|
|
1240
|
+
## Menubar
|
|
1241
|
+
|
|
1242
|
+
Application menu bar.
|
|
1243
|
+
|
|
1244
|
+
### Menubar installation
|
|
1245
|
+
|
|
1246
|
+
```sh
|
|
1247
|
+
bun zt flow:add menubar
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
Or import directly from the package: `import { Menubar } from "@zerotal/flow-ui";`
|
|
1251
|
+
|
|
1252
|
+
### Menubar preview
|
|
1253
|
+
|
|
1254
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1255
|
+
<div x-data="{ open: -1 }" x-on:click.outside="open = -1" x-on:keydown.escape.window="open = -1" role="menubar" class="flex items-center gap-0.5 rounded-md border border-border bg-card p-1"><div class="relative"><button type="button" role="menuitem" x-on:click="open = open === 0 ? -1 : 0" x-on:mouseenter="if (open !== -1) open = 0" x-bind:aria-expanded="open === 0" x-bind:class="open === 0 && 'bg-accent text-accent-foreground'" class="rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground">File</button><div x-show="open === 0" x-cloak x-transition role="menu" class="absolute left-0 top-full z-50 mt-1 min-w-48 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">New</span><span class="text-xs text-muted-foreground">⌘N</span></button><div role="separator" class="-mx-1 my-1 h-px bg-border"></div><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Export</span></button></div></div><div class="relative"><button type="button" role="menuitem" x-on:click="open = open === 1 ? -1 : 1" x-on:mouseenter="if (open !== -1) open = 1" x-bind:aria-expanded="open === 1" x-bind:class="open === 1 && 'bg-accent text-accent-foreground'" class="rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground">Edit</button><div x-show="open === 1" x-cloak x-transition role="menu" class="absolute left-0 top-full z-50 mt-1 min-w-48 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Undo</span><span class="text-xs text-muted-foreground">⌘Z</span></button><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Redo</span><span class="text-xs text-muted-foreground">⇧⌘Z</span></button></div></div><div class="relative"><button type="button" role="menuitem" x-on:click="open = open === 2 ? -1 : 2" x-on:mouseenter="if (open !== -1) open = 2" x-bind:aria-expanded="open === 2" x-bind:class="open === 2 && 'bg-accent text-accent-foreground'" class="rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground">View</button><div x-show="open === 2" x-cloak x-transition role="menu" class="absolute left-0 top-full z-50 mt-1 min-w-48 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Zoom in</span></button><button type="button" role="menuitem" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"><span class="flex-1">Zoom out</span></button></div></div></div>
|
|
1256
|
+
</div>
|
|
1257
|
+
|
|
1258
|
+
### Menubar usage
|
|
1259
|
+
|
|
1260
|
+
```tsx
|
|
1261
|
+
<Menubar
|
|
1262
|
+
menus={[
|
|
1263
|
+
{ label: "File", items: [{ label: "New", shortcut: "⌘N" }] },
|
|
1264
|
+
{ label: "Edit", items: [{ label: "Undo", shortcut: "⌘Z" }] },
|
|
1265
|
+
]}
|
|
1266
|
+
/>
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
### Menubar props
|
|
1270
|
+
|
|
1271
|
+
<table>
|
|
1272
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1273
|
+
<tbody>
|
|
1274
|
+
<tr><td><code>menus</code></td><td><code>MenubarMenu[]</code></td><td>—</td><td>Top-level menus, each with its own items.</td></tr>
|
|
1275
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
1276
|
+
</tbody>
|
|
1277
|
+
</table>
|
|
1278
|
+
|
|
1279
|
+
<a id="components-navigation-menu"></a>
|
|
1280
|
+
|
|
1281
|
+
## NavigationMenu
|
|
1282
|
+
|
|
1283
|
+
Site nav with dropdown panels.
|
|
1284
|
+
|
|
1285
|
+
### NavigationMenu installation
|
|
1286
|
+
|
|
1287
|
+
```sh
|
|
1288
|
+
bun zt flow:add navigation-menu
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
Or import directly from the package: `import { NavigationMenu } from "@zerotal/flow-ui";`
|
|
1292
|
+
|
|
1293
|
+
### NavigationMenu preview
|
|
1294
|
+
|
|
1295
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1296
|
+
<nav class="relative flex items-center gap-1"><a href="#" flow:navigate class="inline-flex h-9 items-center gap-1 rounded-md px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring">Docs</a><div x-data="{ open: false }" x-on:mouseenter="open = true" x-on:mouseleave="open = false" x-on:focusin="open = true" x-on:focusout="open = false" class="relative"><button type="button" x-bind:aria-expanded="open" class="inline-flex h-9 items-center gap-1 rounded-md px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring">Products<svg class="h-3 w-3 transition-transform" x-bind:class="open && 'rotate-180'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></button><div x-show="open" x-cloak x-transition class="absolute left-0 top-full z-50 mt-1.5 rounded-lg border border-border bg-popover p-2 text-popover-foreground shadow-lg w-72 space-y-1"><a href="#" flow:navigate class="flex gap-3 rounded-md p-3 transition-colors hover:bg-accent hover:text-accent-foreground"><span class="min-w-0"><span class="block text-sm font-medium">Admin</span><span class="mt-0.5 block text-xs leading-snug text-muted-foreground">Declarative back office</span></span></a><a href="#" flow:navigate class="flex gap-3 rounded-md p-3 transition-colors hover:bg-accent hover:text-accent-foreground"><span class="min-w-0"><span class="block text-sm font-medium">Flow</span><span class="mt-0.5 block text-xs leading-snug text-muted-foreground">Server-driven reactivity</span></span></a></div></div></nav>
|
|
1297
|
+
</div>
|
|
1298
|
+
|
|
1299
|
+
### NavigationMenu usage
|
|
1300
|
+
|
|
1301
|
+
```tsx
|
|
1302
|
+
<NavigationMenu
|
|
1303
|
+
items={[
|
|
1304
|
+
{ label: "Docs", href: "/docs" },
|
|
1305
|
+
{ label: "Products", panel: [{ label: "Admin", href: "/admin", description: "Back office" }] },
|
|
1306
|
+
]}
|
|
1307
|
+
/>
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
### NavigationMenu props
|
|
1311
|
+
|
|
1312
|
+
<table>
|
|
1313
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1314
|
+
<tbody>
|
|
1315
|
+
<tr><td><code>items</code></td><td><code>NavigationMenuItem[]</code></td><td>—</td><td>Links, some of which open a panel.</td></tr>
|
|
1316
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
1317
|
+
</tbody>
|
|
1318
|
+
</table>
|
|
1319
|
+
|
|
1320
|
+
<a id="components-sidebar"></a>
|
|
1321
|
+
|
|
1322
|
+
## Sidebar
|
|
1323
|
+
|
|
1324
|
+
App shell nav rail with a mobile drawer.
|
|
1325
|
+
|
|
1326
|
+
### Sidebar installation
|
|
1327
|
+
|
|
1328
|
+
```sh
|
|
1329
|
+
bun zt flow:add sidebar
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
Or import directly from the package: `import { Sidebar } from "@zerotal/flow-ui";`
|
|
1333
|
+
|
|
1334
|
+
### Sidebar preview
|
|
1335
|
+
|
|
1336
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1337
|
+
<div class="h-64 overflow-hidden rounded-lg border border-border"><aside class="flex w-64 flex-col border-r border-border bg-card"><a href="/" flow:navigate class="flex h-14 shrink-0 items-center gap-2 border-b border-border px-4"><span class="min-w-0"><span class="block truncate text-sm font-semibold">Zerotal</span><span class="block truncate text-[11px] text-muted-foreground">Back office</span></span></a><nav class="flex-1 space-y-4 overflow-y-auto p-3"><div class="space-y-1"><p class="px-2.5 pb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70">Shop</p><a href="/admin/products" flow:navigate aria-current="page" class="group flex items-center gap-2.5 rounded-md px-2.5 py-2 text-sm font-medium transition-colors hover:bg-accent/50 hover:text-foreground [&[aria-current]]:bg-accent [&[aria-current]]:text-accent-foreground bg-accent text-accent-foreground"><span class="truncate">Products</span><span class="ml-auto inline-flex min-w-5 items-center justify-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold leading-none bg-muted text-muted-foreground">12</span></a><a href="/admin/orders" flow:navigate class="group flex items-center gap-2.5 rounded-md px-2.5 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-accent/50 hover:text-foreground [&[aria-current]]:bg-accent [&[aria-current]]:text-accent-foreground"><span class="truncate">Orders</span></a></div><div class="space-y-1"><p class="px-2.5 pb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70">System</p><a href="/admin/settings" flow:navigate class="group flex items-center gap-2.5 rounded-md px-2.5 py-2 text-sm font-medium transition-colors text-muted-foreground hover:bg-accent/50 hover:text-foreground [&[aria-current]]:bg-accent [&[aria-current]]:text-accent-foreground"><span class="truncate">Settings</span></a></div></nav></aside></div>
|
|
1338
|
+
</div>
|
|
1339
|
+
|
|
1340
|
+
### Sidebar usage
|
|
1341
|
+
|
|
1342
|
+
```tsx
|
|
1343
|
+
<Sidebar
|
|
1344
|
+
brand="Zerotal"
|
|
1345
|
+
tagline="Back office"
|
|
1346
|
+
current={path}
|
|
1347
|
+
groups={[{ label: "Shop", items: [{ label: "Products", href: "/admin/products", badge: 12 }] }]}
|
|
1348
|
+
/>
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
### Sidebar props
|
|
1352
|
+
|
|
1353
|
+
<table>
|
|
1354
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1355
|
+
<tbody>
|
|
1356
|
+
<tr><td><code>groups</code></td><td><code>SidebarGroup[]</code></td><td>—</td><td>Nav tree, optionally nested one level.</td></tr>
|
|
1357
|
+
<tr><td><code>current</code></td><td><code>string</code></td><td>—</td><td>Current path, for marking the active item.</td></tr>
|
|
1358
|
+
<tr><td><code>collapsible</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Render the mobile drawer toggle.</td></tr>
|
|
1359
|
+
<tr><td><code>footer</code></td><td><code>node</code></td><td>—</td><td>Pinned to the bottom — a user menu, a version.</td></tr>
|
|
1360
|
+
</tbody>
|
|
1361
|
+
</table>
|
|
1362
|
+
|
|
1363
|
+
<a id="components-breadcrumb"></a>
|
|
1364
|
+
|
|
1365
|
+
## Breadcrumb
|
|
1366
|
+
|
|
1367
|
+
Trail showing where a page sits.
|
|
1368
|
+
|
|
1369
|
+
### Breadcrumb installation
|
|
1370
|
+
|
|
1371
|
+
```sh
|
|
1372
|
+
bun zt flow:add breadcrumb
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
Or import directly from the package: `import { Breadcrumb } from "@zerotal/flow-ui";`
|
|
1376
|
+
|
|
1377
|
+
### Breadcrumb preview
|
|
1378
|
+
|
|
1379
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1380
|
+
<nav aria-label="Breadcrumb" class="text-xs text-muted-foreground"><ol class="flex flex-wrap items-center gap-1.5"><li class="inline-flex items-center gap-1.5"><a href="#" flow:navigate class="inline-flex items-center gap-1 transition-colors hover:text-foreground">Dashboard</a></li><li class="inline-flex items-center gap-1.5"><svg class="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg><a href="#" flow:navigate class="inline-flex items-center gap-1 transition-colors hover:text-foreground">Products</a></li><li class="inline-flex items-center gap-1.5"><svg class="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg><span aria-current="page" class="inline-flex items-center gap-1 text-foreground">Desk Lamp</span></li></ol></nav>
|
|
1381
|
+
</div>
|
|
1382
|
+
|
|
1383
|
+
### Breadcrumb usage
|
|
1384
|
+
|
|
1385
|
+
```tsx
|
|
1386
|
+
<Breadcrumb
|
|
1387
|
+
items={[
|
|
1388
|
+
{ label: "Dashboard", href: "/admin" },
|
|
1389
|
+
{ label: "Products", href: "/admin/products" },
|
|
1390
|
+
{ label: "Desk Lamp" },
|
|
1391
|
+
]}
|
|
1392
|
+
/>
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
### Breadcrumb props
|
|
1396
|
+
|
|
1397
|
+
<table>
|
|
1398
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1399
|
+
<tbody>
|
|
1400
|
+
<tr><td><code>items</code></td><td><code>BreadcrumbItem[]</code></td><td>—</td><td>The trail. The last item renders as the current page.</td></tr>
|
|
1401
|
+
<tr><td><code>maxItems</code></td><td><code>number</code></td><td>—</td><td>Collapse a longer trail to first + last few.</td></tr>
|
|
1402
|
+
<tr><td><code>separator</code></td><td><code>node</code></td><td>—</td><td>What sits between items.</td></tr>
|
|
1403
|
+
</tbody>
|
|
1404
|
+
</table>
|
|
1405
|
+
|
|
1406
|
+
<a id="components-pagination"></a>
|
|
1407
|
+
|
|
1408
|
+
## Pagination
|
|
1409
|
+
|
|
1410
|
+
Page links with a windowed number range.
|
|
1411
|
+
|
|
1412
|
+
### Pagination installation
|
|
1413
|
+
|
|
1414
|
+
```sh
|
|
1415
|
+
bun zt flow:add pagination
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
Or import directly from the package: `import { Pagination } from "@zerotal/flow-ui";`
|
|
1419
|
+
|
|
1420
|
+
### Pagination preview
|
|
1421
|
+
|
|
1422
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1423
|
+
<nav aria-label="Pagination" class="flex flex-wrap items-center justify-between gap-3"><p class="text-sm text-muted-foreground">61–80 of 231</p><div class="flex items-center gap-1"><a href="#" flow:navigate aria-label="Previous page" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"></path></svg></a><a href="#" flow:navigate aria-label="Page 1" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">1</a><a href="#" flow:navigate aria-label="Page 2" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">2</a><a href="#" flow:navigate aria-label="Page 3" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">3</a><a href="#" flow:navigate aria-current="page" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors bg-primary text-primary-foreground">4</a><a href="#" flow:navigate aria-label="Page 5" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">5</a><a href="#" flow:navigate aria-label="Page 6" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">6</a><span class="px-1 text-sm text-muted-foreground" aria-hidden="true">…</span><a href="#" flow:navigate aria-label="Page 12" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground">12</a><a href="#" flow:navigate aria-label="Next page" class="inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg></a></div></nav>
|
|
1424
|
+
</div>
|
|
1425
|
+
|
|
1426
|
+
### Pagination usage
|
|
1427
|
+
|
|
1428
|
+
```tsx
|
|
1429
|
+
<Pagination
|
|
1430
|
+
page={p.page}
|
|
1431
|
+
lastPage={p.lastPage}
|
|
1432
|
+
total={p.total}
|
|
1433
|
+
perPage={p.perPage}
|
|
1434
|
+
href={(n) => `?page=${n}`}
|
|
1435
|
+
/>
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1438
|
+
### Pagination props
|
|
1439
|
+
|
|
1440
|
+
<table>
|
|
1441
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1442
|
+
<tbody>
|
|
1443
|
+
<tr><td><code>page</code></td><td><code>number</code></td><td>—</td><td>Current page, 1-based.</td></tr>
|
|
1444
|
+
<tr><td><code>lastPage</code></td><td><code>number</code></td><td>—</td><td>How many pages there are.</td></tr>
|
|
1445
|
+
<tr><td><code>href</code></td><td><code>(page: number) => string</code></td><td>—</td><td>Builds each page's URL, keeping your other params.</td></tr>
|
|
1446
|
+
<tr><td><code>total</code></td><td><code>number</code></td><td>—</td><td>Row count, shown as “1–20 of 231”.</td></tr>
|
|
1447
|
+
<tr><td><code>siblings</code></td><td><code>number</code></td><td><code>5</code></td><td>Numbered links around the current page.</td></tr>
|
|
1448
|
+
</tbody>
|
|
1449
|
+
</table>
|
|
1450
|
+
|
|
1451
|
+
<a id="components-field"></a>
|
|
1452
|
+
|
|
1453
|
+
## Field
|
|
1454
|
+
|
|
1455
|
+
Label + control + description + error.
|
|
1456
|
+
|
|
1457
|
+
### Field installation
|
|
1458
|
+
|
|
1459
|
+
```sh
|
|
1460
|
+
bun zt flow:add field
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
Or import directly from the package: `import { Field } from "@zerotal/flow-ui";`
|
|
1464
|
+
|
|
1465
|
+
### Field preview
|
|
1466
|
+
|
|
1467
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1468
|
+
<div class="w-full max-w-sm space-y-4"><div class="space-y-1.5"><label for="flow-field-1" class="block text-sm font-medium leading-none text-foreground">Email<span class="ml-0.5 text-destructive" aria-hidden="true">*</span></label><div class="space-y-1.5"><input type="email" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" placeholder="ada@example.com" id="flow-field-1" aria-describedby="flow-field-1-description" aria-required="true"><p id="flow-field-1-description" class="text-xs text-muted-foreground">We never share it.</p></div></div><div class="space-y-1.5"><label for="flow-field-2" class="block text-sm font-medium leading-none text-foreground">Password</label><div class="space-y-1.5"><input type="password" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" id="flow-field-2" aria-describedby="flow-field-2-error" aria-invalid="true"><p id="flow-field-2-error" role="alert" class="text-xs font-medium text-destructive">Must be at least 8 characters.</p></div></div></div>
|
|
1469
|
+
</div>
|
|
1470
|
+
|
|
1471
|
+
### Field usage
|
|
1472
|
+
|
|
1473
|
+
```tsx
|
|
1474
|
+
<Field label="Email" description="We never share it." error={errors.email} required>
|
|
1475
|
+
<Input type="email" flow:model="form.email" />
|
|
1476
|
+
</Field>
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
### Field props
|
|
1480
|
+
|
|
1481
|
+
<table>
|
|
1482
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1483
|
+
<tbody>
|
|
1484
|
+
<tr><td><code>label</code></td><td><code>node</code></td><td>—</td><td>Associated with the control by a generated id.</td></tr>
|
|
1485
|
+
<tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>Helper text, linked by aria-describedby.</td></tr>
|
|
1486
|
+
<tr><td><code>error</code></td><td><code>node</code></td><td>—</td><td>Its presence marks the field invalid and announces it.</td></tr>
|
|
1487
|
+
<tr><td><code>required</code></td><td><code>boolean</code></td><td>—</td><td>Shows the marker and sets aria-required.</td></tr>
|
|
1488
|
+
<tr><td><code>orientation</code></td><td><code>"vertical" | "horizontal"</code></td><td><code>"vertical"</code></td><td>Label above or beside.</td></tr>
|
|
1489
|
+
</tbody>
|
|
1490
|
+
</table>
|
|
1491
|
+
|
|
1492
|
+
<a id="components-input-group"></a>
|
|
1493
|
+
|
|
1494
|
+
## InputGroup
|
|
1495
|
+
|
|
1496
|
+
Input with affixes or addons.
|
|
1497
|
+
|
|
1498
|
+
### InputGroup installation
|
|
1499
|
+
|
|
1500
|
+
```sh
|
|
1501
|
+
bun zt flow:add input-group
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
Or import directly from the package: `import { InputGroup } from "@zerotal/flow-ui";`
|
|
1505
|
+
|
|
1506
|
+
### InputGroup preview
|
|
1507
|
+
|
|
1508
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1509
|
+
<div class="w-full max-w-sm space-y-3"><div class="flex w-full items-stretch"><div class="relative flex-1"><span class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-sm text-muted-foreground">R</span><div class="[&>input]:w-full [&>input]:pl-8"><input type="text" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" placeholder="0.00"></div></div></div><div class="flex w-full items-stretch"><div class="relative flex-1"><div class="[&>input]:w-full [&>input]:rounded-r-none"><input type="text" data-slot="input" class="flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40" value="kln_live_8f3a"></div></div><span class="inline-flex shrink-0 items-center [&>button]:rounded-l-none"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Copy</button></span></div></div>
|
|
1510
|
+
</div>
|
|
1511
|
+
|
|
1512
|
+
### InputGroup usage
|
|
1513
|
+
|
|
1514
|
+
```tsx
|
|
1515
|
+
<InputGroup prefix="R"><Input flow:model="form.price" /></InputGroup>
|
|
1516
|
+
<InputGroup addonAfter={<Button>Copy</Button>}><Input value={key} /></InputGroup>
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
### InputGroup props
|
|
1520
|
+
|
|
1521
|
+
<table>
|
|
1522
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1523
|
+
<tbody>
|
|
1524
|
+
<tr><td><code>prefix</code></td><td><code>node</code></td><td>—</td><td>Inside the border, before the text.</td></tr>
|
|
1525
|
+
<tr><td><code>suffix</code></td><td><code>node</code></td><td>—</td><td>Inside the border, after the text.</td></tr>
|
|
1526
|
+
<tr><td><code>addonBefore</code></td><td><code>node</code></td><td>—</td><td>Outside the border, in its own cell.</td></tr>
|
|
1527
|
+
<tr><td><code>addonAfter</code></td><td><code>node</code></td><td>—</td><td>Outside the border — often a button.</td></tr>
|
|
1528
|
+
</tbody>
|
|
1529
|
+
</table>
|
|
1530
|
+
|
|
1531
|
+
<a id="components-input-otp"></a>
|
|
1532
|
+
|
|
1533
|
+
## InputOTP
|
|
1534
|
+
|
|
1535
|
+
One-time-code input.
|
|
1536
|
+
|
|
1537
|
+
### InputOTP installation
|
|
1538
|
+
|
|
1539
|
+
```sh
|
|
1540
|
+
bun zt flow:add input-otp
|
|
1541
|
+
```
|
|
1542
|
+
|
|
1543
|
+
Or import directly from the package: `import { InputOTP } from "@zerotal/flow-ui";`
|
|
1544
|
+
|
|
1545
|
+
### InputOTP preview
|
|
1546
|
+
|
|
1547
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1548
|
+
<div x-data="{ value: '' }" class="relative inline-flex" x-on:click="$refs.field.focus()"><input x-ref="field" type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="6" pattern="[0-9]*" x-model="value" class="absolute inset-0 z-10 h-full w-full cursor-default opacity-0"><div class="flex items-center gap-2"><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 0 && 'ring-2 ring-ring border-ring'"><span x-text="value[0] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 1 && 'ring-2 ring-ring border-ring'"><span x-text="value[1] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 2 && 'ring-2 ring-ring border-ring'"><span x-text="value[2] || ''"></span></div><span class="w-2"></span><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 3 && 'ring-2 ring-ring border-ring'"><span x-text="value[3] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 4 && 'ring-2 ring-ring border-ring'"><span x-text="value[4] || ''"></span></div><div class="flex h-10 w-10 items-center justify-center rounded-md border border-input bg-background text-sm font-medium tabular-nums transition-colors" x-bind:class="value.length === 5 && 'ring-2 ring-ring border-ring'"><span x-text="value[5] || ''"></span></div></div></div>
|
|
1549
|
+
</div>
|
|
1550
|
+
|
|
1551
|
+
### InputOTP usage
|
|
1552
|
+
|
|
1553
|
+
```tsx
|
|
1554
|
+
<InputOTP length={6} groupAfter={3} flow:model="form.code" />
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
### InputOTP props
|
|
1558
|
+
|
|
1559
|
+
<table>
|
|
1560
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1561
|
+
<tbody>
|
|
1562
|
+
<tr><td><code>length</code></td><td><code>number</code></td><td><code>6</code></td><td>How many characters the code has.</td></tr>
|
|
1563
|
+
<tr><td><code>numeric</code></td><td><code>boolean</code></td><td><code>true</code></td><td>Restrict to digits.</td></tr>
|
|
1564
|
+
<tr><td><code>groupAfter</code></td><td><code>number</code></td><td>—</td><td>Insert a wider gap after this many boxes.</td></tr>
|
|
1565
|
+
</tbody>
|
|
1566
|
+
</table>
|
|
1567
|
+
|
|
1568
|
+
<a id="components-combobox"></a>
|
|
1569
|
+
|
|
1570
|
+
## Combobox
|
|
1571
|
+
|
|
1572
|
+
Autocomplete over many options.
|
|
1573
|
+
|
|
1574
|
+
### Combobox installation
|
|
1575
|
+
|
|
1576
|
+
```sh
|
|
1577
|
+
bun zt flow:add combobox
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
Or import directly from the package: `import { Combobox } from "@zerotal/flow-ui";`
|
|
1581
|
+
|
|
1582
|
+
### Combobox preview
|
|
1583
|
+
|
|
1584
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1585
|
+
<div class="w-full max-w-sm"><div x-data="{ open: false, active: -1, query: '' }" class="flow-combobox relative"><input type="text" role="combobox" autocomplete="off" aria-autocomplete="list" aria-controls="flow-combobox-list" :aria-expanded="open" placeholder="Search brands…" :value="query" x-on:input="onInput($event)" x-on:focus="openList()" x-on:click="openList()" x-on:keydown="onKey($event)" class="flow-combobox-input flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50"><ul id="flow-combobox-list" role="listbox" x-show="open" x-cloak x-on:click.outside="close()" :aria-activedescendant="null" class="flow-combobox-options absolute z-50 mt-1 max-h-60 w-full overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"><li role="option" data-value=""1"" data-label="Acme" aria-selected="false" :aria-selected="isSelected("1")" :data-selected="isSelected("1") ? '' : null" :data-active="active === 0 ? '' : null" x-on:click="selectEl($event.currentTarget)" x-on:mousemove="active = 0" class="flow-combobox-option cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none data-active:bg-accent data-active:text-accent-foreground data-selected:font-medium data-disabled:pointer-events-none data-disabled:opacity-50" x-show="!query || "acme".includes(query.toLowerCase())">Acme</li><li role="option" data-value=""2"" data-label="Globex" aria-selected="false" :aria-selected="isSelected("2")" :data-selected="isSelected("2") ? '' : null" :data-active="active === 1 ? '' : null" x-on:click="selectEl($event.currentTarget)" x-on:mousemove="active = 1" class="flow-combobox-option cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none data-active:bg-accent data-active:text-accent-foreground data-selected:font-medium data-disabled:pointer-events-none data-disabled:opacity-50" x-show="!query || "globex".includes(query.toLowerCase())">Globex</li><li role="option" data-value=""3"" data-label="Initech" aria-selected="false" :aria-selected="isSelected("3")" :data-selected="isSelected("3") ? '' : null" :data-active="active === 2 ? '' : null" x-on:click="selectEl($event.currentTarget)" x-on:mousemove="active = 2" class="flow-combobox-option cursor-pointer rounded-sm px-2 py-1.5 text-sm outline-none data-active:bg-accent data-active:text-accent-foreground data-selected:font-medium data-disabled:pointer-events-none data-disabled:opacity-50" x-show="!query || "initech".includes(query.toLowerCase())">Initech</li></ul></div></div>
|
|
1586
|
+
</div>
|
|
1587
|
+
|
|
1588
|
+
### Combobox usage
|
|
1589
|
+
|
|
1590
|
+
```tsx
|
|
1591
|
+
<Combobox bind={this.brandId} options={brands} placeholder="Search brands…" />
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
### Combobox props
|
|
1595
|
+
|
|
1596
|
+
<table>
|
|
1597
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1598
|
+
<tbody>
|
|
1599
|
+
<tr><td><code>bind</code></td><td><code>@expose value</code></td><td>—</td><td>The chosen value.</td></tr>
|
|
1600
|
+
<tr><td><code>options</code></td><td><code>ComboboxOption[]</code></td><td>—</td><td>Choices, filtered as you type.</td></tr>
|
|
1601
|
+
<tr><td><code>query</code></td><td><code>@expose value</code></td><td>—</td><td>Bind to filter on the server instead of the client.</td></tr>
|
|
1602
|
+
</tbody>
|
|
1603
|
+
</table>
|
|
1604
|
+
|
|
1605
|
+
<a id="components-slider"></a>
|
|
1606
|
+
|
|
1607
|
+
## Slider
|
|
1608
|
+
|
|
1609
|
+
Value chosen from a range.
|
|
1610
|
+
|
|
1611
|
+
### Slider installation
|
|
1612
|
+
|
|
1613
|
+
```sh
|
|
1614
|
+
bun zt flow:add slider
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
Or import directly from the package: `import { Slider } from "@zerotal/flow-ui";`
|
|
1618
|
+
|
|
1619
|
+
### Slider preview
|
|
1620
|
+
|
|
1621
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1622
|
+
<div class="w-full max-w-sm"><div value="64" x-data="flowSlider({"name":null,"min":0,"max":100,"step":1})" :data-dragging="dragging ? '' : null" class="flow-slider flex items-center gap-3"><input type="range" min="0" max="100" step="1" value="0" style="background:linear-gradient(to right, var(--primary) 0%, var(--secondary) 0%)" :style="`background:linear-gradient(to right, var(--primary) ${percent()}%, var(--secondary) ${percent()}%)`" x-model.number="value" x-on:input="onInput($event)" x-on:change="commit()" x-on:keyup.debounce.200ms="commit()" class="flow-slider-input h-2 w-full cursor-pointer appearance-none rounded-full outline-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background [&::-webkit-slider-thumb]:shadow [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-primary [&::-moz-range-thumb]:bg-background [&::-moz-range-thumb]:shadow"><span class="w-14 shrink-0 text-right text-sm tabular-nums text-muted-foreground"><span x-text="value"></span></span></div></div>
|
|
1623
|
+
</div>
|
|
1624
|
+
|
|
1625
|
+
### Slider usage
|
|
1626
|
+
|
|
1627
|
+
```tsx
|
|
1628
|
+
<Slider value={this.volume} max={100} showValue />
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
### Slider props
|
|
1632
|
+
|
|
1633
|
+
<table>
|
|
1634
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1635
|
+
<tbody>
|
|
1636
|
+
<tr><td><code>value</code></td><td><code>number</code></td><td>—</td><td>Current value.</td></tr>
|
|
1637
|
+
<tr><td><code>min</code></td><td><code>number</code></td><td><code>0</code></td><td>Lower bound.</td></tr>
|
|
1638
|
+
<tr><td><code>max</code></td><td><code>number</code></td><td><code>100</code></td><td>Upper bound.</td></tr>
|
|
1639
|
+
<tr><td><code>showValue</code></td><td><code>boolean</code></td><td>—</td><td>Show the value beside the track.</td></tr>
|
|
1640
|
+
</tbody>
|
|
1641
|
+
</table>
|
|
1642
|
+
|
|
1643
|
+
<a id="components-toggle"></a>
|
|
1644
|
+
|
|
1645
|
+
## Toggle
|
|
1646
|
+
|
|
1647
|
+
Pressed-state button (+ toggle group).
|
|
1648
|
+
|
|
1649
|
+
### Toggle installation
|
|
1650
|
+
|
|
1651
|
+
```sh
|
|
1652
|
+
bun zt flow:add toggle
|
|
1653
|
+
```
|
|
1654
|
+
|
|
1655
|
+
Or import directly from the package: `import { Toggle } from "@zerotal/flow-ui";`
|
|
1656
|
+
|
|
1657
|
+
### Toggle preview
|
|
1658
|
+
|
|
1659
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1660
|
+
<div class="flex flex-wrap items-center gap-3"><button type="button" aria-pressed="true" class="inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground h-9 px-2.5 bg-accent text-accent-foreground">B</button><button type="button" aria-pressed="false" class="inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground border border-input bg-transparent h-9 px-2.5">I</button><div value="grid" role="radiogroup" x-data="flowToggleGroup({"name":null,"multiple":false})" class="flow-toggle-group inline-flex items-center gap-1 rounded-md"><button type="button" aria-pressed="false" :aria-pressed="isOn("list")" :data-pressed="isOn("list") ? '' : null" x-on:click="toggle("list")" class="flow-toggle inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground border border-input bg-transparent h-9 px-2.5 data-pressed:bg-accent data-pressed:text-accent-foreground">List</button><button type="button" aria-pressed="false" :aria-pressed="isOn("grid")" :data-pressed="isOn("grid") ? '' : null" x-on:click="toggle("grid")" class="flow-toggle inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 hover:bg-muted hover:text-muted-foreground border border-input bg-transparent h-9 px-2.5 data-pressed:bg-accent data-pressed:text-accent-foreground">Grid</button></div></div>
|
|
1661
|
+
</div>
|
|
1662
|
+
|
|
1663
|
+
### Toggle usage
|
|
1664
|
+
|
|
1665
|
+
```tsx
|
|
1666
|
+
<Toggle pressed={this.bold}>B</Toggle>
|
|
1667
|
+
<ToggleGroup value={this.view} options={[
|
|
1668
|
+
{ value: "list", label: "List" }, { value: "grid", label: "Grid" },
|
|
1669
|
+
]} />
|
|
1670
|
+
```
|
|
1671
|
+
|
|
1672
|
+
### Toggle props
|
|
1673
|
+
|
|
1674
|
+
<table>
|
|
1675
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1676
|
+
<tbody>
|
|
1677
|
+
<tr><td><code>pressed</code></td><td><code>boolean</code></td><td>—</td><td>Whether the toggle is on (sets aria-pressed).</td></tr>
|
|
1678
|
+
<tr><td><code>variant</code></td><td><code>"default" | "outline"</code></td><td><code>"default"</code></td><td>Visual style.</td></tr>
|
|
1679
|
+
<tr><td><code>options</code></td><td><code>ToggleOption[]</code></td><td>—</td><td>ToggleGroup only — the members.</td></tr>
|
|
1680
|
+
<tr><td><code>type</code></td><td><code>"single" | "multiple"</code></td><td><code>"single"</code></td><td>ToggleGroup only — how many may be on.</td></tr>
|
|
1681
|
+
</tbody>
|
|
1682
|
+
</table>
|
|
1683
|
+
|
|
1684
|
+
<a id="components-button-group"></a>
|
|
1685
|
+
|
|
1686
|
+
## ButtonGroup
|
|
1687
|
+
|
|
1688
|
+
Buttons joined into one control.
|
|
1689
|
+
|
|
1690
|
+
### ButtonGroup installation
|
|
1691
|
+
|
|
1692
|
+
```sh
|
|
1693
|
+
bun zt flow:add button-group
|
|
1694
|
+
```
|
|
1695
|
+
|
|
1696
|
+
Or import directly from the package: `import { ButtonGroup } from "@zerotal/flow-ui";`
|
|
1697
|
+
|
|
1698
|
+
### ButtonGroup preview
|
|
1699
|
+
|
|
1700
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1701
|
+
<div role="group" class="inline-flex flex-row [&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px [&>*:focus-visible]:relative [&>*:focus-visible]:z-10"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Day</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Week</button><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2">Month</button></div>
|
|
1702
|
+
</div>
|
|
1703
|
+
|
|
1704
|
+
### ButtonGroup usage
|
|
1705
|
+
|
|
1706
|
+
```tsx
|
|
1707
|
+
<ButtonGroup>
|
|
1708
|
+
<Button variant="outline">Day</Button>
|
|
1709
|
+
<Button variant="outline">Week</Button>
|
|
1710
|
+
</ButtonGroup>
|
|
1711
|
+
```
|
|
1712
|
+
|
|
1713
|
+
### ButtonGroup props
|
|
1714
|
+
|
|
1715
|
+
<table>
|
|
1716
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1717
|
+
<tbody>
|
|
1718
|
+
<tr><td><code>orientation</code></td><td><code>"horizontal" | "vertical"</code></td><td><code>"horizontal"</code></td><td>Direction the members join in.</td></tr>
|
|
1719
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
1720
|
+
</tbody>
|
|
1721
|
+
</table>
|
|
1722
|
+
|
|
1723
|
+
<a id="components-calendar"></a>
|
|
1724
|
+
|
|
1725
|
+
## Calendar
|
|
1726
|
+
|
|
1727
|
+
Month grid for picking or laying out dates.
|
|
1728
|
+
|
|
1729
|
+
### Calendar installation
|
|
1730
|
+
|
|
1731
|
+
```sh
|
|
1732
|
+
bun zt flow:add calendar
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
Or import directly from the package: `import { Calendar } from "@zerotal/flow-ui";`
|
|
1736
|
+
|
|
1737
|
+
### Calendar preview
|
|
1738
|
+
|
|
1739
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1740
|
+
<div class="rounded-lg border border-border bg-card p-3"><div class="mb-2 flex items-center justify-between px-1"><span class="w-7"></span><p class="text-sm font-medium">July 2026</p><span class="w-7"></span></div><div class="grid grid-cols-7 gap-px"><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Mon</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Tue</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Wed</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Thu</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Fri</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sat</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sun</div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">9</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">10</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">11</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">12</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">13</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors bg-primary text-primary-foreground"><span class="">14</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">15</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">16</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">17</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">18</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">19</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">20</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">21</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">22</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">23</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">24</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">25</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">26</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">27</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">28</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">31</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">9</span></div></div></div>
|
|
1741
|
+
</div>
|
|
1742
|
+
|
|
1743
|
+
### Calendar usage
|
|
1744
|
+
|
|
1745
|
+
```tsx
|
|
1746
|
+
<Calendar value={this.due} onSelect={this.pick} />
|
|
1747
|
+
<Calendar month="2026-07" events={[{ date: "2026-07-14", label: "Launch" }]} />
|
|
1748
|
+
```
|
|
1749
|
+
|
|
1750
|
+
### Calendar props
|
|
1751
|
+
|
|
1752
|
+
<table>
|
|
1753
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1754
|
+
<tbody>
|
|
1755
|
+
<tr><td><code>month</code></td><td><code>string</code></td><td>—</td><td>`YYYY-MM` to display.</td></tr>
|
|
1756
|
+
<tr><td><code>value</code></td><td><code>string</code></td><td>—</td><td>Selected `YYYY-MM-DD`.</td></tr>
|
|
1757
|
+
<tr><td><code>onSelect</code></td><td><code>handler</code></td><td>—</td><td>Receives the clicked `YYYY-MM-DD`.</td></tr>
|
|
1758
|
+
<tr><td><code>events</code></td><td><code>CalendarEvent[]</code></td><td>—</td><td>Records to lay out across the month.</td></tr>
|
|
1759
|
+
<tr><td><code>min / max</code></td><td><code>string</code></td><td>—</td><td>Selectable range.</td></tr>
|
|
1760
|
+
</tbody>
|
|
1761
|
+
</table>
|
|
1762
|
+
|
|
1763
|
+
<a id="components-date-picker"></a>
|
|
1764
|
+
|
|
1765
|
+
## DatePicker
|
|
1766
|
+
|
|
1767
|
+
Calendar in a popover.
|
|
1768
|
+
|
|
1769
|
+
### DatePicker installation
|
|
1770
|
+
|
|
1771
|
+
```sh
|
|
1772
|
+
bun zt flow:add date-picker
|
|
1773
|
+
```
|
|
1774
|
+
|
|
1775
|
+
Or import directly from the package: `import { DatePicker } from "@zerotal/flow-ui";`
|
|
1776
|
+
|
|
1777
|
+
### DatePicker preview
|
|
1778
|
+
|
|
1779
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1780
|
+
<div class="w-56"><div x-data="{ open: false }" class="flow-popover relative inline-block"><span x-on:click="open = !open" :aria-expanded="open" :data-open="open ? '' : null" role="button" tabindex="0" class="flow-popover-button cursor-pointer outline-none"><span class="inline-flex h-9 w-full items-center gap-2 rounded-md border border-input bg-background px-3 text-sm transition-colors hover:bg-accent hover:text-accent-foreground"><svg class="h-4 w-4 shrink-0 text-muted-foreground" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"></rect><path d="M16 2v4M8 2v4M3 10h18"></path></svg><span>14 Jul 2026</span></span></span><div x-show="open" x-cloak x-transition x-on:click.outside="open = false" x-on:keydown.escape.window="open = false" :data-open="open ? '' : null" class="flow-popover-panel absolute top-full mt-2 left-0 z-50 min-w-[8rem] rounded-md border border-border bg-popover text-popover-foreground shadow-md outline-none p-0"><div class="rounded-lg border-border bg-card p-3 border-0"><div class="mb-2 flex items-center justify-between px-1"><span class="w-7"></span><p class="text-sm font-medium">July 2026</p><span class="w-7"></span></div><div class="grid grid-cols-7 gap-px"><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Mon</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Tue</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Wed</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Thu</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Fri</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sat</div><div class="pb-1 text-center text-[11px] font-medium text-muted-foreground">Sun</div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">9</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">10</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">11</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">12</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">13</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors bg-primary text-primary-foreground"><span class="">14</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">15</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">16</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">17</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">18</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">19</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">20</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">21</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">22</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">23</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">24</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">25</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">26</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">27</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">28</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">29</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">30</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors"><span class="">31</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">1</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">2</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">3</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">4</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">5</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">6</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">7</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">8</span></div><div class="h-9 items-center justify-center flex flex-col rounded-md text-sm transition-colors text-muted-foreground/40"><span class="">9</span></div></div></div></div></div></div>
|
|
1781
|
+
</div>
|
|
1782
|
+
|
|
1783
|
+
### DatePicker usage
|
|
1784
|
+
|
|
1785
|
+
```tsx
|
|
1786
|
+
<DatePicker value={this.due} onSelect={this.setDue} />
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
### DatePicker props
|
|
1790
|
+
|
|
1791
|
+
<table>
|
|
1792
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1793
|
+
<tbody>
|
|
1794
|
+
<tr><td><code>value</code></td><td><code>string</code></td><td>—</td><td>Selected `YYYY-MM-DD`.</td></tr>
|
|
1795
|
+
<tr><td><code>onSelect</code></td><td><code>handler</code></td><td>—</td><td>Receives the clicked `YYYY-MM-DD`.</td></tr>
|
|
1796
|
+
<tr><td><code>placeholder</code></td><td><code>string</code></td><td><code>"Pick a date"</code></td><td>Shown when nothing is chosen.</td></tr>
|
|
1797
|
+
<tr><td><code>min / max</code></td><td><code>string</code></td><td>—</td><td>Selectable range.</td></tr>
|
|
1798
|
+
</tbody>
|
|
1799
|
+
</table>
|
|
1800
|
+
|
|
1801
|
+
<a id="components-toast"></a>
|
|
1802
|
+
|
|
1803
|
+
## Toaster
|
|
1804
|
+
|
|
1805
|
+
Host for transient flash messages.
|
|
1806
|
+
|
|
1807
|
+
### Toaster installation
|
|
1808
|
+
|
|
1809
|
+
```sh
|
|
1810
|
+
bun zt flow:add toast
|
|
1811
|
+
```
|
|
1812
|
+
|
|
1813
|
+
Or import directly from the package: `import { Toaster } from "@zerotal/flow-ui";`
|
|
1814
|
+
|
|
1815
|
+
### Toaster preview
|
|
1816
|
+
|
|
1817
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1818
|
+
<p class="text-sm text-muted-foreground">Mounted once per layout; every <code class="font-mono text-xs">page.flash()</code> lands in it.</p>
|
|
1819
|
+
</div>
|
|
1820
|
+
|
|
1821
|
+
### Toaster usage
|
|
1822
|
+
|
|
1823
|
+
```tsx
|
|
1824
|
+
<Toaster position="bottom-right" />;
|
|
1825
|
+
// then anywhere on the server:
|
|
1826
|
+
page.flash("Saved.", "success");
|
|
1827
|
+
```
|
|
1828
|
+
|
|
1829
|
+
### Toaster props
|
|
1830
|
+
|
|
1831
|
+
<table>
|
|
1832
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1833
|
+
<tbody>
|
|
1834
|
+
<tr><td><code>position</code></td><td><code>"top-right" | "bottom-right" | …</code></td><td><code>"bottom-right"</code></td><td>Corner it stacks in.</td></tr>
|
|
1835
|
+
<tr><td><code>duration</code></td><td><code>number</code></td><td><code>4000</code></td><td>How long a toast stays, in ms.</td></tr>
|
|
1836
|
+
<tr><td><code>max</code></td><td><code>number</code></td><td><code>4</code></td><td>Most on screen at once.</td></tr>
|
|
1837
|
+
</tbody>
|
|
1838
|
+
</table>
|
|
1839
|
+
|
|
1840
|
+
<a id="components-progress"></a>
|
|
1841
|
+
|
|
1842
|
+
## Progress
|
|
1843
|
+
|
|
1844
|
+
Determinate progress bar.
|
|
1845
|
+
|
|
1846
|
+
### Progress installation
|
|
1847
|
+
|
|
1848
|
+
```sh
|
|
1849
|
+
bun zt flow:add progress
|
|
1850
|
+
```
|
|
1851
|
+
|
|
1852
|
+
Or import directly from the package: `import { Progress } from "@zerotal/flow-ui";`
|
|
1853
|
+
|
|
1854
|
+
### Progress preview
|
|
1855
|
+
|
|
1856
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1857
|
+
<div class="w-full max-w-sm space-y-3"><div class="flex items-center gap-3"><div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="62" class="relative h-2 w-full overflow-hidden rounded-full bg-secondary"><div class="h-full rounded-full bg-primary transition-[width] duration-300 ease-out" style="width:62%"></div></div><span class="shrink-0 text-xs tabular-nums text-muted-foreground">62%</span></div><div class="flex items-center gap-3"><div role="progressbar" aria-valuemin="0" aria-valuemax="100" class="relative h-2 w-full overflow-hidden rounded-full bg-secondary"><div class="h-full rounded-full bg-primary transition-[width] duration-300 ease-out w-1/3 animate-[flow-progress_1.2s_ease-in-out_infinite]"></div></div></div></div>
|
|
1858
|
+
</div>
|
|
1859
|
+
|
|
1860
|
+
### Progress usage
|
|
1861
|
+
|
|
1862
|
+
```tsx
|
|
1863
|
+
<Progress value={imported} max={total} showValue />
|
|
1864
|
+
<Progress /> {/* indeterminate */}
|
|
1865
|
+
```
|
|
1866
|
+
|
|
1867
|
+
### Progress props
|
|
1868
|
+
|
|
1869
|
+
<table>
|
|
1870
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1871
|
+
<tbody>
|
|
1872
|
+
<tr><td><code>value</code></td><td><code>number</code></td><td>—</td><td>Completed amount. Omit for the indeterminate bar.</td></tr>
|
|
1873
|
+
<tr><td><code>max</code></td><td><code>number</code></td><td><code>100</code></td><td>The total.</td></tr>
|
|
1874
|
+
<tr><td><code>showValue</code></td><td><code>boolean</code></td><td>—</td><td>Show the percentage beside the bar.</td></tr>
|
|
1875
|
+
<tr><td><code>label</code></td><td><code>string</code></td><td>—</td><td>Describes what is progressing, for screen readers.</td></tr>
|
|
1876
|
+
</tbody>
|
|
1877
|
+
</table>
|
|
1878
|
+
|
|
1879
|
+
<a id="components-spinner"></a>
|
|
1880
|
+
|
|
1881
|
+
## Spinner
|
|
1882
|
+
|
|
1883
|
+
Indeterminate loading indicator.
|
|
1884
|
+
|
|
1885
|
+
### Spinner installation
|
|
1886
|
+
|
|
1887
|
+
```sh
|
|
1888
|
+
bun zt flow:add spinner
|
|
1889
|
+
```
|
|
1890
|
+
|
|
1891
|
+
Or import directly from the package: `import { Spinner } from "@zerotal/flow-ui";`
|
|
1892
|
+
|
|
1893
|
+
### Spinner preview
|
|
1894
|
+
|
|
1895
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1896
|
+
<div class="flex flex-wrap items-center gap-3"><span role="status" class="inline-flex items-center"><svg class="animate-spin text-current h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.2"></circle><path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path></svg><span class="sr-only">Loading</span></span><span role="status" class="inline-flex items-center"><svg class="animate-spin text-current h-5 w-5" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.2"></circle><path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path></svg><span class="sr-only">Loading</span></span><span role="status" class="inline-flex items-center"><svg class="animate-spin text-current h-8 w-8" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.2"></circle><path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"></path></svg><span class="sr-only">Loading</span></span></div>
|
|
1897
|
+
</div>
|
|
1898
|
+
|
|
1899
|
+
### Spinner usage
|
|
1900
|
+
|
|
1901
|
+
```tsx
|
|
1902
|
+
<Spinner />
|
|
1903
|
+
<Button disabled><Spinner size="sm" /> Saving…</Button>
|
|
1904
|
+
```
|
|
1905
|
+
|
|
1906
|
+
### Spinner props
|
|
1907
|
+
|
|
1908
|
+
<table>
|
|
1909
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1910
|
+
<tbody>
|
|
1911
|
+
<tr><td><code>size</code></td><td><code>"sm" | "default" | "lg"</code></td><td><code>"default"</code></td><td>Sizing.</td></tr>
|
|
1912
|
+
<tr><td><code>label</code></td><td><code>string | null</code></td><td><code>"Loading"</code></td><td>Announced to screen readers.</td></tr>
|
|
1913
|
+
</tbody>
|
|
1914
|
+
</table>
|
|
1915
|
+
|
|
1916
|
+
<a id="components-empty"></a>
|
|
1917
|
+
|
|
1918
|
+
## Empty
|
|
1919
|
+
|
|
1920
|
+
Empty-state block with an action.
|
|
1921
|
+
|
|
1922
|
+
### Empty installation
|
|
1923
|
+
|
|
1924
|
+
```sh
|
|
1925
|
+
bun zt flow:add empty
|
|
1926
|
+
```
|
|
1927
|
+
|
|
1928
|
+
Or import directly from the package: `import { Empty } from "@zerotal/flow-ui";`
|
|
1929
|
+
|
|
1930
|
+
### Empty preview
|
|
1931
|
+
|
|
1932
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1933
|
+
<div class="flex flex-col items-center justify-center px-6 py-12 text-center rounded-lg border border-dashed border-border"><p class="text-sm font-medium text-foreground">No orders yet</p><p class="mt-1 max-w-sm text-sm text-muted-foreground">Orders appear here as customers place them.</p><div class="mt-4 flex items-center gap-2"><button type="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-8 rounded-md px-3 text-xs">New order</button></div></div>
|
|
1934
|
+
</div>
|
|
1935
|
+
|
|
1936
|
+
### Empty usage
|
|
1937
|
+
|
|
1938
|
+
```tsx
|
|
1939
|
+
<Empty
|
|
1940
|
+
icon={icon}
|
|
1941
|
+
title="No orders yet"
|
|
1942
|
+
description="Orders appear here as customers place them."
|
|
1943
|
+
action={<Button>New order</Button>}
|
|
1944
|
+
/>
|
|
1945
|
+
```
|
|
1946
|
+
|
|
1947
|
+
### Empty props
|
|
1948
|
+
|
|
1949
|
+
<table>
|
|
1950
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1951
|
+
<tbody>
|
|
1952
|
+
<tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>What is missing.</td></tr>
|
|
1953
|
+
<tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>Why, or what to do about it.</td></tr>
|
|
1954
|
+
<tr><td><code>action</code></td><td><code>node</code></td><td>—</td><td>The next step — usually a button.</td></tr>
|
|
1955
|
+
<tr><td><code>bare</code></td><td><code>boolean</code></td><td>—</td><td>Drop the dashed border, inside a card that draws its own.</td></tr>
|
|
1956
|
+
</tbody>
|
|
1957
|
+
</table>
|
|
1958
|
+
|
|
1959
|
+
<a id="components-kbd"></a>
|
|
1960
|
+
|
|
1961
|
+
## Kbd
|
|
1962
|
+
|
|
1963
|
+
Keyboard key (+ platform modifier).
|
|
1964
|
+
|
|
1965
|
+
### Kbd installation
|
|
1966
|
+
|
|
1967
|
+
```sh
|
|
1968
|
+
bun zt flow:add kbd
|
|
1969
|
+
```
|
|
1970
|
+
|
|
1971
|
+
Or import directly from the package: `import { Kbd } from "@zerotal/flow-ui";`
|
|
1972
|
+
|
|
1973
|
+
### Kbd preview
|
|
1974
|
+
|
|
1975
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
1976
|
+
<div class="flex flex-wrap items-center gap-3"><span class="inline-flex items-center gap-1"><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground" x-data="{ mac: false }" x-init="mac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)" x-text="mac ? '⌘' : 'Ctrl'">Ctrl</kbd><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">K</kbd></span><span class="inline-flex items-center gap-1"><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">⇧</kbd><kbd class="pointer-events-none inline-flex h-5 min-w-5 select-none items-center justify-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">↵</kbd></span></div>
|
|
1977
|
+
</div>
|
|
1978
|
+
|
|
1979
|
+
### Kbd usage
|
|
1980
|
+
|
|
1981
|
+
```tsx
|
|
1982
|
+
<KbdMod /> <Kbd>K</Kbd>
|
|
1983
|
+
```
|
|
1984
|
+
|
|
1985
|
+
### Kbd props
|
|
1986
|
+
|
|
1987
|
+
<table>
|
|
1988
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
1989
|
+
<tbody>
|
|
1990
|
+
<tr><td><code>children</code></td><td><code>node</code></td><td>—</td><td>The key. `<KbdMod />` renders ⌘ or Ctrl per platform.</td></tr>
|
|
1991
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
1992
|
+
</tbody>
|
|
1993
|
+
</table>
|
|
1994
|
+
|
|
1995
|
+
<a id="components-accordion"></a>
|
|
1996
|
+
|
|
1997
|
+
## Accordion
|
|
1998
|
+
|
|
1999
|
+
Stacked collapsible sections.
|
|
2000
|
+
|
|
2001
|
+
### Accordion installation
|
|
2002
|
+
|
|
2003
|
+
```sh
|
|
2004
|
+
bun zt flow:add accordion
|
|
2005
|
+
```
|
|
2006
|
+
|
|
2007
|
+
Or import directly from the package: `import { Accordion } from "@zerotal/flow-ui";`
|
|
2008
|
+
|
|
2009
|
+
### Accordion preview
|
|
2010
|
+
|
|
2011
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2012
|
+
<div class="w-full max-w-md"><div x-data="{ active: 0 }" class="flow-accordion divide-y divide-border border-y border-border"><div class="flow-accordion-item" x-id="['flow-accordion']"><button type="button" x-on:click="active = active === 0 ? -1 : 0" :aria-expanded="active === 0" :data-open="active === 0 ? '' : null" :aria-controls="$id('flow-accordion')" class="flow-accordion-button flex w-full items-center justify-between gap-4 py-4 text-left text-sm font-medium transition-colors hover:underline outline-none focus-visible:ring-2 focus-visible:ring-ring [&[data-open]>svg]:rotate-180">Shipping</button><div :id="$id('flow-accordion')" x-show="active === 0" x-cloak :data-open="active === 0 ? '' : null" class="flow-accordion-panel pb-4 text-sm text-muted-foreground"><p>Ships in 2–3 days.</p></div></div><div class="flow-accordion-item" x-id="['flow-accordion']"><button type="button" x-on:click="active = active === 1 ? -1 : 1" :aria-expanded="active === 1" :data-open="active === 1 ? '' : null" :aria-controls="$id('flow-accordion')" class="flow-accordion-button flex w-full items-center justify-between gap-4 py-4 text-left text-sm font-medium transition-colors hover:underline outline-none focus-visible:ring-2 focus-visible:ring-ring [&[data-open]>svg]:rotate-180">Returns</button><div :id="$id('flow-accordion')" x-show="active === 1" x-cloak :data-open="active === 1 ? '' : null" class="flow-accordion-panel pb-4 text-sm text-muted-foreground"><p>30 days, no questions.</p></div></div></div></div>
|
|
2013
|
+
</div>
|
|
2014
|
+
|
|
2015
|
+
### Accordion usage
|
|
2016
|
+
|
|
2017
|
+
```tsx
|
|
2018
|
+
<Accordion
|
|
2019
|
+
items={[
|
|
2020
|
+
{ label: "Shipping", content: <p>Ships in 2–3 days.</p> },
|
|
2021
|
+
{ label: "Returns", content: <p>30 days, no questions.</p> },
|
|
2022
|
+
]}
|
|
2023
|
+
/>
|
|
2024
|
+
```
|
|
2025
|
+
|
|
2026
|
+
### Accordion props
|
|
2027
|
+
|
|
2028
|
+
<table>
|
|
2029
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2030
|
+
<tbody>
|
|
2031
|
+
<tr><td><code>items</code></td><td><code>AccordionItem[]</code></td><td>—</td><td>Each with a label and its content.</td></tr>
|
|
2032
|
+
<tr><td><code>multiple</code></td><td><code>boolean</code></td><td>—</td><td>Allow several open at once.</td></tr>
|
|
2033
|
+
<tr><td><code>defaultIndex</code></td><td><code>number</code></td><td><code>-1</code></td><td>Which starts open.</td></tr>
|
|
2034
|
+
</tbody>
|
|
2035
|
+
</table>
|
|
2036
|
+
|
|
2037
|
+
<a id="components-collapsible"></a>
|
|
2038
|
+
|
|
2039
|
+
## Collapsible
|
|
2040
|
+
|
|
2041
|
+
One section that opens and closes.
|
|
2042
|
+
|
|
2043
|
+
### Collapsible installation
|
|
2044
|
+
|
|
2045
|
+
```sh
|
|
2046
|
+
bun zt flow:add collapsible
|
|
2047
|
+
```
|
|
2048
|
+
|
|
2049
|
+
Or import directly from the package: `import { Collapsible } from "@zerotal/flow-ui";`
|
|
2050
|
+
|
|
2051
|
+
### Collapsible preview
|
|
2052
|
+
|
|
2053
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2054
|
+
<div class="w-full max-w-md"><div x-data="{ open: false }" x-id="['flow-disclosure']" class="flow-disclosure"><button type="button" x-on:click="open = !open" :aria-expanded="open" :data-open="open ? '' : null" :aria-controls="$id('flow-disclosure')" class="flow-disclosure-button inline-flex items-center gap-1.5 rounded-md text-sm font-medium text-foreground transition-colors hover:text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring [&[data-open]>svg]:rotate-180">Advanced</button><div :id="$id('flow-disclosure')" x-show="open" x-cloak :data-open="open ? '' : null" class="flow-disclosure-panel pt-2"><p class="text-sm text-muted-foreground">Rarely-changed settings live here.</p></div></div></div>
|
|
2055
|
+
</div>
|
|
2056
|
+
|
|
2057
|
+
### Collapsible usage
|
|
2058
|
+
|
|
2059
|
+
```tsx
|
|
2060
|
+
<Collapsible label="Advanced">
|
|
2061
|
+
<Field label="Timeout">
|
|
2062
|
+
<Input />
|
|
2063
|
+
</Field>
|
|
2064
|
+
</Collapsible>
|
|
2065
|
+
```
|
|
2066
|
+
|
|
2067
|
+
### Collapsible props
|
|
2068
|
+
|
|
2069
|
+
<table>
|
|
2070
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2071
|
+
<tbody>
|
|
2072
|
+
<tr><td><code>label</code></td><td><code>node</code></td><td>—</td><td>Text for the default trigger.</td></tr>
|
|
2073
|
+
<tr><td><code>trigger</code></td><td><code>node</code></td><td>—</td><td>A custom trigger instead.</td></tr>
|
|
2074
|
+
<tr><td><code>defaultOpen</code></td><td><code>boolean</code></td><td>—</td><td>Start open.</td></tr>
|
|
2075
|
+
</tbody>
|
|
2076
|
+
</table>
|
|
2077
|
+
|
|
2078
|
+
<a id="components-scroll-area"></a>
|
|
2079
|
+
|
|
2080
|
+
## ScrollArea
|
|
2081
|
+
|
|
2082
|
+
Scrollable region with a styled bar.
|
|
2083
|
+
|
|
2084
|
+
### ScrollArea installation
|
|
2085
|
+
|
|
2086
|
+
```sh
|
|
2087
|
+
bun zt flow:add scroll-area
|
|
2088
|
+
```
|
|
2089
|
+
|
|
2090
|
+
Or import directly from the package: `import { ScrollArea } from "@zerotal/flow-ui";`
|
|
2091
|
+
|
|
2092
|
+
### ScrollArea preview
|
|
2093
|
+
|
|
2094
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2095
|
+
<div class="relative overflow-y-auto overflow-x-hidden [scrollbar-width:thin] [scrollbar-color:var(--border)_transparent] [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border [&::-webkit-scrollbar-thumb:hover]:bg-muted-foreground/40 h-32 w-full max-w-sm rounded-md border border-border p-3"><div class="space-y-2 text-sm"><p>Row 1</p><p>Row 2</p><p>Row 3</p><p>Row 4</p><p>Row 5</p><p>Row 6</p><p>Row 7</p><p>Row 8</p><p>Row 9</p><p>Row 10</p><p>Row 11</p><p>Row 12</p></div></div>
|
|
2096
|
+
</div>
|
|
2097
|
+
|
|
2098
|
+
### ScrollArea usage
|
|
2099
|
+
|
|
2100
|
+
```tsx
|
|
2101
|
+
<ScrollArea class="h-72">…long list…</ScrollArea>
|
|
2102
|
+
```
|
|
2103
|
+
|
|
2104
|
+
### ScrollArea props
|
|
2105
|
+
|
|
2106
|
+
<table>
|
|
2107
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2108
|
+
<tbody>
|
|
2109
|
+
<tr><td><code>orientation</code></td><td><code>"vertical" | "horizontal" | "both"</code></td><td><code>"vertical"</code></td><td>Which way it scrolls.</td></tr>
|
|
2110
|
+
<tr><td><code>fade</code></td><td><code>boolean</code></td><td>—</td><td>Fade the content at the scrollable edges.</td></tr>
|
|
2111
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
2112
|
+
</tbody>
|
|
2113
|
+
</table>
|
|
2114
|
+
|
|
2115
|
+
<a id="components-resizable"></a>
|
|
2116
|
+
|
|
2117
|
+
## Resizable
|
|
2118
|
+
|
|
2119
|
+
Two panes with a draggable handle.
|
|
2120
|
+
|
|
2121
|
+
### Resizable installation
|
|
2122
|
+
|
|
2123
|
+
```sh
|
|
2124
|
+
bun zt flow:add resizable
|
|
2125
|
+
```
|
|
2126
|
+
|
|
2127
|
+
Or import directly from the package: `import { Resizable } from "@zerotal/flow-ui";`
|
|
2128
|
+
|
|
2129
|
+
### Resizable preview
|
|
2130
|
+
|
|
2131
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2132
|
+
<div x-data="{ size: 35, dragging: false, start(e) { this.dragging = true; e.target.setPointerCapture(e.pointerId); }, move(e) { if (!this.dragging) return; const box = $el.getBoundingClientRect(); const raw = ((e.clientX - box.left) / box.width) * 100; this.size = Math.min(90, Math.max(10, raw)); }, stop(e) { this.dragging = false; if (e.pointerId != null) e.target.releasePointerCapture?.(e.pointerId); }, nudge(by) { this.size = Math.min(90, Math.max(10, this.size + by)); } }" x-on:pointermove="move($event)" x-on:pointerup="stop($event)" class="flex w-full flex-row h-32 rounded-md border border-border"><div x-bind:style="`flex-basis:${size}%`" class="min-w-0 overflow-hidden"><div class="h-full bg-muted/40 p-3 text-sm">Sidebar</div></div><div role="separator" tabindex="0" aria-orientation="vertical" x-bind:aria-valuenow="Math.round(size)" aria-valuemin="10" aria-valuemax="90" aria-label="Resize panes" x-on:pointerdown="start($event)" x-on:keydown="if ($event.key === 'ArrowLeft') { $event.preventDefault(); nudge(-2) } if ($event.key === 'ArrowRight') { $event.preventDefault(); nudge(2) }" x-bind:class="dragging && 'bg-primary'" class="group relative shrink-0 bg-border transition-colors hover:bg-primary/50 outline-none focus-visible:bg-primary w-px cursor-col-resize"><span class="absolute -inset-x-1.5 inset-y-0"></span></div><div class="min-w-0 flex-1 overflow-hidden"><div class="h-full p-3 text-sm">Content</div></div></div>
|
|
2133
|
+
</div>
|
|
2134
|
+
|
|
2135
|
+
### Resizable usage
|
|
2136
|
+
|
|
2137
|
+
```tsx
|
|
2138
|
+
<Resizable start={<Tree />} end={<Editor />} defaultSize={30} />
|
|
2139
|
+
```
|
|
2140
|
+
|
|
2141
|
+
### Resizable props
|
|
2142
|
+
|
|
2143
|
+
<table>
|
|
2144
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2145
|
+
<tbody>
|
|
2146
|
+
<tr><td><code>start / end</code></td><td><code>node</code></td><td>—</td><td>The two panes.</td></tr>
|
|
2147
|
+
<tr><td><code>defaultSize</code></td><td><code>number</code></td><td><code>50</code></td><td>First pane's starting size, as a percentage.</td></tr>
|
|
2148
|
+
<tr><td><code>min</code></td><td><code>number</code></td><td><code>10</code></td><td>Smallest either pane may become.</td></tr>
|
|
2149
|
+
<tr><td><code>orientation</code></td><td><code>"horizontal" | "vertical"</code></td><td><code>"horizontal"</code></td><td>Split direction.</td></tr>
|
|
2150
|
+
</tbody>
|
|
2151
|
+
</table>
|
|
2152
|
+
|
|
2153
|
+
<a id="components-carousel"></a>
|
|
2154
|
+
|
|
2155
|
+
## Carousel
|
|
2156
|
+
|
|
2157
|
+
Snap-scrolling strip with controls.
|
|
2158
|
+
|
|
2159
|
+
### Carousel installation
|
|
2160
|
+
|
|
2161
|
+
```sh
|
|
2162
|
+
bun zt flow:add carousel
|
|
2163
|
+
```
|
|
2164
|
+
|
|
2165
|
+
Or import directly from the package: `import { Carousel } from "@zerotal/flow-ui";`
|
|
2166
|
+
|
|
2167
|
+
### Carousel preview
|
|
2168
|
+
|
|
2169
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2170
|
+
<div x-data="{ atStart: true, atEnd: false, sync() { const el = $refs.track; this.atStart = el.scrollLeft <= 1; this.atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 1; }, page(dir) { $refs.track.scrollBy({ left: dir * $refs.track.clientWidth * 0.9, behavior: 'smooth' }); } }" x-init="sync()" role="region" aria-roledescription="carousel" aria-label="Carousel" class="relative"><div x-ref="track" x-on:scroll.debounce.50ms="sync()" class="flex snap-x snap-mandatory gap-4 overflow-x-auto scroll-smooth pb-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 1</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 2</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 3</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 4</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 5</div></div><div class="shrink-0 snap-start w-40"><div class="flex h-24 items-center justify-center rounded-lg border border-border bg-muted/40 text-sm">Slide 6</div></div></div><button type="button" aria-label="Previous" x-on:click="page(-1)" x-bind:disabled="atStart" class="absolute top-1/2 z-10 inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-background shadow-sm transition-opacity hover:bg-accent disabled:pointer-events-none disabled:opacity-0 left-0 -translate-x-1/2"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"></path></svg></button><button type="button" aria-label="Next" x-on:click="page(1)" x-bind:disabled="atEnd" class="absolute top-1/2 z-10 inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-background shadow-sm transition-opacity hover:bg-accent disabled:pointer-events-none disabled:opacity-0 right-0 translate-x-1/2"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"></path></svg></button></div>
|
|
2171
|
+
</div>
|
|
2172
|
+
|
|
2173
|
+
### Carousel usage
|
|
2174
|
+
|
|
2175
|
+
```tsx
|
|
2176
|
+
<Carousel
|
|
2177
|
+
items={products.map((p) => (
|
|
2178
|
+
<ProductCard product={p} />
|
|
2179
|
+
))}
|
|
2180
|
+
/>
|
|
2181
|
+
```
|
|
2182
|
+
|
|
2183
|
+
### Carousel props
|
|
2184
|
+
|
|
2185
|
+
<table>
|
|
2186
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2187
|
+
<tbody>
|
|
2188
|
+
<tr><td><code>items</code></td><td><code>node[]</code></td><td>—</td><td>The slides.</td></tr>
|
|
2189
|
+
<tr><td><code>itemClass</code></td><td><code>string</code></td><td><code>"w-64 sm:w-72"</code></td><td>Width of each slide.</td></tr>
|
|
2190
|
+
<tr><td><code>hideControls</code></td><td><code>boolean</code></td><td>—</td><td>Swipe and scroll only.</td></tr>
|
|
2191
|
+
</tbody>
|
|
2192
|
+
</table>
|
|
2193
|
+
|
|
2194
|
+
<a id="components-aspect-ratio"></a>
|
|
2195
|
+
|
|
2196
|
+
## AspectRatio
|
|
2197
|
+
|
|
2198
|
+
Fixed width-to-height box.
|
|
2199
|
+
|
|
2200
|
+
### AspectRatio installation
|
|
2201
|
+
|
|
2202
|
+
```sh
|
|
2203
|
+
bun zt flow:add aspect-ratio
|
|
2204
|
+
```
|
|
2205
|
+
|
|
2206
|
+
Or import directly from the package: `import { AspectRatio } from "@zerotal/flow-ui";`
|
|
2207
|
+
|
|
2208
|
+
### AspectRatio preview
|
|
2209
|
+
|
|
2210
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2211
|
+
<div class="w-64"><div style="aspect-ratio:1.7777777777777777" class="relative w-full overflow-hidden rounded-lg border border-border bg-muted/40"><div class="flex h-full items-center justify-center text-sm text-muted-foreground">16 / 9</div></div></div>
|
|
2212
|
+
</div>
|
|
2213
|
+
|
|
2214
|
+
### AspectRatio usage
|
|
2215
|
+
|
|
2216
|
+
```tsx
|
|
2217
|
+
<AspectRatio ratio={16 / 9}>
|
|
2218
|
+
<img src={cover} class="h-full w-full object-cover" />
|
|
2219
|
+
</AspectRatio>
|
|
2220
|
+
```
|
|
2221
|
+
|
|
2222
|
+
### AspectRatio props
|
|
2223
|
+
|
|
2224
|
+
<table>
|
|
2225
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2226
|
+
<tbody>
|
|
2227
|
+
<tr><td><code>ratio</code></td><td><code>number</code></td><td><code>1</code></td><td>Width ÷ height.</td></tr>
|
|
2228
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
2229
|
+
</tbody>
|
|
2230
|
+
</table>
|
|
2231
|
+
|
|
2232
|
+
<a id="components-item"></a>
|
|
2233
|
+
|
|
2234
|
+
## Item
|
|
2235
|
+
|
|
2236
|
+
Icon + title + description + action row.
|
|
2237
|
+
|
|
2238
|
+
### Item installation
|
|
2239
|
+
|
|
2240
|
+
```sh
|
|
2241
|
+
bun zt flow:add item
|
|
2242
|
+
```
|
|
2243
|
+
|
|
2244
|
+
Or import directly from the package: `import { Item } from "@zerotal/flow-ui";`
|
|
2245
|
+
|
|
2246
|
+
### Item preview
|
|
2247
|
+
|
|
2248
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2249
|
+
<div class="w-full max-w-md rounded-lg border border-border p-1"><div class="flex w-full items-center gap-3 rounded-md px-3 py-2 text-left"><span class="min-w-0 flex-1"><span class="block truncate text-sm font-medium">Team</span><span class="block truncate text-xs text-muted-foreground">4 members</span></span><span class="flex shrink-0 items-center gap-2"><span class="inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80">Owner</span></span></div><div class="flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition-colors hover:bg-accent hover:text-accent-foreground"><span class="min-w-0 flex-1"><span class="block truncate text-sm font-medium">Billing</span><span class="block truncate text-xs text-muted-foreground">Visa ending 4242</span></span></div></div>
|
|
2250
|
+
</div>
|
|
2251
|
+
|
|
2252
|
+
### Item usage
|
|
2253
|
+
|
|
2254
|
+
```tsx
|
|
2255
|
+
<Item title="Team" description="4 members" action={<Button size="sm">Manage</Button>} />
|
|
2256
|
+
```
|
|
2257
|
+
|
|
2258
|
+
### Item props
|
|
2259
|
+
|
|
2260
|
+
<table>
|
|
2261
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2262
|
+
<tbody>
|
|
2263
|
+
<tr><td><code>title</code></td><td><code>node</code></td><td>—</td><td>The primary line.</td></tr>
|
|
2264
|
+
<tr><td><code>description</code></td><td><code>node</code></td><td>—</td><td>The supporting line.</td></tr>
|
|
2265
|
+
<tr><td><code>action</code></td><td><code>node</code></td><td>—</td><td>Trailing content.</td></tr>
|
|
2266
|
+
<tr><td><code>href</code></td><td><code>string</code></td><td>—</td><td>Makes the whole row a link.</td></tr>
|
|
2267
|
+
</tbody>
|
|
2268
|
+
</table>
|
|
2269
|
+
|
|
2270
|
+
<a id="components-chart"></a>
|
|
2271
|
+
|
|
2272
|
+
## Chart
|
|
2273
|
+
|
|
2274
|
+
SVG line, area, bar and donut charts.
|
|
2275
|
+
|
|
2276
|
+
### Chart installation
|
|
2277
|
+
|
|
2278
|
+
```sh
|
|
2279
|
+
bun zt flow:add chart
|
|
2280
|
+
```
|
|
2281
|
+
|
|
2282
|
+
Or import directly from the package: `import { Chart } from "@zerotal/flow-ui";`
|
|
2283
|
+
|
|
2284
|
+
### Chart preview
|
|
2285
|
+
|
|
2286
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2287
|
+
<div class="w-full space-y-6"><figure class="relative w-full" x-data="flowChart({"points":[{"x":0.07333333333333333,"label":"Mon","values":[{"label":"Orders","value":"12","color":"var(--primary)"}]},{"x":0.2277777777777778,"label":"Tue","values":[{"label":"Orders","value":"19","color":"var(--primary)"}]},{"x":0.38222222222222224,"label":"Wed","values":[{"label":"Orders","value":"14","color":"var(--primary)"}]},{"x":0.5366666666666666,"label":"Thu","values":[{"label":"Orders","value":"27","color":"var(--primary)"}]},{"x":0.6911111111111111,"label":"Fri","values":[{"label":"Orders","value":"22","color":"var(--primary)"}]},{"x":0.8455555555555556,"label":"Sat","values":[{"label":"Orders","value":"31","color":"var(--primary)"}]},{"x":1,"label":"Sun","values":[{"label":"Orders","value":"25","color":"var(--primary)"}]}]})" x-on:pointermove="onMove($event)" x-on:pointerleave="onLeave()"><svg viewBox="0 0 600 160" width="100%" height="160" preserveAspectRatio="none" role="img" aria-label="Orders: 12, 19, 14, 27, 22, 31, 25" class="overflow-visible"><line x1="44" y1="138" x2="600" y2="138" stroke="var(--border)" stroke-width="1"></line><text x="36" y="142" text-anchor="end" font-size="11" fill="var(--muted-foreground)">0</text><line x1="44" y1="103.5" x2="600" y2="103.5" stroke="var(--border)" stroke-width="1"></line><text x="36" y="107.5" text-anchor="end" font-size="11" fill="var(--muted-foreground)">13</text><line x1="44" y1="69" x2="600" y2="69" stroke="var(--border)" stroke-width="1"></line><text x="36" y="73" text-anchor="end" font-size="11" fill="var(--muted-foreground)">25</text><line x1="44" y1="34.5" x2="600" y2="34.5" stroke="var(--border)" stroke-width="1"></line><text x="36" y="38.5" text-anchor="end" font-size="11" fill="var(--muted-foreground)">38</text><line x1="44" y1="0" x2="600" y2="0" stroke="var(--border)" stroke-width="1"></line><text x="36" y="4" text-anchor="end" font-size="11" fill="var(--muted-foreground)">50</text><g transform="translate(44,0)"><path d="M0.00,104.88 L92.67,85.56 L185.33,99.36 L278.00,63.48 L370.67,77.28 L463.33,52.44 L556.00,69.00 L556,138 L0,138 Z" fill="var(--primary)" opacity="0.12"></path><path d="M0.00,104.88 L92.67,85.56 L185.33,99.36 L278.00,63.48 L370.67,77.28 L463.33,52.44 L556.00,69.00" fill="none" stroke="var(--primary)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" vector-effect="non-scaling-stroke" x-bind:style="`stroke-dasharray:2000;stroke-dashoffset:${ready ? 0 : 2000};transition:stroke-dashoffset .7s ease-out`"></path></g><text x="44" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Mon</text><text x="136.66666666666669" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Tue</text><text x="229.33333333333334" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Wed</text><text x="322" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Thu</text><text x="414.6666666666667" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Fri</text><text x="507.33333333333337" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Sat</text><text x="600" y="154" text-anchor="middle" font-size="11" fill="var(--muted-foreground)">Sun</text></svg><div x-show="hover >= 0" x-cloak class="pointer-events-none absolute inset-y-0 w-px bg-border" x-bind:style="`left:${tipX}px`"></div><div x-show="hover >= 0" x-cloak x-transition.opacity class="pointer-events-none absolute top-2 z-10 min-w-32 -translate-x-1/2 rounded-md border border-border bg-popover px-2.5 py-2 text-xs shadow-md" x-bind:style="`left:${tipX}px`"><p class="mb-1 font-medium" x-text="tip()?.label"></p><template x-for="row in (tip()?.values ?? [])" :key="row.label"><span class="flex items-center gap-1.5"><span class="h-2 w-2 shrink-0 rounded-sm" x-bind:style="`background:${row.color}`"></span><span class="text-muted-foreground" x-text="row.label"></span><span class="ml-auto font-medium tabular-nums" x-text="row.value"></span></span></template></div></figure><figure class="flex items-center gap-6"><svg viewBox="0 0 140 140" width="140" height="140" role="img" aria-label="Series: 82, 24, 6"><path d="M70,2 A68,68 0 1 1 2.4275697272595096,77.61358437502494 L28.1050932309009,74.72042231251547 A42.16,42.16 0 1 0 70,27.840000000000003 Z" fill="var(--primary)"></path><path d="M2.4275697272595096,77.61358437502494 A68,68 0 0 1 47.54102378704863,5.815933539031008 L56.07543474797015,30.20587879419923 A42.16,42.16 0 0 0 28.1050932309009,74.72042231251547 Z" fill="var(--flow-toast-success, #16a34a)"></path><path d="M47.54102378704863,5.815933539031008 A68,68 0 0 1 69.99999999999999,2 L69.99999999999999,27.840000000000003 A42.16,42.16 0 0 0 56.07543474797015,30.20587879419923 Z" fill="var(--flow-toast-warning, #d97706)"></path></svg><figcaption class="space-y-1.5 text-sm"><span class="flex items-center gap-2"><span class="h-2.5 w-2.5 shrink-0 rounded-sm" style="background:var(--primary)"></span><span class="text-muted-foreground">Paid</span><span class="ml-auto font-medium tabular-nums">82</span></span><span class="flex items-center gap-2"><span class="h-2.5 w-2.5 shrink-0 rounded-sm" style="background:var(--flow-toast-success, #16a34a)"></span><span class="text-muted-foreground">Pending</span><span class="ml-auto font-medium tabular-nums">24</span></span><span class="flex items-center gap-2"><span class="h-2.5 w-2.5 shrink-0 rounded-sm" style="background:var(--flow-toast-warning, #d97706)"></span><span class="text-muted-foreground">Refunded</span><span class="ml-auto font-medium tabular-nums">6</span></span></figcaption></figure></div>
|
|
2288
|
+
</div>
|
|
2289
|
+
|
|
2290
|
+
### Chart usage
|
|
2291
|
+
|
|
2292
|
+
```tsx
|
|
2293
|
+
<Chart type="line" labels={days} datasets={[{ label: "Orders", data: counts }]} />
|
|
2294
|
+
<Chart type="donut" labels={["Paid","Pending"]} datasets={[{ data: [82, 18] }]} />
|
|
2295
|
+
```
|
|
2296
|
+
|
|
2297
|
+
### Chart props
|
|
2298
|
+
|
|
2299
|
+
<table>
|
|
2300
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2301
|
+
<tbody>
|
|
2302
|
+
<tr><td><code>type</code></td><td><code>"line" | "area" | "bar" | "donut"</code></td><td><code>"line"</code></td><td>Chart kind.</td></tr>
|
|
2303
|
+
<tr><td><code>labels</code></td><td><code>string[]</code></td><td>—</td><td>Axis or legend labels.</td></tr>
|
|
2304
|
+
<tr><td><code>datasets</code></td><td><code>ChartDataset[]</code></td><td>—</td><td>One or more series.</td></tr>
|
|
2305
|
+
<tr><td><code>height</code></td><td><code>number</code></td><td><code>220</code></td><td>Drawing height; width is fluid.</td></tr>
|
|
2306
|
+
<tr><td><code>format</code></td><td><code>(n: number) => string</code></td><td>—</td><td>Formats axis values and the accessible summary.</td></tr>
|
|
2307
|
+
</tbody>
|
|
2308
|
+
</table>
|
|
2309
|
+
|
|
2310
|
+
<a id="components-typography"></a>
|
|
2311
|
+
|
|
2312
|
+
## Prose
|
|
2313
|
+
|
|
2314
|
+
Prose wrapper + heading helpers.
|
|
2315
|
+
|
|
2316
|
+
### Prose installation
|
|
2317
|
+
|
|
2318
|
+
```sh
|
|
2319
|
+
bun zt flow:add typography
|
|
2320
|
+
```
|
|
2321
|
+
|
|
2322
|
+
Or import directly from the package: `import { Prose } from "@zerotal/flow-ui";`
|
|
2323
|
+
|
|
2324
|
+
### Prose preview
|
|
2325
|
+
|
|
2326
|
+
<div class="not-prose my-6 flex min-h-32 items-center justify-center gap-4 rounded-lg border border-border bg-background p-10">
|
|
2327
|
+
<div class="text-foreground [&_h1]:mt-8 [&_h1]:scroll-m-20 [&_h1]:text-3xl [&_h1]:font-semibold [&_h1]:tracking-tight [&_h2]:mt-8 [&_h2]:scroll-m-20 [&_h2]:border-b [&_h2]:border-border [&_h2]:pb-2 [&_h2]:text-2xl [&_h2]:font-semibold [&_h3]:mt-6 [&_h3]:scroll-m-20 [&_h3]:text-xl [&_h3]:font-semibold [&_p]:leading-7 [&_p:not(:first-child)]:mt-4 [&_a]:font-medium [&_a]:text-primary [&_a]:underline [&_a]:underline-offset-4 [&_ul]:my-4 [&_ul]:ml-6 [&_ul]:list-disc [&_ol]:my-4 [&_ol]:ml-6 [&_ol]:list-decimal [&_li]:mt-2 [&_blockquote]:mt-4 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-4 [&_blockquote]:italic [&_code]:rounded [&_code]:bg-muted [&_code]:px-[0.3rem] [&_code]:py-[0.2rem] [&_code]:font-mono [&_code]:text-sm [&_pre]:mt-4 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:border [&_pre]:border-border [&_pre]:bg-muted [&_pre]:p-4 [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_hr]:my-8 [&_hr]:border-border [&_table]:w-full [&_table]:text-sm [&_th]:border-b [&_th]:border-border [&_th]:px-3 [&_th]:py-2 [&_th]:text-left [&_td]:border-b [&_td]:border-border [&_td]:px-3 [&_td]:py-2 [&_img]:rounded-lg max-w-md"><h2>A rendered document</h2><p>Prose styles its descendants, for content that arrives as a blob — Markdown, a CMS field, a rich-text column.</p><ul><li>Headings, lists and quotes</li><li>Inline <code>code</code> and code blocks</li></ul></div>
|
|
2328
|
+
</div>
|
|
2329
|
+
|
|
2330
|
+
### Prose usage
|
|
2331
|
+
|
|
2332
|
+
```tsx
|
|
2333
|
+
<Prose dangerouslySetInnerHTML={{ __html: rendered }} />
|
|
2334
|
+
<H1>Page title</H1>
|
|
2335
|
+
<Muted>Last updated yesterday</Muted>
|
|
2336
|
+
```
|
|
2337
|
+
|
|
2338
|
+
### Prose props
|
|
2339
|
+
|
|
2340
|
+
<table>
|
|
2341
|
+
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2342
|
+
<tbody>
|
|
2343
|
+
<tr><td><code>children</code></td><td><code>node</code></td><td>—</td><td>Markup you did not author — Prose styles its descendants.</td></tr>
|
|
2344
|
+
<tr><td><code>class</code></td><td><code>string</code></td><td>—</td><td>Extra classes, merged last (wins over defaults).</td></tr>
|
|
2345
|
+
</tbody>
|
|
2346
|
+
</table>
|
|
2347
|
+
|
|
2348
|
+
<!-- END GENERATED COMPONENTS -->
|
|
2349
|
+
|
|
2350
|
+
## Testing
|
|
2351
|
+
|
|
2352
|
+
Set your suite up once as described in [Testing](/docs/testing). A `flow-ui`
|
|
2353
|
+
component is a plain function returning a node, so it tests like any other
|
|
2354
|
+
[view](/docs/view#testing) — render it and assert on the string.
|
|
2355
|
+
|
|
2356
|
+
```typescript
|
|
2357
|
+
// tests/components/Button.test.ts
|
|
2358
|
+
import { test, expect } from "bun:test";
|
|
2359
|
+
import { Button } from "@zerotal/flow-ui";
|
|
2360
|
+
|
|
2361
|
+
test("renders the variant's classes", () => {
|
|
2362
|
+
const html = String(Button({ variant: "destructive", children: "Delete" }));
|
|
2363
|
+
|
|
2364
|
+
expect(html).toContain("Delete");
|
|
2365
|
+
expect(html).toContain("destructive");
|
|
2366
|
+
});
|
|
2367
|
+
```
|
|
2368
|
+
|
|
2369
|
+
**Test your own components, not the library's.** The twenty components ship with
|
|
2370
|
+
their own suite; a test asserting that `Button` renders a `<button>` re-tests
|
|
2371
|
+
someone else's work and breaks when they restyle. What earns a test is the
|
|
2372
|
+
component _you_ composed from them, and the props you pass it.
|
|
2373
|
+
|
|
2374
|
+
**Copy-in components are yours the moment you run `flow:add`.** Once the source
|
|
2375
|
+
lives in your repo, it is application code — it changes when you edit it, and
|
|
2376
|
+
nothing upstream will catch a regression you introduce:
|
|
2377
|
+
|
|
2378
|
+
```typescript
|
|
2379
|
+
// tests/components/StatusBadge.test.ts
|
|
2380
|
+
import { test, expect } from "bun:test";
|
|
2381
|
+
import { StatusBadge } from "../../resources/components/StatusBadge.tsx";
|
|
2382
|
+
|
|
2383
|
+
test("an overdue invoice is flagged", () => {
|
|
2384
|
+
const html = String(StatusBadge({ status: "overdue" }));
|
|
2385
|
+
|
|
2386
|
+
expect(html).toContain("Overdue");
|
|
2387
|
+
expect(html).toContain("bg-red"); // whatever your theme maps it to
|
|
2388
|
+
});
|
|
2389
|
+
```
|
|
2390
|
+
|
|
2391
|
+
**Assert behaviour, not class strings, wherever you can.** A test pinned to
|
|
2392
|
+
`bg-red-500` fails on a palette change that broke nothing. Prefer the visible
|
|
2393
|
+
text, an `aria-` attribute, or a `data-` hook you control.
|
|
2394
|
+
|
|
2395
|
+
> **Note** — Interactive behaviour — a dropdown opening, a dialog trapping focus
|
|
2396
|
+
> — needs a real browser. See [Browser Tests](/docs/testing/browser); rendering
|
|
2397
|
+
> assertions stop at the markup the server produced.
|
|
2398
|
+
|
|
2399
|
+
## References
|
|
2400
|
+
|
|
2401
|
+
The full export surface of `@zerotal/flow-ui`:
|
|
2402
|
+
|
|
2403
|
+
| Export | Kind | Description |
|
|
2404
|
+
| --------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------- |
|
|
2405
|
+
| `FlowUiProvider` | provider | Registers the `flow:list` / `flow:add` / `flow:init` commands. |
|
|
2406
|
+
| `COMPONENTS`, `UTILS`, `findComponent` | registry | The manifest behind `flow:add`. |
|
|
2407
|
+
| `cn(...classes)` | util | Merge class strings (clsx + tailwind-merge). |
|
|
2408
|
+
| `gva(base, config)` | util | Build token-backed variant class functions. |
|
|
2409
|
+
| `Button`, `Badge`, `Card` (+ parts), `Input`, `Textarea`, `Label` | components | Styled leaf and composite components. |
|
|
2410
|
+
| `Separator`, `Skeleton`, `Avatar` | components | Styled leaf components. |
|
|
2411
|
+
| `Switch`, `Checkbox`, `Select`, `RadioGroup` | components | Themed wrappers over Flow's headless primitives. |
|
|
2412
|
+
| `Dialog`, `Sheet`, `DropdownMenu` (+ parts), `Tabs`, `Alert`, `Tooltip`, `Table` | components | Themed interactive components. |
|
|
2413
|
+
| `Disclosure`, `Accordion`, `Popover`, `Listbox`, `Combobox`, `Field`, `Fieldset`, `Legend`, `Description` | re-exports | Headless [Flow](/docs/flow) primitives, re-exported so flow-ui stays a single import. |
|
|
2414
|
+
|
|
2415
|
+
CLI commands (registered by `FlowUiProvider`):
|
|
2416
|
+
|
|
2417
|
+
| Command | Signature | Description |
|
|
2418
|
+
| ----------- | --------------------------------------------------------- | ----------------------------------------- |
|
|
2419
|
+
| `flow:init` | `flow:init [--dir <path>] [--css <path>]` | Set up the shared utils and theme import. |
|
|
2420
|
+
| `flow:add` | `flow:add <name[,name]> [--all] [--force] [--dir <path>]` | Copy component source into your app. |
|
|
2421
|
+
| `flow:list` | `flow:list` | List every component available to add. |
|
|
2422
|
+
|
|
2423
|
+
## Next steps
|
|
2424
|
+
|
|
2425
|
+
- [Flow](/docs/flow) — the server-driven component framework these wrap.
|
|
2426
|
+
- [Validator](/docs/validator) — validate the form fields you bind with `Input` and `Select`.
|
|
2427
|
+
- [Assets](/docs/assets) — build the Tailwind CSS that powers the theme tokens.
|