Files
HiveGrid-Wireframes/hivegrid-cloud/docs/subframe/COMPONENT-MAPPING.md
T

217 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Subframe-Migration: Komponenten-Mapping
Vertrag für die Migration der shadcn/Radix-UI (`apps/web/app/components/ui/*`) auf das
Subframe-Designsystem „Basedesign" (`apps/web/app/ui/*`, project `f023064a5ca0`).
## Grundsätze
- **Theme:** Subframe ist die alleinige Quelle (`apps/web/app/ui/theme.css`, eingebunden in
`globals.css`). Der Brand ist grayscale und deckt sich faktisch mit dem bisherigen
zinc-Primary. Die shadcn-Tokens (`--primary`, `--background` …) bleiben aktiv, bis ihre
Consumer migriert sind, und werden am Ende entfernt (samt JS-`@config` → beseitigt die
22 transitorischen CSS-Optimierungs-Warnungen).
- **Mechanismus:** Strangler-Shim. Pro Primitive wird die Implementierung in
`components/ui/<x>.tsx` durch einen Wrapper über die Subframe-Komponente ersetzt; die
bestehende API bleibt erhalten, Call-Sites ändern sich nicht. Schwer abbildbare Spezialfälle
bleiben vorerst auf dem Legacy-Pfad und werden später auf native Subframe-Komponenten
(LinkButton, IconButton …) umgestellt.
- **Verifikation pro Schritt:** `pnpm --filter @toolctrl/web typecheck` + `build` grün,
Komponenten-Tests grün, danach `pnpm verify:a11y-coverage`.
- **Generierte Dateien** in `app/ui/` gehören Subframe. Lokale Fixes nur mit
`// @subframe/sync-disable` (sonst überschreibt der nächste `pnpm subframe:sync`).
## Button ✅ migriert (Shim)
`components/ui/button.tsx` rendert für den Normalfall die Subframe-`Button`.
Prop-Mapping:
- `variant`: default→`primary`, destructive→`destructive`, outline→`tertiary`,
secondary→`secondary`, ghost→`ghost`, link→`ghost`
- `size`: default→`medium`, sm→`small`, xs→`small`, lg→`large`, icon→Legacy
- `isLoading`→`loading`; `dirty`→eigene Klasse (bleibt); `type`→Default `submit` erzwungen
(Subframe würde sonst `button` erzwingen und Formular-Buttons brechen)
Legacy-Pfad (noch shadcn, später nativ):
- `asChild` (46 Stellen) → Ziel: Subframe `LinkButton`
- `size="icon"` (16 Stellen) → Ziel: Subframe `IconButton`
- `buttonVariants`-Export (Consumer: alert-dialog, calendar, pagination, json/ShowModalButton)
## Formularfelder (`components/ui/input/*`) — der eigentliche Hebel
Die bare `input.tsx` wird kaum genutzt (9×); die Frequenz liegt in den Feld-Komponenten.
Subframes `TextInput`/`TextArea` sind `<label>`-Container (Label + Box + `*.Input`) und passen
hier — nicht auf die bare `input.tsx`. Beide haben eine `max-w`-Begrenzung, die für volle
Feldbreite mit `className="w-full max-w-none"` überschrieben werden muss.
- **InputText (129×) ✅ migriert** — volle Subframe `TextInput`/`TextArea` inkl. Label (rich
Label-Content als ReactNode), `error`, controlled+uncontrolled, type-Mapping. Editor-Branches
(monaco/wysiwyg) bleiben unverändert (in der Praxis ungenutzt). Label-Assoziation jetzt
**implizit** (Input im `<label>`) statt `for`/`id` → a11y-Test entsprechend angepasst.
Hinweis: `type="password"`/`"search"` zeigen jetzt Subframes eingebautes Lock-/Such-Icon.
- **Welle 2 ✅ migriert** (Shim → Subframe, typecheck/build/a11y grün): InputNumber→NumberInput,
InputSearch/InputSearchWithURL→TextInput(type=search), InputSearchButton→Button,
InputSelect/InputSelector→Select, InputCheckbox/Inline/WithDescription→Checkbox,
InputCheckboxCards→CheckboxCard, InputRadioGroup→RadioGroup, InputRadioGroupCards→RadioCardGroup,
InputChoicebox→RadioCardGroup, Toggle→Switch. InputResponsiveSelector erbt via InputSelector.
app/ui-Patch: NumberInput (hardcodiertes `value=""` entfernt, sync-disable).
Trade-offs: InputNumber `icon`-Prop jetzt visuell No-op; InputCheckboxWithDescription Layout-Shift.
## Welle 3 (Buttons-Folder / Banners / Atome)
- **Migriert ✅** (typecheck/build/a11y grün): `buttons/ButtonShare` + `buttons/ButtonFlyout` →
via Button-Shim (Rest des buttons/-Ordners lief schon über den Shim); `banners/*` (Error/Warning/
Success/Info) → BannerAlert (type-Mapping; `onDismiss` als Overlay erhalten, da BannerAlerts
dismiss-Button ein Mock ohne onClick ist); `progress.tsx` → Progress; `skeleton.tsx` → SkeletonText.
### ⚠️ Subframe-Komponenten, die NICHT passen (Mock oder Struktur-Mismatch) → bleiben shadcn
Rein präsentational (kein onChange/State/Navigation) oder strukturell inkompatibel — bewusst NICHT migriert:
- **Combobox / MultiSelect** — `<input value="">` hartkodiert, kein onChange → InputCombobox bleibt shadcn.
- **DatePicker** — hartkodierte Tage/Monate, Pfeile ohne onClick → InputDate/InputDatePicker bleiben shadcn (funktionaler `Calendar` wrappt react-day-picker und bleibt).
- **SearchInput** — value hartkodiert; echte Suchfelder nutzen `TextInput type=search`.
- **Tooltip** — statischer `<div>`-Mock (kein Radix-Trigger/Content/Hover) → `tooltip.tsx` bleibt shadcn.
- **Alert** — Subframe ist prop-basiert (title/description), shadcn ist Compound (Alert/AlertTitle/AlertDescription) → bleibt shadcn.
- **Avatar** — Subframe hat keine AvatarImage/AvatarFallback-Subkomponenten + keine Fallback-bei-Ladefehler-Logik → bleibt shadcn.
- **Badge** (+ badges/*) — BadgeFeedback erzwingt einen führenden Dot, ist `<div>` statt `<span>`, kein `asChild`, keine freien Farben (Badge nutzt 8+ freie Farben) → bleibt shadcn.
Damit diese migrierbar werden, müsste das Subframe-Design die Interaktionen/Struktur liefern (oder
wir behalten shadcn). Bei jedem Sync prüfen, ob upstream funktionale/passende Varianten kommen.
## Welle 4 (funktionale Compounds) — Decke erreicht
Nur **`slider.tsx` → Subframe Slider** migriert (funktional, gleicher Radix; im Repo aktuell ungenutzt;
ref nicht weitergereicht, da span vs. div). Alle anderen Compounds **gestoppt** — die Subframe-
Komponenten sind zwar teils Radix-funktional, aber **kompositorisch unvollständig**:
- **Dialog** — Subframe exportiert nur Root+Content (kein Trigger/Close/Portal/Overlay), Root hardcodiert
Portal+Overlay+Title → würde an jeder Modal-Stelle doppeltes Overlay erzeugen. Bleibt shadcn.
(Korrektur: `components/ui/dialog` wird direkt nur in 3 Dateien importiert; die Modal-Frequenz läuft
transitiv über `modals/DialogSticky`/`CommandDialog`/`ModalBlank`, ~57 Dateien.)
- **DropdownMenu** — Subframe ist nur Item-/Surface-Styling; das funktionale Radix ist DASSELBE Paket
wie bei shadcn (kein Gewinn, hohes Risiko). Bleibt shadcn.
- **Drawer / Sheet** — Subframe Drawer ist right-only, kein Trigger/Close, hardcodiertes Content-Styling;
`sheet` braucht `side`-Prop; `drawer` ist ein vaul-Drag-Drawer. Bleiben shadcn.
- **Accordion** — Subframe basiert auf Collapsible (kein Multi-Item `type`/`value`). Bleibt shadcn.
**Fazit:** Die sauber migrierbare Fläche ist mit dem aktuellen „Basedesign"-Stand weitgehend
ausgeschöpft. Compounds brauchen in Subframe **komponierbare Teile** (Trigger/Close/Portal/Content +
Sub-Items), Mocks brauchen **Funktionalität**. Weiterer Fortschritt = Subframe-Design-Arbeit, nicht Code.
## Welle 5 (Rest-Sweep „alles Migrierbare")
- **Migriert ✅** (typecheck/build/a11y grün): `switch`→Switch, `checkbox`→Checkbox, `radio-group`→RadioGroup
(alle mit controlled/uncontrolled-Shim + null→undefined-Coercion), `calendar`→Subframe Calendar
(= dasselbe react-day-picker, kein Feature-Verlust), `button-group`/`split-button`→ButtonGroup,
`loaders/Loading`+`FloatingLoader`→Loader, `steps/ProgressBar`→StepperHorizontal.
- **Gestoppt** (Struktur/Compound/Semantik-Mismatch, bleiben shadcn): `breadcrumb`(+breadcrumbs/) — Subframe-Items sind keine Links;
`empty`(+emptyState/) — Subframe-Button ist Mock ohne onClick; `stepper` — Subframe ist rein präsentational (keine
Step-State/Trigger); `toggle`/`toggle-group` — Subframe ist single-only + bricht `toggleVariants`-Export;
`links/` — URL-Filter-Toggle, kein Link-Button; `errors/ErrorPage`+`ServerError` — Full-Page-Layout vs. Inline-Karte,
Retry-Button ist Mock; `headers/HeaderFlyoutItem` — Popover-Flyout vs. Top-Bar; `stats/*` — StatsCard erzwingt
Trend-Icon + Pointer + bricht `<dl>`/sr-only-a11y; `select` — Compound (10 Exporte, kompositionale Inversion);
`table` — Subframe Table nicht komponierbar (keine Header/Body/Footer/Caption-Teile).
**Endstand der Code-Migration:** Was mit dem aktuellen Subframe-Projekt sauber ging, ist migriert. Alles Weitere
ist durch die Vollständigkeit von „Basedesign" blockiert (siehe Mock-/Compound-Listen oben).
## Direkte 1:1-Ziele (gleiche Reihenfolge wie Migrationswellen)
| shadcn | Subframe | Hinweis |
|---|---|---|
| input | TextInput | placeholder=string |
| textarea | TextArea | |
| select | Select | gepatcht (sync-disable) |
| checkbox | Checkbox | |
| radio-group | RadioGroup | |
| switch | Switch | |
| badge | BadgeFeedback / Tag_Label | kein generisches Badge |
| avatar | Avatar | + AvatarGroup |
| tooltip | Tooltip | |
| tabs | Tabs | |
| accordion | Accordion | |
| breadcrumb | Breadcrumbs | |
| progress | Progress | + ProgressCircle |
| slider | Slider | |
| skeleton | SkeletonText / SkeletonCircle | |
| table | Table | |
| dialog | Dialog | + ModalBlank/ConfirmDialog/ResultDialog |
| drawer / sheet | Drawer | |
| dropdown-menu | DropdownMenu | |
| context-menu | ContextMenu | |
| sidebar | SidebarWithSections | |
| stepper | StepperHorizontal/Vertical | |
| toggle-group / toggle | ToggleGroup | |
| input-otp | InputOtp | |
| command | Combobox / SearchInput | keine Command-Palette; gepatcht |
| multi-select | MultiSelect | + MultiSelectTags |
| calendar | Calendar | + DatePicker |
| chart | AreaChart/BarChart/LineChart/PieChart | konkrete Typen statt Wrapper |
| card | CardIcon/CardImage/StatsCard | kein generisches Card |
| empty | EmptyState | |
| alert | Alert | |
| toast/toaster/sonner | Toast | |
## Lücken — bleiben shadcn (kein Subframe-Pendant)
`aspect-ratio`, `carousel`, `collapsible`, `hover-card`, `input-group`, `label`,
`pagination`, `popover`, `resizable`, `scroll-area`, `separator`.
## Bekannte Generierungs-Bugs (gepatcht, sync-disabled)
- `Select`: undefined `checked`/`highlighted` → entfernt (Styling via data-state)
- `Combobox`, `SearchInput`: `placeholder` ReactNode → `string`
## Token-Zuordnung: shadcn → Subframe (für „Logik=Radix, Look=Subframe")
Strategie für alle noch nicht migrierten Komponenten: die bestehende Radix/shadcn-Komponente
BEHALTEN (Logik bleibt), NUR die Farb-/Stil-Klassen auf Subframe-Tokens umstellen. Keine
Struktur-/Import-/Export-Änderung — nur className. Sync-sicher (alles in components/ui; die
Subframe-Tokens kommen global aus app/ui/theme.css). Subframe-`app/ui`-Mock-Komponenten werden
für diese NICHT verwendet.
| shadcn-Token | Subframe-Token |
|---|---|
| `bg-background` | `bg-surface-neutral-primary` |
| `bg-card`, `bg-popover` | `bg-surface-neutral-primary` (Overlays/Modal: `bg-surface-neutral-modal`) |
| `text-foreground`, `text-card-foreground` | `text-text-neutral-primary` |
| `text-muted-foreground` | `text-text-neutral-tertiary` |
| `placeholder:text-muted-foreground` | `placeholder:text-text-neutral-subtle` |
| `bg-primary` (hover `/90`) | `bg-brand-brand-surface-solid-default` (hover `-hover`) |
| `text-primary-foreground` | `text-text-neutral-on-solid` |
| `bg-secondary`, `bg-muted`, `bg-accent` | `bg-surface-neutral-secondary` |
| `text-secondary-foreground`, `text-accent-foreground` | `text-text-neutral-primary` |
| `hover:bg-accent` | `hover:bg-surface-neutral-secondary` (aktiv: `-tertiary`) |
| `bg-destructive` (hover `/90`) | `bg-surface-feedback-solid-negative-default` (hover `-hover`) |
| `text-destructive` | `text-text-feedback-negative` |
| `border`, `border-border` | `border-border-default` |
| `border-input` | `border-border-strong` |
| Fokus: `ring-ring`, `ring-[3px]`, `focus-visible:ring-*` | `shadow-focus` (+ `border-brand-brand-border-solid-default`) |
| disabled: `opacity-50`/muted | `bg-surface-neutral-disabled`, `text-text-neutral-disabled`, `border-border-disabled` |
Regel: nur diese Art Klassen ersetzen. Layout-/Spacing-/Radius-Utilities (flex, gap, px, rounded …)
unverändert lassen. Im Zweifel an Button.tsx / TextInput.tsx orientieren (echte Subframe-Tokens).
## Welle 6+7 — „Logik=Radix, Look=Subframe" (Restyle aller restlichen Komponenten)
Strategie-Wechsel (vom Designer bestätigt: es geht um Logik, nicht um neue Subframe-Komponenten):
Die zuvor als „blockiert/Mock" eingestuften Komponenten werden NICHT durch Subframe-Mock-
Komponenten ersetzt — stattdessen behalten sie ihre funktionierende Radix/shadcn-Logik und
bekommen nur den Subframe-LOOK per Token-Swap (className). Sync-sicher (nur components/ui,
Tokens global aus theme.css). Carbon nicht nötig — Radix ist die headless Logik-Basis.
Restyled (typecheck/build/a11y/axe grün): dialog, alert-dialog, modals/*, dropdown-menu,
context-menu, dropdowns/*, tabs (+tabs/*), accordion, collapsible, tooltip, popover, hover-card,
tooltips/*, table (+tables/*), select, drawer, sheet, sidebar, slideOvers/*, card, alert, avatar,
badge (+badges/*, freie Farben bewusst erhalten), breadcrumb (+breadcrumbs/*), empty (+emptyState/*),
stepper, stats/*, separator, scroll-area, resizable, input-group, input, textarea, choicebox,
dividers/*, command (+commandPalettes/*), toast, notifications/*, errors/*, headers/*, chart
(nur Tooltip/Legende), carousel (erbt via Button).
Bewusst NICHT umgestellt: freie Farben (Badge-Paletten, Trend-grün/rot, Status-Icons),
Overlay-Backdrops (`bg-black/50`), Sidebar-eigener `sidebar-*`-Token-Namespace, recharts-Datenfarben,
sowie Komponenten ohne Farb-Tokens (label, pagination, aspect-ratio — Look kommt via Button/Tokens).
Langer Schwanz (noch offen, meist Spezial/Utility, geringe Sichtbarkeit): lists/, sort/, selectors/,
misc/, json/, code/, docs/, layouts/, media/, images/, sections/, wizards/, poll/, pdf/ + Spezial-
Inputs (InputColorSelector/MultiText/Image/Media/RichTextEditor). Icons (111) bleiben außen vor.