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.
Files changed (73) hide show
  1. package/apps/lib/components/Avatar.tsx +1 -1
  2. package/apps/lib/components/BadgeField.tsx +2 -2
  3. package/apps/lib/components/Card.tsx +68 -54
  4. package/apps/lib/components/DataViews/ARCHITECTURE.md +439 -0
  5. package/apps/lib/components/DataViews/DataViewRadio.tsx +47 -0
  6. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +427 -0
  7. package/apps/lib/components/DataViews/DataViewsHeader.tsx +228 -0
  8. package/apps/lib/components/DataViews/DataViewsLayout.tsx +330 -0
  9. package/apps/lib/components/DataViews/FilterPanel.tsx +469 -0
  10. package/apps/lib/components/DataViews/HeaderSearch.tsx +97 -0
  11. package/apps/lib/components/DataViews/InboxView.tsx +495 -0
  12. package/apps/lib/components/DataViews/InboxViewCard.tsx +136 -0
  13. package/apps/lib/components/DataViews/KanbanView.tsx +353 -0
  14. package/apps/lib/components/DataViews/PanelControls.tsx +49 -0
  15. package/apps/lib/components/DataViews/SettingsPanel.tsx +285 -0
  16. package/apps/lib/components/DataViews/TableView.tsx +232 -0
  17. package/apps/lib/components/DataViews/TreeView.tsx +392 -0
  18. package/apps/lib/components/DataViews/badgeAdapter.ts +45 -0
  19. package/apps/lib/components/DataViews/fieldRenderers.tsx +334 -0
  20. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +113 -0
  21. package/apps/lib/components/DataViews/filters/PresetChips.tsx +45 -0
  22. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +154 -0
  23. package/apps/lib/components/DataViews/index.ts +36 -0
  24. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +54 -0
  25. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +77 -0
  26. package/apps/lib/components/DataViews/types.ts +206 -0
  27. package/apps/lib/components/Radio.tsx +18 -21
  28. package/apps/lib/components/Switch.tsx +3 -1
  29. package/apps/lib/components/Table.tsx +1 -1
  30. package/apps/lib/components/TreeFolder/TreeFolder.tsx +410 -0
  31. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +80 -0
  32. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +363 -0
  33. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +60 -0
  34. package/apps/lib/components/TreeFolder/icons.tsx +63 -0
  35. package/apps/lib/components/TreeFolder/index.ts +17 -0
  36. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +114 -0
  37. package/apps/lib/components/TreeFolder/types.ts +77 -0
  38. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +261 -0
  39. package/apps/lib/hooks/useDataViewsState.ts +169 -0
  40. package/apps/lib/hooks/useIsMobile.ts +21 -0
  41. package/apps/lib/layouts/DataViewCard.tsx +76 -0
  42. package/apps/lib/utils/dataViews/columnUtils.ts +130 -0
  43. package/apps/lib/utils/dataViews/fieldUtils.ts +198 -0
  44. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +364 -0
  45. package/apps/lib/utils/dataViews/pathUtils.ts +132 -0
  46. package/apps/lib/utils/dataViews/rangeUtils.ts +225 -0
  47. package/apps/lib/utils/dataViews/treeUtils.ts +403 -0
  48. package/dist/bin/index.js +3 -3
  49. package/dist/bin/index.js.map +1 -1
  50. package/dist/src/commands/add.d.ts.map +1 -1
  51. package/dist/src/commands/add.js +29 -6
  52. package/dist/src/commands/add.js.map +1 -1
  53. package/dist/src/commands/utils.d.ts.map +1 -1
  54. package/dist/src/commands/utils.js +22 -2
  55. package/dist/src/commands/utils.js.map +1 -1
  56. package/dist/src/shared/copyComponentsRecursively.d.ts.map +1 -1
  57. package/dist/src/shared/copyComponentsRecursively.js +17 -2
  58. package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
  59. package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts +18 -4
  60. package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
  61. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +110 -40
  62. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
  63. package/docs/components/data-views-config-panel.md +204 -0
  64. package/docs/components/data-views-layout.md +270 -0
  65. package/docs/components/form-stepper.md +244 -0
  66. package/docs/components/stepper.md +215 -0
  67. package/docs/components/timeline.md +248 -0
  68. package/package.json +6 -6
  69. package/apps/lib/components/Charts-dev.tsx +0 -365
  70. package/apps/lib/components/Command-dev.tsx +0 -151
  71. package/apps/lib/components/IosDatePicker-dev.tsx +0 -341
  72. /package/docs/components/{labeled-checkbox.md → labeled-check-box.md} +0 -0
  73. /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.