@dashforge/tw 0.3.0-beta → 0.4.0-beta

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 (58) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/PARITY.md +483 -0
  3. package/PERFORMANCE.md +169 -0
  4. package/dist/index.esm.js +493 -2
  5. package/dist/src/components/Accordion/Accordion.d.ts +21 -0
  6. package/dist/src/components/Accordion/Accordion.d.ts.map +1 -0
  7. package/dist/src/components/Accordion/accordion.types.d.ts +74 -0
  8. package/dist/src/components/Accordion/accordion.types.d.ts.map +1 -0
  9. package/dist/src/components/Accordion/accordion.variants.d.ts +58 -0
  10. package/dist/src/components/Accordion/accordion.variants.d.ts.map +1 -0
  11. package/dist/src/components/Dialog/Dialog.d.ts +17 -0
  12. package/dist/src/components/Dialog/Dialog.d.ts.map +1 -0
  13. package/dist/src/components/Dialog/dialog.types.d.ts +75 -0
  14. package/dist/src/components/Dialog/dialog.types.d.ts.map +1 -0
  15. package/dist/src/components/Dialog/dialog.variants.d.ts +80 -0
  16. package/dist/src/components/Dialog/dialog.variants.d.ts.map +1 -0
  17. package/dist/src/components/Popover/Popover.d.ts +15 -0
  18. package/dist/src/components/Popover/Popover.d.ts.map +1 -0
  19. package/dist/src/components/Popover/popover.types.d.ts +45 -0
  20. package/dist/src/components/Popover/popover.types.d.ts.map +1 -0
  21. package/dist/src/components/Popover/popover.variants.d.ts +34 -0
  22. package/dist/src/components/Popover/popover.variants.d.ts.map +1 -0
  23. package/dist/src/components/Tabs/Tabs.d.ts +15 -0
  24. package/dist/src/components/Tabs/Tabs.d.ts.map +1 -0
  25. package/dist/src/components/Tabs/tabs.types.d.ts +55 -0
  26. package/dist/src/components/Tabs/tabs.types.d.ts.map +1 -0
  27. package/dist/src/components/Tabs/tabs.variants.d.ts +82 -0
  28. package/dist/src/components/Tabs/tabs.variants.d.ts.map +1 -0
  29. package/dist/src/components/Tooltip/Tooltip.d.ts +29 -0
  30. package/dist/src/components/Tooltip/Tooltip.d.ts.map +1 -0
  31. package/dist/src/components/Tooltip/tooltip.types.d.ts +47 -0
  32. package/dist/src/components/Tooltip/tooltip.types.d.ts.map +1 -0
  33. package/dist/src/components/Tooltip/tooltip.variants.d.ts +34 -0
  34. package/dist/src/components/Tooltip/tooltip.variants.d.ts.map +1 -0
  35. package/dist/src/index.d.ts +16 -1
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/package.json +8 -3
  38. package/src/components/Accordion/Accordion.test.tsx +95 -0
  39. package/src/components/Accordion/Accordion.tsx +97 -0
  40. package/src/components/Accordion/accordion.types.ts +66 -0
  41. package/src/components/Accordion/accordion.variants.ts +30 -0
  42. package/src/components/Dialog/Dialog.test.tsx +96 -0
  43. package/src/components/Dialog/Dialog.tsx +93 -0
  44. package/src/components/Dialog/dialog.types.ts +62 -0
  45. package/src/components/Dialog/dialog.variants.ts +62 -0
  46. package/src/components/Popover/Popover.test.tsx +72 -0
  47. package/src/components/Popover/Popover.tsx +55 -0
  48. package/src/components/Popover/popover.types.ts +42 -0
  49. package/src/components/Popover/popover.variants.ts +18 -0
  50. package/src/components/Tabs/Tabs.test.tsx +74 -0
  51. package/src/components/Tabs/Tabs.tsx +66 -0
  52. package/src/components/Tabs/tabs.types.ts +49 -0
  53. package/src/components/Tabs/tabs.variants.ts +58 -0
  54. package/src/components/Tooltip/Tooltip.test.tsx +73 -0
  55. package/src/components/Tooltip/Tooltip.tsx +70 -0
  56. package/src/components/Tooltip/tooltip.types.ts +44 -0
  57. package/src/components/Tooltip/tooltip.variants.ts +18 -0
  58. package/src/index.ts +40 -1
package/CHANGELOG.md CHANGED
@@ -12,6 +12,129 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
12
  > duplicated intentionally — no shared "lowest common denominator" headless
13
13
  > layer.
14
14
 
15
+ ## [0.4.0-beta] — 2026-05-19
16
+
17
+ **Sprint 3 release.** Five new Tier-4 overlay & disclosure
18
+ components (Dialog, Tabs, Tooltip, Popover, Accordion) + the
19
+ internal **MUI ↔ TW parity audit** (`PARITY.md`) + the
20
+ **customization escape hatch playbook** in the docs lab + the
21
+ first **performance baseline** (`PERFORMANCE.md`). Strictly
22
+ additive — zero breaking changes on the existing 24-component
23
+ surface.
24
+
25
+ **Minor bump** for the 5 new public exports + new types. All
26
+ existing usages keep working byte-identical. Drop-in upgrade from
27
+ `0.3.0-beta`.
28
+
29
+ ### Added
30
+
31
+ - **`<Dialog>`** — declarative modal dialog (Radix `Dialog`-backed).
32
+ Three size variants (`sm` / `md` / `lg`), controlled `open` /
33
+ `onOpenChange`, optional title + description (wired to
34
+ `aria-labelledby` / `aria-describedby`), `showCloseButton` toggle,
35
+ `disableBackdropClose` / `disableEscapeClose` escapes. APG dialog
36
+ pattern out of the box: focus trap, restore focus on close, Esc
37
+ dismissal, scroll lock. Portal-rendered.
38
+ - **`<Tabs>`** — declarative tab navigation (Radix `Tabs`-backed).
39
+ Two variants (`underline` default / `pill`), two orientations
40
+ (`horizontal` default / `vertical`), controlled / uncontrolled
41
+ modes. APG tabs pattern: arrow-key navigation,
42
+ `role="tablist"` / `role="tab"` / `role="tabpanel"` wiring,
43
+ `aria-orientation`.
44
+ - **`<Tooltip>`** — hover/focus tooltip (Radix `Tooltip`-backed).
45
+ Per-component provider for delay configuration (default 200ms),
46
+ four placement sides, alignment options, optional arrow
47
+ indicator. APG tooltip pattern: `role="tooltip"`,
48
+ `aria-describedby` wired automatically.
49
+ - **`<Popover>`** — clickable floating panel (Radix
50
+ `Popover`-backed). For richer floating UI than tooltip — action
51
+ menus, color pickers, settings panels. Focus trap inside,
52
+ outside-click + Escape dismiss, portal-rendered.
53
+ - **`<Accordion>`** — collapsible section list (Radix
54
+ `Accordion`-backed). Two modes (`single` default with
55
+ `collapsible: true` / `multiple`), per-item disabled flag,
56
+ CSS-only chevron flip via `data-state=open` selector. APG
57
+ accordion pattern: arrow-key navigation between triggers,
58
+ `aria-expanded` on triggers, `role="region"` on panels.
59
+ - **`PARITY.md`** — internal parity audit between `@dashforge/tw`
60
+ and `@dashforge/ui` (MUI line). Covers the 10 bridge-integrated
61
+ components. Documents intentional deltas (Radix callback
62
+ signatures `onCheckedChange` / `onValueChange` vs MUI
63
+ `onChange(event, value)`; variant taxonomy `solid` / `outline` /
64
+ `soft` / `ghost` vs MUI's `contained` / `outlined` / `text`;
65
+ TW-only features like `loadOptions` async loader on Autocomplete,
66
+ `slotProps.prefix` / `suffix` on TextField, `showStepper` on
67
+ NumberField). Motivation: internal consistency + foundation for
68
+ Sprint 5 starter kits. Not a customer migration document — the
69
+ switch story was scrapped as a non-existent use case.
70
+ - **`/docs/guides/customization.mdx`** — customization escape
71
+ hatch playbook. Three sections: `sx` vs `slotProps` decision
72
+ tree with 4 canonical examples (outer wrapper styling, slot
73
+ styling, conflict resolution, combining both), preset extension
74
+ recipes (`extendPreset({ colors: { brand: { … } } })` + custom
75
+ intent augmentation + custom font stack), and a custom-component
76
+ tutorial (`PhoneInput` built on `useDashFieldMeta` +
77
+ `useAccessState`).
78
+ - **`PERFORMANCE.md`** — first formal performance baseline. Bundle
79
+ raw + gzipped (312 KB / 68.85 KB for 29 components),
80
+ per-component source size proxy, representative bundle subsets
81
+ (form / layout / foundation / tier-4 / full), render perf table
82
+ (12.1 ms mount / 7-8.6 ms update from `dash` consumer). Sets
83
+ the regression budget policy: any PR with >+5% gzipped delta
84
+ requires a justification line in the CHANGELOG; >+10% requires
85
+ explicit reviewer sign-off.
86
+
87
+ ### Internal
88
+
89
+ - **MUI ↔ TW parity audit pact.** Every release that touches a
90
+ bridge-integrated component on either line MUST re-run the
91
+ parity audit (low cost — ~30 min per component-level diff). The
92
+ pact is documented at the end of `PARITY.md`.
93
+ - **Performance regression budget.** 5% / 10% gzipped thresholds
94
+ documented in `PERFORMANCE.md`. To be enforced via CI in Sprint
95
+ 4+ (out of scope for Sprint 3 — the policy is the foundation).
96
+ - **Sidebar entry** for the new `Customization` guide added to
97
+ `dashforge-docs-lab/src/tw-docs/sidebar.model.ts`.
98
+ - **42 new unit tests** for the 5 Tier-4 components — full TW suite
99
+ now at 634/634 passing (37 files).
100
+
101
+ ### Compatibility
102
+
103
+ | Axis | Pre-`0.4.0` | Post-`0.4.0` |
104
+ |---|---|---|
105
+ | Public API surface | 24 components + foundation + bridge hooks | **+5 components + their `*Props` / `*SlotProps` types + their `*Variants` recipes** |
106
+ | Peer deps | `react ^18 \|\| ^19`, `tw-theme workspace`, `tw-tokens workspace` | unchanged |
107
+ | Bridge deps | `forms` / `rbac` / `ui-core` `workspace:*` | unchanged |
108
+ | New runtime deps | — | `@radix-ui/react-dialog ^1.1.0` · `@radix-ui/react-tabs ^1.1.0` · `@radix-ui/react-tooltip ^1.1.0` · `@radix-ui/react-popover ^1.1.0` · `@radix-ui/react-accordion ^1.2.0` |
109
+ | Breaking changes | — | **Zero**. The `sx` + `slotProps` design discussion concluded with "keep both, document only" — no rename. |
110
+ | Bundle size | 272 KB raw / ~60 KB gzipped | **312 KB raw / 68.85 KB gzipped** (+40 KB raw / +8.85 KB gz; within the projected 14% budget for 5 Radix-backed components) |
111
+ | Migration | — | Drop-in. Zero code changes required on existing usages. |
112
+
113
+ ### Migration
114
+
115
+ No code changes required:
116
+
117
+ ```bash
118
+ pnpm up @dashforge/tw@^0.4.0-beta
119
+ ```
120
+
121
+ To adopt the new Tier-4 components:
122
+
123
+ ```tsx
124
+ import { Dialog, Tabs, Tooltip, Popover, Accordion } from '@dashforge/tw';
125
+
126
+ <Tooltip content="Delete this item">
127
+ <Button variant="ghost"><TrashIcon /></Button>
128
+ </Tooltip>
129
+
130
+ <Tabs items={[
131
+ { value: 'overview', label: 'Overview', content: <OverviewPanel /> },
132
+ { value: 'details', label: 'Details', content: <DetailsPanel /> },
133
+ ]} />
134
+
135
+ <Accordion items={faqItems} type="single" defaultValue="q-1" />
136
+ ```
137
+
15
138
  ## [0.3.0-beta] — 2026-05-18
16
139
 
17
140
  **Sprint 2 release.** Bundle of 9 fixes across 7 components + 1 new
package/PARITY.md ADDED
@@ -0,0 +1,483 @@
1
+ # `@dashforge/tw` ↔ `@dashforge/ui` parity audit
2
+
3
+ > Sprint 3 P1 deliverable (2026-05-19). Per-component diff between the
4
+ > two Dashforge UI lines for the **10 bridge-integrated components**.
5
+ >
6
+ > **Motivation (revised post-Sprint 2).** This audit does NOT exist to
7
+ > support customer migration MUI→TW (no consumer switches across UI
8
+ > ecosystems in real life). It exists because:
9
+ >
10
+ > 1. **Internal consistency** — a refactor on `@dashforge/ui` that
11
+ > doesn't land on `@dashforge/tw` would otherwise be invisible.
12
+ > 2. **Bridge contract validation** — proves that the same
13
+ > `@dashforge/forms` schema works identically on both lines.
14
+ > 3. **Foundation for Sprint 5 starter kits** — two parallel kits
15
+ > (one MUI, one TW) require call-site-level parity to stay
16
+ > parallel-maintainable.
17
+
18
+ ## Versions audited
19
+
20
+ | Lib | Version | Date |
21
+ |---|---|---|
22
+ | `@dashforge/tw` | `0.3.0-beta` | 2026-05-18 |
23
+ | `@dashforge/ui` | `0.2.3-beta` | 2026-05-15 |
24
+
25
+ ## Scoring legend
26
+
27
+ | Symbol | Meaning |
28
+ |---|---|
29
+ | ✓ | Parity verified — the two lines accept the same prop / behave the same way at the call-site level. |
30
+ | ⚠ | Intentional delta — different prop or slightly different behavior, motivated and documented in the per-component section below. |
31
+ | ✗ | Unintended drift — bug or oversight, files a follow-up issue. |
32
+
33
+ ## Bridge contract — universal across all 10 components
34
+
35
+ Both libs share an identical bridge contract. The following props
36
+ behave **byte-identical** in both lines (verified by reading the
37
+ types files of all 10 components):
38
+
39
+ | Prop | Type | Semantics |
40
+ |---|---|---|
41
+ | `name` | `string` (required) | Field name registered with the `DashFormProvider` bridge. |
42
+ | `rules` | `unknown` | Validation rules forwarded to the bridge engine — opaque to the component. |
43
+ | `visibleWhen` | `(engine: Engine) => boolean` | Engine predicate. Component returns `null` when false. |
44
+ | `access` | `AccessRequirement` | RBAC requirement. Behaviors (`hide` / `disable` / `readonly`) identical in both libs. |
45
+ | `helperText` | `ReactNode` | Helper text slot below the control. Auto-replaced with bridge error message when (a) error is true and (b) the Form Closure v1 gate passes (`touched \|\| submitCount > 0`). |
46
+ | `error` | `boolean` | Explicit error semaphore override. Cuts ahead of the bridge's auto-error. |
47
+ | `label` | `ReactNode` | Visible label. |
48
+
49
+ The bridge integration semantics (Form Closure v1 error gating, prop
50
+ precedence, granular per-field subscriptions, StrictMode-safe
51
+ unregister on unmount) are implemented identically across both lines
52
+ via the shared `@dashforge/ui-core` + `@dashforge/forms` packages.
53
+
54
+ This is the **portability layer**: any consumer who swaps
55
+ `@dashforge/ui` for `@dashforge/tw` (or vice versa) keeps their
56
+ form schemas, validation rules, and RBAC declarations unchanged.
57
+
58
+ ## Per-component parity table
59
+
60
+ | # | Component | Signature | Behavior | Variant axis | Bridge | Overall |
61
+ |---|---|---|---|---|---|---|
62
+ | 1 | Button | ✓ | ✓ | ⚠ | ✓ | ⚠ |
63
+ | 2 | TextField | ✓ | ✓ | ⚠ | ✓ | ⚠ |
64
+ | 3 | Checkbox | ⚠ | ✓ | ✓ | ✓ | ⚠ |
65
+ | 4 | Switch | ⚠ | ✓ | ✓ | ✓ | ⚠ |
66
+ | 5 | RadioGroup | ⚠ | ✓ | ⚠ | ✓ | ⚠ |
67
+ | 6 | Textarea | ⚠ | ✓ | ⚠ | ✓ | ⚠ |
68
+ | 7 | NumberField | ✓ | ✓ | ⚠ | ✓ | ⚠ |
69
+ | 8 | OTPField | ✓ | ✓ | ⚠ | ✓ | ⚠ |
70
+ | 9 | Autocomplete | ⚠ | ⚠ | ⚠ | ✓ | ⚠ |
71
+ | 10 | DateTimePicker | ✓ | ✓ | ✓ | ✓ | ✓ |
72
+
73
+ **No `✗` rows** — no unintended drift was discovered. All deltas
74
+ are intentional and motivated below. The single `✓` overall
75
+ (DateTimePicker) is the only component where the two lines are
76
+ truly byte-identical at the prop level.
77
+
78
+ ---
79
+
80
+ ## 1. Button
81
+
82
+ ### Signature
83
+
84
+ | Prop | `@dashforge/ui` (MUI) | `@dashforge/tw` |
85
+ |---|---|---|
86
+ | Inherits | `Omit<MuiButtonProps, 'disabled'>` | `Omit<ButtonHTMLAttributes, 'className' \| 'color'>` + `Pick<TVariants, 'variant' \| 'color' \| 'size' \| 'fullWidth' \| 'loading'>` |
87
+ | `access` | ✓ | ✓ |
88
+ | `disabled` | ✓ | ✓ |
89
+ | `asChild` | ✗ | ✓ (Radix Slot polymorphism) |
90
+ | `sx` | ✗ (MUI sx-object inherited via MuiButtonProps) | ✓ (utility-string) |
91
+ | `loading` | ✗ (consumer adds it manually) | ✓ (built-in variant axis) |
92
+
93
+ ### Variant axis ⚠
94
+
95
+ | Axis | MUI | TW |
96
+ |---|---|---|
97
+ | `variant` | `contained` \| `outlined` \| `text` | `solid` \| `outline` \| `soft` \| `ghost` |
98
+ | `size` | `small` \| `medium` \| `large` | `sm` \| `md` \| `lg` |
99
+ | `color` | `primary` \| `secondary` \| `error` \| `info` \| `success` \| `warning` | same 6 intents |
100
+ | `fullWidth` | ✓ | ✓ |
101
+
102
+ **Intentional delta.** TW renames `contained → solid`, adds `soft`
103
+ + `ghost` variants, renames sizes to short forms. The MUI naming is
104
+ a vestige of Material Design vocabulary; TW adopts the
105
+ shadcn/Radix/Mantine common-denominator naming because that's what
106
+ TW-native developers expect.
107
+
108
+ ### Notes
109
+
110
+ - `loading` is a built-in TW variant (renders spinner + sets
111
+ `aria-busy`). On MUI side it's a consumer-level pattern.
112
+ - TW's `asChild` (Radix Slot) is more powerful than MUI's
113
+ `component`/`href` polymorphism — single API, no DOM doubling.
114
+
115
+ ---
116
+
117
+ ## 2. TextField
118
+
119
+ ### Signature
120
+
121
+ | Prop | MUI | TW |
122
+ |---|---|---|
123
+ | Inherits | `Omit<MuiTextFieldProps, 'name' \| 'SelectProps' \| 'InputProps' \| 'InputLabelProps' \| 'FormHelperTextProps' \| 'inputProps'>` | `Omit<InputHTMLAttributes, 'size' \| 'className'>` + `Pick<TVariants, 'size' \| 'layout' \| 'fullWidth'>` |
124
+ | `layout` | ✓ (`floating` \| `stacked` \| `inline`) | ✓ (same enum) |
125
+ | `required` | ✓ (renders asterisk) | ✓ (same) |
126
+ | `slotProps.prefix/suffix` | ✗ (use `slotProps.input.startAdornment` / `endAdornment` — MUI v9 API) | ✓ (new in `0.3.0-beta` — inline adornments) |
127
+ | `sx` | MUI sx-object | TW utility-string |
128
+
129
+ ### Variant axis ⚠
130
+
131
+ | Axis | MUI | TW |
132
+ |---|---|---|
133
+ | `variant` | `outlined` \| `filled` \| `standard` | not exposed (defaults to `outlined`-equivalent) |
134
+ | `size` | `small` \| `medium` | `sm` \| `md` \| `lg` |
135
+ | `layout` | `floating` \| `stacked` \| `inline` | same |
136
+
137
+ **Intentional delta.** TW intentionally drops MUI's
138
+ `variant="standard"` (underline-only field) — it's a Material Design
139
+ artifact that doesn't fit a typical Tailwind design system. TW also
140
+ drops MUI's `variant="filled"` for now (no design system demand).
141
+
142
+ ### Notes
143
+
144
+ - TW exposes `slotProps.prefix` / `slotProps.suffix` (added in
145
+ `0.3.0-beta`) for inline adornments. MUI achieves the same via
146
+ `slotProps.input.startAdornment` / `endAdornment` — different shape,
147
+ same outcome.
148
+ - MUI's deprecated `inputProps` / `InputProps` / `InputLabelProps` /
149
+ `FormHelperTextProps` are explicitly Omit-ed from TW (cleanly,
150
+ since TW never inherited them in the first place).
151
+
152
+ ---
153
+
154
+ ## 3. Checkbox
155
+
156
+ ### Signature ⚠
157
+
158
+ | Prop | MUI | TW |
159
+ |---|---|---|
160
+ | Inherits | `Omit<MuiCheckboxProps, 'name'>` (inherits MUI's `onChange: (event, checked) => void`) | `Pick<TVariants, 'size'>` (no HTML attrs inherited) |
161
+ | `onChange` | ✓ MUI HTML signature: `(event, checked) => void` | ✗ (not exposed) |
162
+ | `onCheckedChange` | ✗ | ✓ Radix signature: `(checked: boolean) => void` |
163
+ | `checked` / `defaultChecked` | ✓ | ✓ |
164
+ | `slotProps.indicator` | ✗ | ✓ |
165
+
166
+ **Intentional delta.** Callback signature differs: MUI uses the HTML
167
+ `onChange(event, checked)`, TW uses Radix's `onCheckedChange(checked)`.
168
+ A consumer rewriting `<Checkbox onChange={…}>` → `<Checkbox onCheckedChange={…}>`
169
+ is the only call-site change. Documented in `customization.mdx`
170
+ (Sprint 3 P2).
171
+
172
+ ### Variant axis ✓
173
+
174
+ Both libs expose `size` only (`small`/`medium`/`large` MUI ↔
175
+ `sm`/`md`/`lg` TW — same naming convention as Button).
176
+
177
+ ### Notes
178
+
179
+ - TW's Indicator renders BOTH `<CheckIcon />` and `<DashIcon />` and
180
+ toggles via Radix `data-state` (added in `0.3.0-beta`).
181
+ Indeterminate state is handled CSS-only.
182
+ - Bridge contract identical: `useDashFieldMeta(name)` granular
183
+ subscription, Form Closure v1 gating.
184
+
185
+ ---
186
+
187
+ ## 4. Switch
188
+
189
+ ### Signature ⚠
190
+
191
+ Same delta as Checkbox: TW exposes `onCheckedChange(checked)` (Radix),
192
+ MUI exposes `onChange(event, checked)` (HTML). All other props
193
+ identical (name, label, rules, visibleWhen, helperText, error, access,
194
+ checked, defaultChecked, disabled).
195
+
196
+ ### Variant axis ✓
197
+
198
+ Both libs: `size` only.
199
+
200
+ ### Notes
201
+
202
+ - TW gates the thumb-slide animation on `prefers-reduced-motion`
203
+ (added Sprint 2 0.3.0-beta).
204
+
205
+ ---
206
+
207
+ ## 5. RadioGroup
208
+
209
+ ### Signature ⚠
210
+
211
+ | Prop | MUI | TW |
212
+ |---|---|---|
213
+ | Inherits | `Omit<MuiRadioGroupProps, 'name'>` | `RadioGroupVariants` (Pick-style) |
214
+ | `onChange` | ✓ MUI signature | ✗ |
215
+ | `onValueChange` | ✗ | ✓ Radix signature: `(value: string) => void` |
216
+ | `options` | ✓ (`RadioGroupOption[]` — identical shape) | ✓ |
217
+ | `RadioGroupOption.access` | ✓ option-level RBAC | ✓ option-level RBAC |
218
+
219
+ ### Variant axis ⚠
220
+
221
+ | Axis | MUI | TW |
222
+ |---|---|---|
223
+ | `orientation` | inherited from MUI's `row` boolean | dedicated `orientation: 'horizontal' \| 'vertical'` |
224
+ | `size` | inherited (`small`/`medium`) | `sm` \| `md` \| `lg` |
225
+
226
+ **Intentional delta.** TW exposes `orientation` as a typed variant
227
+ axis (vs MUI's legacy `row={true}` boolean). MUI's `row` prop is a
228
+ Material legacy; TW's `orientation` is the Radix/Aria standard.
229
+
230
+ ### Notes
231
+
232
+ - Option-level RBAC is implemented identically in both libs
233
+ (`hide` keeps selected option visible-but-disabled to expose the
234
+ user's previous choice).
235
+ - Group-level access has precedence over option-level access in
236
+ both libs.
237
+
238
+ ---
239
+
240
+ ## 6. Textarea
241
+
242
+ ### Signature ⚠
243
+
244
+ | Prop | MUI | TW |
245
+ |---|---|---|
246
+ | Inherits | `Omit<MuiTextFieldProps, 'name'>` (with internal `multiline={true}` hard-coded) | `Omit<TextareaHTMLAttributes, 'name' \| 'size' \| 'onChange' \| 'onBlur' \| 'value' \| 'defaultValue'>` + `TextareaVariants` |
247
+ | `rows` / `minRows` | ✓ (defaults `minRows={3}`) | ✓ (`rows` defaults to 3) |
248
+ | `resize` | ✗ (CSS native, no prop) | ✓ variant axis (`none` \| `vertical` \| `horizontal` \| `both`) |
249
+
250
+ ### Variant axis ⚠
251
+
252
+ TW adds the `resize` variant axis as a typed prop. MUI relies on the
253
+ consumer adding inline `style={{ resize: 'vertical' }}` or CSS.
254
+
255
+ ### Notes
256
+
257
+ - Form bridge contract identical.
258
+ - TW exposes own `onChange` / `onBlur` typed for `HTMLTextAreaElement`;
259
+ MUI inherits from MUI's `TextFieldProps` typing for the
260
+ multiline-disguised-as-input case.
261
+
262
+ ---
263
+
264
+ ## 7. NumberField
265
+
266
+ ### Signature
267
+
268
+ | Prop | MUI | TW |
269
+ |---|---|---|
270
+ | Inherits | `Omit<MuiTextFieldProps, 'name' \| 'type' \| 'value' \| 'onChange'>` | `Omit<InputHTMLAttributes, 'name' \| 'type' \| 'size' \| ...>` + `NumberFieldVariants` |
271
+ | `value` | `number \| string \| null` | `number \| string \| null` (identical) |
272
+ | `min` / `max` / `step` | inherited HTML attrs | typed at top level |
273
+ | `showStepper` | ✗ | ✓ (TW-only: inline +/− buttons) |
274
+ | `slotProps.stepper` / `slotProps.stepperButton` | ✗ | ✓ |
275
+
276
+ ### Variant axis ⚠
277
+
278
+ TW exposes the stepper as a typed feature (`showStepper={true}`).
279
+ MUI's NumberField is a plain `type="number"` input with no stepper UI.
280
+
281
+ ### Behavior ✓
282
+
283
+ **Value contract is byte-identical** between the two lines:
284
+
285
+ - Bridge storage type: `number \| null` (NEVER `NaN`)
286
+ - UI display: `number → String(number)`, `null/undefined → ""`
287
+ - Empty string → `setValue(name, null)`
288
+ - Parseable number → `setValue(name, parsed)`
289
+ - Non-numeric input → no bridge write (UI keeps string for UX,
290
+ bridge stays at last valid value)
291
+ - Parsing uses `Number(...)` + `Number.isFinite(...)`. No locale parsing.
292
+
293
+ This contract is explicitly documented in both lines' types files —
294
+ the audit confirms they match word-for-word.
295
+
296
+ ---
297
+
298
+ ## 8. OTPField
299
+
300
+ ### Signature
301
+
302
+ | Prop | MUI | TW |
303
+ |---|---|---|
304
+ | `length` | ✓ (default 6) | ✓ (default 6) |
305
+ | `mode` | `numeric` \| `alphanumeric` \| `alpha` | `numeric` \| `alphanumeric` |
306
+ | `onComplete` | ✓ | ✓ |
307
+ | `onChange` | `(value: string) => void` | `(value: string) => void` (identical) |
308
+ | `autoFocus` | ✓ (default `true`) | not in types (defaults via native `<input autoFocus>`) |
309
+
310
+ ### Variant axis ⚠
311
+
312
+ | Axis | MUI | TW |
313
+ |---|---|---|
314
+ | Slot rendering | custom React per-slot | custom React per-slot + Tailwind variant (`size`, `tone`) |
315
+
316
+ **Intentional delta.** TW drops MUI's `alpha`-only mode (no design
317
+ system demand; consumers needing pure-alpha can use `alphanumeric`
318
+ + client-side filter). TW also exposes per-slot styling via typed
319
+ `slotProps.slot` / `slotProps.slotChar`.
320
+
321
+ ### Behavior ✓
322
+
323
+ Both libs:
324
+ - Sanitize input per `mode` on every keystroke
325
+ - Sanitize paste content and fill slots sequentially
326
+ - Emit `onChange` with the joined sanitized value (≤ `length`)
327
+ - Fire `onComplete` when all slots filled
328
+
329
+ ---
330
+
331
+ ## 9. Autocomplete
332
+
333
+ ### Signature ⚠
334
+
335
+ | Prop | MUI | TW |
336
+ |---|---|---|
337
+ | `multiple` (multi-select) | ✗ (MUI v9 inherits but Dashforge wrapper doesn't expose) | ✓ |
338
+ | `freeSolo` | ✗ (MUI inherits but wrapper hides it) | ✓ |
339
+ | `loadOptions` (async loader) | ✗ | ✓ |
340
+ | `loadDebounceMs` | ✗ | ✓ (default 250ms) |
341
+ | `optionsFromFieldData` | ✓ (MUI-only feature for runtime options from form runtime) | ✗ (TW doesn't have a runtime-data integration yet — F6+ work) |
342
+ | `getOptionValue` / `getOptionLabel` / `getOptionDisabled` | ✓ | ✓ |
343
+ | `getOptionKey` | ✗ | ✓ |
344
+ | `onChange` | `(value: TValue \| null) => void` | `onValueChange: (AutocompleteValue) => void` (AutocompleteValue = `string \| string[] \| null`) |
345
+
346
+ ### Variant axis ⚠
347
+
348
+ | Axis | MUI | TW |
349
+ |---|---|---|
350
+ | Mode | single-select only | single + multi + free-solo |
351
+ | Async | not in wrapper (use MUI's directly if needed) | first-class `loadOptions` prop |
352
+
353
+ **Intentional delta — TW is feature-ahead.** TW's Autocomplete is
354
+ the most-extended bridge component:
355
+
356
+ - **Multi-select** (chips inside input, backspace to remove last)
357
+ - **Free-solo** (Enter commits typed text as value)
358
+ - **Async loadOptions** (debounced, replaces static `options` while
359
+ the user types)
360
+
361
+ MUI's Dashforge wrapper trails behind because: (a) MUI's underlying
362
+ Autocomplete already supports these via inherited props, so wrapping
363
+ them feels redundant; (b) the MUI consumer can drop down to
364
+ `MuiAutocomplete` directly when they need these features. TW had to
365
+ implement them from scratch (built on React Aria's `useComboBox`),
366
+ so it was natural to expose them as first-class props.
367
+
368
+ **Reverse delta — MUI is ahead on**: `optionsFromFieldData` (runtime
369
+ field data integration for dynamic option lists fed by the form
370
+ engine). This is a F6+ feature on the TW roadmap.
371
+
372
+ ### Behavior ⚠
373
+
374
+ The core bridge contract is parity:
375
+
376
+ - Storage policy: option pick → mapped `option.value`; clear → `null`
377
+ - Form Closure v1 error gating
378
+ - StrictMode-safe unregister on unmount
379
+
380
+ But the **storage type** differs:
381
+ - MUI: `TValue \| null` where TValue is `string | number`
382
+ - TW: `string \| string[] \| null` (`string[]` for multi-select)
383
+
384
+ This is a real call-site delta. A consumer migrating MUI → TW with
385
+ a NumberField-style numeric `TValue` would need to switch to string
386
+ storage + cast on submission.
387
+
388
+ ---
389
+
390
+ ## 10. DateTimePicker
391
+
392
+ ### Signature ✓
393
+
394
+ All props parity:
395
+
396
+ - `mode: 'date' \| 'time' \| 'datetime'` — identical
397
+ - `value: string \| null` — identical (ISO 8601-ish naive string)
398
+ - `min` / `max` / `step` — identical
399
+ - `name` / `rules` / `visibleWhen` / `access` — identical (bridge)
400
+ - `label` / `helperText` / `error` / `required` / `disabled` — identical
401
+
402
+ ### Variant axis ✓
403
+
404
+ Layout: both libs allow `stacked` / `inline` and downgrade `floating`
405
+ to `stacked` with a dev warning (rationale: native input always
406
+ shows a placeholder mask that overlaps the floating label).
407
+
408
+ ### Behavior ✓
409
+
410
+ Both libs:
411
+ - Use native `<input type="date|time|datetime-local">`
412
+ - Bridge contract identical
413
+ - ISO string contract identical (`datetime-local` is naive — no TZ;
414
+ if you need TZ-aware persistence, both libs say to convert
415
+ upstream)
416
+
417
+ **This is the cleanest parity case in the audit** — the only ✓✓✓
418
+ across all four columns. Likely because both lines deliberately
419
+ chose native HTML5 inputs over a custom calendar widget, leaving
420
+ very little surface to diverge on.
421
+
422
+ ### Notes
423
+
424
+ - MUI exposes `@deprecated inputProps` / `InputLabelProps` shims for
425
+ MUI v7 backward compatibility. TW never had these (no MUI lineage).
426
+
427
+ ---
428
+
429
+ ## Summary of intentional deltas
430
+
431
+ 1. **Callback signatures**: TW prefers Radix-style `onCheckedChange` /
432
+ `onValueChange` (single-arg). MUI inherits HTML `onChange(event, value)`.
433
+ → **Documented in `customization.mdx` decision tree.**
434
+ 2. **Variant taxonomy**: TW uses shadcn/Radix common-denominator
435
+ vocabulary (`solid`/`outline`/`soft`/`ghost`, `sm`/`md`/`lg`).
436
+ MUI uses Material Design vocabulary (`contained`/`outlined`/`text`,
437
+ `small`/`medium`/`large`). 1:1 mappable.
438
+ 3. **`sx` shape**: TW = utility string, MUI = sx-object. Same name,
439
+ different semantics — by design (see `customization.mdx` §1).
440
+ 4. **TW-only props**: `slotProps`, `asChild` (Button), `loading`
441
+ (Button), `slotProps.prefix/suffix` (TextField), `showStepper`
442
+ (NumberField), `multiple` / `freeSolo` / `loadOptions`
443
+ (Autocomplete), `resize` variant (Textarea).
444
+ 5. **MUI-only props**: `optionsFromFieldData` (Autocomplete runtime
445
+ data integration).
446
+
447
+ ## Components NOT covered by this audit
448
+
449
+ The MUI line has these additional components that have no TW
450
+ counterpart yet (NOT in scope for this audit — they're not part of
451
+ the bridge-integrated tier):
452
+
453
+ - `Select` (MUI-only) — TW uses Autocomplete for select-like UX
454
+ - `Animate` (MUI-only utility)
455
+
456
+ The TW line has these additional components with no MUI counterpart
457
+ (Sprint 1-2 Foundation work, not in scope here):
458
+
459
+ - `Typography`, `Box`, `Stack`, `Grid`, `Container`, `Divider`,
460
+ `AspectRatio`, `VisuallyHidden` (8 Foundation primitives)
461
+
462
+ Tier-4 components (Dialog, Tabs, Tooltip, Popover, Accordion) ship in
463
+ Sprint 3 P3 — they will be MUI-side counterparts of MUI's own
464
+ `Dialog`, `Tabs`, `Tooltip`, `Popover`, `Accordion`. Future PARITY.md
465
+ revisions will include them.
466
+
467
+ ## Follow-up items
468
+
469
+ No `✗` rows surfaced, so no critical follow-ups. The intentional
470
+ deltas are documented above. The following nice-to-haves were
471
+ identified but are explicitly deferred:
472
+
473
+ | Item | Notes | Sprint |
474
+ |---|---|---|
475
+ | Optional `onChange` (HTML-style) on Checkbox / Switch / RadioGroup | For MUI-migrating consumers who want byte-identical call-sites. Could be added as an additional optional callback alongside `onCheckedChange`. | Not planned. Intentional choice; documented in customization.mdx instead. |
476
+ | `optionsFromFieldData` on TW Autocomplete | Runtime field data integration. Requires F6 infrastructure (form runtime data hooks not yet ported to TW). | Sprint 6 (post-1.0 candidate). |
477
+ | MUI-side `loadOptions` / `freeSolo` / `multiple` exposure | MUI consumer can use `MuiAutocomplete` directly for these. Not adding to the wrapper unless concrete demand. | Not planned. |
478
+ | `variant="standard"` / `"filled"` on TW TextField | No design system demand. | Not planned. |
479
+
480
+ Maintenance pact: every release that touches a bridge-integrated
481
+ component on either line MUST re-run this audit. The cost is low
482
+ (~30 min for a single-component diff) and the consistency value is
483
+ high.