106 lines
10 KiB
Markdown
106 lines
10 KiB
Markdown
# Wave 3 Component Reconciliation
|
|
|
|
**Date**: 2026-05-05
|
|
**Date locked into proposal**: 2026-05-05T06:54:49+01:00
|
|
**Source**: `npx shadcn@latest search @shadcn --limit 1000` (registry version `shadcn@4.6.0`, captured 2026-05-05)
|
|
**Live shadcn `registry:ui` count**: 56 components
|
|
|
|
## Summary
|
|
|
|
The live `@shadcn` registry exposes **56** `registry:ui` items. The proposal's provisional 8-batch table listed **63** components. Reconciling the two:
|
|
|
|
- **Direct match** (53 components): every item in batches 3a, 3b (minus `Field` which existed but as separate entry), 3c, 3d (minus `StatusBadge`/`Empty` which need separate notes), 3e, 3f, 3g (minus `ConfirmDeleteDialog`/`ErrorBoundary`/`ThemeToggle` — see notes), 3h (minus `MultiSelect`/`SearchableSelect`/`MoneyInput`/`LanguagePicker`).
|
|
- **Split / renamed in upstream**: `Combobox` is a separate item in shadcn (good — confirms cross-cutting design §3.4 hint was wrong; we keep `Combobox` as a discrete component, NOT folded into `Select`). `Field` ships as `field` — confirms the form-primitives split.
|
|
- **Suaveplan-only additions** (10 components): `StatusBadge`, `Empty` (now exists upstream as `empty`), `ConfirmDeleteDialog`, `ErrorBoundary`, `ThemeToggle`, `MultiSelect`, `SearchableSelect`, `MoneyInput`, `LanguagePicker`, `FieldGroup` (shadcn ships `field` + `field-group` separately — `field-group` exists, mapped to our `FieldGroup`).
|
|
- **Upstream additions we should consider for future batches** (5 components): `button-group`, `direction`, `kbd`, `native-select`, `spinner` (already in 3d), `form` (legacy alias — `react-forms` will provide; we do NOT ship a `Form` component in `react-ui`).
|
|
|
|
## Reconciled batch table
|
|
|
|
| Component | Proposal Batch | Live shadcn | Action | Notes |
|
|
|---|---|---|---|---|
|
|
| Button | 3a | exists (`button`) | implement | direct shadcn parity |
|
|
| Input | 3a | exists (`input`) | implement | direct shadcn parity |
|
|
| Label | 3a | exists (`label`) | implement | wraps `@radix-ui/react-label` |
|
|
| Separator | 3a | exists (`separator`) | implement | wraps `@radix-ui/react-separator` |
|
|
| Skeleton | 3a | exists (`skeleton`) | implement | direct shadcn parity |
|
|
| FieldGroup | 3b | exists (`field-group`) | implement in 3b | shadcn separates `field` + `field-group` |
|
|
| Field | 3b | exists (`field`) | implement in 3b | direct match |
|
|
| FieldSet | 3b | renamed → component split | implement in 3b | shadcn folds into `field` (`Field.Set`); we keep distinct primitive |
|
|
| InputGroup | 3b | exists (`input-group`) | implement in 3b | direct match |
|
|
| Textarea | 3b | exists (`textarea`) | implement in 3b | direct match |
|
|
| Checkbox | 3b | exists (`checkbox`) | implement in 3b | direct match |
|
|
| Switch | 3b | exists (`switch`) | implement in 3b | direct match |
|
|
| RadioGroup | 3b | exists (`radio-group`) | implement in 3b | direct match |
|
|
| Toggle | 3b | exists (`toggle`) | implement in 3b | direct match |
|
|
| ToggleGroup | 3b | exists (`toggle-group`) | implement in 3b | direct match |
|
|
| Dialog | 3c | exists (`dialog`) | implement in 3c | direct match |
|
|
| Sheet | 3c | exists (`sheet`) | implement in 3c | direct match |
|
|
| Drawer | 3c | exists (`drawer`) | implement in 3c | wraps `vaul` |
|
|
| AlertDialog | 3c | exists (`alert-dialog`) | implement in 3c | direct match |
|
|
| Popover | 3c | exists (`popover`) | implement in 3c | direct match |
|
|
| HoverCard | 3c | exists (`hover-card`) | implement in 3c | direct match |
|
|
| Tooltip | 3c | exists (`tooltip`) | implement in 3c | direct match |
|
|
| ContextMenu | 3c | exists (`context-menu`) | implement in 3c | direct match |
|
|
| Table | 3d | exists (`table`) | implement in 3d | direct match |
|
|
| DataTable | 3d | NOT in @shadcn (consumer-built) | implement in 3d | suaveplan addition; shadcn docs ship as recipe, not registry item |
|
|
| Card | 3d | exists (`card`) | implement in 3d | direct match |
|
|
| Badge | 3d | exists (`badge`) | implement in 3d | direct match |
|
|
| StatusBadge | 3d | NOT in @shadcn | implement in 3d | suaveplan addition; Zod-validated state surface |
|
|
| Avatar | 3d | exists (`avatar`) | implement in 3d | direct match |
|
|
| Alert | 3d | exists (`alert`) | implement in 3d | direct match |
|
|
| Empty | 3d | exists (`empty`) | implement in 3d | direct match — proposal listed this; confirmed live |
|
|
| Progress | 3d | exists (`progress`) | implement in 3d | direct match |
|
|
| Spinner | 3d | exists (`spinner`) | implement in 3d | direct match — proposal listed this; confirmed live |
|
|
| Sidebar | 3e | exists (`sidebar`) | implement in 3e | direct match; per AGENTS.md §3 needs sub-components to stay <300 lines |
|
|
| NavigationMenu | 3e | exists (`navigation-menu`) | implement in 3e | direct match |
|
|
| Breadcrumb | 3e | exists (`breadcrumb`) | implement in 3e | direct match |
|
|
| Tabs | 3e | exists (`tabs`) | implement in 3e | direct match |
|
|
| Pagination | 3e | exists (`pagination`) | implement in 3e | direct match; chevron-swap under `useDirection() === 'rtl'` |
|
|
| Menubar | 3e | exists (`menubar`) | implement in 3e | direct match |
|
|
| DropdownMenu | 3e | exists (`dropdown-menu`) | implement in 3e | direct match |
|
|
| Command | 3e | exists (`command`) | implement in 3e | direct match; emits `command.select` span |
|
|
| Accordion | 3f | exists (`accordion`) | implement in 3f | direct match |
|
|
| Collapsible | 3f | exists (`collapsible`) | implement in 3f | direct match |
|
|
| Resizable | 3f | exists (`resizable`) | implement in 3f | wraps `react-resizable-panels` |
|
|
| ScrollArea | 3f | exists (`scroll-area`) | implement in 3f | direct match |
|
|
| AspectRatio | 3f | exists (`aspect-ratio`) | implement in 3f | direct match |
|
|
| Carousel | 3f | exists (`carousel`) | implement in 3f | wraps `embla-carousel-react` |
|
|
| Sonner | 3g | exists (`sonner`) | implement in 3g | direct match; replaces legacy `Toast`/`Toaster` |
|
|
| ConfirmDeleteDialog | 3g | NOT in @shadcn | implement in 3g | suaveplan addition; specialised dialog |
|
|
| ErrorBoundary | 3g | NOT in @shadcn | implement in 3g | suaveplan addition; declarative React 19 boundary |
|
|
| ThemeToggle | 3g | NOT in @shadcn | implement in 3g | suaveplan addition; bound `<ThemeSwitcher>` |
|
|
| Calendar | 3h | exists (`calendar`) | implement in 3h | wraps `react-day-picker` |
|
|
| DatePicker | 3h | NOT in @shadcn (recipe only) | implement in 3h | shadcn docs as `Calendar + Popover`; we ship as a discrete component |
|
|
| Select | 3h | exists (`select`) | implement in 3h | direct match |
|
|
| MultiSelect | 3h | NOT in @shadcn | implement in 3h | suaveplan addition |
|
|
| Combobox | 3h | NOT in @shadcn registry as discrete UI item; ships as recipe (`command + popover`) | implement in 3h | suaveplan ships discrete component (cross-cutting design §3.4 hint REJECTED — we keep distinct from `Select`) |
|
|
| SearchableSelect | 3h | NOT in @shadcn | implement in 3h | suaveplan addition |
|
|
| Slider | 3h | exists (`slider`) | implement in 3h | direct match |
|
|
| InputOTP | 3h | exists (`input-otp`) | implement in 3h | wraps `input-otp` |
|
|
| Chart | 3h | exists (`chart`) | implement in 3h | wraps `recharts` |
|
|
| MoneyInput | 3h | NOT in @shadcn | implement in 3h | suaveplan addition; consumes `@suaveplan/i18n` formatters |
|
|
| LanguagePicker | 3h | NOT in @shadcn | implement in 3h | suaveplan addition; reads `@suaveplan/locale-data` |
|
|
| Item | 3h | exists (`item`) | implement in 3h | direct match |
|
|
|
|
## Findings to flag for later batches
|
|
|
|
1. **`Field`/`FieldGroup`/`FieldSet` split** (Wave 3b): shadcn ships `field` and `field-group` as discrete registry items. The `FieldSet` primitive in our proposal has no upstream peer — we ship it as a suaveplan addition. The Wave 3b implementer should mirror the shadcn `field` + `field-group` API where possible, then layer `FieldSet` as an additive primitive (`<fieldset>` semantics + label aggregation).
|
|
|
|
2. **`Combobox` is NOT folded into `Select`** (Wave 3h): the cross-cutting design §3.4 hinted at a possible fold; the live registry ships `Combobox` as a recipe (`command + popover`). We ship `Combobox` as a discrete component with the same props surface so consumers do not need to compose recipes themselves. `MultiSelect` and `SearchableSelect` are suaveplan-only additions atop the same recipe.
|
|
|
|
3. **`button-group` and `kbd` exist upstream but are NOT in the proposal's 8-batch table** (post-Wave 3): both are useful primitives. The proposal's count is locked at 63 for the v1 ship; `button-group` and `kbd` are deferred to a v1.1 follow-up change. No Wave 3 work needed.
|
|
|
|
4. **`direction` and `native-select` exist upstream but are out-of-scope** (post-Wave 3): `direction` is a Radix re-export (we already use `@radix-ui/react-direction` directly via the chassis `useDirection` hook); `native-select` is shadcn's bare-`<select>` wrapper which we deliberately replace with `Select` (Radix-backed) for a11y and styling consistency. Both deferred indefinitely.
|
|
|
|
5. **`form` ships in shadcn as the `react-hook-form`-bound wrapper**: per the proposal, this is **owned by `@suaveplan/react-forms`** (sibling change `enterprise-react-forms-v2`), NOT by `react-ui`. We deliberately do NOT add `form` to any react-ui batch; the form factory's `react-hook-form` + Zod-resolvers dep weight is incompatible with consumers wanting only a Button.
|
|
|
|
6. **`Empty` and `Spinner` already exist upstream** (Wave 3d): the proposal listed both as suaveplan additions in 3d — they have since landed in `@shadcn` as discrete items. Wave 3d implementer should mirror the upstream shape rather than invent; tag the implementation notes accordingly.
|
|
|
|
7. **`StatusBadge`, `DataTable`, `MoneyInput`, `LanguagePicker`, `MultiSelect`, `SearchableSelect`, `ConfirmDeleteDialog`, `ErrorBoundary`, `ThemeToggle`** are all suaveplan-only additions. Wave 3d/3g/3h implementers should NOT search for upstream peers; they should ship these as documented chassis-native components and emit registry items into `registry/r/` so external consumers can `npx shadcn@latest add @suaveplan/<name>` against `https://ui.suaveplan.com/r/`.
|
|
|
|
## Lock-in note
|
|
|
|
The §"Component count — resolved here" table in `proposal.md` is hereby **locked at 63 components** for the v1 ship. Changes to this list require a follow-up OpenSpec change (NOT an amendment to this proposal), per the proposal's §"Component count — resolved here" gate.
|
|
|
|
The Wave 3a implementation (this commit) ships the 5 Foundation components — Button, Input, Label, Separator, Skeleton — independent of the reconciliation findings above (they are stable across every shadcn version and live in `@shadcn` as direct matches).
|