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

14 KiB
Raw Blame History

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.