Mall-OS UI (@mall-os/ui)
Das Dashboard und viele Tools bauen auf dem gemeinsamen Paket @mall-os/ui auf (Tailwind v4, Radix-Primitives, shadcn-artige Komponenten). Styling und Verhalten sollen zentral angepasst werden, nicht pro Seite dupliziert.
Theme & Farben
- Single Source of Truth:
packages/ui/src/cockpit-dashboard-theme.css— alle Design-Tokens als HSL-Komponenten (--background: 216 20% 97%…), damit Dashboard (Tailwind v3) und@mall-os/ui(Tailwind v4) dieselben Werte nutzen. - Dashboard: importiert die Datei in
apps/dashboard/src/app/globals.cssvia@import "@mall-os/ui/cockpit-dashboard-theme.css". - UI-Paket:
packages/ui/src/styles.cssimportiert dieselbe Datei und mappt@theme inlineaufhsl(var(--…)). - Seiten-Hintergrund (hell):
--backgroundentspricht etwa#f6f7f9; Karten (--card) und Sidebar bleiben weiß für klaren Kontrast. - Primärfarbe: tiefes Navy (
--primary: 220 38% 26%, ≈#2a4060) für CTAs, Fokus und aktive Sidebar — bewusst nicht Agentur-Signalrot (#cd1619, bleibt--destructive) und nicht das frühere Signal-Blau (#2563eb). Dunkelmodus: aufgehelltes Navy (220 42% 52%). - Favicon / App-Icon:
apps/dashboard/src/app/icon.tsx(32px: Monogramm C),apple-icon.tsx(180px: cOS); Farben ausapps/dashboard/src/lib/cockpit-brand.ts. - Marken-Badge:
apps/dashboard/src/components/cockpit-os-badge.tsx— Navy-Kachel cOS in Sidebar (sidebar-brand-header.tsx) und Top-Header. - E-Mails: Links und Primary-Buttons nutzen
COCKPIT_BRAND.primaryHex(email-layout.ts,dashboard-email-templates.ts) — nicht Center-Theme-Farben. - Radius:
--radiussteuertrounded-*über die Theme-Skala (radius-sm…radius-xl). - Karten-Schatten:
--shadow-cardund--shadow-card-hoverincockpit-dashboard-theme.css;Cardnutztshadow-[var(--shadow-card)]. - Sekundärtext:
--muted-foreground(und verwandte Flächen wie--muted) sind so gesetzt, dass Hilfstext und Beschreibungen auf hellen Hintergründen lesbar bleiben (ausreichender Kontrast zu grauen Flächen).
Änderungen an der „globalen Optik“ gehören in cockpit-dashboard-theme.css (nicht doppelt in globals.css pflegen).
Dialog, AlertDialog & Sheet (Modals)
- Inhalt:
bg-card/text-card-foreground(nichtbg-background), damit das Fenster sich von der Seitenfläche abhebt. - Overlay: etwa
bg-black/55undbackdrop-blur-smfür eine klare Dim-Schicht. - Erhöhung:
shadow-2xlund dezenterringfür Tiefe. - z-index:
z-[10400]— über Dropdown-Popovern, die im Dashboard oft~9999nutzen; gleiche Schicht für Dialog, AlertDialog und Sheet.
Implementierung: packages/ui/src/components/ui/dialog.tsx, alert-dialog.tsx, sheet.tsx. Eigene „Fullscreen“-Overlays im Dashboard sollten dieselbe z-Schicht und ähnliche Card-Optik nutzen, statt z-[200] + Seiten-Hintergrundfarbe.
Dashboard-Audit (manuell): u. a. content/hot-picks, scheduling/videos, Galerie-Lightbox (gallery-block.plugin) — Overlays auf z-[10400] + bg-black/55 / backdrop-blur-sm bzw. Lightbox bewusst dunkel. Weitere Apps (center-website-Template-Kopien von dialog.tsx, digital-signage, center-manager) nutzen noch eigene z-50-Varianten; dort bei Bedarf nachziehen oder auf @mall-os/ui umstellen.
-
Form controls:
Input,SelectTriggerundTextareanutzen durchgängigbg-card,border-border,h-11(Input/Select default),shadow-smund gleiche Focus-Ringe — damit Suche und Dropdowns auf grauem Seitenhintergrund gleich wirken. Filter-Leisten: Icon links im Trigger überflex min-w-0 flex-1+SelectValue; Suche mit Lupen-Icon (Search), nicht mit Filter-Icon. -
Select & DropdownMenu (kein Layout-Sprung): Beide nutzen in
@mall-os/uistandardmäßigmodal={false}(select.tsx,dropdown-menu.tsx). So setzt Radix keinen Body-Scroll-Lock — die Seite springt nicht horizontal, wenn ein Menü aufgeht. Zusätzlich verhindernSelectContent/DropdownMenuContentperonOpenAutoFocus→preventDefault()das browserseitige Scrollen zum fokussierten Portal (typisches „Zucken“ bei langen Seiten). Für Einzelfälle kannmodal={true}gesetzt werden (z. B. stärkere Fokus-Falle). -
Scrollbar &
react-remove-scroll: Radix Select bindet intern immerRemoveScroll(keinmodal={false}in der API) — beim Öffnen verschwindet die Scrollbar kurz undreact-remove-scrollsetzt oftpadding-rightaufbody. Im Dashboard reserviertscrollbar-gutter: stable(globals.css) dauerhaft Platz;body[data-scroll-locked] { margin-right: 0; padding-right: 0 }verhindert Doppel-Kompensation (Layout-Sprung nach rechts). DropdownMenu mitmodal={false}sollte keinen Lock auslösen; tritt der Sprung trotzdem auf, prüfen ob direkt@radix-ui/react-dropdown-menuohne Wrapper genutzt wird.
Dashboard-Seiten-Shell (PageContainer)
Standard für Inhaltsseiten im Dashboard (nicht für Hub-Kacheln oder Redirects):
import { PageContainer, PageContent, PageHeader } from "@mall-os/ui";
export default function ExamplePage() {
return (
<PageContainer>
<PageHeader title="Titel" description="Kurzbeschreibung" actions={…} />
<PageContent>{/* Karten, Tabellen, Formulare */}</PageContent>
</PageContainer>
);
}
| Muster | Wann |
|---|---|
PageContainer + PageHeader + PageContent | Listen, Detail, Formulare, Einstellungen |
CockpitSectionHub | Modul-Hubs (Website, Personal, Organisation, Redaktionsfluss) |
SocialPageShell / SocialHubView | Social-Cockpit |
redirect() / CenterTabRedirect | Legacy-Pfade, Tab-Deep-Links |
Dünner Re-Export (import X; export default X) | Route → gemeinsame Komponente mit Shell |
Status-Badges zentral: apps/dashboard/src/lib/content-status-badges.ts — gemeinsame Chip-Komponenten unter apps/dashboard/src/components/badges/ (z. B. Template-Freigabe, QR-Typ). Imports: immer @/lib/content-status-badges, keine relativen ../../../lib/…-Pfade (tiefe Routen brechen sonst den Build).
Prüfung:
node scripts/check-dashboard-page-shell.mjs # Report
node scripts/check-dashboard-page-shell.mjs --strict # CI / prebuild
node scripts/check-dashboard-import-aliases.mjs --strict
Dashboard-Layout-Shell
Das feste Chrome (Sidebar-Inset, Sticky-Header, Content-Padding) liegt zentral in apps/dashboard/src/components/cockpit-dashboard-shell.tsx (CockpitDashboardShell, cockpitDashboardShellClasses). Das Dashboard-Layout bindet diese Komponente ein, statt Klassen zu duplizieren.
Action-Buttons
ActionButton / CreateButton / … in packages/ui/src/components/ui/action-button.tsx nutzen Button-Varianten (default, outline, destructive, ghost, success) und ergänzende className-Tokens (primary, success, muted, border …) statt fester Tailwind-Farben wie blue-600.
Bunny-Upload-Typen
BunnyUploadFolderTypewird inpackages/ui/src/hooks/use-bunny-upload.tsexportiert und beschreibt erlaubtetype-Werte für Uploads (inkl. Dashboard-spezifischer Ordner wietile,shop-chain,themes,dooh,wayfinding,videos,images).- Das Dashboard importiert diesen Typ für
apps/dashboard/src/hooks/use-bunny-upload.tsundbunny-upload.tsx, damit Props und Hook dieselbe Union nutzen.
Button & Dashboard-@/components/ui/*
- Primitives: Formular-, Layout- und Overlay-Komponenten (Input, Card, Dialog, Tabs, Sidebar, Toast, …) werden im Dashboard als Re-Export aus
@mall-os/uigeführt — eine Implementierung unterpackages/ui/src/components/ui/, Pfade wie@/components/ui/inputbleiben gültig. - Ausnahmen (lokal im Dashboard):
bunny-upload.tsx(erweiterte Upload-Typen + Dashboard-Hook),simple-rich-text-editor.tsx,rich-text-editor.tsx,safe-html-content.tsx. - Button:
packages/ui/src/components/ui/button.tsx(Button,buttonVariants); Dashboard-Datei re-exportiert. - Tailwind: Dashboard-
contententhält../../packages/ui/src/**, damit Klassen aus dem Paket im JIT ankommen.
Kernkomponenten (Auszug)
| Bereich | Datei unter packages/ui/src/components/ui/ |
|---|---|
| Buttons, Inputs, Textarea | button.tsx, input.tsx, textarea.tsx |
| Auswahl | select.tsx, checkbox.tsx, radio-group.tsx, switch.tsx |
| Überlagerungen | dialog.tsx, alert-dialog.tsx, sheet.tsx, popover.tsx |
| Menüs & Toasts | dropdown-menu.tsx, sonner.tsx, toast.tsx |
| Tabs | tabs.tsx |
| Karten & Layout | card.tsx, separator.tsx, page-layout.tsx |
| Avatar | avatar.tsx |
| Hinweise | alert.tsx, alert-banner.tsx |
Hinweise (Alert & AlertBanner)
- Dateien:
packages/ui/src/components/ui/alert.tsx,alert-banner.tsx. - Alert: shadcn-Muster — abgerundeter Rand, dezenter Hintergrund, Icon links, Titel + Beschreibung. Varianten:
default(Info/Blau),destructive,warning,success. Kein manuellesborder-blue-200 bg-blue-50o. Ä. - AlertBanner: Seiten-Hinweis mit optionaler Aktion rechts (Button/Link). Baut auf
Alertauf — für Meldungen wie „Viele Freigaben offen“, Pending-Drafts, Setup-Hinweise. - Formular-Abschnitte (z. B. „Schnell-Erstellung“): normale
Cardohneborder-l-4, Schatten kommt auscard.tsx. - Schnellaktions-Kacheln (News-Liste):
Cardmithover:shadow-md, Icon inbg-primary/10— keine farbigen Rand/Hintergrund-Kombinationen.
import { AlertBanner, Button } from "@mall-os/ui";
<AlertBanner
variant="warning"
title="Viele Freigaben offen!"
description="26 News warten auf Freigabe."
action={<Button size="sm" variant="outline">Alle bearbeiten</Button>}
/>
Einfache Inline-Hinweise ohne Aktion: Alert + AlertTitle + AlertDescription direkt.
Action-Chips (Schnellstart & Aufgaben)
- Datei:
packages/ui/src/components/ui/action-chip.tsx QuickActionChip— Create-/Schnellstart-Aktionen: Icon inbg-primary/10, Karten-Schatten wieStatTile, Hover mit Primary-Rand.TaskChip— Offene Aufgaben mit Zähler: leicht getönte Primary-Fläche, Zahl fett, Label gedämpft; beicount === 0standardmäßig ausgeblendet.ActionChipGroup— einheitlicherflex-wrap-Abstand (gap-2.5).
Mit Next.js Link über asChild:
import Link from "next/link";
import { ActionChipGroup, QuickActionChip, TaskChip } from "@mall-os/ui";
<ActionChipGroup>
<QuickActionChip asChild label="News anlegen">
<Link href="/dashboard/content/news/new" />
</QuickActionChip>
<TaskChip asChild count={3} label="Freigaben" icon={FileText}>
<Link href="/dashboard/workflow" />
</TaskChip>
</ActionChipGroup>
Statt ad-hoc Button variant="outline" oder rounded-full-Pills — gleiche Optik dashboard-weit wiederverwendbar.
Hub-Navigationskacheln (HubNavTile)
- Datei:
packages/ui/src/components/ui/hub-nav-tile.tsx HubNavTile— Navigations-Kachel für Modul-Hubs (Website, Marketing, Bereiche, Social). Varianten:layout="default"— vertikal wie/dashboard/website(Icon, Titel, Beschreibung, Hover-Pfeil)layout="compact"— horizontal (Dashboard-Start „Bereiche“, Social „Mehr“)layout="featured"— großes Icon + CTA-Fußzeile (Social Hero-Aktionen)
HubNavGrid— Gridsm:2 lg:3, optionalcolumns="dense"fürxl:4(Marketing)HubSection— Shell mitPageHeader+ optional GridHUB_ICON_TONE/HubSurfaceVariant— semantische Icon-Farben überSurface
Mit Next.js Link als Wrapper (Kachel bleibt div + Card):
import Link from "next/link";
import { HubNavGrid, HubNavTile } from "@mall-os/ui";
<HubNavGrid columns="dense">
<Link href="/dashboard/website" className="block h-full no-underline">
<HubNavTile
title="Center-Website"
description="Template, Domain und SEO"
icon={Globe}
surfaceVariant="info"
meta="3857 veröffentlicht"
/>
</Link>
</HubNavGrid>
Dashboard: CockpitSectionHub (apps/dashboard/src/components/cockpit-section-hub.tsx) ist ein dünner Wrapper über HubSection + HubNavTile. HubResourceCard (hub-resource-card.tsx) für /dashboard/content — Zähler + Verwalten/Neu pro Content-Typ.
HubResourceCard (Content-Hub)
import { HubNavGrid, HubResourceCard } from "@mall-os/ui";
<HubNavGrid columns="dense">
<HubResourceCard
title="News"
description="Neuigkeiten und Ankündigungen"
icon={Newspaper}
count={42}
manageHref="/dashboard/content/news"
createHref="/dashboard/content/news/new"
surfaceVariant="info"
/>
</HubNavGrid>
Verwendet in: /dashboard/content (Bereiche-Grid oben, Performance + Top-Inhalte darunter).
Semantische Flächen & Layout-Bausteine
Zentral in @mall-os/ui — keine duplizierten bg-*-50-Klassen im Dashboard.
| Komponente | Datei | Einsatz |
|---|---|---|
Surface | surface.tsx | Hervorgehobene Abschnitte (info, success, warning, destructive, muted) |
StatTile | stat-tile.tsx | KPI / Metrik (Zahl + Label, optional Icon) |
QuickActionChip / TaskChip | action-chip.tsx | Schnellstart- und Aufgaben-Chips (Dashboard-Startseite, überall wo kompakte Aktions-Reihen) |
HubNavTile / HubNavGrid / HubSection | hub-nav-tile.tsx | Modul-Hub-Navigation (Website, Marketing, Bereiche, Social) |
HubResourceCard | hub-resource-card.tsx | Content-Hub mit Zähler + Verwalten/Neu |
Table | table.tsx | Listen — Container + <tbody> mit bg-card (weiße Fläche auf --background) |
Table bringt Rahmen, Schatten und bg-card (hell: weiß) mit — keinen zusätzlichen <div className="rounded-md border"> drumherum (doppelter Rahmen, grauer Durchschein). Für native <table> ohne Komponente: contentTableShellClass aus @/lib/content-ui-tokens.
| SelectableCard | selectable-card.tsx | Auswahl-Kacheln (AI-Modus, Quick-Setup) |
| KanbanColumn | kanban-column.tsx | Board-Spalten mit token-basiertem Header/Body |
| StatusBadge | status-badge.tsx | Einheitliche Status-Chips (published, pending, …) |
Badge-Varianten (outline-success, active, …) nutzen Design-Tokens (--success, --warning, --info) — Änderungen in cockpit-dashboard-theme.css wirken global.
Hub-Seiten (CockpitSectionHub)
- Datei:
apps/dashboard/src/components/cockpit-section-hub.tsx— Wrapper umHubSection+HubNavTileaus@mall-os/ui - Kachel-Icons über
surfaceVariant+Surface— nicht mehr übercolor-Tailwind-Strings. - Verwendet in: Center-Website, Personal, Redaktionsfluss, Organisation; Marketing-, Social-, Property-, Digital-Experience- und WordPress-Hubs nutzen
HubNavTiledirekt; Content-Hub nutztHubResourceCard.
Content-Status-Badges (Dashboard)
- Datei:
apps/dashboard/src/lib/content-status-badges.ts— Badge-Varianten (News, Events, Pages, Jobs, Offers, …) - Datei:
apps/dashboard/src/lib/content-ui-tokens.ts— Links (contentDetailLinkClass), Metrik-Farben (contentMetric), Listen-Icons, Formular-Helfer (contentGlobalLabelClass,contentFormSectionIconClass,contentNativeCheckboxClass), Detail-Ansicht (contentFieldLabelClass,contentCaptionClass,contentDetailIconClass) - Datei:
apps/dashboard/src/lib/content-status-badges.ts— u. a.officeVerifiedBadgeVariant,officeFeaturedBadgeVariant - Datei:
apps/dashboard/src/components/content/category-select-option.tsx— einheitliche Kategorie-Zeile in Create/Edit-Selects (Name, Center, „Global“, Master-Kategorie, Farbpunkt) - Datei:
apps/dashboard/src/components/content/content-form-footer.tsx— fixierter Formular-Fuß (Center, Status, Geplant-Veröffentlichung) nutzt dieselben Tokens; u. a. Shops, Jobs, Services, Offices, Hot-Picks, Offers, News, Events, Shop-Locations (New/Edit) und Categories (Edit). PropshowCenterSelect={false}wenn die Center-Zuordnung fest ist. - Datei:
apps/dashboard/src/components/content/content-form-steps-hint.tsx— Hinweis-Banner über News/Event/Angebot/Job-Formularen (3 Reiter: Inhalt → Veröffentlichen → Optional).
Hub-Kacheln (HubNavTile, HubResourceCard)
- Dateien:
packages/ui/src/components/ui/hub-nav-tile.tsx,hub-resource-card.tsx - Feste Beschreibungshöhe (
HUB_TILE_DESCRIPTION_CLASS, 2 Zeilen) + Grid/Flex — Zähler und Buttons in einer Zeile auf gleicher Höhe;HubNavGridmititems-stretch/[&>*]:h-full.
Social Hub (SocialHubView)
- Datei:
apps/dashboard/src/components/social/social-hub-view.tsx - „Heute für dich“:
StatTile(5 KPIs inkl. offene Kommentare); Featured-CTAs und „Mehr“-Grid:HubNavTile/HubNavGrid; Daten:GET /api/social/hub. - Community-Hub (
/dashboard/community) und Analytics-Übersicht (/dashboard/analytics) nutzen dasselbe Hub-Kachel-Muster für Subnavigation.
Qualitätssicherung
pnpm check:dashboard-ui # Exit 1 bei Verstößen (strict, Repo-Root)
pnpm check:dashboard-ui-slop # AI-Slop-Muster (Spinner, Side-Tabs, …)
pnpm check:dashboard-page-shell # Page-Shell für dashboard/**/page.tsx
pnpm check:dashboard-import-aliases # Keine relativen ../lib-Imports
pnpm --filter @mall-os/dashboard run check:ui-tokens # wie prebuild
pnpm --filter @mall-os/dashboard run check:ui-slop # wie prebuild
node scripts/check-dashboard-ui-tokens.mjs # Nur Report ohne Build-Abbruch
node scripts/check-dashboard-ui-tokens.mjs --strict # wie CI / prebuild
node scripts/check-dashboard-ui-slop.mjs --strict # wie CI / prebuild
node scripts/check-dashboard-import-aliases.mjs --strict
node scripts/migrate-dashboard-color-classes.mjs # Codemod (optional --dry-run)
Das Skript läuft im Dashboard-prebuild automatisch mit --strict. Es scannt alle apps/dashboard/src/**/*.ts(x) auf hardcodierte Tailwind-Palette-Utilities:
- Utilities:
text,bg,border,from,to,via,ring(inkl./opacity) - Varianten:
hover:,focus:,dark:… - Farben:
blue,green,red,gray,slate,zinc,emerald,amber,orange,yellow,purple,sky,pink,indigo,neutral,stone - Shade-Abgrenzung
(?!\d)—bg-blue-500wird nicht alsbg-blue-50gematcht
Ignoriert (bewusste Farbdaten / Vorschau): lib/plugins/, lib/theme-templates/, block-preview.tsx, HeaderFooterColorSettings.tsx, homepage-tiles/background-config.tsx — siehe .cursor/rules/dashboard-ui-tokens.mdc. Status-Badge-Mapper: lib/content-status-badges.ts.
Migration: scripts/migrate-dashboard-color-classes.mjs mappt gängige Klassen auf Tokens (text-muted-foreground, bg-primary/[0.09], text-[hsl(var(--success))], …). Nach Codemod Rest manuell prüfen; Baseline-Ziel ist 0 Treffer unter --strict.
Zusätzlich: GitHub-Workflow .github/workflows/dashboard-quality.yml — schnelles Feedback bei PRs (UI-Token-Check, Slop-Check, Dynamic-Route-Slug-Check ohne vollen Next-Build).
Cursor-Regel für neue Dashboard-UI: .cursor/rules/dashboard-ui-tokens.mdc.
Avatar
- Datei:
packages/ui/src/components/ui/avatar.tsx(Radix Avatar). - Darstellung: Root als
inline-flexmititems-center,justify-center,leading-none,overflow-hidden,rounded-full— vermeidet Layout-Lücken durchinline-IMG-Baseline. - Bild:
AvatarImagemitblock,h-full,w-full,object-cover,object-center— Kreis wird zuverlässig gefüllt;max-h-full/max-w-fullgegen Konflikte mit globalen IMG-Regeln (z. B. Preflight).
Switch (Toggle)
- Datei:
packages/ui/src/components/ui/switch.tsx. - Aus: Schiene
bg-muted-foreground/25(hell) bzw./40im Dark-Mode,border-muted-foreground/45— klar grauer „Graben“; Daumenbg-white(bzw.zinc-100dark) mitborder-2,shadow-md. - An: Schiene
bg-primary, Daumenbg-primary-foreground; etwas größere Hitbox (h-7,w-[3.25rem], Daumenh-6 w-6) für bessere Erkennbarkeit von links/rechts.
Tabs
<Tabs> nutzt ausschließlich die segmented-Optik (Social Cockpit) — abgerundete Leiste bg-muted/30, Rand, horizontal scrollbar bei Bedarf; inaktiv text-muted-foreground, aktiv bg-background, shadow-sm, ring-1 ring-primary/40. Styles liegen in packages/ui/src/segmented-tabs.css (data-slot), unabhängig vom Tailwind-Content-Scan.
Route-basierte Subnavigation (z. B. Social Cockpit) nutzt SegmentedNav / SegmentedNavLink in apps/dashboard/src/components/dashboard/segmented-nav.tsx.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@mall-os/ui";
<Tabs defaultValue="a">
<TabsList className="w-full">
<TabsTrigger value="a">Eins</TabsTrigger>
<TabsTrigger value="b">Zwei</TabsTrigger>
</TabsList>
…
</Tabs>
TabsList nicht mit grid / flex-wrap überschreiben — sonst weicht die Leiste vom Standard ab. Spacing (mb-4, max-w-3xl) ist ok.
Spinner & Anti-Slop
Was: Einheitlicher Ladeindikator <Spinner /> aus @mall-os/ui statt handgebauter border-b-2-Divs, Side-Tab-border-l-4, Side-Stripe-Divs (absolute inset-y-0 left-0 w-1 …) und farbige border-l-2 (Impeccable-Detector-Lücke: nur ≥4px ohne rounded, bzw. ≥2px mit rounded), Gradient-Überschriften oder hardcodierter bg-blue-*/bg-gray-* in UI-Kernkomponenten.
Warum: Solche Muster („AI Slop“) wirken generisch, brechen das Token-System und divergieren zwischen Seiten.
Wo: packages/ui/src/components/ui/spinner.tsx — Größen xs | sm | default | lg | xl. In Buttons: className="mr-2 text-primary-foreground" bzw. text-destructive statt border-white.
import { Spinner } from "@mall-os/ui";
{loading ? <Spinner className="mx-auto" /> : null}
Check: pnpm check:dashboard-ui-slop (strict in CI und Dashboard-prebuild). Skript: scripts/check-dashboard-ui-slop.mjs.
v0-Frontends (Vercel, nicht dieses Repo): Impeccable — npx impeccable detect app/ components/ lib/ (46 Regeln, Exit 2). In v0-Instructions Teil C/J und v0 + Cockpit (So geht’s) beschrieben.
Nicht mehr nutzen: contentLoadingSpinnerClass / Ring-Varianten in content-ui-tokens.ts — entfernt zugunsten von <Spinner />.
Content-Listen (Shops, Events, Services, Jobs)
Was: Übersichtsseiten unter apps/dashboard/src/app/dashboard/content/*/page.tsx folgen einem schlanken Muster: kurzer Seitentitel, max. **4 StatTile-KPIs, ContentFilterBar(Center, Suche, Status, Kategorie/Typ), Tabs, kompakte Tabelle (Meta-Zeile unter dem Namen statt 8+ Spalten),BulkActionBar` bei Mehrfachauswahl.
Filter (DRY): Gemeinsame Presets und Matcher in apps/dashboard/src/lib/content-list-filters.ts (CONTENT_FILTER_ALL, Status-Optionen pro Typ, matchesContentSearch, …). Kategorien laden über use-content-categories.ts (Shop-Kategorien, Service-Kategorien, Inhaltskategorien für Events).
StatTile / MetricCard: Weiße Karte mit Label oben, großem Wert, optionaler Statuszeile (Icon + farbiger Status + Hint) und farbiger Akzentleiste unten (packages/ui/src/components/ui/stat-tile.tsx). Varianten: default, success, warning, destructive, muted. Für KPI-Panels mit freiem Inhalt: <MetricCard title="…" variant="info">…</MetricCard>.
Schrift: Dashboard nutzt Geist (next/font/google in apps/dashboard/src/app/layout.tsx, font-sans auf body).
Nicht: Fake-KI-Blöcke (Sparkles, „Content-Optimierung“, hardcodierte +80%-Metriken), 6er-KPI-Grids, tote Export/Insights-Buttons, UUID-Spalten in Tabellen, overflow-x-auto nur um breite Tabellen zu retten.
Referenz: content/news/page.tsx, content/shops/page.tsx, content/page.tsx (Hub), content/media/page.tsx.
Sidebar-Audit (104 Seiten)
Check: node scripts/audit-dashboard-nav-pages.mjs — scannt navigation.ts top→bottom (inkl. dashboard-home.tsx für /dashboard) auf Loader2, Brain, Sparkles, 6er-KPI-Grids, KI-Deko-Text, overflow-x-auto.
Stand: Alle 104 Sidebar-Seiten ohne Slop-Treffer (Finance, Analytics, Property/Security, Chatbot, Settings analog zu Content-Listen bereinigt: neutrale Icons, keine Brain/Sparkles-Deko, Spinner statt Loader2).
UI-Audit (Impeccable + globale Nutzung)
Automatische Checks (lokal / CI):
| Skript | Zweck |
|---|---|
node scripts/check-dashboard-ui-slop.mjs --strict | Side-Tabs, Gradient-Headings, hardcodierte blue/gray in UI-Kern |
node scripts/check-dashboard-jsx-imports.mjs --strict | JSX ohne Import (Badge, Spinner, StatTile, …) — verhindert ReferenceError |
node scripts/check-dashboard-ui-tokens.mjs --strict | Hardcodierte Tailwind-Farben |
node scripts/check-dashboard-page-shell.mjs --strict | PageContainer/PageHeader auf Routen |
node scripts/audit-dashboard-nav-pages.mjs | 104 Sidebar-Seiten: Loader2, Sparkles, 6er-KPI-Grid |
node scripts/audit-dashboard-ui-components.mjs | StatTile vs. Legacy-KPIs, Loader2-Zähler, violet palette |
npx impeccable detect apps/dashboard/src … packages/ui/src/components/ui | Externer Anti-Slop (46 Regeln) |
Canonical-Komponenten (immer bevorzugen):
| Use case | Komponente |
|---|---|
| KPI-Zahl | StatTile |
| KPI-Panel mit Inhalt | MetricCard |
| Seitenrahmen | PageContainer, PageHeader, PageContent |
| Laden | Spinner (nicht Loader2 direkt) |
| Hinweis mit Aktion | AlertBanner / Alert |
| Semantische Fläche (kein KPI) | Surface |
Migrations-Stand (typisch): 309 55 % canonical). Offen u. a. Content-Übersicht (Nav-Karten), System-/Property-Mock-Bereiche, ~90 Dateien mit raw StatTile-Nutzungen (Loader2 (überwiegend Komponenten).
Wo nicht anpacken
- Center-Website-Templates können eigene Kopien unter
apps/center-website/.../components/ui/haben — dort gelten nicht automatisch alle Änderungen an@mall-os/ui. - Einmalige Layout-Sonderfälle im Dashboard: lieber
classNameauf der Seite setzen als neue Varianten ohne Bedarf duplizieren.
Siehe auch
packages/ui/src/components/ui/index.ts— Exportlistepackages/ui/src/cockpit-dashboard-theme.css— Design-Tokens (SSoT)packages/ui/src/styles.css— Tailwind v4 +@theme(importiert das Theme)
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /developer-guide/mall-os-ui