{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "ekklesi",
  "homepage": "https://www.ekklesi.tools/design",
  "description": "Design-System der ekklesi-Produktfamilie — token-getriebene React-Primitives (CSS Modules), extrahiert aus ekklesi:presenter.",
  "updatedAt": "2026-07-26T20:35:17.969Z",
  "count": 27,
  "items": [
    {
      "id": "tokens",
      "name": "tokens",
      "title": "Design Tokens",
      "type": "registry:theme",
      "description": "Das gesamte Token- und Style-Fundament: Farben, Typografie, Spacing, Radien, Schatten, Motion und Field-/Control-Tokens — plus Reset und globale Scrollbar-/Focus-Stile. Light + Dark (über [data-theme=\"dark\"]). Voraussetzung für alle Komponenten; in fremden Projekten Token-Werte überschreiben = eigenes Theme.",
      "tags": [
        "theme",
        "tokens",
        "foundation"
      ],
      "dependencies": [],
      "registryDependencies": [],
      "tokensUsed": [
        "--accent",
        "--accent-fg",
        "--accent-on",
        "--bg",
        "--border",
        "--brand-accent-2",
        "--brand-aqua",
        "--brand-citron",
        "--brand-ecclesial-blue",
        "--brand-grad",
        "--brand-ink",
        "--brand-primary",
        "--brand-sanctus-violet",
        "--brand-secondary",
        "--brand-vesper-mint",
        "--brand-vigil-teal",
        "--danger",
        "--fg",
        "--focus-ring",
        "--font-body",
        "--font-brand",
        "--font-display",
        "--font-mono",
        "--font-stage",
        "--leading-body",
        "--muted",
        "--radius-lg",
        "--radius-md",
        "--radius-sm",
        "--section-bridge",
        "--section-chorus",
        "--section-verse",
        "--space-5",
        "--stage-bg",
        "--stage-fg",
        "--status-info",
        "--success",
        "--surface",
        "--text-2xl",
        "--text-3xl",
        "--text-lg",
        "--text-sm",
        "--text-xl",
        "--text-xs",
        "--warn",
        "--x"
      ],
      "file": "components/tokens.json"
    },
    {
      "id": "button",
      "name": "button",
      "title": "Button",
      "type": "registry:ui",
      "description": "Polymorpher Button mit Varianten (default, primary, ghost, danger) und Größen (default, square für Icon-Buttons). forwardRef, reicht alle nativen <button>-Attribute durch. aria-pressed=true rendert den aktiven Toggle-Zustand.",
      "tags": [
        "action",
        "form"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--accent-active",
        "--accent-hover",
        "--accent-on",
        "--bg",
        "--border",
        "--control-height",
        "--danger",
        "--ease-standard",
        "--fg",
        "--field-pad-x",
        "--focus-ring",
        "--motion-fast",
        "--radius-sm",
        "--space-2",
        "--surface",
        "--text-sm"
      ],
      "file": "components/button.json"
    },
    {
      "id": "section-label",
      "name": "section-label",
      "title": "SectionLabel",
      "type": "registry:ui",
      "description": "Kleines, mono/uppercase Sektions-Label (z. B. Spaltentitel). Polymorph (h2/h3/div/span/label) und optional mit trailing `actions`-Slot, der das Label in eine Flex-Headerzeile verwandelt.",
      "tags": [
        "typography",
        "layout"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--font-mono",
        "--muted",
        "--space-2"
      ],
      "file": "components/section-label.json"
    },
    {
      "id": "text-field",
      "name": "text-field",
      "title": "TextField",
      "type": "registry:ui",
      "description": "Formularfeld-Wrapper: TextInput, TextArea und SelectInput mit optionalem Label (via SectionLabel) und Hinweistext. Einheitliche Höhe/Padding über die Field-Tokens; SelectInput hat ein eigenes Chevron statt des System-Pfeils.",
      "tags": [
        "form",
        "input"
      ],
      "dependencies": [],
      "registryDependencies": [
        "section-label",
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--bg",
        "--border",
        "--control-height",
        "--ease-standard",
        "--fg",
        "--field-chevron",
        "--field-chevron-offset",
        "--field-chevron-pad",
        "--field-font-size",
        "--field-line-height",
        "--field-pad-x",
        "--field-pad-y",
        "--field-radius",
        "--focus-ring",
        "--font-stage",
        "--motion-fast",
        "--muted",
        "--surface",
        "--text-base",
        "--text-xs"
      ],
      "file": "components/text-field.json"
    },
    {
      "id": "modal",
      "name": "modal",
      "title": "Modal",
      "type": "registry:ui",
      "description": "Portal-basierter Dialog mit Header/Body/Footer-Slots, Größen (sm/md/lg/xl/full), Escape- und Backdrop-Klick zum Schließen. Header kann einen eigenen titleNode (z. B. Tabs) oder headerActions statt des Schließen-X tragen.",
      "tags": [
        "overlay",
        "dialog"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--bg",
        "--border",
        "--elev-overlay",
        "--fg",
        "--font-display",
        "--muted",
        "--radius-lg",
        "--radius-sm",
        "--space-2",
        "--space-3",
        "--space-4",
        "--space-5",
        "--surface",
        "--text-lg",
        "--tracking-display"
      ],
      "file": "components/modal.json"
    },
    {
      "id": "icon",
      "name": "icon",
      "title": "Icon-Set",
      "type": "registry:ui",
      "description": "Einheitlich vorkonfigurierte Wrapper um Tabler-Icons (stroke 1.75, Default-Größe 14) mit sprechenden Namen (SongIcon, BibleIcon, SearchIcon, ChevronDownIcon, SettingsIcon, …). Eine Stelle = überall gleicher Icon-Stil.",
      "tags": [
        "icon",
        "foundation"
      ],
      "dependencies": [
        "@tabler/icons-react"
      ],
      "registryDependencies": [],
      "tokensUsed": [],
      "file": "components/icon.json"
    },
    {
      "id": "dropdown-menu",
      "name": "dropdown-menu",
      "title": "DropdownMenu",
      "type": "registry:ui",
      "description": "Headless Popover-Menü: render-prop-Trigger, Items und Separatoren. Schließt bei Klick-außerhalb und Escape. Ausrichtung (links/rechts), Aufklapprichtung (down/up) und Mindestbreite konfigurierbar.",
      "tags": [
        "overlay",
        "menu"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--bg",
        "--border",
        "--elev-overlay",
        "--fg",
        "--font-body",
        "--muted",
        "--radius-md",
        "--radius-sm",
        "--space-2",
        "--surface",
        "--text-sm"
      ],
      "file": "components/dropdown-menu.json"
    },
    {
      "id": "filter-dropdown",
      "name": "filter-dropdown",
      "title": "FilterDropdown",
      "type": "registry:ui",
      "description": "Einheitlicher Filter-Trigger (Label + optionaler „· N\"-Zähler + Chevron), der ein DropdownMenu-Popover öffnet. Markiert sich aktiv, sobald etwas gewählt ist. Inhalt bestimmt Single- vs. Multi-Select.",
      "tags": [
        "filter",
        "overlay"
      ],
      "dependencies": [],
      "registryDependencies": [
        "dropdown-menu",
        "icon",
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--bg",
        "--border",
        "--control-height",
        "--ease-standard",
        "--fg",
        "--field-radius",
        "--focus-ring",
        "--motion-fast",
        "--muted",
        "--space-2"
      ],
      "file": "components/filter-dropdown.json"
    },
    {
      "id": "multi-select-dropdown",
      "name": "multi-select-dropdown",
      "title": "MultiSelectDropdown",
      "type": "registry:ui",
      "description": "Mehrfach-Checkbox-Filter mit Einschluss-Logik: leere Auswahl = „alles sichtbar\", sonst nur die gewählten Keys. „Alle anzeigen\" oben setzt zurück. MultiSelectList ist die wiederverwendbare Checkbox-Liste ohne Trigger.",
      "tags": [
        "filter",
        "overlay",
        "form"
      ],
      "dependencies": [],
      "registryDependencies": [
        "filter-dropdown",
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--bg",
        "--border",
        "--muted",
        "--radius-sm",
        "--space-2"
      ],
      "file": "components/multi-select-dropdown.json"
    },
    {
      "id": "search-input",
      "name": "search-input",
      "title": "SearchInput",
      "type": "registry:ui",
      "description": "Suchfeld mit führendem Lupen-Icon und type=\"search\". Kontrolliert über value/onChange(string). Einheitliche Control-Höhe.",
      "tags": [
        "form",
        "input",
        "search"
      ],
      "dependencies": [],
      "registryDependencies": [
        "icon",
        "tokens"
      ],
      "tokensUsed": [
        "--bg",
        "--border",
        "--control-height",
        "--fg",
        "--field-font-size",
        "--field-pad-x",
        "--field-radius",
        "--font-mono",
        "--muted",
        "--surface",
        "--text-xs"
      ],
      "file": "components/search-input.json"
    },
    {
      "id": "toast",
      "name": "toast",
      "title": "Toast",
      "type": "registry:ui",
      "description": "Benachrichtigungs-Toast (success/warn/error/info) mit Akzentstreifen, optionalem Auto-Dismiss und Schließen-Button. ToastStack ist der fixierte Container unten rechts. Store-frei: nimmt ein reines ToastModel entgegen.",
      "tags": [
        "feedback",
        "overlay"
      ],
      "dependencies": [],
      "registryDependencies": [
        "icon",
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--border",
        "--danger",
        "--ease-standard",
        "--elev-raised",
        "--fg",
        "--focus-ring",
        "--leading-body",
        "--leading-tight",
        "--motion-fast",
        "--muted",
        "--radius-md",
        "--radius-sm",
        "--space-1",
        "--space-2",
        "--space-3",
        "--space-4",
        "--space-6",
        "--success",
        "--surface",
        "--text-sm",
        "--text-xs",
        "--warn"
      ],
      "file": "components/toast.json"
    },
    {
      "id": "panel-resizer",
      "name": "panel-resizer",
      "title": "PanelResizer",
      "type": "registry:ui",
      "description": "Drag-Griff auf einer vertikalen Spaltengrenze plus useColumnWidth-Hook für persistente, geklemmte Breiten (LocalStorage, CSS-Variable). Magnetisches Einrasten auf die Standardbreite, Doppelklick = Reset.",
      "tags": [
        "layout",
        "interaction"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--ease-standard",
        "--motion-fast"
      ],
      "file": "components/panel-resizer.json"
    },
    {
      "id": "app-window",
      "name": "app-window",
      "title": "AppWindow",
      "type": "registry:ui",
      "description": "Das gemeinsame App-Fenster der Produktfamilie: Kopfzeile · Arbeitsbereich · optionale Statuszeile · optionale Icon-Tab-Leiste. Dazu AppWindowColumns — das 3-Spalten-Raster mit optional resizbaren Seitenspalten (über useColumnWidth) oder festen Breiten, plus opt-in Einspalten-Modus für schmale Fenster. Store-frei: alle Inhalte sind Slots, alle Zustände Props/Callbacks.",
      "tags": [
        "layout",
        "shell",
        "app-window"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens",
        "panel-resizer"
      ],
      "tokensUsed": [
        "--app-window-col-narrow",
        "--app-window-left",
        "--app-window-right",
        "--app-window-tabbar-height",
        "--bg",
        "--border",
        "--surface"
      ],
      "file": "components/app-window.json"
    },
    {
      "id": "app-window-header",
      "name": "app-window-header",
      "title": "AppWindowHeader",
      "type": "registry:ui",
      "description": "Kopfzeile des App-Fensters im Raster auto·1fr·auto: Marke links, Suchschlitz mittig, Status und Konto rechts. Die Slots sind offen — Logo, Badges und Profilmenü sind Inhalte (SearchInput, LicenseBadge, AccountMenu), nicht Aufgabe des Fensters. Mit stackSearchWhenNarrow rutscht die Suche unter 1180px in eine eigene Zeile (opt-in).",
      "tags": [
        "layout",
        "header",
        "app-window"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--app-window-gutter",
        "--app-window-header-height",
        "--space-3",
        "--space-5"
      ],
      "file": "components/app-window-header.json"
    },
    {
      "id": "app-window-tabs",
      "name": "app-window-tabs",
      "title": "AppWindowTabs",
      "type": "registry:ui",
      "description": "Untere Icon-Tab-Leiste für den Bereichswechsel. Generisch über die Id — kennt weder einen Store noch einen Router: `activeId` und `onSelect` verdrahtet die App. Einträge können deaktiviert sein.",
      "tags": [
        "navigation",
        "shell",
        "app-window"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--accent-fg",
        "--ease-standard",
        "--fg",
        "--focus-ring",
        "--font-body",
        "--motion-fast",
        "--muted",
        "--space-1",
        "--space-4"
      ],
      "file": "components/app-window-tabs.json"
    },
    {
      "id": "icon-button",
      "name": "icon-button",
      "title": "IconButton",
      "type": "registry:ui",
      "description": "Quadratischer Icon-Button für Zeilen-Aktionen. Trägt IMMER ein Label — es wird zu aria-label und title, weil nur ein Glyph sichtbar ist. Tonlagen: neutral · accent (primäre Aktion) · danger (destruktiv); `pressed` macht daraus einen Toggle.",
      "tags": [
        "action",
        "icon"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--accent-fg",
        "--accent-soft",
        "--ease-standard",
        "--fg",
        "--focus-ring",
        "--motion-fast",
        "--muted",
        "--radius-sm",
        "--tag-danger-fg",
        "--tag-warn-fg"
      ],
      "file": "components/icon-button.json"
    },
    {
      "id": "section-add-button",
      "name": "section-add-button",
      "title": "SectionAddButton",
      "type": "registry:ui",
      "description": "Dezenter „+ Neu\"-Button für den actions-Slot eines SectionLabel. Gestrichelte Kontur, tritt optisch hinter den Inhalt zurück; Beschriftung über children überschreibbar.",
      "tags": [
        "action",
        "layout"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens",
        "section-label"
      ],
      "tokensUsed": [
        "--border",
        "--ease-standard",
        "--fg",
        "--focus-ring",
        "--font-body",
        "--motion-fast",
        "--muted",
        "--radius-sm"
      ],
      "file": "components/section-add-button.json"
    },
    {
      "id": "license-badge",
      "name": "license-badge",
      "title": "LicenseBadge",
      "type": "registry:ui",
      "description": "Plan-Badge (Gratis/Pro) im Profil-Trigger. `plan` wird injiziert statt aus einem Store gelesen. Steht links schon ein Name, unterdrückt „Pro\" das Badge, um die Dopplung zu vermeiden. Der Tooltip ist überschreibbar — der Default nennt den Beamer-Wasserzeichen-Kontext, der nicht in jeder App gilt.",
      "tags": [
        "license",
        "badge"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--font-mono",
        "--muted"
      ],
      "file": "components/license-badge.json"
    },
    {
      "id": "status-pill",
      "name": "status-pill",
      "title": "StatusPill",
      "type": "registry:ui",
      "description": "Status-Pille der Kopfzeile (Lizenz/CT): Mono-Label mit Ampel-Punkt, als Trigger für ein DropdownMenu. Feste 32px-Höhe wie der AccountMenu-Trigger — beide stehen in derselben Header-Zeile. `StatusDot` gibt es einzeln für die Popover-Überschrift, damit Pille und Detailansicht dieselbe Ampel zeigen.",
      "tags": [
        "status",
        "shell",
        "header"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--bg",
        "--border",
        "--danger",
        "--ease-standard",
        "--fg",
        "--focus-ring",
        "--font-mono",
        "--motion-fast",
        "--muted",
        "--radius-pill",
        "--space-2",
        "--space-3",
        "--success"
      ],
      "file": "components/status-pill.json"
    },
    {
      "id": "account-menu",
      "name": "account-menu",
      "title": "AccountMenu",
      "type": "registry:ui",
      "description": "Konto-Pille oben rechts: Name + Avatar, im Menü Konto-Block („Gerät freigeschaltet für“), Einstellungen, Theme-Zyklus und optionale App-Extras. Die Lizenzstufe zeigt die StatusPill der Kopfzeile — hier gibt es bewusst kein Plan-Badge. Rein präsentational: jede App verdrahtet ihre Stores über Props/Callbacks dahinter (`extraItems` nimmt app-spezifische Einträge auf, z. B. presenters Fernsteuerung).",
      "tags": [
        "account",
        "menu",
        "shell"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens",
        "dropdown-menu",
        "icon",
        "license-badge"
      ],
      "tokensUsed": [
        "--bg",
        "--border",
        "--ease-standard",
        "--fg",
        "--focus-ring",
        "--motion-fast",
        "--muted",
        "--radius-pill",
        "--space-2",
        "--space-3",
        "--text-sm",
        "--text-xs"
      ],
      "file": "components/account-menu.json"
    },
    {
      "id": "settings-shell",
      "name": "settings-shell",
      "title": "SettingsShell",
      "type": "registry:ui",
      "description": "Einstellungen-Rahmen mit linker Tab-Sidebar auf Basis des geteilten Modal. Die Shell weiß nichts über Konto/ChurchTools — Tabs und Panel-Inhalte liefert die App.",
      "tags": [
        "layout",
        "settings",
        "shell"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens",
        "modal"
      ],
      "tokensUsed": [
        "--accent",
        "--bg",
        "--border",
        "--ease-standard",
        "--fg",
        "--focus-ring",
        "--font-body",
        "--motion-fast",
        "--muted",
        "--radius-sm",
        "--space-2",
        "--space-3",
        "--space-6"
      ],
      "file": "components/settings-shell.json"
    },
    {
      "id": "ct-panel-view",
      "name": "ct-panel-view",
      "title": "CTPanelView",
      "type": "registry:ui",
      "description": "Präsentation der ChurchTools-Anbindung: Verbindungszustand, Login-Slot und Sync-Fortschritt. Optik geteilt, Engine injiziert — presenter fährt CT-Client + Dexie-Delta-Sync dahinter, worship Broker-Login + resync.",
      "tags": [
        "churchtools",
        "settings"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens",
        "button"
      ],
      "tokensUsed": [
        "--accent",
        "--border",
        "--muted",
        "--status-error"
      ],
      "file": "components/ct-panel-view.json"
    },
    {
      "id": "attachment-list",
      "name": "attachment-list",
      "title": "AttachmentList",
      "type": "registry:ui",
      "description": "Liste der Anhänge eines Liedes mit Typ-Icon, Gruppen-Trennern und Inline-Umbenennung. Enthält AttachmentIcon (Typ-Glyph je AttachmentKind) und die Sortier-/Beschriftungs-Helfer: Anzeige-Typen zuerst (PDF, Bild, ChordPro, SongBeamer, MusicXML, Link), dann Medien (Audio, YouTube), dann Sonstiges — ein Trenner erscheint nur beim Gruppenwechsel.",
      "tags": [
        "attachments",
        "list"
      ],
      "dependencies": [
        "@repo/data-core"
      ],
      "registryDependencies": [
        "tokens",
        "icon",
        "icon-button"
      ],
      "tokensUsed": [
        "--accent-border",
        "--accent-fg",
        "--accent-soft",
        "--bg",
        "--divider",
        "--fg",
        "--focus-ring",
        "--muted",
        "--radius-sm",
        "--section-bridge",
        "--section-chorus",
        "--section-verse",
        "--space-1",
        "--space-2",
        "--space-3",
        "--surface",
        "--surface-2",
        "--tag-warn-fg",
        "--text-sm",
        "--text-xs"
      ],
      "file": "components/attachment-list.json"
    },
    {
      "id": "empty-hint",
      "name": "empty-hint",
      "title": "EmptyHint",
      "type": "registry:ui",
      "description": "Einheitlicher Leerzustand: zentrierter, gestrichelter Kasten mit gedämpftem Text für leere Listen/Bereiche.",
      "tags": [
        "feedback",
        "layout"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--border",
        "--muted",
        "--radius-sm",
        "--space-3",
        "--text-xs"
      ],
      "file": "components/empty-hint.json"
    },
    {
      "id": "content-header",
      "name": "content-header",
      "title": "ContentHeader",
      "type": "registry:ui",
      "description": "Präsentationale Kopfzeile einer Content-Spalte: Titel (+ optionale Inline-Aktion), optionaler Untertitel und trailing Aktionen.",
      "tags": [
        "layout",
        "header"
      ],
      "dependencies": [],
      "registryDependencies": [
        "tokens"
      ],
      "tokensUsed": [
        "--border",
        "--font-display",
        "--leading-tight",
        "--muted",
        "--space-1",
        "--space-3",
        "--space-6",
        "--surface",
        "--text-base",
        "--text-xs",
        "--tracking-display"
      ],
      "file": "components/content-header.json"
    },
    {
      "id": "file-drop-zone",
      "name": "file-drop-zone",
      "title": "FileDropZone",
      "type": "registry:ui",
      "description": "Wrappt beliebige Children und nimmt darauf abgelegte Dateien entgegen (MIME-Filter via accept). Beim Drag-Over erscheint ein dezenter Overlay-Hinweis. Nutzt den useFileDrop-Hook.",
      "tags": [
        "form",
        "upload",
        "interaction"
      ],
      "dependencies": [],
      "registryDependencies": [
        "use-file-drop",
        "tokens"
      ],
      "tokensUsed": [
        "--accent",
        "--elev-raised",
        "--fg",
        "--radius-md",
        "--space-3",
        "--space-5",
        "--surface"
      ],
      "file": "components/file-drop-zone.json"
    },
    {
      "id": "use-file-drop",
      "name": "use-file-drop",
      "title": "useFileDrop",
      "type": "registry:hook",
      "description": "Drag&Drop-Hook für Datei-Uploads: liefert ein dragOver-Flag und Event-Handler zum Spread auf einen Container. MIME-Filter via accept; verlässt-Container-Erkennung gegen Flackern bei Kind-Elementen.",
      "tags": [
        "hook",
        "upload"
      ],
      "dependencies": [],
      "registryDependencies": [],
      "tokensUsed": [],
      "file": "components/use-file-drop.json"
    }
  ]
}
