torch-glare 2.1.1 → 2.1.3
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/apps/lib/components/Avatar.tsx +1 -1
- package/apps/lib/components/BadgeField.tsx +2 -2
- package/apps/lib/components/Card.tsx +68 -54
- package/apps/lib/components/DataViews/ARCHITECTURE.md +439 -0
- package/apps/lib/components/DataViews/DataViewRadio.tsx +47 -0
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +427 -0
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +228 -0
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +330 -0
- package/apps/lib/components/DataViews/FilterPanel.tsx +469 -0
- package/apps/lib/components/DataViews/HeaderSearch.tsx +97 -0
- package/apps/lib/components/DataViews/InboxView.tsx +495 -0
- package/apps/lib/components/DataViews/InboxViewCard.tsx +136 -0
- package/apps/lib/components/DataViews/KanbanView.tsx +353 -0
- package/apps/lib/components/DataViews/PanelControls.tsx +49 -0
- package/apps/lib/components/DataViews/SettingsPanel.tsx +285 -0
- package/apps/lib/components/DataViews/TableView.tsx +232 -0
- package/apps/lib/components/DataViews/TreeView.tsx +392 -0
- package/apps/lib/components/DataViews/badgeAdapter.ts +45 -0
- package/apps/lib/components/DataViews/fieldRenderers.tsx +334 -0
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +113 -0
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +45 -0
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +154 -0
- package/apps/lib/components/DataViews/index.ts +36 -0
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +54 -0
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +77 -0
- package/apps/lib/components/DataViews/types.ts +206 -0
- package/apps/lib/components/Radio.tsx +18 -21
- package/apps/lib/components/Switch.tsx +3 -1
- package/apps/lib/components/Table.tsx +1 -1
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +410 -0
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +80 -0
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +363 -0
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +60 -0
- package/apps/lib/components/TreeFolder/icons.tsx +63 -0
- package/apps/lib/components/TreeFolder/index.ts +17 -0
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +114 -0
- package/apps/lib/components/TreeFolder/types.ts +77 -0
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +261 -0
- package/apps/lib/hooks/useDataViewsState.ts +169 -0
- package/apps/lib/hooks/useIsMobile.ts +21 -0
- package/apps/lib/layouts/DataViewCard.tsx +76 -0
- package/apps/lib/utils/dataViews/columnUtils.ts +130 -0
- package/apps/lib/utils/dataViews/fieldUtils.ts +198 -0
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +364 -0
- package/apps/lib/utils/dataViews/pathUtils.ts +132 -0
- package/apps/lib/utils/dataViews/rangeUtils.ts +225 -0
- package/apps/lib/utils/dataViews/treeUtils.ts +403 -0
- package/dist/bin/index.js +3 -3
- package/dist/bin/index.js.map +1 -1
- package/dist/src/commands/add.d.ts.map +1 -1
- package/dist/src/commands/add.js +29 -6
- package/dist/src/commands/add.js.map +1 -1
- package/dist/src/commands/utils.d.ts.map +1 -1
- package/dist/src/commands/utils.js +22 -2
- package/dist/src/commands/utils.js.map +1 -1
- package/dist/src/shared/copyComponentsRecursively.d.ts.map +1 -1
- package/dist/src/shared/copyComponentsRecursively.js +17 -2
- package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts +18 -4
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +110 -40
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
- package/docs/components/data-views-config-panel.md +204 -0
- package/docs/components/data-views-layout.md +270 -0
- package/docs/components/form-stepper.md +244 -0
- package/docs/components/stepper.md +215 -0
- package/docs/components/timeline.md +248 -0
- package/package.json +6 -6
- package/apps/lib/components/Charts-dev.tsx +0 -365
- package/apps/lib/components/Command-dev.tsx +0 -151
- package/apps/lib/components/IosDatePicker-dev.tsx +0 -341
- /package/docs/components/{labeled-checkbox.md → labeled-check-box.md} +0 -0
- /package/docs/components/{tree-dropdown.md → tree-drop-down.md} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: DataViewsLayout
|
|
3
|
+
description: Composable multi-view layout that renders any backend response as Table, Kanban, Inbox, and/or Tree, with per-screen selection of which views to show.
|
|
4
|
+
group: Data Display
|
|
5
|
+
keywords: [data-views, layout, table, kanban, inbox, tree, multi-view, filter, switcher, dashboard, dynamic-data, fields]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# DataViewsLayout
|
|
9
|
+
|
|
10
|
+
> One backend response → many UI shapes. Pass an array of records plus a declarative list of fields, then pick which views the screen needs (`{ table: true, kanban: true }`, etc.). Filters and the settings cog are togglable per-screen.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
The component is part of `torch-glare`. It depends on `@radix-ui/react-slider`, `react-day-picker`, `lucide-react`, and `vaul` (all already vendored by the library). No extra install needed.
|
|
15
|
+
|
|
16
|
+
## Import
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { DataViewsLayout } from "torch-glare"
|
|
20
|
+
import type { FieldConfig, ViewVisibility } from "torch-glare"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Examples
|
|
24
|
+
|
|
25
|
+
### 1. All views, auto-detected
|
|
26
|
+
|
|
27
|
+
Just pass `data` — every primitive field becomes a column, all four views appear, the Tree tab auto-hides if no `children`/`parentId` is found.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
const employees = [
|
|
31
|
+
{ id: 1, name: "Ada Lovelace", role: "Engineer", salary: 120000, joinDate: "2024-04-12" },
|
|
32
|
+
{ id: 2, name: "Linus Torvalds", role: "Engineer", salary: 145000, joinDate: "2023-09-01" },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
<DataViewsLayout title="Employees" data={employees} />
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Pick specific views only
|
|
39
|
+
|
|
40
|
+
Use `views` to enable only what this screen needs. Tabs for the others are hidden.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<DataViewsLayout
|
|
44
|
+
title="Orders"
|
|
45
|
+
data={orders}
|
|
46
|
+
views={{ table: true, kanban: true }} // only Table + Kanban tabs render
|
|
47
|
+
kanbanGroupBy="status"
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Declarative fields with badges and filters
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
const fields: FieldConfig[] = [
|
|
55
|
+
{ path: "id", label: "Order #", type: "number" },
|
|
56
|
+
{ path: "customer", type: "text" },
|
|
57
|
+
{
|
|
58
|
+
path: "status",
|
|
59
|
+
type: "enum-badge",
|
|
60
|
+
variants: { Pending: "yellow", Shipped: "blue", Delivered: "green" },
|
|
61
|
+
filterable: true,
|
|
62
|
+
},
|
|
63
|
+
{ path: "total", type: "currency", currency: "USD", filterable: true },
|
|
64
|
+
{ path: "createdAt", type: "date-format", dateFormat: "YYYY-MM-DD", filterable: true },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
<DataViewsLayout
|
|
68
|
+
title="Orders"
|
|
69
|
+
data={orders}
|
|
70
|
+
fields={fields}
|
|
71
|
+
views={{ table: true, kanban: true }}
|
|
72
|
+
kanbanGroupBy="status"
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 4. Disable filters and the settings cog
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<DataViewsLayout
|
|
80
|
+
data={orders}
|
|
81
|
+
fields={fields}
|
|
82
|
+
views={{ table: true }}
|
|
83
|
+
showFilters={false}
|
|
84
|
+
showSettings={false}
|
|
85
|
+
/>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 5. Controlled filter state (wire to backend pagination)
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
const [filterState, setFilterState] = useState<FilterState>({})
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
fetchFromBackend({ filters: filterState }).then(setRows)
|
|
95
|
+
}, [filterState])
|
|
96
|
+
|
|
97
|
+
<DataViewsLayout
|
|
98
|
+
data={rows}
|
|
99
|
+
fields={fields}
|
|
100
|
+
filterState={filterState}
|
|
101
|
+
onFilterChange={setFilterState}
|
|
102
|
+
/>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 6. Hierarchical data
|
|
106
|
+
|
|
107
|
+
When records carry a `children: []` array (or a `parentId` reference), the Tree tab auto-appears. Override the detection with `treeConfig`.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<DataViewsLayout
|
|
111
|
+
data={departments} // each has children[]
|
|
112
|
+
treeConfig={{ childrenField: "children", nodeLabel: "name", defaultExpanded: "roots" }}
|
|
113
|
+
fields={fields}
|
|
114
|
+
/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 7. Inbox shape (read/starred/priority)
|
|
118
|
+
|
|
119
|
+
Inbox auto-detects `isRead`, `isStarred`, `hasAttachment`, `priority`. Override with `inboxConfig`.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<DataViewsLayout
|
|
123
|
+
data={messages}
|
|
124
|
+
fields={fields}
|
|
125
|
+
views={{ inbox: true }}
|
|
126
|
+
inboxConfig={{ titlePath: "subject", previewPath: "from.name" }}
|
|
127
|
+
/>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## API Reference
|
|
131
|
+
|
|
132
|
+
### `DataViewsLayoutProps`
|
|
133
|
+
|
|
134
|
+
| Prop | Type | Default | Description |
|
|
135
|
+
|---|---|---|---|
|
|
136
|
+
| `data` | `DynamicRecord[]` | `[]` | Array of records of any shape. Fields are auto-detected from the first records. |
|
|
137
|
+
| `fields` | `FieldConfig[]` | auto-detected | Declarative field map: `path`, `type`, `filterable`, `variants`, `currency`, etc. |
|
|
138
|
+
| `title` | `string` | `"Data Views"` | Header title (hidden when `showTitle={false}`). |
|
|
139
|
+
| `description` | `string` | `"Unified data visualization across multiple views"` | Subtitle under the title. |
|
|
140
|
+
| `views` | `ViewVisibility` | all on (tree auto) | Per-view toggle: `{ table?, kanban?, inbox?, tree? }`. Omitted keys default to `true` (Tree auto-hides without hierarchy). |
|
|
141
|
+
| `kanbanGroupBy` | `string` | `"status"` | Dot-path to the field used for Kanban columns. |
|
|
142
|
+
| `inboxConfig` | `InboxConfig` | auto-detected | Map of starred/read/attachment/priority field paths for Inbox. |
|
|
143
|
+
| `treeConfig` | `TreeConfig` | auto-detected | `childrenField`, `parentField`, `idField`, `nodeLabel`, `defaultExpanded`. |
|
|
144
|
+
| `filterState` | `FilterState` | uncontrolled | Controlled filter state. Pair with `onFilterChange`. |
|
|
145
|
+
| `onFilterChange` | `(state: FilterState) => void` | — | Fires when any filter changes. When provided, the layout is controlled. |
|
|
146
|
+
| `showFilters` | `boolean` | `true` | Hide the filter panel inside every view. |
|
|
147
|
+
| `showSettings` | `boolean` | `true` | Hide the settings cog + side panel. |
|
|
148
|
+
| `showTitle` | `boolean` | `true` | Hide the header bar entirely. Useful when embedding. |
|
|
149
|
+
| `config` | `Partial<ViewConfig>` | — | Initial config: `defaultView`, `sortBy`, `sortOrder`, etc. |
|
|
150
|
+
| `className` | `string` | — | Forwarded to the root `<div>`. |
|
|
151
|
+
| `theme` | `"dark" \| "light" \| "default"` | — | Applied as `data-theme` on the root. |
|
|
152
|
+
|
|
153
|
+
### `FieldConfig`
|
|
154
|
+
|
|
155
|
+
| Prop | Type | Description |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `path` | `string` | Dot-path into the record (`"contact.email"`). |
|
|
158
|
+
| `label` | `string` | Display label. Auto-formatted from path tail if omitted. |
|
|
159
|
+
| `type` | `FieldType` | Renderer key (see below). Auto-inferred if omitted. |
|
|
160
|
+
| `visible` | `boolean` | Show in cells. Default `true`. |
|
|
161
|
+
| `order` | `number` | Display order. |
|
|
162
|
+
| `filterable` | `boolean` | Surface this field in the filter panel. |
|
|
163
|
+
| `variants` | `Record<string, BadgeVariant>` | For `enum-badge`: per-value color map. |
|
|
164
|
+
| `currency` | `string \| CurrencyOptions` | For `currency`: ISO code or `{ symbol, locale, decimals, code }`. |
|
|
165
|
+
| `thresholds` | `[number, number]` | For `progress-bar`: warning/ok thresholds. |
|
|
166
|
+
| `max` | `number` | For `star-rating`: stars displayed. |
|
|
167
|
+
| `dateFormat` | `string \| Intl.DateTimeFormatOptions` | For `date-format`: token like `"YYYY-MM-DD"` or Intl options. |
|
|
168
|
+
| `render` | `(value, row) => ReactNode` | Escape hatch for custom JSX. |
|
|
169
|
+
|
|
170
|
+
### `FieldType` (built-in renderers)
|
|
171
|
+
|
|
172
|
+
`text` · `number` · `date` · `date-format` · `boolean` · `currency` · `number-format` · `enum-badge` · `badge-array` · `progress-bar` · `star-rating` · `icon-text` · `two-line` · `avatar` · `link` · `image` · `hidden`
|
|
173
|
+
|
|
174
|
+
### `BadgeVariant`
|
|
175
|
+
|
|
176
|
+
`green` · `greenLight` · `cocktailGreen` · `yellow` · `redOrange` · `redLight` · `rose` · `purple` · `bluePurple` · `blue` · `navy` · `gray` · `highlight`
|
|
177
|
+
|
|
178
|
+
### `ViewVisibility`
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
type ViewVisibility = {
|
|
182
|
+
table?: boolean
|
|
183
|
+
kanban?: boolean
|
|
184
|
+
inbox?: boolean
|
|
185
|
+
tree?: boolean
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Config & Filters Panel
|
|
190
|
+
|
|
191
|
+
The settings cog in the header (shown unless `showSettings={false}`) opens a
|
|
192
|
+
slide-in side panel — [`DataViewsConfigPanel`](./data-views-config-panel.md).
|
|
193
|
+
`DataViewsLayout` mounts and animates it for you; you do not render it yourself
|
|
194
|
+
in tab mode. It has two tabs:
|
|
195
|
+
|
|
196
|
+
| Tab | Section | What it does | Wired through `DataViewsLayout`? |
|
|
197
|
+
|---|---|---|---|
|
|
198
|
+
| **Config.** | Saved View | Radio list of named views + "Save a New View" button. | ❌ **Presentational only.** The layout does not pass `savedViews` / `onSavedViewChange` / `onSaveNewView`, so the panel shows a single fallback "Default View" and selection is local-state only (it does not persist or change `config`). See "Saved View status" below. |
|
|
199
|
+
| **Config.** | Table Columns | Show/hide each column (green `Switch`) and drag-reorder them. | ✅ Reads/writes `config.tableColumns` via the layout's internal config state. |
|
|
200
|
+
| **Config.** | Default Sort | Single-choice radio list that sets `config.sortBy`. | ✅ Writes `config.sortBy` (sort direction stays on `config.sortOrder`). |
|
|
201
|
+
| **Filters** | — | Delegates to `FilterPanel` for the same `fields` you passed. | ✅ Reads/writes the layout's `filterState` (and your `onFilterChange` if controlled). |
|
|
202
|
+
|
|
203
|
+
### Saved View status (read this before relying on it)
|
|
204
|
+
|
|
205
|
+
Saved View is currently a **presentational shell**. The props exist on
|
|
206
|
+
`DataViewsConfigPanel` (`savedViews`, `activeSavedView`, `onSavedViewChange`,
|
|
207
|
+
`onSaveNewView`) but `DataViewsLayout` does **not** thread them through. In tab
|
|
208
|
+
mode you therefore cannot supply or persist saved views today — clicking a row
|
|
209
|
+
updates the panel's internal state only and resets on unmount.
|
|
210
|
+
|
|
211
|
+
To get real saved-view behaviour you have two options:
|
|
212
|
+
|
|
213
|
+
1. Use **Composable Mode** (below) and render `DataViewsConfigPanel` yourself,
|
|
214
|
+
passing `savedViews` + `onSavedViewChange` wired to your own persistence.
|
|
215
|
+
2. Extend `DataViewsLayout` to forward the four `savedView*` props down to the
|
|
216
|
+
panel (see "Extending the panel" in the
|
|
217
|
+
[`DataViewsConfigPanel` doc](./data-views-config-panel.md)).
|
|
218
|
+
|
|
219
|
+
This limitation is documented honestly so an agent does not generate code that
|
|
220
|
+
passes `savedViews` to `DataViewsLayout` expecting it to work.
|
|
221
|
+
|
|
222
|
+
## Composable Mode (no tabs)
|
|
223
|
+
|
|
224
|
+
When you want a custom layout (e.g. Table on the left, Kanban on the right), bypass `DataViewsLayout` and compose the views directly.
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { TableView, KanbanView, FilterPanel, useDataViewsState } from "torch-glare"
|
|
228
|
+
|
|
229
|
+
function CustomScreen({ data, fields }: Props) {
|
|
230
|
+
const state = useDataViewsState({ data, fields })
|
|
231
|
+
return (
|
|
232
|
+
<div className="grid grid-cols-2 gap-4 h-screen">
|
|
233
|
+
<TableView
|
|
234
|
+
data={state.flatItems}
|
|
235
|
+
fields={state.resolvedFields}
|
|
236
|
+
config={state.config}
|
|
237
|
+
filterState={state.filterState}
|
|
238
|
+
onFilterChange={state.setFilterState}
|
|
239
|
+
showFilters={false}
|
|
240
|
+
/>
|
|
241
|
+
<KanbanView
|
|
242
|
+
data={state.flatItems}
|
|
243
|
+
fields={state.resolvedFields}
|
|
244
|
+
config={state.config}
|
|
245
|
+
groupByField="status"
|
|
246
|
+
/>
|
|
247
|
+
</div>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Accessibility
|
|
253
|
+
|
|
254
|
+
- The view-switcher uses `TabFormItem` (button-based, full keyboard support via Tab/Enter/Space).
|
|
255
|
+
- Tree rows expose `role="treeitem"` with `aria-expanded` and `aria-selected`.
|
|
256
|
+
- Filter checkboxes carry labels and `htmlFor` linkage.
|
|
257
|
+
- Settings panel buttons have `aria-pressed` for sort direction.
|
|
258
|
+
|
|
259
|
+
## Theming
|
|
260
|
+
|
|
261
|
+
The component uses only `*-presentation-*` design tokens. Wrap with `ThemeProvider` or pass `theme="dark" | "light" | "default"` to control color scheme.
|
|
262
|
+
|
|
263
|
+
## Related
|
|
264
|
+
|
|
265
|
+
- [`DataViewsConfigPanel`](./data-views-config-panel.md) — the settings/filters side panel (Saved View, columns, sort)
|
|
266
|
+
- [`TableView`](./table-view.md) — standalone table
|
|
267
|
+
- [`KanbanView`](./kanban-view.md) — standalone kanban
|
|
268
|
+
- [`InboxView`](./inbox-view.md) — standalone inbox
|
|
269
|
+
- [`TreeView`](./tree-view.md) — standalone tree
|
|
270
|
+
- [How-to: Render a backend response with DataViews](../how-to/data-views-from-backend-response.md) — recipes by data shape.
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: FormStepper
|
|
3
|
+
description: Pill-shaped multi-step indicator for forms and wizards. Three semantic step types (default, success, negative) with resting, hover, selected, and selected-hover states. Full LTR and RTL support.
|
|
4
|
+
component: true
|
|
5
|
+
group: Forms
|
|
6
|
+
keywords: [form-stepper, stepper, wizard, steps, pill, multi-step, form, indicator, RTL]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# FormStepper
|
|
10
|
+
|
|
11
|
+
A pill-shaped multi-step indicator for forms and wizards. Each step renders a circular indicator and a label inside a pill. Selection swaps the pill background to black with a white label; hover deepens the shadow on selected pills and grows the label gap on non-selected ones. The status badge on the indicator switches the visual to `success` (green check) or `negative` (red info).
|
|
12
|
+
|
|
13
|
+
The component is composed of `FormStepper`, `FormStep`, `FormStepIndicator`, and `FormStepLabel`.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx torch-glare@latest add FormStepper
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Imports
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import {
|
|
25
|
+
FormStepper,
|
|
26
|
+
FormStep,
|
|
27
|
+
FormStepIndicator,
|
|
28
|
+
FormStepLabel,
|
|
29
|
+
} from '@/components/FormStepper'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Basic Usage
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { useState } from 'react'
|
|
36
|
+
import {
|
|
37
|
+
FormStepper,
|
|
38
|
+
FormStep,
|
|
39
|
+
FormStepIndicator,
|
|
40
|
+
FormStepLabel,
|
|
41
|
+
} from '@/components/FormStepper'
|
|
42
|
+
|
|
43
|
+
export function BasicFormStepper() {
|
|
44
|
+
const [activeStep, setActiveStep] = useState(0)
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<FormStepper activeStep={activeStep}>
|
|
48
|
+
<FormStep index={0} type="success" onClick={() => setActiveStep(0)}>
|
|
49
|
+
<FormStepIndicator />
|
|
50
|
+
<FormStepLabel>Account</FormStepLabel>
|
|
51
|
+
</FormStep>
|
|
52
|
+
<FormStep index={1} type="default" onClick={() => setActiveStep(1)}>
|
|
53
|
+
<FormStepIndicator />
|
|
54
|
+
<FormStepLabel>Profile</FormStepLabel>
|
|
55
|
+
</FormStep>
|
|
56
|
+
<FormStep index={2} type="negative" onClick={() => setActiveStep(2)}>
|
|
57
|
+
<FormStepIndicator />
|
|
58
|
+
<FormStepLabel>Payment</FormStepLabel>
|
|
59
|
+
</FormStep>
|
|
60
|
+
<FormStep index={3} type="default" onClick={() => setActiveStep(3)}>
|
|
61
|
+
<FormStepIndicator />
|
|
62
|
+
<FormStepLabel>Confirm</FormStepLabel>
|
|
63
|
+
</FormStep>
|
|
64
|
+
</FormStepper>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`FormStepper.activeStep` drives which pill renders selected — each `FormStep` auto-selects when its `index` matches. Pass `selected` on a step to override the match.
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
### Step Types
|
|
74
|
+
|
|
75
|
+
Three semantic types. `success` and `negative` add a small status badge on the indicator (check / info icon) and use filled colors when selected. `default` uses a gray ring at rest, blue ring on hover, and a solid blue fill when selected.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
export function StepTypes() {
|
|
79
|
+
return (
|
|
80
|
+
<FormStepper>
|
|
81
|
+
<FormStep index={0} type="default" selected={false}>
|
|
82
|
+
<FormStepIndicator />
|
|
83
|
+
<FormStepLabel>Default</FormStepLabel>
|
|
84
|
+
</FormStep>
|
|
85
|
+
<FormStep index={1} type="success" selected={false}>
|
|
86
|
+
<FormStepIndicator />
|
|
87
|
+
<FormStepLabel>Success</FormStepLabel>
|
|
88
|
+
</FormStep>
|
|
89
|
+
<FormStep index={2} type="negative" selected={false}>
|
|
90
|
+
<FormStepIndicator />
|
|
91
|
+
<FormStepLabel>Negative</FormStepLabel>
|
|
92
|
+
</FormStep>
|
|
93
|
+
</FormStepper>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Selected state
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
export function SelectedSteps() {
|
|
102
|
+
return (
|
|
103
|
+
<FormStepper>
|
|
104
|
+
<FormStep index={0} type="default" selected>
|
|
105
|
+
<FormStepIndicator />
|
|
106
|
+
<FormStepLabel>Default</FormStepLabel>
|
|
107
|
+
</FormStep>
|
|
108
|
+
<FormStep index={1} type="success" selected>
|
|
109
|
+
<FormStepIndicator />
|
|
110
|
+
<FormStepLabel>Success</FormStepLabel>
|
|
111
|
+
</FormStep>
|
|
112
|
+
<FormStep index={2} type="negative" selected>
|
|
113
|
+
<FormStepIndicator />
|
|
114
|
+
<FormStepLabel>Negative</FormStepLabel>
|
|
115
|
+
</FormStep>
|
|
116
|
+
</FormStepper>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### RTL direction
|
|
122
|
+
|
|
123
|
+
The pill, label spacing, and indicator badge all flip under `dir="rtl"`.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
export function RTLFormStepper() {
|
|
127
|
+
return (
|
|
128
|
+
<div dir="rtl">
|
|
129
|
+
<FormStepper>
|
|
130
|
+
<FormStep index={0} type="default" selected>
|
|
131
|
+
<FormStepIndicator />
|
|
132
|
+
<FormStepLabel>افتراضي</FormStepLabel>
|
|
133
|
+
</FormStep>
|
|
134
|
+
<FormStep index={1} type="success">
|
|
135
|
+
<FormStepIndicator />
|
|
136
|
+
<FormStepLabel>نجاح</FormStepLabel>
|
|
137
|
+
</FormStep>
|
|
138
|
+
<FormStep index={2} type="negative">
|
|
139
|
+
<FormStepIndicator />
|
|
140
|
+
<FormStepLabel>خطأ</FormStepLabel>
|
|
141
|
+
</FormStep>
|
|
142
|
+
</FormStepper>
|
|
143
|
+
</div>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Custom badge icon
|
|
149
|
+
|
|
150
|
+
`FormStepIndicator.badgeIcon` overrides the default check / info icon for `success` / `negative` types.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<FormStep index={0} type="success" selected>
|
|
154
|
+
<FormStepIndicator badgeIcon={<i className="ri-shield-check-line" />} />
|
|
155
|
+
<FormStepLabel>Verified</FormStepLabel>
|
|
156
|
+
</FormStep>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Custom indicator content
|
|
160
|
+
|
|
161
|
+
Children of `FormStepIndicator` replace the auto-rendered step number.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
<FormStep index={0} type="default" selected>
|
|
165
|
+
<FormStepIndicator>
|
|
166
|
+
<i className="ri-user-line" />
|
|
167
|
+
</FormStepIndicator>
|
|
168
|
+
<FormStepLabel>Account</FormStepLabel>
|
|
169
|
+
</FormStep>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## API Reference
|
|
173
|
+
|
|
174
|
+
### FormStepper
|
|
175
|
+
|
|
176
|
+
| Prop | Type | Default | Description |
|
|
177
|
+
| ------------ | ------------------------------- | ------- | ----------------------------------------------------------------- |
|
|
178
|
+
| `activeStep` | `number` | `0` | Zero-based index of the currently selected step. |
|
|
179
|
+
| `theme` | `'dark' \| 'light' \| 'default'` | — | Theme override applied via `data-theme`. |
|
|
180
|
+
| `className` | `string` | — | Extra classes merged onto the root `<div>`. |
|
|
181
|
+
|
|
182
|
+
Standard `HTMLAttributes<HTMLDivElement>` are forwarded.
|
|
183
|
+
|
|
184
|
+
### FormStep
|
|
185
|
+
|
|
186
|
+
| Prop | Type | Default | Description |
|
|
187
|
+
| ----------- | --------------------------------------- | ----------- | -------------------------------------------------------------------------- |
|
|
188
|
+
| `index` | `number` | `0` | Zero-based step index. Matched against `FormStepper.activeStep`. |
|
|
189
|
+
| `type` | `'default' \| 'success' \| 'negative'` | `'default'` | Visual type. `success` and `negative` add a status badge on the indicator. |
|
|
190
|
+
| `selected` | `boolean` | — | Force the selected state, overriding the index/`activeStep` match. |
|
|
191
|
+
| `className` | `string` | — | Extra classes merged onto the pill root. |
|
|
192
|
+
|
|
193
|
+
Standard `HTMLAttributes<HTMLDivElement>` (minus `type`) are forwarded — typical use is `onClick` for navigation.
|
|
194
|
+
|
|
195
|
+
### FormStepIndicator
|
|
196
|
+
|
|
197
|
+
| Prop | Type | Default | Description |
|
|
198
|
+
| ----------- | ----------- | ------- | ----------------------------------------------------------------------------- |
|
|
199
|
+
| `badgeIcon` | `ReactNode` | — | Override the default badge icon (check for `success`, info for `negative`). |
|
|
200
|
+
| `children` | `ReactNode` | — | Replaces the auto-rendered step number. |
|
|
201
|
+
| `className` | `string` | — | Extra classes merged onto the indicator `<div>`. |
|
|
202
|
+
|
|
203
|
+
### FormStepLabel
|
|
204
|
+
|
|
205
|
+
Forwards `HTMLAttributes<HTMLDivElement>`. Color and label spacing follow the parent `FormStep` selection state.
|
|
206
|
+
|
|
207
|
+
## Styling
|
|
208
|
+
|
|
209
|
+
- Pill height: `28px`, fully rounded, `2px` inner padding.
|
|
210
|
+
- Indicator: `24×24px` circle, `border-[3px]` for `default`, solid fill for `success`/`negative`.
|
|
211
|
+
- Status badge: `15×15px` circle pinned to the top-right of the indicator (top-left under RTL), with `border` matching the page background.
|
|
212
|
+
- Selection: `bg-[#000000]` pill with `text-[#FFFFFF]` label and a `0 0 32px 2px rgba(0,0,0,0.05)` shadow that deepens on hover.
|
|
213
|
+
- Non-selected hover: `bg-[#FFFFFF]` pill, label gap grows from `6px` to `9px`, ring color flips to `#004699`.
|
|
214
|
+
|
|
215
|
+
## TypeScript Types
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
type FormStepperType = 'default' | 'success' | 'negative'
|
|
219
|
+
|
|
220
|
+
interface FormStepperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
221
|
+
activeStep?: number
|
|
222
|
+
theme?: 'dark' | 'light' | 'default'
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface FormStepProps
|
|
226
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'type'> {
|
|
227
|
+
index?: number
|
|
228
|
+
type?: FormStepperType
|
|
229
|
+
selected?: boolean
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Accessibility
|
|
234
|
+
|
|
235
|
+
- Each `FormStep` is a focusable interactive surface — attach `onClick` and (if needed) `role="button"` plus `tabIndex={0}` for keyboard navigation.
|
|
236
|
+
- The status badge is `aria-hidden`; the meaning should be carried by the label text (e.g. `"Payment — error"`).
|
|
237
|
+
- Color is never the sole signal for `success`/`negative` — pair with text or an off-screen description.
|
|
238
|
+
|
|
239
|
+
## Best Practices
|
|
240
|
+
|
|
241
|
+
1. Drive selection with `activeStep` from the parent form state — avoid setting `selected` per step manually.
|
|
242
|
+
2. Use `success` only for *completed and validated* steps, `negative` only for *failed* steps. Default = pending or current.
|
|
243
|
+
3. Keep labels short — the pill grows by `~3px` on hover, and long labels make that animation jittery.
|
|
244
|
+
4. Wire `onClick` for non-linear navigation (jump-to-step). For strict wizards, omit `onClick` on future steps.
|