Zum Hauptinhalt springen

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.css via @import "@mall-os/ui/cockpit-dashboard-theme.css".
  • UI-Paket: packages/ui/src/styles.css importiert dieselbe Datei und mappt @theme inline auf hsl(var(--…)).
  • Seiten-Hintergrund (hell): --background entspricht 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 aus apps/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: --radius steuert rounded-* über die Theme-Skala (radius-smradius-xl).
  • Karten-Schatten: --shadow-card und --shadow-card-hover in cockpit-dashboard-theme.css; Card nutzt shadow-[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 (nicht bg-background), damit das Fenster sich von der Seitenfläche abhebt.
  • Overlay: etwa bg-black/55 und backdrop-blur-sm für eine klare Dim-Schicht.
  • Erhöhung: shadow-2xl und dezenter ring für Tiefe.
  • z-index: z-[10400] — über Dropdown-Popovern, die im Dashboard oft ~9999 nutzen; 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, SelectTrigger und Textarea nutzen durchgängig bg-card, border-border, h-11 (Input/Select default), shadow-sm und gleiche Focus-Ringe — damit Suche und Dropdowns auf grauem Seitenhintergrund gleich wirken. Filter-Leisten: Icon links im Trigger über flex 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/ui standardmäßig modal={false} (select.tsx, dropdown-menu.tsx). So setzt Radix keinen Body-Scroll-Lock — die Seite springt nicht horizontal, wenn ein Menü aufgeht. Zusätzlich verhindern SelectContent / DropdownMenuContent per onOpenAutoFocuspreventDefault() das browserseitige Scrollen zum fokussierten Portal (typisches „Zucken“ bei langen Seiten). Für Einzelfälle kann modal={true} gesetzt werden (z. B. stärkere Fokus-Falle).

  • Scrollbar & react-remove-scroll: Radix Select bindet intern immer RemoveScroll (kein modal={false} in der API) — beim Öffnen verschwindet die Scrollbar kurz und react-remove-scroll setzt oft padding-right auf body. Im Dashboard reserviert scrollbar-gutter: stable (globals.css) dauerhaft Platz; body[data-scroll-locked] { margin-right: 0; padding-right: 0 } verhindert Doppel-Kompensation (Layout-Sprung nach rechts). DropdownMenu mit modal={false} sollte keinen Lock auslösen; tritt der Sprung trotzdem auf, prüfen ob direkt @radix-ui/react-dropdown-menu ohne 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>
);
}
MusterWann
PageContainer + PageHeader + PageContentListen, Detail, Formulare, Einstellungen
CockpitSectionHubModul-Hubs (Website, Personal, Organisation, Redaktionsfluss)
SocialPageShell / SocialHubViewSocial-Cockpit
redirect() / CenterTabRedirectLegacy-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

  • BunnyUploadFolderType wird in packages/ui/src/hooks/use-bunny-upload.ts exportiert und beschreibt erlaubte type-Werte für Uploads (inkl. Dashboard-spezifischer Ordner wie tile, shop-chain, themes, dooh, wayfinding, videos, images).
  • Das Dashboard importiert diesen Typ für apps/dashboard/src/hooks/use-bunny-upload.ts und bunny-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/ui geführt — eine Implementierung unter packages/ui/src/components/ui/, Pfade wie @/components/ui/input bleiben 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-content enthält ../../packages/ui/src/**, damit Klassen aus dem Paket im JIT ankommen.

Kernkomponenten (Auszug)

BereichDatei unter packages/ui/src/components/ui/
Buttons, Inputs, Textareabutton.tsx, input.tsx, textarea.tsx
Auswahlselect.tsx, checkbox.tsx, radio-group.tsx, switch.tsx
Überlagerungendialog.tsx, alert-dialog.tsx, sheet.tsx, popover.tsx
Menüs & Toastsdropdown-menu.tsx, sonner.tsx, toast.tsx
Tabstabs.tsx
Karten & Layoutcard.tsx, separator.tsx, page-layout.tsx
Avataravatar.tsx
Hinweisealert.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 manuelles border-blue-200 bg-blue-50 o. Ä.
  • AlertBanner: Seiten-Hinweis mit optionaler Aktion rechts (Button/Link). Baut auf Alert auf — für Meldungen wie „Viele Freigaben offen“, Pending-Drafts, Setup-Hinweise.
  • Formular-Abschnitte (z. B. „Schnell-Erstellung“): normale Card ohne border-l-4, Schatten kommt aus card.tsx.
  • Schnellaktions-Kacheln (News-Liste): Card mit hover:shadow-md, Icon in bg-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 in bg-primary/10, Karten-Schatten wie StatTile, Hover mit Primary-Rand.
  • TaskChip — Offene Aufgaben mit Zähler: leicht getönte Primary-Fläche, Zahl fett, Label gedämpft; bei count === 0 standardmäßig ausgeblendet.
  • ActionChipGroup — einheitlicher flex-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 — Grid sm:2 lg:3, optional columns="dense" für xl:4 (Marketing)
  • HubSection — Shell mit PageHeader + optional Grid
  • HUB_ICON_TONE / HubSurfaceVariant — semantische Icon-Farben über Surface

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/uikeine duplizierten bg-*-50-Klassen im Dashboard.

KomponenteDateiEinsatz
Surfacesurface.tsxHervorgehobene Abschnitte (info, success, warning, destructive, muted)
StatTilestat-tile.tsxKPI / Metrik (Zahl + Label, optional Icon)
QuickActionChip / TaskChipaction-chip.tsxSchnellstart- und Aufgaben-Chips (Dashboard-Startseite, überall wo kompakte Aktions-Reihen)
HubNavTile / HubNavGrid / HubSectionhub-nav-tile.tsxModul-Hub-Navigation (Website, Marketing, Bereiche, Social)
HubResourceCardhub-resource-card.tsxContent-Hub mit Zähler + Verwalten/Neu
Tabletable.tsxListen — 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 um HubSection + HubNavTile aus @mall-os/ui
  • Kachel-Icons über surfaceVariant + Surface — nicht mehr über color-Tailwind-Strings.
  • Verwendet in: Center-Website, Personal, Redaktionsfluss, Organisation; Marketing-, Social-, Property-, Digital-Experience- und WordPress-Hubs nutzen HubNavTile direkt; Content-Hub nutzt HubResourceCard.

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). Prop showCenterSelect={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; HubNavGrid mit items-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-500 wird nicht als bg-blue-50 gematcht

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-flex mit items-center, justify-center, leading-none, overflow-hidden, rounded-full — vermeidet Layout-Lücken durch inline-IMG-Baseline.
  • Bild: AvatarImage mit block, h-full, w-full, object-cover, object-center — Kreis wird zuverlässig gefüllt; max-h-full / max-w-full gegen 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. /40 im Dark-Mode, border-muted-foreground/45 — klar grauer „Graben“; Daumen bg-white (bzw. zinc-100 dark) mit border-2, shadow-md.
  • An: Schiene bg-primary, Daumen bg-primary-foreground; etwas größere Hitbox (h-7, w-[3.25rem], Daumen h-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): Impeccablenpx 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.

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):

SkriptZweck
node scripts/check-dashboard-ui-slop.mjs --strictSide-Tabs, Gradient-Headings, hardcodierte blue/gray in UI-Kern
node scripts/check-dashboard-jsx-imports.mjs --strictJSX ohne Import (Badge, Spinner, StatTile, …) — verhindert ReferenceError
node scripts/check-dashboard-ui-tokens.mjs --strictHardcodierte Tailwind-Farben
node scripts/check-dashboard-page-shell.mjs --strictPageContainer/PageHeader auf Routen
node scripts/audit-dashboard-nav-pages.mjs104 Sidebar-Seiten: Loader2, Sparkles, 6er-KPI-Grid
node scripts/audit-dashboard-ui-components.mjsStatTile vs. Legacy-KPIs, Loader2-Zähler, violet palette
npx impeccable detect apps/dashboard/src … packages/ui/src/components/uiExterner Anti-Slop (46 Regeln)

Canonical-Komponenten (immer bevorzugen):

Use caseKomponente
KPI-ZahlStatTile
KPI-Panel mit InhaltMetricCard
SeitenrahmenPageContainer, PageHeader, PageContent
LadenSpinner (nicht Loader2 direkt)
Hinweis mit AktionAlertBanner / Alert
Semantische Fläche (kein KPI)Surface

Migrations-Stand (typisch): 309 StatTile-Nutzungen (55 % canonical). Offen u. a. Content-Übersicht (Nav-Karten), System-/Property-Mock-Bereiche, ~90 Dateien mit raw 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 className auf der Seite setzen als neue Varianten ohne Bedarf duplizieren.

Siehe auch

  • packages/ui/src/components/ui/index.ts — Exportliste
  • packages/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