@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.
- package/CHANGELOG.md +123 -0
- package/PARITY.md +483 -0
- package/PERFORMANCE.md +169 -0
- package/dist/index.esm.js +493 -2
- package/dist/src/components/Accordion/Accordion.d.ts +21 -0
- package/dist/src/components/Accordion/Accordion.d.ts.map +1 -0
- package/dist/src/components/Accordion/accordion.types.d.ts +74 -0
- package/dist/src/components/Accordion/accordion.types.d.ts.map +1 -0
- package/dist/src/components/Accordion/accordion.variants.d.ts +58 -0
- package/dist/src/components/Accordion/accordion.variants.d.ts.map +1 -0
- package/dist/src/components/Dialog/Dialog.d.ts +17 -0
- package/dist/src/components/Dialog/Dialog.d.ts.map +1 -0
- package/dist/src/components/Dialog/dialog.types.d.ts +75 -0
- package/dist/src/components/Dialog/dialog.types.d.ts.map +1 -0
- package/dist/src/components/Dialog/dialog.variants.d.ts +80 -0
- package/dist/src/components/Dialog/dialog.variants.d.ts.map +1 -0
- package/dist/src/components/Popover/Popover.d.ts +15 -0
- package/dist/src/components/Popover/Popover.d.ts.map +1 -0
- package/dist/src/components/Popover/popover.types.d.ts +45 -0
- package/dist/src/components/Popover/popover.types.d.ts.map +1 -0
- package/dist/src/components/Popover/popover.variants.d.ts +34 -0
- package/dist/src/components/Popover/popover.variants.d.ts.map +1 -0
- package/dist/src/components/Tabs/Tabs.d.ts +15 -0
- package/dist/src/components/Tabs/Tabs.d.ts.map +1 -0
- package/dist/src/components/Tabs/tabs.types.d.ts +55 -0
- package/dist/src/components/Tabs/tabs.types.d.ts.map +1 -0
- package/dist/src/components/Tabs/tabs.variants.d.ts +82 -0
- package/dist/src/components/Tabs/tabs.variants.d.ts.map +1 -0
- package/dist/src/components/Tooltip/Tooltip.d.ts +29 -0
- package/dist/src/components/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/src/components/Tooltip/tooltip.types.d.ts +47 -0
- package/dist/src/components/Tooltip/tooltip.types.d.ts.map +1 -0
- package/dist/src/components/Tooltip/tooltip.variants.d.ts +34 -0
- package/dist/src/components/Tooltip/tooltip.variants.d.ts.map +1 -0
- package/dist/src/index.d.ts +16 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +8 -3
- package/src/components/Accordion/Accordion.test.tsx +95 -0
- package/src/components/Accordion/Accordion.tsx +97 -0
- package/src/components/Accordion/accordion.types.ts +66 -0
- package/src/components/Accordion/accordion.variants.ts +30 -0
- package/src/components/Dialog/Dialog.test.tsx +96 -0
- package/src/components/Dialog/Dialog.tsx +93 -0
- package/src/components/Dialog/dialog.types.ts +62 -0
- package/src/components/Dialog/dialog.variants.ts +62 -0
- package/src/components/Popover/Popover.test.tsx +72 -0
- package/src/components/Popover/Popover.tsx +55 -0
- package/src/components/Popover/popover.types.ts +42 -0
- package/src/components/Popover/popover.variants.ts +18 -0
- package/src/components/Tabs/Tabs.test.tsx +74 -0
- package/src/components/Tabs/Tabs.tsx +66 -0
- package/src/components/Tabs/tabs.types.ts +49 -0
- package/src/components/Tabs/tabs.variants.ts +58 -0
- package/src/components/Tooltip/Tooltip.test.tsx +73 -0
- package/src/components/Tooltip/Tooltip.tsx +70 -0
- package/src/components/Tooltip/tooltip.types.ts +44 -0
- package/src/components/Tooltip/tooltip.variants.ts +18 -0
- 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.
|