@twentyfirstmedia/ui 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":"0.3.0","site":"https://ui.twentyfirst.media","kits":[{"name":"halo","label":"Halo","package":"@twentyfirstmedia/halo","tagline":"Soft, rounded and light-first: big white cards on a warm canvas, five pastel tints and ink as the only accent.","defaultTheme":"system","defaultDensity":"comfortable","tokens":{"color":[{"name":"background","value":{"light":"#f4f3f0","dark":"#0f0f10"},"usage":"Theme: page background behind every panel (shadcn `background`)."},{"name":"foreground","value":{"light":"#141414","dark":"#f2f1ed"},"usage":"Theme: default text and icons (shadcn `foreground`)."},{"name":"card","value":{"light":"#ffffff","dark":"#1a1a1c"},"usage":"Theme: cards, panels, inputs and the workspace sheet (shadcn `card`)."},{"name":"card-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on cards."},{"name":"popover","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: menus, popovers, select lists, command palette, hover cards, dialogs."},{"name":"popover-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on popovers."},{"name":"primary","value":{"light":"#141414","dark":"#f2f1ed"},"usage":"Theme: the brand colour. Primary buttons, checked boxes, switches, selected chips and days, progress, active marks."},{"name":"primary-foreground","value":{"light":"#ffffff","dark":"#141414"},"usage":"Theme: text and icons on `primary`."},{"name":"primary-hover","value":{"light":"{primary}","dark":"{primary}"},"usage":"Theme: `primary` under the pointer."},{"name":"secondary","value":{"light":"#ecebe7","dark":"#232326"},"usage":"Theme: secondary actions. Soft buttons and icon buttons, neutral chips and badges."},{"name":"secondary-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `secondary`."},{"name":"muted","value":{"light":"#ecebe7","dark":"#232326"},"usage":"Theme: quiet fills. Tracks of segmented controls, tabs and progress, search fields, code, placeholders."},{"name":"muted-foreground","value":{"light":"#5f5e5a","dark":"#a9a8a3"},"usage":"Theme: secondary text, captions, metadata."},{"name":"accent","value":{"light":"#ecebe7","dark":"#232326"},"usage":"Theme: highlight under the pointer or keyboard. Menu items, list rows, options, ghost buttons, nav items."},{"name":"accent-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `accent`."},{"name":"destructive","value":{"light":"#8a2b23","dark":"#f2a8a0"},"usage":"Theme: destructive actions and errors. Danger buttons, error text and icons."},{"name":"destructive-foreground","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: text on solid `destructive`."},{"name":"border","value":{"light":"#e5e4df","dark":"#2c2c30"},"usage":"Theme: hairlines and dividers between surfaces."},{"name":"input","value":{"light":"#a8a7a1","dark":"#5a5a60"},"usage":"Theme: borders of fields, checkboxes and radios, and strong lines."},{"name":"ring","value":{"light":"#2f6fe0","dark":"#8fb8ff"},"usage":"Theme: keyboard focus rings."},{"name":"chart-1","value":{"light":"{sky-ink}","dark":"{sky-ink}"},"usage":"Theme: chart series 1 (sky series)."},{"name":"chart-2","value":{"light":"{mint-ink}","dark":"{mint-ink}"},"usage":"Theme: chart series 2 (mint series)."},{"name":"chart-3","value":{"light":"{butter-ink}","dark":"{butter-ink}"},"usage":"Theme: chart series 3 (butter series)."},{"name":"chart-4","value":{"light":"{lilac-ink}","dark":"{lilac-ink}"},"usage":"Theme: chart series 4 (lilac series)."},{"name":"chart-5","value":{"light":"{blush-ink}","dark":"{blush-ink}"},"usage":"Theme: chart series 5 (blush series)."},{"name":"sidebar","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: Sidebar background."},{"name":"sidebar-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: Sidebar text."},{"name":"sidebar-primary","value":{"light":"{primary}","dark":"{primary}"},"usage":"Theme: Sidebar emphasis (current item mark, badges)."},{"name":"sidebar-primary-foreground","value":{"light":"{primary-foreground}","dark":"{primary-foreground}"},"usage":"Theme: text on `sidebar-primary`."},{"name":"sidebar-accent","value":{"light":"{accent}","dark":"{accent}"},"usage":"Theme: Sidebar item under the pointer and the current item."},{"name":"sidebar-accent-foreground","value":{"light":"{accent-foreground}","dark":"{accent-foreground}"},"usage":"Theme: text on `sidebar-accent`."},{"name":"sidebar-border","value":{"light":"{border}","dark":"{border}"},"usage":"Theme: Sidebar edges and dividers."},{"name":"sidebar-ring","value":{"light":"{ring}","dark":"{ring}"},"usage":"Theme: focus rings inside the Sidebar."},{"name":"canvas","value":{"light":"{background}","dark":"{background}"},"usage":"Page background behind every panel and card."},{"name":"surface","value":{"light":"{card}","dark":"{card}"},"usage":"Cards, panels, inputs and the main workspace sheet."},{"name":"surface-sunken","value":{"light":"{muted}","dark":"{muted}"},"usage":"Search fields, segmented-tab tracks, quiet chips and icon-button fills on `surface`."},{"name":"frame","value":{"light":"#121212","dark":"#050505"},"usage":"The dark app frame: top bar, dock and the outer shell around the workspace."},{"name":"on-frame","value":{"light":"#f4f3f0","dark":"#f2f1ed"},"usage":"Text and icons on `frame`."},{"name":"on-frame-muted","value":{"light":"#a3a29d","dark":"#a3a29d"},"usage":"Secondary text and idle nav icons on `frame` (7:1+)."},{"name":"ink","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Headings, numbers and body copy on `surface`, `canvas`, `surface-sunken` and every tint."},{"name":"ink-muted","value":{"light":"{muted-foreground}","dark":"{muted-foreground}"},"usage":"Descriptions, meta lines and labels on `surface` and `canvas` (4.5:1+ in both themes)."},{"name":"ink-subtle","value":{"light":"color-mix(in oklab, {muted-foreground} 91%, {background})","dark":"color-mix(in oklab, {muted-foreground} 84%, {background})"},"usage":"Placeholders, disabled text and the quietest meta; 4.5:1 on surface and surface-sunken in both themes, still clearly lighter than ink-muted."},{"name":"line","value":{"light":"{border}","dark":"{border}"},"usage":"Hairline dividers and table rules; decorative, never the only boundary of a control."},{"name":"line-strong","value":{"light":"{input}","dark":"{input}"},"usage":"Dashed placeholder tiles and input borders (3:1 on `surface`)."},{"name":"sky","value":{"light":"#cfe3fb","dark":"#1e3550"},"usage":"Info tint: stat tiles, event cards, the 'scheduled' state. Text on it is `ink`."},{"name":"sky-ink","value":{"light":"#1f4f8a","dark":"#9cc6f7"},"usage":"Icons and labels on `sky` or as an icon colour on `surface`."},{"name":"mint","value":{"light":"#d6efc3","dark":"#24401a"},"usage":"Success tint: published/live states, positive stat tiles. Text on it is `ink`."},{"name":"mint-ink","value":{"light":"#2f5d16","dark":"#a8db86"},"usage":"Icons and labels on `mint`; success marks on `surface`."},{"name":"lilac","value":{"light":"#e9cdf4","dark":"#3d2547"},"usage":"Highlight tint: the one featured card in a grid, lesson events. Text on it is `ink`."},{"name":"lilac-ink","value":{"light":"#6a2a82","dark":"#dba6ee"},"usage":"Icons and labels on `lilac`."},{"name":"butter","value":{"light":"#fbe7aa","dark":"#4a3a10"},"usage":"Money and attention tint: revenue tiles, task events, pending states. Text on it is `ink`."},{"name":"butter-ink","value":{"light":"#6b4d00","dark":"#f3d374"},"usage":"Icons and labels on `butter`."},{"name":"blush","value":{"light":"#f8d6d2","dark":"#4a2320"},"usage":"Danger and support tint: errors, tickets, destructive confirms. Text on it is `ink`."},{"name":"blush-ink","value":{"light":"#8a2b23","dark":"#f2a8a0"},"usage":"Icons and labels on `blush`; destructive text on `surface`."},{"name":"focus","value":{"light":"{ring}","dark":"{ring}"},"usage":"2px focus ring on every interactive element (3:1+ on `surface`, `canvas` and every tint)."},{"name":"count","value":{"light":"#d6efc3","dark":"#d6efc3"},"usage":"Count pills on nav items and tabs (unread, open approvals)."},{"name":"on-count","value":{"light":"#141414","dark":"#141414"},"usage":"Text on `count`."},{"name":"on-frame-success","value":{"light":"#a8db86","dark":"#a8db86"},"usage":"Success marks on frame: the brand dot, ok lines in logs."},{"name":"on-frame-danger","value":{"light":"#f2a8a0","dark":"#f2a8a0"},"usage":"Error lines in logs and other danger marks on frame."},{"name":"scrim","value":{"light":"rgba(10, 10, 10, 0.32)","dark":"rgba(10, 10, 10, 0.32)"},"usage":"Backdrop behind drawers."},{"name":"scrim-strong","value":{"light":"rgba(10, 10, 10, 0.38)","dark":"rgba(10, 10, 10, 0.38)"},"usage":"Backdrop behind modals and the command palette."},{"name":"glass","value":{"light":"color-mix(in srgb, {card} 72%, transparent)","dark":"color-mix(in srgb, {card} 70%, transparent)"},"usage":"Glass surfaces: panels, cards, docks and map overlays floating over content. Opaque enough that body text stays at 4.5:1 or more over any backdrop."},{"name":"glass-strong","value":{"light":"color-mix(in srgb, {card} 86%, transparent)","dark":"color-mix(in srgb, {card} 86%, transparent)"},"usage":"Glass for text-heavy floating layers: menus, popovers, dialogs, drawers."},{"name":"glass-control","value":{"light":"color-mix(in srgb, {card} 62%, transparent)","dark":"color-mix(in srgb, {foreground} 10%, transparent)"},"usage":"Inputs, secondary buttons and chips on glass (replaces surface inside a glass surface)."},{"name":"glass-well","value":{"light":"color-mix(in srgb, {foreground} 6%, transparent)","dark":"color-mix(in srgb, {foreground} 6%, transparent)"},"usage":"Tab tracks, badges, search fields and nested cards on glass (replaces surface-sunken)."},{"name":"glass-hover","value":{"light":"color-mix(in srgb, {foreground} 8%, transparent)","dark":"color-mix(in srgb, {foreground} 12%, transparent)"},"usage":"Hover and selection highlight on glass (replaces accent)."},{"name":"glass-border","value":{"light":"rgba(255, 255, 255, 0.6)","dark":"rgba(255, 255, 255, 0.1)"},"usage":"The edge of a glass surface."},{"name":"glass-highlight","value":{"light":"rgba(255, 255, 255, 0.95)","dark":"rgba(255, 255, 255, 0.18)"},"usage":"Light catching the top edge of a glass surface."},{"name":"glass-sheen","value":{"light":"rgba(255, 255, 255, 0.5)","dark":"rgba(255, 255, 255, 0.05)"},"usage":"Soft light falling from the top of a glass surface."},{"name":"ambient-base","value":{"light":"{background}","dark":"{background}"},"usage":"Ground of the Ambient background, under the colour fields."},{"name":"ambient-1","value":{"light":"color-mix(in oklab, {sky} 72%, {sky-ink})","dark":"{sky}"},"usage":"First colour field of the Ambient gradient."},{"name":"ambient-2","value":{"light":"color-mix(in oklab, {mint} 72%, {mint-ink})","dark":"{mint}"},"usage":"Second colour field of the Ambient gradient."},{"name":"ambient-3","value":{"light":"color-mix(in oklab, {lilac} 72%, {lilac-ink})","dark":"{lilac}"},"usage":"Third colour field of the Ambient gradient."},{"name":"ambient-4","value":{"light":"color-mix(in oklab, {butter} 72%, {butter-ink})","dark":"{butter}"},"usage":"Fourth colour field of the Ambient gradient."},{"name":"ambient-scrim","value":{"light":"rgba(255, 255, 255, 0.28)","dark":"rgba(10, 10, 10, 0.45)"},"usage":"Softens a photo or video under glass so the page keeps its calm."},{"name":"bg-000","value":{"light":"{canvas}","dark":"{canvas}"},"usage":"Canvas: app background behind panels and grids."},{"name":"bg-100","value":{"light":"{surface}","dark":"{surface}"},"usage":"Surface: cards, panels, cells, dialogs."},{"name":"bg-200","value":{"light":"{surface-sunken}","dark":"{surface-sunken}"},"usage":"Raised or sunken fill: hover, tracks, kbd keys, skeletons."},{"name":"bg-300","value":{"light":"{line}","dark":"{line}"},"usage":"Selected fill: active nav item, pressed segment."},{"name":"fg-100","value":{"light":"{ink}","dark":"{ink}"},"usage":"Primary text and icons."},{"name":"fg-200","value":{"light":"{ink-muted}","dark":"{ink-muted}"},"usage":"Secondary text: descriptions, inactive nav."},{"name":"fg-300","value":{"light":"{ink-muted}","dark":"{ink-muted}"},"usage":"Muted text: meta, timestamps, column headers."},{"name":"fg-400","value":{"light":"{ink-subtle}","dark":"{ink-subtle}"},"usage":"Disabled text and placeholders."},{"name":"line-100","value":{"light":"{line}","dark":"{line}"},"usage":"Faint hairline between rows."},{"name":"line-200","value":{"light":"{line}","dark":"{line}"},"usage":"Structural hairline between cells and around overlays."},{"name":"line-300","value":{"light":"{line-strong}","dark":"{line-strong}"},"usage":"Control borders (3:1)."},{"name":"chart-grid","value":{"light":"{line}","dark":"{line}"},"usage":"Chart gridlines."},{"name":"chart-sky","value":{"light":"{sky}","dark":"{sky-ink}"},"usage":"Chart bars and segments tinted sky: the tint in light, its -ink partner in dark."},{"name":"chart-mint","value":{"light":"{mint}","dark":"{mint-ink}"},"usage":"Chart bars and segments tinted mint: the tint in light, its -ink partner in dark."},{"name":"chart-lilac","value":{"light":"{lilac}","dark":"{lilac-ink}"},"usage":"Chart bars and segments tinted lilac: the tint in light, its -ink partner in dark."},{"name":"chart-butter","value":{"light":"{butter}","dark":"{butter-ink}"},"usage":"Chart bars and segments tinted butter: the tint in light, its -ink partner in dark."},{"name":"chart-blush","value":{"light":"{blush}","dark":"{blush-ink}"},"usage":"Chart bars and segments tinted blush: the tint in light, its -ink partner in dark."},{"name":"ok","value":{"light":"#2f5d16","dark":"#a8db86"},"usage":"Status ok: done, online, valid (dot). Alias of `mint-ink`."},{"name":"ok-bg","value":{"light":"#d6efc3","dark":"#24401a"},"usage":"Fill behind ok. Alias of `mint`."},{"name":"info","value":{"light":"#1f4f8a","dark":"#9cc6f7"},"usage":"Status running: working, syncing (pulsing dot). Alias of `sky-ink`."},{"name":"info-bg","value":{"light":"#cfe3fb","dark":"#1e3550"},"usage":"Fill behind info. Alias of `sky`."},{"name":"warn","value":{"light":"#6b4d00","dark":"#f3d374"},"usage":"Status warn: paused, expiring (triangle). Alias of `butter-ink`."},{"name":"warn-bg","value":{"light":"#fbe7aa","dark":"#4a3a10"},"usage":"Fill behind warn. Alias of `butter`."},{"name":"danger","value":{"light":"{destructive}","dark":"{destructive}"},"usage":"Status danger: failed, offline (square). Alias of `blush-ink`."},{"name":"danger-bg","value":{"light":"#f8d6d2","dark":"#4a2320"},"usage":"Fill behind danger. Alias of `blush`."},{"name":"signal","value":{"light":"#6a2a82","dark":"#dba6ee"},"usage":"Status signal: waits for a human (diamond). The only state that may draw attention unasked. Alias of `lilac-ink`."},{"name":"signal-bg","value":{"light":"#e9cdf4","dark":"#3d2547"},"usage":"Fill behind signal, e.g. the approval header. Alias of `lilac`."},{"name":"idle","value":{"light":"#5f5e5a","dark":"#a9a8a3"},"usage":"Status idle: ready, inactive (ring). Alias of `ink-muted`."},{"name":"diff-add","value":{"light":"#2f5d16","dark":"#a8db86"},"usage":"Added lines. Alias of `mint-ink`."},{"name":"diff-add-bg","value":{"light":"#d6efc3","dark":"#24401a"},"usage":"Fill of added lines. Alias of `mint`."},{"name":"diff-del","value":{"light":"#8a2b23","dark":"#f2a8a0"},"usage":"Removed lines. Alias of `blush-ink`."},{"name":"diff-del-bg","value":{"light":"#f8d6d2","dark":"#4a2320"},"usage":"Fill of removed lines. Alias of `blush`."},{"name":"paper","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Document previews (invoices, PDFs, mails) stay white in both themes."},{"name":"paper-ink","value":{"light":"#141414","dark":"#141414"},"usage":"Text on `paper`."},{"name":"frame-hover","value":{"light":"rgba(255, 255, 255, 0.08)","dark":"rgba(255, 255, 255, 0.08)"},"usage":"Hover fill on the dark frame (top bar, dock, code blocks). Same in both themes."},{"name":"frame-active","value":{"light":"rgba(255, 255, 255, 0.12)","dark":"rgba(255, 255, 255, 0.12)"},"usage":"Active or selected fill on the dark frame. Same in both themes."},{"name":"frame-strong","value":{"light":"rgba(255, 255, 255, 0.18)","dark":"rgba(255, 255, 255, 0.18)"},"usage":"Strong fill or divider on the dark frame. Same in both themes."},{"name":"on-frame-strong","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Highlighted text on the frame (log highlights). Same in both themes."},{"name":"on-frame-warn","value":{"light":"#f3d374","dark":"#f3d374"},"usage":"Warnings and numbers in logs and code on the frame. Same in both themes."},{"name":"on-frame-signal","value":{"light":"#d4b8f0","dark":"#d4b8f0"},"usage":"Keywords in code on the frame. Same in both themes."},{"name":"ink-hover","value":{"light":"rgba(0, 0, 0, 0.06)","dark":"rgba(0, 0, 0, 0.06)"},"usage":"Hover fill on tinted surfaces such as alerts. Same in both themes."},{"name":"hairline","value":{"light":"rgba(0, 0, 0, 0.12)","dark":"rgba(0, 0, 0, 0.12)"},"usage":"Thin outline around swatches and colour wells. Same in both themes."},{"name":"media-scrim","value":{"light":"rgba(10, 10, 10, 0.6)","dark":"rgba(10, 10, 10, 0.6)"},"usage":"Labels and counters on top of photos. Same in both themes."},{"name":"media-ink","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Text and lines on top of photos. Same in both themes."},{"name":"media-shadow","value":{"light":"rgba(0, 0, 0, 0.3)","dark":"rgba(0, 0, 0, 0.3)"},"usage":"Shadow behind lines drawn on photos. Same in both themes."},{"name":"lightbox","value":{"light":"rgba(8, 8, 8, 0.92)","dark":"rgba(8, 8, 8, 0.92)"},"usage":"Full-screen image viewer background. Same in both themes."},{"name":"lightbox-ink","value":{"light":"rgba(255, 255, 255, 0.8)","dark":"rgba(255, 255, 255, 0.8)"},"usage":"Captions in the image viewer. Same in both themes."},{"name":"paper-muted","value":{"light":"#706f6a","dark":"#706f6a"},"usage":"Secondary text on paper (4.5:1 on white)."},{"name":"paper-subtle","value":{"light":"#6b6a65","dark":"#6b6a65"},"usage":"Small print on `paper`. Same in both themes."},{"name":"serp-ink","value":{"light":"#202124","dark":"#202124"},"usage":"Search ad preview: text. Same in both themes."},{"name":"serp-muted","value":{"light":"#4d5156","dark":"#4d5156"},"usage":"Search ad preview: description and URL. Same in both themes."},{"name":"serp-link","value":{"light":"#1a0dab","dark":"#1a0dab"},"usage":"Search ad preview: headline link. Same in both themes."},{"name":"serp-chip","value":{"light":"#f1f3f4","dark":"#f1f3f4"},"usage":"Search ad preview: favicon chip. Same in both themes."}],"shadow":[{"name":"shadow-card","value":{"light":"0 1px 2px rgba(20,20,20,0.04), 0 6px 20px rgba(20,20,20,0.05)","dark":"0 1px 2px rgba(0,0,0,0.4)"},"usage":"Resting cards on `canvas`."},{"name":"shadow-lift","value":{"light":"0 2px 4px rgba(20,20,20,0.06), 0 14px 32px rgba(20,20,20,0.10)","dark":"0 2px 6px rgba(0,0,0,0.5), 0 14px 32px rgba(0,0,0,0.45)"},"usage":"Hovered card, open menu."},{"name":"shadow-float","value":{"light":"0 16px 40px rgba(0,0,0,0.22)","dark":"0 16px 40px rgba(0,0,0,0.6)"},"usage":"The floating dock, toasts, menus and modals."},{"name":"shadow-overlay","value":{"light":"{shadow-float}","dark":"{shadow-float}"},"usage":"Floating layers: menus, popovers, dialogs, toasts."},{"name":"shadow-glass","value":{"light":"0 1px 2px rgba(20, 20, 40, 0.06), 0 12px 40px rgba(20, 20, 40, 0.14)","dark":"0 1px 2px rgba(0, 0, 0, 0.3), 0 16px 48px rgba(0, 0, 0, 0.45)"},"usage":"Shadow under glass surfaces."}],"component":[{"name":"button-radius","value":"{radius-pill}","usage":"Corners of buttons (Button, IconButton). One knob for a theme's button shape."},{"name":"field-radius","value":"{radius-pill}","usage":"Corners of inputs and selects."},{"name":"badge-radius","value":"{radius-pill}","usage":"Corners of badges and chips."},{"name":"panel-bg","value":"{surface}","usage":"Background of a panel: a cell of the hairline grid, a section, a side column."},{"name":"panel-border","value":"transparent","usage":"Border colour of a panel standing on its own; transparent where panels float."},{"name":"panel-radius","value":"{radius-lg}","usage":"Corners of a panel. Pro: square cells of the hairline grid."},{"name":"panel-shadow","value":"{shadow-card}","usage":"Resting shadow of a panel. Pro: none, the hairlines divide."},{"name":"panel-pad","value":"{space-6}","usage":"Inner padding of a panel."},{"name":"panel-gap","value":"{space-4}","usage":"Gap between panels in a grid: the hairline in Pro, air between cards in Halo."},{"name":"glass-filter","value":{"light":"blur(24px) saturate(180%) brightness(1.05)","dark":"blur(24px) saturate(160%) brightness(0.85)"},"usage":"backdrop-filter of glass surfaces: blur, colour lift and a nudge towards the theme's side."},{"name":"glass-tint","value":"62%","usage":"How strongly a tint (StatTile, tinted Card) colours the glass it sits on."}],"space":[{"name":"space-0-5","value":"2px","usage":"Icon-to-badge nudges, hairline offsets."},{"name":"space-1","value":"4px","usage":"Tight pairs (label + value); flush-panel row gap. Micro 2/6 only inside badges and tags."},{"name":"space-2","value":"8px","usage":"Inline gap: buttons in a group, icon-to-label; flush panel inset."},{"name":"space-3","value":"12px","usage":"Gap between stacked lines inside a card."},{"name":"space-4","value":"16px","usage":"Grid gap between cards; compact tile inset; row and cell padding; control padding-x."},{"name":"space-5","value":"20px","usage":"Card inset."},{"name":"space-6","value":"24px","usage":"Panel inset; gap between page sections."},{"name":"space-8","value":"32px","usage":"Workspace sheet padding."},{"name":"space-10","value":"40px","usage":"Outer margin of the app frame on large screens."},{"name":"space-12","value":"48px","usage":"Empty-state padding."},{"name":"space-16","value":"64px","usage":"Login and onboarding vertical rhythm."}],"radius":[{"name":"radius","value":"16px","usage":"Theme: the base corner radius; the scale below derives from it (shadcn `radius`)."},{"name":"radius-sm","value":"calc(var(--radius) * 0.625)","usage":"Inputs inside cards, table row hovers, small tiles."},{"name":"radius-md","value":"var(--radius)","usage":"Stat tiles, event cards, menus."},{"name":"radius-lg","value":"calc(var(--radius) * 1.375)","usage":"Content cards and side panels."},{"name":"radius-xl","value":"calc(var(--radius) * 1.875)","usage":"The workspace sheet and the app frame."},{"name":"radius-pill","value":"999px","usage":"Buttons, chips, badges, tabs, search, icon buttons and the dock."},{"name":"radius-0","value":"0px","usage":"Square corners."},{"name":"radius-1","value":"{radius-sm}","usage":"Small controls and tiles."},{"name":"radius-full","value":"9999px","usage":"Circles: status dots, radios, avatars."}],"motion":[{"name":"dur-fast","value":"120ms","usage":"Press, hover colour changes, chip toggles."},{"name":"dur-base","value":"220ms","usage":"Tab indicator slide, menu open, card lift."},{"name":"dur-enter","value":"520ms","usage":"Entrance of cards, tiles, panels and rows."},{"name":"dur-slow","value":"900ms","usage":"Number count-up, chart bars growing, lines drawing, progress fills."},{"name":"stagger-step","value":"55ms","usage":"Delay added per item in a staggered entrance (cards, rows, bars)."},{"name":"dur-exit","value":"160ms","usage":"Overlays leaving: menus, popovers, dialogs, drawers, toasts."},{"name":"motion-time","value":"1","usage":"Multiplier for choreography delays (when the dock, side panels and chart data come in)."},{"name":"motion-travel","value":"1","usage":"Multiplier for how far things travel when they come in."},{"name":"motion-blur","value":"0px","usage":"Blur things come out of as they rise (expressive Halo only)."},{"name":"ease-out","value":"cubic-bezier(0.22, 1, 0.36, 1)","usage":"Entrances, count-ups and draw-ins: fast start, soft landing."},{"name":"ease-spring","value":"cubic-bezier(0.34, 1.56, 0.64, 1)","usage":"Small playful overshoot: dock, toast, modal, badge pop."},{"name":"ease-in-out","value":"cubic-bezier(0.65, 0, 0.35, 1)","usage":"Things that move between two resting places: tab indicator, switch knob."},{"name":"duration-fast","value":"{dur-fast}","usage":"Hover and press feedback."},{"name":"duration-base","value":"{dur-base}","usage":"Overlays entering."},{"name":"ease-standard","value":"{ease-out}","usage":"Default curve."}],"size":[{"name":"control-sm","value":"32px","usage":"Small buttons and inputs."},{"name":"control-md","value":"40px","usage":"Buttons, inputs, selects and search."},{"name":"control-lg","value":"48px","usage":"Large controls."},{"name":"row-dense","value":"40px","usage":"Row height in compact density."},{"name":"row-comfy","value":"52px","usage":"Row height in comfortable density."},{"name":"control-dense","value":"{control-sm}","usage":"Control height in compact density."},{"name":"control-comfy","value":"{control-md}","usage":"Control height in comfortable density."},{"name":"topbar-h","value":"60px","usage":"Top bar height."},{"name":"sidebar-w","value":"44px","usage":"Side rail width."},{"name":"sidebar-w-collapsed","value":"44px","usage":"Side rail width, collapsed."},{"name":"subnav-w","value":"208px","usage":"SubNav width."},{"name":"side-panel-w","value":"304px","usage":"Side panel width."},{"name":"icon-sm","value":"14px","usage":"Icons in chips, badges and event bars."},{"name":"icon-md","value":"16px","usage":"Default icon size."},{"name":"icon-lg","value":"20px","usage":"Icons in navigation."},{"name":"line-width","value":"1px","usage":"Borders and rules."}]}},{"name":"pro","label":"Pro","package":"@twentyfirstmedia/pro","tagline":"Sharp, dense and dark-first: monochrome cells on a hairline grid, square corners, Geist, and colour only for state.","defaultTheme":"dark","defaultDensity":"compact","tokens":{"color":[{"name":"background","value":{"light":"#f6f6f7","dark":"#09090b"},"usage":"Theme: page background behind every panel (shadcn `background`)."},{"name":"foreground","value":{"light":"#111114","dark":"#ededf0"},"usage":"Theme: default text and icons (shadcn `foreground`)."},{"name":"card","value":{"light":"#ffffff","dark":"#0e0e11"},"usage":"Theme: cards, panels, inputs and the workspace sheet (shadcn `card`)."},{"name":"card-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on cards."},{"name":"popover","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: menus, popovers, select lists, command palette, hover cards, dialogs."},{"name":"popover-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on popovers."},{"name":"primary","value":{"light":"#111114","dark":"#ededf0"},"usage":"Theme: the brand colour. Primary buttons, checked boxes, switches, selected chips and days, progress, active marks."},{"name":"primary-foreground","value":{"light":"#ffffff","dark":"#09090b"},"usage":"Theme: text and icons on `primary`."},{"name":"primary-hover","value":{"light":"color-mix(in oklab, {primary} 70%, #000000)","dark":"color-mix(in oklab, {primary} 70%, #ffffff)"},"usage":"Theme: `primary` under the pointer."},{"name":"secondary","value":{"light":"#f0f0f2","dark":"#16161a"},"usage":"Theme: secondary actions. Soft buttons and icon buttons, neutral chips and badges."},{"name":"secondary-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `secondary`."},{"name":"muted","value":{"light":"#f0f0f2","dark":"#16161a"},"usage":"Theme: quiet fills. Tracks of segmented controls, tabs and progress, search fields, code, placeholders."},{"name":"muted-foreground","value":{"light":"#4f4f58","dark":"#a6a6b0"},"usage":"Theme: secondary text, captions, metadata."},{"name":"accent","value":{"light":"#f0f0f2","dark":"#16161a"},"usage":"Theme: highlight under the pointer or keyboard. Menu items, list rows, options, ghost buttons, nav items."},{"name":"accent-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `accent`."},{"name":"destructive","value":{"light":"#b0301f","dark":"#ef6f61"},"usage":"Theme: destructive actions and errors. Danger buttons, error text and icons."},{"name":"destructive-foreground","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: text on solid `destructive`."},{"name":"border","value":{"light":"#dfdfe3","dark":"#26262c"},"usage":"Theme: hairlines and dividers between surfaces."},{"name":"input","value":{"light":"#8a8a94","dark":"#62626d"},"usage":"Theme: borders of fields, checkboxes and radios, and strong lines."},{"name":"ring","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: keyboard focus rings."},{"name":"chart-1","value":{"light":"{sky-ink}","dark":"{sky-ink}"},"usage":"Theme: chart series 1 (sky series)."},{"name":"chart-2","value":{"light":"{mint-ink}","dark":"{mint-ink}"},"usage":"Theme: chart series 2 (mint series)."},{"name":"chart-3","value":{"light":"{butter-ink}","dark":"{butter-ink}"},"usage":"Theme: chart series 3 (butter series)."},{"name":"chart-4","value":{"light":"{lilac-ink}","dark":"{lilac-ink}"},"usage":"Theme: chart series 4 (lilac series)."},{"name":"chart-5","value":{"light":"{blush-ink}","dark":"{blush-ink}"},"usage":"Theme: chart series 5 (blush series)."},{"name":"sidebar","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: Sidebar background."},{"name":"sidebar-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: Sidebar text."},{"name":"sidebar-primary","value":{"light":"{primary}","dark":"{primary}"},"usage":"Theme: Sidebar emphasis (current item mark, badges)."},{"name":"sidebar-primary-foreground","value":{"light":"{primary-foreground}","dark":"{primary-foreground}"},"usage":"Theme: text on `sidebar-primary`."},{"name":"sidebar-accent","value":{"light":"{accent}","dark":"{accent}"},"usage":"Theme: Sidebar item under the pointer and the current item."},{"name":"sidebar-accent-foreground","value":{"light":"{accent-foreground}","dark":"{accent-foreground}"},"usage":"Theme: text on `sidebar-accent`."},{"name":"sidebar-border","value":{"light":"{border}","dark":"{border}"},"usage":"Theme: Sidebar edges and dividers."},{"name":"sidebar-ring","value":{"light":"{ring}","dark":"{ring}"},"usage":"Theme: focus rings inside the Sidebar."},{"name":"canvas","value":{"light":"{bg-000}","dark":"{bg-000}"},"usage":"Page background behind every panel and card."},{"name":"surface","value":{"light":"{bg-100}","dark":"{bg-100}"},"usage":"Cards, panels, inputs and the main workspace sheet."},{"name":"surface-sunken","value":{"light":"{bg-200}","dark":"{bg-200}"},"usage":"Search fields, segmented-tab tracks, quiet chips and icon-button fills on `surface`."},{"name":"frame","value":{"light":"{bg-100}","dark":"{bg-100}"},"usage":"The dark app frame: top bar, dock and the outer shell around the workspace."},{"name":"on-frame","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Text and icons on `frame`."},{"name":"on-frame-muted","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Secondary text and idle nav icons on `frame` (7:1+)."},{"name":"ink","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Headings, numbers and body copy on `surface`, `canvas`, `surface-sunken` and every tint."},{"name":"ink-muted","value":{"light":"{fg-200}","dark":"{fg-200}"},"usage":"Descriptions, meta lines and labels on `surface` and `canvas` (4.5:1+ in both themes)."},{"name":"ink-subtle","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Placeholders, disabled text and the quietest meta; 4.5:1 on surface and surface-sunken in both themes, still clearly lighter than ink-muted."},{"name":"line","value":{"light":"{line-200}","dark":"{line-200}"},"usage":"Hairline dividers and table rules; decorative, never the only boundary of a control."},{"name":"line-strong","value":{"light":"{line-300}","dark":"{line-300}"},"usage":"Dashed placeholder tiles and input borders (3:1 on `surface`)."},{"name":"sky","value":{"light":"{info-bg}","dark":"{info-bg}"},"usage":"Info tint: stat tiles, event cards, the 'scheduled' state. Text on it is `ink`."},{"name":"sky-ink","value":{"light":"{info}","dark":"{info}"},"usage":"Icons and labels on `sky` or as an icon colour on `surface`."},{"name":"mint","value":{"light":"{ok-bg}","dark":"{ok-bg}"},"usage":"Success tint: published/live states, positive stat tiles. Text on it is `ink`."},{"name":"mint-ink","value":{"light":"{ok}","dark":"{ok}"},"usage":"Icons and labels on `mint`; success marks on `surface`."},{"name":"lilac","value":{"light":"{signal-bg}","dark":"{signal-bg}"},"usage":"Highlight tint: the one featured card in a grid, lesson events. Text on it is `ink`."},{"name":"lilac-ink","value":{"light":"{signal}","dark":"{signal}"},"usage":"Icons and labels on `lilac`."},{"name":"butter","value":{"light":"{warn-bg}","dark":"{warn-bg}"},"usage":"Money and attention tint: revenue tiles, task events, pending states. Text on it is `ink`."},{"name":"butter-ink","value":{"light":"{warn}","dark":"{warn}"},"usage":"Icons and labels on `butter`."},{"name":"blush","value":{"light":"{danger-bg}","dark":"{danger-bg}"},"usage":"Danger and support tint: errors, tickets, destructive confirms. Text on it is `ink`."},{"name":"blush-ink","value":{"light":"{danger}","dark":"{danger}"},"usage":"Icons and labels on `blush`; destructive text on `surface`."},{"name":"focus","value":{"light":"{ring}","dark":"{ring}"},"usage":"Focus ring: 2px solid outline, 1px offset, on every interactive element."},{"name":"count","value":{"light":"{signal}","dark":"{signal}"},"usage":"Count pills on nav items and tabs (unread, open approvals)."},{"name":"on-count","value":{"light":"{bg-000}","dark":"{bg-000}"},"usage":"Text on `count`."},{"name":"on-frame-success","value":{"light":"{ok}","dark":"{ok}"},"usage":"Success marks on frame: the brand dot, ok lines in logs."},{"name":"on-frame-danger","value":{"light":"{danger}","dark":"{danger}"},"usage":"Error lines in logs and other danger marks on frame."},{"name":"scrim","value":{"light":"rgba(17,17,20,0.36)","dark":"rgba(0,0,0,0.62)"},"usage":"Backdrop behind dialogs, sheets and the command palette."},{"name":"scrim-strong","value":{"light":"{scrim}","dark":"{scrim}"},"usage":"Backdrop behind modals and the command palette."},{"name":"glass","value":{"light":"color-mix(in srgb, {card} 74%, transparent)","dark":"color-mix(in srgb, {card} 72%, transparent)"},"usage":"Glass surfaces: panels, cards, docks and map overlays floating over content. Opaque enough that body text stays at 4.5:1 or more over any backdrop."},{"name":"glass-strong","value":{"light":"color-mix(in srgb, {card} 88%, transparent)","dark":"color-mix(in srgb, {card} 88%, transparent)"},"usage":"Glass for text-heavy floating layers: menus, popovers, dialogs, drawers."},{"name":"glass-control","value":{"light":"color-mix(in srgb, {card} 55%, transparent)","dark":"color-mix(in srgb, {foreground} 7%, transparent)"},"usage":"Inputs, secondary buttons and chips on glass (replaces surface inside a glass surface)."},{"name":"glass-well","value":{"light":"color-mix(in srgb, {foreground} 5%, transparent)","dark":"color-mix(in srgb, {foreground} 5%, transparent)"},"usage":"Tab tracks, badges, search fields and nested cards on glass (replaces surface-sunken)."},{"name":"glass-hover","value":{"light":"color-mix(in srgb, {foreground} 7%, transparent)","dark":"color-mix(in srgb, {foreground} 9%, transparent)"},"usage":"Hover and selection highlight on glass (replaces accent)."},{"name":"glass-border","value":{"light":"color-mix(in srgb, {foreground} 12%, transparent)","dark":"color-mix(in srgb, {foreground} 14%, transparent)"},"usage":"The hairline edge of a glass surface."},{"name":"glass-highlight","value":{"light":"rgba(255, 255, 255, 0.7)","dark":"rgba(255, 255, 255, 0.07)"},"usage":"Light catching the top edge of a glass surface."},{"name":"glass-sheen","value":{"light":"transparent","dark":"transparent"},"usage":"Pro glass is flat: no sheen."},{"name":"ambient-base","value":{"light":"{background}","dark":"{background}"},"usage":"Ground of the Ambient background, under the colour fields."},{"name":"ambient-1","value":{"light":"color-mix(in oklab, {chart-1} 22%, {background})","dark":"color-mix(in oklab, {chart-1} 30%, {background})"},"usage":"First colour field of the Ambient gradient (a faint aurora in Pro)."},{"name":"ambient-2","value":{"light":"color-mix(in oklab, {chart-4} 18%, {background})","dark":"color-mix(in oklab, {chart-4} 26%, {background})"},"usage":"Second colour field of the Ambient gradient."},{"name":"ambient-3","value":{"light":"color-mix(in oklab, {chart-2} 16%, {background})","dark":"color-mix(in oklab, {chart-2} 22%, {background})"},"usage":"Third colour field of the Ambient gradient."},{"name":"ambient-4","value":{"light":"color-mix(in oklab, {foreground} 6%, {background})","dark":"color-mix(in oklab, {foreground} 8%, {background})"},"usage":"Fourth colour field of the Ambient gradient."},{"name":"ambient-scrim","value":{"light":"rgba(246, 246, 247, 0.4)","dark":"rgba(9, 9, 11, 0.55)"},"usage":"Softens a photo or video under glass so the page keeps its calm."},{"name":"bg-000","value":{"light":"{background}","dark":"{background}"},"usage":"Canvas: app background, behind hairline grids, inputs on surfaces."},{"name":"bg-100","value":{"light":"{card}","dark":"{card}"},"usage":"Surface: every grid cell, panel, table, sidebar, dialog."},{"name":"bg-200","value":{"light":"{muted}","dark":"{muted}"},"usage":"Raised: row and button hover, kbd keys, skeletons, code blocks."},{"name":"bg-300","value":{"light":"color-mix(in oklab, {foreground} 3%, {muted})","dark":"color-mix(in oklab, {foreground} 5%, {muted})"},"usage":"Selected: active nav item, selected row, pressed segment, open menu trigger."},{"name":"fg-100","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Primary text and icons on every surface (bg-000 to bg-300)."},{"name":"fg-200","value":{"light":"{muted-foreground}","dark":"{muted-foreground}"},"usage":"Secondary text: descriptions, table cells that are not the row's key, inactive nav items. On bg-000 to bg-300."},{"name":"fg-300","value":{"light":"color-mix(in oklab, {muted-foreground} 92%, {background})","dark":"color-mix(in oklab, {muted-foreground} 90%, {background})"},"usage":"Muted text: meta, timestamps, column headers, eyebrows. On bg-000 to bg-300."},{"name":"fg-400","value":{"light":"color-mix(in oklab, {muted-foreground} 47%, {background})","dark":"color-mix(in oklab, {muted-foreground} 57%, {background})"},"usage":"Disabled text and placeholders only. Below 4.5:1 by design; never for information."},{"name":"line-100","value":{"light":"color-mix(in oklab, {border} 59%, {card})","dark":"color-mix(in oklab, {border} 52%, {card})"},"usage":"Faint hairline: row separators inside tables and lists, background grid."},{"name":"line-200","value":{"light":"{border}","dark":"{border}"},"usage":"Hairline: the structural 1px grid between cells, panel and overlay borders, table header rule. Decorative, not a control boundary."},{"name":"line-300","value":{"light":"{input}","dark":"{input}"},"usage":"Control border (3:1 on bg-000, bg-100, bg-200): inputs, checkboxes, secondary buttons, crosshair marks."},{"name":"chart-grid","value":{"light":"{line-100}","dark":"{line-100}"},"usage":"Chart gridlines."},{"name":"chart-sky","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Chart series tinted sky: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-mint","value":{"light":"color-mix(in oklab, {foreground} 38%, {card})","dark":"color-mix(in oklab, {foreground} 48%, {card})"},"usage":"Chart series tinted mint: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-lilac","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Chart series tinted lilac: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-butter","value":{"light":"{fg-200}","dark":"{fg-200}"},"usage":"Chart series tinted butter: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-blush","value":{"light":"{line-300}","dark":"{line-300}"},"usage":"Chart series tinted blush: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"ok","value":{"light":"#177447","dark":"#5cc28f"},"usage":"Success / done / healthy: text and status shapes (circle). On bg-000 to bg-300."},{"name":"ok-bg","value":{"light":"rgba(23,116,71,0.09)","dark":"rgba(92,194,143,0.12)"},"usage":"Tint behind ok badges, banners and diff additions."},{"name":"info","value":{"light":"#2856b0","dark":"#7aa7f5"},"usage":"Running / in progress / neutral notices. Shape: pulsing dot."},{"name":"info-bg","value":{"light":"rgba(40,86,176,0.08)","dark":"rgba(122,167,245,0.12)"},"usage":"Tint behind info badges and banners."},{"name":"warn","value":{"light":"#765600","dark":"#d9b44a"},"usage":"Warning: budget at 80 %, expiring domains, degraded checks. Shape: triangle."},{"name":"warn-bg","value":{"light":"rgba(118,86,0,0.09)","dark":"rgba(217,180,74,0.12)"},"usage":"Tint behind warning badges and banners."},{"name":"danger","value":{"light":"{destructive}","dark":"{destructive}"},"usage":"Error, failed run, destructive actions, budget stop. Shape: square."},{"name":"danger-bg","value":{"light":"rgba(176,48,31,0.08)","dark":"rgba(239,111,97,0.12)"},"usage":"Tint behind error badges, banners, diff removals."},{"name":"signal","value":{"light":"#9a4807","dark":"#f2994a"},"usage":"Needs a human: approval waiting, beacons, the approval count. Shape: diamond. The only hue that may draw the eye unprompted."},{"name":"signal-bg","value":{"light":"rgba(154,72,7,0.09)","dark":"rgba(242,153,74,0.14)"},"usage":"Tint behind approval cards' header and signal badges."},{"name":"idle","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Status idle: ready, inactive (ring). Alias of `ink-muted`."},{"name":"diff-add","value":{"light":"{ok}","dark":"{ok}"},"usage":"Added lines in override diffs."},{"name":"diff-add-bg","value":{"light":"{ok-bg}","dark":"{ok-bg}"},"usage":"Fill of added lines. Alias of `mint`."},{"name":"diff-del","value":{"light":"{danger}","dark":"{danger}"},"usage":"Removed lines in override diffs."},{"name":"diff-del-bg","value":{"light":"{danger-bg}","dark":"{danger-bg}"},"usage":"Fill of removed lines. Alias of `blush`."},{"name":"paper","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Document previews (invoices, PDFs): paper is white in both themes."},{"name":"paper-ink","value":{"light":"#111114","dark":"#111114"},"usage":"Text on paper."},{"name":"frame-hover","value":{"light":"{bg-200}","dark":"{bg-200}"},"usage":"Hover fill on the dark frame (top bar, dock, code blocks). Same in both themes."},{"name":"frame-active","value":{"light":"{bg-300}","dark":"{bg-300}"},"usage":"Active or selected fill on the dark frame. Same in both themes."},{"name":"frame-strong","value":{"light":"{bg-300}","dark":"{bg-300}"},"usage":"Strong fill or divider on the dark frame. Same in both themes."},{"name":"on-frame-strong","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Highlighted text on the frame (log highlights). Same in both themes."},{"name":"on-frame-warn","value":{"light":"{warn}","dark":"{warn}"},"usage":"Warnings and numbers in logs and code on the frame. Same in both themes."},{"name":"on-frame-signal","value":{"light":"{signal}","dark":"{signal}"},"usage":"Keywords in code on the frame. Same in both themes."},{"name":"ink-hover","value":{"light":"{bg-200}","dark":"{bg-200}"},"usage":"Hover fill on tinted surfaces such as alerts. Same in both themes."},{"name":"hairline","value":{"light":"{line-200}","dark":"{line-200}"},"usage":"Thin outline around swatches and colour wells. Same in both themes."},{"name":"media-scrim","value":{"light":"rgba(10, 10, 10, 0.6)","dark":"rgba(10, 10, 10, 0.6)"},"usage":"Labels and counters on top of photos. Same in both themes."},{"name":"media-ink","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Text and lines on top of photos. Same in both themes."},{"name":"media-shadow","value":{"light":"rgba(0, 0, 0, 0.3)","dark":"rgba(0, 0, 0, 0.3)"},"usage":"Shadow behind lines drawn on photos. Same in both themes."},{"name":"lightbox","value":{"light":"rgba(8, 8, 8, 0.92)","dark":"rgba(8, 8, 8, 0.92)"},"usage":"Full-screen image viewer background. Same in both themes."},{"name":"lightbox-ink","value":{"light":"rgba(255, 255, 255, 0.8)","dark":"rgba(255, 255, 255, 0.8)"},"usage":"Captions in the image viewer. Same in both themes."},{"name":"paper-muted","value":{"light":"#706f6a","dark":"#706f6a"},"usage":"Secondary text on paper (4.5:1 on white)."},{"name":"paper-subtle","value":{"light":"#6b6a65","dark":"#6b6a65"},"usage":"Small print on `paper`. Same in both themes."},{"name":"serp-ink","value":{"light":"#202124","dark":"#202124"},"usage":"Search ad preview: text. Same in both themes."},{"name":"serp-muted","value":{"light":"#4d5156","dark":"#4d5156"},"usage":"Search ad preview: description and URL. Same in both themes."},{"name":"serp-link","value":{"light":"#1a0dab","dark":"#1a0dab"},"usage":"Search ad preview: headline link. Same in both themes."},{"name":"serp-chip","value":{"light":"#f1f3f4","dark":"#f1f3f4"},"usage":"Search ad preview: favicon chip. Same in both themes."}],"shadow":[{"name":"shadow-card","value":{"light":"none","dark":"none"},"usage":"Resting cards. Pro: none, hairlines divide."},{"name":"shadow-lift","value":{"light":"none","dark":"none"},"usage":"Hovered card. Pro: none."},{"name":"shadow-float","value":{"light":"0 16px 40px rgba(17,17,20,0.12)","dark":"0 16px 40px rgba(0,0,0,0.55)"},"usage":"Floating layers: dock, toasts, menus and dialogs."},{"name":"shadow-overlay","value":{"light":"0 16px 40px rgba(17,17,20,0.12)","dark":"0 16px 40px rgba(0,0,0,0.55)"},"usage":"Only floating layers: menus, popovers, dialogs, toasts, command palette. Always paired with a line-200 border."},{"name":"shadow-glass","value":{"light":"0 8px 24px rgba(17, 17, 20, 0.1)","dark":"0 12px 32px rgba(0, 0, 0, 0.5)"},"usage":"Shadow under glass surfaces."}],"component":[{"name":"button-radius","value":"{radius-1}","usage":"Corners of buttons (Button, IconButton). One knob for a theme's button shape."},{"name":"field-radius","value":"{radius-1}","usage":"Corners of inputs and selects."},{"name":"badge-radius","value":"{radius-1}","usage":"Corners of badges and chips."},{"name":"panel-bg","value":"{bg-100}","usage":"Background of a panel: a cell of the hairline grid, a section, a side column."},{"name":"panel-border","value":"{line-200}","usage":"Border colour of a panel standing on its own; transparent where panels float."},{"name":"panel-radius","value":"{radius-0}","usage":"Corners of a panel. Pro: square cells of the hairline grid."},{"name":"panel-shadow","value":"none","usage":"Resting shadow of a panel. Pro: none, the hairlines divide."},{"name":"panel-pad","value":"{space-4}","usage":"Inner padding of a panel."},{"name":"panel-gap","value":"1px","usage":"Gap between panels in a grid: the hairline in Pro, air between cards in Halo."},{"name":"glass-filter","value":{"light":"blur(16px) saturate(130%) brightness(1.04)","dark":"blur(16px) saturate(120%) brightness(0.8)"},"usage":"backdrop-filter of glass surfaces: blur, colour lift and a nudge towards the theme's side."},{"name":"glass-tint","value":"40%","usage":"How strongly a tint (StatTile, tinted Card) colours the glass it sits on."}],"space":[{"name":"space-0-5","value":"2px","usage":"Icon-to-badge nudges, hairline offsets."},{"name":"space-1","value":"4px","usage":"Gap inside badges and kbd groups."},{"name":"space-2","value":"8px","usage":"Gap between icon and label, between buttons in a group."},{"name":"space-3","value":"12px","usage":"Compact horizontal cell and control padding."},{"name":"space-4","value":"16px","usage":"Grid cell padding, comfortable control padding, page gutter on mobile."},{"name":"space-5","value":"20px","usage":"Dialog body padding."},{"name":"space-6","value":"24px","usage":"Cell padding on overview grids; background-grid pitch."},{"name":"space-8","value":"32px","usage":"Gap between stacked sections inside a panel."},{"name":"space-10","value":"40px","usage":"Outer margin of the app frame on large screens."},{"name":"space-12","value":"48px","usage":"Empty-state padding."},{"name":"space-16","value":"64px","usage":"Login and onboarding vertical rhythm."}],"radius":[{"name":"radius","value":"2px","usage":"Theme: the base corner radius; Pro is square at 2px, larger values round cards and menus too (shadcn `radius`)."},{"name":"radius-sm","value":"{radius-1}","usage":"Inputs inside cells, row hovers, small tiles."},{"name":"radius-md","value":"calc((var(--radius) - 2px) * 1)","usage":"Stat cells, event cards, menus: square in Pro."},{"name":"radius-lg","value":"calc((var(--radius) - 2px) * 1.375)","usage":"Cards and panels: square cells of the hairline grid."},{"name":"radius-xl","value":"calc((var(--radius) - 2px) * 1.875)","usage":"The workspace sheet and app frame: square in Pro."},{"name":"radius-pill","value":"{radius-1}","usage":"Buttons, chips, badges, tabs and inputs: 2px in Pro."},{"name":"radius-0","value":"0px","usage":"Default for everything structural: cells, panels, tables, dialogs, sheets, cards."},{"name":"radius-1","value":"var(--radius)","usage":"Controls: buttons, inputs, badges, kbd, switches, tooltips."},{"name":"radius-full","value":"9999px","usage":"Only status dots and radio buttons."}],"motion":[{"name":"dur-fast","value":"{duration-fast}","usage":"Press, hover colour changes, chip toggles."},{"name":"dur-base","value":"{duration-base}","usage":"Tab indicator slide, menu open, card lift."},{"name":"dur-enter","value":"{duration-base}","usage":"Entrances: short and quiet in Pro."},{"name":"dur-slow","value":"{duration-base}","usage":"Count-ups, chart draw-ins and progress fills: short in Pro."},{"name":"stagger-step","value":"0ms","usage":"No staggered entrances in Pro."},{"name":"dur-exit","value":"{duration-fast}","usage":"Overlays leaving: menus, popovers, dialogs, drawers, toasts."},{"name":"motion-time","value":"1","usage":"Multiplier for choreography delays (when the dock, side panels and chart data come in)."},{"name":"motion-travel","value":"1","usage":"Multiplier for how far things travel when they come in."},{"name":"motion-blur","value":"0px","usage":"Blur things come out of as they rise (expressive Halo only)."},{"name":"ease-out","value":"{ease-standard}","usage":"Entrances."},{"name":"ease-spring","value":"{ease-standard}","usage":"No overshoot in Pro: the standard curve."},{"name":"ease-in-out","value":"{ease-standard}","usage":"Things moving between two resting places."},{"name":"duration-fast","value":"90ms","usage":"Hover and press feedback."},{"name":"duration-base","value":"140ms","usage":"Menus, popovers, sheets, toasts entering."},{"name":"ease-standard","value":"cubic-bezier(0.2, 0, 0, 1)","usage":"The only easing curve."}],"size":[{"name":"control-sm","value":"24px","usage":"Small buttons in dense toolbars."},{"name":"control-md","value":"{control-dense}","usage":"Default control height (follows density in components)."},{"name":"control-lg","value":"{control-comfy}","usage":"Large controls."},{"name":"row-dense","value":"28px","usage":"Row height for tables, lists and nav items in compact density."},{"name":"row-comfy","value":"36px","usage":"Row height in comfortable density."},{"name":"control-dense","value":"28px","usage":"Button and input height in compact density."},{"name":"control-comfy","value":"34px","usage":"Button and input height in comfortable density."},{"name":"topbar-h","value":"44px","usage":"Top bar height."},{"name":"sidebar-w","value":"232px","usage":"Sidebar width, expanded."},{"name":"sidebar-w-collapsed","value":"52px","usage":"Sidebar width, icon-only."},{"name":"subnav-w","value":"{sidebar-w}","usage":"SubNav width."},{"name":"side-panel-w","value":"320px","usage":"Side panel width."},{"name":"icon-sm","value":"14px","usage":"Icons inside badges and dense table cells."},{"name":"icon-md","value":"16px","usage":"Default Lucide icon size."},{"name":"icon-lg","value":"16px","usage":"Icons in navigation: same as icon-md in Pro."},{"name":"line-width","value":"1px","usage":"Every border and grid line (Pro calls it hairline)."}]}}],"guides":[{"slug":"introduction","title":"Introduction","description":"{{Kit}} is one of the two Twentyfirst UI kits: React components, tokens and complete screens with the same API as {{Other}}.","markdown":"Twentyfirst UI is two React UI kits with one component API.\n\n- **Halo** is soft, rounded and light-first: big white cards on a warm canvas, five pastel tints that carry meaning and ink as the only accent.\n- **Pro** is sharp, dense and dark-first: monochrome cells on a visible hairline grid, square corners, Geist, and colour only for state.\n\nBoth kits have exactly the same components, props, markup, classes and tokens. Only tokens and CSS differ, so an app can load both and switch at runtime with `data-kit`, without changing markup or losing state.\n\n## What is in the box\n\n- {{componentCount}} components on {{componentPages}} pages: foundations, navigation, forms, data, charts, overlays, AI (chat, agents, approvals), commerce, admin, real estate and media.\n- {{patternCount}} patterns: complete screens built only from kit parts.\n- Tokens for colour, type, space, radius, shadow and motion in light and dark, as CSS variables and as a Tailwind v4 preset.\n- A CLI that sets up an app and copies components when you want to own them, and an MCP server so coding agents know every component.\n\n## Principles\n\n- **Mobile first.** Every component works from a 320px screen up: shells collapse, tables scroll, dialogs become sheets and inputs never zoom.\n- **Accessible.** Keyboard paths, focus rings and ARIA follow the WAI-ARIA practices; axe (WCAG 2.1 AA) runs on every example of both kits, light and dark.\n- **Tokens only.** No colour literals in component CSS.\n- **Private.** Fonts are self-hosted, nothing phones home and components make no requests of their own.\n- **English, formatted with Intl.** Built-in text is plain English and overridable through props or `UIProvider messages`; numbers, money, percentages and dates go through `formatNumber`, `formatMoney`, `formatPercent` and `formatDate`, in en-US or the `UIProvider locale` ([Text and locale](/{{kit}}/text/)).\n\n## Quick start\n\n```sh\nnpx @twentyfirstmedia/ui init --kit {{kit}}\n```\n\n```tsx\nimport \"@twentyfirstmedia/{{kit}}/styles.css\";\nimport { UIProvider, Button } from \"@twentyfirstmedia/{{kit}}\";\n\nexport function App() {\n return (\n <UIProvider>\n <Button variant=\"primary\">Save</Button>\n </UIProvider>\n );\n}\n```\n\nNext: [Installation](/{{kit}}/installation/), [CLI](/{{kit}}/cli/), [Theming and tokens](/{{kit}}/theming/), [AI agents and MCP](/{{kit}}/ai/) and [all components](/{{kit}}/components/).\n"},{"slug":"installation","title":"Installation","description":"Install one kit from npm, import its styles once and wrap the app in UIProvider. React 18.3 or 19; works with Next.js, Vite and any React setup.","markdown":"## 1. The kit\n\nThe packages are on npm under the MIT license. Install a kit with the CLI (it also sets up styles, the provider and the Next.js layout) or by hand:\n\n```sh\nnpx @twentyfirstmedia/ui init --kit {{kit}}\n# or\npnpm add @twentyfirstmedia/{{kit}}\n```\n\n## 2. Styles and provider\n\nImport `styles.css` once. It brings the self-hosted fonts, the tokens (light, dark, density) and every component style; no `data-kit` attribute is needed.\n\n```tsx\nimport \"@twentyfirstmedia/{{kit}}/styles.css\";\nimport { UIProvider, Button } from \"@twentyfirstmedia/{{kit}}\";\n\nexport function App() {\n return (\n <UIProvider theme=\"system\">\n <Button variant=\"primary\">Save</Button>\n </UIProvider>\n );\n}\n```\n\n## Tailwind v4\n\nThe kit ships a preset that maps every token to Tailwind and clears the default palette, so utilities can only use kit values.\n\n```css\n@import \"tailwindcss\";\n@import \"@twentyfirstmedia/{{kit}}/tailwind.css\";\n```\n\n## Both kits in one app\n\nImport `@twentyfirstmedia/ui-core/themes.css` instead (both kits, scoped under `[data-kit=\"…\"]`) and switch with `<UIProvider kit=\"pro\">` or the attribute on `<html>`. Markup, props and state stay as they are.\n\n```tsx\nimport \"@twentyfirstmedia/ui-core/themes.css\";\nimport { UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider kit={userPrefersPro ? \"pro\" : \"halo\"}>{children}</UIProvider>;\n```\n\n## Coding agents\n\nGive Claude Code, Cursor or VS Code the kit's docs with the MCP server, see [AI agents and MCP](/{{kit}}/ai/):\n\n```sh\nnpx @twentyfirstmedia/ui mcp init\n```\n"},{"slug":"cli","title":"CLI","description":"npx @twentyfirstmedia/ui sets up a kit, copies components into your app when you want to change them and runs the MCP server for coding agents.","markdown":"## init\n\nInstalls the kit, imports its styles (and the Tailwind preset when the app uses Tailwind), creates `components/ui-root.tsx` with the `UIProvider`, wraps the Next.js layout and sets `data-kit` on `<html>`. Running it again changes nothing.\n\n```sh\nnpx @twentyfirstmedia/ui init --kit {{kit}}\nnpx @twentyfirstmedia/ui init --kit {{kit}} --both # load both kits and switch at runtime\n```\n\n## add\n\nCopies a component, with the private helpers it needs, into `components/ui`. Everything public stays an import from `@twentyfirstmedia/ui-core`, so the copy keeps working in both kits.\n\n```sh\nnpx @twentyfirstmedia/ui add ApprovalCard DataTable\n```\n\n## list and diff\n\n`list` shows every component by group and marks the ones you copied. `diff` compares your copy with the current package source, so updates are easy to merge by hand.\n\n```sh\nnpx @twentyfirstmedia/ui list\nnpx @twentyfirstmedia/ui diff ApprovalCard\n```\n\n## mcp\n\n`mcp` runs the MCP server on stdio; `mcp init` writes the config for Claude Code, Cursor and VS Code and adds the kit rules to `AGENTS.md`. See [AI agents and MCP](/{{kit}}/ai/).\n\n```sh\nnpx @twentyfirstmedia/ui mcp init --kit {{kit}}\nnpx @twentyfirstmedia/ui mcp init --client cursor --no-rules\n```\n"},{"slug":"theming","title":"Theming and tokens","description":"Theme both kits like shadcn/ui: the same variables (primary, secondary, muted, accent, destructive, border, ring, radius, charts, sidebar) restyle every component, light and dark.","markdown":"Both kits use shadcn/ui's theme variables. A theme is any CSS that sets them, so a theme from ui.shadcn.com/themes, tweakcn or your own design system works as it is, and every component follows it: buttons, fields, menus, tables, charts, the Sidebar, in Halo and in Pro, light and dark.\n\n```css\n@import \"@twentyfirstmedia/{{kit}}/styles.css\";\n\n:root {\n --primary: oklch(0.546 0.245 262.881);\n --primary-foreground: oklch(0.985 0 0);\n --ring: oklch(0.546 0.245 262.881);\n --radius: 0.75rem;\n}\n.dark,\n[data-theme=\"dark\"] {\n --primary: oklch(0.707 0.165 254.624);\n --primary-foreground: oklch(0.145 0 0);\n}\n```\n\nThe kits define their variables with zero specificity, so your theme wins in any order. Dark mode follows `data-theme=\"dark\"` or the `.dark` class on `<html>` (next-themes works as it is), and `data-theme=\"system\"` follows the operating system.\n\n## The variables\n\n| Variable | Used for |\n| --- | --- |\n| `--background`, `--foreground` | Page background and default text |\n| `--card`, `--card-foreground` | Cards, panels, fields, the workspace sheet |\n| `--popover`, `--popover-foreground` | Menus, popovers, select lists, command palette, hover cards, dialogs |\n| `--primary`, `--primary-foreground` | Primary buttons, checked boxes and radios, switches, selected chips and days, progress, current marks |\n| `--secondary`, `--secondary-foreground` | Soft buttons and icon buttons |\n| `--muted`, `--muted-foreground` | Quiet fills (tracks, search fields, code) and secondary text |\n| `--accent`, `--accent-foreground` | The highlight under the pointer or keyboard: menu items, rows, options, nav items |\n| `--destructive`, `--destructive-foreground` | Danger buttons, error text and icons |\n| `--border`, `--input`, `--ring` | Hairlines, field and checkbox borders, focus rings |\n| `--radius` | The base corner radius; every corner derives from it |\n| `--chart-1` … `--chart-5` | Chart series (lines, dots, legends) |\n| `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` | The Sidebar |\n\nThe kits' own names (`--canvas`, `--surface`, `--ink`, `--line`, Pro's `--bg-000` … `--fg-400` scale) point at these, and the in-between shades are mixed from them, so you only set the variables above. Outside the theme on purpose: the five tints and status colours (they carry meaning), Halo's dark app frame, paper (e-mail and invoice previews) and media overlays. Halo draws chart bars in soft fills of the series colours (`--chart-sky` … `--chart-blush`); the theme builder and `twentyfirst-ui theme` set them for you when the charts change.\n\n## Presets, CLI and builder\n\nSix brand presets keep each kit's neutrals and change the brand colour: blue, violet, emerald, orange, rose and teal.\n\n```css\n@import \"@twentyfirstmedia/{{kit}}/styles.css\";\n@import \"@twentyfirstmedia/{{kit}}/themes/violet.css\";\n/* both kits loaded: @twentyfirstmedia/ui-core/themes/violet.css */\n```\n\nThe CLI writes a theme into your app and imports it: a preset, a brand colour (light, optionally dark) or a shadcn theme file.\n\n```sh\nnpx @twentyfirstmedia/ui theme violet\nnpx @twentyfirstmedia/ui theme \"#2563eb,#60a5fa\"\nnpx @twentyfirstmedia/ui theme ./shadcn-theme.css\n```\n\nThe [theme builder](/{{kit}}/themes/) shows a theme live on real components and gives you the CSS.\n\n## Themes at runtime\n\nFor a brand per customer or a colour the user picks, `UIProvider` takes the same variables. One set applies in both modes; `{ light, dark }` picks by the current theme.\n\n```tsx\nimport { UIProvider, brandTheme } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider tokens={{ primary: tenant.color, ring: tenant.color }}>{children}</UIProvider>;\n<UIProvider tokens={brandTheme(\"#2563eb\", \"#60a5fa\")}>{children}</UIProvider>;\n```\n\n`themeCss(theme, { kit })` writes the CSS for a theme (for a server-rendered `<style>`), `parseThemeCss(css)` reads a shadcn theme, and `brandTheme(color)` picks readable text for a brand colour.\n\n## Glass\n\nFor UI that floats over a map, a 3D scene, a video or photos, surfaces can be glass: translucent, blurred and colour-lifted over what is behind, with light catching the top edge. Halo's glass is soft and milky, Pro's is dark with a hairline edge.\n\n```tsx\nimport { FloatingLayout, MapView, Panel, SearchField, UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n// a layout for UI over a background: four slots, glass by default\n<FloatingLayout\n height={720}\n background={<MapView background={<YourMap />} points={places} height=\"100%\" />}\n top={<SearchField placeholder=\"Search places\" />}\n start={<Panel title=\"Layers\">…</Panel>}\n end={<Panel title=\"Harbour Museum\">…</Panel>}\n/>;\n\n// one surface\n<Panel surface=\"glass\" title=\"Layers\">…</Panel>;\n\n// everything floating in a subtree: panels, cards, dock, map legend, menus, popovers, dialogs\n<UIProvider scope=\"subtree\" surface=\"glass\">…</UIProvider>;\n```\n\n- Inside a glass region, top-level Panels, Cards, the Dock and map legends and controls turn into glass, and so do loose controls (search field, chips, icon and secondary buttons, segmented controls). Menus, popovers, dialogs and drawers take a stronger fill because they carry more text. A surface nested in a glass surface stays clear instead of stacking glass on glass; `surface=\"solid\"` opts one out.\n- What sits on glass follows it: inputs, tab tracks, badges, hover and selection highlights turn translucent too.\n- The fills keep body text at 4.5:1 or more over any backdrop. Reduced transparency and forced colours switch to solid surfaces, more contrast to the stronger fill, and browsers without backdrop blur get the stronger fill too.\n- The glass tokens are themable like every other colour: `--glass`, `--glass-strong`, `--glass-control`, `--glass-well`, `--glass-hover`, `--glass-border`, `--glass-highlight`, `--glass-sheen`, `--shadow-glass` and `--glass-filter` (the backdrop filter). By default they derive from `--card` and `--foreground`, so a theme carries over.\n- Without React: `class=\"tf-glass\"` on a surface, or `data-surface=\"glass\"` on any container.\n\nSee the [map pattern](/{{kit}}/patterns/MapScreen/) for a full screen.\n\n### Glass dashboards\n\nA whole app can be glass: put `Ambient` behind it and wrap it in `<UIProvider surface=\"glass\">`. The `AppShell` turns into glass too (top bar, rail, sidebar; the sheet clears so the background shows through), and tinted tiles (`StatTile tint=\"sky\"`, tinted Cards) lay their tint over the glass instead of an opaque block.\n\n```tsx\nimport { Ambient, AppShell, StatTile, UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider surface=\"glass\">\n <Ambient fixed />\n <AppShell brand=\"Bloom\">\n <StatTile value=\"7h 42m\" label=\"Sleep\" tint=\"sky\" />\n …\n </AppShell>\n</UIProvider>;\n```\n\n`Ambient` draws soft colour fields from `--ambient-1…4` (pastels in Halo, a faint aurora in Pro) that drift slowly; `colors` takes your own, `src` a photo or (with `video`) a video under a calming scrim, and children your own layer. The drift stops with motion off and pauses off screen. With `fixed`, keep `body` transparent so the background shows. On glass, the faintest text steps up one shade so secondary text stays readable. See the [wellbeing pattern](/{{kit}}/patterns/WellbeingScreen/).\n\n## Tailwind\n\nThe preset maps the variables to shadcn's utility names, so `bg-primary text-primary-foreground`, `bg-muted text-muted-foreground`, `border-border`, `ring-ring` and `bg-sidebar` work, plus the kits' own names (`bg-surface`, `text-ink`).\n\n## Light, dark and density\n\nTheme and density are attributes: `data-theme=\"light|dark|system\"` and `data-density=\"compact|comfortable\"` on `<html>` or any subtree, or the `theme` and `density` props of `UIProvider`. Halo follows the system theme and is comfortable by default; Pro is dark and compact by default.\n\n```tsx\n<UIProvider theme=\"dark\" density=\"compact\">…</UIProvider>\n```\n\n## Layout and breakpoints\n\nThe kits are mobile first: every component works from a 320px wide screen up, and from 1181px on the full desktop layout applies. Phones (up to 760px) get one column: the shell stacks, rail and page nav become scrolling rows, the dock sticks to the bottom, tables scroll inside their box, dialogs open as bottom sheets and popovers stay on screen. Tablets (761–1180px) move asides below the main column. On touch screens text fields use 16px so browsers do not zoom in, and a compact kit (Pro) switches to comfortable hit targets unless `data-density` is set.\n\nFor your own grids, `tf-grid` takes its columns from custom properties: `--cols` on desktop, `--cols-md` on tablets (defaults to `--cols`) and `--cols-sm` on phones (defaults to one column).\n\n```tsx\n<div\n className=\"tf-grid\"\n style={{ \"--cols\": \"repeat(4, minmax(0, 1fr))\", \"--cols-sm\": \"1fr 1fr\" } as React.CSSProperties}\n>\n <StatTile value=\"18\" label=\"Projects\" />\n …\n</div>\n```\n\n## Control sizes\n\nEvery control sits on one height scale: Halo 32 / 40 / 48px (sm / md / lg), Pro 24 / 28 / 34px. Mix sizes only on purpose; a row of controls uses one size.\n"},{"slug":"themes","title":"Theme builder","description":"Build a shadcn-style theme for {{Kit}} live on real components: presets, brand colours, radius and imported shadcn themes, with the CSS to copy.","markdown":"Pick a preset, change any colour or paste a theme from ui.shadcn.com/themes or tweakcn; the preview uses the real components of the kit. Light and dark are edited separately: switch the theme in the header. \"Apply to the whole site\" lets you browse every component page in your theme (in this tab only).\n\n## Use the result\n\nSave the CSS as `theme.css` and import it once, after the kit's styles, or let the CLI write and import it:\n\n```sh\nnpx @twentyfirstmedia/ui theme ./theme.css\nnpx @twentyfirstmedia/ui theme violet\n```\n\nIn React you can also pass a theme at runtime: `<UIProvider tokens={theme}>`. See [Theming and tokens](/{{kit}}/theming/) for every variable.\n"},{"slug":"motion","title":"Motion","description":"Choreographed page entrances, data that draws itself, layers that animate in and out, page transitions and reveal on scroll, with one switch: off, standard or expressive.","markdown":"Both kits move with purpose: the shell settles in, cards rise one after another, charts draw and numbers count up once their card is there, and menus, dialogs, drawers and toasts animate in and out. One switch sets how much:\n\n| Level | Halo | Pro |\n| --- | --- | --- |\n| `standard` (default) | Soft and springy: 520ms entrances, 55ms between cards | Quick and quiet: 140ms, no stagger, no springs |\n| `expressive` | Orchestrated: 760ms entrances, 90ms between cards, more travel, a short focus-in blur | Crisp: 360ms entrances, 45ms between cards, sharp easing, no blur or springs |\n| `off` | Nothing moves; values and layers appear at once | The same |\n\n```tsx\nimport { UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider motion=\"expressive\">{children}</UIProvider>;\n// a setting in your app\n<UIProvider motion={settings.reduceMotion ? \"off\" : \"standard\"}>{children}</UIProvider>;\n```\n\nThe level is an attribute, so it works without React too: `data-motion=\"expressive\"` on `<html>` or any subtree. The reader's reduced-motion setting always wins and turns motion off, whatever the app sets.\n\n## The choreography\n\n- **Shell**: the top bar drops in, side panels slide in, the dock springs up and its buttons pop in turn.\n- **Content**: wrap a page's sections in `Stagger` and they rise one after another. Below the fold a `Stagger` waits until it scrolls into view (`reveal={false}` plays at once).\n- **Data**: bars grow, lines draw, areas and dots fade in, gauges and progress bars fill and `CountUp` counts, each waiting for the card it sits in. New values count from the old one.\n- **Layers**: popovers, menus, tooltips, hover cards, comboboxes, dialogs, drawers, the command palette and toasts animate out as well as in. `Modal`, `Drawer` and `CommandPalette` take an optional `open` prop for that; without it, render them only while open, as before.\n\n```tsx\n<Stagger className=\"tf-grid\" style={{ \"--cols\": \"repeat(3, minmax(0, 1fr))\" } as CSSProperties}>\n <StatTile value=\"1,284\" label=\"Orders\" />\n <Panel title=\"Revenue\">\n <LineChart labels={days} series={series} />\n </Panel>\n …\n</Stagger>\n<Drawer open={open} title=\"Order 1042\" onClose={() => setOpen(false)}>…</Drawer>\n```\n\n## Page transitions\n\n`startViewTransition(update)` runs a route change or a big update as a view transition: the old page fades out and the new one rises in, in the kit's motion. Browsers without view transitions, and motion `off`, get a plain update.\n\n```tsx\nimport { startViewTransition } from \"@twentyfirstmedia/{{kit}}\";\n\nstartViewTransition(() => router.push(\"/orders\"));\n```\n\nThe kits style `::view-transition-old(root)` and `::view-transition-new(root)`, so transitions started another way (Next.js `experimental.viewTransition` with React's `<ViewTransition>`, Astro, plain `document.startViewTransition`) look the same.\n\n## Your own animations\n\n- `useMotion()` returns the level in effect (`\"off\"` while the reader asks for reduced motion).\n- `usePresence(open)` keeps your own layer mounted while it animates out: render while `mounted`, spread `leaving(closing)` on its root (sets `data-closing` and `inert`).\n- The motion tokens drive everything: `--dur-fast`, `--dur-base`, `--dur-enter`, `--dur-slow`, `--dur-exit`, `--stagger-step`, `--ease-out`, `--ease-spring`, `--motion-time` (delays), `--motion-travel` (distances) and `--motion-blur`. Set them like any other token for a motion style of your own.\n\nOnly `transform`, `opacity` and the short expressive blur animate; layout never moves under the reader.\n"},{"slug":"text","title":"Text and locale","description":"Built-in text is English by default; replace any of it with UIProvider messages, and format numbers, money and dates for any locale with UIProvider locale.","markdown":"The kits ship plain English and no translations. Every text a component shows on its own (accessible names, button texts, placeholders, status words, validation messages) can be replaced, and numbers, money, percentages and dates follow a locale you set. Props such as `title`, `label` or `placeholder` on a component still win over both.\n\n```tsx\nimport { UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider\n locale=\"de-DE\"\n messages={{\n close: \"Schließen\",\n send: \"Senden\",\n composerHint: \"Enter zum Senden · Shift+Enter für eine neue Zeile\",\n selectedCount: (n, formatted) => `${formatted ?? n} ausgewählt`,\n agentStatus: { Running: \"Läuft\", Idle: \"Bereit\" },\n }}\n>\n {children}\n</UIProvider>;\n```\n\n## Messages\n\n- `messages` is partial: what you leave out stays English. Nested providers merge on top of the outer one, so a section of the app can change a few strings.\n- Strings with a value in them are functions (`` removeItem: (name) => `${name} entfernen` ``), so the word order is yours. A few take React nodes where the value is highlighted (`typeToConfirm`, `freeShippingIn`).\n- Status words (`agentStatus`, `orderStatus`, `reviewState`, `riskFamily`, `flowStep`) are groups keyed by the status value, which stays English in your data. Groups merge one level deep.\n- `DEFAULT_STRINGS` holds every key with its English text; the `UIStrings` type documents each one. Type your own set as `UIMessages` to get completion and checks.\n- `useUIStrings()` returns the strings that apply where it is called, for your own components.\n- Server components (`StatusScreen`, `AuthCard`) cannot read the provider; their texts are props.\n\nSet `lang` on `<html>` (or the element around a translated part) yourself, so screen readers pronounce the text in the right language.\n\n## Locale\n\n`locale` is a BCP 47 tag (default `en-US`). It applies to every number, amount, percentage and date a component formats, to calendar month and weekday names, and to `CountUp` and `StatTile`, which read string values in that locale: in `de-DE`, `\"13,05 €/m²\"` counts up to 13.05 and keeps its unit.\n\nThe helpers take the locale as their last argument, and `useFormat()` returns them bound to the provider's locale:\n\n```tsx\nimport { formatMoney, useFormat } from \"@twentyfirstmedia/{{kit}}\";\n\nformatMoney(1290, \"EUR\", undefined, \"de-DE\"); // \"1.290 €\"\n\nfunction Price({ value }: { value: number }) {\n const { formatMoney } = useFormat();\n return <b>{formatMoney(value)}</b>;\n}\n```\n\nMoney defaults to euros; pass a currency per call. Calendars start on Monday.\n"},{"slug":"ai","title":"AI agents and MCP","description":"Give Claude Code, Cursor, VS Code and other coding agents the complete kit docs, so they build with real components instead of guessing.","markdown":"The kits are newer than most models' training data, and the exact props matter. Two things fix that: an MCP server that answers from the docs of the exact version you installed, and plain Markdown for every page of this site.\n\n## MCP server\n\nThe CLI includes an MCP server (stdio, no network access, the docs are bundled). One command sets it up for the clients you use and adds the kit rules to `AGENTS.md`:\n\n```sh\nnpx @twentyfirstmedia/ui mcp init --kit {{kit}}\n```\n\nIt writes `.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor) and `.vscode/mcp.json` (VS Code). Pick clients with `--client claude,cursor,vscode` and skip the rules with `--no-rules`.\n\nOr add it by hand. Claude Code:\n\n```sh\nclaude mcp add twentyfirst-ui -- npx -y @twentyfirstmedia/ui mcp --kit {{kit}}\n```\n\nCursor (`.cursor/mcp.json`), Windsurf and Claude Desktop use the same shape:\n\n```json\n{\n \"mcpServers\": {\n \"twentyfirst-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@twentyfirstmedia/ui\", \"mcp\", \"--kit\", \"{{kit}}\"]\n }\n }\n}\n```\n\nVS Code (`.vscode/mcp.json`):\n\n```json\n{\n \"servers\": {\n \"twentyfirst-ui\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@twentyfirstmedia/ui\", \"mcp\", \"--kit\", \"{{kit}}\"]\n }\n }\n}\n```\n\nCodex (`~/.codex/config.toml`):\n\n```toml\n[mcp_servers.twentyfirst-ui]\ncommand = \"npx\"\nargs = [\"-y\", \"@twentyfirstmedia/ui\", \"mcp\", \"--kit\", \"{{kit}}\"]\n```\n\n### Tools\n\n| Tool | What it returns |\n| --- | --- |\n| `search_docs` | Components, patterns, guides, tokens and icons that match a query |\n| `list_components` | Every component page by group with a one-line summary |\n| `get_component` | Description, import line, example code and props of a component |\n| `get_pattern` | A complete screen built from kit parts, with its code |\n| `get_guide` | Installation, CLI, theming, status and approvals, this page |\n| `get_tokens` | Token names with Halo and Pro values, by group |\n| `list_icons` | Every valid icon name |\n| `check_usage` | Checks code: unknown components, props and icons, missing required props, colour literals |\n\nEvery page is also a resource (`twentyfirst-ui://components/ApprovalCard`, `twentyfirst-ui://guides/theming`), and the prompts `build_screen` and `review_ui` start the two most common jobs.\n\n## Rules for agents\n\n`mcp init` adds these to `AGENTS.md` (and `CLAUDE.md` when it exists); you can copy them by hand:\n\n```md\n{{rules}}\n```\n\n## Markdown and llms.txt\n\n- [/llms.txt]({{site}}/llms.txt): what the kits are, the rules, and a link to every page.\n- [/llms-full.txt]({{site}}/llms-full.txt): every guide and component page in one file.\n- Every page has a Markdown twin: append `.md` to its path, e.g. [/{{kit}}/components/ApprovalCard.md]({{site}}/{{kit}}/components/ApprovalCard.md). The \"Markdown\" button at the top of each page copies it.\n"},{"slug":"html","title":"Plain HTML and other frameworks","description":"Every component as a custom element (<tf-button>, <tf-data-table> …) for plain HTML, Vue, Svelte, Angular, Astro and server templates. No React setup needed.","markdown":"`@twentyfirstmedia/elements` turns every component into a custom element with the same props: `Button` is `<tf-button>`, `DataTable` is `<tf-data-table>`, `UIProvider` is `<tf-provider>`. React is bundled inside, so the page needs no build step and no React tooling. The elements use no shadow DOM: the {{Kit}} stylesheet styles them like everywhere else.\n\n## From a CDN\n\nLoad the {{Kit}} CSS and the elements, then write HTML:\n\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/{{kit}}/dist/styles.css\">\n<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/elements/dist/elements.js\"></script>\n\n<tf-panel title=\"Revenue\" subtitle=\"Last 7 days\">\n <tf-button slot=\"action\" variant=\"ghost\" size=\"sm\">Export</tf-button>\n <tf-bar-chart label=\"Revenue per day\"\n data='[{\"label\":\"Mon\",\"value\":32},{\"label\":\"Tue\",\"value\":48},{\"label\":\"Wed\",\"value\":41}]'>\n </tf-bar-chart>\n</tf-panel>\n```\n\nunpkg works the same way (`https://unpkg.com/@twentyfirstmedia/elements/dist/elements.js`). For a classic `<script>` without `type=\"module\"` use `dist/elements.global.js`; it exposes `window.TwentyfirstElements` with `toast` and the `Intl` formatters. Pin a version in production, for example `@twentyfirstmedia/elements@0.1`.\n\nSelf-host when you can: the CDN is the only external request. The components themselves never load anything, and the fonts come with the stylesheet.\n\n## From npm\n\n```sh\nnpm install @twentyfirstmedia/elements @twentyfirstmedia/{{kit}}\n```\n\n```js\nimport \"@twentyfirstmedia/{{kit}}/styles.css\";\nimport { toast } from \"@twentyfirstmedia/elements\"; // defines every tf-* element\n```\n\nThe `@twentyfirstmedia/{{kit}}` package is only needed for its stylesheet; the elements bring their own copy of the components and React.\n\n## Attributes, properties, events and slots\n\n| Component API | As an element |\n| --- | --- |\n| `variant=\"ghost\"`, `size=\"sm\"` | the same attribute |\n| `defaultValue`, `iconRight` | kebab-case: `default-value`, `icon-right` |\n| `disabled`, `selectable` (boolean) | present means true, absent means unset, `\"false\"` means false |\n| `max={8}` (number) | `max=\"8\"` |\n| `items`, `columns`, `rows` (arrays, objects) | a property, `el.items = [...]`, or JSON in the attribute: `items='[\"Day\",\"Week\"]'`; lists of strings also take `items=\"Day, Week\"` |\n| `format`, `renderExpanded`, `onVerify` (functions) | properties only: `el.format = (v) => ...` |\n| `onChange`, `onDecide`, `onClose` | DOM events `change`, `decide`, `close` (the name without `on`, lowercase) |\n| `children` | the element's content: text, HTML and other tf-* elements |\n| `action`, `trace`, `corner` (ReactNode props) | children with `slot=\"action\"`; a DOM node set as a property works too |\n| `actions` (a list of nodes) | every child with `slot=\"actions\"` is one item |\n| `panels` on Tabs (a record of nodes) | children with `slot=\"panels:Overview\"` |\n| `className`, `style` | the `class` and `style` attributes |\n\nEvents bubble, are composed and can be cancelled. `event.detail` holds the callback's argument, or an array when the callback takes several (`decide` gives `[key, text]`). Where a component would pass a React change event, the detail is the control's value (`checked` for switches and checkboxes).\n\n```js\nconst tabs = document.querySelector(\"tf-tabs\");\ntabs.addEventListener(\"change\", (e) => console.log(\"now on\", e.detail));\n\nconst card = document.querySelector(\"tf-approval-card\");\ncard.addEventListener(\"decide\", (e) => {\n const [key, text] = e.detail;\n card.decision = { key, by: \"Mara\", at: \"09:41\", text };\n});\n```\n\nA few rules make plain HTML pleasant:\n\n- **Uncontrolled by default.** Without `value` or `checked`, Tabs, Segmented, Switch and friends keep their own state. Set the attribute to control them: the element then follows the user's input like a native control, unless a listener calls `event.preventDefault()`.\n- **Optional callbacks follow listeners.** Some components only show a part when the callback is set, such as the dismiss button of `<tf-alert>`. The element passes those callbacks while something listens for the event on the element or a tf-* element around it.\n- **Closing hides the element.** Modal, Drawer, CommandPalette and Alert set the `hidden` attribute after `close` (or `dismiss`) unless the event is cancelled. Remove `hidden` to show them again. `<tf-modal>` opens as a dialog over the page: focus moves in and stays there, Escape and the backdrop close it, and focus returns to where it was.\n- **Menus close their popover.** A `<tf-menu>` inside `<tf-popover>` closes it when an item runs or on Escape.\n- **Nested elements share context.** A tf-* element inside another one renders in its parent's React tree, so `<tf-text-field name=\"email\">` inside `<tf-form>` shows the form's validation message.\n- **Layout is unchanged.** Each element is `display: contents`; the component's root is the flex or grid item. The `id` attribute stays on the element, and `title`, `role` and `lang` move into the component.\n\nContent is read when the element first renders; children added later with `append` are picked up as well. Content that is not shown at the moment (a closed popover, a hidden modal) stays in the document, inside the element, so `getElementById` keeps working.\n\n## Toasts and helpers\n\n```html\n<tf-toaster></tf-toaster>\n<script type=\"module\">\n import { toast, formatMoney } from \"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/elements/dist/elements.js\";\n toast({ text: `Paid ${formatMoney(1290)}`, tone: \"success\" });\n</script>\n```\n\n## Kit switching and theming\n\nWith one kit's `styles.css` no attribute is needed. To switch between Halo and Pro at runtime, load `@twentyfirstmedia/ui-core/dist/themes.css` (both kits) and set `data-kit` on `<html>`. Theme and density work the same way in both setups:\n\n```html\n<html data-kit=\"{{kit}}\" data-theme=\"system\">\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/ui-core/dist/themes.css\">\n\n<script type=\"module\">\n document.documentElement.dataset.kit = \"{{other}}\";\n document.documentElement.dataset.theme = \"dark\";\n</script>\n```\n\n`<tf-provider kit=\"{{kit}}\" theme=\"dark\" density=\"compact\">` does the same declaratively; with `scope=\"subtree\"` it applies to its content only. Every colour, radius, shadow and spacing is a CSS variable, so your own CSS can retheme the kit (see [Theming and tokens](/{{kit}}/theming/)):\n\n```css\n:root {\n --primary: var(--lilac-ink);\n --radius-lg: 12px;\n}\n```\n\n## Vue\n\nTell the compiler that `tf-*` tags are custom elements. Vue binds arrays and objects as properties and listens with `@`:\n\n```js\n// vite.config.js\nimport vue from \"@vitejs/plugin-vue\";\nexport default {\n plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith(\"tf-\") } } })],\n};\n```\n\n```html\n<script setup>\nimport \"@twentyfirstmedia/elements\";\nimport { ref } from \"vue\";\nconst tab = ref(\"Overview\");\n</script>\n\n<template>\n <tf-tabs :items=\"['Overview', 'Activity']\" :value=\"tab\" @change=\"tab = $event.detail\" />\n</template>\n```\n\n## Svelte\n\n```html\n<script>\n import \"@twentyfirstmedia/elements\";\n let checked = $state(false);\n</script>\n\n<tf-switch checked={checked} onchange={(e) => (checked = e.detail)}>Email me updates</tf-switch>\n<tf-data-table columns={columns} rows={rows} selectable></tf-data-table>\n```\n\n## Angular\n\nAllow custom elements in the component and bind properties with `[ ]` and events with `( )`:\n\n```ts\nimport { Component, CUSTOM_ELEMENTS_SCHEMA } from \"@angular/core\";\nimport \"@twentyfirstmedia/elements\";\n\n@Component({\n selector: \"app-team\",\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n template: `\n <tf-tabs [items]=\"tabs\" (change)=\"onTab($event)\"></tf-tabs>\n <tf-data-table [columns]=\"columns\" [rows]=\"rows\" selectable></tf-data-table>\n `,\n})\nexport class TeamComponent {\n tabs = [\"Members\", \"Invites\"];\n tab = \"Members\";\n columns = [{ key: \"name\", label: \"Name\" }];\n rows = [{ id: 1, name: \"Ava Vogel\" }];\n onTab(e: Event) {\n this.tab = (e as CustomEvent<string>).detail;\n }\n}\n```\n\n## Astro, Rails, Django and other server templates\n\nRender the tags on the server and load the elements once in the layout. Pass data as JSON in attributes:\n\n```html\n---\n// Astro\nconst items = [\"Overview\", \"Activity\"];\n---\n<tf-tabs items={JSON.stringify(items)}></tf-tabs>\n<script>\n import \"@twentyfirstmedia/elements\";\n</script>\n```\n\n```html\n<!-- Django: escape the JSON for the attribute -->\n<tf-bar-chart label=\"Signups\" data=\"{{ signups_json }}\"></tf-bar-chart>\n```\n\n## Static markup with the kit classes\n\nFor markup that needs no behaviour, the kit classes work without any script: the stylesheet alone is enough.\n\n```html\n<section class=\"tf-panel\">\n <div class=\"tf-row\">\n <button class=\"tf-btn tf-btn-primary\" type=\"button\">Save</button>\n <button class=\"tf-btn tf-btn-ghost\" type=\"button\">Cancel</button>\n <span class=\"tf-badge tf-badge-success\"><span class=\"tf-badge-dot\"></span>Live</span>\n </div>\n</section>\n```\n\nInteractive components (tabs, menus, dialogs, tables) need the elements for their keyboard paths and ARIA.\n\nFor UI over a map, a video or photos, `class=\"tf-glass\"` turns a surface into glass, and `data-surface=\"glass\"` on a container turns everything floating inside into glass (see [Glass](/{{kit}}/theming/#glass)):\n\n```html\n<div data-surface=\"glass\" style=\"position: relative\">\n <canvas id=\"map\"></canvas>\n <section class=\"tf-panel\" style=\"position: absolute; top: 16px; left: 16px\">Layers</section>\n</div>\n```\n\n## Editor support and types\n\nThe package ships a Custom Elements Manifest (`custom-elements.json`) for autocompletion of tags, attributes, events and slots, and VS Code HTML data:\n\n```json\n{ \"html.customData\": [\"./node_modules/@twentyfirstmedia/elements/dist/vscode.html-custom-data.json\"] }\n```\n\nIn TypeScript, `document.querySelector(\"tf-data-table\")` is typed with every property of `DataTable`.\n"},{"slug":"status-and-approvals","title":"Status and approvals","description":"One state model and one way to ask a human, in both kits.","markdown":"State is shape plus colour, so it reads without colour vision: **ok** = dot, **running** = pulsing dot, **signal** = diamond (waits for a human), **warn** = triangle, **danger** = square, **idle** = ring. One status word always maps to the same tone on every page. Only `signal` may draw attention unasked; warnings and errors never animate.\n\nEverything an agent may not do alone goes through `ApprovalCard` with four answers in this order: **A** yes, as shown · **B** yes, with a change · **C** no, discard · **D** different instruction. The card shows the risk family, agent and project, age and escalation time, and the exact payload that will run. A–D work as keys. Once decided it collapses to one line with letter, person and time. `ApprovalInbox` lists open approvals oldest first; J and K move.\n\nSee [StatusIndicator](/{{kit}}/components/StatusIndicator/), [ApprovalCard](/{{kit}}/components/ApprovalCard/) and [ApprovalInbox](/{{kit}}/components/ApprovalInbox/) for the props.\n"},{"slug":"changelog","title":"Changelog","description":"What changed in each release of the Halo and Pro kits, the shared core, the CLI and the custom elements. All five packages are versioned together.","markdown":"## 0.3.0\n\n### Minor Changes\n\n- [#30](https://github.com/twentyfirstmedia/ui/pull/30) [`bef588d`](https://github.com/twentyfirstmedia/ui/commit/bef588d0fb521a5b458113095a7952222efe3c63) Thanks [@robintrepte](https://github.com/robintrepte)! - `Button` and `IconButton` pass native attributes on to their `<button>` (or, with `asChild`, to the child): `id`, `name`, `value`, `form`, `title`, `tabIndex`, `autoFocus`, `aria-label`, `aria-labelledby`, `aria-describedby`, `aria-expanded`, `aria-controls`, `aria-haspopup`, `aria-pressed`, `aria-current` (documented as `NativeButtonProps`), plus any other `<button>` attribute, `data-*`, event handlers and, in React 19, `ref`. `type` also takes `\"reset\"`, and `IconButton` takes `type`. On `IconButton`, `aria-label` or `title` override the ones `label` sets. With `asChild`, `onClick` and the other attributes now reach the child as well.\n \n In `@twentyfirstmedia/elements`, `<tf-button>` and `<tf-icon-button>` take these as attributes; `aria-*` attributes move from the host to the inner button. The docs and the MCP server list them with both components.\n\n- [#28](https://github.com/twentyfirstmedia/ui/pull/28) [`ef1090f`](https://github.com/twentyfirstmedia/ui/commit/ef1090f32204a227ed0ad717e74ea057f6d4da90) Thanks [@robintrepte](https://github.com/robintrepte)! - `FileDrop` hands over the real files.\n \n - New `onFiles(files: File[])` with the `File` objects the person picked or dropped, after `accept` and `multiple`. Added list entries carry their `file` too, so `onChange` gets them as well.\n - Progress only when the app reports it: with `onFiles`, nothing is simulated; set `progress` on controlled `files` to show a bar (now a labelled `progressbar`). An uncontrolled list with `onFiles` shows each file with its size only.\n - `accept` is checked on drop as well; `onReject` receives what did not match. `onRemove` reports removed or cancelled files.\n - `simulateProgress` keeps the old demo animation; it stays the default without `onFiles`, so existing code behaves as before. Running simulations stop on unmount.\n\n- [#31](https://github.com/twentyfirstmedia/ui/pull/31) [`ece54b1`](https://github.com/twentyfirstmedia/ui/commit/ece54b18c3594f595386608e12c93190aa41d5bb) Thanks [@robintrepte](https://github.com/robintrepte)! - `Gallery` and `BeforeAfter` load images lazily and take `loading` and `referrerPolicy`.\n \n - `Gallery`: the main image loads lazily by default (`loading=\"eager\"` for a gallery at the top of the page), thumbnails always lazily, all images decode asynchronously; `referrerPolicy` applies to every image (new type `ImageReferrerPolicy`).\n - `BeforeAfter`: the slider has an `aria-valuetext` (\"Before 40%, After 60%\", with `beforeLabel`/`afterLabel`), overridable with `valueText(percent)`; its accessible name is `label`. Page Up/Down, Home and End move the slider; arrow keys no longer scroll the page while it has focus. Both images take `loading` (default lazy) and `referrerPolicy`.\n\n- [#29](https://github.com/twentyfirstmedia/ui/pull/29) [`4cf034a`](https://github.com/twentyfirstmedia/ui/commit/4cf034af9d2a7eafae5281252e6f7857a74f8540) Thanks [@robintrepte](https://github.com/robintrepte)! - `Pagination` can be controlled: `page` with `onChange(page)` (1-based), or `defaultPage` with an optional `onChange` for an uncontrolled pager. `page` without `onChange` stays the start page, as before. Clicking the current page or past either end no longer reports a change.\n\n- [#33](https://github.com/twentyfirstmedia/ui/pull/33) [`d408a82`](https://github.com/twentyfirstmedia/ui/commit/d408a82cc5ab0acf5401c56118acf36e62516a81) Thanks [@robintrepte](https://github.com/robintrepte)! - Every built-in string is overridable, and numbers follow a locale.\n \n - `UIProvider messages`: replace any text a component shows on its own (accessible names such as \"Close\", \"Send\", \"Previous image\", \"Select row\", button texts, placeholders, status words, validation messages). Partial and nested; props such as `title` or `label` still win. English stays the default and no translations ship. `useUIStrings()`, `DEFAULT_STRINGS`, the `UIStrings` and `UIMessages` types, and one exported group per component file (`MEDIA_STRINGS` …).\n - `UIProvider locale` (BCP 47, default `en-US`): every number, amount, percentage and date in components, calendar month and weekday names. `useFormat()` returns the helpers bound to it; `formatNumber`, `formatMoney`, `formatPercent` and `formatDate` take an optional locale as their last argument.\n - `CountUp` and `StatTile` read string values in the provider locale (\"13,05 €/m²\" in de-DE counts to 13.05 instead of showing \"1,305\"). New helpers `parseFormatted`, `formatLike`, `numberSeparators`.\n - `SearchField` takes `label` (accessible name).\n - Fixed: `VoiceMode` with `stateLabel` showed \"Listening…\".\n\n- [#32](https://github.com/twentyfirstmedia/ui/pull/32) [`3b0992d`](https://github.com/twentyfirstmedia/ui/commit/3b0992dd44ef4f864e96c9735d55a9dbb18b8747) Thanks [@robintrepte](https://github.com/robintrepte)! - `TextField` passes native constraints to its input: `min`, `max`, `step`, `minLength`, `maxLength`, `pattern`, `inputMode`, `readOnly` and `autoFocus` (next to the existing `name`, `required` and `id`), all typed and documented. `PlanCard` takes `disabled` for its button.\n\n## 0.2.1\n\n### Patch Changes\n\n- [#26](https://github.com/twentyfirstmedia/ui/pull/26) [`863179b`](https://github.com/twentyfirstmedia/ui/commit/863179b194fcd8e43bac596039ef555cc294d03e) Thanks [@robintrepte](https://github.com/robintrepte)! - Published through npm Trusted Publishing: the release workflow no longer uses an npm token, and every package carries a provenance statement linking it to the GitHub Actions run that built it. No code changes.\n\n## 0.2.0\n\n### Minor Changes\n\n- [#24](https://github.com/twentyfirstmedia/ui/pull/24) [`4529ab5`](https://github.com/twentyfirstmedia/ui/commit/4529ab565c0de3dadb52b248585f1d4bb0ae0b3c) Thanks [@robintrepte](https://github.com/robintrepte)! - Whole dashboards in glass, in both kits.\n \n - New `Ambient`: a calm, living background of soft colour fields from the kit (pastels in Halo, a faint aurora in Pro) that drift slowly, or a photo or video under a scrim, or your own layer. Still with motion off, paused off screen. New tokens `--ambient-base`, `--ambient-1…4`, `--ambient-scrim`.\n - In a glass region, `AppShell` turns into glass (top bar, rail, sidebar; the sheet clears), and `StatTile` and tinted cards lay their tint over the glass (`--glass-tint`).\n - On glass, the faintest text steps up one shade so secondary text stays readable.\n - `AppShell` applies `className`.\n - New pattern: a wellbeing dashboard in glass.\n\n- [#22](https://github.com/twentyfirstmedia/ui/pull/22) [`beb3043`](https://github.com/twentyfirstmedia/ui/commit/beb30433be7abc8ed1adabf0bae2fc4de526e8f7) Thanks [@robintrepte](https://github.com/robintrepte)! - Glass surfaces for UI that floats over maps, 3D scenes, video or photos, in both kits: translucent, blurred and colour-lifted, soft and milky in Halo, dark with a hairline edge in Pro.\n \n - `surface=\"glass\"` on `Panel`, `Card`, `Dock` and `MapView` (legend, controls, popup), and `<UIProvider surface=\"glass\">` for everything floating in a subtree: top-level surfaces, loose controls, menus, popovers, dialogs and drawers. What sits on glass (inputs, tab tracks, badges, highlights) turns translucent too; `surface=\"solid\"` opts one out. Without React: `class=\"tf-glass\"` or `data-surface=\"glass\"`.\n - New `FloatingLayout`: four slots (`top`, `start`, `end`, `bottom`) over a full-bleed `background`, glass by default; on phones the slots stack and the layout grows with them.\n - New glass tokens in both kits (`--glass`, `--glass-strong`, `--glass-control`, `--glass-well`, `--glass-hover`, `--glass-border`, `--glass-highlight`, `--glass-sheen`, `--shadow-glass`, `--glass-filter`), derived from the theme. Body text on glass keeps 4.5:1 over any backdrop; reduced transparency and forced colours switch to solid surfaces, more contrast to the stronger fill.\n - `MapView` takes `height=\"100%\"` (any CSS length).\n - `Dock` now applies `className` and `style`.\n - A `UIProvider` subtree with its own `tokens` keeps the outer theme, so derived colours follow the tokens even without a `theme` prop.\n - New pattern: a city map with glass controls.\n\n- [#22](https://github.com/twentyfirstmedia/ui/pull/22) [`beb3043`](https://github.com/twentyfirstmedia/ui/commit/beb30433be7abc8ed1adabf0bae2fc4de526e8f7) Thanks [@robintrepte](https://github.com/robintrepte)! - Motion with one switch, in both kits.\n \n - `<UIProvider motion=\"off\" | \"standard\" | \"expressive\">` (or `data-motion`): `expressive` gives orchestrated page entrances (Halo: longer, more travel, a short focus-in blur; Pro: crisp and staggered), `off` stops everything, e.g. for a setting in your app. The reader's reduced-motion setting always turns motion off.\n - Choreography: charts, progress bars and `CountUp` wait for the card they sit in; a `Stagger` below the fold waits until it scrolls into view (`reveal={false}` plays at once).\n - Layers animate out as well as in: popovers, tooltips, hover cards, comboboxes, dialogs, drawers, the command palette and toasts. `Modal`, `Drawer` and `CommandPalette` take an optional `open` for that.\n - `startViewTransition(update)` for page transitions (the kits style `::view-transition`), `useMotion()` for the level in effect, `usePresence(open)` and `leaving()` for your own layers.\n - New motion tokens: `--dur-exit`, `--motion-time`, `--motion-travel`, `--motion-blur`.\n - Fixed: with `themes.css` or `scoped.css`, keyframes defined in another file of the kit never ran (spinners, skeleton shimmer, pulses, pop-ins).\n\n### Patch Changes\n\n- [#25](https://github.com/twentyfirstmedia/ui/pull/25) [`688f141`](https://github.com/twentyfirstmedia/ui/commit/688f141210ed82c5c10b375b38c9d06bbc7a7bf2) Thanks [@robintrepte](https://github.com/robintrepte)! - Glass app shell: it keeps the layout of the solid shell and turns its frame into glass, with the top bar and rail sitting on it as they sit on the solid frame (they no longer float as separate pieces). A `ToggleGroup` is only as wide as its items.\n\n## 0.1.1\n\nNo changes in this release.\n\n## 0.1.0\n\n### Minor Changes\n\n- [#12](https://github.com/twentyfirstmedia/ui/pull/12) [`2cf6196`](https://github.com/twentyfirstmedia/ui/commit/2cf6196cc8e81afa0668ce36e35dfe01501e44ea) Thanks [@robintrepte](https://github.com/robintrepte)! - Consistency pass over every component in both kits and themes. The app shell responds to its own width (container queries); kit boxes no longer depend on the host's box-sizing; controls and code use the kit fonts; bold stays on the type scale; status shapes, list icons, stat values, pagination and invoice numbers line up; segmented controls and tabs no longer stretch or break mid-word. Gauge, Funnel and ProgressRing now fill when reduced motion is on. Pro follows its reference more closely: decorative tints and chart series are monochrome, bars are flat, in-flow cards use hairlines, badges sit on the corner. New: `formatPercent`.\n\n- [#8](https://github.com/twentyfirstmedia/ui/pull/8) [`2db7b83`](https://github.com/twentyfirstmedia/ui/commit/2db7b83266449602d1209c346b44ad62a66c416d) Thanks [@robintrepte](https://github.com/robintrepte)! - First release: every component of the Halo reference in `ui-core`, the Halo and Pro kits with the same components, props and markup (switchable at runtime via `data-kit`), tokens and a Tailwind v4 preset per kit, self-hosted fonts and the CLI (`init`, `add`, `list`, `diff`).\n\n- [#10](https://github.com/twentyfirstmedia/ui/pull/10) [`532f835`](https://github.com/twentyfirstmedia/ui/commit/532f83532d4ce4098733020d34277299c025fa08) Thanks [@robintrepte](https://github.com/robintrepte)! - Mobile first: every component works from 320px up in both kits (desktop from 1181px unchanged). The shell, rail and page nav reflow on phones, tables scroll in their own box, dialogs open as bottom sheets, popovers and tooltips stay on screen, text fields use 16px on touch screens and Pro switches to comfortable hit targets on touch unless `data-density` is set. New: `tf-grid` columns via `--cols` / `--cols-md` / `--cols-sm`, `tf-sheet-nav` / `tf-sheet-aside`, `tf-table-scroll`, `useKeepInView`, `useScrollFocus`, `Tooltip` `defaultOpen`.\n\n- [#16](https://github.com/twentyfirstmedia/ui/pull/16) [`350ca53`](https://github.com/twentyfirstmedia/ui/commit/350ca53ea6b1a4dd77759449ac4b56bb8ed2a62a) Thanks [@robintrepte](https://github.com/robintrepte)! - Open source under the MIT license and published on npmjs.com: install with `npm i @twentyfirstmedia/halo` (or `pro`), no registry setup or token. `twentyfirst-ui init` no longer writes an `.npmrc`.\n\n- [#13](https://github.com/twentyfirstmedia/ui/pull/13) [`559cb2d`](https://github.com/twentyfirstmedia/ui/commit/559cb2df5ac851e71ad0f3808b1442f7f79a6401) Thanks [@robintrepte](https://github.com/robintrepte)! - English only: built-in text is plain English and overridable through props; numbers, money, percentages and dates format with Intl in en-US (`formatNumber`, `formatMoney`, `formatPercent`, `formatDate`).\n \n New components in both kits: Accordion, Separator, AspectRatio, Label, Toggle, ToggleGroup, ButtonGroup, ScrollArea, HoverCard, Carousel, Menubar, NavigationMenu, Sidebar, SelectMenu, Form and ResizablePanels. Menu gains keyboard navigation, typeahead, submenus, checkbox/radio and disabled items; Tabs render panels; Drawer takes `side`; Popover flips when there is no room; CommandPalette takes `loading`, `empty` and `onQueryChange`; Button and IconButton take `asChild`; inputs take `id`, `name` and `required`.\n\n- [#18](https://github.com/twentyfirstmedia/ui/pull/18) [`0f19447`](https://github.com/twentyfirstmedia/ui/commit/0f19447291603a148759d10b7a2bf13ea7e95cb6) Thanks [@robintrepte](https://github.com/robintrepte)! - shadcn-style theming in both kits. The kits use shadcn/ui's theme variables (`background`, `foreground`, `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `destructive`, `border`, `input`, `ring`, `radius`, `chart-1` … `chart-5`, `sidebar-*`) and every component reads them, so a pasted shadcn theme restyles everything, light and dark. The old `--accent`/`--on-accent`/`--accent-hover` are now `--primary`/`--primary-foreground`/`--primary-hover`; `--accent` is the highlight under the pointer, as in shadcn. Token blocks have zero specificity and dark mode also follows the `.dark` class. New: `themeCss`, `parseThemeCss`, `brandTheme`, `THEME_PRESETS` and preset CSS files (`themes/blue.css` …), `UIProvider tokens`, `twentyfirst-ui theme`, Tailwind utilities with shadcn's names.\n\n### Patch Changes\n\n- [#15](https://github.com/twentyfirstmedia/ui/pull/15) [`2c5ddb6`](https://github.com/twentyfirstmedia/ui/commit/2c5ddb6715c7c296a3379ea241158218185e9262) Thanks [@robintrepte](https://github.com/robintrepte)! - The CLI ships an MCP server for coding agents: `twentyfirst-ui mcp` (stdio, offline, the docs are bundled) with the tools `search_docs`, `list_components`, `get_component`, `get_pattern`, `get_guide`, `get_tokens`, `list_icons` and `check_usage`, every page as a resource, and the prompts `build_screen` and `review_ui`. `twentyfirst-ui mcp init` registers it for Claude Code, Cursor and VS Code and adds the kit rules to `AGENTS.md`.\n\n- [#11](https://github.com/twentyfirstmedia/ui/pull/11) [`acec782`](https://github.com/twentyfirstmedia/ui/commit/acec782abd5f1d188f9ee5e7c8eb563e6c450031) Thanks [@robintrepte](https://github.com/robintrepte)! - Icons are centred: 36 icon drawings were off the middle of their 24px grid (up to 1.75 units), visible in round icon buttons and tinted icons. They are now centred on their bounding box; shapes are unchanged, and `play` stays optically centred.\n"}],"components":[{"name":"ActivityFeed","title":"Activity Feed","group":"AI","summary":"What happened and what needs you: ActivityFeed (timeline with status shapes) and DailyBriefing (the morning summary).","description":"What happened and what needs you: `ActivityFeed` (timeline with status shapes) and `DailyBriefing` (the morning summary).\n\nActivityFeed: `groups` ({title, items}) or `items` ({time, status, actor, text, link}).\nDailyBriefing: `title`, `time`, `tint`, `summary`, `attention` ({text, status, onClick}), `suggestions` (strings), `attentionTitle`, `suggestionsTitle`.\n","components":["ActivityFeed","DailyBriefing"],"code":"import * as React from \"react\";\nimport { ActivityFeed, DailyBriefing } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <ActivityFeed\n groups={[\n {\n title: \"Today\",\n items: [\n {\n time: \"09:41\",\n status: \"signal\",\n actor: \"Atlas\",\n text: \"asked to send a reply to Northwind\",\n },\n {\n time: \"09:12\",\n status: \"warn\",\n actor: \"Ads\",\n text: \"reached 82% of the monthly budget\",\n },\n {\n time: \"08:02\",\n status: \"ok\",\n actor: \"Ledger\",\n text: \"matched 31 payments\",\n },\n ],\n },\n {\n title: \"Yesterday\",\n items: [\n {\n time: \"18:02\",\n status: \"danger\",\n actor: \"Echo\",\n text: \"stopped: the docs connection expired\",\n },\n ],\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <DailyBriefing\n title=\"Main Brain\"\n time=\"Today, 08:00\"\n summary=\"26 runs overnight, 22 succeeded. €4.18 spent, well under budget. Two drafts wait for you and the docs connection needs a new token.\"\n attention={[\n {\n text: \"Approve the reply to Northwind\",\n },\n {\n text: \"Reconnect the docs workspace\",\n status: \"danger\",\n },\n ]}\n suggestions={[\n \"Move the weekly report to Monday 09:00\",\n \"Give Ledger read access to the new bank account\",\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/ActivityFeed"},{"name":"AgentCard","title":"Agent Card","group":"AI","summary":"One agent at a glance (AgentCard) and everything about it beside a chat (AgentDetails).","description":"One agent at a glance (`AgentCard`) and everything about it beside a chat (`AgentDetails`).\n\nAgentCard: `name`, `role`, `tint`, `status` + `statusLabel`, `model`, `cost`, `last`, `onClick`, `onChat`.\nAgentDetails: `name`, `role`, `tint`, `status` + `statusLabel`, `sections` ({title, meta, items (KeyValue) or children, open}), `actions`.\n","components":["AgentCard","AgentDetails","Button"],"code":"import * as React from \"react\";\nimport { AgentCard, AgentDetails, Button } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr 300px\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <AgentCard\n name=\"Atlas\"\n role=\"Support triage\"\n tint=\"sky\"\n status=\"running\"\n statusLabel=\"Working\"\n model=\"Claude Sonnet 5.5\"\n cost=\"€1.84\"\n last=\"Drafted a reply to Northwind\"\n onChat={() => {}}\n onClick={() => {}}\n />\n <AgentCard\n name=\"Ledger\"\n role=\"Bookkeeping\"\n tint=\"butter\"\n status=\"signal\"\n statusLabel=\"Waiting for approval\"\n model=\"Claude Haiku 5.5\"\n cost=\"€0.22\"\n last=\"Proposed 3 bookings\"\n />\n <div className=\"tf-panel\">\n <AgentDetails\n name=\"Atlas\"\n role=\"Support triage · Northwind\"\n tint=\"sky\"\n status=\"running\"\n statusLabel=\"Working\"\n sections={[\n {\n title: \"Numbers\",\n items: [\n [\"Model\", \"Claude Sonnet 5.5\"],\n [\"Today\", \"€1.84\", true],\n [\"This month\", \"€38.20\", true],\n [\"Runs\", \"142\"],\n ],\n },\n {\n title: \"Tools\",\n meta: \"3\",\n items: [\n [\"Mailbox\", \"read, draft\"],\n [\"Knowledge base\", \"read\"],\n ],\n },\n {\n title: \"Memory\",\n open: false,\n children: \"Prefers short replies.\",\n },\n ]}\n actions={[\n <Button key=\"c\" variant=\"soft\" icon=\"settings\">\n Configure\n </Button>,\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/AgentCard"},{"name":"Alert","title":"Alert","group":"Overlays","summary":"Inline banner for a state the page is in: a warning, a failure, a note.","description":"Inline banner for a state the page is in: a warning, a failure, a note. Lives in the page, unlike a toast.\n\nProps: `tone` (\"info\" | \"success\" | \"warning\" | \"danger\" | \"highlight\"), `title`, children (one sentence), `icon` (override), `action` (one small Button), `onDismiss`.\n\n- One alert per page section, at the top of what it concerns. Two warnings: merge them.\n- Title says the state, body says the consequence or the next step.\n- Warning and danger are announced to screen readers; info and success are polite.\n- Never use an alert for something that already happened and needs no action: that's a toast.\n","components":["Alert","Button"],"code":"import { Alert, Button } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <Alert tone=\"info\" title=\"Bank feed syncs every 4 hours\" onDismiss={() => {}}>\n New transactions show up here and get matched automatically.\n </Alert>\n <Alert tone=\"success\" title=\"Domain verified\">\n halo.app is ready to send mail.\n </Alert>\n <Alert\n tone=\"warning\"\n title=\"halo.dev expires on May 4, 2026\"\n action={\n <Button size=\"sm\" variant=\"secondary\">\n Turn on auto-renew\n </Button>\n }\n >\n Auto-renew is off. If it lapses, mail on this domain stops.\n </Alert>\n <Alert\n tone=\"danger\"\n title=\"Payment failed\"\n action={\n <Button size=\"sm\" variant=\"secondary\">\n Update card\n </Button>\n }\n >\n We couldn’t charge the card ending 4242. We retry in 3 days.\n </Alert>\n <Alert tone=\"highlight\" title=\"3 agent drafts need your review\" />\n </div>\n );\n}\nexport default App;\n","path":"components/Alert"},{"name":"ApprovalCard","title":"Approval Card","group":"AI","summary":"The human-in-the-loop decision: what an agent wants to do, the exact payload, and the four answers A–D.","description":"The human-in-the-loop decision: what an agent wants to do, the exact payload, and the four answers A–D.\n\nProps: `title` (the action as one sentence), `text` (why), children (the payload, verbatim), `agent`, `project`, `family` (\"money\" | \"infra\" | \"comms\" | \"marketing\" | \"apps\" | \"legal\") or `familyLabel`, `age`, `escalation`, `trace` (node), `note`, `heading`, `onDecide(key, text)`, `decision` ({key, by, at, text}: renders the one-line record), `options` (override A–D; each {key, label, input}), `approveLabel` (relabels A), `compact`. Legacy `onApprove` / `onDeny` / `onEdit` and `state` still work.\n\n- Always four answers in this order: **A** yes, as shown · **B** yes, with a change · **C** no, discard · **D** different instruction. B and D open a text field; ⌘/Ctrl+Enter sends.\n- A–D work as keys while the card has focus.\n- The payload is exactly what will run: the full mail, the amount and recipient, the command, the DNS change before and after. Never a summary.\n- Show the risk family, the age and the escalation time. Nothing runs automatically when time is up.\n- After a decision the card collapses to one line: letter, who, when (and the change or instruction).\n- The A–D wording can be changed through `options`.\n","components":["ApprovalCard"],"code":"import * as React from \"react\";\nimport { ApprovalCard } from \"@twentyfirstmedia/ui-core\";\nimport type { ApprovalDecision } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const d = React.useState<ApprovalDecision | null>(null);\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <ApprovalCard\n agent=\"Atlas\"\n project=\"Support\"\n family=\"comms\"\n age=\"4 min\"\n title=\"Send this email to ida@northwind.io\"\n text=\"Atlas is set to drafts only, so it needs your OK.\"\n escalation=\"Escalates to the owner in 1:52 h\"\n trace={<a href=\"#\">Open trace</a>}\n decision={d[0]}\n onDecide={(k, t) => {\n d[1]({\n key: k,\n by: \"Mara Ellis\",\n at: \"09:41\",\n text: t,\n });\n }}\n >\n <div className=\"tf-approval-mail\">\n {\n \"Hi Ida,\\n\\nhappy to add 40 seats before your renewal on May 1. The quote is attached.\\n\\nBest,\\nMara\"\n }\n </div>\n </ApprovalCard>\n <ApprovalCard\n agent=\"Ledger\"\n project=\"Finance\"\n family=\"money\"\n title=\"Refund €10.00 to the card ending 4242\"\n decision={{\n key: \"C\",\n by: \"Lukas\",\n at: \"Yesterday 18:02\",\n }}\n />\n <ApprovalCard\n agent=\"Scout\"\n project=\"Domains\"\n family=\"infra\"\n title=\"Add TXT record ops-test\"\n decision={{\n key: \"B\",\n by: \"Mara Ellis\",\n at: \"08:10\",\n text: \"Use TTL 300 instead\",\n }}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ApprovalCard"},{"name":"ApprovalInbox","title":"Approval Inbox","group":"AI","summary":"All open approvals across projects, oldest first, next to the selected ApprovalCard.","description":"All open approvals across projects, oldest first, next to the selected ApprovalCard.\n\nProps: `items` ({id, title, agent, project, family, familyLabel, age, ageMin, urgent}), `selected`, `onSelect(id)`, `emptyTitle`, `emptyText`, `label`.\n\n- Oldest first: the one closest to escalation is on top; `urgent` turns its age into a warning.\n- J / K or ↓ / ↑ move the selection; the card next to it takes A–D.\n","components":["ApprovalInbox"],"code":"import * as React from \"react\";\nimport { ApprovalInbox } from \"@twentyfirstmedia/ui-core\";\nimport type { ApprovalInboxItem } from \"@twentyfirstmedia/ui-core\";\n\nconst items: ApprovalInboxItem[] = [\n {\n id: 1,\n title: \"Send reply to Northwind\",\n agent: \"Atlas\",\n project: \"Support\",\n family: \"comms\",\n age: \"2 h 10 min\",\n ageMin: 130,\n urgent: true,\n },\n {\n id: 2,\n title: \"Refund €10.00 to card ending 4242\",\n agent: \"Ledger\",\n project: \"Chatteo\",\n family: \"money\",\n age: \"38 min\",\n ageMin: 38,\n },\n {\n id: 3,\n title: \"Set daily ad budget to €30\",\n agent: \"Ads\",\n project: \"Northwind\",\n family: \"marketing\",\n age: \"12 min\",\n ageMin: 12,\n },\n {\n id: 4,\n title: \"Add TXT record ops-test\",\n agent: \"Scout\",\n project: \"Domains\",\n family: \"infra\",\n age: \"3 min\",\n ageMin: 3,\n },\n];\nfunction App() {\n const s = React.useState<string | number>(1);\n return (\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <ApprovalInbox items={items} selected={s[0]} onSelect={s[1]} />\n </div>\n );\n}\nexport default App;\n","path":"components/ApprovalInbox"},{"name":"AreaChart","title":"Area Chart","group":"Charts","summary":"Smooth stacked areas for volume over time, with a crosshair tooltip.","description":"Smooth stacked areas for volume over time, with a crosshair tooltip.\n\nProps: `labels`, `series` ({name, tint, values}), `stacked` (default true), `height`, `width`, `format(v)`, `legend`, `label`.\n\n- Areas are the light tints, edges the `-ink` lines, so the stack reads in both themes.\n- Use stacked areas when the total matters; switch to LineChart when you compare series against each other.\n- Labels thin out automatically past 12 points.\n","components":["AreaChart"],"code":"import { AreaChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">Visitors by channel</h3>\n <p className=\"tf-panel-sub\">Stacked, per week</p>\n </div>\n <AreaChart\n height={180}\n labels={[\"W14\", \"W15\", \"W16\", \"W17\", \"W18\", \"W19\", \"W20\", \"W21\"]}\n format={(v) => (v / 1000).toFixed(1) + \"k\"}\n series={[\n {\n name: \"Organic\",\n tint: \"sky\",\n values: [4200, 4600, 4400, 5100, 5600, 5300, 6100, 6500],\n },\n {\n name: \"Newsletter\",\n tint: \"butter\",\n values: [1800, 2600, 1900, 2100, 3400, 2200, 2400, 3900],\n },\n {\n name: \"Partners\",\n tint: \"mint\",\n values: [1200, 1300, 1500, 1400, 1600, 1900, 1800, 2100],\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/AreaChart"},{"name":"BackupList","title":"Backup List","group":"Admin","summary":"Admin safety lists: BackupList (snapshots with restore) and BlockList (blocked IPs, emails, accounts).","description":"Admin safety lists: `BackupList` (snapshots with restore) and `BlockList` (blocked IPs, emails, accounts).\n\nBackupList: `items` ({id, name, kind, where, retention, size, status}), `onDownload`, `onRestore`, `footer`. Restore is always confirmed (ConfirmDialog) by the app.\nBlockList: `items` ({id, value, kind, reason, by, until}), `onAdd`, `onRemove`, `addLabel`, `removeLabel`, `searchPlaceholder`, `emptyText`.\n","components":["BackupList","BlockList"],"code":"import * as React from \"react\";\nimport { BackupList, BlockList } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <BackupList\n onDownload={() => {}}\n onRestore={() => {}}\n footer=\"Kept for 30 days · restore test passed Nov 2\"\n items={[\n {\n id: 1,\n name: \"Today 03:00\",\n kind: \"Database\",\n where: \"R2 Frankfurt\",\n size: \"1.84 GB\",\n status: \"ok\",\n },\n {\n id: 2,\n name: \"Yesterday 03:00\",\n kind: \"Database\",\n where: \"R2 Frankfurt\",\n size: \"1.83 GB\",\n status: \"ok\",\n },\n {\n id: 3,\n name: \"Sun 03:00\",\n kind: \"Files\",\n where: \"R2 Frankfurt\",\n size: \"12.4 GB\",\n status: \"warn\",\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <BlockList\n onAdd={() => {}}\n onRemove={() => {}}\n items={[\n {\n id: 1,\n value: \"185.220.101.4\",\n kind: \"IP\",\n reason: \"Card testing\",\n by: \"Fraud rule\",\n until: \"Nov 15\",\n },\n {\n id: 2,\n value: \"spam@mailinator.com\",\n kind: \"Email\",\n reason: \"Chargeback\",\n by: \"Lukas\",\n },\n {\n id: 3,\n value: \"@growthhacker_xx\",\n kind: \"Account\",\n reason: \"Abuse\",\n by: \"Mara Ellis\",\n until: \"Dec 1\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/BackupList"},{"name":"BalanceCard","title":"Balance Card","group":"Admin","summary":"Prepaid balances at providers and the health of the services the shop depends on: BalanceCard and ServiceMonitor.","description":"Prepaid balances at providers and the health of the services the shop depends on: `BalanceCard` and `ServiceMonitor`.\n\nBalanceCard: `name`, `sub`, `logo` or `icon` + `tint`, `balance`, `currency`, `perDay` (burn rate → \"lasts about n days\"), `low` (warning threshold), `spark`, `status` / `statusLabel` (override), `onTopUp`, `topUpLabel`, `action`.\nServiceMonitor: `items` ({id, name, status, statusLabel, latency (ms), errorRate (%), checked, history (statuses, oldest first)}), `label`.\n","components":["BalanceCard","ServiceMonitor"],"code":"import * as React from \"react\";\nimport { BalanceCard, ServiceMonitor } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <BalanceCard\n name=\"Provider A\"\n sub=\"Followers, likes\"\n balance={1840.5}\n perDay={212}\n low={500}\n spark={[2400, 2300, 2150, 2020, 1990, 1910, 1840]}\n onTopUp={() => {}}\n />\n <BalanceCard\n name=\"Provider B\"\n sub=\"Views\"\n balance={310.2}\n perDay={96}\n low={400}\n spark={[900, 780, 640, 520, 470, 380, 310]}\n onTopUp={() => {}}\n />\n <BalanceCard\n name=\"SMS gateway\"\n sub=\"Verification codes\"\n balance={0}\n perDay={4.1}\n low={20}\n icon=\"phone\"\n tint=\"sky\"\n onTopUp={() => {}}\n />\n </div>\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <ServiceMonitor\n items={[\n {\n name: \"Checkout\",\n status: \"ok\",\n statusLabel: \"Operational\",\n latency: 182,\n errorRate: 0.2,\n checked: \"30 s ago\",\n history: [\"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\"],\n },\n {\n name: \"Provider A API\",\n status: \"warn\",\n statusLabel: \"Slow\",\n latency: 2310,\n errorRate: 3.4,\n checked: \"30 s ago\",\n history: [\n \"ok\",\n \"ok\",\n \"ok\",\n \"warn\",\n \"ok\",\n \"ok\",\n \"warn\",\n \"warn\",\n \"ok\",\n \"warn\",\n \"warn\",\n \"warn\",\n ],\n },\n {\n name: \"Mail delivery\",\n status: \"danger\",\n statusLabel: \"Down\",\n latency: null,\n errorRate: 100,\n checked: \"1 min ago\",\n history: [\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"danger\",\n \"danger\",\n \"danger\",\n ],\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/BalanceCard"},{"name":"BarChart","title":"Bar Chart","group":"Charts","summary":"","description":"","components":["BarChart"],"code":"import { BarChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n maxWidth: 620,\n }}\n >\n <BarChart\n data={[\n {\n label: \"Mon\",\n value: 32,\n },\n {\n label: \"Tue\",\n value: 48,\n },\n {\n label: \"Wed\",\n value: 41,\n },\n {\n label: \"Thu\",\n value: 66,\n tint: \"lilac\",\n },\n {\n label: \"Fri\",\n value: 58,\n },\n {\n label: \"Sat\",\n value: 22,\n },\n {\n label: \"Sun\",\n value: 18,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/BarChart"},{"name":"Card","title":"Card","group":"Data","summary":"","description":"","components":["Badge","Button","Card","IconButton","Stagger","TintIcon"],"code":"import * as React from \"react\";\nimport { Badge, Button, Card, IconButton, Stagger, TintIcon } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n >\n <Card\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new workspace admins.\"\n meta={[\"Mara Ellis\", \"12 steps\", \"348 users\"]}\n corner={<TintIcon icon=\"book\" tint=\"mint\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={[\n <IconButton key={1} icon=\"more\" label=\"More\" size=\"sm\" />,\n <IconButton key={2} icon=\"x\" label=\"Archive\" size=\"sm\" />,\n <IconButton key={3} icon=\"check\" label=\"Approve\" size=\"sm\" variant=\"solid\" />,\n ]}\n />\n <Card\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table for annual plans.\"\n corner={<IconButton icon=\"play\" label=\"Preview\" size=\"sm\" variant=\"outline\" />}\n status={<Badge tone=\"neutral\">86 viewing</Badge>}\n actions={[<IconButton key={1} icon=\"edit\" label=\"Edit\" size=\"sm\" variant=\"solid\" />]}\n />\n <Card\n title=\"Invoice #2041 is overdue\"\n description=\"Net-30 passed four days ago; reminder bounced.\"\n meta={[\"Rui Costa\", \"High priority\"]}\n corner={<TintIcon icon=\"mail\" tint=\"blush\" />}\n status={<Badge tone=\"warning\">Open</Badge>}\n actions={[\n <Button key={1} size=\"sm\" variant=\"soft\">\n Reply\n </Button>,\n ]}\n />\n </Stagger>\n );\n}\n","path":"components/Card"},{"name":"ChatLauncher","title":"Chat Launcher","group":"Overlays","summary":"Site-wide chrome for customer-facing apps: ChatLauncher (floating assistant with page context), CookieConsent and AnnouncementBar.","description":"Site-wide chrome for customer-facing apps: `ChatLauncher` (floating assistant with page context), `CookieConsent` and `AnnouncementBar`.\n\nChatLauncher: `title`, `context` (what the assistant is looking at), `tint`, `open` / `onOpenChange`, `unread`, `headerAction`, children (the thread), `composer`, `inline` (for previews; fixed bottom-right by default).\nCookieConsent: `title`, `text`, `policy` (link), `categories` ({id, label, text, required, default}), `onSave(choices)`, `open`, `inline`. \"Accept all\" and \"Only necessary\" carry equal weight.\nAnnouncementBar: children, `tint`, `icon`, `action`, `onDismiss` (false hides the close button).\n","components":["AnnouncementBar","ChatLauncher","ChatMessage","Composer","CookieConsent","PromptStarters"],"code":"import * as React from \"react\";\nimport {\n AnnouncementBar,\n ChatLauncher,\n ChatMessage,\n Composer,\n CookieConsent,\n PromptStarters,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const o = React.useState(true);\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <AnnouncementBar tint=\"butter\" action={<a href=\"#\">See plans</a>}>\n Free plan: 3 stagings left this month.\n </AnnouncementBar>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"end\",\n } as React.CSSProperties\n }\n >\n <CookieConsent\n inline\n policy={<a href=\"#\">Privacy policy</a>}\n categories={[\n {\n id: \"necessary\",\n label: \"Necessary\",\n text: \"Sign-in, security, cart\",\n required: true,\n },\n {\n id: \"stats\",\n label: \"Statistics\",\n text: \"Anonymous usage numbers\",\n },\n {\n id: \"marketing\",\n label: \"Marketing\",\n text: \"Ads and remarketing\",\n },\n ]}\n onSave={() => {}}\n />\n <div\n style={{\n display: \"flex\",\n justifyContent: \"flex-end\",\n }}\n >\n <ChatLauncher\n inline\n open={o[0]}\n onOpenChange={o[1]}\n title=\"Homefield assistant\"\n context=\"Looking at: Loft in HafenCity\"\n composer={<Composer placeholder=\"Ask about this listing…\" />}\n >\n <ChatMessage\n role=\"assistant\"\n name=\"Assistant\"\n text=\"This loft is about **4% above** the estimated value. Want me to draft a first message to the agent?\"\n />\n <PromptStarters\n items={[\n {\n title: \"Draft a message\",\n icon: \"mail\",\n },\n {\n title: \"Compare with similar\",\n icon: \"chart\",\n },\n ]}\n />\n </ChatLauncher>\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/ChatLauncher"},{"name":"ChatMessage","title":"Chat Message","group":"AI","summary":"One turn in a conversation with an agent: user bubble on the right, agent reply as rich text on the left.","description":"One turn in a conversation with an agent: user bubble on the right, agent reply as rich text on the left.\n\nProps: `role` (\"user\" | \"assistant\" | \"system\"), `name`, `time`, `model`, `text` (Markdown subset: paragraphs, **bold**, `code`, - lists, 1. lists, ### headings, links, ``` code blocks), `streaming` (blinking caret, no actions yet), `tint` (agent color), `sources` ({title, icon, url | onClick}), `attachments` ({name, size}), `usage` (tokens · cost text), `onRetry`, `onFeedback(\"up\" | \"down\")`, `actions` (false hides the row), `before` / `after` (slots for tool calls, reasoning, approvals).\n\n- User messages are ink pills, agent replies are plain text on the surface: the agent's words get the space.\n- Actions (copy, regenerate, thumbs) appear once streaming ends.\n- Number sources in the order they were used; keep titles short.\n- `RichText` and `CodeBlock` are exported on their own for docs, notes and logs.\n","components":["ChatMessage"],"code":"import { ChatMessage } from \"@twentyfirstmedia/ui-core\";\n\nconst md =\n 'Three invoices are overdue, **€2,520** in total:\\n\\n- **#2041** Harbor & Co · €860\\n- **#2058** Pinegate · €1,240\\n- **#2063** Orbit Foods · €420\\n\\nThe fix for the reminder job:\\n\\n```ts\\nconst due = invoices.filter(i => i.dueAt < now && !i.paid);\\nawait sendReminders(due, { tone: \"friendly\" });\\n```';\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <ChatMessage\n role=\"user\"\n name=\"Mara Ellis\"\n text=\"Which invoices are overdue? Fix the reminder job too.\"\n attachments={[\n {\n name: \"reminders.ts\",\n size: \"4 KB\",\n },\n ]}\n />\n <ChatMessage\n role=\"assistant\"\n name=\"Ledger\"\n tint=\"butter\"\n time=\"09:41\"\n model=\"Claude Sonnet 5.5\"\n text={md}\n sources={[\n {\n title: \"Invoices · due\",\n icon: \"card\",\n },\n {\n title: \"jobs/reminders.ts\",\n icon: \"code\",\n },\n ]}\n usage=\"1,842 tokens · €0.01\"\n onRetry={() => {}}\n />\n <ChatMessage role=\"system\" text=\"Ledger switched to Claude Haiku 5.5\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Ledger\"\n tint=\"butter\"\n streaming\n text={\"Sending reminders now. Harbor & Co first because\"}\n />\n </div>\n );\n}\n","path":"components/ChatMessage"},{"name":"ChatWorkspace","title":"Chat Workspace","group":"AI","summary":"Messenger-style workspace for agents and people: ChatWorkspace (list | thread | details), ChatList, SearchHits, ThinkingLine, FeedbackRating.","description":"Messenger-style workspace for agents and people: `ChatWorkspace` (list | thread | details), `ChatList`, `SearchHits`, `ThinkingLine`, `FeedbackRating`.\n\nChatWorkspace: `list`, `thread`, `details` (third column, hidden on narrow screens).\nChatList: `groups` ({title, mark (tint), items: {id, name, sub, time, tint, avatar, status, unread, pinned}}), `selected`, `onSelect`, `onSearch`, `filters`, `searchPlaceholder`. Order: pinned first, then one group per project, then people.\nSearchHits: `items` ({id, chat, time, who, text}), `query` (marked), `onOpen`.\nThinkingLine: `text` (\"Reading 3 mails…\"). FeedbackRating: `value` / `onRate`, `onSubmit(value, comment)`, `label`, `placeholder`, `thanks`, `askOnUp`.\n","components":["ChatList","ChatMessage","ChatWorkspace","FeedbackRating","SearchHits","ThinkingLine"],"code":"import * as React from \"react\";\nimport {\n ChatList,\n ChatMessage,\n ChatWorkspace,\n FeedbackRating,\n SearchHits,\n ThinkingLine,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { ChatListProps } from \"@twentyfirstmedia/ui-core\";\n\nconst groups: ChatListProps[\"groups\"] = [\n {\n title: \"Pinned\",\n items: [\n {\n id: \"mb\",\n name: \"Main Brain\",\n sub: \"Morning briefing is ready\",\n time: \"08:00\",\n tint: \"lilac\",\n status: \"ok\",\n pinned: true,\n },\n ],\n },\n {\n title: \"Northwind\",\n mark: \"sky\",\n items: [\n {\n id: \"atlas\",\n name: \"Atlas\",\n sub: \"Waiting for your approval\",\n time: \"09:41\",\n tint: \"sky\",\n status: \"signal\",\n unread: 1,\n },\n {\n id: \"ads\",\n name: \"Ads\",\n sub: \"Budget at 82%\",\n time: \"09:12\",\n tint: \"mint\",\n status: \"warn\",\n },\n ],\n },\n {\n title: \"People\",\n items: [\n {\n id: \"lukas\",\n name: \"Lukas Weber\",\n sub: \"Sounds good\",\n time: \"Mon\",\n avatar: true,\n },\n ],\n },\n];\nfunction App() {\n const s = React.useState(\"atlas\");\n return (\n <div\n style={{\n height: 580,\n }}\n >\n <ChatWorkspace\n className=\"demo-fill\"\n list={<ChatList groups={groups} selected={s[0]} onSelect={s[1]} />}\n thread={\n <div\n className=\"tf-col\"\n style={{\n gap: 20,\n }}\n >\n <ChatMessage role=\"user\" text=\"Draft a reply to Northwind\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Atlas\"\n tint=\"sky\"\n time=\"09:41\"\n text=\"Drafted. It quotes **40 seats at €24** prorated to May 1.\"\n />\n <ThinkingLine text=\"Reading 3 messages…\" />\n <FeedbackRating label=\"Was this useful?\" />\n </div>\n }\n details={\n <SearchHits\n query=\"seats\"\n items={[\n {\n id: 1,\n chat: \"Atlas\",\n time: \"09:41\",\n who: \"Atlas\",\n text: \"It quotes 40 seats at €24 prorated to May 1.\",\n },\n {\n id: 2,\n chat: \"Main Brain\",\n time: \"Mon\",\n text: \"Northwind asked about seats twice this quarter.\",\n },\n ]}\n />\n }\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ChatWorkspace"},{"name":"Combobox","title":"Combobox","group":"Forms","summary":"Searchable select for long lists, single or multiple, with optional create.","description":"Searchable select for long lists, single or multiple, with optional create. `TagInput` is the free-text sibling for tags.\n\nCombobox props: `label`, `options` (strings or {value, label, hint, icon, avatar}), `value`/`defaultValue`, `onChange`, `multiple`, `creatable`, `placeholder`, `hint`, `error`, `required`.\nTagInput props: `label`, `value`/`defaultValue` (string[]), `onChange`, `placeholder`, `hint`.\n\n- Keyboard: ↑ ↓ to move, Enter to pick, Esc to close, Backspace removes the last token.\n- Up to five fixed options without search: use RadioGroup or a native Select instead.\n- Selected items in `multiple` show as tokens inside the field; keep their labels short.\n- Show a `hint` on people (team, email) so two people with the same name can be told apart.\n","components":["Combobox","TagInput"],"code":"import * as React from \"react\";\nimport { Combobox, TagInput } from \"@twentyfirstmedia/ui-core\";\n\nconst TZ = [\n \"Europe/Berlin (UTC+1)\",\n \"Europe/London (UTC+0)\",\n \"Asia/Dubai (UTC+4)\",\n \"Asia/Singapore (UTC+8)\",\n \"America/New_York (UTC−5)\",\n \"America/Los_Angeles (UTC−8)\",\n];\nconst PEOPLE = [\n {\n value: \"ava\",\n label: \"Ava Vogel\",\n hint: \"Engineering\",\n avatar: true,\n },\n {\n value: \"ben\",\n label: \"Ben Okafor\",\n hint: \"Sales\",\n avatar: true,\n },\n {\n value: \"clara\",\n label: \"Clara Haddad\",\n hint: \"Design\",\n avatar: true,\n },\n {\n value: \"elif\",\n label: \"Elif Sato\",\n hint: \"Engineering\",\n avatar: true,\n },\n];\nfunction App() {\n const t = React.useState<string | string[] | null>(\"Asia/Dubai (UTC+4)\"),\n p = React.useState<string | string[] | null>([\"ava\", \"clara\"]),\n tags = React.useState([\"urgent\", \"q3\"]);\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignContent: \"start\",\n } as React.CSSProperties\n }\n >\n <Combobox\n label=\"Time zone\"\n options={TZ}\n value={t[0]}\n onChange={t[1]}\n placeholder=\"Search time zones\"\n />\n <Combobox\n label=\"Reviewers\"\n multiple\n options={PEOPLE}\n value={p[0]}\n onChange={p[1]}\n placeholder=\"Add people\"\n />\n <Combobox\n label=\"Label\"\n creatable\n options={[\"Customers\", \"Agents\", \"Finance\"]}\n placeholder=\"Pick or create\"\n hint=\"Type a new name to create it\"\n />\n <TagInput label=\"Tags\" value={tags[0]} onChange={tags[1]} />\n </div>\n );\n}\nexport default App;\n","path":"components/Combobox"},{"name":"CommandPalette","title":"Command Palette","group":"Overlays","summary":"","description":"","components":["CommandPalette"],"code":"import { CommandPalette } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 430,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <CommandPalette\n inline\n onClose={() => {}}\n items={[\n {\n id: 1,\n group: \"Pages\",\n label: \"Invoices\",\n hint: \"Finance\",\n icon: \"card\",\n },\n {\n id: 2,\n group: \"Pages\",\n label: \"Pipeline\",\n hint: \"Customers\",\n icon: \"trend\",\n },\n {\n id: 3,\n group: \"Actions\",\n label: \"New invoice\",\n icon: \"card\",\n tint: \"butter\",\n },\n {\n id: 4,\n group: \"Actions\",\n label: \"Compose mail\",\n icon: \"edit\",\n tint: \"sky\",\n },\n {\n id: 5,\n group: \"Actions\",\n label: \"New agent\",\n icon: \"bot\",\n tint: \"lilac\",\n },\n {\n id: 6,\n group: \"Records\",\n label: \"Northwind\",\n hint: \"Customer · Team\",\n icon: \"users\",\n tint: \"sky\",\n searchOnly: true,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/CommandPalette"},{"name":"Composer","title":"Composer","group":"AI","summary":"Message input for agents: grows with the text, attachments, slash commands, a model picker, and Send that turns into Stop while the agent works.","description":"Message input for agents: grows with the text, attachments, slash commands, a model picker, and Send that turns into Stop while the agent works.\n\nComposer props: `value` / `onChange`, `onSend(text, attachments)`, `busy`, `onStop`, `placeholder`, `models` ({id, label, hint}) + `model` + `onModel`, `commands` ({name, hint, icon, insert}) + `onCommand`, `attachments` + `onAttach(FileList)` + `onRemoveAttachment(i)`, `hint`, `left` (extra tools).\nAlso here: `PromptStarters` (`items` {icon, tint, title, text, prompt}, `onPick`), `ModelPicker`, `AgentStatus` (`status`, `step`), `UsageMeter` (`used`, `limit`, `format`, `label`, `caption`), `AgentMark`, `TypingDots`.\n\n- Enter sends, Shift+Enter adds a line; typing \"/\" opens commands (↑ ↓ Enter).\n- Drop files anywhere on the composer to attach them.\n- Stop must always work: cancel streaming and mark running tools as stopped.\n- Show starters only on an empty conversation, three or four, written as things people actually ask.\n","components":["AgentStatus","Composer","PromptStarters","UsageMeter"],"code":"import * as React from \"react\";\nimport { AgentStatus, Composer, PromptStarters, UsageMeter } from \"@twentyfirstmedia/ui-core\";\n\nconst MODELS = [\n {\n id: \"opus\",\n label: \"Claude Opus 5.5\",\n hint: \"Deepest reasoning\",\n },\n {\n id: \"sonnet\",\n label: \"Claude Sonnet 5.5\",\n hint: \"Balanced default\",\n },\n {\n id: \"haiku\",\n label: \"Claude Haiku 5.5\",\n hint: \"Fast and cheap\",\n },\n];\nfunction App() {\n const m = React.useState(\"sonnet\"),\n f = React.useState([\n {\n name: \"Q1-report.pdf\",\n },\n ]),\n busy = React.useState(false);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <PromptStarters\n items={[\n {\n icon: \"edit\",\n tint: \"sky\",\n title: \"Draft a reply\",\n text: \"To the latest customer email\",\n },\n {\n icon: \"file\",\n tint: \"mint\",\n title: \"Summarize today\",\n text: \"Runs, approvals, costs\",\n },\n {\n icon: \"search\",\n tint: \"butter\",\n title: \"Ask the docs\",\n text: \"Search the knowledge base\",\n },\n ]}\n />\n <Composer\n placeholder=\"Ask Atlas something, or type / for commands\"\n models={MODELS}\n model={m[0]}\n onModel={m[1]}\n attachments={f[0]}\n onAttach={(fl) => {\n f[1](f[0].concat(Array.prototype.slice.call(fl)));\n }}\n onRemoveAttachment={(i) => {\n f[1](f[0].filter((_, k) => k !== i));\n }}\n commands={[\n {\n name: \"summarize\",\n hint: \"Summarize today\",\n icon: \"file\",\n },\n {\n name: \"draft\",\n hint: \"Draft a reply\",\n icon: \"edit\",\n },\n {\n name: \"clear\",\n hint: \"New conversation\",\n icon: \"trash\",\n },\n ]}\n busy={busy[0]}\n onSend={() => {\n busy[1](true);\n setTimeout(() => {\n busy[1](false);\n }, 2500);\n }}\n onStop={() => {\n busy[1](false);\n }}\n />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <AgentStatus\n status={busy[0] ? \"Thinking\" : \"Idle\"}\n step={busy[0] ? \"Reading 3 messages\" : \"Drafts only\"}\n />\n <div\n style={{\n width: 200,\n }}\n >\n <UsageMeter label=\"Context\" used={21} limit={200} format={(v) => v + \"k\"} />\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/Composer"},{"name":"ConfigPRSummary","title":"Config PR Summary","group":"AI","summary":"Platform structure: ConfigPRSummary (a proposed config change in plain language) and ModuleRegistry (installed modules).","description":"Platform structure: `ConfigPRSummary` (a proposed config change in plain language) and `ModuleRegistry` (installed modules).\n\nConfigPRSummary: `title`, `meta`, `status` + `statusLabel`, `summary`, `items` ({kind: \"project\" | \"team\" | \"agent\" | \"tool\" | \"trigger\" | \"file\", name, detail, op: \"add\" | \"change\" | \"remove\"}), `actions`.\nModuleRegistry: `items` ({id, name, version, text, icon, core, enabled, adds}), `onToggle(module)`, `coreLabel`.\n","components":["Button","ConfigPRSummary","ModuleRegistry"],"code":"import * as React from \"react\";\nimport { Button, ConfigPRSummary, ModuleRegistry } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <ConfigPRSummary\n title=\"New team: Support for Chatteo\"\n meta=\"Proposed by Main Brain · PR #212\"\n status=\"signal\"\n statusLabel=\"Needs review\"\n summary=\"Adds a support team with two agents. Both start in drafts-only mode.\"\n items={[\n {\n kind: \"team\",\n name: \"chatteo/support\",\n },\n {\n kind: \"agent\",\n name: \"Iris\",\n detail: \"Mailbox (read, draft) · Knowledge base\",\n },\n {\n kind: \"agent\",\n name: \"Nora\",\n detail: \"WhatsApp (reply within 24 h)\",\n },\n {\n kind: \"trigger\",\n name: \"New mail to hello@chatteo.de\",\n op: \"change\",\n },\n ]}\n actions={[\n <Button key=\"v\" variant=\"secondary\" size=\"sm\">\n View PR\n </Button>,\n <Button key=\"m\" size=\"sm\" icon=\"check\">\n Approve\n </Button>,\n ]}\n />\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <ModuleRegistry\n onToggle={() => {}}\n items={[\n {\n id: \"chats\",\n name: \"Chats\",\n version: \"1.4.0\",\n text: \"Agents and people in one place\",\n core: true,\n icon: \"mail\",\n },\n {\n id: \"crm\",\n name: \"CRM\",\n version: \"0.9.2\",\n text: \"Companies, contacts, pipeline\",\n enabled: true,\n icon: \"users\",\n adds: [\"Nav\", \"Tool\", \"Jobs\"],\n },\n {\n id: \"brand\",\n name: \"Brand studio\",\n version: \"0.6.0\",\n text: \"Brand packages for customers\",\n enabled: false,\n icon: \"sparkle\",\n adds: [\"Nav\", \"Tool\"],\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/ConfigPRSummary"},{"name":"ConfirmDialog","title":"Confirm Dialog","group":"Overlays","summary":"Asks before a destructive or hard-to-undo action.","description":"Asks before a destructive or hard-to-undo action. Focus starts on Cancel; Esc cancels.\n\nProps: `open`, `title` (a question naming the thing), `text` (what happens), `danger`, `icon`, `confirmLabel` (a verb: \"Delete domain\", never \"OK\"), `cancelLabel`, `confirmText` (type-to-confirm), `onConfirm`, `onCancel`.\n\n- Prefer undo over confirm: removing a list item is instant with an Undo toast; confirm only when undo is impossible or the blast radius is large.\n- Use `confirmText` only for irreversible deletes of whole things (a domain, a workspace).\n- Renders into document.body, so it sits above every stacking context.\n","components":["ConfirmDialog"],"code":"import { ConfirmDialog } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 360,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n transform: \"translateZ(0)\",\n }}\n >\n <ConfirmDialog\n open\n danger\n title=\"Delete halo.dev?\"\n text=\"DNS records, mailboxes and certificates for this domain are removed. This can’t be undone.\"\n confirmText=\"halo.dev\"\n confirmLabel=\"Delete domain\"\n onCancel={() => {}}\n onConfirm={() => {}}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ConfirmDialog"},{"name":"ConnectionCard","title":"Connection Card","group":"Data","summary":"Connected services and the live connection: ConnectionCard and ConnectionBanner.","description":"Connected services and the live connection: `ConnectionCard` and `ConnectionBanner`.\n\nConnectionCard: `name`, `icon` or `logo` (node, e.g. the service's mark), `text`, `status` (\"connected\" | \"expiring\" | \"error\" | \"missing\"), `statusLabel`, `action`.\nConnectionBanner: `state` (\"reconnecting\" | \"offline\" | \"online\" hides it), `seconds`, `text`, `onRetry`. Thin bar under the top bar.\n","components":["Button","ConnectionBanner","ConnectionCard"],"code":"import { Button, ConnectionBanner, ConnectionCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <ConnectionBanner state=\"reconnecting\" seconds={4} onRetry={() => {}} />\n <ConnectionCard\n name=\"Stripe · FollowerX GmbH\"\n icon=\"card\"\n text=\"Payments and refunds · 3 agents\"\n status=\"connected\"\n statusLabel=\"Connected\"\n action={\n <Button size=\"sm\" variant=\"soft\">\n Manage\n </Button>\n }\n />\n <ConnectionCard\n name=\"Google Ads\"\n icon=\"trend\"\n text=\"Budgets and campaigns · 1 agent\"\n status=\"expiring\"\n statusLabel=\"Expires in 5 days\"\n action={<Button size=\"sm\">Renew</Button>}\n />\n <ConnectionCard\n name=\"Docs workspace\"\n icon=\"book\"\n text=\"Release notes · Echo\"\n status=\"error\"\n statusLabel=\"Error\"\n action={<Button size=\"sm\">Reconnect</Button>}\n />\n <ConnectionBanner state=\"offline\" />\n </div>\n );\n}\n","path":"components/ConnectionCard"},{"name":"CouponCard","title":"Coupon Card","group":"Commerce","summary":"Discount codes with their usage and what they earned.","description":"Discount codes with their usage and what they earned.\n\nProps: `code`, `kind` (\"percent\" | \"fixed\"), `value` (percent or amount), `currency`, `used`, `limit`, `until`, `min` (minimum order), `revenue`, `text`, `status` + `statusLabel`, `onCopy`, `menu`.\n","components":["CouponCard"],"code":"import * as React from \"react\";\nimport { CouponCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <CouponCard\n code=\"SPRING20\"\n kind=\"percent\"\n value={20}\n used={312}\n limit={500}\n until=\"Apr 30\"\n min={10}\n revenue={8420}\n text=\"All Instagram packages\"\n menu={[\n {\n label: \"Edit\",\n icon: \"edit\",\n },\n {\n label: \"Duplicate\",\n icon: \"copy\",\n },\n \"-\",\n {\n label: \"Deactivate\",\n icon: \"pause\",\n danger: true,\n },\n ]}\n />\n <CouponCard\n code=\"WELCOME5\"\n kind=\"fixed\"\n value={5}\n used={1204}\n text=\"First order\"\n revenue={21890}\n />\n <CouponCard\n code=\"BLACKFRIDAY\"\n kind=\"percent\"\n value={30}\n used={1000}\n limit={1000}\n until=\"Nov 29\"\n statusLabel=\"Used up\"\n />\n </div>\n );\n}\n","path":"components/CouponCard"},{"name":"DataTable","title":"Data Table","group":"Data","summary":"Table with the features list pages need: sorting, row selection with a bulk action bar, column toggles, sticky header and expandable rows.","description":"Table with the features list pages need: sorting, row selection with a bulk action bar, column toggles, sticky header and expandable rows. `FilterBar` sits above it.\n\nDataTable props: `columns` ({key, label, render, align, sortable, sortValue(row), hideable, hidden, width}), `rows`, `rowKey` (\"id\"), `selectable`, `bulkActions` ({label, icon, danger, onClick(rows, clear)}), `renderExpanded(row)`, `stickyHeader`, `maxHeight`, `defaultSort` ({key, dir}), `toolbar`, `columnToggle` (false hides), `empty`.\nFilterBar props: `filters` ({key, label, icon, options}), `value` / `onChange` ({key: option}), `search` (placeholder), `query` / `onQuery`, `right`.\n\n- Sort cycles ascending → descending → off; the arrow only shows on the sorted or hovered column.\n- The bulk bar floats at the bottom of the table while rows are selected; destructive actions are red and go through an Undo toast or ConfirmDialog.\n- Active filters are ink chips you can click to change or × to remove. \"Clear all\" appears from two filters on.\n- The header checkbox shows a dash when some, not all, rows are selected.\n- Put it in `tf-panel tf-panel-flush` so cells line up at 24px with other panels.\n","components":["Avatar","Badge","DataTable","FilterBar"],"code":"import * as React from \"react\";\nimport { Avatar, Badge, DataTable, FilterBar } from \"@twentyfirstmedia/ui-core\";\nimport type { Tone } from \"@twentyfirstmedia/ui-core\";\n\nconst ROWS = [\n {\n id: 1,\n name: \"Ava Vogel\",\n email: \"ava@halo.app\",\n role: \"Editor\",\n team: \"Engineering\",\n status: \"Active\",\n seats: 3,\n },\n {\n id: 2,\n name: \"Ben Okafor\",\n email: \"ben@halo.app\",\n role: \"Viewer\",\n team: \"Sales\",\n status: \"Invited\",\n seats: 1,\n },\n {\n id: 3,\n name: \"Clara Haddad\",\n email: \"clara@halo.app\",\n role: \"Admin\",\n team: \"Design\",\n status: \"Active\",\n seats: 5,\n },\n {\n id: 4,\n name: \"Elif Sato\",\n email: \"elif@halo.app\",\n role: \"Editor\",\n team: \"Engineering\",\n status: \"Away\",\n seats: 2,\n },\n {\n id: 5,\n name: \"Jonah Petit\",\n email: \"jonah@halo.app\",\n role: \"Editor\",\n team: \"Growth\",\n status: \"Active\",\n seats: 4,\n },\n];\nconst TONE: Record<string, Tone> = {\n Active: \"success\",\n Invited: \"info\",\n Away: \"warning\",\n};\nfunction App() {\n const f = React.useState<Record<string, string>>({\n Team: \"Engineering\",\n }),\n q = React.useState(\"\");\n const rows = ROWS.filter(\n (r) =>\n (!f[0].Team || r.team === f[0].Team) &&\n (!f[0].Status || r.status === f[0].Status) &&\n (!q[0] || r.name.toLowerCase().indexOf(q[0].toLowerCase()) >= 0),\n );\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <FilterBar\n search=\"Search people\"\n query={q[0]}\n onQuery={q[1]}\n value={f[0]}\n onChange={f[1]}\n filters={[\n {\n key: \"Team\",\n label: \"Team\",\n options: [\"Design\", \"Engineering\", \"Growth\", \"Sales\"],\n },\n {\n key: \"Status\",\n label: \"Status\",\n options: [\"Active\", \"Invited\", \"Away\"],\n },\n ]}\n />\n <div className=\"tf-panel tf-panel-flush\">\n <DataTable\n rows={rows}\n selectable\n stickyHeader\n defaultSort={{\n key: \"name\",\n dir: \"asc\",\n }}\n toolbar={<span className=\"tf-card-desc\">{rows.length + \" of \" + ROWS.length}</span>}\n bulkActions={[\n {\n label: \"Change role\",\n icon: \"edit\",\n onClick: () => {},\n },\n {\n label: \"Remove\",\n icon: \"trash\",\n danger: true,\n onClick: (r, clear) => {\n clear();\n },\n },\n ]}\n renderExpanded={(r) => (\n <span className=\"tf-card-desc\">{r.email + \" · \" + r.seats + \" seats assigned\"}</span>\n )}\n columns={[\n {\n key: \"name\",\n label: \"Name\",\n sortable: true,\n hideable: false,\n render: (r) => (\n <span\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Avatar name={r.name} size={28} />\n <b>{r.name}</b>\n </span>\n ),\n },\n {\n key: \"role\",\n label: \"Role\",\n sortable: true,\n },\n {\n key: \"team\",\n label: \"Team\",\n sortable: true,\n },\n {\n key: \"status\",\n label: \"Status\",\n sortable: true,\n render: (r) => <Badge tone={TONE[r.status]}>{r.status}</Badge>,\n },\n {\n key: \"seats\",\n label: \"Seats\",\n align: \"right\",\n sortable: true,\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/DataTable"},{"name":"DatePicker","title":"Date Picker","group":"Forms","summary":"Date field that opens a month calendar in a Popover.","description":"Date field that opens a month calendar in a Popover. `range` picks a from–to span and adds presets.\n\nProps: `label`, `value`/`defaultValue` (Date, or {from, to} with `range`), `onChange`, `range`, `presets` (false to hide), `min`, `max`, `today`, `placeholder`, `hint`, `error`, `required`. `Calendar` is the bare month grid for inline use.\n\n- Weeks start on Monday; dates read DD.MM.YYYY. Today has a dot under the number.\n- A range closes after the second click; a single date closes after one.\n- Presets cover the ranges people actually pick (7, 30, 90 days, this year); keep them to four.\n","components":["Calendar","DatePicker"],"code":"import * as React from \"react\";\nimport { Calendar, DatePicker } from \"@twentyfirstmedia/ui-core\";\nimport type { DateRange } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const d = React.useState<Date | DateRange | null>(new Date(2026, 3, 16)),\n r = React.useState<Date | DateRange | null>({\n from: new Date(2026, 3, 6),\n to: new Date(2026, 3, 16),\n });\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignContent: \"start\",\n } as React.CSSProperties\n }\n >\n <DatePicker label=\"Due date\" value={d[0]} onChange={d[1]} today={new Date(2026, 3, 16)} />\n <DatePicker\n label=\"Report range\"\n range\n value={r[0]}\n onChange={r[1]}\n today={new Date(2026, 3, 16)}\n hint=\"Presets on the left\"\n />\n <div\n style={{\n gridColumn: \"1 / -1\",\n }}\n >\n <div\n className=\"tf-popover\"\n style={{\n position: \"static\",\n width: 476,\n maxWidth: \"100%\",\n animation: \"none\",\n }}\n >\n <div className=\"tf-dp with-presets\">\n <div className=\"tf-dp-presets\">\n {[\"Last 7 days\", \"Last 30 days\", \"Last 90 days\", \"This year\"].map((x, i) => (\n <button key={x} type=\"button\" className={\"tf-dp-preset\" + (i === 0 ? \" on\" : \"\")}>\n {x}\n </button>\n ))}\n </div>\n <Calendar range value={r[0]} today={new Date(2026, 3, 16)} onChange={r[1]} />\n </div>\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/DatePicker"},{"name":"DnsRecordTable","title":"Dns Record Table","group":"Data","summary":"Domains and DNS: DnsRecordTable, DomainList (sorted by what needs attention) and MailAuthStatus (MX, SPF, DKIM, DMARC lights).","description":"Domains and DNS: `DnsRecordTable`, `DomainList` (sorted by what needs attention) and `MailAuthStatus` (MX, SPF, DKIM, DMARC lights).\n\nDnsRecordTable: `records` ({id, type, name, content, ttl, proxy: true | false | null}), `rowMenu(record)` (Menu items), `selectable`, `bulkActions`, `toolbar`. Built on DataTable.\nDomainList: `items` ({name, registrar, dns, days, ssl, sslLabel}), `onOpen`. Under 30 days warns, under 7 days is danger.\nMailAuthStatus: `rows` ({domain, mx, spf, dkim, dmarc: {status, label}}), `columns`, `onOpen(row, column)`.\n","components":["DnsRecordTable","DomainList","MailAuthStatus"],"code":"import * as React from \"react\";\nimport { DnsRecordTable, DomainList, MailAuthStatus } from \"@twentyfirstmedia/ui-core\";\n\nconst recs = [\n {\n id: 1,\n type: \"A\",\n name: \"@\",\n content: \"95.217.14.80\",\n ttl: \"Auto\",\n proxy: true,\n },\n {\n id: 2,\n type: \"CNAME\",\n name: \"www\",\n content: \"northwind.de\",\n ttl: \"Auto\",\n proxy: true,\n },\n {\n id: 3,\n type: \"MX\",\n name: \"@\",\n content: \"10 mx.halo-mail.net\",\n ttl: \"3600\",\n proxy: null,\n },\n {\n id: 4,\n type: \"TXT\",\n name: \"@\",\n content: \"v=spf1 include:_spf.halo-mail.net ~all\",\n ttl: \"3600\",\n proxy: null,\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush\">\n <DnsRecordTable\n records={recs}\n rowMenu={() => [\n {\n label: \"Edit\",\n icon: \"edit\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <DomainList\n items={[\n {\n name: \"halo.dev\",\n registrar: \"Cloudflare\",\n dns: \"Cloudflare\",\n days: 18,\n ssl: \"ok\",\n sslLabel: \"Valid\",\n },\n {\n name: \"northwind.de\",\n registrar: \"one.com\",\n dns: \"Cloudflare\",\n days: 210,\n ssl: \"ok\",\n sslLabel: \"Valid\",\n },\n {\n name: \"chatteo.de\",\n registrar: \"one.com\",\n dns: \"one.com\",\n days: 5,\n ssl: \"warn\",\n sslLabel: \"Renewing\",\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <MailAuthStatus\n rows={[\n {\n domain: \"northwind.de\",\n mx: {\n status: \"ok\",\n label: \"ok\",\n },\n spf: {\n status: \"ok\",\n label: \"ok\",\n },\n dkim: {\n status: \"ok\",\n label: \"ok\",\n },\n dmarc: {\n status: \"warn\",\n label: \"soft\",\n },\n },\n {\n domain: \"chatteo.de\",\n mx: {\n status: \"ok\",\n label: \"ok\",\n },\n spf: {\n status: \"danger\",\n label: \"missing\",\n },\n dkim: {\n status: \"idle\",\n label: \"—\",\n },\n dmarc: {\n status: \"danger\",\n label: \"missing\",\n },\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\n","path":"components/DnsRecordTable"},{"name":"Drawer","title":"Drawer","group":"Overlays","summary":"Right-side sheet for a record's details, sliding in over the page.","description":"Right-side sheet for a record's details, sliding in over the page.\n\nProps: `title`, `subtitle`, `avatar` (name for an Avatar), `onClose`, `width`, `inline` (render inside a positioned container, for previews), children.\n\n- Use for a record you look at without leaving the list: a customer, an invoice, a run.\n- Traps keyboard focus, closes on Esc and backdrop click, returns focus to the opener.\n- Put Tabs right under the badges when the record has more than one view.\n","components":["Badge","Drawer","StatTile","Tabs"],"code":"import * as React from \"react\";\nimport { Badge, Drawer, StatTile, Tabs } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 480,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <Drawer\n inline\n title=\"Northwind\"\n subtitle=\"Logistics · Hamburg · customer since 2021\"\n avatar=\"Northwind\"\n onClose={() => {}}\n >\n <div className=\"tf-row\">\n <Badge tone=\"success\">Good</Badge>\n <Badge tone=\"info\" dot={false}>\n Team · 24 seats\n </Badge>\n </div>\n <Tabs block items={[\"Overview\", \"Invoices\", \"Mail\"]} />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€1,240\" label=\"MRR\" />\n <StatTile tint=\"mint\" value=\"€14,880\" label=\"Paid to date\" />\n </div>\n </Drawer>\n </div>\n );\n}\n","path":"components/Drawer"},{"name":"EmailPreview","title":"Email Preview","group":"Admin","summary":"How a mail will look in the inbox and when opened, on desktop and phone.","description":"How a mail will look in the inbox and when opened, on desktop and phone.\n\nProps: `fromName`, `from`, `subject`, `preheader`, `html` (rendered in a sandboxed iframe, no scripts) or children, `device` / `onDevice` (\"desktop\" | \"mobile\"), `checks` ({status, label}).\n\n- The body sits on `paper` and stays white in dark mode, like most mail clients.\n","components":["EmailPreview"],"code":"import { EmailPreview } from \"@twentyfirstmedia/ui-core\";\n\nconst html =\n '<div style=\"font-family:Arial,sans-serif;max-width:560px;margin:0 auto;padding:32px 24px;color:#141414\"><div style=\"font-weight:800;font-size:20px;margin-bottom:24px\">Northwind</div><h1 style=\"font-size:24px;margin:0 0 12px\">Your order is on its way</h1><p style=\"font-size:15px;line-height:24px;color:#444\">Hi Ida, your 1,000 Instagram followers start arriving within the next hour. Delivery spreads over 24 hours so it looks natural.</p><a style=\"display:inline-block;margin-top:16px;padding:12px 20px;border-radius:999px;background:#141414;color:#fff;text-decoration:none;font-weight:700\">Track order</a></div>';\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <EmailPreview\n from=\"hello@northwind.de\"\n fromName=\"Northwind\"\n subject=\"Your order is on its way\"\n preheader=\"Delivery starts within the hour\"\n html={html}\n checks={[\n {\n status: \"ok\",\n label: \"Links work\",\n },\n {\n status: \"ok\",\n label: \"Unsubscribe link\",\n },\n {\n status: \"warn\",\n label: \"Subject is 46 characters, may cut on phones\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/EmailPreview"},{"name":"EventCard","title":"Event Card","group":"Data","summary":"","description":"","components":["EventCard"],"code":"import { EventCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n maxWidth: 300,\n }}\n >\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Tue, Apr 14\" time=\"10:30\">\n Roadmap check-in with design and growth; bring last sprint numbers.\n </EventCard>\n <EventCard kind=\"deadline\" label=\"Deadline\" date=\"Fri, Apr 17\" time=\"17:00\">\n Send Q2 invoices to all annual customers.\n </EventCard>\n </div>\n );\n}\n","path":"components/EventCard"},{"name":"ExportDialog","title":"Export Dialog","group":"Overlays","summary":"Exporting a filtered list: format, period, columns, progress and download.","description":"Exporting a filtered list: format, period, columns, progress and download.\n\nProps: `open` / `onClose` (or `inline`), `title`, `text`, `count`, `formats`, `columns` ({key, label, default: false = off}), `ranges` (false hides the period), `range`, `note`, `onExport(options, progress)` (return a promise; call `progress(0–100)`), `onDownload` or `download` (node), `doneText`.\n\n- Columns with personal data start unchecked.\n","components":["ExportDialog"],"code":"import { ExportDialog } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n display: \"grid\",\n placeItems: \"center\",\n padding: 24,\n background: \"var(--canvas)\",\n borderRadius: \"var(--radius-lg)\",\n }}\n >\n <ExportDialog\n inline\n title=\"Export orders\"\n count={4812}\n formats={[\"csv\", \"xlsx\", \"json\"]}\n columns={[\n {\n key: \"id\",\n label: \"Order number\",\n },\n {\n key: \"date\",\n label: \"Date\",\n },\n {\n key: \"customer\",\n label: \"Customer\",\n },\n {\n key: \"product\",\n label: \"Product\",\n },\n {\n key: \"net\",\n label: \"Net\",\n },\n {\n key: \"vat\",\n label: \"VAT\",\n },\n {\n key: \"email\",\n label: \"Email\",\n default: false,\n },\n {\n key: \"ip\",\n label: \"IP address\",\n default: false,\n },\n ]}\n note=\"Personal data is only included when you pick those columns.\"\n onExport={(o, prog) =>\n new Promise((r) => {\n let x = 0;\n const t = setInterval(() => {\n x += 20;\n prog(x);\n if (x >= 100) {\n clearInterval(t);\n r();\n }\n }, 300);\n })\n }\n onClose={() => {}}\n />\n </div>\n );\n}\n","path":"components/ExportDialog"},{"name":"FileDrop","title":"File Drop","group":"Forms","summary":"Dashed drop zone plus an upload list.","description":"Dashed drop zone plus an upload list. Click, Enter or drop to add files.\n\n- `onFiles(files)` hands over the real `File` objects the person picked or dropped, after `accept` and `multiple`; upload them yourself. Dropped files that do not match `accept` go to `onReject`; say so with an inline Alert, not a toast.\n- Progress is yours to report: control `files` (`{ id, name, size, progress, file }`) and set `progress` from your upload. Below 100 a row shows a progress bar and a cancel button, at 100 it reads \"uploaded\". With `onFiles` and no `files`, rows show only the size. `onRemove` tells you a file was removed or its upload cancelled.\n- Without `onFiles` the list animates made-up progress, as a demo of the look (`simulateProgress` turns that on or off).\n- The zone turns mint while a file is held over it, so people know the drop will land.\n- Say what is accepted in the hint (types, size).\n","components":["FileDrop"],"code":"import * as React from \"react\";\nimport { FileDrop } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const f = React.useState([\n {\n id: \"a\",\n name: \"Receipt-April-Hetzner.pdf\",\n size: 184320,\n progress: 100,\n },\n {\n id: \"b\",\n name: \"Team-offsite-photos.zip\",\n size: 24117248,\n progress: 46,\n },\n ]);\n return (\n <div className=\"tf-panel\">\n <FileDrop\n files={f[0]}\n onChange={f[1]}\n hint=\"PDF, PNG or JPG up to 10 MB · drag several at once\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/FileDrop"},{"name":"Funnel","title":"Funnel","group":"Charts","summary":"Horizontal funnel: each step's share of the first, plus step-to-step conversion on the right.","description":"Horizontal funnel: each step's share of the first, plus step-to-step conversion on the right.\n\nProps: `steps` ({label, value, tint}), `tint` (default \"sky\"), `format(v)`.\n\n- Three to six steps, named as actions people take (\"Started trial\"), in order.\n- The right column is the conversion from the step above; that's the number people act on.\n","components":["Funnel"],"code":"import { Funnel } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">Trial to paid</h3>\n <p className=\"tf-panel-sub\">Where people drop off</p>\n </div>\n <Funnel\n steps={[\n {\n label: \"Visited pricing\",\n value: 7495,\n },\n {\n label: \"Started trial\",\n value: 3609,\n },\n {\n label: \"Invited a teammate\",\n value: 2221,\n },\n {\n label: \"Became paying\",\n value: 1388,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/Funnel"},{"name":"Gallery","title":"Gallery","group":"Media","summary":"Images: Gallery (photos with thumbnails and full screen), BeforeAfter (drag to compare, e.g.","description":"Images: `Gallery` (photos with thumbnails and full screen), `BeforeAfter` (drag to compare, e.g. virtual staging or upscaling) and `ImageVariants` (the sizes and formats generated from one image).\n\nGallery: `items` ({src, alt, caption, art, tint}), `start`, `ratio`, `thumbs`, `fullscreen`, `badge`, `loading`, `referrerPolicy`. Arrow keys and Esc work in full screen.\nBeforeAfter: `before`, `after` ({src, alt} or {art}), `start` (0–100), `ratio`, `beforeLabel`, `afterLabel`, `label`, `valueText`, `loading`, `referrerPolicy`. Drag, click or use ← → (Page Up/Down for bigger steps, Home and End for the ends).\nImageVariants: `variants` ({id, label, src, size, format, weight, ratio, status}), `selected` / `onSelect`, `alt`, `tint`, `actions`.\n\n- Images load lazily (`loading=\"eager\"` for a gallery at the top of the page; thumbnails always load lazily) and decode off the main thread.\n- `referrerPolicy=\"no-referrer\"` keeps your page address from hosts the photos come from.\n- The compare slider tells screen readers both shares (\"Before 40%, After 60%\", with your labels); `valueText` words it your way.\n","components":["BeforeAfter","Gallery","ImageVariants"],"code":"import * as React from \"react\";\nimport { BeforeAfter, Gallery, ImageVariants } from \"@twentyfirstmedia/ui-core\";\nimport type { Tint } from \"@twentyfirstmedia/ui-core\";\n\nconst art = (t: Tint, s: string) => (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(160deg, var(--\" + t + \") 0%, var(--surface) 120%)\",\n display: \"grid\",\n placeItems: \"center\",\n color: \"var(--\" + t + \"-ink)\",\n font: \"var(--type-title-weight) 14px/1 var(--font-sans)\",\n }}\n >\n {s}\n </div>\n);\nconst before = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #ddd8cf 0 62%, #b9ad9c 62%)\",\n }}\n />\n ),\n};\nconst after = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #e9e2d6 0 62%, #a68b6a 62%)\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n left: \"18%\",\n bottom: \"22%\",\n width: \"38%\",\n height: \"22%\",\n borderRadius: \"var(--radius-md)\",\n background: \"#5b6b5a\",\n }}\n />\n <div\n style={{\n position: \"absolute\",\n right: \"16%\",\n bottom: \"38%\",\n width: \"14%\",\n height: \"30%\",\n borderRadius: \"var(--radius-sm)\",\n background: \"#3d3a36\",\n }}\n />\n </div>\n ),\n};\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <Gallery\n badge=\"Virtual staging\"\n items={[\n {\n art: art(\"sky\", \"Living room\"),\n caption: \"Living room\",\n },\n {\n art: art(\"mint\", \"Kitchen\"),\n },\n {\n art: art(\"butter\", \"Bedroom\"),\n },\n {\n art: art(\"lilac\", \"Balcony\"),\n },\n {\n art: art(\"blush\", \"Bathroom\"),\n },\n ]}\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Virtual staging</b>\n <BeforeAfter before={before} after={after} afterLabel=\"Staged · Scandinavian\" />\n </div>\n <div className=\"tf-panel\">\n <ImageVariants\n tint=\"sky\"\n variants={[\n {\n id: 1,\n label: \"Original\",\n size: \"4032 × 3024\",\n format: \"JPEG\",\n weight: \"3.8 MB\",\n ratio: \"4 / 3\",\n },\n {\n id: 2,\n label: \"Listing\",\n size: \"1600 × 1200\",\n format: \"WebP\",\n weight: \"214 KB\",\n ratio: \"4 / 3\",\n status: \"ok\",\n },\n {\n id: 3,\n label: \"Square\",\n size: \"1080 × 1080\",\n format: \"WebP\",\n weight: \"168 KB\",\n ratio: \"1 / 1\",\n status: \"ok\",\n },\n {\n id: 4,\n label: \"Upscaled 2×\",\n size: \"8064 × 6048\",\n format: \"PNG\",\n status: \"running\",\n ratio: \"4 / 3\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\n","path":"components/Gallery"},{"name":"Gauge","title":"Gauge","group":"Charts","summary":"Half-ring showing one value against a maximum: a goal, a budget, a quota.","description":"Half-ring showing one value against a maximum: a goal, a budget, a quota.\n\nProps: `value`, `max`, `caption`, `format(v)`, `tint`, or `thresholds` ([warn, danger] switches mint → butter → blush), `size`, `stroke`, `showRange`, `label`.\n\n- One gauge per idea; never put gauges in a row to compare things — use a BarChart.\n- Use `thresholds` for limits (budgets, storage), a fixed `tint` for goals where more is better.\n","components":["Gauge"],"code":"import { Gauge } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n flexDirection: \"row\",\n justifyContent: \"space-around\",\n flexWrap: \"wrap\",\n gap: 24,\n }}\n >\n <Gauge\n value={60}\n max={75}\n caption=\"80% of monthly target\"\n format={(v) => \"€\" + v + \"k\"}\n tint=\"mint\"\n />\n <Gauge\n value={72}\n max={100}\n thresholds={[60, 85]}\n caption=\"Agent budget used\"\n format={(v) => v + \"%\"}\n />\n <Gauge\n value={91}\n max={100}\n thresholds={[60, 85]}\n caption=\"Storage used\"\n format={(v) => v + \"%\"}\n />\n </div>\n );\n}\n","path":"components/Gauge"},{"name":"Heatmap","title":"Heatmap","group":"Charts","summary":"Grid of cells shaded by value, for patterns across two dimensions (weekday × hour, week × day).","description":"Grid of cells shaded by value, for patterns across two dimensions (weekday × hour, week × day).\n\nProps: `rows`, `cols` (labels), `values` (rows × cols numbers), `colEvery` (label every nth column), `caption`, `format(v)`, `label`.\n\n- Five steps of one hue (mint-ink mixed into the surface), so it reads by lightness in both themes and for color-blind viewers.\n- The footer shows the hovered cell's exact value; keep the Less–More scale visible.\n","components":["Heatmap"],"code":"import { Heatmap } from \"@twentyfirstmedia/ui-core\";\n\nconst vals = [0, 1, 2, 3, 4, 5, 6].map((d) =>\n Array.from(\n {\n length: 24,\n },\n (_, hr) => {\n const w = d < 5 ? 1 : 0.35;\n return Math.round(\n 48 *\n w *\n (Math.exp(-Math.pow((hr - 11) / 3.2, 2)) +\n 0.7 * Math.exp(-Math.pow((hr - 16) / 2.6, 2))) +\n ((d * 31 + hr * 17) % 7),\n );\n },\n ),\n);\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">When people sign up</h3>\n <p className=\"tf-panel-sub\">By weekday and hour</p>\n </div>\n <Heatmap\n rows={[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]}\n cols={Array.from(\n {\n length: 24,\n },\n (_, i) => (i < 10 ? \"0\" : \"\") + i,\n )}\n colEvery={3}\n values={vals}\n caption=\"Hover a cell for the count\"\n format={(v) => v + \" sign-ups\"}\n />\n </div>\n );\n}\n","path":"components/Heatmap"},{"name":"InboxList","title":"Inbox List","group":"Data","summary":"Webmail pieces: AccountSwitcher (one login, every address), InboxList (with agent state per mail), MailThread, SignatureEditor, AttachmentPreview.","description":"Webmail pieces: `AccountSwitcher` (one login, every address), `InboxList` (with agent state per mail), `MailThread`, `SignatureEditor`, `AttachmentPreview`.\n\nAccountSwitcher: `accounts` ({address, domain, unread}), `value` (null = all inboxes), `onChange`, `unread`, `allLabel`.\nInboxList: `items` ({id, from, time, subject, snippet, unread, to, agent: {label, tone}, tags}), `selected`, `onOpen`.\nMailThread: `subject`, `messages` ({id, from, to, time, snippet, body, agent, tint, agentNote, attachments}), `reply` (node). Oldest collapsed, newest open; agent mails carry a signal edge.\nSignatureEditor: `value` / `onChange` ({name, role, company, phone, website}), `fields`, `note`, `extra`.\nAttachmentPreview: `name`, `meta`, children (the document on paper), `facts` (KeyValue), `found`, `actions`, `onClose`.\n","components":["AccountSwitcher","InboxList","MailThread"],"code":"import * as React from \"react\";\nimport { AccountSwitcher, InboxList, MailThread } from \"@twentyfirstmedia/ui-core\";\n\nconst accounts = [\n {\n address: \"support@northwind.de\",\n domain: \"northwind.de\",\n unread: 3,\n },\n {\n address: \"hello@chatteo.de\",\n domain: \"chatteo.de\",\n unread: 1,\n },\n {\n address: \"robin@twentyfirst.media\",\n domain: \"twentyfirst.media\",\n },\n];\nfunction App() {\n const a = React.useState<string | null>(null),\n s = React.useState<string | number>(1);\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"360px 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <AccountSwitcher accounts={accounts} value={a[0]} onChange={a[1]} unread={4} />\n <InboxList\n selected={s[0]}\n onOpen={(m) => {\n s[1](m.id);\n }}\n items={[\n {\n id: 1,\n from: \"Ida Nolan\",\n time: \"08:40\",\n subject: \"Seat upgrade before renewal\",\n snippet: \"We are growing faster than planned and need 40 more seats.\",\n unread: true,\n to: \"support@\",\n agent: {\n label: \"Draft ready\",\n tone: \"highlight\",\n },\n },\n {\n id: 2,\n from: \"Hetzner\",\n time: \"07:12\",\n subject: \"Invoice 2026-0412\",\n snippet: \"Your invoice is attached.\",\n tags: [\"Receipt\"],\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <MailThread\n subject=\"Seat upgrade before renewal\"\n messages={[\n {\n id: 1,\n from: \"Ida Nolan\",\n to: \"to support@northwind.de\",\n time: \"Mon 16:20\",\n snippet: \"Hi! Quick question about seats…\",\n body: \"Hi! Quick question about seats before our renewal.\",\n },\n {\n id: 2,\n from: \"Atlas\",\n agent: true,\n tint: \"sky\",\n agentNote: \"sent after approval\",\n to: \"to ida@northwind.io\",\n time: \"Tue 09:41\",\n body: \"Hi Ida,\\n\\nhappy to add 40 seats before your renewal on May 1.\\n\\nBest,\\nMara\",\n attachments: [\n {\n name: \"Quote-0412.pdf\",\n size: \"84 KB\",\n },\n ],\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/InboxList"},{"name":"KanbanColumn","title":"Kanban Column","group":"Data","summary":"","description":"","components":["AvatarStack","Badge","Card","KanbanColumn"],"code":"import { AvatarStack, Badge, Card, KanbanColumn } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n maxWidth: 300,\n }}\n >\n <KanbanColumn title=\"In review\" dot=\"lilac\">\n <Card\n title=\"Billing settings flow\"\n meta={[\"Due Fri\"]}\n status={<Badge tone=\"highlight\">Review</Badge>}\n actions={[<AvatarStack key={1} names={[\"Mara Ellis\", \"Sun Lee\"]} size={24} />]}\n />\n <Card\n title=\"Empty states for reports\"\n meta={[\"Due Mon\"]}\n status={<Badge tone=\"warning\">Changes</Badge>}\n />\n </KanbanColumn>\n </div>\n );\n}\n","path":"components/KanbanColumn"},{"name":"KeyValue","title":"Key Value","group":"Foundations","summary":"Small building blocks for detail panels: KeyValue (label/value list), Kbd (keyboard keys), Spinner, Collapsible.","description":"Small building blocks for detail panels: `KeyValue` (label/value list), `Kbd` (keyboard keys), `Spinner`, `Collapsible`.\n\nKeyValue: `items` ([label, value, mono] or {k, v, mono}), `dense`. Empty values show a dash.\nKbd: `keys` (array). Spinner: `size` (\"lg\"), `label`. Collapsible: `title`, `meta`, `defaultOpen` / `open` + `onOpenChange`, children.\n\n- Keep keys short nouns; values that are codes, IDs or addresses use `mono`.\n- Show a spinner only after ~300 ms; inside a button it replaces the icon.\n","components":["Collapsible","Kbd","KeyValue","Spinner","StatusIndicator"],"code":"import * as React from \"react\";\nimport { Collapsible, Kbd, KeyValue, Spinner, StatusIndicator } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <KeyValue\n items={[\n [\"Server\", \"ax41-fsn1\"],\n [\"Address\", \"95.217.14.80\", true],\n [\"Kernel\", \"6.8.0-45\"],\n [\"Backups\", <StatusIndicator status=\"ok\" label=\"Nightly · 03:00\" />],\n [\"Notes\", null],\n ]}\n />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-row\">\n <span>Search</span>\n <Kbd keys={[\"⌘\", \"K\"]} />\n <span>Approve</span>\n <Kbd keys={[\"A\"]} />\n <Spinner />\n <Spinner size=\"lg\" />\n </div>\n <Collapsible title=\"Advanced settings\" meta=\"3 changed\" defaultOpen>\n <KeyValue\n dense\n items={[\n [\"Timeout\", \"30 s\"],\n [\"Retries\", \"3\"],\n ]}\n />\n </Collapsible>\n <Collapsible title=\"Raw payload\">Hidden until opened</Collapsible>\n </div>\n </div>\n );\n}\n","path":"components/KeyValue"},{"name":"LedgerTable","title":"Ledger Table","group":"Data","summary":"Bookkeeping: LedgerTable (bookings with project and agent suggestions), PnLStatement and ReceiptSuggestion.","description":"Bookkeeping: `LedgerTable` (bookings with project and agent suggestions), `PnLStatement` and `ReceiptSuggestion`.\n\nLedgerTable: `rows` ({id, date, ref, text, category, project, projectTint, net, vat, gross, suggested, status, statusLabel}), `categories` (makes the category editable in place) + `onEdit(row, change)`, `currency`, `selectable`, `bulkActions`, `toolbar`, `maxHeight`.\nPnLStatement: `columns` (months), `rows` ({kind: \"section\" | \"subtotal\" | \"total\", label, values}), `currency`, `total` (false hides it).\nReceiptSuggestion: `file`, `source`, `fields` ({label, value, mono, confidence 0–1}), `suggestion`, `onConfirm`, `onDiscard`, `confirmLabel`, `discardLabel`, children.\n\n- Agent suggestions carry the signal diamond until a person confirms them.\n- Negative amounts are blush; amounts use the locale's currency format.\n","components":["LedgerTable","PnLStatement","ReceiptSuggestion"],"code":"import * as React from \"react\";\nimport { LedgerTable, PnLStatement, ReceiptSuggestion } from \"@twentyfirstmedia/ui-core\";\nimport type { LedgerRow } from \"@twentyfirstmedia/ui-core\";\n\nconst rows: LedgerRow[] = [\n {\n id: 1,\n date: \"Apr 2\",\n ref: \"RE-0412\",\n text: \"Hetzner Online GmbH\",\n category: \"Hosting\",\n project: \"Northwind\",\n projectTint: \"sky\",\n net: 84.03,\n vat: 15.97,\n gross: 100.0,\n status: \"ok\",\n statusLabel: \"Booked\",\n },\n {\n id: 2,\n date: \"Apr 3\",\n ref: \"ST-2231\",\n text: \"Stripe payout\",\n category: \"Revenue\",\n project: \"Chatteo\",\n projectTint: \"mint\",\n net: 4210.0,\n vat: 0,\n gross: 4210.0,\n status: \"ok\",\n statusLabel: \"Booked\",\n },\n {\n id: 3,\n date: \"Apr 4\",\n ref: \"MAIL-88\",\n text: \"Google Ireland Ltd.\",\n category: \"Marketing\",\n project: \"Northwind\",\n projectTint: \"sky\",\n net: -320.0,\n vat: 0,\n gross: -320.0,\n suggested: \"Ledger\",\n status: \"signal\",\n statusLabel: \"Suggested\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush\">\n <LedgerTable\n rows={rows}\n selectable\n categories={[\"Hosting\", \"Revenue\", \"Marketing\", \"Software\"]}\n bulkActions={[\n {\n label: \"Confirm\",\n icon: \"check\",\n onClick: (r, c) => {\n c();\n },\n },\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.4fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <PnLStatement\n columns={[\"Jan\", \"Feb\", \"Mar\"]}\n rows={[\n {\n kind: \"section\",\n label: \"Revenue\",\n },\n {\n label: \"Subscriptions\",\n values: [18200, 19400, 21100],\n },\n {\n label: \"One-off\",\n values: [2100, 1800, 2600],\n },\n {\n kind: \"section\",\n label: \"Costs\",\n },\n {\n label: \"Hosting\",\n values: [-820, -840, -910],\n },\n {\n label: \"Marketing\",\n values: [-4200, -3900, -5100],\n },\n {\n kind: \"total\",\n label: \"Result\",\n values: [15280, 16460, 17690],\n },\n ]}\n />\n </div>\n <ReceiptSuggestion\n file=\"Rechnung_2026-0412.pdf\"\n source=\"From billing@hetzner.com · Apr 2\"\n fields={[\n {\n label: \"Vendor\",\n value: \"Hetzner Online GmbH\",\n confidence: 0.99,\n },\n {\n label: \"Date\",\n value: \"Apr 2, 2026\",\n mono: true,\n confidence: 0.98,\n },\n {\n label: \"Gross\",\n value: \"€100.00\",\n mono: true,\n confidence: 0.97,\n },\n {\n label: \"VAT\",\n value: \"19%\",\n confidence: 0.72,\n },\n ]}\n suggestion=\"Hosting · Northwind · as every month\"\n onConfirm={() => {}}\n onDiscard={() => {}}\n />\n </div>\n </div>\n );\n}\n","path":"components/LedgerTable"},{"name":"LineChart","title":"Line Chart","group":"Charts","summary":"","description":"","components":["LineChart"],"code":"import { LineChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n maxWidth: 620,\n }}\n >\n <LineChart\n labels={[\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"]}\n format={(v) => \"€\" + Math.round(v / 1000) + \"k\"}\n series={[\n {\n values: [18000, 22000, 21000, 29000, 34000, 41000],\n tint: \"mint\",\n area: true,\n },\n {\n values: [12000, 14000, 17000, 16000, 21000, 24000],\n tint: \"sky\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/LineChart"},{"name":"ListRow","title":"List Row","group":"Data","summary":"","description":"","components":["ListRow"],"code":"import { ListRow } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-list\"\n style={{\n maxWidth: 380,\n }}\n >\n <ListRow\n avatar=\"Jonah Petit\"\n title=\"Jonah Petit\"\n subtitle=\"Can we move the review to Thursday?\"\n meta=\"09:12\"\n unread\n active\n />\n <ListRow\n avatar=\"Sun Lee\"\n title=\"Sun Lee\"\n subtitle=\"Payout file for March is attached\"\n meta=\"Yesterday\"\n />\n <ListRow\n icon=\"bell\"\n tint=\"butter\"\n title=\"Billing\"\n subtitle=\"Card ending 4417 expires soon\"\n meta=\"Mon\"\n />\n </div>\n );\n}\n","path":"components/ListRow"},{"name":"ListingCard","title":"Listing Card","group":"Real estate","summary":"Property listings from portals or your own stock: ListingCard (grid card or list row) and ResultsToolbar (count, selection, bulk actions, sort, view).","description":"Property listings from portals or your own stock: `ListingCard` (grid card or list row) and `ResultsToolbar` (count, selection, bulk actions, sort, view).\n\nListingCard: `listing` ({id, title, price (null = on request), rent, currency, pricePerSqm, area, rooms, place, image, photos, source, seller (\"private\" | \"agent\"), sellerLabel, phone, isNew, age, status, wished}), `layout` (\"card\" | \"row\"), `selected` + `onSelect`, `onWish`, `statusOptions` + `onStatus` (StatusSelect), `onOpen`.\nResultsToolbar: `title`, `total`, `meta`, `selected`, `onSelectAll(all)`, `bulkActions` ({label, icon, variant, busy, busyLabel, onClick}; shown while something is selected), `sortOptions` + `sort` + `onSort`, `views` (\"grid\" | \"list\" | \"map\") + `view` + `onView`.\n\n- Area in m², money and counts follow `setLocale`.\n","components":["ListingCard","ResultsToolbar"],"code":"import * as React from \"react\";\nimport { ListingCard, ResultsToolbar } from \"@twentyfirstmedia/ui-core\";\nimport type { Listing, StatusSelectProps } from \"@twentyfirstmedia/ui-core\";\n\nconst st: StatusSelectProps[\"options\"] = [\n {\n value: \"unread\",\n label: \"New\",\n status: \"running\",\n },\n {\n value: \"read\",\n label: \"Seen\",\n status: \"idle\",\n },\n {\n value: \"contacted\",\n label: \"Contacted\",\n status: \"ok\",\n },\n];\nconst L0: Listing[] = [\n {\n id: 1,\n title: \"Bright 3-room flat with balcony near Isebek canal\",\n price: 649000,\n pricePerSqm: 7726,\n area: 84,\n rooms: 3,\n place: \"Eppendorf, Hamburg\",\n source: \"ImmoScout\",\n seller: \"private\",\n phone: true,\n photos: 14,\n isNew: true,\n age: \"2 h\",\n status: \"unread\",\n tint: \"sky\",\n },\n {\n id: 2,\n title: \"Loft in HafenCity with water view\",\n price: 1190000,\n pricePerSqm: 10625,\n area: 112,\n rooms: 2.5,\n place: \"HafenCity, Hamburg\",\n source: \"Kleinanzeigen\",\n seller: \"agent\",\n photos: 22,\n age: \"1 d\",\n status: \"contacted\",\n tint: \"lilac\",\n wished: true,\n },\n {\n id: 3,\n title: \"2-room flat, renovated old building\",\n price: 1480,\n rent: true,\n area: 58,\n rooms: 2,\n place: \"Altona, Hamburg\",\n source: \"Immowelt\",\n seller: \"agent\",\n photos: 9,\n age: \"3 d\",\n status: \"read\",\n tint: \"mint\",\n },\n];\nfunction App() {\n const sel = React.useState<(string | number)[]>([1]),\n list = React.useState(L0),\n view = React.useState(\"grid\"),\n sort = React.useState(\"new\");\n function tog(x: Listing) {\n sel[1](sel[0].indexOf(x.id) > -1 ? sel[0].filter((i) => i !== x.id) : sel[0].concat([x.id]));\n }\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <ResultsToolbar\n title=\"Hamburg · flats to buy\"\n total={128}\n meta=\"updated 06:00\"\n selected={sel[0].length}\n onSelectAll={(all) => {\n sel[1](all ? list[0].map((x) => x.id) : []);\n }}\n bulkActions={[\n {\n label: \"Send to CRM\",\n icon: \"send\",\n variant: \"primary\",\n },\n {\n label: \"Mark as seen\",\n icon: \"eye\",\n },\n ]}\n sortOptions={[\n {\n value: \"new\",\n label: \"Newest\",\n },\n {\n value: \"price\",\n label: \"Price\",\n },\n {\n value: \"sqm\",\n label: \"Price per m²\",\n },\n ]}\n sort={sort[0]}\n onSort={sort[1]}\n views={[\"grid\", \"list\", \"map\"]}\n view={view[0]}\n onView={view[1]}\n />\n <div\n className=\"tf-grid\"\n style={\n view[0] === \"list\"\n ? {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 12,\n }\n : ({\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties)\n }\n >\n {list[0].map((x) => (\n <ListingCard\n key={x.id}\n listing={x}\n layout={view[0] === \"list\" ? \"row\" : \"card\"}\n selected={sel[0].indexOf(x.id) > -1}\n onSelect={tog}\n onWish={() => {}}\n statusOptions={st}\n onStatus={(l, s) => {\n list[1](\n list[0].map((y) =>\n y.id === l.id\n ? Object.assign({}, y, {\n status: s,\n })\n : y,\n ),\n );\n }}\n />\n ))}\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/ListingCard"},{"name":"LiveFeed","title":"Live Feed","group":"Data","summary":"Real-time activity: LiveFeed (events as they happen, with pause and filters) and BarList (ranked values with inline bars, e.g.","description":"Real-time activity: `LiveFeed` (events as they happen, with pause and filters) and `BarList` (ranked values with inline bars, e.g. top countries or services).\n\nLiveFeed: `items` ({id, type, icon, tint, title, text, amount, currency, time, fresh}), `types` ({value, label} filter chips), `paused` / `onPause`, `maxHeight`, `emptyText`. While paused, new events are held back and counted.\nBarList: `items` ({id, label, mark, value, share (0–1), tint}), `format`, `onPick(item)`, `label`.\n\n- Show at most ~50 events; older ones belong in a table.\n- `fresh` slides a row in once; it never blinks or repeats.\n","components":["BarList","LiveFeed"],"code":"import * as React from \"react\";\nimport { BarList, LiveFeed, formatMoney } from \"@twentyfirstmedia/ui-core\";\nimport type { LiveFeedItem, LiveFeedProps } from \"@twentyfirstmedia/ui-core\";\n\nconst types: LiveFeedProps[\"types\"] = [\n {\n value: \"order\",\n label: \"Orders\",\n },\n {\n value: \"payment\",\n label: \"Payments\",\n },\n {\n value: \"error\",\n label: \"Errors\",\n },\n];\nconst seed: LiveFeedItem[] = [\n {\n id: 1,\n type: \"order\",\n icon: \"cart\",\n tint: \"mint\",\n title: \"1,000 Instagram followers\",\n text: \"Order #48213 · Germany · PayPal\",\n amount: 12.9,\n time: \"2s\",\n },\n {\n id: 2,\n type: \"payment\",\n icon: \"card\",\n tint: \"sky\",\n title: \"Payment received\",\n text: \"Stripe · card ending 4242\",\n amount: 39,\n time: \"14s\",\n },\n {\n id: 3,\n type: \"error\",\n icon: \"zap\",\n tint: \"blush\",\n title: \"Provider timeout\",\n text: \"SMM provider B · retried\",\n time: \"41s\",\n },\n {\n id: 4,\n type: \"order\",\n icon: \"cart\",\n tint: \"mint\",\n title: \"5,000 TikTok views\",\n text: \"Order #48212 · Austria\",\n amount: 4.9,\n time: \"1m\",\n },\n];\nfunction App() {\n const it = React.useState<LiveFeedItem[]>(seed);\n React.useEffect(() => {\n let n = 100;\n const t = setInterval(() => {\n n++;\n it[1]((l): LiveFeedItem[] => [\n {\n id: n,\n type: \"order\",\n icon: \"cart\",\n tint: \"mint\",\n title: ((n % 3) + 1) * 500 + \" starter kits\",\n text: \"Order #\" + (48213 + n - 100) + \" · Switzerland\",\n amount: 2.9 + (n % 5),\n time: \"now\",\n fresh: true,\n },\n ...l.slice(0, 7),\n ]);\n }, 2600);\n return () => {\n clearInterval(t);\n };\n }, []);\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.4fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <LiveFeed items={it[0]} types={types} maxHeight={420} />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Revenue by country</b>\n <BarList\n format={(v) => formatMoney(v)}\n items={[\n {\n label: \"Germany\",\n mark: \"🇩🇪\",\n value: 18240,\n share: 0.52,\n },\n {\n label: \"Austria\",\n mark: \"🇦🇹\",\n value: 6120,\n share: 0.17,\n },\n {\n label: \"Switzerland\",\n mark: \"🇨🇭\",\n value: 5480,\n share: 0.16,\n tint: \"mint\",\n },\n {\n label: \"Netherlands\",\n mark: \"🇳🇱\",\n value: 2210,\n share: 0.06,\n tint: \"butter\",\n },\n {\n label: \"Other\",\n value: 3100,\n share: 0.09,\n tint: \"lilac\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/LiveFeed"},{"name":"LocaleField","title":"Locale Field","group":"Forms","summary":"Content in several languages and switching context: LocaleField, LocaleSwitcher, WorkspaceSwitcher.","description":"Content in several languages and switching context: `LocaleField`, `LocaleSwitcher`, `WorkspaceSwitcher`.\n\nLocaleField: `label`, `locales` ({code, label, flag}), `value` / `defaultValue` ({[code]: text}) + `onChange`, `source` (language others copy from; default the first), `active`, `multiline`, `rows`, `onTranslate(text, to)` (return or resolve the translation), `hint`, `error`. Missing languages carry a warn diamond; empty fields show the source text as placeholder.\nLocaleSwitcher: `languages` ({code, label, short, flag}), `language` / `onLanguage`, `currencies`, `currency` / `onCurrency`, `variant` (\"frame\" for the dark top bar).\nWorkspaceSwitcher: `items` ({id, name, sub, tint, logo}), `value` / `onChange`, `label`, `footer`, `variant` (\"frame\").\n","components":["LocaleField","LocaleSwitcher","WorkspaceSwitcher"],"code":"import * as React from \"react\";\nimport { LocaleField, LocaleSwitcher, WorkspaceSwitcher } from \"@twentyfirstmedia/ui-core\";\n\nconst langs = [\n {\n code: \"de\",\n label: \"Deutsch\",\n short: \"de\",\n flag: \"🇩🇪\",\n },\n {\n code: \"en\",\n label: \"English\",\n short: \"en\",\n flag: \"🇬🇧\",\n },\n {\n code: \"fr\",\n label: \"Français\",\n short: \"fr\",\n flag: \"🇫🇷\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.3fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 20,\n }}\n >\n <LocaleField\n label=\"Product name\"\n locales={[\n {\n code: \"de\",\n label: \"DE\",\n },\n {\n code: \"en\",\n label: \"EN\",\n },\n {\n code: \"fr\",\n label: \"FR\",\n },\n {\n code: \"es\",\n label: \"ES\",\n },\n ]}\n defaultValue={{\n de: \"Instagram Follower kaufen\",\n en: \"Buy Instagram followers\",\n }}\n onTranslate={(t, to) => \"[\" + to + \"] \" + t}\n />\n <LocaleField\n label=\"Short description\"\n multiline\n active=\"en\"\n locales={[\n {\n code: \"de\",\n label: \"DE\",\n },\n {\n code: \"en\",\n label: \"EN\",\n },\n ]}\n defaultValue={{\n de: \"Echte Follower, Lieferung in einer Stunde.\",\n }}\n />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n alignItems: \"flex-start\",\n }}\n >\n <WorkspaceSwitcher\n label=\"Shops\"\n items={[\n {\n id: \"fp\",\n name: \"Northwind\",\n sub: \"northwind.de\",\n tint: \"sky\",\n },\n {\n id: \"fm\",\n name: \"FollowerMarket\",\n sub: \"followermarket.com\",\n tint: \"mint\",\n },\n {\n id: \"fr\",\n name: \"FollowerRocket\",\n sub: \"followerrocket.de\",\n tint: \"butter\",\n },\n ]}\n />\n <LocaleSwitcher languages={langs} currencies={[\"EUR\", \"CHF\", \"USD\"]} />\n </div>\n </div>\n );\n}\n","path":"components/LocaleField"},{"name":"LogView","title":"Log View","group":"Foundations","summary":"Monospace output: LogView for logs with line numbers and levels, CodeViewer for JSON, YAML and code.","description":"Monospace output: `LogView` for logs with line numbers and levels, `CodeViewer` for JSON, YAML and code.\n\nLogView: `lines` (strings or {time, text, level: \"ok\" | \"warn\" | \"err\" | \"hl\", fresh}), `title`, `right`, `numbers` (false hides them), `follow` (default true: sticks to the newest line unless the pointer is over it), `maxHeight`.\nCodeViewer: `value` (string or object, pretty-printed as JSON), `lang`. Built on CodeBlock with copy.\n","components":["CodeViewer","LogView"],"code":"import { CodeViewer, LogView } from \"@twentyfirstmedia/ui-core\";\nimport type { LogLine } from \"@twentyfirstmedia/ui-core\";\n\nconst lines: LogLine[] = [\n {\n time: \"09:41:02\",\n text: \"pull ghcr.io/halo/web:8f2a91c\",\n level: \"hl\",\n },\n {\n time: \"09:41:09\",\n text: \"build 146 modules transformed\",\n },\n {\n time: \"09:41:22\",\n text: \"test 146 passed, 0 failed\",\n level: \"ok\",\n },\n {\n time: \"09:41:23\",\n text: \"warn bundle is 512 kB, above the 500 kB budget\",\n level: \"warn\",\n },\n {\n time: \"09:41:30\",\n text: \"health GET /v1/health → 503\",\n level: \"err\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <LogView title=\"deploy #482\" lines={lines} />\n <CodeViewer\n value={{\n tool: \"cloudflare.dns\",\n action: \"create\",\n record: {\n type: \"TXT\",\n name: \"ops-test\",\n ttl: 300,\n },\n }}\n />\n </div>\n );\n}\n","path":"components/LogView"},{"name":"MapView","title":"Map View","group":"Data","summary":"Points on a map: price pins for listings or bubbles for where orders come from.","description":"Points on a map: price pins for listings or bubbles for where orders come from. The map itself (tiles from MapLibre, Leaflet or Google, an SVG or an image) goes in `background`; Halo draws pins, popups, legend and controls on top in its own style.\n\nProps: `points` ({id, label, sub, lat, lng, value, text (pin label), tint, pulse}), `bounds` ({n, s, w, e}; what the background shows), `background` (node), `bubbles` (size by value instead of pins), `selected` / `onSelect`, `renderPopup(point)`, `legend`, `controls`, `height`, `label`.\n\n- With a real map library, pass its projection's bounds or render the pins through its overlay API with the same classes (`tf-map-pin`, `tf-map-pop`).\n- Price pins show the short price; the popup carries the details.\n","components":["IconButton","MapView"],"code":"import * as React from \"react\";\nimport { IconButton, MapView } from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\nconst world: MapPoint[] = [\n {\n id: \"de\",\n label: \"Germany\",\n lat: 51,\n lng: 10,\n value: 820,\n sub: \"820 orders today\",\n },\n {\n id: \"at\",\n label: \"Austria\",\n lat: 47.5,\n lng: 14.5,\n value: 260,\n tint: \"mint\",\n },\n {\n id: \"ch\",\n label: \"Switzerland\",\n lat: 46.8,\n lng: 8.2,\n value: 210,\n tint: \"mint\",\n },\n {\n id: \"us\",\n label: \"United States\",\n lat: 39,\n lng: -98,\n value: 140,\n tint: \"butter\",\n },\n {\n id: \"ae\",\n label: \"UAE\",\n lat: 24,\n lng: 54,\n value: 90,\n tint: \"lilac\",\n pulse: true,\n },\n {\n id: \"br\",\n label: \"Brazil\",\n lat: -12,\n lng: -52,\n value: 60,\n tint: \"blush\",\n },\n];\nconst city: MapPoint[] = [\n {\n id: 1,\n label: \"3-room flat, Eppendorf\",\n sub: \"€649,000 · 84 m²\",\n lat: 53.59,\n lng: 9.98,\n text: \"€649k\",\n },\n {\n id: 2,\n label: \"Loft, HafenCity\",\n sub: \"€1,190,000 · 112 m²\",\n lat: 53.541,\n lng: 10.0,\n text: \"€1.19m\",\n tint: \"lilac\",\n },\n {\n id: 3,\n label: \"2-room flat, Altona\",\n sub: \"€1,480 / month\",\n lat: 53.55,\n lng: 9.935,\n text: \"€1,480\",\n tint: \"mint\",\n },\n {\n id: 4,\n label: \"House, Blankenese\",\n lat: 53.565,\n lng: 9.81,\n text: \"€2.4m\",\n tint: \"butter\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Orders by country</b>\n <MapView bubbles points={world} height={360} legend=\"Bubble size = orders today\" />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Listings</b>\n <MapView\n points={city}\n bounds={{\n n: 53.62,\n s: 53.52,\n w: 9.78,\n e: 10.06,\n }}\n height={360}\n selected={2}\n controls={[\n <IconButton key={1} icon=\"plus\" label=\"Zoom in\" size=\"sm\" />,\n <IconButton key={2} icon=\"minus\" label=\"Zoom out\" size=\"sm\" />,\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/MapView"},{"name":"MemoryViewer","title":"Memory Viewer","group":"AI","summary":"What an agent remembers (MemoryViewer) and how its runtime settings differ from code (OverrideDiff).","description":"What an agent remembers (`MemoryViewer`) and how its runtime settings differ from code (`OverrideDiff`).\n\nMemoryViewer: `items` ({id, scope, source, date, text}), `onEdit(item)`, `onDelete(item)`. Search and scope chips built in.\nOverrideDiff: `title`, `versions`, `by`, `before`, `after` (text, diffed by line), `onDiscard`, `onPromote`, `discardLabel`, `promoteLabel`.\n","components":["MemoryViewer","OverrideDiff"],"code":"import * as React from \"react\";\nimport { MemoryViewer, OverrideDiff } from \"@twentyfirstmedia/ui-core\";\n\nconst mem = [\n {\n id: 1,\n scope: \"Agent\",\n source: \"Chat\",\n date: \"Apr 12\",\n text: \"Ida at Northwind prefers short replies and a quote attached.\",\n },\n {\n id: 2,\n scope: \"Project\",\n source: \"Feedback\",\n date: \"Apr 9\",\n text: \"Never promise refunds; hand them to a person.\",\n },\n {\n id: 3,\n scope: \"Global\",\n source: \"Import\",\n date: \"Apr 2\",\n text: \"Company is FollowerX GmbH, Hamburg.\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <MemoryViewer items={mem} onEdit={() => {}} onDelete={() => {}} />\n </div>\n <OverrideDiff\n title=\"Instruction\"\n versions=\"Code default v1 → Override v3\"\n by=\"Main Brain after feedback\"\n before={\"You are Atlas, the support agent.\\nBe friendly.\\nAnswer within one day.\"}\n after={\n \"You are Atlas, the support agent.\\nBe brief and friendly.\\nAnswer within one day.\\nNever promise refunds.\"\n }\n onDiscard={() => {}}\n onPromote={() => {}}\n />\n </div>\n );\n}\n","path":"components/MemoryViewer"},{"name":"Menu","title":"Menu","group":"Overlays","summary":"","description":"","components":["Menu"],"code":"import * as React from \"react\";\nimport { Menu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n const [grid, setGrid] = React.useState(true);\n const [density, setDensity] = React.useState(\"comfortable\");\n return (\n <div className=\"tf-row\" style={{ gap: 24, alignItems: \"flex-start\", paddingBottom: 120 }}>\n <Menu\n items={[\n {\n label: \"Open\",\n icon: \"eye\",\n shortcut: \"↵\",\n },\n {\n label: \"Rename\",\n icon: \"edit\",\n shortcut: \"R\",\n },\n {\n label: \"Duplicate\",\n icon: \"file\",\n },\n {\n label: \"Share link\",\n icon: \"link\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n />\n <Menu\n label=\"View\"\n items={[\n { label: \"Show grid\", type: \"checkbox\", checked: grid, onClick: () => setGrid(!grid) },\n { label: \"Show minimap\", type: \"checkbox\", checked: false },\n \"-\",\n {\n label: \"Comfortable\",\n type: \"radio\",\n group: \"density\",\n checked: density === \"comfortable\",\n onClick: () => setDensity(\"comfortable\"),\n },\n {\n label: \"Compact\",\n type: \"radio\",\n group: \"density\",\n checked: density === \"compact\",\n onClick: () => setDensity(\"compact\"),\n },\n \"-\",\n {\n label: \"Sort by\",\n icon: \"list\",\n items: [\n { label: \"Name\", type: \"radio\", checked: true },\n { label: \"Last change\", type: \"radio\" },\n { label: \"Size\", type: \"radio\" },\n ],\n },\n { label: \"Export as PDF\", icon: \"download\", disabled: true },\n ]}\n />\n </div>\n );\n}\n","path":"components/Menu"},{"name":"MobileTabBar","title":"Mobile Tab Bar","group":"Overlays","summary":"Phone chrome: MobileTabBar (bottom navigation) and PushNotification (how an approval arrives on the lock screen).","description":"Phone chrome: `MobileTabBar` (bottom navigation) and `PushNotification` (how an approval arrives on the lock screen).\n\nMobileTabBar: `items` ({id, label, icon, count}), `active`, `onPick`, `label`.\nPushNotification: `app`, `time`, `title`, `text`, `actions` ({label, onClick}). Only A can be answered from the notification; B–D open the app.\n","components":["MobileTabBar","PushNotification"],"code":"import * as React from \"react\";\nimport { MobileTabBar, PushNotification } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const a = React.useState(\"approvals\");\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n alignItems: \"center\",\n }}\n >\n <PushNotification\n app=\"Halo\"\n time=\"now\"\n title=\"Approval needed · Atlas\"\n text=\"Send this email to ida@northwind.io?\"\n actions={[\n {\n label: \"A · Run it\",\n },\n {\n label: \"Open\",\n },\n ]}\n />\n <div\n style={{\n width: 390,\n boxSizing: \"border-box\",\n maxWidth: \"100%\",\n }}\n >\n <MobileTabBar\n active={a[0]}\n onPick={a[1]}\n items={[\n {\n id: \"home\",\n label: \"Home\",\n icon: \"home\",\n },\n {\n id: \"approvals\",\n label: \"Approvals\",\n icon: \"check\",\n count: 3,\n },\n {\n id: \"mail\",\n label: \"Mail\",\n icon: \"mail\",\n },\n {\n id: \"more\",\n label: \"More\",\n icon: \"menu\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/MobileTabBar"},{"name":"Modal","title":"Modal","group":"Overlays","summary":"","description":"","components":["Button","Input","Modal"],"code":"import { Button, Input, Modal } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n height: 340,\n position: \"relative\",\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <Modal\n style={{\n position: \"absolute\",\n inset: 0,\n }}\n icon=\"users\"\n tint=\"sky\"\n title=\"Invite teammates\"\n text=\"They get access to every project in this workspace.\"\n actions={[\n <Button key={1} variant=\"ghost\">\n Cancel\n </Button>,\n <Button key={2} icon=\"send\">\n Send invites\n </Button>,\n ]}\n >\n <Input label=\"Email addresses\" placeholder=\"name@company.com, …\" />\n </Modal>\n </div>\n );\n}\n","path":"components/Modal"},{"name":"NotificationList","title":"Notification List","group":"Overlays","summary":"","description":"","components":["NotificationList"],"code":"import { NotificationList } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n width: 380,\n boxSizing: \"border-box\",\n maxWidth: \"100%\",\n background: \"var(--surface)\",\n borderRadius: \"var(--radius-lg)\",\n padding: 8,\n boxShadow: \"var(--shadow-float)\",\n }}\n >\n <NotificationList\n items={[\n {\n id: 1,\n icon: \"bot\",\n tint: \"lilac\",\n title: \"Atlas needs your approval\",\n text: \"4 reply drafts are waiting\",\n time: \"07:55\",\n unread: true,\n },\n {\n id: 2,\n icon: \"flag\",\n tint: \"blush\",\n title: \"Invoice #2041 is overdue\",\n text: \"Harbor & Co · €860\",\n time: \"07:30\",\n unread: true,\n },\n {\n id: 3,\n icon: \"zap\",\n tint: \"mint\",\n title: \"halo-web is live\",\n text: \"Add seat selector to checkout\",\n time: \"Yesterday\",\n },\n {\n id: 4,\n icon: \"users\",\n tint: \"sky\",\n title: \"Ava Brandt joined\",\n text: \"Invited as Viewer\",\n time: \"Mon\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/NotificationList"},{"name":"NumberInput","title":"Number Input","group":"Forms","summary":"Number entry three ways: NumberInput with − / + steppers, Slider for a value on a known scale, OtpInput for one-time codes.","description":"Number entry three ways: `NumberInput` with − / + steppers, `Slider` for a value on a known scale, `OtpInput` for one-time codes.\n\nNumberInput: `label`, `value`/`defaultValue`, `onChange(n)`, `min`, `max`, `step`, `unit`, `hint`.\nSlider: `label`, `min`, `max`, `step`, `value`/`defaultValue`, `onChange(n)`, `format(v)`, `marks` (false hides min/max), `hint`.\nOtpInput: `length` (6), `onComplete(code)`, `error` (shakes and turns red), `label`.\n\n- NumberInput for small exact counts (seats, days); arrow keys step too.\n- Slider when the range matters more than the exact value (budgets, thresholds); always show the value bubble.\n- OtpInput moves focus forward as you type, back on Backspace, and fills every box on paste.\n","components":["NumberInput","OtpInput","Slider"],"code":"import * as React from \"react\";\nimport { NumberInput, OtpInput, Slider } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const s = React.useState(12),\n b = React.useState(120);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <div\n style={{\n display: \"flex\",\n gap: 32,\n flexWrap: \"wrap\",\n alignItems: \"flex-start\",\n }}\n >\n <NumberInput\n label=\"Seats\"\n value={s[0]}\n onChange={s[1]}\n min={1}\n max={200}\n hint=\"€24 per seat\"\n />\n <NumberInput label=\"VAT\" defaultValue={19} min={0} max={25} unit=\"%\" />\n <div className=\"tf-field\">\n <span className=\"tf-field-label\">Verification code</span>\n <OtpInput onComplete={() => {}} />\n <span className=\"tf-field-hint\">Paste works too</span>\n </div>\n </div>\n <Slider\n label=\"Monthly agent budget\"\n min={0}\n max={500}\n step={10}\n value={b[0]}\n onChange={b[1]}\n format={(v) => \"€\" + v}\n hint=\"Agents pause and ask you once they reach it\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/NumberInput"},{"name":"OrderSummary","title":"Order Summary","group":"Commerce","summary":"Cart and checkout pieces: CartLine rows, the OrderSummary with discount code and free-shipping progress, and CheckoutSteps.","description":"Cart and checkout pieces: `CartLine` rows, the `OrderSummary` with discount code and free-shipping progress, and `CheckoutSteps`.\n\nCartLine props: `name`, `variant`, `note`, `art` + `tint`, `price`, `qty`, `onQty(n)`, `onRemove` (the − button becomes a bin at 1).\nOrderSummary props: `lines` ({name, variant, art, tint, price, qty}), `showLines`, `discount` ({code, pct | amount, label}), `onCoupon(code)` (return false to show \"isn’t valid\"; null removes), `shipping` (fixed) or `freeShippingFrom` (default 75, else €4.90), `vat` (19, prices include it), `title`, `action` (the CTA).\nCheckoutSteps props: `steps` (Cart, Details, Shipping, Payment), `current`, `onPick` (go back only).\n\n- Keep the summary in a sticky side column on desktop; it moves under the form on phones.\n- Show what's left for free shipping; it is the strongest nudge in a cart.\n- Removing a line shows an Undo toast instead of a confirm.\n","components":["Button","CartLine","CheckoutSteps","OrderSummary"],"code":"import * as React from \"react\";\nimport { Button, CartLine, CheckoutSteps, OrderSummary } from \"@twentyfirstmedia/ui-core\";\nimport type { CartLineProps, Discount } from \"@twentyfirstmedia/ui-core\";\n\ntype OrderLine = Omit<CartLineProps, \"onQty\" | \"onRemove\"> & { key: string };\n\nfunction App() {\n const lines = React.useState<OrderLine[]>([\n {\n key: \"a\",\n name: \"Halo Cotton Tee\",\n variant: \"M · Sky\",\n art: \"tee\",\n tint: \"sky\",\n price: 32,\n qty: 1,\n },\n {\n key: \"b\",\n name: \"Studio Mug\",\n art: \"mug\",\n tint: \"butter\",\n price: 18,\n qty: 2,\n note: \"Only 6 left\",\n },\n ]),\n d = React.useState<Discount | null>(null),\n step = React.useState(1);\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <CheckoutSteps current={step[0]} onPick={step[1]} />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1fr) 340px\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n {lines[0].map((l) => (\n <CartLine\n {...l}\n key={l.key}\n onQty={(n) => {\n lines[1](\n lines[0].map((z) =>\n z.key === l.key\n ? Object.assign({}, z, {\n qty: n,\n })\n : z,\n ),\n );\n }}\n onRemove={() => {\n lines[1](lines[0].filter((z) => z.key !== l.key));\n }}\n />\n ))}\n </div>\n <div className=\"tf-panel\">\n <OrderSummary\n lines={lines[0]}\n showLines\n discount={d[0]}\n freeShippingFrom={75}\n onCoupon={(c) => {\n if (c === null) return d[1](null);\n if (c !== \"HALO10\") return false;\n d[1]({\n code: \"HALO10\",\n pct: 10,\n label: \"10% off everything\",\n });\n }}\n action={<Button icon=\"lock\">Pay now</Button>}\n />\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/OrderSummary"},{"name":"OrderTimeline","title":"Order Timeline","group":"Commerce","summary":"Five-step fulfilment progress for an order: Placed, Paid, Packed, Shipped, Delivered.","description":"Five-step fulfilment progress for an order: Placed, Paid, Packed, Shipped, Delivered.\n\nProps: `status` (one of the five, or \"Refunded\" / \"Cancelled\" which dims the line and shows a badge), `times` ({step: text}).\n\n- Use the same five words as the order status badges, so list and detail agree.\n- The current step gets a sky ring and a clock until it's done.\n","components":["OrderTimeline"],"code":"import { OrderTimeline } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 32,\n }}\n >\n <OrderTimeline\n status=\"Shipped\"\n times={{\n Placed: \"Apr 12, 09:14\",\n Paid: \"Apr 12, 09:15\",\n Packed: \"Apr 12, 15:40\",\n Shipped: \"Apr 13, 08:02\",\n }}\n />\n <OrderTimeline\n status=\"Paid\"\n times={{\n Placed: \"Just now\",\n Paid: \"Just now\",\n }}\n />\n <OrderTimeline status=\"Refunded\" />\n </div>\n );\n}\n","path":"components/OrderTimeline"},{"name":"OrgChart","title":"Org Chart","group":"AI","summary":"The hierarchy as a chart: orchestrator, leads per team, executing agents and people.","description":"The hierarchy as a chart: orchestrator, leads per team, executing agents and people.\n\nProps: `root` ({name, role, tint, status, avatar, children}). Scrolls sideways when wide.\n","components":["OrgChart"],"code":"import { OrgChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <OrgChart\n root={{\n name: \"Main Brain\",\n role: \"Orchestrator\",\n tint: \"lilac\",\n status: \"ok\",\n children: [\n {\n name: \"Atlas\",\n role: \"Support lead\",\n tint: \"sky\",\n status: \"running\",\n children: [\n {\n name: \"Triage\",\n role: \"Tags mail\",\n tint: \"sky\",\n status: \"idle\",\n },\n {\n name: \"Drafts\",\n role: \"Writes replies\",\n tint: \"sky\",\n status: \"signal\",\n },\n ],\n },\n {\n name: \"Ledger\",\n role: \"Finance lead\",\n tint: \"butter\",\n status: \"ok\",\n },\n {\n name: \"Robin\",\n role: \"Owner\",\n avatar: true,\n },\n ],\n }}\n />\n </div>\n );\n}\n","path":"components/OrgChart"},{"name":"Panel","title":"Panel","group":"Data","summary":"","description":"","components":["EventCard","IconButton","Panel","Tabs"],"code":"import { EventCard, IconButton, Panel, Tabs } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n maxWidth: 340,\n background: \"var(--canvas)\",\n padding: 12,\n borderRadius: \"var(--radius-lg)\",\n }}\n >\n <Panel\n title=\"Schedule\"\n subtitle=\"5 items waiting · 2 today\"\n action={<IconButton icon=\"plus\" label=\"Add\" />}\n >\n <Tabs block items={[\"Events\", \"Approvals\", \"History\"]} />\n <EventCard kind=\"review\" label=\"Design review\" date=\"Wed, Apr 15\" time=\"14:00\">\n Walk through the new billing settings flow.\n </EventCard>\n </Panel>\n </div>\n );\n}\n","path":"components/Panel"},{"name":"PlanCard","title":"Plan Card","group":"Commerce","summary":"Pricing tier for plan pages and upgrade dialogs.","description":"Pricing tier for plan pages and upgrade dialogs. Wrap several in `.tf-plans` for an even grid.\n\nProps: `name`, `text`, `price` (number, 0 = \"Free\", null = \"Let’s talk\"), `was` (struck price, e.g. monthly vs yearly), `per`, `features` (strings; prefix \"-\" for not included), `featured` (dark card + flag), `flag`, `current` (disables the button), `disabled` (disables the button, e.g. while a checkout starts or when the plan is not available), `cta`, `onPick`.\n\n- One featured plan, the one most customers should pick. It inverts to the frame color.\n- List the same features in the same order on every card, so rows can be compared; mark missing ones instead of leaving them out.\n- Pair with a Segmented Monthly / Yearly switch that states the saving.\n","components":["PlanCard"],"code":"import { PlanCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-plans\">\n <PlanCard\n name=\"Starter\"\n price={9}\n was={12}\n per=\"per seat / month, billed yearly\"\n text=\"For small teams getting organised\"\n features={[\n \"3 projects\",\n \"1 agent · Haiku only\",\n \"Shop with up to 50 products\",\n \"-Approvals and audit log\",\n \"-SSO\",\n ]}\n />\n <PlanCard\n name=\"Team\"\n price={24}\n was={29}\n per=\"per seat / month, billed yearly\"\n text=\"Everything a growing company runs on\"\n featured\n features={[\n \"Unlimited projects\",\n \"10 agents · all models\",\n \"Unlimited products, 3 channels\",\n \"Approvals and audit log\",\n \"-SSO\",\n ]}\n />\n <PlanCard\n name=\"Business\"\n price={null}\n text=\"Security, control and an SLA\"\n cta=\"Talk to sales\"\n current={false}\n features={[\"Everything in Team\", \"SSO and SCIM\", \"EU data residency\", \"99.9% uptime SLA\"]}\n />\n </div>\n );\n}\n","path":"components/PlanCard"},{"name":"ProductCard","title":"Product Card","group":"Commerce","summary":"Product tile for catalogs and grids, plus the pieces it is made of: Price, Rating, StockBadge and ProductArt.","description":"Product tile for catalogs and grids, plus the pieces it is made of: `Price`, `Rating`, `StockBadge` and `ProductArt`.\n\nProductCard props: `name`, `meta`, `art` + `tint` (or swap in your own image inside `.tf-product-media`), `price`, `compareAt`, `rating`, `reviews`, `badge` + `badgeTone`, `stock`, `wished` + `onWish`, `added` + `onAdd`, `onClick`.\nPrice: `value`, `compareAt` (shows the struck price and −%), `size` (\"lg\"), `currency`, `showSave`. Rating: `value`, `count`, `size`. StockBadge: `stock`, `low` (10), `compact`. ProductArt: `kind` (tee, mug, bottle, cap, tote, sneaker, watch, lamp), `tint`, `size`.\n\n- One badge per card at most (Bestseller, New, Sold out). Sale is shown by the price, not a badge.\n- Quick-add only for products without variants; otherwise open the product page.\n- Low stock is a warning badge with the number left; out of stock disables add.\n","components":["Price","ProductArt","ProductCard","Rating","StockBadge"],"code":"import * as React from \"react\";\nimport { Price, ProductArt, ProductCard, Rating, StockBadge } from \"@twentyfirstmedia/ui-core\";\nimport type { ProductCardProps } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const w = React.useState<string[]>([\"p2\"]),\n a = React.useState<string[]>([]);\n const P: (ProductCardProps & { id: string })[] = [\n {\n id: \"p1\",\n name: \"Halo Cotton Tee\",\n meta: \"Apparel\",\n art: \"tee\",\n tint: \"sky\",\n price: 32,\n compareAt: 39,\n rating: 4.7,\n reviews: 128,\n badge: \"Bestseller\",\n },\n {\n id: \"p2\",\n name: \"Studio Mug\",\n meta: \"Home\",\n art: \"mug\",\n tint: \"butter\",\n price: 18,\n rating: 4.9,\n reviews: 96,\n },\n {\n id: \"p3\",\n name: \"Field Bottle\",\n meta: \"Outdoor\",\n art: \"bottle\",\n tint: \"mint\",\n price: 28,\n rating: 4.6,\n reviews: 61,\n badge: \"New\",\n },\n {\n id: \"p4\",\n name: \"Canvas Tote\",\n meta: \"Bags\",\n art: \"tote\",\n tint: \"lilac\",\n price: 24,\n rating: 4.8,\n reviews: 143,\n stock: 0,\n badge: \"Sold out\",\n badgeTone: \"neutral\",\n },\n ];\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n {P.map((x) => (\n <ProductCard\n key={x.id}\n {...x}\n wished={w[0].indexOf(x.id) > -1}\n added={a[0].indexOf(x.id) > -1}\n onWish={() => {\n w[1](w[0].indexOf(x.id) > -1 ? w[0].filter((i) => i !== x.id) : w[0].concat([x.id]));\n }}\n onAdd={() => {\n a[1](a[0].concat([x.id]));\n }}\n />\n ))}\n <div\n className=\"tf-panel\"\n style={{\n gridColumn: \"1 / -1\",\n flexDirection: \"row\",\n flexWrap: \"wrap\",\n gap: 24,\n alignItems: \"center\",\n }}\n >\n <Price value={32} compareAt={39} size=\"lg\" />\n <Rating value={4.6} count={61} />\n <StockBadge stock={84} />\n <StockBadge stock={6} />\n <StockBadge stock={0} />\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 8,\n }}\n >\n {ProductArt.kinds.map((k, i) => (\n <ProductArt\n key={k}\n kind={k}\n tint={([\"sky\", \"mint\", \"butter\", \"lilac\", \"blush\"] as const)[i % 5]}\n size={40}\n />\n ))}\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/ProductCard"},{"name":"RadioGroup","title":"Radio Group","group":"Forms","summary":"One choice out of a few, always visible.","description":"One choice out of a few, always visible. `RadioGroup` for 2–5 options with labels and hints, `Segmented` for 2–4 short options that switch a value inline.\n\nRadioGroup props: `label`, `options` (strings or {value, label, hint, disabled}), `value`/`defaultValue`, `onChange(value)`, `cards` (bordered tiles in a grid), `hint`.\nSegmented props: `options` (strings or {value, label, icon}), `value`/`defaultValue`, `onChange(value)`, `size` (\"sm\"), `block`, `label` (accessible name).\n\n- Six or more options, or options that need search: use Combobox.\n- Segmented sets a value (billing period, density); Tabs switch views of content. Don't mix them up.\n- `cards` when each option needs a sentence to explain it (plans, themes).\n","components":["RadioGroup","Segmented"],"code":"import * as React from \"react\";\nimport { RadioGroup, Segmented } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const a = React.useState(\"Team\"),\n b = React.useState(\"Monthly\");\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <RadioGroup\n label=\"Plan\"\n cards\n value={a[0]}\n onChange={a[1]}\n options={[\n {\n value: \"Starter\",\n label: \"Starter\",\n hint: \"€9 per seat · 3 projects\",\n },\n {\n value: \"Team\",\n label: \"Team\",\n hint: \"€24 per seat · unlimited\",\n },\n {\n value: \"Business\",\n label: \"Business\",\n hint: \"SSO, audit log, SLA\",\n },\n ]}\n />\n <div\n style={{\n display: \"flex\",\n gap: 48,\n flexWrap: \"wrap\",\n alignItems: \"flex-start\",\n }}\n >\n <RadioGroup\n label=\"Invoice reminders\"\n defaultValue=\"due\"\n options={[\n {\n value: \"never\",\n label: \"Never\",\n },\n {\n value: \"due\",\n label: \"On the due date\",\n },\n {\n value: \"weekly\",\n label: \"Weekly until paid\",\n hint: \"Stops when the bank feed matches it\",\n },\n ]}\n />\n <div className=\"tf-field\">\n <span className=\"tf-field-label\">Billing</span>\n <Segmented label=\"Billing\" value={b[0]} onChange={b[1]} options={[\"Monthly\", \"Yearly\"]} />\n </div>\n <div className=\"tf-field\">\n <span className=\"tf-field-label\">View</span>\n <Segmented\n label=\"View\"\n size=\"sm\"\n defaultValue=\"grid\"\n options={[\n {\n value: \"grid\",\n label: \"Grid\",\n icon: \"grid\",\n },\n {\n value: \"list\",\n label: \"List\",\n icon: \"menu\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/RadioGroup"},{"name":"RangeSlider","title":"Range Slider","group":"Forms","summary":"Form pieces added for search filters and admin settings: RangeSlider (two thumbs), StatusSelect (a status pill that opens a list), TilePicker (pick one or many visual options) and ColorField (hex input with contrast check).","description":"Form pieces added for search filters and admin settings: `RangeSlider` (two thumbs), `StatusSelect` (a status pill that opens a list), `TilePicker` (pick one or many visual options) and `ColorField` (hex input with contrast check).\n\nRangeSlider: `label`, `min`, `max`, `step`, `value` / `defaultValue` ([low, high]) + `onChange`, `unit`, `inputs` (false shows values instead of fields), `openEnd` (adds \"+\" at max), `format`.\nStatusSelect: `options` ({value, label, status}), `value` / `onChange`, `size` (\"sm\"), `label`.\nTilePicker: `options` ({value, label, meta, image | icon | art, tint, color, disabled}), `value` / `onChange`, `multiple`, `min` (tile width), `label`.\nColorField: `label`, `value` / `onChange`, `against` (colour to check contrast with; false hides it), `hint`.\n","components":["ColorField","RangeSlider","StatusSelect","TilePicker"],"code":"import * as React from \"react\";\nimport { ColorField, RangeSlider, StatusSelect, TilePicker } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const r = React.useState<[number, number]>([300000, 650000]),\n t = React.useState<string | string[]>(\"scandi\"),\n s = React.useState(\"read\");\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 20,\n }}\n >\n <RangeSlider\n label=\"Price\"\n min={0}\n max={1500000}\n step={10000}\n value={r[0]}\n onChange={r[1]}\n unit=\"€\"\n />\n <RangeSlider\n label=\"Living area\"\n min={20}\n max={300}\n defaultValue={[60, 140]}\n inputs={false}\n openEnd\n format={(v) => v + \" m²\"}\n />\n <div className=\"tf-row\">\n <StatusSelect\n value={s[0]}\n onChange={s[1]}\n label=\"Status\"\n options={[\n {\n value: \"unread\",\n label: \"Unread\",\n status: \"running\",\n },\n {\n value: \"read\",\n label: \"Read\",\n status: \"idle\",\n },\n {\n value: \"contacted\",\n label: \"Contacted\",\n status: \"ok\",\n },\n {\n value: \"rejected\",\n label: \"Not a fit\",\n status: \"danger\",\n },\n ]}\n />\n <ColorField value=\"#2f6fe0\" against=\"#ffffff\" />\n </div>\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Room style</b>\n <TilePicker\n value={t[0]}\n onChange={(v) => t[1](v ?? \"\")}\n label=\"Room style\"\n min={120}\n options={[\n {\n value: \"scandi\",\n label: \"Scandinavian\",\n tint: \"sky\",\n icon: \"home\",\n },\n {\n value: \"modern\",\n label: \"Modern\",\n tint: \"mint\",\n icon: \"grid\",\n },\n {\n value: \"industrial\",\n label: \"Industrial\",\n tint: \"butter\",\n icon: \"layers\",\n },\n {\n value: \"classic\",\n label: \"Classic\",\n tint: \"blush\",\n icon: \"star\",\n meta: \"Pro\",\n },\n {\n value: \"japandi\",\n label: \"Japandi\",\n tint: \"lilac\",\n icon: \"sun\",\n },\n {\n value: \"boho\",\n label: \"Boho\",\n disabled: true,\n icon: \"sparkle\",\n meta: \"Soon\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/RangeSlider"},{"name":"ReviewCard","title":"Review Card","group":"Commerce","summary":"Moderating customer reviews before and after they go live.","description":"Moderating customer reviews before and after they go live.\n\nProps: `author`, `verified`, `product`, `date`, `rating`, `title`, `text`, `state` (\"pending\" | \"published\" | \"hidden\" | \"flagged\"), `stateLabel`, `highlight` (words to mark, e.g. refund, scam), `reply`, `onPublish`, `onHide`, `onReply(text)`, `replyLabel`.\n\n- Pending reviews carry the signal edge until someone decides.\n- Hiding is reversible; deleting a review is not offered here.\n","components":["ReviewCard"],"code":"import * as React from \"react\";\nimport { ReviewCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <ReviewCard\n author=\"Jonas P.\"\n verified\n product=\"1,000 followers\"\n date=\"2 days ago\"\n rating={2}\n state=\"pending\"\n title=\"Slower than promised\"\n text=\"Arrived after two days instead of one hour. Support answered quickly though, so two stars. Would be great to get a refund for the delay.\"\n highlight={[\"refund\"]}\n onPublish={() => {}}\n onHide={() => {}}\n onReply={() => {}}\n />\n <ReviewCard\n author=\"Ida N.\"\n product=\"TikTok views\"\n date=\"Apr 12\"\n rating={5}\n state=\"published\"\n text=\"Fast, simple, worked exactly as described.\"\n reply=\"Thanks Ida, glad it worked!\"\n onHide={() => {}}\n onReply={() => {}}\n />\n </div>\n );\n}\n","path":"components/ReviewCard"},{"name":"RichTextEditor","title":"Rich Text Editor","group":"Forms","summary":"Formatted text for newsletters, product descriptions, legal pages and canned replies: toolbar, placeholder fields and a character count.","description":"Formatted text for newsletters, product descriptions, legal pages and canned replies: toolbar, placeholder fields and a character count.\n\nProps: `label`, `defaultValue` (HTML), `onChange(html)`, `placeholder`, `variables` ({key, label}: inserted as `{key}`), `maxLength`, `minHeight`, `footer`, `hint`, `error`, `disabled`.\n\n- The toolbar keeps to bold, italic, underline, heading, quote, lists, link, undo/redo. Anything richer belongs in a dedicated editor.\n- Built on Selection/Range, no `execCommand`. Shortcuts: ⌘/Ctrl B, I, U, K (link), Z and ⇧Z (undo, redo).\n- Links open a small field instead of a browser prompt; addresses without a scheme get https://.\n- Pasting inserts plain text; `onChange` returns HTML limited to p, h3, blockquote, ul, ol, li, b, i, u, a and br.\n- Still sanitize on the server before you send or store it.\n","components":["RichTextEditor"],"code":"import { RichTextEditor } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <RichTextEditor\n label=\"Newsletter text\"\n placeholder=\"Write something…\"\n maxLength={2000}\n defaultValue=\"<h3>Your boost is live</h3><p>Hi {first_name}, your <b>1,000 followers</b> start arriving within the next hour.</p><ul><li>Delivery spreads over 24 h</li><li>Refill guarantee for 30 days</li></ul>\"\n variables={[\n {\n key: \"first_name\",\n label: \"First name\",\n },\n {\n key: \"order_id\",\n label: \"Order number\",\n },\n {\n key: \"coupon\",\n label: \"Coupon code\",\n },\n ]}\n hint=\"Fields in braces are filled per recipient.\"\n />\n </div>\n );\n}\n","path":"components/RichTextEditor"},{"name":"RunHistory","title":"Run History","group":"Admin","summary":"Background work: RunHistory (scraping jobs, cron, imports) and WebhookEndpoint (an outgoing or incoming hook with its deliveries).","description":"Background work: `RunHistory` (scraping jobs, cron, imports) and `WebhookEndpoint` (an outgoing or incoming hook with its deliveries).\n\nRunHistory: `items` ({id, name, status, started, duration, by, counts: {value, label, tone}, detail (node; makes the row expandable)}), `label`.\nWebhookEndpoint: `url`, `events`, `status` + `statusLabel`, `successRate`, `enabled` + `onToggle`, `secret` (node, e.g. SecretField), `deliveries` ({id, code, event, time, ms}), `onRetry(delivery)`, `menu`, `defaultOpen`.\n","components":["LogView","RunHistory","WebhookEndpoint"],"code":"import { LogView, RunHistory, WebhookEndpoint } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <RunHistory\n items={[\n {\n id: 1,\n name: \"ImmoScout · Hamburg\",\n status: \"ok\",\n started: \"Today 06:00\",\n duration: \"4 m 12 s\",\n counts: [\n {\n value: 1284,\n label: \"checked\",\n },\n {\n value: 37,\n label: \"new\",\n tone: \"success\",\n },\n ],\n },\n {\n id: 2,\n name: \"Kleinanzeigen · Hamburg\",\n status: \"warn\",\n started: \"Today 06:00\",\n duration: \"7 m 40 s\",\n counts: [\n {\n value: 922,\n label: \"checked\",\n },\n {\n value: 12,\n label: \"new\",\n tone: \"success\",\n },\n {\n value: 18,\n label: \"failed\",\n tone: \"danger\",\n },\n ],\n detail: (\n <LogView\n title=\"errors\"\n lines={[\n {\n time: \"06:03:11\",\n text: \"HTTP 429 on page 14, waited 30 s\",\n level: \"warn\",\n },\n {\n time: \"06:05:40\",\n text: \"18 detail pages without price, skipped\",\n level: \"warn\",\n },\n ]}\n />\n ),\n },\n {\n id: 3,\n name: \"Daily cleanup\",\n status: \"danger\",\n started: \"Yesterday 03:00\",\n duration: \"12 s\",\n by: \"cron\",\n counts: [\n {\n value: 1,\n label: \"error\",\n tone: \"danger\",\n },\n ],\n detail: \"Database connection refused.\",\n },\n ]}\n />\n </div>\n <WebhookEndpoint\n url=\"https://api.homefield.com/hooks/stripe\"\n events={[\"checkout.session.completed\", \"invoice.paid\", \"customer.subscription.deleted\"]}\n status=\"ok\"\n statusLabel=\"Active\"\n successRate={98.7}\n onToggle={() => {}}\n onRetry={() => {}}\n defaultOpen\n menu={[\n {\n label: \"Send test event\",\n icon: \"send\",\n },\n {\n label: \"Rotate secret\",\n icon: \"key\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n deliveries={[\n {\n id: 1,\n code: 200,\n event: \"invoice.paid\",\n time: \"09:41\",\n ms: 182,\n },\n {\n id: 2,\n code: 500,\n event: \"checkout.session.completed\",\n time: \"09:12\",\n ms: 3012,\n },\n {\n id: 3,\n code: 200,\n event: \"customer.subscription.deleted\",\n time: \"Yesterday\",\n ms: 240,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/RunHistory"},{"name":"RunTrace","title":"Run Trace","group":"AI","summary":"Waterfall of one agent run: every model call, tool call, search and approval, with timing bars and expandable details.","description":"Waterfall of one agent run: every model call, tool call, search and approval, with timing bars and expandable details.\n\nProps: `steps` ({kind: \"model\" | \"tool\" | \"search\" | \"memory\" | \"approval\" | \"message\" | \"error\", label, meta, start (ms), ms, detail (string or node), status (\"done\" | \"running\" | \"error\")}), `defaultOpen` (index), `footer`.\n\n- The bar shows when a step ran inside the whole run, so slow tools are obvious at a glance.\n- Errors are blush; a running step pulses. Put model, tokens and cost in the footer.\n- Use it in run history and when an approval asks \"why does the agent want this?\".\n","components":["RunTrace"],"code":"import { RunTrace } from \"@twentyfirstmedia/ui-core\";\nimport type { TraceStep } from \"@twentyfirstmedia/ui-core\";\n\nconst steps: TraceStep[] = [\n {\n kind: \"message\",\n label: \"Task received\",\n meta: \"Triage 12 new support emails\",\n start: 0,\n ms: 30,\n detail: \"Trigger: schedule · every hour\",\n },\n {\n kind: \"model\",\n label: \"Planned the work\",\n meta: \"Claude Sonnet 5.5 · 2,140 tokens\",\n start: 70,\n ms: 1300,\n detail: \"1. Read new mail\\n2. Tag each thread\\n3. Draft replies for billing questions\",\n },\n {\n kind: \"tool\",\n label: \"Mailbox\",\n meta: \"Fetched 12 messages\",\n start: 1410,\n ms: 620,\n },\n {\n kind: \"search\",\n label: \"Knowledge base\",\n meta: \"Looked up 4 articles\",\n start: 2070,\n ms: 480,\n },\n {\n kind: \"model\",\n label: \"Wrote 4 drafts\",\n meta: \"9,820 tokens\",\n start: 2590,\n ms: 3100,\n },\n {\n kind: \"approval\",\n label: \"Waiting for approval\",\n meta: \"Autonomy: Drafts only\",\n start: 5730,\n ms: 0,\n status: \"running\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel tf-panel-flush\"\n style={{\n padding: 16,\n }}\n >\n <RunTrace\n steps={steps}\n defaultOpen={1}\n footer={\n <>\n <span>\n {\"Model \"}\n <b>Claude Sonnet 5.5</b>\n </span>\n <span>\n {\"Tokens \"}\n <b>11,960</b>\n </span>\n <span>\n {\"Cost \"}\n <b>€0.036</b>\n </span>\n </>\n }\n />\n </div>\n );\n}\n","path":"components/RunTrace"},{"name":"SearchAdPreview","title":"Search Ad Preview","group":"Admin","summary":"A text ad as it appears in search results, on phone and desktop, with the length limits checked live.","description":"A text ad as it appears in search results, on phone and desktop, with the length limits checked live.\n\nProps: `name`, `domain`, `paths` (up to 2), `headlines` (up to 3 shown), `descriptions` (up to 2), `sitelinks`, `device` / `onDevice`, `sponsoredLabel`.\n\n- Limits: headline 30, description 90, path 15 characters; anything longer is listed as a warning under the preview.\n- The preview imitates the result layout in neutral colours, not the search engine's brand.\n","components":["SearchAdPreview","TextField"],"code":"import * as React from \"react\";\nimport { SearchAdPreview, TextField } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const hl = React.useState([\n \"Real Instagram Followers\",\n \"Delivery in 1 Hour\",\n \"Trusted by 50,000 Creators\",\n ]),\n ds = React.useState([\n \"Grow your account with real followers. Secure payment, 30-day refill guarantee, German support.\",\n \"Start from €2.90. No password needed.\",\n ]);\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n {hl[0].map((v, i) => (\n <TextField\n key={i}\n label={\"Headline \" + (i + 1)}\n value={v}\n onChange={(e) => {\n const n = hl[0].slice();\n n[i] = e.target.value;\n hl[1](n);\n }}\n hint={v.length + \" / 30\"}\n />\n ))}\n {ds[0].map((v, i) => (\n <TextField\n key={\"d\" + i}\n label={\"Description \" + (i + 1)}\n value={v}\n onChange={(e) => {\n const n = ds[0].slice();\n n[i] = e.target.value;\n ds[1](n);\n }}\n hint={v.length + \" / 90\"}\n />\n ))}\n </div>\n <div className=\"tf-panel\">\n <SearchAdPreview\n name=\"Northwind\"\n domain=\"northwind.de\"\n paths={[\"instagram\", \"followers\"]}\n headlines={hl[0]}\n descriptions={ds[0]}\n sitelinks={[\"Buy likes\", \"Pricing\", \"Refill guarantee\", \"Support\"]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/SearchAdPreview"},{"name":"SecretField","title":"Secret Field","group":"Forms","summary":"SecretField shows a stored credential without exposing it; MoneyInput takes amounts in the locale's format with a VAT rate.","description":"`SecretField` shows a stored credential without exposing it; `MoneyInput` takes amounts in the locale's format with a VAT rate.\n\nSecretField: `prefix`, `last4`, `value` (shown only after `onReveal` resolves truthy; ask for a passkey there), `meta`, `onCopy`, `onRotate`.\nMoneyInput: `label`, `value` / `onChange(number)`, `currency`, `vat` + `onVat` + `vatOptions`, `showGross`, `hint`, `error`. Parses and formats with setLocale.\n\n- Never render a full secret by default; reveal is a step-up action.\n- Amounts are right-aligned tabular mono so columns line up.\n","components":["MoneyInput","SecretField"],"code":"import * as React from \"react\";\nimport { MoneyInput, SecretField } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const vat = React.useState(\"19\"),\n v = React.useState<number | null>(1240);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <SecretField\n prefix=\"sk_live_\"\n last4=\"4f2a\"\n meta=\"Added Mar 12\"\n onReveal={() => true}\n value=\"sk_live_51Hx…demo…4f2a\"\n onCopy={() => {}}\n onRotate={() => {}}\n />\n <MoneyInput\n label=\"Amount\"\n value={v[0]}\n onChange={v[1]}\n vat={vat[0]}\n onVat={vat[1]}\n vatOptions={[\n {\n value: \"19\",\n label: \"19%\",\n },\n {\n value: \"7\",\n label: \"7%\",\n },\n {\n value: \"0\",\n label: \"0%\",\n },\n {\n value: \"rc\",\n label: \"Reverse charge\",\n },\n ]}\n showGross\n hint=\"Net, as on the invoice\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/SecretField"},{"name":"SettingsLayout","title":"Settings Layout","group":"Foundations","summary":"Standard settings page: a label column with one sentence on what it does, controls on the right, one row per topic.","description":"Standard settings page: a label column with one sentence on what it does, controls on the right, one row per topic.\n\nProps: `sections` ({id, title, text, badge, children}), `footer`. Stacks on narrow screens.\n","components":["Button","SecretField","Segmented","SettingsLayout","Switch"],"code":"import { Button, SecretField, Segmented, SettingsLayout, Switch } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <SettingsLayout\n sections={[\n {\n title: \"Appearance\",\n text: \"Theme and density for this browser.\",\n children: <Segmented defaultValue=\"System\" options={[\"System\", \"Light\", \"Dark\"]} />,\n },\n {\n title: \"Notifications\",\n text: \"What reaches your phone.\",\n children: [\n <Switch key={1} spread defaultChecked>\n Approvals waiting on me\n </Switch>,\n <Switch key={2} spread>\n Weekly digest\n </Switch>,\n ],\n },\n {\n title: \"API key\",\n text: \"Used by the CLI.\",\n children: <SecretField prefix=\"hq_\" last4=\"9c1e\" onCopy={() => {}} />,\n },\n ]}\n footer={<Button icon=\"check\">Save changes</Button>}\n />\n </div>\n );\n}\n","path":"components/SettingsLayout"},{"name":"ShortcutsOverlay","title":"Shortcuts Overlay","group":"Overlays","summary":"The \"?\" overlay listing every keyboard shortcut, grouped by area.","description":"The \"?\" overlay listing every keyboard shortcut, grouped by area.\n\nProps: `open`, `onClose`, `title`, `groups` ({title, items: [label, keys[]]}). Esc closes.\n","components":["Button","ShortcutsOverlay"],"code":"import * as React from \"react\";\nimport { Button, ShortcutsOverlay } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const o = React.useState(true);\n return (\n <div\n style={{\n position: \"relative\",\n height: 460,\n }}\n >\n <Button\n onClick={() => {\n o[1](true);\n }}\n >\n Show shortcuts (?)\n </Button>\n <ShortcutsOverlay\n open={o[0]}\n onClose={() => {\n o[1](false);\n }}\n groups={[\n {\n title: \"Global\",\n items: [\n [\"Search\", [\"⌘\", \"K\"]],\n [\"Shortcuts\", [\"?\"]],\n ],\n },\n {\n title: \"Navigation\",\n items: [\n [\"Go to chats\", [\"G\", \"C\"]],\n [\"Go to approvals\", [\"G\", \"A\"]],\n ],\n },\n {\n title: \"Approvals\",\n items: [\n [\"Run as shown\", [\"A\"]],\n [\"With a change\", [\"B\"]],\n [\"Discard\", [\"C\"]],\n [\"Other instruction\", [\"D\"]],\n [\"Next / previous\", [\"J\", \"K\"]],\n ],\n },\n ]}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ShortcutsOverlay"},{"name":"SplitPane","title":"Split Pane","group":"Foundations","summary":"Resizable list–detail layout.","description":"Resizable list–detail layout. The divider is the hairline itself with a wider invisible grab area.\n\nProps: `left`, `right`, `defaultSize` (px), `min`, `max`, `storageKey` (remembers the width in this browser).\n\n- Use for mail, approvals and anything with a list and a reading pane. On phones stack the panes instead.\n","components":["InboxList","SplitPane"],"code":"import { InboxList, SplitPane } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel tf-panel-flush\"\n style={{\n height: 280,\n padding: 0,\n overflow: \"hidden\",\n }}\n >\n <SplitPane\n defaultSize={320}\n min={220}\n max={600}\n storageKey=\"demo\"\n left={\n <div\n style={{\n padding: 16,\n }}\n >\n <InboxList\n selected={1}\n items={[\n {\n id: 1,\n from: \"Ida Nolan\",\n time: \"08:40\",\n subject: \"Seat upgrade before renewal\",\n snippet: \"We are growing faster than planned…\",\n unread: true,\n },\n {\n id: 2,\n from: \"Jonas Petit\",\n time: \"Yesterday\",\n subject: \"Pricing review → Thursday?\",\n snippet: \"Growth wants to join…\",\n },\n ]}\n />\n </div>\n }\n right={\n <div\n style={{\n padding: 24,\n }}\n >\n <b>Drag the divider</b>\n <p className=\"tf-card-desc\">\n Double-click resets. ← → move it when focused. The width is remembered per storageKey.\n </p>\n </div>\n }\n />\n </div>\n );\n}\n","path":"components/SplitPane"},{"name":"StackedBarChart","title":"Stacked Bar Chart","group":"Charts","summary":"Bars split into parts of a whole per period, with a hover tooltip and legend.","description":"Bars split into parts of a whole per period, with a hover tooltip and legend.\n\nProps: `labels`, `series` ({name, tint, values}), `height`, `width`, `barWidth`, `format(v)`, `legend` (false hides), `label`.\n\n- Three series at most, ordered bottom-up from the steadiest to the most volatile.\n- Segments use the tints' `-ink` colors with a 2px gap, so neighbours differ in lightness, not hue alone.\n- Hovering a period dims the others and shows totals per series, top series first.\n","components":["StackedBarChart"],"code":"import { StackedBarChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">Revenue by plan</h3>\n <p className=\"tf-panel-sub\">Hover a bar for the split</p>\n </div>\n <StackedBarChart\n height={180}\n labels={[\"Nov\", \"Dec\", \"Jan\", \"Feb\", \"Mar\", \"Apr\"]}\n format={(v) => \"€\" + (v / 1000).toFixed(1) + \"k\"}\n series={[\n {\n name: \"Starter\",\n tint: \"sky\",\n values: [3100, 3400, 3300, 3900, 4200, 4500],\n },\n {\n name: \"Team\",\n tint: \"mint\",\n values: [9800, 10400, 11200, 11900, 12800, 13600],\n },\n {\n name: \"Business\",\n tint: \"lilac\",\n values: [5200, 5900, 6100, 6600, 7400, 8100],\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/StackedBarChart"},{"name":"StatCell","title":"Stat Cell","group":"Data","summary":"A KPI with its comparison: label, value, ▲/▼ delta that names what it compares to, optional sparkline.","description":"A KPI with its comparison: label, value, ▲/▼ delta that names what it compares to, optional sparkline.\n\nProps: `label`, `value`, `delta` (percent, signed), `deltaText` (override), `compare` (\"vs last Friday\"), `invert` (down is good: refunds, failures, cost), `spark` (numbers), `icon` + `iconTint`, `tint`.\n\n- Use 3–5 in a row, only when those numbers are the point of the screen.\n- Always say what the delta compares to; good is mint, bad is blush, decided by `invert`.\n","components":["StatCell"],"code":"import * as React from \"react\";\nimport { StatCell } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <StatCell\n label=\"Revenue today\"\n value=\"€4,812\"\n delta={12.4}\n compare=\"vs last Friday\"\n spark={[3, 4, 4, 6, 5, 7, 8]}\n icon=\"card\"\n iconTint=\"butter\"\n />\n <StatCell\n label=\"Orders\"\n value=\"128\"\n delta={4.1}\n compare=\"vs last Friday\"\n spark={[5, 6, 5, 7, 6, 8, 8]}\n />\n <StatCell\n label=\"Refund rate\"\n value=\"1.2%\"\n delta={-0.4}\n compare=\"vs last week\"\n invert\n spark={[4, 4, 3, 3, 2, 2, 2]}\n />\n <StatCell\n label=\"Failed orders\"\n value=\"7\"\n delta={40}\n compare=\"vs yesterday\"\n invert\n spark={[1, 2, 1, 3, 2, 4, 5]}\n tint=\"blush\"\n />\n </div>\n );\n}\n","path":"components/StatCell"},{"name":"StatTile","title":"Stat Tile","group":"Data","summary":"","description":"","components":["Stagger","StatTile"],"code":"import * as React from \"react\";\nimport { Stagger, StatTile } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"128\" label=\"Active members\" />\n <StatTile tint=\"mint\" value=\"24\" label=\"Live projects\" delta=\"+3 this week\" />\n <StatTile tint=\"placeholder\" value=\"9\" label=\"Needs review\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Revenue (MTD)\" />\n </Stagger>\n );\n}\n","path":"components/StatTile"},{"name":"StatusIndicator","title":"Status Indicator","group":"Foundations","summary":"State as shape and colour together, so it reads at a glance and without colour vision.","description":"State as shape and colour together, so it reads at a glance and without colour vision.\n\nProps: `status` (\"ok\" | \"running\" | \"signal\" | \"warn\" | \"danger\" | \"idle\"), `label`, `size` (\"sm\"). `StatusShape` is the bare shape.\n\n| status | shape | token | typical words |\n| --- | --- | --- | --- |\n| ok | dot | `ok` | Done, Online, Valid, Booked |\n| running | pulsing dot | `info` | Working, Syncing |\n| signal | diamond | `signal` | Waiting for approval, Needs a human |\n| warn | triangle | `warn` | Paused · budget, Expiring |\n| danger | square | `danger` | Failed, Offline |\n| idle | ring | `idle` | Ready, Inactive |\n\n- Always shape plus a word; drop the word only in dense columns whose header already names the state.\n- Only `signal` may draw attention unasked. Warnings and errors stay in their own area and never animate.\n- Badges use the same shapes: success dot, info dot, highlight diamond, warning triangle, danger square, neutral ring. Badge tones also accept the status names (ok, running, signal, warn, idle).\n","components":["Badge","StatusIndicator"],"code":"import * as React from \"react\";\nimport { Badge, StatusIndicator } from \"@twentyfirstmedia/ui-core\";\nimport type { Status, Tone } from \"@twentyfirstmedia/ui-core\";\n\nconst S: [Status, string][] = [\n [\"ok\", \"Done\"],\n [\"running\", \"Working\"],\n [\"signal\", \"Waiting for approval\"],\n [\"warn\", \"Paused · budget\"],\n [\"danger\", \"Failed\"],\n [\"idle\", \"Ready\"],\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n {S.map((s) => (\n <StatusIndicator key={s[0]} status={s[0]} label={s[1]} />\n ))}\n </div>\n <div className=\"tf-row\">\n {(\n [\n [\"success\", \"Live\"],\n [\"info\", \"Pending\"],\n [\"highlight\", \"Needs review\"],\n [\"warning\", \"Expiring\"],\n [\"danger\", \"Overdue\"],\n [\"neutral\", \"Draft\"],\n ] as [Tone, string][]\n ).map((b) => (\n <Badge key={b[0]} tone={b[0]}>\n {b[1]}\n </Badge>\n ))}\n </div>\n </div>\n );\n}\n","path":"components/StatusIndicator"},{"name":"Table","title":"Table","group":"Data","summary":"Quiet data table: uppercase captions, hairline rows, right-aligned tabular numbers.","description":"Quiet data table: uppercase captions, hairline rows, right-aligned tabular numbers.\n\nProps: `columns` ({key, label, align, render}), `rows`. Place inside a `surface` Panel; numbers right-aligned; status as Badges.\n","components":["Avatar","Badge","Table"],"code":"import { Avatar, Badge, Table } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n background: \"var(--surface)\",\n borderRadius: \"var(--radius-lg)\",\n padding: 8,\n }}\n >\n <Table\n columns={[\n {\n key: \"name\",\n label: \"Customer\",\n render: (r) => (\n <span className=\"tf-row\">\n <Avatar name={r.name} size={26} />\n {r.name}\n </span>\n ),\n },\n {\n key: \"plan\",\n label: \"Plan\",\n },\n {\n key: \"status\",\n label: \"Status\",\n render: (r) => <Badge tone={r.tone}>{r.status}</Badge>,\n },\n {\n key: \"mrr\",\n label: \"MRR\",\n align: \"right\",\n },\n ]}\n rows={[\n {\n name: \"Northwind\",\n plan: \"Team\",\n status: \"Active\",\n tone: \"success\",\n mrr: \"€1,240\",\n },\n {\n name: \"Blue Fern\",\n plan: \"Starter\",\n status: \"Trial\",\n tone: \"info\",\n mrr: \"€0\",\n },\n {\n name: \"Copperline\",\n plan: \"Business\",\n status: \"Past due\",\n tone: \"warning\",\n mrr: \"€3,900\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/Table"},{"name":"TextField","title":"Text Field","group":"Forms","summary":"Text input with label, required or optional mark, hint, and inline validation (error or success).","description":"Text input with label, required or optional mark, hint, and inline validation (error or success).\n\nProps: `label`, `required`, `optional`, `hint`, `error`, `success`, `icon` (leading IconName), `suffix` (unit text), `type`, `value`, `defaultValue`, `onChange`, `onBlur`, `disabled`, `readOnly`, `placeholder`, `autoComplete`, `autoFocus`, `id`, `name`.\n\nNative constraints go straight to the input, so the browser checks them and `Form` reads them: `min`, `max` and `step` (for `type=\"number\"`, dates and times), `minLength`, `maxLength`, `pattern`, and `inputMode` for the touch keyboard.\n\n```tsx\n<TextField label=\"Seats\" name=\"seats\" type=\"number\" min={1} max={50} step={1} required />\n```\n\n- `name` is the value's key in `Form` and where its validation message shows; `required` marks the label and sets the native attribute.\n- Validate on blur, not on every key: show the error once the person leaves the field, then clear it live as they fix it.\n- Error text says what is wrong in plain words (\"That doesn’t look like an email address\"), never \"Invalid input\".\n- Use `success` sparingly, for checks that took effort (a free username, a verified domain).\n- Mark the minority: if most fields are required, mark the optional ones instead.\n- `FormField` is the same shell without an input, for wrapping custom controls.\n","components":["TextField"],"code":"import * as React from \"react\";\nimport { TextField } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const e = React.useState(\"mara@\"),\n n = React.useState(\"Mara Ellis\");\n const bad = !/^[^@\\s]+@[^@\\s]+\\.[a-z]{2,}$/i.test(e[0]);\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <TextField\n label=\"Full name\"\n required\n value={n[0]}\n onChange={(x) => {\n n[1](x.target.value);\n }}\n success={n[0].length > 1 ? \"Looks good\" : null}\n />\n <TextField\n label=\"Email\"\n required\n icon=\"mail\"\n value={e[0]}\n onChange={(x) => {\n e[1](x.target.value);\n }}\n error={bad ? \"That doesn’t look like an email address\" : null}\n />\n <TextField label=\"Company\" optional placeholder=\"Acme GmbH\" hint=\"Shown on invoices\" />\n <TextField label=\"Monthly budget\" suffix=\"EUR\" defaultValue=\"1,200\" />\n <TextField\n label=\"Workspace URL\"\n defaultValue=\"halo.app/acme\"\n disabled\n hint=\"Ask an owner to change it\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/TextField"},{"name":"Toaster","title":"Toaster","group":"Overlays","summary":"Short confirmation that something happened, with optional Undo.","description":"Short confirmation that something happened, with optional Undo. Call `toast(...)` anywhere; render `<Toaster/>` once.\n\n`toast(text)` or `toast({ text, tone, icon, undo, action: {label, onClick}, duration })` returns an id; `toast.dismiss(id)`.\nToaster props: `position` (\"bottom\" default, \"top\").\n\n- Tone follows the status map: success for done, info for started, danger for removed or failed.\n- Offer `undo` for every reversible destructive action instead of a confirm dialog; Undo toasts last 6 s, others 4 s.\n- Hover pauses the timer; the thin line at the bottom shows time left.\n- One sentence, past tense, names the thing: \"Invoice #2097 sent\", not \"Success!\".\n","components":["Button","Toaster"],"code":"import * as React from \"react\";\nimport { Button, Toaster, toast } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n React.useEffect(() => {\n toast({\n text: \"Invoice #2097 sent to Northwind\",\n tone: \"success\",\n duration: 600000,\n });\n toast({\n text: \"2 people removed\",\n tone: \"danger\",\n icon: \"trash\",\n undo: () => {\n toast({\n text: \"Restored\",\n tone: \"info\",\n duration: 600000,\n });\n },\n duration: 600000,\n });\n }, []);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <div className=\"tf-row\">\n <Button\n variant=\"soft\"\n onClick={() => {\n toast(\"Profile saved\");\n }}\n >\n Success\n </Button>\n <Button\n variant=\"soft\"\n onClick={() => {\n toast({\n text: \"Build started for halo-web\",\n tone: \"info\",\n });\n }}\n >\n Info\n </Button>\n <Button\n variant=\"soft\"\n onClick={() => {\n toast({\n text: \"Project archived\",\n undo: () => {\n toast(\"Restored\");\n },\n });\n }}\n >\n With undo\n </Button>\n </div>\n <p\n className=\"tf-card-desc\"\n style={{\n margin: 0,\n }}\n >\n Toasts stack bottom-center above the dock, three at most. Hover pauses the timer.\n </p>\n <Toaster />\n </div>\n );\n}\nexport default App;\n","path":"components/Toaster"},{"name":"ToolCall","title":"Tool Call","group":"AI","summary":"Shows what an agent did between your question and its answer.","description":"Shows what an agent did between your question and its answer. `ToolCall` is one tool use; `Reasoning` is the agent's plan.\n\nToolCall props: `name`, `icon`, `status` (\"running\" | \"done\" | \"error\" | \"waiting\"), `summary` (one line, past tense when done), `duration`, `input`, `output` (string or object, shown as JSON), `defaultOpen`.\nReasoning props: `steps` (string[]) or `text`, `duration`, `streaming` (shimmering \"Thinking…\"), `defaultOpen`.\n\n- Collapsed by default: the one-line summary is what people read; details are there when they doubt it.\n- Status icon colors follow the status map: mint done, sky running, butter waiting, blush error.\n- Put them in ChatMessage's `before` slot, reasoning first, tools in the order they ran.\n","components":["Reasoning","ToolCall"],"code":"import { Reasoning, ToolCall } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 8,\n }}\n >\n <Reasoning\n steps={[\n \"Find the latest thread from Northwind\",\n \"Check seat pricing and the renewal date\",\n \"Draft a short reply, ask before sending\",\n ]}\n duration=\"1.4 s\"\n defaultOpen\n />\n <ToolCall\n name=\"Mailbox\"\n icon=\"mail\"\n status=\"done\"\n summary=\"Read 3 messages in “Seat upgrade”\"\n duration=\"0.8 s\"\n defaultOpen\n input={{\n query: \"from:ida@northwind.io\",\n limit: 3,\n }}\n output={{\n messages: 3,\n latest: \"We need 40 more seats before May 1.\",\n }}\n />\n <ToolCall\n name=\"Knowledge base\"\n icon=\"book\"\n status=\"running\"\n summary=\"Searching “seat upgrade pricing”\"\n />\n <ToolCall\n name=\"Run checks\"\n icon=\"terminal\"\n status=\"error\"\n summary=\"146 passed · 1 type error\"\n duration=\"4.2 s\"\n output=\"Seats.tsx:42 Type 'string' is not assignable to type 'number'.\"\n />\n <ToolCall\n name=\"Deployments\"\n icon=\"zap\"\n status=\"waiting\"\n summary=\"Needs approval to deploy to production\"\n />\n <Reasoning streaming />\n </div>\n );\n}\n","path":"components/ToolCall"},{"name":"Tooltip","title":"Tooltip","group":"Overlays","summary":"Tooltip names an icon-only control on hover or focus; Popover holds a small interactive panel anchored to a trigger.","description":"`Tooltip` names an icon-only control on hover or focus; `Popover` holds a small interactive panel anchored to a trigger.\n\nTooltip props: `label`, `shortcut`, `side` (\"top\" | \"bottom\" | \"left\" | \"right\"), one child element.\nPopover props: `trigger` (element, or `({open, toggle}) => element`), `open`/`onOpenChange` (optional control), `align` (\"start\" | \"end\"), `side` (\"top\"), `width`, `flush` (no padding, for Menus), `block`, children or `({close}) => children`.\n\n- Tooltips only add a name or a shortcut. Anything you must read or click goes in a Popover.\n- Tooltips appear after 350 ms on hover, immediately on keyboard focus, and close on Esc.\n- Popovers close on outside click and Esc. Use a Modal when the task needs more than a few fields.\n","components":["Button","Combobox","Icon","Popover","Tooltip"],"code":"import { Button, Combobox, Icon, Popover, Tooltip } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 40,\n paddingTop: 56,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n }}\n >\n <Tooltip label=\"Archive\" shortcut=\"E\">\n <button type=\"button\" className=\"tf-iconbtn tf-iconbtn-soft\" aria-label=\"Archive\">\n <Icon name=\"inbox\" />\n </button>\n </Tooltip>\n <Tooltip label=\"Star\">\n <button type=\"button\" className=\"tf-iconbtn tf-iconbtn-soft\" aria-label=\"Star\">\n <Icon name=\"star\" />\n </button>\n </Tooltip>\n <Tooltip label=\"Opens in a new tab\" side=\"bottom\">\n <button type=\"button\" className=\"tf-btn tf-btn-secondary\">\n View invoice\n </button>\n </Tooltip>\n <Tooltip label=\"Copy share link\" shortcut=\"⌘L\" defaultOpen>\n <button type=\"button\" className=\"tf-iconbtn tf-iconbtn-soft\" aria-label=\"Share\">\n <Icon name=\"share\" />\n </button>\n </Tooltip>\n </div>\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n alignItems: \"flex-start\",\n // room for the open popover, so the preview shows it whole\n minHeight: 260,\n }}\n >\n <Popover\n open\n width={280}\n trigger={\n <button type=\"button\" className=\"tf-btn tf-btn-soft\">\n <Icon name=\"users\" size={15} />\n Assign\n </button>\n }\n >\n <div\n className=\"tf-col\"\n style={{\n gap: 12,\n }}\n >\n <b>Assign to</b>\n <Combobox\n options={[\"Ava Vogel\", \"Ben Okafor\", \"Clara Haddad\"]}\n placeholder=\"Search people\"\n />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"flex-end\",\n }}\n >\n <Button size=\"sm\">Assign</Button>\n </div>\n </div>\n </Popover>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/Tooltip"},{"name":"TreeView","title":"Tree View","group":"Foundations","summary":"Hierarchies, in-place editing and object menus: TreeView, InlineEdit, ContextMenu.","description":"Hierarchies, in-place editing and object menus: `TreeView`, `InlineEdit`, `ContextMenu`.\n\nTreeView: `items` ({id, label, icon, meta, children}), `defaultOpen` (ids), `selected` + `onSelect`, `label`. Arrow keys open, close and move; meta sits right in mono.\nInlineEdit: `value`, `onSave(v)`, `options` (select instead of text), `mono`, `type`, `display(v)`, `align`. Double-click or Enter edits, Enter saves, Esc cancels.\nContextMenu: `items` (Menu items), children (the target). Right-click or a long press opens it at the pointer; destructive actions last.\n","components":["ContextMenu","InlineEdit","KeyValue","TreeView"],"code":"import * as React from \"react\";\nimport { ContextMenu, InlineEdit, KeyValue, TreeView } from \"@twentyfirstmedia/ui-core\";\n\nconst tree = [\n {\n id: \"fx\",\n label: \"FollowerX GmbH\",\n meta: \"€48,210\",\n children: [\n {\n id: \"fp\",\n label: \"Northwind\",\n meta: \"€31,400\",\n children: [\n {\n id: \"ads\",\n label: \"Ads\",\n meta: \"€9,800\",\n },\n {\n id: \"smm\",\n label: \"SMM providers\",\n meta: \"€14,100\",\n },\n ],\n },\n {\n id: \"ch\",\n label: \"Chatteo\",\n meta: \"€12,610\",\n },\n ],\n },\n {\n id: \"tv\",\n label: \"Trepte Ventures UG\",\n meta: \"€4,200\",\n },\n];\nfunction App() {\n const s = React.useState(\"ads\");\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n padding: 12,\n }}\n >\n <TreeView\n items={tree}\n defaultOpen={[\"fx\", \"fp\"]}\n selected={s[0]}\n onSelect={s[1]}\n label=\"Cost centres\"\n />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <span className=\"tf-card-desc\">Double-click or Enter to edit</span>\n <KeyValue\n items={[\n [\n \"Category\",\n <InlineEdit value=\"Software\" options={[\"Software\", \"Hosting\", \"Marketing\"]} />,\n ],\n [\"Amount\", <InlineEdit value=\"1,240.00\" mono />],\n ]}\n />\n <ContextMenu\n items={[\n {\n label: \"Open\",\n icon: \"eye\",\n },\n {\n label: \"Rename\",\n icon: \"edit\",\n shortcut: \"R\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n >\n <div\n style={{\n padding: 24,\n borderRadius: \"var(--radius-md)\",\n background: \"var(--canvas)\",\n textAlign: \"center\",\n }}\n >\n Right-click or long-press here\n </div>\n </ContextMenu>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/TreeView"},{"name":"TriggerList","title":"Trigger List","group":"AI","summary":"What starts agents and when: TriggerList and the week view ScheduleCalendar.","description":"What starts agents and when: `TriggerList` and the week view `ScheduleCalendar`.\n\nTriggerList: `items` ({id, type: \"schedule\" | \"event\" | \"webhook\" | \"mail\", rule, agent, by, next, enabled}), `onToggle(item)`, `onRun(item)`.\nScheduleCalendar: `events` ({day 0–6, start, end (hours), label, sub, tint, self}), `from`, `to`, `days` (labels; defaults to the locale's weekdays), `onOpen(event)`. Events an agent created for itself get a signal outline.\n","components":["ScheduleCalendar","TriggerList"],"code":"import { ScheduleCalendar, TriggerList } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <TriggerList\n onToggle={() => {}}\n onRun={() => {}}\n items={[\n {\n id: 1,\n type: \"schedule\",\n rule: \"Mon–Fri 08:00\",\n agent: \"Main Brain\",\n by: \"Config\",\n next: \"Tomorrow 08:00\",\n },\n {\n id: 2,\n type: \"mail\",\n rule: \"New mail to support@\",\n agent: \"Atlas\",\n by: \"Mara Ellis\",\n next: \"On event\",\n },\n {\n id: 3,\n type: \"webhook\",\n rule: \"Stripe dispute opened\",\n agent: \"Ledger\",\n by: \"Config\",\n next: \"On event\",\n enabled: false,\n },\n ]}\n />\n </div>\n <div className=\"tf-panel tf-scroll-x\">\n <ScheduleCalendar\n from={7}\n to={19}\n days={[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]}\n events={[\n {\n day: 0,\n start: 8,\n end: 9,\n label: \"Briefing\",\n tint: \"lilac\",\n },\n {\n day: 1,\n start: 8,\n end: 9,\n label: \"Briefing\",\n tint: \"lilac\",\n },\n {\n day: 2,\n start: 10,\n end: 12,\n label: \"Weekly report\",\n sub: \"Ledger\",\n tint: \"butter\",\n },\n {\n day: 4,\n start: 14,\n end: 15,\n label: \"Follow-ups\",\n tint: \"sky\",\n self: true,\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/TriggerList"},{"name":"TwoFactorSetup","title":"Two Factor Setup","group":"Forms","summary":"Turning on two-factor sign-in: scan, confirm with a code, save recovery codes.","description":"Turning on two-factor sign-in: scan, confirm with a code, save recovery codes.\n\nProps: `qr` (node: render the otpauth URI with your QR library), `secret`, `onVerify(code)` (return or resolve true/false), `codes` (recovery codes), `onDownload`, `onDone`, `step`, `scanText`, `codesText`.\n\n- The last step stays locked until the person confirms they saved the codes. (In this preview the code 123456 works.)\n","components":["TwoFactorSetup"],"code":"import { TwoFactorSetup } from \"@twentyfirstmedia/ui-core\";\nfunction qr() {\n const cells = [],\n n = 25;\n let seed = 7;\n for (let y = 0; y < n; y++)\n for (let x = 0; x < n; x++) {\n seed = (seed * 9301 + 49297) % 233280;\n const f = (x < 7 && y < 7) || (x > 17 && y < 7) || (x < 7 && y > 17);\n const on = f\n ? (x % 6 === 0 || y % 6 === 0 || (x % 6 > 1 && x % 6 < 5 && y % 6 > 1 && y % 6 < 5)) &&\n !(x === 7 || y === 7)\n : seed / 233280 > 0.5;\n if (on) cells.push(<rect key={x + \"-\" + y} x={x} y={y} width={1} height={1} />);\n }\n return (\n <svg\n viewBox=\"0 0 25 25\"\n width={144}\n height={144}\n fill=\"currentColor\"\n shapeRendering=\"crispEdges\"\n >\n {cells}\n </svg>\n );\n}\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <TwoFactorSetup\n qr={qr()}\n secret=\"JBSWY3DPEHPK3PXP\"\n codes={[\n \"8f2a-91c4\",\n \"3b7e-0d55\",\n \"a1c9-6f20\",\n \"7d44-e8b1\",\n \"c0f3-2a97\",\n \"5e18-b3d6\",\n \"9b62-4c0e\",\n \"e7a5-1f83\",\n ]}\n onVerify={(c) => c === \"123456\"}\n onDownload={() => {}}\n onDone={() => {}}\n />\n </div>\n );\n}\n","path":"components/TwoFactorSetup"},{"name":"UptimeBar","title":"Uptime Bar","group":"Data","summary":"Health at a glance: UptimeBar (a bar per day), Meter (usage against a limit), ServerCard, DeployPipeline.","description":"Health at a glance: `UptimeBar` (a bar per day), `Meter` (usage against a limit), `ServerCard`, `DeployPipeline`.\n\nUptimeBar: `days` (\"ok\" | \"warn\" | \"danger\" | \"none\" or {status, label, note}), `uptime` (percent), `caption`. Hover shows the day.\nMeter: `label`, `value`, `max`, `text`, `warn` (75), `danger` (90), `tone`.\nServerCard: `name`, `provider`, `address`, `logo`, `status`, `statusLabel`, `meters` ({label, value, max, text, unknown}; value null shows \"Unknown\"), `uptime` (UptimeBar props), `foot`.\nDeployPipeline: `stages` ({name, status: \"done\" | \"running\" | \"signal\" | \"danger\" | \"idle\", duration, action}).\n","components":["DeployPipeline","Meter","ServerCard","UptimeBar"],"code":"import * as React from \"react\";\nimport { DeployPipeline, Meter, ServerCard, UptimeBar } from \"@twentyfirstmedia/ui-core\";\nimport type { UptimeDay } from \"@twentyfirstmedia/ui-core\";\n\nconst days: UptimeDay[] = [];\nfor (let i = 0; i < 90; i++)\n days.push(\n i === 61\n ? {\n status: \"danger\",\n note: \"14 min down\",\n }\n : i === 40 || i === 77\n ? {\n status: \"warn\",\n note: \"slow responses\",\n }\n : {\n status: i < 6 ? \"none\" : \"ok\",\n },\n );\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <ServerCard\n name=\"ax41-fsn1\"\n provider=\"Hetzner\"\n address=\"95.217.14.80\"\n status=\"ok\"\n statusLabel=\"Online\"\n meters={[\n {\n label: \"CPU\",\n value: 34,\n text: \"34%\",\n },\n {\n label: \"RAM\",\n value: 81,\n text: \"52 / 64 GB\",\n },\n {\n label: \"Disk\",\n value: null,\n },\n ]}\n foot=\"Last deploy 2 h ago · #482\"\n />\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <b>northwind.de</b>\n <UptimeBar days={days} uptime={99.96} />\n <Meter label=\"Budget\" value={82} text=\"€820 / €1,000\" />\n <DeployPipeline\n stages={[\n {\n name: \"Pull\",\n status: \"done\",\n duration: \"4 s\",\n },\n {\n name: \"Build\",\n status: \"done\",\n duration: \"1 m 12 s\",\n },\n {\n name: \"Approve\",\n status: \"signal\",\n },\n {\n name: \"Release\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/UptimeBar"},{"name":"ValuationCard","title":"Valuation Card","group":"Real estate","summary":"Real-estate figures: ValuationCard (estimate with range and asking price), SavedSearch (a search that runs on its own and reports new hits) and EnergyClass (the energy label scale).","description":"Real-estate figures: `ValuationCard` (estimate with range and asking price), `SavedSearch` (a search that runs on its own and reports new hits) and `EnergyClass` (the energy label scale).\n\nValuationCard: `value`, `low`, `high`, `asking`, `perSqm`, `compare`, `currency`, `confidence` (0–1) or `confidenceLabel`, `facts` (KeyValue items), `note`, `title`, `action`.\nSavedSearch: `name`, `criteria` (strings), `lastRun`, `frequency`, `newCount`, `enabled` + `onToggle`, `onEdit`, `onOpen`, `openLabel`, `icon`, `tint`.\nEnergyClass: `value`, `kwh`, `kind`, `scale` (default A+…H).\n","components":["EnergyClass","SavedSearch","ValuationCard"],"code":"import * as React from \"react\";\nimport { EnergyClass, SavedSearch, ValuationCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <ValuationCard\n value={612000}\n low={571000}\n high={655000}\n asking={649000}\n perSqm={7286}\n compare=\"district average €6,940\"\n confidence={0.78}\n facts={[\n [\"Comparables\", \"42 sales, last 18 months\"],\n [\"Method\", \"Comparable sales\"],\n [\"As of\", \"Nov 8, 2026\"],\n ]}\n note=\"An estimate, not an appraisal.\"\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <SavedSearch\n name=\"Eppendorf 3 rooms\"\n criteria={[\n \"Buy\",\n \"Hamburg-Eppendorf\",\n \"€400k–700k\",\n \"70–110 m²\",\n \"3+ rooms\",\n \"Private sellers\",\n ]}\n lastRun=\"06:00\"\n frequency=\"Daily mail\"\n newCount={4}\n onToggle={() => {}}\n onEdit={() => {}}\n onOpen={() => {}}\n />\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Energy</b>\n <EnergyClass value=\"C\" kwh={92} kind=\"Consumption · gas\" />\n </div>\n </div>\n </div>\n );\n}\n","path":"components/ValuationCard"},{"name":"VariantPicker","title":"Variant Picker","group":"Commerce","summary":"Pick a product option: colour swatches or text pills (sizes, materials).","description":"Pick a product option: colour swatches or text pills (sizes, materials).\n\nProps: `label`, `type` (\"color\" for swatches), `options` (strings or {value, label, color, disabled}), `value` / `defaultValue`, `onChange`.\n\n- The legend repeats the current choice (\"Colour Sky\") so swatches don't need text.\n- Sold-out options stay visible but struck through and disabled, so people know the size exists.\n","components":["VariantPicker"],"code":"import { VariantPicker } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <VariantPicker\n label=\"Colour\"\n type=\"color\"\n defaultValue=\"Sky\"\n options={[\n {\n value: \"Sky\",\n label: \"Sky\",\n color: \"#9fc3f0\",\n },\n {\n value: \"Ink\",\n label: \"Ink\",\n color: \"#1d1d1f\",\n },\n {\n value: \"Bone\",\n label: \"Bone\",\n color: \"#ece6d9\",\n },\n {\n value: \"Moss\",\n label: \"Moss\",\n color: \"#7d9a5e\",\n disabled: true,\n },\n ]}\n />\n <VariantPicker\n label=\"Size\"\n defaultValue=\"M\"\n options={[\n {\n value: \"XS\",\n label: \"XS\",\n disabled: true,\n },\n \"S\",\n \"M\",\n \"L\",\n \"XL\",\n ]}\n />\n </div>\n );\n}\n","path":"components/VariantPicker"},{"name":"VoiceMode","title":"Voice Mode","group":"AI","summary":"Talking to an agent instead of typing: same agent, same tools, same approvals, same thread.","description":"Talking to an agent instead of typing: same agent, same tools, same approvals, same thread.\n\nProps: `name`, `tint`, `state` (\"listening\" | \"thinking\" | \"speaking\" | \"idle\"), `stateLabel`, `transcript`, `muted`, `onMute`, `onEnd`.\n\n- Approvals asked by voice still show the ApprovalCard; money needs the passkey on screen.\n","components":["Segmented","VoiceMode"],"code":"import * as React from \"react\";\nimport { Segmented, VoiceMode } from \"@twentyfirstmedia/ui-core\";\nimport type { VoiceModeProps } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const s = React.useState<VoiceModeProps[\"state\"]>(\"listening\");\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <Segmented\n value={s[0]}\n onChange={(v) => s[1](v as VoiceModeProps[\"state\"])}\n options={[\n { value: \"listening\", label: \"Listening\" },\n { value: \"thinking\", label: \"Thinking\" },\n { value: \"speaking\", label: \"Speaking\" },\n { value: \"idle\", label: \"Idle\" },\n ]}\n />\n <VoiceMode\n name=\"Atlas\"\n tint=\"sky\"\n state={s[0]}\n transcript={s[0] === \"speaking\" ? \"“I drafted the reply. Should I send it?”\" : null}\n onMute={() => {}}\n onEnd={() => {}}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/VoiceMode"},{"name":"Accordion","title":"Accordion","group":"Foundations","summary":"Stacked sections that open and close.","description":"Stacked sections that open and close. One section at a time by default, or several with `multiple`.\n\n- `cards` (default) shows each section as a card; `flush` uses hairlines, for use inside a panel.\n- Keyboard: Tab reaches each header, ↑/↓ move between headers, Home/End jump, Enter or Space toggles.\n- Headers are buttons with `aria-expanded`; panels are regions labelled by their header.\n","components":["Accordion","Badge","Panel"],"code":"import * as React from \"react\";\nimport { Accordion, Badge, Panel } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", gap: 24, alignItems: \"start\" } as React.CSSProperties}\n >\n <Accordion\n defaultValue={[\"billing\"]}\n items={[\n {\n id: \"billing\",\n title: \"How does billing work?\",\n content:\n \"You pay per seat and month. Seats added mid-cycle are prorated to the next invoice.\",\n },\n {\n id: \"export\",\n title: \"Can I export my data?\",\n content: \"Yes. Every table exports to CSV, and the API returns the same fields.\",\n },\n {\n id: \"sso\",\n title: \"Do you support single sign-on?\",\n meta: <Badge tone=\"info\">Team plan</Badge>,\n content: \"SAML and OIDC are included from the Team plan up.\",\n },\n { id: \"old\", title: \"Legacy importer\", disabled: true, content: \"\" },\n ]}\n />\n <Panel title=\"Settings\">\n <Accordion\n variant=\"flush\"\n multiple\n defaultValue={[\"n\", \"p\"]}\n items={[\n {\n id: \"n\",\n icon: \"bell\",\n title: \"Notifications\",\n content: \"Approvals and overdue invoices reach you by mail and push.\",\n },\n {\n id: \"p\",\n icon: \"lock\",\n title: \"Privacy\",\n content: \"Workspace data stays in the EU region.\",\n },\n {\n id: \"a\",\n icon: \"code\",\n title: \"API access\",\n content: \"Two active keys. Rotate them from Developer settings.\",\n },\n ]}\n />\n </Panel>\n </div>\n );\n}\n","path":"components/Accordion"},{"name":"Separator","title":"Separator","group":"Foundations","summary":"Small layout primitives.","description":"Small layout primitives.\n\n- **Separator**: a hairline between groups, horizontal or vertical, optionally with a label in the middle (\"or\").\n- **Label**: the field label on its own, with a required mark or a muted hint (\"optional\").\n- **AspectRatio**: a box that keeps its ratio (16 : 9 by default) for images, video and placeholders.\n","components":["Separator","Label","AspectRatio","Input","Button"],"code":"import * as React from \"react\";\nimport { AspectRatio, Button, Input, Label, Separator } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", gap: 24, alignItems: \"start\" } as React.CSSProperties}\n >\n <div className=\"tf-panel\" style={{ gap: 16 }}>\n <div className=\"tf-col\" style={{ gap: 8 }}>\n <Label htmlFor=\"sep-mail\" required>\n Work email\n </Label>\n <Input id=\"sep-mail\" placeholder=\"you@company.com\" />\n </div>\n <div className=\"tf-col\" style={{ gap: 8 }}>\n <Label htmlFor=\"sep-team\" hint=\"optional\">\n Team name\n </Label>\n <Input id=\"sep-team\" placeholder=\"Design\" />\n </div>\n <Separator label=\"or\" />\n <Button variant=\"secondary\" icon=\"globe\">\n Continue with Google\n </Button>\n <div className=\"tf-row\" style={{ gap: 12, alignItems: \"center\", flexWrap: \"nowrap\" }}>\n <span className=\"tf-card-desc\">Docs</span>\n <Separator orientation=\"vertical\" />\n <span className=\"tf-card-desc\">Status</span>\n <Separator orientation=\"vertical\" />\n <span className=\"tf-card-desc\">Changelog</span>\n </div>\n </div>\n <div className=\"tf-panel\" style={{ gap: 12 }}>\n <AspectRatio ratio={16 / 9}>\n <div\n className=\"tf-gallery-ph tf-t-sky\"\n style={{ height: \"100%\", display: \"grid\", placeItems: \"center\" }}\n >\n 16 : 9\n </div>\n </AspectRatio>\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", \"--cols-sm\": \"1fr 1fr\", gap: 12 } as React.CSSProperties}\n >\n <AspectRatio ratio={1}>\n <div\n className=\"tf-gallery-ph tf-t-mint\"\n style={{ height: \"100%\", display: \"grid\", placeItems: \"center\" }}\n >\n 1 : 1\n </div>\n </AspectRatio>\n <AspectRatio ratio={4 / 5}>\n <div\n className=\"tf-gallery-ph tf-t-butter\"\n style={{ height: \"100%\", display: \"grid\", placeItems: \"center\" }}\n >\n 4 : 5\n </div>\n </AspectRatio>\n </div>\n </div>\n </div>\n );\n}\n","path":"components/Separator"},{"name":"ButtonGroup","title":"Button Group","group":"Foundations","summary":"Attached buttons that act as one control: undo/redo, zoom, or a split button whose arrow opens a menu.","description":"Attached buttons that act as one control: undo/redo, zoom, or a split button whose arrow opens a menu.\n\n- Children are regular `Button`, `IconButton` or a `Popover` trigger; the group only joins their corners.\n- `label` names the group for assistive tech; `orientation=\"vertical\"` stacks them.\n","components":["ButtonGroup","Button","IconButton","Popover","Menu"],"code":"import { Button, ButtonGroup, IconButton, Menu, Popover } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ gap: 20, paddingBottom: 200 }}>\n <div className=\"tf-row\" style={{ gap: 16 }}>\n <ButtonGroup label=\"History\">\n <Button variant=\"secondary\" icon=\"undo\">\n Undo\n </Button>\n <Button variant=\"secondary\" icon=\"redo\">\n Redo\n </Button>\n </ButtonGroup>\n <ButtonGroup label=\"Zoom\">\n <IconButton icon=\"minus\" label=\"Zoom out\" variant=\"outline\" />\n <Button variant=\"secondary\">100%</Button>\n <IconButton icon=\"plus\" label=\"Zoom in\" variant=\"outline\" />\n </ButtonGroup>\n </div>\n <ButtonGroup label=\"Publish\">\n <Button icon=\"send\">Publish</Button>\n <Popover\n open\n flush\n align=\"end\"\n trigger={<IconButton icon=\"chevronDown\" label=\"More publish options\" variant=\"solid\" />}\n >\n <Menu\n items={[\n { label: \"Schedule…\", icon: \"calendar\" },\n { label: \"Publish to staging\", icon: \"upload\" },\n \"-\",\n { label: \"Save as draft\", icon: \"file\" },\n ]}\n />\n </Popover>\n </ButtonGroup>\n </div>\n );\n}\n","path":"components/ButtonGroup"},{"name":"ScrollArea","title":"Scroll Area","group":"Foundations","summary":"A scrolling box with thin scrollbars in the kit's colours, vertical, horizontal or both.","description":"A scrolling box with thin scrollbars in the kit's colours, vertical, horizontal or both.\n\n- The area is a focusable region with a name (`label`), so it scrolls with the arrow keys.\n- Scrolling stays inside the box (no scroll chaining into the page).\n","components":["ScrollArea","Avatar","Separator"],"code":"import * as React from \"react\";\nimport { Avatar, ScrollArea, Separator } from \"@twentyfirstmedia/ui-core\";\n\nconst PEOPLE = [\n \"Mara Ellis\",\n \"Jonah Petit\",\n \"Sun Lee\",\n \"Rui Costa\",\n \"Ava Brandt\",\n \"Ida Nolan\",\n \"Theo Marsh\",\n \"Clara Haddad\",\n \"Ben Okafor\",\n \"Lena Vogt\",\n \"Omar Said\",\n \"Pia Lund\",\n];\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"320px 1fr\", gap: 24, alignItems: \"start\" } as React.CSSProperties}\n >\n <div className=\"tf-panel\" style={{ gap: 12, padding: 16 }}>\n <b>Team · {PEOPLE.length}</b>\n <ScrollArea label=\"Team members\" height={240}>\n <div className=\"tf-col\" style={{ gap: 4 }}>\n {PEOPLE.map((n) => (\n <div\n key={n}\n className=\"tf-row\"\n style={{ gap: 12, padding: \"6px 4px\", flexWrap: \"nowrap\" }}\n >\n <Avatar name={n} size={28} />\n <span>{n}</span>\n </div>\n ))}\n </div>\n </ScrollArea>\n </div>\n <div className=\"tf-panel\" style={{ gap: 12, padding: 16 }}>\n <b>Releases</b>\n <ScrollArea label=\"Releases\" orientation=\"horizontal\">\n <div className=\"tf-row\" style={{ gap: 12, flexWrap: \"nowrap\", paddingBottom: 8 }}>\n {[\"v2.4\", \"v2.3\", \"v2.2\", \"v2.1\", \"v2.0\", \"v1.9\", \"v1.8\"].map((v, i) => (\n <div key={v} className=\"tf-card\" style={{ minWidth: 160, minHeight: 0, gap: 4 }}>\n <b>{v}</b>\n <span className=\"tf-card-desc\">\n {[\"Apr\", \"Mar\", \"Feb\", \"Jan\", \"Dec\", \"Nov\", \"Oct\"][i]} release\n </span>\n </div>\n ))}\n </div>\n </ScrollArea>\n <Separator />\n <span className=\"tf-card-desc\">\n Scroll sideways, or focus the area and use the arrow keys.\n </span>\n </div>\n </div>\n );\n}\n","path":"components/ScrollArea"},{"name":"ToggleGroup","title":"Toggle Group","group":"Forms","summary":"On/off buttons.","description":"On/off buttons. A **Toggle** is a single pressed state (star, pin, follow). A **ToggleGroup** joins\nseveral: `type=\"single\"` allows at most one, `type=\"multiple\"` any number (text styles, filters).\n\n- Pressed state is `aria-pressed`; icon-only toggles take their name from `label` (or the option's `hint`).\n- ←/→, Home and End move between the toggles of a group.\n- For choosing exactly one of a few views, `Segmented` is the closer fit.\n","components":["ToggleGroup","Toggle"],"code":"import * as React from \"react\";\nimport { Toggle, ToggleGroup } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n const [marks, setMarks] = React.useState([\"bold\"]);\n return (\n <div className=\"tf-panel\" style={{ gap: 20 }}>\n <div className=\"tf-row\" style={{ gap: 12 }}>\n <ToggleGroup\n label=\"Text style\"\n type=\"multiple\"\n value={marks}\n onChange={setMarks}\n items={[\n { value: \"bold\", label: \"\", icon: \"bold\", hint: \"Bold\" },\n { value: \"list\", label: \"\", icon: \"list\", hint: \"List\" },\n { value: \"quote\", label: \"\", icon: \"quote\", hint: \"Quote\" },\n ]}\n />\n <ToggleGroup\n label=\"View\"\n defaultValue={[\"week\"]}\n items={[\"Day\", \"Week\", \"Month\"].map((x) => ({ value: x.toLowerCase(), label: x }))}\n />\n </div>\n <div className=\"tf-row\" style={{ gap: 8 }}>\n <Toggle icon=\"star\" label=\"Starred\" defaultPressed />\n <Toggle icon=\"pin\" label=\"Pinned\" />\n <Toggle icon=\"bell\">Follow thread</Toggle>\n <Toggle size=\"sm\" icon=\"eye\" disabled>\n Watching\n </Toggle>\n </div>\n </div>\n );\n}\n","path":"components/ToggleGroup"},{"name":"HoverCard","title":"Hover Card","group":"Overlays","summary":"A preview card that opens when the pointer rests on a trigger (a name, a link) or when it gets keyboard focus, and closes when the pointer or focus leaves or on Escape.","description":"A preview card that opens when the pointer rests on a trigger (a name, a link) or when it gets keyboard\nfocus, and closes when the pointer or focus leaves or on Escape.\n\n- Use it for supplementary details only; the trigger must still lead somewhere on its own.\n- `openDelay` and `closeDelay` tune the timing; the card stays inside the screen on narrow layouts.\n","components":["HoverCard","Avatar","Badge"],"code":"import { Avatar, Badge, HoverCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ gap: 16, paddingBottom: 180 }}>\n <p style={{ margin: 0 }}>\n Assigned to{\" \"}\n <HoverCard trigger={<a href=\"#\">@mara</a>} side=\"bottom\" align=\"start\">\n <div className=\"tf-row\" style={{ gap: 12, flexWrap: \"nowrap\", alignItems: \"flex-start\" }}>\n <Avatar name=\"Mara Ellis\" size={40} />\n <div className=\"tf-col\" style={{ gap: 4 }}>\n <b>Mara Ellis</b>\n <span className=\"tf-card-desc\">Owner · Leadership · joined Mar 2024</span>\n <div className=\"tf-row\" style={{ gap: 6 }}>\n <Badge tone=\"success\">Online</Badge>\n <Badge>Berlin</Badge>\n </div>\n </div>\n </div>\n </HoverCard>{\" \"}\n by the pricing workflow. Hover or focus the name to see the card.\n </p>\n </div>\n );\n}\n","path":"components/HoverCard"},{"name":"Carousel","title":"Carousel","group":"Media","summary":"A row of slides with previous/next buttons and dots.","description":"A row of slides with previous/next buttons and dots. Swipe or scroll on touch, ←/→ on the keyboard.\n\n- `perView` shows several slides on wide screens; phones always show one (with a peek of the next).\n- `loop` wraps around at the ends.\n- The carousel is a labelled region; each slide is announced as \"n of total\".\n","components":["Carousel","ProductArt"],"code":"import { Carousel, ProductArt } from \"@twentyfirstmedia/ui-core\";\n\nconst ITEMS = [\n [\"tee\", \"sky\", \"Halo Cotton Tee\", \"€32\"],\n [\"mug\", \"butter\", \"Studio Mug\", \"€18\"],\n [\"bottle\", \"mint\", \"Field Bottle\", \"€28\"],\n [\"tote\", \"lilac\", \"Canvas Tote\", \"€24\"],\n [\"cap\", \"blush\", \"Trail Cap\", \"€22\"],\n] as const;\n\nexport default function Preview() {\n return (\n <div className=\"tf-col\" style={{ gap: 32 }}>\n <Carousel\n label=\"Featured products\"\n perView={3}\n slides={ITEMS.map(([k, t, name, price]) => (\n <div key={k} className=\"tf-card\" style={{ minHeight: 0, gap: 8 }}>\n <ProductArt kind={k} tint={t} />\n <b>{name}</b>\n <span className=\"tf-card-desc\">{price}</span>\n </div>\n ))}\n />\n <Carousel\n label=\"Onboarding\"\n loop\n slides={[\"Invite your team\", \"Connect your mailbox\", \"Approve your first draft\"].map(\n (t, i) => (\n <div\n key={t}\n className=\"tf-panel\"\n style={{\n minHeight: 160,\n justifyContent: \"center\",\n alignItems: \"center\",\n textAlign: \"center\",\n }}\n >\n <span className=\"tf-card-desc\">Step {i + 1} of 3</span>\n <b style={{ fontSize: 20 }}>{t}</b>\n </div>\n ),\n )}\n />\n </div>\n );\n}\n","path":"components/Carousel"},{"name":"Ambient","title":"Ambient","group":"Navigation","summary":"A calm, living background for glass UI: soft colour fields from the kit (pastels in Halo, a faint aurora in Pro) that drift slowly, a photo or video under a scrim, or your own layer.","description":"A calm, living background for glass UI: soft colour fields from the kit (pastels in Halo, a faint aurora in Pro) that drift slowly, a photo or video under a scrim, or your own layer.\n\n- Put it first inside a container: it fills the container (or the window with `fixed`) and sits behind the content.\n- With `<UIProvider surface=\"glass\">` around it, panels, cards, tiles, the app shell and floating layers turn into glass over it; text keeps 4.5:1 over any backdrop.\n- The fields drift only with `transform`, stop with motion off or reduced motion and pause off screen. `animate={false}` keeps them still.\n- `colors` takes any CSS colours (brand colours, `var(--chart-1)`); by default the kit's `--ambient-1…4`, themable like every token.\n- `src` shows a photo, `src` with `video` a muted looping video; children replace both with your own layer (canvas, WebGL, a map).\n","components":["Ambient","Badge","Button","Panel","StatTile","UIProvider"],"code":"import * as React from \"react\";\nimport { Ambient, Badge, Button, Panel, StatTile, UIProvider } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <UIProvider scope=\"subtree\" surface=\"glass\">\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(auto-fit, minmax(min(100%, 240px), 1fr))\",\n gap: 16,\n padding: 24,\n borderRadius: \"var(--radius-xl)\",\n overflow: \"hidden\",\n }}\n >\n <Ambient />\n <StatTile value=\"7h 42m\" label=\"Sleep\" tint=\"sky\" delta=\"+18 min\" />\n <StatTile value=\"12 days\" label=\"Streak\" tint=\"butter\" />\n <Panel\n title=\"Breathe\"\n subtitle=\"Four minutes, box breathing\"\n action={<Badge tone=\"success\">New</Badge>}\n >\n <Button variant=\"primary\" icon=\"play\">\n Start\n </Button>\n </Panel>\n </div>\n </UIProvider>\n );\n}\n","path":"components/Ambient"},{"name":"Menubar","title":"Menubar","group":"Navigation","summary":"A desktop-style row of menus (File, Edit, View).","description":"A desktop-style row of menus (File, Edit, View). Each menu is a regular `Menu`, so items can have\nicons, shortcuts, checkbox and radio states and submenus.\n\n- Keyboard: ←/→ move between menus (and switch the open one), ↓/Enter/Space open, ↑/↓ move inside,\n → and ← open and close submenus, Escape closes and returns to the bar.\n- Moving the pointer across the bar switches menus once one is open.\n","components":["Menubar","Menu"],"code":"import { Menubar } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div style={{ minHeight: 260 }}>\n <Menubar\n label=\"Editor\"\n menus={[\n {\n label: \"File\",\n items: [\n { label: \"New page\", icon: \"file\", shortcut: \"⌘N\" },\n { label: \"Open…\", icon: \"folder\", shortcut: \"⌘O\" },\n {\n label: \"Export\",\n icon: \"download\",\n items: [{ label: \"PDF\" }, { label: \"Markdown\" }, { label: \"HTML\" }],\n },\n \"-\",\n { label: \"Close\", shortcut: \"⌘W\" },\n ],\n },\n {\n label: \"Edit\",\n items: [\n { label: \"Undo\", icon: \"undo\", shortcut: \"⌘Z\" },\n { label: \"Redo\", icon: \"redo\", shortcut: \"⇧⌘Z\" },\n \"-\",\n { label: \"Find…\", icon: \"search\", shortcut: \"⌘F\" },\n ],\n },\n {\n label: \"View\",\n items: [\n { label: \"Show sidebar\", type: \"checkbox\", checked: true },\n { label: \"Show comments\", type: \"checkbox\" },\n \"-\",\n { label: \"Zoom in\", shortcut: \"⌘+\" },\n { label: \"Zoom out\", shortcut: \"⌘−\" },\n ],\n },\n {\n label: \"Help\",\n items: [{ label: \"Keyboard shortcuts\", shortcut: \"?\" }, { label: \"What's new\" }],\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/Menubar"},{"name":"NavigationMenu","title":"Navigation Menu","group":"Navigation","summary":"Site navigation for marketing pages: plain links and items that open a panel of links with icons and descriptions, optionally with a highlighted card.","description":"Site navigation for marketing pages: plain links and items that open a panel of links with icons\nand descriptions, optionally with a highlighted card.\n\n- Panels open on hover and on click; Escape closes the panel and returns focus to its trigger.\n- Use `TopBar` for app navigation inside the product and this for public pages.\n","components":["NavigationMenu","Button"],"code":"import { Button, NavigationMenu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ gap: 16, minHeight: 320 }}>\n <div className=\"tf-row\" style={{ justifyContent: \"space-between\", gap: 16 }}>\n <NavigationMenu\n label=\"Site\"\n items={[\n {\n label: \"Product\",\n feature: (\n <>\n <b>Halo Workspace</b>\n <span className=\"tf-card-desc\">\n One place for approvals, billing and the team.\n </span>\n </>\n ),\n links: [\n { label: \"Approvals\", icon: \"check\", description: \"Agents ask, people decide.\" },\n { label: \"Billing\", icon: \"card\", description: \"Invoices, plans and payouts.\" },\n { label: \"Analytics\", icon: \"chart\", description: \"Growth week over week.\" },\n ],\n },\n {\n label: \"Resources\",\n links: [\n { label: \"Docs\", icon: \"book\", description: \"Guides and API reference.\" },\n { label: \"Changelog\", icon: \"file\", description: \"What shipped this month.\" },\n ],\n },\n { label: \"Pricing\", href: \"#\" },\n ]}\n />\n <Button asChild size=\"sm\">\n <a href=\"#\">Get started</a>\n </Button>\n </div>\n </div>\n );\n}\n","path":"components/NavigationMenu"},{"name":"Tabs","title":"Tabs","group":"Navigation","summary":"A tab bar that switches views.","description":"A tab bar that switches views. With `panels` it renders the panels too, wired with the tab,\ntablist and tabpanel roles; without, it is a bar you connect to your own content.\n\n- Keyboard: ←/→ move between tabs and select them, Home/End jump; only the selected tab is in the\n tab order.\n- `block` stretches the tabs to the full width.\n","components":["Tabs","Panel","KeyValue"],"code":"import { KeyValue, Panel, Tabs } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <Panel title=\"Northwind\">\n <Tabs\n label=\"Customer\"\n items={[\"Overview\", \"Invoices\", \"Notes\"]}\n panels={{\n Overview: (\n <KeyValue\n items={[\n { k: \"Plan\", v: \"Team · 24 seats\" },\n { k: \"MRR\", v: \"€1,240\" },\n { k: \"Renews\", v: \"May 1, 2026\" },\n ]}\n />\n ),\n Invoices: <p className=\"tf-card-desc\">Three invoices this year, all paid.</p>,\n Notes: <p className=\"tf-card-desc\">Wants 40 more seats before the renewal.</p>,\n }}\n />\n </Panel>\n );\n}\n","path":"components/Tabs"},{"name":"Sidebar","title":"Sidebar","group":"Navigation","summary":"App navigation down the side: groups with headings, counts, one level of nested pages and a collapsed mode with icons only.","description":"App navigation down the side: groups with headings, counts, one level of nested pages and a\ncollapsed mode with icons only.\n\n- The collapse button sits in the footer next to anything you pass (an account, settings).\n- Collapsed, labels stay available as tooltips and to assistive tech.\n- On phones the sidebar takes the full width; place it in a `Drawer` (`side=\"left\"`) for an\n off-canvas menu.\n","components":["Sidebar","Avatar"],"code":"import * as React from \"react\";\nimport { Avatar, Sidebar } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n const [page, setPage] = React.useState(\"invoices\");\n const groups = [\n {\n items: [\n { id: \"home\", label: \"Home\", icon: \"home\" as const },\n { id: \"inbox\", label: \"Inbox\", icon: \"mail\" as const, count: 4 },\n ],\n },\n {\n label: \"Finance\",\n items: [\n {\n id: \"billing\",\n label: \"Billing\",\n icon: \"card\" as const,\n items: [\n { id: \"invoices\", label: \"Invoices\", icon: \"file\" as const },\n { id: \"payouts\", label: \"Payouts\", icon: \"download\" as const },\n ],\n },\n { id: \"reports\", label: \"Reports\", icon: \"chart\" as const },\n ],\n },\n ];\n const head = (\n <div className=\"tf-row\" style={{ gap: 8, padding: \"4px 4px 0\", flexWrap: \"nowrap\" }}>\n <span className=\"tf-brand\" style={{ color: \"var(--ink)\" }}>\n Halo\n <span className=\"tf-brand-dot\" />\n </span>\n </div>\n );\n return (\n <div className=\"tf-row\" style={{ gap: 24, alignItems: \"stretch\", flexWrap: \"wrap\" }}>\n <div style={{ height: 420, display: \"flex\" }}>\n <Sidebar\n label=\"Workspace\"\n groups={groups}\n current={page}\n onPick={setPage}\n header={head}\n footer={<Avatar name=\"Mara Ellis\" size={28} />}\n />\n </div>\n <div style={{ height: 420, display: \"flex\" }}>\n <Sidebar\n label=\"Workspace (collapsed)\"\n groups={groups}\n current={page}\n onPick={setPage}\n defaultCollapsed\n />\n </div>\n </div>\n );\n}\n","path":"components/Sidebar"},{"name":"FloatingLayout","title":"Floating Layout","group":"Navigation","summary":"UI floating over a full-bleed background: a map, a 3D scene, a video or a photo.","description":"UI floating over a full-bleed background: a map, a 3D scene, a video or a photo. Four slots sit along the edges (`top`, `start`, `end`, `bottom`) and everything in them is glass by default, translucent and blurred over what is behind.\n\n- The background stays interactive wherever no slot sits on it: drag the map, click a pin.\n- Below 720px of width the slots stack (top, start, the background, end, bottom) and the layout grows with them instead of scrolling inside.\n- `surface=\"solid\"` turns the glass off; `surface=\"solid\"` on a single Panel or Card opts just that one out.\n- Panels, cards, the dock, map legends and controls, and loose controls (search field, chips, icon buttons, segmented controls) turn into glass inside it; menus, popovers and dialogs take the stronger glass fill.\n- Text on glass keeps 4.5:1 over any backdrop. Reduced transparency, more contrast and forced colours fall back to solid surfaces.\n","components":["Badge","FloatingLayout","IconButton","KeyValue","MapView","Panel","SearchField","Segmented"],"code":"import * as React from \"react\";\nimport {\n Badge,\n FloatingLayout,\n IconButton,\n KeyValue,\n MapView,\n Panel,\n SearchField,\n Segmented,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\nconst hubs: MapPoint[] = [\n { id: \"ham\", label: \"Hamburg\", lat: 53.5, lng: 10, value: 840, sub: \"840 parcels today\" },\n { id: \"rot\", label: \"Rotterdam\", lat: 51.9, lng: 4.5, value: 610, tint: \"mint\" },\n { id: \"nyc\", label: \"New York\", lat: 40.7, lng: -74, value: 380, tint: \"butter\" },\n { id: \"dxb\", label: \"Dubai\", lat: 25.2, lng: 55.3, value: 290, tint: \"lilac\", pulse: true },\n { id: \"sgp\", label: \"Singapore\", lat: 1.3, lng: 103.8, value: 450, tint: \"blush\" },\n];\n\nexport default function Preview() {\n const [view, setView] = React.useState(\"Today\");\n return (\n <FloatingLayout\n label=\"Shipments\"\n height={520}\n background={<MapView label=\"Shipments by hub\" bubbles points={hubs} height=\"100%\" />}\n top={\n <>\n <SearchField placeholder=\"Search hubs\" style={{ flex: \"1 1 220px\", maxWidth: 360 }} />\n <IconButton icon=\"filter\" label=\"Filters\" variant=\"outline\" />\n </>\n }\n end={\n <Panel\n title=\"Hamburg\"\n subtitle=\"Hub\"\n action={\n <Badge tone=\"success\" dot>\n On time\n </Badge>\n }\n >\n <KeyValue\n items={[\n [\"Parcels\", \"840\"],\n [\"Delayed\", \"12\"],\n [\"Next truck\", \"14:20\"],\n ]}\n />\n </Panel>\n }\n bottom={\n <Segmented\n label=\"Period\"\n options={[\"Today\", \"Week\", \"Month\"]}\n value={view}\n onChange={setView}\n />\n }\n />\n );\n}\n","path":"components/FloatingLayout"},{"name":"SelectMenu","title":"Select Menu","group":"Forms","summary":"A styled select: the button shows the choice, the list opens below it with groups, icons, hints and disabled options.","description":"A styled select: the button shows the choice, the list opens below it with groups, icons, hints\nand disabled options. Use the native `Select` when the system picker is preferred (long lists on\nphones).\n\n- Keyboard: ↑/↓/Enter/Space open, arrows move, Home/End jump, typing jumps to a match, Enter picks,\n Escape closes.\n- Inside a `Form` it submits its value under `name` and shows its validation message.\n","components":["SelectMenu"],"code":"import * as React from \"react\";\nimport { SelectMenu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", gap: 20, paddingBottom: 260 } as React.CSSProperties}\n >\n <SelectMenu\n label=\"Assignee\"\n placeholder=\"Choose a person\"\n options={[\n { value: \"mara\", label: \"Mara Ellis\", hint: \"Owner\", group: \"Leadership\" },\n { value: \"jonah\", label: \"Jonah Petit\", hint: \"Design\", group: \"Team\" },\n { value: \"sun\", label: \"Sun Lee\", hint: \"Growth\", group: \"Team\" },\n { value: \"rui\", label: \"Rui Costa\", hint: \"Finance\", group: \"Team\" },\n { value: \"ava\", label: \"Ava Brandt\", hint: \"Invited\", group: \"Team\", disabled: true },\n ]}\n />\n <SelectMenu\n label=\"Region\"\n defaultValue=\"eu\"\n hint=\"Where workspace data is stored\"\n options={[\n { value: \"eu\", label: \"Europe (Frankfurt)\", icon: \"globe\" },\n { value: \"us\", label: \"United States (Ohio)\", icon: \"globe\" },\n { value: \"ap\", label: \"Asia Pacific (Sydney)\", icon: \"globe\" },\n ]}\n />\n </div>\n );\n}\n","path":"components/SelectMenu"},{"name":"Form","title":"Form","group":"Forms","summary":"Validation for native forms.","description":"Validation for native forms. Give `rules` by field `name`; on submit the form checks them, shows\neach message under its field, marks the field `aria-invalid` and focuses the first one. After the\nfirst submit, fields re-check as they change.\n\n- Rules: `required`, `email`, `minLength`/`maxLength`, `min`/`max`, `pattern`, and `validate` for\n anything else. Messages can be set per rule.\n- `onSubmit` receives the values by name; `errors` shows messages from the server.\n- Works with `TextField`, `Input`, `Select`, `Textarea`, `SelectMenu`, `Checkbox` and `Switch`.\n","components":["Form","TextField","SelectMenu","Textarea","Checkbox","Button"],"code":"import * as React from \"react\";\nimport {\n Button,\n Checkbox,\n Form,\n SelectMenu,\n TextField,\n Textarea,\n toast,\n} from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ maxWidth: 520 }}>\n <Form\n rules={{\n name: { required: \"Tell us your name\" },\n email: { required: true, email: true },\n team: { required: \"Pick a team\" },\n message: { minLength: 20, message: \"Write at least 20 characters\" },\n terms: { required: \"Accept the terms to continue\" },\n }}\n onSubmit={(v) => toast({ text: `Thanks, ${v.name}` })}\n >\n <TextField name=\"name\" label=\"Name\" placeholder=\"Mara Ellis\" required />\n <TextField\n name=\"email\"\n label=\"Work email\"\n type=\"email\"\n placeholder=\"you@company.com\"\n required\n />\n <SelectMenu\n name=\"team\"\n label=\"Team\"\n placeholder=\"Choose a team\"\n required\n options={[\"Design\", \"Engineering\", \"Growth\", \"Finance\"]}\n />\n <Textarea name=\"message\" label=\"Message\" placeholder=\"What do you need?\" rows={3} />\n <Checkbox name=\"terms\" value=\"yes\">\n I accept the terms\n </Checkbox>\n <div className=\"tf-row\" style={{ justifyContent: \"flex-end\" }}>\n <Button type=\"submit\">Send</Button>\n </div>\n </Form>\n </div>\n );\n}\n","path":"components/Form"},{"name":"Sheet","title":"Sheet","group":"Overlays","summary":"A Drawer that slides in from any edge: side=\"right\" (default), \"left\", \"top\" or \"bottom\".","description":"A `Drawer` that slides in from any edge: `side=\"right\"` (default), `\"left\"`, `\"top\"` or\n`\"bottom\"`. Bottom sheets suit filters and quick actions on phones; left sheets hold navigation.\n\n- Focus moves into the sheet and stays there until it closes; Escape and a click outside close it.\n","components":["Drawer","SelectMenu","Button"],"code":"import * as React from \"react\";\nimport { Button, Drawer, SelectMenu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 420,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <Drawer\n inline\n side=\"bottom\"\n title=\"Filters\"\n subtitle=\"12 of 48 listings match\"\n onClose={() => {}}\n >\n <div className=\"tf-grid\" style={{ \"--cols\": \"1fr 1fr\", gap: 16 } as React.CSSProperties}>\n <SelectMenu label=\"Rooms\" defaultValue=\"3\" options={[\"1\", \"2\", \"3\", \"4+\"]} />\n <SelectMenu label=\"Sort by\" defaultValue=\"Newest\" options={[\"Newest\", \"Price\", \"Size\"]} />\n </div>\n <div className=\"tf-row\" style={{ justifyContent: \"flex-end\" }}>\n <Button variant=\"ghost\">Reset</Button>\n <Button>Show 12 listings</Button>\n </div>\n </Drawer>\n </div>\n );\n}\n","path":"components/Sheet"},{"name":"ResizablePanels","title":"Resizable Panels","group":"Foundations","summary":"Two or more panes with drag handles between them, side by side or stacked; panels can nest.","description":"Two or more panes with drag handles between them, side by side or stacked; panels can nest.\n\n- Handles are separators with a value: ←/→ (↑/↓ when stacked) resize by 2%, Home/End jump to the\n limits, double-click resets to equal sizes.\n- `min` keeps every pane above a share of the whole; `storageKey` remembers the sizes.\n","components":["ResizablePanels"],"code":"import { ResizablePanels } from \"@twentyfirstmedia/ui-core\";\n\nconst pane = (title: string, text: string) => (\n <div className=\"tf-col\" style={{ gap: 6, padding: 16 }}>\n <b>{title}</b>\n <span className=\"tf-card-desc\">{text}</span>\n </div>\n);\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ padding: 0, height: 320, overflow: \"hidden\" }}>\n <ResizablePanels\n labels={[\"Folders\", \"Messages\"]}\n defaultSizes={[22, 38, 40]}\n panels={[\n pane(\"Folders\", \"Inbox, Sent, Archive\"),\n pane(\"Messages\", \"Drag a handle, or focus it and use the arrow keys.\"),\n <ResizablePanels\n key=\"r\"\n orientation=\"vertical\"\n labels={[\"Thread\"]}\n defaultSizes={[60, 40]}\n panels={[pane(\"Thread\", \"Northwind · Seat upgrade\"), pane(\"Reply\", \"Write a reply…\")]}\n />,\n ]}\n />\n </div>\n );\n}\n","path":"components/ResizablePanels"}],"patterns":[{"name":"AIChatScreen","title":"AI Chat","group":"Pages","summary":"A complete screen built from AgentMark, AgentStatus, ApprovalCard, ChatMessage and 6 more components.","description":"","components":["AgentMark","AgentStatus","ApprovalCard","ChatMessage","Composer","Icon","Reasoning","SubNav","ToolCall","TopBar","UsageMeter"],"code":"import {\n AgentMark,\n AgentStatus,\n ApprovalCard,\n ChatMessage,\n Composer,\n Reasoning,\n ToolCall,\n UsageMeter,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nconst md = \"I drafted a reply to **Ida Nolan**. It quotes **40 seats at €24** prorated to May 1.\";\nfunction App() {\n return (\n <Shell\n area=\"Agents\"\n current=\"Chat\"\n action={{\n label: \"New agent\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"Fleet\",\n label: \"Fleet\",\n icon: \"bot\",\n },\n {\n id: \"Chat\",\n label: \"Chat\",\n icon: \"mail\",\n },\n {\n id: \"Builder\",\n label: \"Builder\",\n icon: \"settings\",\n },\n {\n id: \"Runs\",\n label: \"Runs\",\n icon: \"refresh\",\n count: 2,\n },\n {\n id: \"Knowledge\",\n label: \"Knowledge\",\n icon: \"book\",\n },\n ]}\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n paddingBottom: 16,\n borderBottom: \"1px solid var(--line)\",\n }}\n >\n <AgentMark tint=\"sky\" live size={40} />\n <div\n style={{\n flex: 1,\n }}\n >\n <div\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Atlas\n </div>\n <AgentStatus status=\"Waiting for approval\" step=\"Drafts only\" />\n </div>\n <div\n style={{\n width: 160,\n }}\n >\n <UsageMeter label=\"Context\" used={19} limit={200} format={(v) => v + \"k\"} />\n </div>\n </div>\n <ChatMessage role=\"user\" text=\"Draft a reply to Northwind about the seat upgrade\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Atlas\"\n tint=\"sky\"\n time=\"09:41\"\n model=\"Claude Sonnet 5.5\"\n text={md}\n sources={[\n {\n title: \"Seat upgrades · pricing v4\",\n },\n {\n title: \"Thread: Seat upgrade\",\n icon: \"mail\",\n },\n ]}\n usage=\"1,527 tokens · €0.06\"\n before={\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <Reasoning\n duration=\"1.4 s\"\n steps={[\"Find the thread\", \"Check pricing\", \"Draft, then ask\"]}\n />\n <ToolCall name=\"Mailbox\" icon=\"mail\" summary=\"Read 3 messages\" duration=\"0.8 s\" />\n <ToolCall\n name=\"Knowledge base\"\n icon=\"book\"\n summary=\"Found “Seat upgrades”\"\n duration=\"0.6 s\"\n />\n </div>\n }\n after={\n <ApprovalCard\n title=\"Send this email to ida@northwind.io?\"\n text=\"Atlas is set to “Drafts only”.\"\n approveLabel=\"Send email\"\n onEdit={() => {}}\n >\n {\n \"Hi Ida,\\n\\nhappy to add 40 seats before your renewal on May 1. The quote is attached.\\n\\nBest, Mara\"\n }\n </ApprovalCard>\n }\n />\n <Composer\n placeholder=\"Ask Atlas something, or type / for commands\"\n models={[\n {\n id: \"s\",\n label: \"Claude Sonnet 5.5\",\n },\n ]}\n model=\"s\"\n onAttach={() => {}}\n commands={[\n {\n name: \"clear\",\n },\n ]}\n />\n </div>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/AIChatScreen"},{"name":"AgentBuilderScreen","title":"Agent Builder","group":"Pages","summary":"A complete screen built from Button, ChatMessage, Combobox, Composer and 7 more components.","description":"","components":["Button","ChatMessage","Combobox","Composer","Icon","PageHeader","Panel","RadioGroup","Segmented","Slider","SubNav","TopBar"],"code":"import * as React from \"react\";\nimport {\n Button,\n ChatMessage,\n Combobox,\n Composer,\n PageHeader,\n Panel,\n RadioGroup,\n Segmented,\n Slider,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n return (\n <Shell\n area=\"Agents\"\n current=\"Builder\"\n action={{\n label: \"New agent\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"Fleet\",\n label: \"Fleet\",\n icon: \"bot\",\n },\n {\n id: \"Chat\",\n label: \"Chat\",\n icon: \"mail\",\n },\n {\n id: \"Builder\",\n label: \"Builder\",\n icon: \"settings\",\n },\n {\n id: \"Runs\",\n label: \"Runs\",\n icon: \"refresh\",\n },\n ]}\n >\n <PageHeader\n crumbs={[\"Agents\", \"Atlas\"]}\n title=\"Configure Atlas\"\n subtitle=\"Support triage · changes apply to new runs\"\n actions={[\n <Button key=\"s\" icon=\"check\">\n Save changes\n </Button>,\n ]}\n />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1fr) 340px\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <Panel title=\"Instructions\" subtitle=\"What it does, how it talks, what it must never do\">\n <textarea\n className=\"tf-input\"\n aria-label=\"Instructions\"\n rows={5}\n style={{\n fontFamily: \"var(--font-mono)\",\n fontSize: 13,\n }}\n defaultValue={\n \"You are Atlas, the support triage agent for Halo.\\n- Tag new mail and draft replies.\\n- Never promise refunds; hand those to a person.\"\n }\n />\n </Panel>\n <Panel title=\"Model\">\n <RadioGroup\n cards\n defaultValue=\"Sonnet\"\n options={[\n {\n value: \"Opus\",\n label: \"Opus 5.5\",\n hint: \"Deepest · €15 / M\",\n },\n {\n value: \"Sonnet\",\n label: \"Sonnet 5.5\",\n hint: \"Balanced · €3 / M\",\n },\n {\n value: \"Haiku\",\n label: \"Haiku 5.5\",\n hint: \"Fast · €1 / M\",\n },\n ]}\n />\n </Panel>\n <Panel title=\"Guardrails\">\n <Segmented\n defaultValue=\"Drafts only\"\n options={[\"Drafts only\", \"Acts, then reports\", \"Fully autonomous\"]}\n />\n <Combobox\n label=\"Always ask before\"\n multiple\n defaultValue={[\"Sending email\", \"Spending money\"]}\n options={[\"Sending email\", \"Spending money\", \"Deleting anything\"]}\n />\n <Slider\n label=\"Monthly budget\"\n min={0}\n max={500}\n defaultValue={120}\n format={(v) => \"€\" + v}\n />\n </Panel>\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <div>\n <h3 className=\"tf-panel-title\">Test</h3>\n <p className=\"tf-panel-sub\">Uses your unsaved setup</p>\n </div>\n <ChatMessage role=\"user\" text=\"Can I get a refund?\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Atlas\"\n tint=\"sky\"\n actions={false}\n text=\"I’ll pass this to a person, since refunds need a human. You’ll hear back today.\"\n />\n <Composer placeholder=\"Test a message…\" hint=\"\" />\n </div>\n </div>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/AgentBuilderScreen"},{"name":"AnalyticsScreen","title":"Analytics","group":"Pages","summary":"A complete screen built from AppShell, BarChart, Button, CountUp and 10 more components.","description":"","components":["AppShell","BarChart","Button","CountUp","Donut","IconButton","LineChart","PageHeader","Panel","Sparkline","Stagger","Table","Tabs","TintIcon"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n BarChart,\n Button,\n CountUp,\n Donut,\n LineChart,\n PageHeader,\n Panel,\n Sparkline,\n Stagger,\n Table,\n Tabs,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { IconName, Tint } from \"@twentyfirstmedia/ui-core\";\n\ninterface KpiProps {\n label: string;\n value: string;\n delta: string;\n up?: boolean;\n icon: IconName;\n tint: Tint;\n spark: number[];\n}\n\nfunction Kpi(p: KpiProps) {\n return (\n <div\n className=\"tf-card\"\n style={{\n minHeight: 0,\n gap: 8,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span className=\"tf-card-desc\">{p.label}</span>\n <TintIcon icon={p.icon} tint={p.tint} />\n </div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n alignItems: \"flex-end\",\n }}\n >\n <div>\n <CountUp className=\"tf-stat-value\" value={p.value} />\n <div\n style={{\n font: \"var(--type-label-weight) 12px/16px var(--font-sans)\",\n color: \"var(--\" + (p.up ? \"mint\" : \"blush\") + \"-ink)\",\n marginTop: 2,\n }}\n >\n {p.delta}\n </div>\n </div>\n <Sparkline values={p.spark} tint={p.up ? \"mint\" : \"blush\"} width={96} height={34} />\n </div>\n </div>\n );\n}\nfunction App() {\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Reports\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Analytics\"\n subtitle=\"How the workspace is growing, week over week\"\n actions={[\n <Tabs\n key=\"t\"\n items={[\"7 days\", \"30 days\", \"Quarter\", \"Year\"]}\n defaultValue=\"30 days\"\n />,\n <Button key=\"e\" variant=\"secondary\" icon=\"download\">\n Export\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <Kpi\n label=\"Revenue\"\n value=\"€41,280\"\n delta=\"+18.2% vs last month\"\n up\n icon=\"card\"\n tint=\"butter\"\n spark={[18, 22, 21, 29, 34, 38, 41]}\n />\n <Kpi\n label=\"Active members\"\n value=\"1,284\"\n delta=\"+6.4%\"\n up\n icon=\"users\"\n tint=\"sky\"\n spark={[9, 10, 10, 11, 12, 12, 13]}\n />\n <Kpi\n label=\"Conversion\"\n value=\"4.8%\"\n delta=\"+0.6 pts\"\n up\n icon=\"trend\"\n tint=\"mint\"\n spark={[3.9, 4.1, 4.0, 4.4, 4.5, 4.6, 4.8]}\n />\n <Kpi\n label=\"Churn\"\n value=\"1.9%\"\n delta=\"+0.3 pts\"\n up={false}\n icon=\"flag\"\n tint=\"blush\"\n spark={[1.4, 1.5, 1.5, 1.6, 1.8, 1.7, 1.9]}\n />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.6fr 1fr\",\n } as React.CSSProperties\n }\n delay={300}\n >\n <Panel\n title=\"Revenue\"\n subtitle=\"Subscriptions vs. services\"\n action={\n <div className=\"tf-legend\">\n <span>\n <i\n style={{\n background: \"var(--mint-ink)\",\n }}\n />\n Subscriptions\n </span>\n <span>\n <i\n style={{\n background: \"var(--sky-ink)\",\n }}\n />\n Services\n </span>\n </div>\n }\n >\n <LineChart\n height={190}\n labels={[\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\"]}\n format={(v) => \"€\" + Math.round(v / 1000) + \"k\"}\n series={[\n {\n values: [18000, 22000, 21000, 29000, 34000, 38000, 41000],\n tint: \"mint\",\n area: true,\n },\n {\n values: [9000, 11000, 14000, 13000, 17000, 19000, 23000],\n tint: \"sky\",\n },\n ]}\n />\n </Panel>\n <Panel title=\"Plan mix\" subtitle=\"1,284 paying members\">\n <Donut\n size={150}\n stroke={20}\n center=\"1,284\"\n caption=\"members\"\n data={[\n {\n label: \"Team\",\n value: 54,\n tint: \"sky\",\n },\n {\n label: \"Starter\",\n value: 26,\n tint: \"mint\",\n },\n {\n label: \"Business\",\n value: 14,\n tint: \"lilac\",\n },\n {\n label: \"Trial\",\n value: 6,\n tint: \"butter\",\n },\n ]}\n />\n </Panel>\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1.2fr\",\n } as React.CSSProperties\n }\n delay={480}\n >\n <Panel title=\"Sign-ups this week\" subtitle=\"Thursday was the launch email\">\n <BarChart\n height={150}\n data={[\n {\n label: \"Mon\",\n value: 32,\n },\n {\n label: \"Tue\",\n value: 48,\n },\n {\n label: \"Wed\",\n value: 41,\n },\n {\n label: \"Thu\",\n value: 86,\n tint: \"lilac\",\n },\n {\n label: \"Fri\",\n value: 58,\n },\n {\n label: \"Sat\",\n value: 22,\n },\n {\n label: \"Sun\",\n value: 18,\n },\n ]}\n />\n </Panel>\n <Panel title=\"Top channels\">\n <Table\n columns={[\n {\n key: \"ch\",\n label: \"Channel\",\n render: (r) => (\n <span className=\"tf-row\" style={{ flexWrap: \"nowrap\" }}>\n <TintIcon icon={r.icon} tint={r.tint} />\n {r.ch}\n </span>\n ),\n },\n {\n key: \"v\",\n label: \"Visits\",\n align: \"right\",\n },\n {\n key: \"c\",\n label: \"Conv.\",\n align: \"right\",\n },\n {\n key: \"t\",\n label: \"Trend\",\n align: \"right\",\n render: (r) => (\n <Sparkline values={r.s} tint={r.up ? \"mint\" : \"blush\"} width={70} height={22} />\n ),\n },\n ]}\n rows={[\n {\n ch: \"Organic search\",\n icon: \"search\",\n tint: \"sky\",\n v: \"24,180\",\n c: \"5.2%\",\n s: [3, 4, 4, 5, 6, 6, 7],\n up: true,\n },\n {\n ch: \"Newsletter\",\n icon: \"mail\",\n tint: \"butter\",\n v: \"9,402\",\n c: \"7.9%\",\n s: [2, 2, 3, 5, 4, 6, 7],\n up: true,\n },\n {\n ch: \"Partners\",\n icon: \"users\",\n tint: \"mint\",\n v: \"6,115\",\n c: \"6.1%\",\n s: [5, 5, 4, 5, 6, 6, 6],\n up: true,\n },\n {\n ch: \"Paid social\",\n icon: \"globe\",\n tint: \"blush\",\n v: \"5,870\",\n c: \"2.3%\",\n s: [6, 5, 5, 4, 4, 3, 3],\n up: false,\n },\n ]}\n />\n </Panel>\n </Stagger>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/AnalyticsScreen"},{"name":"AuthCard","title":"Auth Card","group":"Pages","summary":"Centered card for sign-in, sign-up, password reset, two-step codes and invites.","description":"Centered card for sign-in, sign-up, password reset, two-step codes and invites.\n\nProps: `title`, `text`, `icon` + `tint` (for non-sign-in steps), `brand`, `foot` (the one secondary link), children (fields and one primary Button).\n\n- One task per card, one primary button. Secondary paths go in the footer.\n- After \"Send reset link\", swap to a confirmation card that doesn't reveal whether the account exists.\n- Put it on the `canvas` color with a small \"Back\" link at top-left.\n","components":["AuthCard","Button","OtpInput","TextField"],"code":"import * as React from \"react\";\nimport { AuthCard, Button, OtpInput, TextField } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <AuthCard\n brand=\"Halo\"\n title=\"Welcome back\"\n text=\"Sign in to the Halo workspace.\"\n foot=\"New here? Create a workspace\"\n >\n <TextField label=\"Email\" icon=\"mail\" defaultValue=\"mara@halo.app\" />\n <TextField label=\"Password\" type=\"password\" icon=\"lock\" defaultValue=\"secret-pass\" />\n <Button>Sign in</Button>\n </AuthCard>\n <AuthCard\n brand=\"Halo\"\n icon=\"key\"\n tint=\"butter\"\n title=\"Reset your password\"\n text=\"We’ll email you a link to choose a new one.\"\n foot=\"Back to sign in\"\n >\n <TextField label=\"Email\" icon=\"mail\" defaultValue=\"mara@halo.app\" />\n <Button icon=\"send\">Send reset link</Button>\n </AuthCard>\n <AuthCard\n brand=\"Halo\"\n icon=\"shield\"\n tint=\"sky\"\n title=\"Enter your code\"\n text=\"Type the 6-digit code from your authenticator app.\"\n foot=\"Use a recovery code instead\"\n >\n <OtpInput />\n </AuthCard>\n </div>\n );\n}\n","path":"patterns/AuthCard"},{"name":"BillingScreen","title":"Billing","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, BarChart and 9 more components.","description":"","components":["AppShell","Avatar","Badge","BarChart","Button","Icon","IconButton","PageHeader","Panel","Progress","ProgressRing","Stagger","StatTile","Table"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n BarChart,\n Button,\n Icon,\n IconButton,\n PageHeader,\n Panel,\n Progress,\n ProgressRing,\n Stagger,\n StatTile,\n Table,\n} from \"@twentyfirstmedia/ui-core\";\n\nconst INV = [\n {\n id: \"#2044\",\n c: \"Northwind\",\n d: \"Apr 12, 2026\",\n a: \"€1,240.00\",\n s: \"Paid\",\n tone: \"success\",\n },\n {\n id: \"#2043\",\n c: \"Copperline\",\n d: \"Apr 10, 2026\",\n a: \"€3,900.00\",\n s: \"Due\",\n tone: \"info\",\n },\n {\n id: \"#2042\",\n c: \"Blue Fern\",\n d: \"Apr 8, 2026\",\n a: \"€420.00\",\n s: \"Paid\",\n tone: \"success\",\n },\n {\n id: \"#2041\",\n c: \"Harbor & Co\",\n d: \"Mar 2, 2026\",\n a: \"€860.00\",\n s: \"Overdue\",\n tone: \"danger\",\n },\n {\n id: \"#2040\",\n c: \"Lumen Labs\",\n d: \"Feb 28, 2026\",\n a: \"€2,150.00\",\n s: \"Paid\",\n tone: \"success\",\n },\n];\nfunction App() {\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Billing\"\n side={\n <Panel side title=\"Your plan\" subtitle=\"Renews on May 1, 2026\">\n <div\n className=\"tf-card tf-bg-lilac\"\n style={{\n minHeight: 0,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Team\n </span>\n <Badge tone=\"neutral\" dot={false}>\n Annual\n </Badge>\n </div>\n <div>\n <span className=\"tf-stat-value\">€96</span>\n <span className=\"tf-card-desc\">{\" / seat / month\"}</span>\n </div>\n <Button variant=\"primary\" iconRight=\"arrowUpRight\">\n Upgrade to Business\n </Button>\n </div>\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n flexWrap: \"nowrap\",\n }}\n >\n <ProgressRing value={90} tint=\"butter\" size={76} stroke={8} />\n <div>\n <div\n style={{\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n 38 of 42 seats\n </div>\n <div className=\"tf-card-desc\">Add seats before inviting more people.</div>\n </div>\n </div>\n <h3\n className=\"tf-card-title\"\n style={{\n fontSize: 16,\n }}\n >\n Usage\n </h3>\n <div className=\"tf-col\">\n <div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n fontSize: 13,\n fontWeight: \"var(--type-label-weight)\",\n marginBottom: 8,\n }}\n >\n <span>Storage</span>\n <span>62 / 100 GB</span>\n </div>\n <Progress value={62} tint=\"sky\" />\n </div>\n <div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n fontSize: 13,\n fontWeight: \"var(--type-label-weight)\",\n marginBottom: 8,\n }}\n >\n <span>API calls</span>\n <span>41k / 50k</span>\n </div>\n <Progress value={82} tint=\"butter\" />\n </div>\n </div>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Billing\"\n subtitle=\"Invoices, payments and your subscription\"\n actions={[\n <Button key=\"e\" variant=\"secondary\" icon=\"download\">\n Export CSV\n </Button>,\n <Button key=\"n\" icon=\"plus\">\n New invoice\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Collected (MTD)\" />\n <StatTile tint=\"sky\" value=\"€12,480\" label=\"Outstanding\" />\n <StatTile tint=\"blush\" value=\"3\" label=\"Overdue invoices\" />\n <StatTile tint=\"mint\" value=\"98.2%\" label=\"On-time rate\" />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n delay={280}\n >\n <div\n className=\"tf-card\"\n style={{\n minHeight: 0,\n background: \"var(--frame)\",\n color: \"var(--on-frame)\",\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span\n style={{\n font: \"var(--type-label-weight) 12px/16px var(--font-sans)\",\n color: \"var(--on-frame-muted)\",\n }}\n >\n Default payment method\n </span>\n <Icon name=\"card\" size={20} />\n </div>\n <div\n style={{\n font: \"var(--type-title-weight) 20px/26px var(--font-sans)\",\n letterSpacing: \".08em\",\n }}\n >\n •••• •••• •••• 4417\n </div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n color: \"var(--on-frame-muted)\",\n fontSize: 13,\n }}\n >\n <span>Mara Ellis</span>\n <span>Expires 04/26</span>\n </div>\n </div>\n <Panel title=\"Collected per month\">\n <BarChart\n height={92}\n barWidth={22}\n format={(v) => \"€\" + Math.round(v / 1000) + \"k\"}\n data={[\n {\n label: \"Nov\",\n value: 31000,\n },\n {\n label: \"Dec\",\n value: 28000,\n },\n {\n label: \"Jan\",\n value: 36000,\n },\n {\n label: \"Feb\",\n value: 39000,\n },\n {\n label: \"Mar\",\n value: 44000,\n },\n {\n label: \"Apr\",\n value: 48210,\n tint: \"butter\",\n },\n ]}\n />\n </Panel>\n </Stagger>\n <div\n className=\"tf-panel tf-panel-flush tf-in\"\n style={\n {\n \"--d\": \"420ms\",\n } as React.CSSProperties\n }\n >\n <Table\n columns={[\n {\n key: \"id\",\n label: \"Invoice\",\n },\n {\n key: \"c\",\n label: \"Customer\",\n render: (r) => (\n <span className=\"tf-row\">\n <Avatar name={r.c} size={26} />\n {r.c}\n </span>\n ),\n },\n {\n key: \"d\",\n label: \"Issued\",\n },\n {\n key: \"s\",\n label: \"Status\",\n render: (r) => <Badge tone={r.tone}>{r.s}</Badge>,\n },\n {\n key: \"a\",\n label: \"Amount\",\n align: \"right\",\n },\n {\n key: \"x\",\n label: \"\",\n align: \"right\",\n render: () => <IconButton icon=\"download\" label=\"Download PDF\" size=\"sm\" />,\n },\n ]}\n rows={INV}\n />\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/BillingScreen"},{"name":"BoardScreen","title":"Board","group":"Pages","summary":"A complete screen built from AppShell, AvatarStack, Badge, Button and 6 more components.","description":"","components":["AppShell","AvatarStack","Badge","Button","Card","IconButton","KanbanColumn","PageHeader","Progress","Tabs"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n AvatarStack,\n Badge,\n Button,\n Card,\n KanbanColumn,\n PageHeader,\n Progress,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { Tint } from \"@twentyfirstmedia/ui-core\";\n\ninterface TaskProps {\n t: string;\n m?: string[];\n b?: React.ReactNode;\n who: string[];\n tint?: Tint;\n p?: number;\n}\n\nfunction T(p: TaskProps) {\n return (\n <Card\n onClick={() => {}}\n tint={p.tint}\n title={p.t}\n meta={p.m}\n status={p.b}\n actions={[<AvatarStack key={1} names={p.who} size={24} />]}\n >\n {p.p != null && <Progress value={p.p} tint=\"mint\" label=\"Progress\" />}\n </Card>\n );\n}\nfunction App() {\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n crumbs={[\"Projects\", \"Pricing page refresh\"]}\n title=\"Sprint 14 board\"\n subtitle=\"18 tasks · ends Friday\"\n actions={[\n <AvatarStack\n key=\"a\"\n names={[\n \"Mara Ellis\",\n \"Jonah Petit\",\n \"Sun Lee\",\n \"Rui Costa\",\n \"Ida Nolan\",\n \"Ava Brandt\",\n ]}\n />,\n <Tabs key=\"t\" items={[\"Board\", \"List\", \"Timeline\"]} />,\n <Button key=\"n\" icon=\"plus\">\n New task\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(4, minmax(260px, 85%))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 16,\n alignItems: \"start\",\n overflowX: \"auto\",\n } as React.CSSProperties\n }\n >\n <KanbanColumn title=\"To do\" dot=\"sky\" delay={120}>\n <T\n t=\"Write FAQ for annual plans\"\n m={[\"Docs\", \"Due Thu\"]}\n b={<Badge>Copy</Badge>}\n who={[\"Ava Brandt\"]}\n />\n <T\n t=\"Localise tiers for DE and FR\"\n m={[\"Growth\"]}\n b={<Badge tone=\"info\">i18n</Badge>}\n who={[\"Sun Lee\", \"Ida Nolan\"]}\n />\n <T\n t=\"Plan migration emails\"\n m={[\"Lifecycle\"]}\n b={<Badge>Email</Badge>}\n who={[\"Rui Costa\"]}\n />\n </KanbanColumn>\n <KanbanColumn title=\"In progress\" dot=\"butter\" delay={220}>\n <T\n tint=\"lilac\"\n t=\"Comparison table component\"\n m={[\"Design system\", \"3 of 5 states\"]}\n b={<Badge tone=\"highlight\">Focus</Badge>}\n who={[\"Jonah Petit\", \"Mara Ellis\"]}\n p={60}\n />\n <T\n t=\"Checkout seat selector\"\n m={[\"Billing\"]}\n b={<Badge tone=\"warning\">Blocked</Badge>}\n who={[\"Rui Costa\"]}\n p={30}\n />\n </KanbanColumn>\n <KanbanColumn title=\"In review\" dot=\"lilac\" delay={320}>\n <T\n t=\"Billing settings flow\"\n m={[\"Due Fri\"]}\n b={<Badge tone=\"highlight\">Review</Badge>}\n who={[\"Mara Ellis\", \"Sun Lee\"]}\n />\n <T\n t=\"Empty states for reports\"\n m={[\"Due Mon\"]}\n b={<Badge tone=\"warning\">Changes</Badge>}\n who={[\"Jonah Petit\"]}\n />\n </KanbanColumn>\n <KanbanColumn title=\"Done\" dot=\"mint\" delay={420}>\n <T\n t=\"Pricing research recap\"\n m={[\"Shipped Tue\"]}\n b={<Badge tone=\"success\">Done</Badge>}\n who={[\"Sun Lee\"]}\n />\n <T\n t=\"New tier names\"\n m={[\"Shipped Mon\"]}\n b={<Badge tone=\"success\">Done</Badge>}\n who={[\"Mara Ellis\"]}\n />\n <T\n t=\"Analytics events for upgrade\"\n m={[\"Shipped Mon\"]}\n b={<Badge tone=\"success\">Done</Badge>}\n who={[\"Ida Nolan\"]}\n />\n </KanbanColumn>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/BoardScreen"},{"name":"CalendarScreen","title":"Calendar","group":"Pages","summary":"A complete screen built from AppShell, Button, EventCard, IconButton and 4 more components.","description":"","components":["AppShell","Button","EventCard","IconButton","PageHeader","Panel","Stagger","Tabs"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Button,\n EventCard,\n IconButton,\n PageHeader,\n Panel,\n Stagger,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\n\nconst DAYS = [\"Mon 13\", \"Tue 14\", \"Wed 15\", \"Thu 16\", \"Fri 17\"];\nconst EV = [\n {\n d: 0,\n s: 9,\n e: 10,\n t: \"Standup\",\n tint: \"sky\",\n who: \"Team\",\n },\n {\n d: 0,\n s: 13,\n e: 14.5,\n t: \"Hiring panel\",\n tint: \"lilac\",\n who: \"Design\",\n },\n {\n d: 1,\n s: 10.5,\n e: 12,\n t: \"Team sync\",\n tint: \"sky\",\n who: \"Growth + Design\",\n },\n {\n d: 1,\n s: 15,\n e: 16,\n t: \"Partner call\",\n tint: \"mint\",\n who: \"Northwind\",\n },\n {\n d: 2,\n s: 9,\n e: 10,\n t: \"Standup\",\n tint: \"sky\",\n who: \"Team\",\n },\n {\n d: 2,\n s: 14,\n e: 15.5,\n t: \"Design review\",\n tint: \"lilac\",\n who: \"Billing flow\",\n },\n {\n d: 3,\n s: 11,\n e: 12.5,\n t: \"Pricing review\",\n tint: \"butter\",\n who: \"Moved from Wed\",\n },\n {\n d: 3,\n s: 16,\n e: 17,\n t: \"Incident retro\",\n tint: \"blush\",\n who: \"Webhooks #88\",\n },\n {\n d: 4,\n s: 9,\n e: 10,\n t: \"Standup\",\n tint: \"sky\",\n who: \"Team\",\n },\n {\n d: 4,\n s: 15.5,\n e: 17,\n t: \"Q2 invoices due\",\n tint: \"butter\",\n who: \"Finance\",\n },\n];\nconst START = 8,\n END = 18,\n ROW = 48;\nfunction App() {\n const hours = [];\n for (let i = START; i <= END; i++) hours.push(i);\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Calendar\"\n dock={false}\n side={\n <Panel side title=\"Up next\" subtitle=\"Thursday, April 16\">\n <div\n className=\"tf-card tf-in\"\n style={\n {\n minHeight: 0,\n gap: 12,\n padding: 16,\n \"--d\": \"200ms\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span\n style={{\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n April 2026\n </span>\n <span\n className=\"tf-row\"\n style={{\n gap: 4,\n }}\n >\n <IconButton icon=\"chevronLeft\" label=\"Previous month\" size=\"sm\" />\n <IconButton icon=\"chevronRight\" label=\"Next month\" size=\"sm\" />\n </span>\n </div>\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(7, 1fr)\",\n gap: 4,\n textAlign: \"center\",\n font: \"var(--type-label-weight) 11px/28px var(--font-sans)\",\n }}\n >\n {[\"M\", \"T\", \"W\", \"T\", \"F\", \"S\", \"S\"].map((d, i) => (\n <span\n key={\"h\" + i}\n style={{\n color: \"var(--ink-muted)\",\n }}\n >\n {d}\n </span>\n ))}\n {([null, null] as (number | null)[])\n .concat(\n Array.from(\n {\n length: 30,\n },\n (_, i) => i + 1,\n ),\n )\n .map((n, i) => {\n const on = n != null && n >= 13 && n <= 17,\n today = n === 16;\n return (\n <span\n key={i}\n style={{\n borderRadius: \"var(--radius-pill)\",\n background: today ? \"var(--primary)\" : on ? \"var(--sky)\" : \"transparent\",\n color: today ? \"var(--primary-foreground)\" : \"var(--ink)\",\n }}\n >\n {n || \"\"}\n </span>\n );\n })}\n </div>\n </div>\n <Stagger className=\"tf-col\" delay={320}>\n <EventCard\n kind=\"review\"\n label=\"Pricing review\"\n date=\"Thu, Apr 16\"\n time=\"11:00\"\n tint=\"butter\"\n icon=\"card\"\n >\n Moved from Wednesday so Growth can join.\n </EventCard>\n <EventCard kind=\"incident\" label=\"Incident retro\" date=\"Thu, Apr 16\" time=\"16:00\">\n What delayed EU webhooks and how we stop it.\n </EventCard>\n </Stagger>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Calendar\"\n subtitle=\"Week 16 · 10 events\"\n actions={[\n <Tabs key=\"t\" items={[\"Day\", \"Week\", \"Month\"]} defaultValue=\"Week\" />,\n <Button key=\"n\" icon=\"plus\">\n New event\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"120ms\",\n padding: 16,\n gap: 0,\n overflowX: \"auto\",\n } as React.CSSProperties\n }\n tabIndex={0}\n role=\"region\"\n aria-label=\"Week view\"\n >\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"48px repeat(5, 1fr)\",\n minWidth: 560,\n gap: 8,\n paddingBottom: 12,\n }}\n >\n <span />\n {DAYS.map((d, i) => (\n <div\n key={d}\n style={{\n textAlign: \"center\",\n font: \"var(--type-title-weight) 13px/18px var(--font-sans)\",\n padding: \"8px 0\",\n borderRadius: \"var(--radius-pill)\",\n background: i === 3 ? \"var(--primary)\" : \"transparent\",\n color: i === 3 ? \"var(--primary-foreground)\" : \"var(--ink)\",\n }}\n >\n {d}\n </div>\n ))}\n </div>\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"48px repeat(5, 1fr)\",\n minWidth: 560,\n gap: 8,\n position: \"relative\",\n }}\n >\n <div>\n {hours.slice(0, -1).map((hr) => (\n <div\n key={hr}\n style={{\n height: ROW,\n font: \"var(--type-label-weight) 11px/1 var(--font-sans)\",\n color: \"var(--ink-muted)\",\n }}\n >\n {(hr < 10 ? \"0\" : \"\") + hr + \":00\"}\n </div>\n ))}\n </div>\n {DAYS.map((d, di) => (\n <div\n key={d}\n style={{\n position: \"relative\",\n height: ROW * (END - START),\n backgroundImage:\n \"repeating-linear-gradient(to bottom, var(--line) 0 1px, transparent 1px \" +\n ROW +\n \"px)\",\n borderRadius: \"var(--radius-sm)\",\n }}\n >\n {EV.filter((e) => e.d === di).map((e, i) => (\n <div\n key={i}\n className={\"tf-in tf-bg-\" + e.tint}\n style={\n {\n \"--d\": 200 + di * 70 + i * 60 + \"ms\",\n position: \"absolute\",\n left: 4,\n right: 4,\n top: (e.s - START) * ROW + 2,\n height: (e.e - e.s) * ROW - 4,\n borderRadius: \"var(--radius-sm)\",\n padding: \"8px 12px\",\n boxSizing: \"border-box\",\n overflow: \"hidden\",\n } as React.CSSProperties\n }\n >\n <div\n style={{\n font: \"var(--type-title-weight) 12px/16px var(--font-sans)\",\n }}\n >\n {e.t}\n </div>\n <div\n style={{\n font: \"var(--type-body-weight) 11px/15px var(--font-sans)\",\n opacity: 0.78,\n }}\n >\n {e.who}\n </div>\n </div>\n ))}\n </div>\n ))}\n </div>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/CalendarScreen"},{"name":"CustomersScreen","title":"Customers","group":"Pages","summary":"A complete screen built from Avatar, Badge, Drawer, IconButton and 9 more components.","description":"","components":["Avatar","Badge","Drawer","Icon","IconButton","ListRow","PageHeader","SearchField","Stagger","StatTile","SubNav","Table","Tabs","TopBar"],"code":"import * as React from \"react\";\nimport {\n Avatar,\n Badge,\n Drawer,\n ListRow,\n PageHeader,\n SearchField,\n Stagger,\n StatTile,\n Table,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\nimport type { Tone } from \"@twentyfirstmedia/ui-core\";\n\nconst CO: [string, string, string, number, string][] = [\n [\"Northwind\", \"Logistics · Hamburg\", \"Team\", 1240, \"Good\"],\n [\"Kestrel Bank\", \"Banking · Frankfurt\", \"Business\", 3880, \"Good\"],\n [\"Copperline\", \"Manufacturing · Munich\", \"Starter\", 190, \"Watch\"],\n [\"Harbor & Co\", \"Retail · Cologne\", \"Team\", 1468, \"At risk\"],\n [\"Lumen Labs\", \"Research · Vienna\", \"Business\", 4076, \"Good\"],\n [\"Orbit Foods\", \"Food · Zurich\", \"Team\", 1445, \"Good\"],\n];\nconst HT: Record<string, Tone> = {\n Good: \"success\",\n Watch: \"warning\",\n \"At risk\": \"danger\",\n};\nfunction App() {\n return (\n <Shell\n area=\"Customers\"\n current=\"companies\"\n action={{\n label: \"New deal\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"companies\",\n label: \"Companies\",\n icon: \"grid\",\n count: 1,\n },\n {\n id: \"pipeline\",\n label: \"Pipeline\",\n icon: \"trend\",\n count: 2,\n },\n {\n id: \"contacts\",\n label: \"Contacts\",\n icon: \"user\",\n },\n ]}\n overlay={\n <Drawer\n inline\n title=\"Northwind\"\n subtitle=\"Logistics · Hamburg · customer since 2021\"\n avatar=\"Northwind\"\n onClose={() => {}}\n >\n <div className=\"tf-row\">\n <Badge tone=\"success\">Good</Badge>\n <Badge tone=\"info\" dot={false}>\n Team · 24 seats\n </Badge>\n <Badge dot={false}>Owner: Mara Ellis</Badge>\n </div>\n <Tabs block items={[\"Overview\", \"Invoices\", \"Mail\", \"Agent brief\"]} />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€1,240\" label=\"MRR\" />\n <StatTile tint=\"mint\" value=\"€14,880\" label=\"Paid to date\" />\n </div>\n <ListRow\n avatar=\"Ida Nolan\"\n title=\"Ida Nolan\"\n subtitle=\"Head of Ops · ida@northwind.example\"\n />\n <ListRow\n avatar=\"Amara Silva\"\n title=\"Amara Silva\"\n subtitle=\"IT admin · amara@northwind.example\"\n />\n </Drawer>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Companies\"\n subtitle=\"15 customers · example data\"\n actions={[<SearchField key=\"s\" placeholder=\"Search companies\" />]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"butter\" value=\"€33,067\" label=\"Monthly recurring revenue\" />\n <StatTile tint=\"sky\" value=\"15\" label=\"Customers\" />\n <StatTile tint=\"placeholder\" value=\"5\" label=\"Need attention\" />\n <StatTile tint=\"mint\" value=\"€182,600\" label=\"Open pipeline\" />\n </Stagger>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n padding: 12,\n \"--d\": \"260ms\",\n } as React.CSSProperties\n }\n >\n <Table\n columns={[\n {\n key: \"0\",\n label: \"Company\",\n render: (r) => (\n <span\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Avatar name={r[0]} size={30} />\n <span>\n <b>{r[0]}</b>\n <div className=\"tf-card-desc\">{r[1]}</div>\n </span>\n </span>\n ),\n },\n {\n key: \"2\",\n label: \"Plan\",\n render: (r) => (\n <Badge\n tone={r[2] === \"Business\" ? \"highlight\" : r[2] === \"Team\" ? \"info\" : \"neutral\"}\n dot={false}\n >\n {r[2]}\n </Badge>\n ),\n },\n {\n key: \"3\",\n label: \"MRR\",\n align: \"right\",\n render: (r) => \"€\" + r[3].toLocaleString(\"en-US\"),\n },\n {\n key: \"4\",\n label: \"Health\",\n render: (r) => <Badge tone={HT[r[4]]}>{r[4]}</Badge>,\n },\n ]}\n rows={CO}\n />\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/CustomersScreen"},{"name":"Dashboard","title":"Dashboard","group":"Pages","summary":"A complete screen built from AppShell, Badge, Button, Card and 10 more components.","description":"","components":["AppShell","Badge","Button","Card","Chip","EventCard","IconButton","PageHeader","Panel","SearchField","Stagger","StatTile","Tabs","TintIcon"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Badge,\n Button,\n Card,\n Chip,\n EventCard,\n IconButton,\n PageHeader,\n Panel,\n SearchField,\n Stagger,\n StatTile,\n Tabs,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport { cardActs } from \"./_workspace\";\n\nfunction App() {\n const f = React.useState(\"All\");\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n side={\n <Panel\n side\n title=\"Schedule\"\n subtitle=\"5 items waiting · 2 today\"\n action={<IconButton icon=\"plus\" label=\"Add event\" />}\n >\n <Tabs block items={[\"Events\", \"Approvals\", \"History\"]} />\n <Stagger className=\"tf-col\" delay={300}>\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Tue, Apr 14\" time=\"10:30\">\n Roadmap check-in with design and growth; bring last sprint numbers.\n </EventCard>\n <EventCard kind=\"review\" label=\"Design review\" date=\"Wed, Apr 15\" time=\"14:00\">\n Walk through the new billing settings flow.\n </EventCard>\n <EventCard kind=\"deadline\" label=\"Deadline\" date=\"Fri, Apr 17\" time=\"17:00\">\n Send Q2 invoices to all annual customers.\n </EventCard>\n </Stagger>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Projects\"\n subtitle=\"Plan, ship and retire work across the workspace\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search people, projects, invoices\" shortcut=\"⌘K\" />,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"All projects\" />\n <StatTile tint=\"mint\" value=\"11\" label=\"Live\" delta=\"+2 this week\" />\n <StatTile tint=\"placeholder\" value=\"4\" label=\"Drafts\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Revenue (MTD)\" />\n </Stagger>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"260ms\",\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 12,\n alignItems: \"center\",\n justifyContent: \"space-between\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-row\">\n {[\"All\", \"Live\", \"Draft\", \"Archived\"].map((t) => (\n <Chip\n key={t}\n selected={f[0] === t}\n onClick={() => {\n f[1](t);\n }}\n >\n {t}\n </Chip>\n ))}\n </div>\n <Button icon=\"plus\">New project</Button>\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n delay={320}\n >\n <Card\n onClick={() => {}}\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new workspace admins.\"\n meta={[\"Mara Ellis\", \"12 steps\", \"348 users\"]}\n corner={<TintIcon icon=\"book\" tint=\"mint\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <Card\n onClick={() => {}}\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table for annual plans.\"\n meta={[\"Jonah Petit\", \"6 sections\"]}\n corner={<IconButton icon=\"play\" label=\"Preview\" size=\"sm\" variant=\"outline\" />}\n status={<Badge tone=\"neutral\">86 viewing</Badge>}\n actions={[<IconButton key={1} icon=\"edit\" label=\"Edit\" size=\"sm\" variant=\"solid\" />]}\n />\n <Card\n onClick={() => {}}\n title=\"Partner referral program\"\n description=\"Track invites, payouts and partner tiers.\"\n meta={[\"Sun Lee\", \"4 steps\", \"57 partners\"]}\n corner={<TintIcon icon=\"users\" tint=\"sky\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <Card\n onClick={() => {}}\n title=\"Invoice #2041 is overdue\"\n description=\"Net-30 passed four days ago; the reminder bounced.\"\n meta={[\"Rui Costa\", \"High priority\"]}\n corner={<TintIcon icon=\"mail\" tint=\"blush\" />}\n status={<Badge tone=\"warning\">Open</Badge>}\n actions={[\n <Button key={1} size=\"sm\" variant=\"soft\">\n Reply\n </Button>,\n ]}\n />\n <Card\n onClick={() => {}}\n title=\"Help center migration\"\n description=\"Move 140 articles to the new docs layout.\"\n meta={[\"Ava Brandt\", \"0 published\"]}\n corner={<TintIcon icon=\"file\" tint=\"mint\" />}\n status={<Badge>Draft</Badge>}\n actions={cardActs()}\n />\n <Card\n onClick={() => {}}\n title=\"Annual plan upgrade request\"\n description=\"Northwind wants 40 more seats before renewal.\"\n meta={[\"Ida Nolan\", \"Normal priority\"]}\n corner={<TintIcon icon=\"card\" tint=\"butter\" />}\n status={<Badge tone=\"info\">Pending</Badge>}\n actions={cardActs()}\n />\n </Stagger>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/Dashboard"},{"name":"FilesScreen","title":"Files","group":"Pages","summary":"A complete screen built from Chip, CommandPalette, FileCard, IconButton and 6 more components.","description":"","components":["Chip","CommandPalette","FileCard","Icon","IconButton","PageHeader","SearchField","Stagger","SubNav","Tabs","TopBar"],"code":"import * as React from \"react\";\nimport {\n Chip,\n CommandPalette,\n FileCard,\n PageHeader,\n SearchField,\n Stagger,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\nimport type { FileKind } from \"@twentyfirstmedia/ui-core\";\n\nconst F: [string, FileKind, string, string, boolean?][] = [\n [\"Northwind – master agreement.pdf\", \"pdf\", \"412 KB\", \"Today\", true],\n [\"Kestrel Bank – proposal v3.pdf\", \"pdf\", \"1.8 MB\", \"Yesterday\", true],\n [\"DPA template (DSGVO).doc\", \"doc\", \"96 KB\", \"Mon\"],\n [\"pricing-tiers.fig\", \"fig\", \"18 MB\", \"Today\", true],\n [\"dashboard-shots.png\", \"img\", \"2.4 MB\", \"Yesterday\"],\n [\"brand-assets.zip\", \"zip\", \"36 MB\", \"Apr 2\"],\n [\"support-macros.sheet\", \"sheet\", \"58 KB\", \"Yesterday\"],\n [\"pnl-q1-2026.sheet\", \"sheet\", \"92 KB\", \"Apr 1\", true],\n];\nfunction App() {\n return (\n <Shell\n area=\"Files\"\n current=\"files\"\n action={{\n label: \"Upload\",\n icon: \"paperclip\",\n }}\n pages={[\n {\n id: \"files\",\n label: \"All files\",\n icon: \"folder\",\n },\n ]}\n overlay={\n <CommandPalette\n inline\n onClose={() => {}}\n items={[\n {\n id: 1,\n group: \"Pages\",\n label: \"All files\",\n hint: \"Files\",\n icon: \"folder\",\n },\n {\n id: 2,\n group: \"Actions\",\n label: \"Upload files\",\n icon: \"paperclip\",\n tint: \"butter\",\n },\n {\n id: 3,\n group: \"Actions\",\n label: \"New invoice\",\n icon: \"card\",\n tint: \"butter\",\n },\n {\n id: 4,\n group: \"Actions\",\n label: \"Compose mail\",\n icon: \"edit\",\n tint: \"sky\",\n },\n {\n id: 5,\n group: \"Actions\",\n label: \"New agent\",\n icon: \"bot\",\n tint: \"lilac\",\n },\n ]}\n />\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Files\"\n subtitle=\"15 files · drop files anywhere to upload\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search files\" />,\n <Tabs key=\"t\" items={[\"Grid\", \"List\"]} />,\n ]}\n />\n </div>\n <div\n className=\"tf-row tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n {[\"All\", \"Contracts\", \"Receipts\", \"Design\", \"Agent knowledge\", \"Exports\"].map((t, i) => (\n <Chip key={t} icon={i ? \"folder\" : undefined} selected={i === 0}>\n {t}\n </Chip>\n ))}\n </div>\n <Stagger\n className=\"tf-grid\"\n style={{\n gridTemplateColumns: \"repeat(auto-fill, minmax(180px, 1fr))\",\n }}\n delay={140}\n >\n {F.map((f) => (\n <FileCard key={f[0]} name={f[0]} kind={f[1]} size={f[2]} meta={f[3]} shared={f[4]} />\n ))}\n </Stagger>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/FilesScreen"},{"name":"InboxScreen","title":"Inbox","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, Button and 9 more components.","description":"","components":["AppShell","Avatar","Badge","Button","FileRow","IconButton","ListRow","PageHeader","Panel","SearchField","Stagger","StatTile","Tabs"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n Button,\n FileRow,\n IconButton,\n ListRow,\n PageHeader,\n Panel,\n SearchField,\n Stagger,\n StatTile,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { ListRowProps } from \"@twentyfirstmedia/ui-core\";\n\nfunction Msg(p: {\n who: string;\n time: string;\n d: number;\n me?: boolean;\n children?: React.ReactNode;\n}) {\n return (\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": p.d + \"ms\",\n display: \"flex\",\n gap: 12,\n flexDirection: p.me ? \"row-reverse\" : \"row\",\n alignItems: \"flex-end\",\n } as React.CSSProperties\n }\n >\n <Avatar name={p.who} size={28} />\n <div\n style={{\n maxWidth: \"72%\",\n padding: \"12px 16px\",\n borderRadius: \"var(--radius-md)\",\n borderBottomLeftRadius: p.me ? 18 : 6,\n borderBottomRightRadius: p.me ? 6 : 18,\n background: p.me ? \"var(--primary)\" : \"var(--surface-sunken)\",\n color: p.me ? \"var(--primary-foreground)\" : \"var(--ink)\",\n font: \"var(--type-body-weight) 13px/20px var(--font-sans)\",\n }}\n >\n {p.children}\n <div\n style={{\n font: \"var(--type-body-weight) 11px/14px var(--font-sans)\",\n opacity: 0.65,\n marginTop: 4,\n }}\n >\n {p.time}\n </div>\n </div>\n </div>\n );\n}\nfunction App() {\n const sel = React.useState(0);\n const list: ListRowProps[] = [\n {\n avatar: \"Jonah Petit\",\n title: \"Jonah Petit\",\n subtitle: \"Can we move the pricing review to Thursday?\",\n meta: \"09:12\",\n unread: true,\n },\n {\n avatar: \"Northwind\",\n title: \"Northwind · Ida\",\n subtitle: \"Seat upgrade before renewal — 40 more\",\n meta: \"08:40\",\n unread: true,\n },\n {\n icon: \"bell\",\n tint: \"butter\",\n title: \"Billing\",\n subtitle: \"Card ending 4417 expires this month\",\n meta: \"Yesterday\",\n },\n {\n avatar: \"Sun Lee\",\n title: \"Sun Lee\",\n subtitle: \"Payout file for March is attached\",\n meta: \"Yesterday\",\n },\n {\n avatar: \"Blue Fern\",\n title: \"Blue Fern · Leo\",\n subtitle: \"Thanks, the export works now!\",\n meta: \"Mon\",\n },\n {\n icon: \"flag\",\n tint: \"blush\",\n title: \"Incident #88\",\n subtitle: \"Webhooks delayed in EU region\",\n meta: \"Sun\",\n },\n ];\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Inbox\"\n dock={false}\n side={\n <Panel side title=\"Northwind\" subtitle=\"Team plan · since 2023\">\n <div className=\"tf-row\">\n <Badge tone=\"success\">Active</Badge>\n <Badge tone=\"info\">Renews May 1</Badge>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 12,\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€1,240\" label=\"MRR\" />\n <StatTile tint=\"sky\" value=\"24\" label=\"Seats\" />\n </div>\n <h3\n className=\"tf-card-title\"\n style={{\n fontSize: 16,\n }}\n >\n Attachments\n </h3>\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n delay={300}\n >\n <FileRow name=\"seat-quote.pdf\" meta=\"182 KB · PDF\" kind=\"pdf\" />\n <FileRow name=\"usage-q1.xlsx\" meta=\"96 KB · Sheet\" kind=\"sheet\" />\n </Stagger>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Inbox\"\n subtitle=\"2 unread · 4 waiting on you\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search messages\" />,\n <Button key=\"n\" icon=\"edit\">\n Compose\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"320px 1fr\",\n gap: 16,\n minHeight: 0,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <Tabs block items={[\"All\", \"Unread\", \"Assigned\"]} />\n <Stagger className=\"tf-list\" delay={120}>\n {list.map((it, i) => (\n <ListRow\n key={i}\n {...it}\n active={sel[0] === i}\n onClick={() => {\n sel[1](i);\n }}\n />\n ))}\n </Stagger>\n </div>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"160ms\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-header\"\n style={{\n alignItems: \"center\",\n }}\n >\n <div className=\"tf-row\">\n <Avatar name=\"Jonah Petit\" size={36} />\n <div>\n <div\n className=\"tf-card-title\"\n style={{\n padding: 0,\n fontSize: 16,\n }}\n >\n Jonah Petit\n </div>\n <div className=\"tf-card-desc\">Pricing page refresh</div>\n </div>\n </div>\n <div className=\"tf-row\">\n <IconButton icon=\"star\" label=\"Star\" />\n <IconButton icon=\"inbox\" label=\"Archive\" />\n <IconButton icon=\"more\" label=\"More\" />\n </div>\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 12,\n padding: \"8px 0\",\n }}\n >\n <Msg who=\"Jonah Petit\" time=\"09:02\" d={260}>\n Morning! The comparison table is ready for a look.\n </Msg>\n <Msg who=\"Jonah Petit\" time=\"09:12\" d={360}>\n Can we move the pricing review to Thursday? Growth wants to join.\n </Msg>\n <Msg who=\"Mara Ellis\" me time=\"09:20\" d={460}>\n Thursday works. I will move the event and add Sun.\n </Msg>\n </div>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"560ms\",\n marginTop: \"auto\",\n display: \"flex\",\n gap: 8,\n alignItems: \"center\",\n padding: \"4px 4px 4px 20px\",\n borderRadius: \"var(--radius-pill)\",\n background: \"var(--surface-sunken)\",\n } as React.CSSProperties\n }\n >\n <IconButton icon=\"paperclip\" label=\"Attach\" variant=\"outline\" />\n <input\n placeholder=\"Write a reply…\"\n aria-label=\"Reply\"\n style={{\n flex: 1,\n minWidth: 0,\n border: 0,\n background: \"transparent\",\n outline: \"none\",\n font: \"var(--type-body-weight) 13px var(--font-sans)\",\n color: \"var(--ink)\",\n }}\n />\n <Button icon=\"send\">Send</Button>\n </div>\n </div>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/InboxScreen"},{"name":"InviteModalScreen","title":"Invite Modal","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, Button and 8 more components.","description":"","components":["AppShell","Avatar","Badge","Button","Card","IconButton","Input","Modal","PageHeader","Select","StatTile","Toast"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n Button,\n Card,\n Input,\n Modal,\n PageHeader,\n Select,\n StatTile,\n Toast,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n style={{\n position: \"relative\",\n }}\n >\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"People\"\n dock={false}\n >\n <PageHeader\n title=\"People\"\n subtitle=\"Everyone with access to this workspace\"\n actions={[\n <Button key=\"b\" icon=\"users\">\n Invite\n </Button>,\n ]}\n />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"38\" label=\"Members\" />\n <StatTile tint=\"mint\" value=\"31\" label=\"Active today\" />\n <StatTile tint=\"placeholder\" value=\"3\" label=\"Pending invites\" />\n <StatTile tint=\"lilac\" value=\"6\" label=\"Admins\" />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n >\n {[\"Mara Ellis\", \"Jonah Petit\", \"Sun Lee\", \"Rui Costa\", \"Ida Nolan\", \"Ava Brandt\"].map(\n (n) => (\n <Card\n key={n}\n title={n}\n description=\"Member\"\n corner={<Avatar name={n} size={32} />}\n status={<Badge tone=\"success\">Active</Badge>}\n />\n ),\n )}\n </div>\n </AppShell>\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n borderRadius: \"var(--radius-xl)\",\n overflow: \"hidden\",\n }}\n >\n <Modal\n style={{\n position: \"absolute\",\n inset: 0,\n }}\n icon=\"users\"\n tint=\"sky\"\n title=\"Invite teammates\"\n text=\"They get access to every project in this workspace.\"\n actions={[\n <Button key={1} variant=\"ghost\">\n Cancel\n </Button>,\n <Button key={2} icon=\"send\">\n Send 3 invites\n </Button>,\n ]}\n >\n <Input\n label=\"Email addresses\"\n defaultValue=\"leo@bluefern.io, nina@bluefern.io, ops@bluefern.io\"\n />\n <Select label=\"Role\" options={[\"Editor\", \"Viewer\", \"Admin\"]} />\n <div className=\"tf-row\">\n <Badge tone=\"info\">3 seats left after this</Badge>\n <Badge>Expires in 7 days</Badge>\n </div>\n </Modal>\n </div>\n <div\n style={{\n position: \"absolute\",\n right: 40,\n bottom: 40,\n }}\n >\n <Toast action=\"Undo\">Ava Brandt was removed</Toast>\n </div>\n </div>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/InviteModalScreen"},{"name":"InvoiceScreen","title":"Invoice","group":"Pages","summary":"A complete screen built from Button, IconButton, InvoicePreview, PageHeader and 5 more components.","description":"","components":["Button","Icon","IconButton","InvoicePreview","PageHeader","Select","SubNav","TintIcon","TopBar","TransactionRow"],"code":"import * as React from \"react\";\nimport {\n Button,\n InvoicePreview,\n PageHeader,\n Select,\n TintIcon,\n TransactionRow,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n const lines = [\n {\n d: \"Halo Team plan · 40 seats · May 2026\",\n q: 40,\n u: 24,\n v: 19,\n },\n {\n d: \"Onboarding workshop (half day)\",\n q: 1,\n u: 650,\n v: 19,\n },\n ];\n return (\n <Shell\n area=\"Finance\"\n current=\"invoices\"\n action={{\n label: \"New invoice\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"overview\",\n label: \"Overview\",\n icon: \"chart\",\n },\n {\n id: \"invoices\",\n label: \"Invoices\",\n icon: \"card\",\n count: 4,\n },\n {\n id: \"expenses\",\n label: \"Expenses\",\n icon: \"file\",\n count: 4,\n },\n {\n id: \"bank\",\n label: \"Bank\",\n icon: \"swap\",\n count: 4,\n },\n {\n id: \"vat\",\n label: \"VAT return\",\n icon: \"flag\",\n },\n {\n id: \"pnl\",\n label: \"Profit & loss\",\n icon: \"trend\",\n },\n ]}\n >\n <div className=\"tf-in\">\n <PageHeader\n crumbs={[\"Finance\", \"Invoices\", \"New\"]}\n title=\"Invoice #2097\"\n subtitle=\"Totals update as you type\"\n actions={[\n <Button key=\"d\" variant=\"secondary\">\n Save draft\n </Button>,\n <Button key=\"s\" icon=\"send\">\n {\"Create & send\"}\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1.1fr) minmax(0, 1fr)\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n <Select label=\"Customer\" options={[\"Northwind\", \"Kestrel Bank\"]} />\n <Select label=\"Payment terms\" options={[\"14 days\", \"30 days\"]} />\n </div>\n {lines.map((l, i) => (\n <div key={i} className=\"tf-file\">\n <TintIcon icon=\"card\" tint=\"butter\" />\n <div\n style={{\n flex: 1,\n minWidth: 0,\n }}\n >\n <div className=\"tf-file-name\">{l.d}</div>\n <div className=\"tf-file-meta\">{l.q + \" × €\" + l.u + \" · VAT \" + l.v + \"%\"}</div>\n </div>\n <b>{\"€\" + (l.q * l.u).toLocaleString(\"en-US\") + \".00\"}</b>\n </div>\n ))}\n <Button variant=\"soft\" icon=\"plus\">\n Add line\n </Button>\n </div>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"160ms\",\n } as React.CSSProperties\n }\n >\n <InvoicePreview\n brand=\"Halo\"\n number={2097}\n from=\"Halo Workspace GmbH · Example Street 1 · 20457 Hamburg\"\n to=\"Northwind\"\n issued=\"Apr 16, 2026\"\n due=\"Apr 30, 2026\"\n lines={lines}\n note=\"Thank you for your business. Please pay within 14 days.\"\n />\n </div>\n </div>\n <div\n className=\"tf-col tf-in\"\n style={\n {\n gap: 8,\n \"--d\": \"240ms\",\n } as React.CSSProperties\n }\n >\n <h3\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Bank: to match\n </h3>\n <TransactionRow\n text=\"STRIPE PAYOUT 0415\"\n date=\"Apr 15, 2026\"\n amount={2318.4}\n hint=\"Payout for 3 card payments: invoices #2093, #2094, #2095\"\n />\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/InvoiceScreen"},{"name":"LoadingScreen","title":"Loading","group":"Pages","summary":"A complete screen built from AppShell, Badge, Button, Card and 7 more components.","description":"","components":["AppShell","Badge","Button","Card","EmptyState","IconButton","PageHeader","Skeleton","Stagger","StatTile","TintIcon"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Badge,\n Button,\n Card,\n EmptyState,\n PageHeader,\n Skeleton,\n Stagger,\n StatTile,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport { cardActs } from \"./_workspace\";\n\nfunction SkelCard() {\n return (\n <div className=\"tf-card\">\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <Skeleton variant=\"title\" width=\"55%\" />\n <Skeleton variant=\"circle\" width={30} />\n </div>\n <Skeleton />\n <Skeleton width=\"70%\" />\n <div className=\"tf-card-foot\">\n <Skeleton width={70} height={26} />\n <div className=\"tf-row\">\n <Skeleton variant=\"circle\" width={30} />\n <Skeleton variant=\"circle\" width={30} />\n </div>\n </div>\n </div>\n );\n}\nfunction App() {\n const done = React.useState(false);\n React.useEffect(() => {\n const t = setTimeout(() => {\n done[1](true);\n }, 2200);\n return () => {\n clearTimeout(t);\n };\n }, []);\n if (done[0])\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Projects\"\n subtitle=\"Loaded — content replaces the skeleton with the normal entrance\"\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"All projects\" />\n <StatTile tint=\"mint\" value=\"11\" label=\"Live\" />\n <StatTile tint=\"placeholder\" value=\"4\" label=\"Drafts\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Revenue (MTD)\" />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n delay={200}\n >\n <Card\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new workspace admins.\"\n corner={<TintIcon icon=\"book\" tint=\"mint\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <Card\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table.\"\n status={<Badge>86 viewing</Badge>}\n />\n <Card\n title=\"Partner referral program\"\n description=\"Track invites, payouts and tiers.\"\n corner={<TintIcon icon=\"users\" tint=\"sky\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <EmptyState\n title=\"No archived projects\"\n text=\"Projects you retire land here, with their history kept.\"\n icon=\"folder\"\n tint=\"butter\"\n action={\n <Button variant=\"soft\" size=\"sm\">\n View all\n </Button>\n }\n />\n </Stagger>\n </AppShell>\n );\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n dock={false}\n >\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <Skeleton variant=\"title\" width={220} height={28} />\n <Skeleton width={320} />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n {[0, 1, 2, 3].map((i) => (\n <Skeleton key={i} variant=\"block\" height={84} />\n ))}\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n >\n {[0, 1, 2, 3, 4, 5].map((i) => (\n <SkelCard key={i} />\n ))}\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/LoadingScreen"},{"name":"MapScreen","title":"Map","group":"Pages","summary":"A city map with glass controls floating over it: search and filters on top, map layers on the start side, the selected place with directions on the end side, zoom buttons and a dock.","description":"A city map with glass controls floating over it: search and filters on top, map layers on the start side, the selected place with directions on the end side, zoom buttons and a dock.\n\n- Built from `FloatingLayout` and `MapView`; the map is drawn from the kit's colours, so it follows the kit and the theme. Put your own map (MapLibre, Mapbox, Cesium, three.js) into `background` instead.\n- Everything floating is glass: translucent, blurred and readable over any backdrop.\n","components":["Badge","Button","ButtonGroup","Chip","Dock","FloatingLayout","IconButton","KeyValue","MapView","Panel","SearchField","Segmented","Switch"],"code":"import * as React from \"react\";\nimport {\n Badge,\n Button,\n ButtonGroup,\n Chip,\n Dock,\n FloatingLayout,\n IconButton,\n KeyValue,\n MapView,\n Panel,\n SearchField,\n Segmented,\n Switch,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\n/** A city drawn from the kit's colours, so it follows the kit and the theme. */\nfunction CityMap() {\n const streets = [80, 190, 300, 410, 520, 630, 740];\n const avenues = [90, 230, 370, 510, 650, 790, 930, 1070];\n return (\n <svg viewBox=\"0 0 1200 800\" preserveAspectRatio=\"xMidYMid slice\" aria-hidden>\n <rect width=\"1200\" height=\"800\" fill=\"var(--background)\" />\n {avenues.flatMap((x, i) =>\n streets.map((y, j) => (\n <rect\n key={`${i}-${j}`}\n x={x + 12}\n y={y + 12}\n width={116}\n height={86}\n rx={6}\n fill=\"var(--muted)\"\n />\n )),\n )}\n <rect x=\"652\" y=\"82\" width=\"256\" height=\"196\" rx=\"18\" fill=\"var(--mint)\" />\n <rect x=\"92\" y=\"522\" width=\"256\" height=\"196\" rx=\"18\" fill=\"var(--mint)\" />\n <circle cx=\"790\" cy=\"180\" r=\"34\" fill=\"var(--sky)\" />\n <path\n d=\"M-40 470 C 180 400, 320 560, 520 470 S 860 330, 1240 430 L 1240 560 C 900 470, 720 610, 520 600 S 160 540, -40 610 Z\"\n fill=\"var(--sky)\"\n />\n <g stroke=\"var(--card)\" strokeWidth=\"10\" strokeLinecap=\"round\">\n {streets.map((y) => (\n <line key={y} x1=\"0\" x2=\"1200\" y1={y + 6} y2={y + 6} />\n ))}\n {avenues.map((x) => (\n <line key={x} x1={x + 6} x2={x + 6} y1=\"0\" y2=\"800\" />\n ))}\n </g>\n <g stroke=\"var(--butter)\" strokeWidth=\"16\" strokeLinecap=\"round\" fill=\"none\">\n <path d=\"M0 196 L 1200 196\" />\n <path d=\"M516 0 L 516 800\" />\n <path d=\"M150 800 L 660 330 L 1200 120\" />\n </g>\n </svg>\n );\n}\n\nconst places: MapPoint[] = [\n {\n id: \"cafe\",\n label: \"Kaffeerösterei\",\n sub: \"Café · open until 18:00\",\n lat: 72,\n lng: 44,\n text: \"Café\",\n tint: \"butter\",\n },\n {\n id: \"museum\",\n label: \"Harbour Museum\",\n sub: \"Museum · €12\",\n lat: 50,\n lng: 54,\n text: \"Museum\",\n tint: \"lilac\",\n },\n {\n id: \"market\",\n label: \"Fish Market\",\n sub: \"Market · Sundays 5–9:30\",\n lat: 24,\n lng: 40,\n text: \"Market\",\n tint: \"blush\",\n },\n {\n id: \"park\",\n label: \"Planten un Blomen\",\n sub: \"Park\",\n lat: 32,\n lng: 60,\n text: \"Park\",\n tint: \"mint\",\n },\n];\n\nexport default function Preview() {\n const [selected, setSelected] = React.useState<MapPoint[\"id\"] | null>(\"museum\");\n const [mode, setMode] = React.useState(\"Explore\");\n const place = places.find((p) => p.id === selected);\n return (\n <FloatingLayout\n label=\"City map\"\n height={720}\n background={\n <MapView\n label=\"Map of the harbour district\"\n background={<CityMap />}\n bounds={{ n: 100, s: 0, w: 0, e: 100 }}\n points={places}\n selected={selected}\n onSelect={setSelected}\n height=\"100%\"\n />\n }\n top={\n <>\n <SearchField\n placeholder=\"Search places and addresses\"\n style={{ flex: \"1 1 260px\", maxWidth: 420 }}\n />\n <Chip>Coffee</Chip>\n <Chip>Museums</Chip>\n <Chip selected>Open now</Chip>\n </>\n }\n start={\n <Panel title=\"Layers\" subtitle=\"What the map shows\">\n <Segmented\n label=\"Map style\"\n options={[\"Explore\", \"Transit\", \"Satellite\"]}\n value={mode}\n onChange={setMode}\n block\n />\n <Switch defaultChecked>Traffic</Switch>\n <Switch defaultChecked>Parks and water</Switch>\n <Switch>Bike lanes</Switch>\n </Panel>\n }\n end={\n <div style={{ display: \"grid\", gap: 12 }}>\n <ButtonGroup orientation=\"vertical\" label=\"Zoom\" style={{ justifySelf: \"end\" }}>\n <IconButton icon=\"plus\" label=\"Zoom in\" variant=\"outline\" />\n <IconButton icon=\"minus\" label=\"Zoom out\" variant=\"outline\" />\n </ButtonGroup>\n {place && (\n <Panel\n title={place.label}\n subtitle={typeof place.sub === \"string\" ? place.sub : undefined}\n action={\n <Badge tone=\"success\" dot>\n Open\n </Badge>\n }\n >\n <KeyValue\n items={[\n [\"Walk\", \"12 min\"],\n [\"Distance\", \"850 m\"],\n [\"Rating\", \"4.7 of 5\"],\n ]}\n />\n <div className=\"tf-row\" style={{ gap: 8 }}>\n <Button variant=\"primary\" icon=\"send\">\n Directions\n </Button>\n <Button variant=\"secondary\" icon=\"star\">\n Save\n </Button>\n </div>\n </Panel>\n )}\n </div>\n }\n bottom={\n <Dock\n label=\"Map tools\"\n items={[\n { icon: \"search\", label: \"Search\", tint: \"sky\" },\n { icon: \"star\", label: \"Saved\", tint: \"butter\" },\n { icon: \"clock\", label: \"Recent\", tint: \"mint\" },\n { icon: \"share\", label: \"Share\", tint: \"lilac\" },\n ]}\n />\n }\n />\n );\n}\n","path":"patterns/MapScreen"},{"name":"MobileScreens","title":"Mobile Screens","group":"Pages","summary":"A complete screen built from Avatar, Badge, Card, Chip and 7 more components.","description":"","components":["Avatar","Badge","Card","Chip","Dock","EventCard","IconButton","ListRow","SearchField","Stagger","StatTile"],"code":"import * as React from \"react\";\nimport {\n Avatar,\n Badge,\n Card,\n Chip,\n Dock,\n EventCard,\n IconButton,\n ListRow,\n SearchField,\n Stagger,\n StatTile,\n} from \"@twentyfirstmedia/ui-core\";\nimport { cardActs } from \"./_workspace\";\n\nfunction Phone(p: { d: number; tab: number; children?: React.ReactNode }) {\n return (\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": p.d + \"ms\",\n width: 360,\n maxWidth: \"100%\",\n height: 760,\n borderRadius: 44,\n background: \"var(--frame)\",\n padding: 10,\n boxSizing: \"border-box\",\n boxShadow: \"var(--shadow-float)\",\n flex: \"none\",\n scrollSnapAlign: \"center\",\n } as React.CSSProperties\n }\n >\n <div\n style={{\n height: \"100%\",\n borderRadius: 36,\n background: \"var(--canvas)\",\n overflow: \"hidden\",\n display: \"flex\",\n flexDirection: \"column\",\n position: \"relative\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"16px 20px 8px\",\n font: \"var(--type-title-weight) 13px var(--font-sans)\",\n }}\n >\n <span>9:41</span>\n <span\n style={{\n width: 90,\n height: 24,\n borderRadius: \"var(--radius-pill)\",\n background: \"var(--frame)\",\n }}\n />\n <span>•••</span>\n </div>\n <div\n style={{\n flex: 1,\n overflow: \"hidden\",\n padding: \"8px 16px 90px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: 16,\n }}\n >\n {p.children}\n </div>\n <div\n style={{\n position: \"absolute\",\n left: 0,\n right: 0,\n bottom: 16,\n display: \"flex\",\n justifyContent: \"center\",\n }}\n >\n <Dock\n items={[\n {\n label: \"Home\",\n icon: \"home\",\n tint: p.tab === 0 ? \"sky\" : \"plain\",\n },\n {\n label: \"Projects\",\n icon: \"grid\",\n tint: p.tab === 1 ? \"mint\" : \"plain\",\n },\n {\n label: \"Inbox\",\n icon: \"mail\",\n tint: p.tab === 2 ? \"lilac\" : \"plain\",\n },\n {\n label: \"New\",\n icon: \"plus\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\nfunction App() {\n return (\n <div\n style={{\n display: \"flex\",\n gap: 28,\n justifyContent: \"safe center\",\n overflowX: \"auto\",\n scrollSnapType: \"x mandatory\",\n padding: \"20px 0\",\n background: \"var(--canvas)\",\n borderRadius: \"var(--radius-xl)\",\n }}\n >\n <Phone d={0} tab={0}>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <div>\n <div className=\"tf-card-desc\">Good morning</div>\n <div\n className=\"tf-header-title\"\n style={{\n fontSize: 24,\n }}\n >\n Mara\n </div>\n </div>\n <Avatar name=\"Mara Ellis\" size={40} />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 12,\n } as React.CSSProperties\n }\n delay={150}\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"Projects\" />\n <StatTile tint=\"butter\" value=\"€48k\" label=\"Revenue\" />\n <StatTile tint=\"mint\" value=\"11\" label=\"Live\" />\n <StatTile tint=\"placeholder\" value=\"4\" label=\"Review\" />\n </Stagger>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"400ms\",\n } as React.CSSProperties\n }\n >\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Today\" time=\"10:30\" actions={false}>\n Roadmap check-in with design and growth.\n </EventCard>\n </div>\n </Phone>\n <Phone d={120} tab={1}>\n <div\n className=\"tf-header-title\"\n style={{\n fontSize: 24,\n }}\n >\n Projects\n </div>\n <div\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Chip selected>All</Chip>\n <Chip>Live</Chip>\n <Chip>Draft</Chip>\n </div>\n <Stagger className=\"tf-col\" delay={250}>\n <Card\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table.\"\n status={<Badge>86 viewing</Badge>}\n actions={[<IconButton key={1} icon=\"edit\" label=\"Edit\" size=\"sm\" variant=\"solid\" />]}\n style={{\n minHeight: 0,\n }}\n />\n <Card\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new admins.\"\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n style={{\n minHeight: 0,\n }}\n />\n <Card\n title=\"Partner referrals\"\n description=\"Invites, payouts, tiers.\"\n status={<Badge tone=\"success\">Live</Badge>}\n style={{\n minHeight: 0,\n }}\n />\n </Stagger>\n </Phone>\n <Phone d={240} tab={2}>\n <div\n className=\"tf-header-title\"\n style={{\n fontSize: 24,\n }}\n >\n Inbox\n </div>\n <SearchField\n placeholder=\"Search messages\"\n style={{\n minWidth: 0,\n }}\n />\n <Stagger className=\"tf-list\" delay={350}>\n <ListRow\n avatar=\"Jonah Petit\"\n title=\"Jonah Petit\"\n subtitle=\"Move the review to Thursday?\"\n meta=\"09:12\"\n unread\n />\n <ListRow\n avatar=\"Northwind\"\n title=\"Northwind\"\n subtitle=\"Seat upgrade before renewal\"\n meta=\"08:40\"\n unread\n />\n <ListRow\n icon=\"bell\"\n tint=\"butter\"\n title=\"Billing\"\n subtitle=\"Card ending 4417 expires\"\n meta=\"Yest.\"\n />\n <ListRow avatar=\"Sun Lee\" title=\"Sun Lee\" subtitle=\"Payout file attached\" meta=\"Yest.\" />\n <ListRow\n icon=\"flag\"\n tint=\"blush\"\n title=\"Incident #88\"\n subtitle=\"Webhooks delayed in EU\"\n meta=\"Sun\"\n />\n </Stagger>\n </Phone>\n </div>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/MobileScreens"},{"name":"PeopleScreen","title":"People","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, Button and 11 more components.","description":"","components":["AppShell","Avatar","Badge","Button","Chip","Donut","IconButton","PageHeader","Pagination","Panel","SearchField","Stagger","StatTile","Table","Timeline"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n Button,\n Chip,\n Donut,\n IconButton,\n PageHeader,\n Pagination,\n Panel,\n SearchField,\n Stagger,\n StatTile,\n Table,\n Timeline,\n} from \"@twentyfirstmedia/ui-core\";\n\nconst MEMBERS = [\n {\n n: \"Mara Ellis\",\n e: \"mara@halo.app\",\n r: \"Owner\",\n t: \"Leadership\",\n s: \"Active\",\n tone: \"success\",\n a: \"Now\",\n },\n {\n n: \"Jonah Petit\",\n e: \"jonah@halo.app\",\n r: \"Admin\",\n t: \"Design\",\n s: \"Active\",\n tone: \"success\",\n a: \"5 min ago\",\n },\n {\n n: \"Sun Lee\",\n e: \"sun@halo.app\",\n r: \"Editor\",\n t: \"Growth\",\n s: \"Active\",\n tone: \"success\",\n a: \"1 h ago\",\n },\n {\n n: \"Rui Costa\",\n e: \"rui@halo.app\",\n r: \"Editor\",\n t: \"Finance\",\n s: \"Away\",\n tone: \"warning\",\n a: \"Yesterday\",\n },\n {\n n: \"Ava Brandt\",\n e: \"ava@halo.app\",\n r: \"Viewer\",\n t: \"Support\",\n s: \"Invited\",\n tone: \"info\",\n a: \"—\",\n },\n {\n n: \"Ida Nolan\",\n e: \"ida@halo.app\",\n r: \"Editor\",\n t: \"Sales\",\n s: \"Active\",\n tone: \"success\",\n a: \"3 h ago\",\n },\n {\n n: \"Theo Marsh\",\n e: \"theo@halo.app\",\n r: \"Viewer\",\n t: \"Design\",\n s: \"Suspended\",\n tone: \"danger\",\n a: \"12 days ago\",\n },\n];\nfunction App() {\n const f = React.useState(\"Everyone\");\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"People\"\n side={\n <Panel side title=\"Team\" subtitle=\"42 seats · 38 used\">\n <Donut\n size={120}\n stroke={16}\n legend={false}\n center=\"38\"\n caption=\"of 42\"\n data={[\n {\n label: \"Design\",\n value: 9,\n tint: \"lilac\",\n },\n {\n label: \"Growth\",\n value: 11,\n tint: \"mint\",\n },\n {\n label: \"Support\",\n value: 8,\n tint: \"sky\",\n },\n {\n label: \"Finance\",\n value: 4,\n tint: \"butter\",\n },\n {\n label: \"Sales\",\n value: 6,\n tint: \"blush\",\n },\n ]}\n />\n <div className=\"tf-legend\">\n {[\n [\"Design\", \"lilac\"],\n [\"Growth\", \"mint\"],\n [\"Support\", \"sky\"],\n [\"Finance\", \"butter\"],\n [\"Sales\", \"blush\"],\n ].map((x) => (\n <span key={x[0]}>\n <i\n style={{\n background: \"var(--\" + x[1] + \"-ink)\",\n }}\n />\n {x[0]}\n </span>\n ))}\n </div>\n <h3\n className=\"tf-card-title\"\n style={{\n fontSize: 16,\n marginTop: 8,\n }}\n >\n Recent activity\n </h3>\n <Timeline\n delay={400}\n items={[\n {\n avatar: \"Jonah Petit\",\n text: (\n <span>\n <b>Jonah</b>\n {\" changed Ava to Viewer\"}\n </span>\n ),\n time: \"10 min ago\",\n },\n {\n icon: \"send\",\n tint: \"sky\",\n text: \"3 invites sent to support@\",\n time: \"1 h ago\",\n },\n {\n avatar: \"Mara Ellis\",\n text: (\n <span>\n <b>Mara</b>\n {\" suspended Theo Marsh\"}\n </span>\n ),\n time: \"Mon\",\n },\n ]}\n />\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"People\"\n subtitle=\"Everyone with access to this workspace\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search by name or email\" />,\n <Button key=\"b\" icon=\"users\">\n Invite\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"sky\" value=\"38\" label=\"Members\" />\n <StatTile tint=\"mint\" value=\"31\" label=\"Active today\" />\n <StatTile tint=\"placeholder\" value=\"3\" label=\"Pending invites\" />\n <StatTile tint=\"lilac\" value=\"6\" label=\"Admins\" />\n </Stagger>\n <div\n className=\"tf-in tf-row\"\n style={\n {\n \"--d\": \"240ms\",\n } as React.CSSProperties\n }\n >\n {[\"Everyone\", \"Admins\", \"Editors\", \"Viewers\", \"Invited\"].map((t) => (\n <Chip\n key={t}\n selected={f[0] === t}\n onClick={() => {\n f[1](t);\n }}\n >\n {t}\n </Chip>\n ))}\n </div>\n <div\n className=\"tf-panel tf-panel-flush tf-in\"\n style={\n {\n \"--d\": \"300ms\",\n } as React.CSSProperties\n }\n >\n <Table\n columns={[\n {\n key: \"n\",\n label: \"Name\",\n render: (r) => (\n <span\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Avatar name={r.n} size={32} />\n <span>\n <div\n style={{\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n {r.n}\n </div>\n <div\n style={{\n fontSize: 12,\n color: \"var(--ink-muted)\",\n }}\n >\n {r.e}\n </div>\n </span>\n </span>\n ),\n },\n {\n key: \"r\",\n label: \"Role\",\n },\n {\n key: \"t\",\n label: \"Team\",\n },\n {\n key: \"s\",\n label: \"Status\",\n render: (r) => <Badge tone={r.tone}>{r.s}</Badge>,\n },\n {\n key: \"a\",\n label: \"Last active\",\n },\n {\n key: \"x\",\n label: \"\",\n align: \"right\",\n render: () => <IconButton icon=\"more\" label=\"Member options\" size=\"sm\" />,\n },\n ]}\n rows={MEMBERS}\n />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n padding: \"8px 16px\",\n }}\n >\n <span className=\"tf-card-desc\">Showing 7 of 38</span>\n <Pagination total={6} page={1} />\n </div>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/PeopleScreen"},{"name":"PricingScreen","title":"Pricing","group":"Pages","summary":"A complete screen built from PageHeader, Panel, PlanCard, Segmented and 3 more components.","description":"","components":["Icon","PageHeader","Panel","PlanCard","Segmented","SubNav","TopBar","UsageMeter"],"code":"import { PageHeader, Panel, PlanCard, Segmented, UsageMeter } from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n return (\n <Shell\n area=\"Account\"\n current={\"Plan & billing\"}\n pages={[\n {\n id: \"Profile\",\n label: \"Profile & theme\",\n icon: \"user\",\n },\n {\n id: \"Roles\",\n label: \"Roles & access\",\n icon: \"lock\",\n },\n {\n id: \"Plan & billing\",\n label: \"Plan & billing\",\n icon: \"card\",\n },\n ]}\n >\n <PageHeader\n title={\"Plan & billing\"}\n subtitle=\"You’re on Team · renews on May 1, 2026\"\n actions={[\n <Segmented\n key=\"y\"\n defaultValue=\"Yearly\"\n options={[\n \"Monthly\",\n {\n value: \"Yearly\",\n label: \"Yearly · save 17%\",\n },\n ]}\n />,\n ]}\n />\n <div className=\"tf-plans\">\n <PlanCard\n name=\"Starter\"\n price={9}\n was={12}\n text=\"For small teams\"\n features={[\"3 projects\", \"1 agent\", \"-Audit log\"]}\n />\n <PlanCard\n name=\"Team\"\n price={24}\n was={29}\n featured\n current\n text=\"For growing companies\"\n features={[\"Unlimited projects\", \"10 agents\", \"Audit log\"]}\n />\n <PlanCard\n name=\"Business\"\n price={null}\n cta=\"Talk to sales\"\n text=\"Security and an SLA\"\n features={[\"Everything in Team\", \"SSO and SCIM\", \"SLA\"]}\n />\n </div>\n <Panel title=\"Usage this month\">\n <UsageMeter\n label=\"Agent usage\"\n used={84}\n limit={120}\n format={(v) => \"€\" + v}\n caption=\"Included up to €120\"\n />\n </Panel>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/PricingScreen"},{"name":"ProjectScreen","title":"Project","group":"Pages","summary":"A complete screen built from AppShell, Avatar, AvatarStack, Badge and 11 more components.","description":"","components":["AppShell","Avatar","AvatarStack","Badge","Button","Checkbox","FileRow","IconButton","PageHeader","Panel","ProgressRing","Stagger","StatTile","Tabs","Timeline"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n AvatarStack,\n Badge,\n Button,\n Checkbox,\n FileRow,\n PageHeader,\n Panel,\n ProgressRing,\n Stagger,\n StatTile,\n Tabs,\n Timeline,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n // task, done, assignee\n const tasks: [string, boolean, string][] = [\n [\"Draft new tier names\", true, \"Mara Ellis\"],\n [\"Comparison table component\", true, \"Jonah Petit\"],\n [\"Localise tiers for DE and FR\", false, \"Sun Lee\"],\n [\"Checkout seat selector\", false, \"Rui Costa\"],\n [\"Write FAQ for annual plans\", false, \"Ava Brandt\"],\n ];\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n side={\n <Panel side title=\"Activity\" subtitle=\"Last 7 days\">\n <Timeline\n delay={300}\n items={[\n {\n avatar: \"Mara Ellis\",\n text: (\n <span>\n <b>Mara</b>\n {\" approved the tier names\"}\n </span>\n ),\n time: \"2 min ago\",\n },\n {\n avatar: \"Jonah Petit\",\n text: (\n <span>\n <b>Jonah</b>\n {\" moved Comparison table to review\"}\n </span>\n ),\n time: \"1 h ago\",\n },\n {\n icon: \"sparkle\",\n tint: \"mint\",\n text: \"Preview deployed to staging\",\n time: \"Yesterday\",\n },\n {\n avatar: \"Rui Costa\",\n text: (\n <span>\n <b>Rui</b>\n {\" flagged the seat selector as blocked\"}\n </span>\n ),\n time: \"Mon\",\n },\n {\n icon: \"flag\",\n tint: \"blush\",\n text: \"Deadline set to Fri, Apr 17\",\n time: \"Mon\",\n },\n ]}\n />\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n crumbs={[\"Projects\", \"Pricing page refresh\"]}\n title=\"Pricing page refresh\"\n subtitle=\"New tiers and a comparison table for annual plans\"\n actions={[\n <AvatarStack key=\"a\" names={[\"Mara Ellis\", \"Jonah Petit\", \"Sun Lee\", \"Rui Costa\"]} />,\n <Button key=\"s\" variant=\"secondary\" icon=\"share\">\n Share\n </Button>,\n <Button key=\"p\" icon=\"play\">\n Preview\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"100ms\",\n } as React.CSSProperties\n }\n >\n <Tabs items={[\"Overview\", \"Tasks\", \"Files\", \"Settings\"]} />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.3fr 1fr\",\n } as React.CSSProperties\n }\n delay={160}\n >\n <div\n className=\"tf-card tf-bg-lilac\"\n style={{\n minHeight: 0,\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: 24,\n }}\n >\n <ProgressRing value={64} size={104} stroke={11} caption=\"done\" />\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <span\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n On track for Friday\n </span>\n <span className=\"tf-card-desc\">\n 11 of 18 tasks done. One blocker on checkout needs a decision from billing.\n </span>\n <div className=\"tf-row\">\n <Badge tone=\"success\">On track</Badge>\n <Badge tone=\"warning\">1 blocker</Badge>\n </div>\n </div>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 12,\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"Tasks\" />\n <StatTile tint=\"butter\" value=\"3\" label=\"Days left\" />\n <StatTile tint=\"mint\" value=\"24\" label=\"Comments\" />\n <StatTile tint=\"placeholder\" value=\"2\" label=\"In review\" />\n </div>\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.3fr 1fr\",\n } as React.CSSProperties\n }\n delay={320}\n >\n <Panel\n title=\"Milestone tasks\"\n action={\n <Button size=\"sm\" variant=\"soft\" icon=\"plus\">\n Add\n </Button>\n }\n >\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 4,\n }}\n delay={420}\n >\n {tasks.map((t) => (\n <div\n key={t[0]}\n className=\"tf-row-item\"\n style={{\n gridTemplateColumns: \"auto 1fr auto\",\n cursor: \"default\",\n }}\n >\n <Checkbox defaultChecked={t[1]} label={t[0]} />\n <span\n style={{\n fontWeight: \"var(--type-label-weight)\",\n fontSize: 14,\n textDecoration: t[1] ? \"line-through\" : \"none\",\n color: t[1] ? \"var(--ink-muted)\" : \"var(--ink)\",\n }}\n >\n {t[0]}\n </span>\n <Avatar name={t[2]} size={24} />\n </div>\n ))}\n </Stagger>\n </Panel>\n <Panel title=\"Files\" subtitle=\"4 attachments\">\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n delay={480}\n >\n <FileRow name=\"brief-v4.pdf\" meta=\"2.4 MB · PDF\" kind=\"pdf\" />\n <FileRow name=\"pricing-tiers.fig\" meta=\"18 MB · Design\" kind=\"fig\" />\n <FileRow name=\"q2-forecast.xlsx\" meta=\"640 KB · Sheet\" kind=\"sheet\" />\n </Stagger>\n </Panel>\n </Stagger>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/ProjectScreen"},{"name":"SettingsScreen","title":"Settings","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Button, IconButton and 7 more components.","description":"","components":["AppShell","Avatar","Button","IconButton","Input","ListRow","PageHeader","Panel","Select","Stagger","Switch"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Button,\n Input,\n ListRow,\n PageHeader,\n Panel,\n Select,\n Stagger,\n Switch,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { IconName, Tint } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const sec = React.useState(0);\n const nav: [string, IconName, Tint | \"plain\"][] = [\n [\"Profile\", \"user\", \"sky\"],\n [\"Workspace\", \"grid\", \"mint\"],\n [\"Notifications\", \"bell\", \"butter\"],\n [\"Security\", \"lock\", \"lilac\"],\n [\"Integrations\", \"link\", \"plain\"],\n [\"Billing\", \"card\", \"blush\"],\n ];\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Settings\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Settings\"\n subtitle=\"Manage your profile and how Halo works for your team\"\n actions={[\n <Button key=\"c\" variant=\"ghost\">\n Discard\n </Button>,\n <Button key=\"s\" icon=\"check\">\n Save changes\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"240px 1fr\",\n gap: 24,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <Stagger className=\"tf-list\" delay={100}>\n {nav.map((n, i) => (\n <ListRow\n key={n[0]}\n icon={n[1]}\n tint={n[2]}\n title={n[0]}\n active={sec[0] === i}\n onClick={() => {\n sec[1](i);\n }}\n />\n ))}\n </Stagger>\n </div>\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n delay={160}\n >\n <Panel title=\"Profile\" subtitle=\"Shown to everyone in the workspace\">\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n }}\n >\n <Avatar name=\"Mara Ellis\" size={64} />\n <Button variant=\"secondary\" size=\"sm\">\n Upload photo\n </Button>\n <Button variant=\"ghost\" size=\"sm\">\n Remove\n </Button>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <Input label=\"Full name\" defaultValue=\"Mara Ellis\" />\n <Input\n label=\"Email\"\n defaultValue=\"mara@halo.app\"\n hint=\"Used for sign-in and receipts.\"\n />\n <Select label=\"Role\" options={[\"Owner\", \"Admin\", \"Editor\", \"Viewer\"]} />\n <Select\n label=\"Time zone\"\n options={[\n \"Europe/Berlin (UTC+2)\",\n \"Asia/Dubai (UTC+4)\",\n \"America/New_York (UTC−4)\",\n ]}\n />\n </div>\n </Panel>\n <Panel title=\"Notifications\" subtitle=\"Choose what reaches your inbox\">\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <Switch spread defaultChecked>\n Approvals waiting on me\n </Switch>\n <Switch spread defaultChecked>\n Invoices paid or overdue\n </Switch>\n <Switch spread>Weekly analytics digest</Switch>\n <Switch spread>Product updates from Halo</Switch>\n </div>\n </Panel>\n <div\n className=\"tf-card tf-bg-blush\"\n style={{\n minHeight: 0,\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <div>\n <div\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Delete workspace\n </div>\n <div className=\"tf-card-desc\">\n Removes every project, file and invoice. This cannot be undone.\n </div>\n </div>\n <Button variant=\"secondary\" icon=\"trash\">\n Delete…\n </Button>\n </div>\n </Stagger>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/SettingsScreen"},{"name":"SetupScreen","title":"Setup","group":"Pages","summary":"A complete screen built from Button, IconButton, PageHeader, StatTile and 4 more components.","description":"","components":["Button","Icon","IconButton","PageHeader","StatTile","SubNav","TintIcon","TopBar","Wizard"],"code":"import * as React from \"react\";\nimport { Button, PageHeader, StatTile, TintIcon, Wizard } from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n const c = React.useState(2);\n return (\n <Shell\n area=\"Workspace\"\n current=\"overview\"\n action={{\n label: \"New project\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"overview\",\n label: \"Overview\",\n icon: \"home\",\n },\n {\n id: \"board\",\n label: \"Projects\",\n icon: \"grid\",\n count: 7,\n },\n {\n id: \"calendar\",\n label: \"Calendar\",\n icon: \"calendar\",\n },\n {\n id: \"people\",\n label: \"People\",\n icon: \"users\",\n },\n {\n id: \"reports\",\n label: \"Reports\",\n icon: \"chart\",\n },\n ]}\n overlay={\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n borderRadius: \"var(--radius-xl)\",\n overflow: \"hidden\",\n }}\n >\n <div\n className=\"tf-modal-wrap\"\n style={{\n position: \"absolute\",\n inset: 0,\n }}\n >\n <div className=\"tf-backdrop\" />\n <Wizard\n brand=\"Halo\"\n current={c[0]}\n onPick={c[1]}\n done={{\n 0: true,\n 1: true,\n }}\n steps={[\n {\n label: \"Name your workspace\",\n icon: \"grid\",\n },\n {\n label: \"Invite your team\",\n icon: \"users\",\n },\n {\n label: \"Connect your mailbox\",\n icon: \"mail\",\n },\n {\n label: \"Connect a domain\",\n icon: \"globe\",\n },\n {\n label: \"Connect your bank\",\n icon: \"card\",\n },\n {\n label: \"Create your first agent\",\n icon: \"bot\",\n },\n ]}\n footer={[\n <Button\n key=\"b\"\n variant=\"ghost\"\n icon=\"chevronLeft\"\n onClick={() => {\n c[1](Math.max(0, c[0] - 1));\n }}\n >\n Back\n </Button>,\n <Button\n key=\"n\"\n iconRight=\"chevronRight\"\n onClick={() => {\n c[1](Math.min(5, c[0] + 1));\n }}\n >\n Continue\n </Button>,\n ]}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n {[\"Google Workspace\", \"Microsoft 365\", \"IMAP / SMTP\", \"Halo Mail\"].map((m, i) => (\n <div\n key={m}\n className=\"tf-file\"\n style={\n i === 0\n ? {\n boxShadow: \"inset 0 0 0 2px var(--ink)\",\n background: \"var(--surface)\",\n }\n : undefined\n }\n >\n <TintIcon icon=\"mail\" tint={i === 0 ? \"mint\" : \"plain\"} />\n <b>{m}</b>\n </div>\n ))}\n </div>\n </Wizard>\n </div>\n </div>\n }\n >\n <PageHeader title=\"Good morning, Mara\" subtitle=\"Everything that needs a decision today\" />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"15\" label=\"All projects\" />\n <StatTile tint=\"mint\" value=\"8\" label=\"Live\" />\n <StatTile tint=\"placeholder\" value=\"3\" label=\"Needs review\" />\n <StatTile tint=\"butter\" value=\"€32,669\" label=\"Revenue (MTD)\" />\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/SetupScreen"},{"name":"ShopScreen","title":"Shop","group":"Pages","summary":"A complete screen built from Alert, Button, Funnel, PageHeader and 5 more components.","description":"","components":["Alert","Button","Funnel","Icon","PageHeader","Panel","StackedBarChart","StatTile","SubNav","TopBar"],"code":"import * as React from \"react\";\nimport {\n Alert,\n Button,\n Funnel,\n PageHeader,\n Panel,\n StackedBarChart,\n StatTile,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\nimport type { SubNavItem } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const pages: SubNavItem[] = [\n {\n id: \"Overview\",\n label: \"Overview\",\n icon: \"chart\",\n },\n {\n id: \"Products\",\n label: \"Products\",\n icon: \"package\",\n },\n {\n id: \"Orders\",\n label: \"Orders\",\n icon: \"truck\",\n count: 8,\n },\n {\n id: \"Storefront\",\n label: \"Storefront\",\n icon: \"cart\",\n },\n ];\n return (\n <Shell\n area=\"Shop\"\n current=\"Overview\"\n action={{\n label: \"Add product\",\n icon: \"plus\",\n }}\n pages={pages}\n >\n <PageHeader\n title=\"Shop\"\n subtitle=\"Halo Goods · last 7 days\"\n actions={[\n <Button key=\"v\" variant=\"secondary\" icon=\"arrowUpRight\">\n View store\n </Button>,\n ]}\n />\n <Alert\n tone=\"warning\"\n title=\"3 products are running low\"\n action={\n <Button size=\"sm\" variant=\"secondary\">\n Review stock\n </Button>\n }\n >\n Studio Mug (6 left), Canvas Tote (sold out), Court Sneaker (9 left).\n </Alert>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€5,592\" label=\"Revenue\" />\n <StatTile tint=\"sky\" value=\"36\" label=\"Orders\" />\n <StatTile tint=\"mint\" value=\"€155\" label=\"Average order\" />\n <StatTile tint=\"placeholder\" value=\"11\" label=\"To fulfil\" />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1.6fr) minmax(0, 1fr)\",\n } as React.CSSProperties\n }\n >\n <Panel title=\"Sales by channel\">\n <StackedBarChart\n height={160}\n labels={[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]}\n format={(v) => \"€\" + Math.round(v)}\n series={[\n {\n name: \"Online store\",\n tint: \"sky\",\n values: [420, 380, 510, 460, 620, 840, 760],\n },\n {\n name: \"Instagram\",\n tint: \"lilac\",\n values: [120, 160, 140, 210, 260, 380, 300],\n },\n {\n name: \"Marketplace\",\n tint: \"butter\",\n values: [90, 110, 80, 130, 150, 170, 160],\n },\n ]}\n />\n </Panel>\n <Panel title=\"Conversion\">\n <Funnel\n steps={[\n {\n label: \"Visited\",\n value: 8420,\n },\n {\n label: \"Viewed product\",\n value: 3910,\n },\n {\n label: \"Added to cart\",\n value: 812,\n },\n {\n label: \"Ordered\",\n value: 214,\n },\n ]}\n />\n </Panel>\n </div>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/ShopScreen"},{"name":"SignInScreen","title":"Sign In","group":"Pages","summary":"A complete screen built from AvatarStack, Badge, Button, Checkbox and 6 more components.","description":"","components":["AvatarStack","Badge","Button","Checkbox","EventCard","IconButton","Input","Progress","Stagger","StatTile"],"code":"import * as React from \"react\";\nimport {\n AvatarStack,\n Badge,\n Button,\n Checkbox,\n EventCard,\n Input,\n Progress,\n Stagger,\n StatTile,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n className=\"tf-shell\"\n style={{\n padding: 8,\n }}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1.1fr\",\n gap: 8,\n minHeight: 760,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-sheet\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"space-between\",\n padding: \"clamp(20px, 6vw, 48px)\",\n gridTemplateColumns: \"none\",\n }}\n >\n <span\n className=\"tf-brand tf-in\"\n style={{\n color: \"var(--ink)\",\n fontSize: 20,\n }}\n >\n Halo\n <span className=\"tf-brand-dot\" />\n </span>\n <div\n className=\"tf-col\"\n style={{\n gap: 18,\n maxWidth: 380,\n alignSelf: \"center\",\n width: \"100%\",\n }}\n >\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n <h1\n className=\"tf-header-title\"\n style={{\n fontSize: 34,\n lineHeight: \"40px\",\n }}\n >\n Welcome back\n </h1>\n <p\n className=\"tf-header-sub\"\n style={{\n fontSize: 14,\n }}\n >\n Sign in to pick up where your team left off.\n </p>\n </div>\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n delay={160}\n >\n <Button variant=\"secondary\" icon=\"globe\">\n Continue with Google\n </Button>\n <div\n className=\"tf-row\"\n style={{\n gap: 12,\n color: \"var(--ink-muted)\",\n fontSize: 12,\n fontWeight: \"var(--type-label-weight)\",\n }}\n >\n <span\n style={{\n flex: 1,\n height: 1,\n background: \"var(--line)\",\n }}\n />\n or with email\n <span\n style={{\n flex: 1,\n height: 1,\n background: \"var(--line)\",\n }}\n />\n </div>\n <Input label=\"Work email\" placeholder=\"you@company.com\" />\n <Input label=\"Password\" type=\"password\" placeholder=\"••••••••••\" />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <Checkbox defaultChecked>Keep me signed in</Checkbox>\n <a\n href=\"#\"\n style={{\n font: \"var(--type-label-weight) 12px var(--font-sans)\",\n color: \"var(--ink)\",\n }}\n >\n Forgot password?\n </a>\n </div>\n <Button iconRight=\"arrowUpRight\">Sign in</Button>\n </Stagger>\n </div>\n <span\n className=\"tf-card-desc tf-in\"\n style={\n {\n \"--d\": \"500ms\",\n } as React.CSSProperties\n }\n >\n {\"New to Halo? \"}\n <a\n href=\"#\"\n style={{\n color: \"var(--ink)\",\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n Create a workspace\n </a>\n </span>\n </div>\n <div\n style={{\n position: \"relative\",\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n padding: \"clamp(20px, 5vw, 40px)\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n gap: 16,\n }}\n >\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 16,\n marginBottom: \"auto\",\n } as React.CSSProperties\n }\n delay={200}\n >\n <StatTile tint=\"sky\" value=\"1,284\" label=\"Members this month\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Collected\" />\n <div\n className=\"tf-card tf-bg-lilac\"\n style={{\n gridColumn: \"span 2\",\n minHeight: 0,\n }}\n >\n <span\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Pricing page refresh\n </span>\n <Progress value={64} label=\"Progress\" />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <Badge tone=\"success\" live>\n On track\n </Badge>\n <AvatarStack names={[\"Mara Ellis\", \"Jonah Petit\", \"Sun Lee\"]} size={26} />\n </div>\n </div>\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Tue\" time=\"10:30\" actions={false}>\n Roadmap check-in.\n </EventCard>\n <EventCard kind=\"launch\" label=\"Launch\" date=\"Fri\" time=\"17:00\" actions={false}>\n Annual plans go live.\n </EventCard>\n </Stagger>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"700ms\",\n color: \"var(--on-frame)\",\n } as React.CSSProperties\n }\n >\n <div\n style={{\n font: \"var(--type-title-weight) 26px/32px var(--font-sans)\",\n letterSpacing: \"-0.015em\",\n }}\n >\n Every decision your team is waiting on, in one calm place.\n </div>\n <div\n style={{\n color: \"var(--on-frame-muted)\",\n marginTop: 8,\n fontSize: 13,\n }}\n >\n Projects, people, billing and schedule — side by side.\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/SignInScreen"},{"name":"StatusScreen","title":"Status","group":"Pages","summary":"Full-page states for errors and interruptions: code \"404\", \"500\", \"403\", \"maintenance\", \"offline\", or your own title / text / icon / tint.","description":"Full-page states for errors and interruptions: `code` \"404\", \"500\", \"403\", \"maintenance\", \"offline\", or your own `title` / `text` / `icon` / `tint`.\n\nProps: `code`, `title`, `text`, `icon`, `tint`, `extra` (search field, progress), `actions` (defaults to Back to overview + Contact support), `onHome`, `onContact`, `reference` (request id for support).\n\n- Say what happened, that the person's data is safe, and what to do next, in that order.\n- Show a request reference on server errors so support can find it.\n- Keep the TopBar when you can (404, 403); go full screen only when the app itself is down.\n","components":["Button","StatusScreen","UsageMeter"],"code":"import * as React from \"react\";\nimport { Button, StatusScreen, UsageMeter } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <StatusScreen code=\"404\" />\n </div>\n <div className=\"tf-panel\">\n <StatusScreen code=\"500\" reference=\"req_8f2a91c4\" />\n </div>\n <div className=\"tf-panel\">\n <StatusScreen\n code=\"maintenance\"\n extra={\n <div\n style={{\n width: \"100%\",\n maxWidth: 300,\n }}\n >\n <UsageMeter label=\"Upgrade progress\" used={72} limit={100} format={(v) => v + \"%\"} />\n </div>\n }\n />\n </div>\n <div className=\"tf-panel\">\n <StatusScreen\n code=\"offline\"\n actions={[\n <Button key=\"r\" icon=\"refresh\">\n Try again\n </Button>,\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"patterns/StatusScreen"},{"name":"StorefrontScreen","title":"Storefront","group":"Pages","summary":"A complete screen built from Breadcrumb, Button, IconButton, Price and 6 more components.","description":"","components":["Breadcrumb","Button","Icon","IconButton","Price","ProductArt","Rating","StockBadge","SubNav","TopBar","VariantPicker"],"code":"import * as React from \"react\";\nimport {\n Breadcrumb,\n Button,\n IconButton,\n Price,\n ProductArt,\n Rating,\n StockBadge,\n VariantPicker,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n style={{\n borderRadius: \"var(--radius-xl)\",\n background: \"var(--canvas)\",\n overflow: \"hidden\",\n boxShadow: \"inset 0 0 0 1px var(--line)\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"16px 24px\",\n background: \"var(--surface)\",\n borderBottom: \"1px solid var(--line)\",\n }}\n >\n <b\n style={{\n font: \"var(--type-title-weight) 20px/26px var(--font-sans)\",\n }}\n >\n Halo Goods\n </b>\n <div className=\"tf-row\">\n <IconButton icon=\"heart\" label=\"Saved\" />\n <Button icon=\"cart\">Cart</Button>\n </div>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1.1fr) minmax(0, 1fr)\",\n gap: 32,\n padding: 24,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-col\">\n <ProductArt kind=\"tee\" tint=\"sky\" />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 12,\n } as React.CSSProperties\n }\n >\n {([\"sky\", \"mint\", \"butter\", \"lilac\"] as const).map((t) => (\n <ProductArt key={t} kind=\"tee\" tint={t} />\n ))}\n </div>\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 20,\n }}\n >\n <Breadcrumb items={[\"Shop\", \"Apparel\", \"Halo Cotton Tee\"]} />\n <h1\n style={{\n margin: 0,\n font: \"var(--type-title-weight) 28px/34px var(--font-sans)\",\n }}\n >\n Halo Cotton Tee\n </h1>\n <Rating value={4.7} count={128} />\n <Price value={32} compareAt={39} size=\"lg\" />\n <VariantPicker\n label=\"Colour\"\n type=\"color\"\n defaultValue=\"Sky\"\n options={[\n {\n value: \"Sky\",\n label: \"Sky\",\n color: \"#9fc3f0\",\n },\n {\n value: \"Ink\",\n label: \"Ink\",\n color: \"#1d1d1f\",\n },\n {\n value: \"Bone\",\n label: \"Bone\",\n color: \"#ece6d9\",\n },\n ]}\n />\n <VariantPicker\n label=\"Size\"\n defaultValue=\"M\"\n options={[\n {\n value: \"XS\",\n label: \"XS\",\n disabled: true,\n },\n \"S\",\n \"M\",\n \"L\",\n \"XL\",\n ]}\n />\n <StockBadge stock={84} />\n <Button icon=\"cart\">Add to cart · €32</Button>\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"patterns/StorefrontScreen"},{"name":"WellbeingScreen","title":"Wellbeing","group":"Pages","summary":"A wellbeing dashboard in glass: sleep, streak and steps as tinted glass tiles, last nights' sleep, today's goal, mindful minutes, mood and habits, and a journal prompt, over the kit's ambient colours.","description":"A wellbeing dashboard in glass: sleep, streak and steps as tinted glass tiles, last nights' sleep, today's goal, mindful minutes, mood and habits, and a journal prompt, over the kit's ambient colours.\n\n- The whole app shell is glass: top bar, rail, panels, tiles and dock float over `Ambient`.\n- Built from `UIProvider surface=\"glass\"`, `Ambient`, `AppShell`, `StatTile`, `BarChart`, `ProgressRing`, `AreaChart`, `Segmented` and `Checkbox`.\n","components":["Ambient","AppShell","AreaChart","BarChart","Button","Checkbox","PageHeader","Panel","ProgressRing","Segmented","Stagger","StatTile","Textarea","UIProvider"],"code":"import * as React from \"react\";\nimport {\n Ambient,\n AppShell,\n AreaChart,\n BarChart,\n Button,\n Checkbox,\n PageHeader,\n Panel,\n ProgressRing,\n Segmented,\n Stagger,\n StatTile,\n Textarea,\n UIProvider,\n} from \"@twentyfirstmedia/ui-core\";\n\n/** A wellbeing dashboard in glass over the kit's ambient colours. */\nfunction App() {\n return (\n <UIProvider scope=\"subtree\" surface=\"glass\">\n <Ambient />\n <AppShell\n brand=\"Bloom\"\n tag=\"Daily\"\n active=\"Today\"\n user={{ name: \"Mara Ellis\", email: \"mara@bloom.app\" }}\n nav={[\n { label: \"Today\", icon: \"sun\" },\n { label: \"Sleep\", icon: \"moon\" },\n { label: \"Habits\", icon: \"check\" },\n { label: \"Journal\", icon: \"book\" },\n ]}\n railTop={[\n { label: \"Today\", icon: \"home\", active: true },\n { label: \"Trends\", icon: \"trend\" },\n { label: \"Reminders\", icon: \"bell\", count: 2 },\n ]}\n railBottom={[{ label: \"Settings\", icon: \"settings\" }]}\n dockItems={[\n { icon: \"heart\", label: \"Log mood\", tint: \"blush\" },\n { icon: \"moon\", label: \"Log sleep\", tint: \"sky\" },\n { icon: \"zap\", label: \"Start a workout\", tint: \"butter\" },\n { icon: \"sparkle\", label: \"Breathe\", tint: \"lilac\" },\n ]}\n side={\n <Panel side title=\"Journal\" subtitle=\"One line is enough\">\n <Textarea\n label=\"What are you grateful for today?\"\n rows={4}\n defaultValue=\"Coffee on the balcony before everyone woke up.\"\n />\n <Button variant=\"primary\" icon=\"check\">\n Save entry\n </Button>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Good morning, Mara\"\n subtitle=\"Thursday · You slept 7 h 42 min\"\n actions={[\n <Button key=\"log\" variant=\"secondary\" icon=\"plus\">\n Log activity\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n \"--cols-sm\": \"1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <StatTile value=\"7h 42m\" label=\"Sleep\" tint=\"sky\" delta=\"+18 min\" />\n <StatTile value=\"12 days\" label=\"Streak\" tint=\"butter\" delta=\"Best 21\" />\n <StatTile value=\"8,240\" label=\"Steps\" tint=\"mint\" delta=\"82% of goal\" />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n delay={200}\n style={\n {\n \"--cols\": \"minmax(0, 3fr) minmax(0, 2fr)\",\n \"--cols-sm\": \"1fr\",\n gap: 16,\n marginTop: 16,\n } as React.CSSProperties\n }\n >\n <Panel title=\"Sleep\" subtitle=\"Last 7 nights, in hours\">\n <BarChart\n label=\"Hours of sleep\"\n height={150}\n data={[\n { label: \"Fri\", value: 6.8, tint: \"sky\" },\n { label: \"Sat\", value: 8.1, tint: \"sky\" },\n { label: \"Sun\", value: 7.9, tint: \"sky\" },\n { label: \"Mon\", value: 6.4, tint: \"lilac\" },\n { label: \"Tue\", value: 7.2, tint: \"sky\" },\n { label: \"Wed\", value: 7.5, tint: \"sky\" },\n { label: \"Thu\", value: 7.7, tint: \"mint\" },\n ]}\n />\n </Panel>\n <Panel title=\"Today's goal\" subtitle=\"Move, breathe, reflect\">\n <div style={{ display: \"grid\", placeItems: \"center\", padding: \"8px 0\" }}>\n <ProgressRing\n value={72}\n tint=\"mint\"\n caption=\"of today\"\n label=\"Daily goal\"\n size={132}\n />\n </div>\n </Panel>\n <Panel title=\"Mindful minutes\" subtitle=\"This week\">\n <AreaChart\n label=\"Mindful minutes\"\n labels={[\"Fri\", \"Sat\", \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\"]}\n series={[{ name: \"Minutes\", values: [6, 12, 15, 4, 10, 14, 18], tint: \"lilac\" }]}\n height={130}\n />\n </Panel>\n <Panel title=\"How do you feel?\" subtitle=\"3 of 4 habits done\">\n <Segmented\n label=\"Mood\"\n block\n defaultValue=\"Calm\"\n options={[\"Tired\", \"Calm\", \"Great\"]}\n />\n <Checkbox defaultChecked>Drink 2 l of water</Checkbox>\n <Checkbox defaultChecked>10 minutes outside</Checkbox>\n <Checkbox defaultChecked>Stretch after lunch</Checkbox>\n <Checkbox>Phone away at 22:00</Checkbox>\n </Panel>\n </Stagger>\n </AppShell>\n </UIProvider>\n );\n}\n\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/WellbeingScreen"},{"name":"WorkflowScreen","title":"Workflow","group":"Pages","summary":"A complete screen built from Badge, Button, Flow, IconButton and 6 more components.","description":"","components":["Badge","Button","Flow","Icon","IconButton","ListRow","PageHeader","Stagger","SubNav","TintIcon","TopBar"],"code":"import * as React from \"react\";\nimport {\n Badge,\n Button,\n Flow,\n IconButton,\n ListRow,\n PageHeader,\n Stagger,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n const run = React.useState<number | null>(null);\n React.useEffect(() => {\n if (run[0] == null || run[0] === 3 || run[0] >= 5) return;\n const t = setTimeout(() => {\n run[1]((run[0] ?? 0) + 1);\n }, 800);\n return () => {\n clearTimeout(t);\n };\n }, [run[0]]);\n return (\n <Shell\n area=\"Agents\"\n current=\"workflows\"\n action={{\n label: \"New agent\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"fleet\",\n label: \"Fleet\",\n icon: \"bot\",\n },\n {\n id: \"chat\",\n label: \"Chat\",\n icon: \"mail\",\n },\n {\n id: \"workflows\",\n label: \"Workflows\",\n icon: \"zap\",\n },\n {\n id: \"runs\",\n label: \"Runs\",\n icon: \"refresh\",\n count: 2,\n },\n {\n id: \"knowledge\",\n label: \"Knowledge\",\n icon: \"book\",\n },\n {\n id: \"usage\",\n label: \"Usage & cost\",\n icon: \"chart\",\n },\n ]}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Workflows\"\n subtitle=\"Chain agents, approvals and actions\"\n actions={[\n <Button key=\"n\" icon=\"plus\">\n New workflow\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 300px) minmax(0, 1fr)\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel tf-panel-flush tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n <Stagger className=\"tf-list\">\n <ListRow\n active\n leading={<TintIcon icon=\"zap\" tint=\"mint\" />}\n title=\"Support replies\"\n subtitle=\"5 steps · 214 runs\"\n trailing={<Badge tone=\"success\">On</Badge>}\n />\n <ListRow\n leading={<TintIcon icon=\"zap\" tint=\"mint\" />}\n title=\"Receipt to booking\"\n subtitle=\"5 steps · 96 runs\"\n trailing={<Badge tone=\"success\">On</Badge>}\n />\n <ListRow\n leading={<TintIcon icon=\"zap\" tint=\"plain\" />}\n title=\"Lead research\"\n subtitle=\"3 steps · 37 runs\"\n trailing={<Badge>Off</Badge>}\n />\n </Stagger>\n </div>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"160ms\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-header\"\n style={{\n alignItems: \"center\",\n }}\n >\n <h2 className=\"tf-panel-title\">Support replies</h2>\n <Button\n variant=\"soft\"\n icon=\"play\"\n disabled={run[0] != null && run[0] < 5}\n onClick={() => {\n run[1](0);\n }}\n >\n {run[0] != null && run[0] < 5 ? \"Running…\" : \"Test run\"}\n </Button>\n </div>\n <Flow\n active={run[0]}\n waiting={run[0] === 3}\n onApprove={() => {\n run[1](4);\n }}\n onReject={() => {\n run[1](null);\n }}\n tools={() => [\n <IconButton key=\"e\" icon=\"edit\" label=\"Edit step\" size=\"sm\" />,\n <IconButton key=\"t\" icon=\"trash\" label=\"Remove step\" size=\"sm\" />,\n ]}\n steps={[\n {\n kind: \"trigger\",\n text: \"New email arrives in Inbox\",\n },\n {\n kind: \"condition\",\n text: \"Label is Customers\",\n },\n {\n kind: \"agent\",\n text: \"Atlas drafts a reply from the knowledge base\",\n },\n {\n kind: \"approval\",\n text: \"Mara approves the draft\",\n },\n {\n kind: \"action\",\n text: \"Send the reply and log it on the company\",\n },\n ]}\n />\n </div>\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/WorkflowScreen"},{"name":"real-estate","title":"Real estate","group":"Pages","summary":"A listing search page: results toolbar with bulk actions, listings as cards or rows, a map with price pins, valuation, energy class and the photo gallery.","description":"A listing search page: results toolbar with bulk actions, listings as cards or rows, a map with price pins, valuation, energy class and the photo gallery.","components":["ListingCard","ResultsToolbar","IconButton","MapView","EnergyClass","SavedSearch","ValuationCard","BeforeAfter","Gallery","ImageVariants"],"code":"// ── ListingCard ──\nimport * as React from \"react\";\nimport { ListingCard, ResultsToolbar } from \"@twentyfirstmedia/ui-core\";\nimport type { Listing, StatusSelectProps } from \"@twentyfirstmedia/ui-core\";\n\nconst st: StatusSelectProps[\"options\"] = [\n {\n value: \"unread\",\n label: \"New\",\n status: \"running\",\n },\n {\n value: \"read\",\n label: \"Seen\",\n status: \"idle\",\n },\n {\n value: \"contacted\",\n label: \"Contacted\",\n status: \"ok\",\n },\n];\nconst L0: Listing[] = [\n {\n id: 1,\n title: \"Bright 3-room flat with balcony near Isebek canal\",\n price: 649000,\n pricePerSqm: 7726,\n area: 84,\n rooms: 3,\n place: \"Eppendorf, Hamburg\",\n source: \"ImmoScout\",\n seller: \"private\",\n phone: true,\n photos: 14,\n isNew: true,\n age: \"2 h\",\n status: \"unread\",\n tint: \"sky\",\n },\n {\n id: 2,\n title: \"Loft in HafenCity with water view\",\n price: 1190000,\n pricePerSqm: 10625,\n area: 112,\n rooms: 2.5,\n place: \"HafenCity, Hamburg\",\n source: \"Kleinanzeigen\",\n seller: \"agent\",\n photos: 22,\n age: \"1 d\",\n status: \"contacted\",\n tint: \"lilac\",\n wished: true,\n },\n {\n id: 3,\n title: \"2-room flat, renovated old building\",\n price: 1480,\n rent: true,\n area: 58,\n rooms: 2,\n place: \"Altona, Hamburg\",\n source: \"Immowelt\",\n seller: \"agent\",\n photos: 9,\n age: \"3 d\",\n status: \"read\",\n tint: \"mint\",\n },\n];\nfunction App() {\n const sel = React.useState<(string | number)[]>([1]),\n list = React.useState(L0),\n view = React.useState(\"grid\"),\n sort = React.useState(\"new\");\n function tog(x: Listing) {\n sel[1](sel[0].indexOf(x.id) > -1 ? sel[0].filter((i) => i !== x.id) : sel[0].concat([x.id]));\n }\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <ResultsToolbar\n title=\"Hamburg · flats to buy\"\n total={128}\n meta=\"updated 06:00\"\n selected={sel[0].length}\n onSelectAll={(all) => {\n sel[1](all ? list[0].map((x) => x.id) : []);\n }}\n bulkActions={[\n {\n label: \"Send to CRM\",\n icon: \"send\",\n variant: \"primary\",\n },\n {\n label: \"Mark as seen\",\n icon: \"eye\",\n },\n ]}\n sortOptions={[\n {\n value: \"new\",\n label: \"Newest\",\n },\n {\n value: \"price\",\n label: \"Price\",\n },\n {\n value: \"sqm\",\n label: \"Price per m²\",\n },\n ]}\n sort={sort[0]}\n onSort={sort[1]}\n views={[\"grid\", \"list\", \"map\"]}\n view={view[0]}\n onView={view[1]}\n />\n <div\n className=\"tf-grid\"\n style={\n view[0] === \"list\"\n ? {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 12,\n }\n : ({\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties)\n }\n >\n {list[0].map((x) => (\n <ListingCard\n key={x.id}\n listing={x}\n layout={view[0] === \"list\" ? \"row\" : \"card\"}\n selected={sel[0].indexOf(x.id) > -1}\n onSelect={tog}\n onWish={() => {}}\n statusOptions={st}\n onStatus={(l, s) => {\n list[1](\n list[0].map((y) =>\n y.id === l.id\n ? Object.assign({}, y, {\n status: s,\n })\n : y,\n ),\n );\n }}\n />\n ))}\n </div>\n </div>\n );\n}\nexport default App;\n\n// ── MapView ──\nimport * as React from \"react\";\nimport { IconButton, MapView } from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\nconst world: MapPoint[] = [\n {\n id: \"de\",\n label: \"Germany\",\n lat: 51,\n lng: 10,\n value: 820,\n sub: \"820 orders today\",\n },\n {\n id: \"at\",\n label: \"Austria\",\n lat: 47.5,\n lng: 14.5,\n value: 260,\n tint: \"mint\",\n },\n {\n id: \"ch\",\n label: \"Switzerland\",\n lat: 46.8,\n lng: 8.2,\n value: 210,\n tint: \"mint\",\n },\n {\n id: \"us\",\n label: \"United States\",\n lat: 39,\n lng: -98,\n value: 140,\n tint: \"butter\",\n },\n {\n id: \"ae\",\n label: \"UAE\",\n lat: 24,\n lng: 54,\n value: 90,\n tint: \"lilac\",\n pulse: true,\n },\n {\n id: \"br\",\n label: \"Brazil\",\n lat: -12,\n lng: -52,\n value: 60,\n tint: \"blush\",\n },\n];\nconst city: MapPoint[] = [\n {\n id: 1,\n label: \"3-room flat, Eppendorf\",\n sub: \"€649,000 · 84 m²\",\n lat: 53.59,\n lng: 9.98,\n text: \"€649k\",\n },\n {\n id: 2,\n label: \"Loft, HafenCity\",\n sub: \"€1,190,000 · 112 m²\",\n lat: 53.541,\n lng: 10.0,\n text: \"€1.19m\",\n tint: \"lilac\",\n },\n {\n id: 3,\n label: \"2-room flat, Altona\",\n sub: \"€1,480 / month\",\n lat: 53.55,\n lng: 9.935,\n text: \"€1,480\",\n tint: \"mint\",\n },\n {\n id: 4,\n label: \"House, Blankenese\",\n lat: 53.565,\n lng: 9.81,\n text: \"€2.4m\",\n tint: \"butter\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Orders by country</b>\n <MapView bubbles points={world} height={360} legend=\"Bubble size = orders today\" />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Listings</b>\n <MapView\n points={city}\n bounds={{\n n: 53.62,\n s: 53.52,\n w: 9.78,\n e: 10.06,\n }}\n height={360}\n selected={2}\n controls={[\n <IconButton key={1} icon=\"plus\" label=\"Zoom in\" size=\"sm\" />,\n <IconButton key={2} icon=\"minus\" label=\"Zoom out\" size=\"sm\" />,\n ]}\n />\n </div>\n </div>\n );\n}\n\n// ── ValuationCard ──\nimport * as React from \"react\";\nimport { EnergyClass, SavedSearch, ValuationCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <ValuationCard\n value={612000}\n low={571000}\n high={655000}\n asking={649000}\n perSqm={7286}\n compare=\"district average €6,940\"\n confidence={0.78}\n facts={[\n [\"Comparables\", \"42 sales, last 18 months\"],\n [\"Method\", \"Comparable sales\"],\n [\"As of\", \"Nov 8, 2026\"],\n ]}\n note=\"An estimate, not an appraisal.\"\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <SavedSearch\n name=\"Eppendorf 3 rooms\"\n criteria={[\n \"Buy\",\n \"Hamburg-Eppendorf\",\n \"€400k–700k\",\n \"70–110 m²\",\n \"3+ rooms\",\n \"Private sellers\",\n ]}\n lastRun=\"06:00\"\n frequency=\"Daily mail\"\n newCount={4}\n onToggle={() => {}}\n onEdit={() => {}}\n onOpen={() => {}}\n />\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Energy</b>\n <EnergyClass value=\"C\" kwh={92} kind=\"Consumption · gas\" />\n </div>\n </div>\n </div>\n );\n}\n\n// ── Gallery ──\nimport * as React from \"react\";\nimport { BeforeAfter, Gallery, ImageVariants } from \"@twentyfirstmedia/ui-core\";\nimport type { Tint } from \"@twentyfirstmedia/ui-core\";\n\nconst art = (t: Tint, s: string) => (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(160deg, var(--\" + t + \") 0%, var(--surface) 120%)\",\n display: \"grid\",\n placeItems: \"center\",\n color: \"var(--\" + t + \"-ink)\",\n font: \"var(--type-title-weight) 14px/1 var(--font-sans)\",\n }}\n >\n {s}\n </div>\n);\nconst before = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #ddd8cf 0 62%, #b9ad9c 62%)\",\n }}\n />\n ),\n};\nconst after = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #e9e2d6 0 62%, #a68b6a 62%)\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n left: \"18%\",\n bottom: \"22%\",\n width: \"38%\",\n height: \"22%\",\n borderRadius: \"var(--radius-md)\",\n background: \"#5b6b5a\",\n }}\n />\n <div\n style={{\n position: \"absolute\",\n right: \"16%\",\n bottom: \"38%\",\n width: \"14%\",\n height: \"30%\",\n borderRadius: \"var(--radius-sm)\",\n background: \"#3d3a36\",\n }}\n />\n </div>\n ),\n};\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <Gallery\n badge=\"Virtual staging\"\n items={[\n {\n art: art(\"sky\", \"Living room\"),\n caption: \"Living room\",\n },\n {\n art: art(\"mint\", \"Kitchen\"),\n },\n {\n art: art(\"butter\", \"Bedroom\"),\n },\n {\n art: art(\"lilac\", \"Balcony\"),\n },\n {\n art: art(\"blush\", \"Bathroom\"),\n },\n ]}\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Virtual staging</b>\n <BeforeAfter before={before} after={after} afterLabel=\"Staged · Scandinavian\" />\n </div>\n <div className=\"tf-panel\">\n <ImageVariants\n tint=\"sky\"\n variants={[\n {\n id: 1,\n label: \"Original\",\n size: \"4032 × 3024\",\n format: \"JPEG\",\n weight: \"3.8 MB\",\n ratio: \"4 / 3\",\n },\n {\n id: 2,\n label: \"Listing\",\n size: \"1600 × 1200\",\n format: \"WebP\",\n weight: \"214 KB\",\n ratio: \"4 / 3\",\n status: \"ok\",\n },\n {\n id: 3,\n label: \"Square\",\n size: \"1080 × 1080\",\n format: \"WebP\",\n weight: \"168 KB\",\n ratio: \"1 / 1\",\n status: \"ok\",\n },\n {\n id: 4,\n label: \"Upscaled 2×\",\n size: \"8064 × 6048\",\n format: \"PNG\",\n status: \"running\",\n ratio: \"4 / 3\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}","path":"patterns/real-estate"}],"props":{"Base":[{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":""}],"Icon":[{"name":"name","type":"IconName","optional":false,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Button":[{"name":"variant","type":"\"primary\" | \"secondary\" | \"soft\" | \"ghost\" | \"danger\" | \"danger-solid\"","optional":true,"doc":""},{"name":"size","type":"\"md\" | \"sm\"","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"iconRight","type":"IconName","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"type","type":"\"button\" | \"submit\" | \"reset\"","optional":true,"doc":""},{"name":"onClick","type":"(e: MouseEvent<HTMLButtonElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"asChild","type":"boolean","optional":true,"doc":"Render the single child element (a link, a router Link) with the button's look instead of a <button>."},{"name":"id","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Form field name and value, sent with the form when this button submits it."},{"name":"value","type":"string","optional":true,"doc":""},{"name":"form","type":"string","optional":true,"doc":"The id of the form this button belongs to, when it sits outside it."},{"name":"title","type":"string","optional":true,"doc":""},{"name":"tabIndex","type":"number","optional":true,"doc":""},{"name":"autoFocus","type":"boolean","optional":true,"doc":""},{"name":"aria-label","type":"string","optional":true,"doc":""},{"name":"aria-labelledby","type":"string","optional":true,"doc":""},{"name":"aria-describedby","type":"string","optional":true,"doc":""},{"name":"aria-expanded","type":"boolean | \"true\" | \"false\"","optional":true,"doc":"For disclosure and menu triggers: whether what the button controls is open."},{"name":"aria-controls","type":"string","optional":true,"doc":""},{"name":"aria-haspopup","type":"boolean | \"true\" | \"false\" | \"menu\" | \"listbox\" | \"tree\" | \"grid\" | \"dialog\"","optional":true,"doc":""},{"name":"aria-pressed","type":"boolean | \"true\" | \"false\" | \"mixed\"","optional":true,"doc":"For toggle buttons."},{"name":"aria-current","type":"boolean | \"true\" | \"false\" | \"page\" | \"step\" | \"location\" | \"date\" | \"time\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"IconButton":[{"name":"icon","type":"IconName","optional":false,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name and tooltip (`aria-label` and `title`); `aria-label` or `title` override them."},{"name":"variant","type":"\"soft\" | \"solid\" | \"outline\" | \"ghost\"","optional":true,"doc":""},{"name":"size","type":"\"sm\" | \"md\" | \"lg\"","optional":true,"doc":""},{"name":"type","type":"\"button\" | \"submit\" | \"reset\"","optional":true,"doc":""},{"name":"onClick","type":"(e: MouseEvent<HTMLButtonElement>) => void","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"asChild","type":"boolean","optional":true,"doc":"Render the single child element (a link) with the button's look; the icon is placed inside it."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"id","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Form field name and value, sent with the form when this button submits it."},{"name":"value","type":"string","optional":true,"doc":""},{"name":"form","type":"string","optional":true,"doc":"The id of the form this button belongs to, when it sits outside it."},{"name":"title","type":"string","optional":true,"doc":""},{"name":"tabIndex","type":"number","optional":true,"doc":""},{"name":"autoFocus","type":"boolean","optional":true,"doc":""},{"name":"aria-label","type":"string","optional":true,"doc":""},{"name":"aria-labelledby","type":"string","optional":true,"doc":""},{"name":"aria-describedby","type":"string","optional":true,"doc":""},{"name":"aria-expanded","type":"boolean | \"true\" | \"false\"","optional":true,"doc":"For disclosure and menu triggers: whether what the button controls is open."},{"name":"aria-controls","type":"string","optional":true,"doc":""},{"name":"aria-haspopup","type":"boolean | \"true\" | \"false\" | \"menu\" | \"listbox\" | \"tree\" | \"grid\" | \"dialog\"","optional":true,"doc":""},{"name":"aria-pressed","type":"boolean | \"true\" | \"false\" | \"mixed\"","optional":true,"doc":"For toggle buttons."},{"name":"aria-current","type":"boolean | \"true\" | \"false\" | \"page\" | \"step\" | \"location\" | \"date\" | \"time\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Chip":[{"name":"selected","type":"boolean","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"count","type":"number","optional":true,"doc":""},{"name":"onClick","type":"(e: MouseEvent<HTMLButtonElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Badge":[{"name":"tone","type":"Tone","optional":true,"doc":""},{"name":"dot","type":"boolean","optional":true,"doc":""},{"name":"live","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TintIcon":[{"name":"icon","type":"IconName","optional":false,"doc":""},{"name":"tint","type":"Tint | \"plain\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Avatar":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"src","type":"string","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"tint","type":"Tint | \"surface-sunken\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AvatarStack":[{"name":"names","type":"string[]","optional":false,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Field":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Form field name: the value's key in `Form`, and where its validation message shows."},{"name":"id","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Input":[{"name":"type","type":"string","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Select":[{"name":"options","type":"string[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLSelectElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Textarea":[{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"rows","type":"number","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLTextAreaElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SearchField":[{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name (default: the placeholder, or \"Search\")."},{"name":"shortcut","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Checkbox":[{"name":"name","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"checked","type":"boolean","optional":true,"doc":""},{"name":"defaultChecked","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Switch":[{"name":"name","type":"string","optional":true,"doc":""},{"name":"checked","type":"boolean","optional":true,"doc":""},{"name":"defaultChecked","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"spread","type":"boolean","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Tabs":[{"name":"items","type":"string[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"block","type":"boolean","optional":true,"doc":""},{"name":"panels","type":"Record<string, ReactNode>","optional":true,"doc":"Content per tab; renders the tab panels under the bar, wired with tab/tabpanel roles."},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the tab list."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatTile":[{"name":"value","type":"ReactNode","optional":false,"doc":""},{"name":"label","type":"string","optional":false,"doc":""},{"name":"tint","type":"Tint | \"surface\" | \"placeholder\"","optional":true,"doc":""},{"name":"delta","type":"string","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Card":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"description","type":"string","optional":true,"doc":""},{"name":"meta","type":"string[]","optional":true,"doc":""},{"name":"corner","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"tint","type":"Tint | \"surface\"","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"eyebrow","type":"ReactNode","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: translucent glass over what is behind (maps, 3D, video, photos); `\"solid\"` opts out inside a glass `UIProvider`."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EventCard":[{"name":"kind","type":"\"meeting\" | \"deadline\" | \"review\" | \"launch\" | \"incident\"","optional":true,"doc":""},{"name":"label","type":"string","optional":false,"doc":""},{"name":"date","type":"string","optional":false,"doc":""},{"name":"time","type":"string","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"actions","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Panel":[{"name":"side","type":"boolean","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"subtitle","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: translucent glass over what is behind (maps, 3D, video, photos); `\"solid\"` opts out inside a glass `UIProvider`."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PageHeader":[{"name":"crumbs","type":"string[]","optional":true,"doc":""},{"name":"title","type":"string","optional":false,"doc":""},{"name":"subtitle","type":"string","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TopBar":[{"name":"brand","type":"string","optional":true,"doc":""},{"name":"tag","type":"string","optional":true,"doc":""},{"name":"items","type":"NavItem[]","optional":false,"doc":""},{"name":"active","type":"string","optional":true,"doc":""},{"name":"user","type":"{ name: string; email?: string }","optional":true,"doc":""},{"name":"onNavigate","type":"(label: string) => void","optional":true,"doc":""},{"name":"extra","type":"ReactNode","optional":true,"doc":""},{"name":"userOpen","type":"boolean","optional":true,"doc":""},{"name":"onUser","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SideRail":[{"name":"top","type":"RailItem[]","optional":true,"doc":""},{"name":"bottom","type":"RailItem[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FloatingLayout":[{"name":"background","type":"ReactNode","optional":true,"doc":"What the UI floats over, filling the layout: a map, a 3D canvas, a video, a photo."},{"name":"top","type":"ReactNode","optional":true,"doc":"Bar along the top edge (search, filters, title)."},{"name":"start","type":"ReactNode","optional":true,"doc":"Column on the start side (layers, lists, settings)."},{"name":"end","type":"ReactNode","optional":true,"doc":"Column on the end side (details of the selected thing)."},{"name":"bottom","type":"ReactNode","optional":true,"doc":"Bar along the bottom edge (dock, playback, a summary)."},{"name":"height","type":"number | string","optional":true,"doc":"Height of the layout (default 640). Below 720px of width the slots stack: top, start, end, bottom."},{"name":"surface","type":"Surface","optional":true,"doc":"Glass for every surface in the slots (default) or solid surfaces."},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the region."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Ambient":[{"name":"colors","type":"string[]","optional":true,"doc":"Colours of the soft fields (any CSS colour); default the kit's `--ambient-1…4`."},{"name":"src","type":"string","optional":true,"doc":"A photo (or, with `video`, a video) instead of the fields, under a scrim that keeps it calm."},{"name":"video","type":"boolean","optional":true,"doc":"Treat `src` as a video (muted, looping, still with motion off)."},{"name":"animate","type":"boolean","optional":true,"doc":"Let the fields drift slowly (default true). Still with motion off; paused off screen."},{"name":"fixed","type":"boolean","optional":true,"doc":"Cover the window behind the page instead of the nearest container."},{"name":"children","type":"ReactNode","optional":true,"doc":"Your own layer (a canvas, WebGL, a map) instead of fields or media."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Dock":[{"name":"items","type":"DockItem[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: translucent glass over what is behind (maps, 3D, video, photos); `\"solid\"` opts out inside a glass `UIProvider`."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Table":[{"name":"columns","type":"TableColumn[]","optional":false,"doc":""},{"name":"rows","type":"Record<string, any>[]","optional":false,"doc":""},{"name":"animate","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Progress":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"tint","type":"\"sky\" | \"mint\" | \"butter\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Toast":[{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"action","type":"string","optional":true,"doc":""},{"name":"onAction","type":"() => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EmptyState":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Breadcrumb":[{"name":"items","type":"string[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Pagination":[{"name":"total","type":"number","optional":false,"doc":"Number of pages."},{"name":"page","type":"number","optional":true,"doc":"The current page (1-based). Controlled together with `onChange`; without `onChange` it is\nonly the start page, as in earlier versions (prefer `defaultPage` for that)."},{"name":"defaultPage","type":"number","optional":true,"doc":"Start page when uncontrolled (default 1)."},{"name":"onChange","type":"(page: number) => void","optional":true,"doc":"A page was picked (previous, next or a number); receives the new 1-based page."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AppShell":[{"name":"active","type":"string","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"tag","type":"string","optional":true,"doc":""},{"name":"nav","type":"NavItem[]","optional":true,"doc":""},{"name":"user","type":"{ name: string; email?: string }","optional":true,"doc":""},{"name":"onNavigate","type":"(label: string) => void","optional":true,"doc":""},{"name":"topExtra","type":"ReactNode","optional":true,"doc":""},{"name":"sheetStyle","type":"CSSProperties","optional":true,"doc":""},{"name":"contentKey","type":"string | number","optional":true,"doc":""},{"name":"side","type":"ReactNode","optional":true,"doc":""},{"name":"dock","type":"boolean","optional":true,"doc":""},{"name":"dockItems","type":"DockProps[\"items\"]","optional":true,"doc":""},{"name":"railTop","type":"RailItem[]","optional":true,"doc":""},{"name":"railBottom","type":"RailItem[]","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Stagger":[{"name":"as","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":"Extra delay before the first child, in ms."},{"name":"reveal","type":"boolean","optional":true,"doc":"Below the fold, wait until the group scrolls into view (default true)."},{"name":"children","type":"ReactNode","optional":true,"doc":""}],"CountUp":[{"name":"value","type":"string | number","optional":false,"doc":""},{"name":"duration","type":"number","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ListRow":[{"name":"avatar","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint | \"plain\"","optional":true,"doc":""},{"name":"leading","type":"ReactNode","optional":true,"doc":""},{"name":"title","type":"ReactNode","optional":false,"doc":""},{"name":"subtitle","type":"ReactNode","optional":true,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"trailing","type":"ReactNode","optional":true,"doc":""},{"name":"unread","type":"boolean","optional":true,"doc":""},{"name":"active","type":"boolean","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Timeline":[{"name":"items","type":"{ avatar?: string; icon?: IconName; tint?: Tint | \"plain\"; text: ReactNode; time: string; }[]","optional":false,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileRow":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"meta","type":"string","optional":false,"doc":""},{"name":"kind","type":"\"pdf\" | \"fig\" | \"sheet\" | \"doc\" | \"zip\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"KanbanColumn":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"dot","type":"Tint","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"onAdd","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ProgressRing":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"stroke","type":"number","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BarChart":[{"name":"data","type":"{ label: string; value: number; tint?: Tint | \"ink\" }[]","optional":false,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"barWidth","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LineChart":[{"name":"labels","type":"string[]","optional":false,"doc":""},{"name":"series","type":"{ values: number[]; tint?: Tint | \"ink\"; area?: boolean }[]","optional":false,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Sparkline":[{"name":"values","type":"number[]","optional":false,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Donut":[{"name":"data","type":"{ label: string; value: number; tint?: Tint | \"ink\" }[]","optional":false,"doc":""},{"name":"center","type":"string","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"stroke","type":"number","optional":true,"doc":""},{"name":"legend","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Skeleton":[{"name":"variant","type":"\"text\" | \"title\" | \"circle\" | \"block\"","optional":true,"doc":""},{"name":"width","type":"number | string","optional":true,"doc":""},{"name":"height","type":"number | string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Menu":[{"name":"items","type":"(MenuItem | \"-\")[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the menu."},{"name":"autoFocus","type":"boolean","optional":true,"doc":"Focus the first item on mount (menus opened from a button or the keyboard)."},{"name":"onClose","type":"() => void","optional":true,"doc":"Called after an item runs, and on Escape, so the owner can close the menu."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Modal":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":"Optional: with `open` the component stays mounted while it animates out; without it, render it only while open."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."}],"SubNav":[{"name":"eyebrow","type":"string","optional":true,"doc":""},{"name":"action","type":"{ label: string; icon?: IconName; onClick?: () => void }","optional":true,"doc":""},{"name":"items","type":"SubNavItem[]","optional":false,"doc":""},{"name":"current","type":"string","optional":true,"doc":""},{"name":"onPick","type":"(id: string) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Drawer":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"subtitle","type":"ReactNode","optional":true,"doc":""},{"name":"avatar","type":"string","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"width","type":"number | string","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":"Optional: with `open` the component stays mounted while it animates out; without it, render it only while open."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CommandPalette":[{"name":"items","type":"PaletteItem[]","optional":false,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"loading","type":"boolean","optional":true,"doc":"Results are on their way (async search): shows a loading row."},{"name":"empty","type":"ReactNode","optional":true,"doc":"Shown when nothing matches (default: \"Nothing matches …\")."},{"name":"onQueryChange","type":"(query: string) => void","optional":true,"doc":"Called with the query as the user types, for searching on a server."},{"name":"open","type":"boolean","optional":true,"doc":"Optional: with `open` the component stays mounted while it animates out; without it, render it only while open."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"NotificationList":[{"name":"items","type":"NotificationItem[]","optional":false,"doc":""},{"name":"onOpen","type":"(n: NotificationItem) => void","optional":true,"doc":""},{"name":"onReadAll","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Flow":[{"name":"steps","type":"FlowStep[]","optional":false,"doc":""},{"name":"active","type":"number | null","optional":true,"doc":""},{"name":"waiting","type":"boolean","optional":true,"doc":""},{"name":"onApprove","type":"() => void","optional":true,"doc":""},{"name":"onReject","type":"() => void","optional":true,"doc":""},{"name":"tools","type":"(step: FlowStep, index: number) => ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"kind","type":"FileKind","optional":false,"doc":""},{"name":"size","type":"string","optional":true,"doc":""},{"name":"meta","type":"string","optional":true,"doc":""},{"name":"shared","type":"boolean","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileThumb":[{"name":"kind","type":"FileKind","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"big","type":"boolean","optional":true,"doc":""},{"name":"entries","type":"string[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Wizard":[{"name":"steps","type":"{ id?: string; label: string; icon?: IconName }[]","optional":false,"doc":""},{"name":"current","type":"number","optional":true,"doc":""},{"name":"done","type":"Record<string, boolean>","optional":true,"doc":""},{"name":"onPick","type":"(i: number) => void","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PermissionMatrix":[{"name":"roles","type":"{ name: string; people?: number; locked?: boolean; levels: (0 | 1 | 2 | 3)[] }[]","optional":false,"doc":""},{"name":"areas","type":"string[]","optional":false,"doc":""},{"name":"onChange","type":"(roleIndex: number, areaIndex: number, nextLevel: number) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"InvoicePreview":[{"name":"number","type":"string | number","optional":false,"doc":""},{"name":"from","type":"string","optional":true,"doc":""},{"name":"to","type":"string","optional":false,"doc":""},{"name":"issued","type":"string","optional":false,"doc":""},{"name":"due","type":"string","optional":false,"doc":""},{"name":"lines","type":"{ d: string; q: number; u: number; v: number }[]","optional":false,"doc":""},{"name":"note","type":"string","optional":true,"doc":""},{"name":"kind","type":"string","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"format","type":"(n: number) => string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TransactionRow":[{"name":"text","type":"string","optional":false,"doc":""},{"name":"date","type":"string","optional":false,"doc":""},{"name":"amount","type":"number","optional":false,"doc":""},{"name":"matched","type":"boolean","optional":true,"doc":""},{"name":"matchRef","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"onMatch","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FormField":[{"name":"id","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Field name inside a `Form`: the field shows that name's validation message."},{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"optional","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"success","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TextField":[{"name":"type","type":"string","optional":true,"doc":"Input type (`\"text\"` by default): `\"email\"`, `\"number\"`, `\"tel\"`, `\"url\"`, `\"password\"` …"},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"suffix","type":"ReactNode","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"readOnly","type":"boolean","optional":true,"doc":""},{"name":"autoComplete","type":"string","optional":true,"doc":""},{"name":"autoFocus","type":"boolean","optional":true,"doc":""},{"name":"min","type":"number | string","optional":true,"doc":"Smallest value for `type=\"number\"`, `\"date\"` and the like (native `min`)."},{"name":"max","type":"number | string","optional":true,"doc":"Largest value (native `max`)."},{"name":"step","type":"number | string","optional":true,"doc":"Step between valid values (native `step`), e.g. `1` for whole numbers or `\"any\"`."},{"name":"minLength","type":"number","optional":true,"doc":"Fewest characters (native `minlength`)."},{"name":"maxLength","type":"number","optional":true,"doc":"Most characters (native `maxlength`)."},{"name":"pattern","type":"string","optional":true,"doc":"Pattern the value must match (native `pattern`)."},{"name":"inputMode","type":"\"none\" | \"text\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | \"search\"","optional":true,"doc":"Virtual keyboard on touch devices (native `inputmode`), e.g. `\"numeric\"` or `\"decimal\"`."},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"onBlur","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""}],"RadioGroup":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"options","type":"(string | Option)[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"cards","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Segmented":[{"name":"options","type":"(string | Option)[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"size","type":"\"sm\"","optional":true,"doc":""},{"name":"block","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Combobox":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"options","type":"(string | Option)[]","optional":false,"doc":""},{"name":"value","type":"string | string[] | null","optional":true,"doc":""},{"name":"defaultValue","type":"string | string[]","optional":true,"doc":""},{"name":"onChange","type":"(v: string | string[] | null) => void","optional":true,"doc":""},{"name":"multiple","type":"boolean","optional":true,"doc":""},{"name":"creatable","type":"boolean","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TagInput":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"value","type":"string[]","optional":true,"doc":""},{"name":"defaultValue","type":"string[]","optional":true,"doc":""},{"name":"onChange","type":"(v: string[]) => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DatePicker":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"range","type":"boolean","optional":true,"doc":""},{"name":"value","type":"Date | DateRange | null","optional":true,"doc":""},{"name":"defaultValue","type":"Date | DateRange","optional":true,"doc":""},{"name":"onChange","type":"(v: Date | DateRange | null) => void","optional":true,"doc":""},{"name":"presets","type":"boolean","optional":true,"doc":""},{"name":"min","type":"Date","optional":true,"doc":""},{"name":"max","type":"Date","optional":true,"doc":""},{"name":"today","type":"Date","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Calendar":[{"name":"range","type":"boolean","optional":true,"doc":""},{"name":"value","type":"Date | DateRange | null","optional":true,"doc":""},{"name":"onChange","type":"(v: Date | DateRange) => void","optional":true,"doc":""},{"name":"today","type":"Date","optional":true,"doc":""},{"name":"min","type":"Date","optional":true,"doc":""},{"name":"max","type":"Date","optional":true,"doc":""},{"name":"month","type":"Date","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"NumberInput":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"value","type":"number","optional":true,"doc":""},{"name":"defaultValue","type":"number","optional":true,"doc":""},{"name":"onChange","type":"(n: number) => void","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"step","type":"number","optional":true,"doc":""},{"name":"unit","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Slider":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"step","type":"number","optional":true,"doc":""},{"name":"value","type":"number","optional":true,"doc":""},{"name":"defaultValue","type":"number","optional":true,"doc":""},{"name":"onChange","type":"(n: number) => void","optional":true,"doc":""},{"name":"format","type":"(n: number) => string","optional":true,"doc":""},{"name":"marks","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OtpInput":[{"name":"length","type":"number","optional":true,"doc":""},{"name":"onComplete","type":"(code: string) => void","optional":true,"doc":""},{"name":"error","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileDrop":[{"name":"files","type":"UploadFile[]","optional":true,"doc":"The listed files (controlled); set `progress` per file to show real upload progress."},{"name":"onChange","type":"(f: UploadFile[]) => void","optional":true,"doc":"The list changed: files added (with their `file`) or removed."},{"name":"onFiles","type":"(files: File[]) => void","optional":true,"doc":"The person picked or dropped files: the real `File` objects, after `accept` and `multiple`.\nUpload them yourself. To show progress, control `files` and set each `progress`; an\nuncontrolled list shows them with their size only."},{"name":"onReject","type":"(files: File[]) => void","optional":true,"doc":"Dropped files that do not match `accept` (show an inline Alert)."},{"name":"onRemove","type":"(f: UploadFile) => void","optional":true,"doc":"A listed file was removed or its upload cancelled."},{"name":"simulateProgress","type":"boolean","optional":true,"doc":"Animate made-up progress for added files, as a demo of the look. Default: on without\n`onFiles` (as before), off with it."},{"name":"title","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"accept","type":"string","optional":true,"doc":"Accepted types, as for `<input type=\"file\">` (\"image/*,.pdf\"); also checked on drop."},{"name":"multiple","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Tooltip":[{"name":"label","type":"ReactNode","optional":false,"doc":""},{"name":"shortcut","type":"string","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\" | \"left\" | \"right\"","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":"Starts open (for demos and onboarding hints); it still closes on blur and Escape."},{"name":"children","type":"ReactNode","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Popover":[{"name":"trigger","type":"ReactNode | ((s: { open: boolean; toggle: () => void }) => ReactNode)","optional":false,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"onOpenChange","type":"(o: boolean) => void","optional":true,"doc":""},{"name":"align","type":"\"start\" | \"end\"","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\"","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"flush","type":"boolean","optional":true,"doc":""},{"name":"block","type":"boolean","optional":true,"doc":""},{"name":"role","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode | ((s: { close: () => void }) => ReactNode)","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Alert":[{"name":"tone","type":"Exclude<Tone, \"neutral\">","optional":true,"doc":""},{"name":"title","type":"ReactNode","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"onDismiss","type":"() => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConfirmDialog":[{"name":"open","type":"boolean","optional":false,"doc":""},{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"danger","type":"boolean","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"confirmLabel","type":"string","optional":true,"doc":""},{"name":"cancelLabel","type":"string","optional":true,"doc":""},{"name":"confirmText","type":"string","optional":true,"doc":""},{"name":"onConfirm","type":"() => void","optional":false,"doc":""},{"name":"onCancel","type":"() => void","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Toaster":[{"name":"position","type":"\"bottom\" | \"top\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FilterBar":[{"name":"filters","type":"FilterDef[]","optional":false,"doc":""},{"name":"value","type":"Record<string, string>","optional":false,"doc":""},{"name":"onChange","type":"(v: Record<string, string>) => void","optional":false,"doc":""},{"name":"search","type":"string","optional":true,"doc":""},{"name":"query","type":"string","optional":true,"doc":""},{"name":"onQuery","type":"(q: string) => void","optional":true,"doc":""},{"name":"right","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DataTable":[{"name":"columns","type":"DataColumn<R>[]","optional":false,"doc":""},{"name":"rows","type":"R[]","optional":false,"doc":""},{"name":"rowKey","type":"string","optional":true,"doc":""},{"name":"selectable","type":"boolean","optional":true,"doc":""},{"name":"bulkActions","type":"{ label: string; icon?: IconName; danger?: boolean; onClick: (rows: R[], clear: () => void) => void; }[]","optional":true,"doc":""},{"name":"renderExpanded","type":"(row: R) => ReactNode","optional":true,"doc":""},{"name":"stickyHeader","type":"boolean","optional":true,"doc":""},{"name":"maxHeight","type":"number | string","optional":true,"doc":""},{"name":"defaultSort","type":"{ key: string; dir: \"asc\" | \"desc\" }","optional":true,"doc":""},{"name":"toolbar","type":"ReactNode","optional":true,"doc":""},{"name":"columnToggle","type":"boolean","optional":true,"doc":""},{"name":"empty","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StackedBarChart":[{"name":"labels","type":"string[]","optional":false,"doc":""},{"name":"series","type":"Series[]","optional":false,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"barWidth","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"legend","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AreaChart":[{"name":"labels","type":"string[]","optional":false,"doc":""},{"name":"series","type":"Series[]","optional":false,"doc":""},{"name":"stacked","type":"boolean","optional":true,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"legend","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Heatmap":[{"name":"rows","type":"string[]","optional":false,"doc":""},{"name":"cols","type":"string[]","optional":false,"doc":""},{"name":"values","type":"number[][]","optional":false,"doc":""},{"name":"colEvery","type":"number","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Funnel":[{"name":"steps","type":"{ label: string; value: number; tint?: Tint }[]","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Gauge":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"thresholds","type":"[number, number]","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"stroke","type":"number","optional":true,"doc":""},{"name":"showRange","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Legend":[{"name":"items","type":"{ name?: string; label?: string; tint?: Tint | \"ink\" }[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatMessage":[{"name":"role","type":"\"user\" | \"assistant\" | \"system\"","optional":false,"doc":""},{"name":"name","type":"string","optional":true,"doc":""},{"name":"time","type":"string","optional":true,"doc":""},{"name":"model","type":"string","optional":true,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"streaming","type":"boolean","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"sources","type":"Source[]","optional":true,"doc":""},{"name":"attachments","type":"{ name: string; size?: string; kind?: string }[]","optional":true,"doc":""},{"name":"usage","type":"string","optional":true,"doc":""},{"name":"onRetry","type":"() => void","optional":true,"doc":""},{"name":"onFeedback","type":"(v: \"up\" | \"down\") => void","optional":true,"doc":""},{"name":"actions","type":"boolean","optional":true,"doc":""},{"name":"before","type":"ReactNode","optional":true,"doc":""},{"name":"after","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"feedback","type":"\"up\" | \"down\" | null","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RichText":[{"name":"text","type":"string","optional":false,"doc":""},{"name":"streaming","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CodeBlock":[{"name":"code","type":"string","optional":false,"doc":""},{"name":"lang","type":"string","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ToolCall":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"status","type":"\"running\" | \"done\" | \"error\" | \"waiting\"","optional":true,"doc":""},{"name":"summary","type":"ReactNode","optional":true,"doc":""},{"name":"duration","type":"string","optional":true,"doc":""},{"name":"input","type":"unknown","optional":true,"doc":""},{"name":"output","type":"unknown","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Reasoning":[{"name":"steps","type":"string[]","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"duration","type":"string","optional":true,"doc":""},{"name":"streaming","type":"boolean","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PromptStarters":[{"name":"items","type":"{ icon?: IconName; tint?: Tint; title: string; text?: string; prompt?: string }[]","optional":false,"doc":""},{"name":"onPick","type":"(prompt: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ModelPicker":[{"name":"models","type":"ModelOption[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(id: string) => void","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Composer":[{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"onSend","type":"(text: string, files: (File | { name: string })[]) => void","optional":true,"doc":""},{"name":"busy","type":"boolean","optional":true,"doc":""},{"name":"onStop","type":"() => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"models","type":"ModelOption[]","optional":true,"doc":""},{"name":"model","type":"string","optional":true,"doc":""},{"name":"onModel","type":"(id: string) => void","optional":true,"doc":""},{"name":"commands","type":"ComposerCommand[]","optional":true,"doc":""},{"name":"onCommand","type":"(c: ComposerCommand) => void","optional":true,"doc":""},{"name":"attachments","type":"(File | { name: string })[]","optional":true,"doc":""},{"name":"onAttach","type":"(files: FileList) => void","optional":true,"doc":""},{"name":"onRemoveAttachment","type":"(i: number) => void","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"left","type":"ReactNode","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentStatus":[{"name":"status","type":"\"Running\" | \"Thinking\" | \"Waiting for approval\" | \"Idle\" | \"Paused\" | \"Error\" | string","optional":false,"doc":""},{"name":"step","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentMark":[{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"live","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RunTrace":[{"name":"steps","type":"TraceStep[]","optional":false,"doc":""},{"name":"defaultOpen","type":"number | null","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"UsageMeter":[{"name":"used","type":"number","optional":false,"doc":""},{"name":"limit","type":"number","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ProductArt":[{"name":"kind","type":"ArtKind","optional":false,"doc":""},{"name":"tint","type":"Tint | \"plain\"","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Price":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"compareAt","type":"number","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"size","type":"\"lg\"","optional":true,"doc":""},{"name":"showSave","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Rating":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"count","type":"number","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StockBadge":[{"name":"stock","type":"number","optional":false,"doc":""},{"name":"low","type":"number","optional":true,"doc":""},{"name":"compact","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ProductCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"meta","type":"string","optional":true,"doc":""},{"name":"art","type":"ArtKind","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"price","type":"number","optional":false,"doc":""},{"name":"compareAt","type":"number","optional":true,"doc":""},{"name":"rating","type":"number","optional":true,"doc":""},{"name":"reviews","type":"number","optional":true,"doc":""},{"name":"badge","type":"string","optional":true,"doc":""},{"name":"badgeTone","type":"Tone","optional":true,"doc":""},{"name":"stock","type":"number","optional":true,"doc":""},{"name":"wished","type":"boolean","optional":true,"doc":""},{"name":"onWish","type":"() => void","optional":true,"doc":""},{"name":"added","type":"boolean","optional":true,"doc":""},{"name":"onAdd","type":"() => void","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"VariantPicker":[{"name":"label","type":"string","optional":false,"doc":""},{"name":"type","type":"\"color\"","optional":true,"doc":""},{"name":"options","type":"(string | { value: string; label?: string; color?: string; disabled?: boolean })[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CartLine":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"variant","type":"string","optional":true,"doc":""},{"name":"note","type":"string","optional":true,"doc":""},{"name":"art","type":"ArtKind","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"price","type":"number","optional":false,"doc":""},{"name":"qty","type":"number","optional":false,"doc":""},{"name":"onQty","type":"(n: number) => void","optional":true,"doc":""},{"name":"onRemove","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OrderSummary":[{"name":"lines","type":"{ name: string; variant?: string; art: ArtKind; tint?: Tint; price: number; qty: number; }[]","optional":false,"doc":""},{"name":"showLines","type":"boolean","optional":true,"doc":""},{"name":"discount","type":"Discount | null","optional":true,"doc":""},{"name":"onCoupon","type":"(code: string | null) => boolean | void","optional":true,"doc":""},{"name":"shipping","type":"number | null","optional":true,"doc":""},{"name":"freeShippingFrom","type":"number","optional":true,"doc":""},{"name":"vat","type":"number","optional":true,"doc":""},{"name":"title","type":"string | false","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CheckoutSteps":[{"name":"steps","type":"string[]","optional":true,"doc":""},{"name":"current","type":"number","optional":true,"doc":""},{"name":"onPick","type":"(i: number) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OrderTimeline":[{"name":"status","type":"\"Placed\" | \"Paid\" | \"Packed\" | \"Shipped\" | \"Delivered\" | \"Refunded\" | \"Cancelled\"","optional":false,"doc":""},{"name":"times","type":"Record<string, string>","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PlanCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"price","type":"number | null","optional":false,"doc":""},{"name":"was","type":"number","optional":true,"doc":""},{"name":"per","type":"string","optional":true,"doc":""},{"name":"features","type":"string[]","optional":true,"doc":""},{"name":"featured","type":"boolean","optional":true,"doc":""},{"name":"flag","type":"string","optional":true,"doc":""},{"name":"current","type":"boolean","optional":true,"doc":""},{"name":"cta","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":"Disable the button, e.g. while a checkout starts or when the plan cannot be picked."},{"name":"onPick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusScreen":[{"name":"code","type":"\"404\" | \"500\" | \"403\" | \"maintenance\" | \"offline\" | string","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"extra","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"onHome","type":"() => void","optional":true,"doc":""},{"name":"onContact","type":"() => void","optional":true,"doc":""},{"name":"reference","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AuthCard":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"foot","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusShape":[{"name":"status","type":"Status","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusIndicator":[{"name":"status","type":"Status","optional":false,"doc":""},{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"size","type":"\"sm\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ApprovalCard":[{"name":"title","type":"ReactNode","optional":false,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"agent","type":"string","optional":true,"doc":""},{"name":"project","type":"string","optional":true,"doc":""},{"name":"family","type":"RiskFamily","optional":true,"doc":""},{"name":"familyLabel","type":"string","optional":true,"doc":""},{"name":"age","type":"string","optional":true,"doc":""},{"name":"escalation","type":"ReactNode","optional":true,"doc":""},{"name":"trace","type":"ReactNode","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"heading","type":"string","optional":true,"doc":""},{"name":"options","type":"ApprovalOption[]","optional":true,"doc":""},{"name":"approveLabel","type":"string","optional":true,"doc":""},{"name":"onDecide","type":"(key: ApprovalKey, text?: string) => void","optional":true,"doc":""},{"name":"decision","type":"ApprovalDecision | null","optional":true,"doc":""},{"name":"compact","type":"boolean","optional":true,"doc":""},{"name":"onApprove","type":"() => void","optional":true,"doc":""},{"name":"onDeny","type":"() => void","optional":true,"doc":""},{"name":"onEdit","type":"(text: string) => void","optional":true,"doc":""},{"name":"state","type":"\"approved\" | \"denied\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ApprovalInbox":[{"name":"items","type":"ApprovalInboxItem[]","optional":false,"doc":""},{"name":"selected","type":"ApprovalInboxItem[\"id\"]","optional":true,"doc":""},{"name":"onSelect","type":"(id: ApprovalInboxItem[\"id\"]) => void","optional":true,"doc":""},{"name":"emptyTitle","type":"string","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Kbd":[{"name":"keys","type":"string[]","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"KeyValue":[{"name":"items","type":"KeyValueItem[]","optional":false,"doc":""},{"name":"dense","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Spinner":[{"name":"size","type":"\"lg\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Collapsible":[{"name":"title","type":"ReactNode","optional":false,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"onOpenChange","type":"(open: boolean) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ContextMenu":[{"name":"items","type":"(MenuItem | \"-\")[]","optional":false,"doc":""},{"name":"children","type":"ReactNode","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SplitPane":[{"name":"left","type":"ReactNode","optional":false,"doc":""},{"name":"right","type":"ReactNode","optional":false,"doc":""},{"name":"defaultSize","type":"number","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"storageKey","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TreeView":[{"name":"items","type":"TreeItem[]","optional":false,"doc":""},{"name":"defaultOpen","type":"string[]","optional":true,"doc":""},{"name":"selected","type":"string","optional":true,"doc":""},{"name":"onSelect","type":"(id: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"InlineEdit":[{"name":"value","type":"string","optional":false,"doc":""},{"name":"onSave","type":"(v: string) => void","optional":true,"doc":""},{"name":"options","type":"string[]","optional":true,"doc":""},{"name":"mono","type":"boolean","optional":true,"doc":""},{"name":"type","type":"string","optional":true,"doc":""},{"name":"display","type":"(v: string) => ReactNode","optional":true,"doc":""},{"name":"align","type":"\"left\" | \"right\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LogView":[{"name":"lines","type":"LogLine[]","optional":false,"doc":""},{"name":"title","type":"ReactNode","optional":true,"doc":""},{"name":"right","type":"ReactNode","optional":true,"doc":""},{"name":"numbers","type":"boolean","optional":true,"doc":""},{"name":"follow","type":"boolean","optional":true,"doc":""},{"name":"maxHeight","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CodeViewer":[{"name":"value","type":"string | object","optional":false,"doc":""},{"name":"lang","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SecretField":[{"name":"prefix","type":"string","optional":true,"doc":""},{"name":"last4","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"onReveal","type":"() => boolean | Promise<boolean>","optional":true,"doc":""},{"name":"onCopy","type":"() => void","optional":true,"doc":""},{"name":"onRotate","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MoneyInput":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"value","type":"number | null","optional":true,"doc":""},{"name":"onChange","type":"(v: number | null) => void","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"vat","type":"string","optional":true,"doc":""},{"name":"onVat","type":"(v: string) => void","optional":true,"doc":""},{"name":"vatOptions","type":"{ value: string; label: string; rate?: number }[]","optional":true,"doc":""},{"name":"showGross","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"defaultValue","type":"number | null","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SettingsLayout":[{"name":"sections","type":"{ id?: string; title: ReactNode; text?: ReactNode; badge?: ReactNode; children?: ReactNode; }[]","optional":false,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ShortcutsOverlay":[{"name":"open","type":"boolean","optional":false,"doc":""},{"name":"onClose","type":"() => void","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"groups","type":"{ title: string; items: [string, string[]][] }[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatCell":[{"name":"label","type":"ReactNode","optional":false,"doc":""},{"name":"value","type":"ReactNode","optional":false,"doc":""},{"name":"delta","type":"number","optional":true,"doc":""},{"name":"deltaText","type":"string","optional":true,"doc":""},{"name":"compare","type":"string","optional":true,"doc":""},{"name":"invert","type":"boolean","optional":true,"doc":""},{"name":"spark","type":"number[]","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"iconTint","type":"Tint","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"UptimeBar":[{"name":"days","type":"UptimeDay[]","optional":false,"doc":""},{"name":"uptime","type":"number","optional":true,"doc":""},{"name":"caption","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Meter":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"value","type":"number | null","optional":false,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"warn","type":"number","optional":true,"doc":""},{"name":"danger","type":"number","optional":true,"doc":""},{"name":"tone","type":"Tint","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"role","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"model","type":"string","optional":true,"doc":""},{"name":"cost","type":"string","optional":true,"doc":""},{"name":"last","type":"ReactNode","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"onChat","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentDetails":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"role","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"sections","type":"{ title: string; meta?: ReactNode; items?: KeyValueItem[]; children?: ReactNode; open?: boolean; }[]","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatList":[{"name":"groups","type":"{ title: string; mark?: Tint; items: ChatListItem[] }[]","optional":false,"doc":""},{"name":"selected","type":"string","optional":true,"doc":""},{"name":"onSelect","type":"(id: string) => void","optional":true,"doc":""},{"name":"onSearch","type":"(q: string) => void","optional":true,"doc":""},{"name":"filters","type":"ReactNode","optional":true,"doc":""},{"name":"searchPlaceholder","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatWorkspace":[{"name":"list","type":"ReactNode","optional":false,"doc":""},{"name":"thread","type":"ReactNode","optional":false,"doc":""},{"name":"details","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SearchHits":[{"name":"items","type":"{ id: string | number; chat: string; time?: string; who?: string; text: string }[]","optional":false,"doc":""},{"name":"query","type":"string","optional":true,"doc":""},{"name":"onOpen","type":"(item: NonNullable<SearchHitsProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ThinkingLine":[{"name":"text","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FeedbackRating":[{"name":"value","type":"\"up\" | \"down\" | null","optional":true,"doc":""},{"name":"onRate","type":"(v: \"up\" | \"down\") => void","optional":true,"doc":""},{"name":"onSubmit","type":"(v: \"up\" | \"down\", comment?: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"thanks","type":"string","optional":true,"doc":""},{"name":"askOnUp","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ActivityFeed":[{"name":"groups","type":"{ title?: string; items: ActivityItem[] }[]","optional":true,"doc":""},{"name":"items","type":"ActivityItem[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DailyBriefing":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"time","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"summary","type":"ReactNode","optional":false,"doc":""},{"name":"attention","type":"{ text: ReactNode; status?: Status; onClick?: () => void }[]","optional":true,"doc":""},{"name":"suggestions","type":"ReactNode[]","optional":true,"doc":""},{"name":"attentionTitle","type":"string","optional":true,"doc":""},{"name":"suggestionsTitle","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BudgetStop":[{"name":"spent","type":"number","optional":false,"doc":""},{"name":"budget","type":"number","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"paused","type":"ReactNode","optional":true,"doc":""},{"name":"running","type":"ReactNode","optional":true,"doc":""},{"name":"onRaise","type":"() => void","optional":true,"doc":""},{"name":"actionLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MemoryViewer":[{"name":"items","type":"{ id: string | number; scope: string; source?: string; date?: string; text: string }[]","optional":false,"doc":""},{"name":"onEdit","type":"(item: NonNullable<MemoryViewerProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"onDelete","type":"(item: NonNullable<MemoryViewerProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OverrideDiff":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"versions","type":"ReactNode","optional":true,"doc":""},{"name":"by","type":"ReactNode","optional":true,"doc":""},{"name":"before","type":"string","optional":false,"doc":""},{"name":"after","type":"string","optional":false,"doc":""},{"name":"onDiscard","type":"() => void","optional":true,"doc":""},{"name":"onPromote","type":"() => void","optional":true,"doc":""},{"name":"discardLabel","type":"string","optional":true,"doc":""},{"name":"promoteLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TriggerList":[{"name":"items","type":"{ id: string | number; type: \"schedule\" | \"event\" | \"webhook\" | \"mail\"; rule: ReactNode; agent?: string; by?: string; next?: string; enabled?: boolean; }[]","optional":false,"doc":""},{"name":"onToggle","type":"(item: NonNullable<TriggerListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"onRun","type":"(item: NonNullable<TriggerListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ScheduleCalendar":[{"name":"events","type":"{ day: number; start: number; end: number; label: string; sub?: string; tint?: Tint; self?: boolean; }[]","optional":false,"doc":""},{"name":"from","type":"number","optional":true,"doc":""},{"name":"to","type":"number","optional":true,"doc":""},{"name":"days","type":"string[]","optional":true,"doc":""},{"name":"onOpen","type":"(e: NonNullable<ScheduleCalendarProps[\"events\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OrgChart":[{"name":"root","type":"OrgNode","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"VoiceMode":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"state","type":"\"listening\" | \"thinking\" | \"speaking\" | \"idle\"","optional":false,"doc":""},{"name":"stateLabel","type":"string","optional":true,"doc":""},{"name":"transcript","type":"ReactNode","optional":true,"doc":""},{"name":"muted","type":"boolean","optional":true,"doc":""},{"name":"onMute","type":"() => void","optional":true,"doc":""},{"name":"onEnd","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConnectionCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"logo","type":"ReactNode","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"\"connected\" | \"expiring\" | \"error\" | \"missing\"","optional":false,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConnectionBanner":[{"name":"state","type":"\"reconnecting\" | \"offline\" | \"online\"","optional":false,"doc":""},{"name":"seconds","type":"number","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"onRetry","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ServerCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"provider","type":"string","optional":true,"doc":""},{"name":"address","type":"string","optional":true,"doc":""},{"name":"logo","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"meters","type":"{ label: string; value: number | null; max?: number; text?: ReactNode; unknown?: string; }[]","optional":true,"doc":""},{"name":"uptime","type":"UptimeBarProps","optional":true,"doc":""},{"name":"foot","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DeployPipeline":[{"name":"stages","type":"{ name: string; status?: \"done\" | \"running\" | \"signal\" | \"danger\" | \"idle\"; duration?: string; action?: ReactNode; }[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DnsRecordTable":[{"name":"records","type":"DnsRecord[]","optional":false,"doc":""},{"name":"rowMenu","type":"(r: DnsRecord) => (MenuItem | \"-\")[]","optional":true,"doc":""},{"name":"selectable","type":"boolean","optional":true,"doc":""},{"name":"bulkActions","type":"BulkAction<DnsRecord>[]","optional":true,"doc":""},{"name":"toolbar","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DomainList":[{"name":"items","type":"{ name: string; registrar?: string; dns?: string; days?: number; ssl?: Status; sslLabel?: string; }[]","optional":false,"doc":""},{"name":"onOpen","type":"(d: NonNullable<DomainListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MailAuthStatus":[{"name":"rows","type":"{ domain: string; mx?: { status: Status; label?: string }; spf?: { status: Status; label?: string }; dkim?: { status: Status; label?: string }; dmarc?: { status: Status; label?: string }; }[]","optional":false,"doc":""},{"name":"columns","type":"string[]","optional":true,"doc":""},{"name":"onOpen","type":"(row: NonNullable<MailAuthStatusProps[\"rows\"]>[number], column: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LedgerTable":[{"name":"rows","type":"LedgerRow[]","optional":false,"doc":""},{"name":"categories","type":"string[]","optional":true,"doc":""},{"name":"onEdit","type":"(row: LedgerRow, change: Partial<LedgerRow>) => void","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"selectable","type":"boolean","optional":true,"doc":""},{"name":"bulkActions","type":"BulkAction<LedgerRow>[]","optional":true,"doc":""},{"name":"toolbar","type":"ReactNode","optional":true,"doc":""},{"name":"maxHeight","type":"number","optional":true,"doc":""},{"name":"columnToggle","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PnLStatement":[{"name":"columns","type":"string[]","optional":false,"doc":""},{"name":"rows","type":"{ kind?: \"section\" | \"subtotal\" | \"total\"; label: string; values?: number[] }[]","optional":false,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"total","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ReceiptSuggestion":[{"name":"file","type":"string","optional":false,"doc":""},{"name":"source","type":"ReactNode","optional":true,"doc":""},{"name":"fields","type":"{ label: string; value: ReactNode; mono?: boolean; confidence?: number }[]","optional":false,"doc":""},{"name":"suggestion","type":"ReactNode","optional":true,"doc":""},{"name":"onConfirm","type":"() => void","optional":true,"doc":""},{"name":"onDiscard","type":"() => void","optional":true,"doc":""},{"name":"confirmLabel","type":"string","optional":true,"doc":""},{"name":"discardLabel","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AccountSwitcher":[{"name":"accounts","type":"{ address: string; domain?: string; unread?: number }[]","optional":false,"doc":""},{"name":"value","type":"string | null","optional":true,"doc":""},{"name":"onChange","type":"(address: string | null) => void","optional":true,"doc":""},{"name":"unread","type":"number","optional":true,"doc":""},{"name":"allLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"InboxList":[{"name":"items","type":"{ id: string | number; from: string; time?: string; subject: string; snippet?: string; unread?: boolean; to?: string; agent?: { label: string; tone?: Tone }; tags?: string[]; }[]","optional":false,"doc":""},{"name":"selected","type":"string | number","optional":true,"doc":""},{"name":"onOpen","type":"(m: NonNullable<InboxListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MailThread":[{"name":"subject","type":"string","optional":false,"doc":""},{"name":"messages","type":"{ id: string | number; from: string; to?: string; time?: string; snippet?: string; body: ReactNode; agent?: boolean; tint?: Tint; agentNote?: string; attachments?: { name: string; size?: string }[]; }[]","optional":false,"doc":""},{"name":"reply","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SignatureEditor":[{"name":"value","type":"Record<string, string>","optional":true,"doc":""},{"name":"onChange","type":"(v: Record<string, string>) => void","optional":true,"doc":""},{"name":"fields","type":"{ key: string; label: string }[]","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"extra","type":"ReactNode","optional":true,"doc":""},{"name":"defaultValue","type":"Record<string, string>","optional":true,"doc":""},{"name":"previewLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AttachmentPreview":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"facts","type":"KeyValueItem[]","optional":true,"doc":""},{"name":"found","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"ReactNode","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"width","type":"number | string","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MobileTabBar":[{"name":"items","type":"{ id: string; label: string; icon: IconName; count?: number }[]","optional":false,"doc":""},{"name":"active","type":"string","optional":true,"doc":""},{"name":"onPick","type":"(id: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PushNotification":[{"name":"app","type":"string","optional":true,"doc":""},{"name":"time","type":"string","optional":true,"doc":""},{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"{ label: string; onClick?: () => void }[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConfigPRSummary":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"summary","type":"ReactNode","optional":true,"doc":""},{"name":"items","type":"{ kind: \"project\" | \"team\" | \"agent\" | \"tool\" | \"trigger\" | \"file\"; name: string; detail?: ReactNode; op?: \"add\" | \"change\" | \"remove\"; }[]","optional":false,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ModuleRegistry":[{"name":"items","type":"{ id: string; name: string; version?: string; text?: ReactNode; icon?: IconName; core?: boolean; enabled?: boolean; adds?: string[]; }[]","optional":false,"doc":""},{"name":"onToggle","type":"(m: NonNullable<ModuleRegistryProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"coreLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RangeSlider":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"step","type":"number","optional":true,"doc":""},{"name":"value","type":"[number, number]","optional":true,"doc":""},{"name":"defaultValue","type":"[number, number]","optional":true,"doc":""},{"name":"onChange","type":"(v: [number, number]) => void","optional":true,"doc":""},{"name":"unit","type":"string","optional":true,"doc":""},{"name":"inputs","type":"boolean","optional":true,"doc":""},{"name":"openEnd","type":"boolean","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BarList":[{"name":"items","type":"{ id?: string; label: ReactNode; mark?: ReactNode; value: number; share?: number; tint?: Tint; }[]","optional":false,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"onPick","type":"(item: NonNullable<BarListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusSelect":[{"name":"options","type":"{ value: string; label: string; status?: Status }[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"size","type":"\"sm\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TilePicker":[{"name":"options","type":"{ value: string; label: ReactNode; meta?: ReactNode; image?: string; icon?: IconName; art?: ReactNode; tint?: Tint; color?: string; disabled?: boolean; }[]","optional":false,"doc":""},{"name":"value","type":"string | string[]","optional":true,"doc":""},{"name":"defaultValue","type":"string | string[]","optional":true,"doc":""},{"name":"onChange","type":"(v: string | string[] | null) => void","optional":true,"doc":""},{"name":"multiple","type":"boolean","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ColorField":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"against","type":"string | false","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MapView":[{"name":"points","type":"MapPoint[]","optional":true,"doc":""},{"name":"bounds","type":"{ n: number; s: number; w: number; e: number }","optional":true,"doc":""},{"name":"background","type":"ReactNode","optional":true,"doc":""},{"name":"bubbles","type":"boolean","optional":true,"doc":""},{"name":"selected","type":"MapPoint[\"id\"] | null","optional":true,"doc":""},{"name":"onSelect","type":"(id: MapPoint[\"id\"] | null) => void","optional":true,"doc":""},{"name":"renderPopup","type":"(p: MapPoint) => ReactNode","optional":true,"doc":""},{"name":"legend","type":"ReactNode","optional":true,"doc":""},{"name":"controls","type":"ReactNode","optional":true,"doc":""},{"name":"height","type":"number | string","optional":true,"doc":"Height in px (default 320) or any CSS length, e.g. \"100%\" to fill a FloatingLayout."},{"name":"label","type":"string","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: the legend, controls and popup float as glass over the map (for a live map in `background`)."},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LiveFeed":[{"name":"items","type":"LiveFeedItem[]","optional":false,"doc":""},{"name":"types","type":"{ value: string; label: string }[]","optional":true,"doc":""},{"name":"paused","type":"boolean","optional":true,"doc":""},{"name":"onPause","type":"(paused: boolean) => void","optional":true,"doc":""},{"name":"maxHeight","type":"number","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RichTextEditor":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(html: string) => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"variables","type":"{ key: string; label: string }[]","optional":true,"doc":""},{"name":"maxLength","type":"number","optional":true,"doc":""},{"name":"minHeight","type":"number","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EmailPreview":[{"name":"from","type":"string","optional":true,"doc":""},{"name":"fromName","type":"string","optional":true,"doc":""},{"name":"subject","type":"string","optional":true,"doc":""},{"name":"preheader","type":"string","optional":true,"doc":""},{"name":"html","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"device","type":"\"desktop\" | \"mobile\"","optional":true,"doc":""},{"name":"onDevice","type":"(d: string) => void","optional":true,"doc":""},{"name":"checks","type":"{ status: Status; label: ReactNode }[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SearchAdPreview":[{"name":"name","type":"string","optional":true,"doc":""},{"name":"domain","type":"string","optional":false,"doc":""},{"name":"paths","type":"string[]","optional":true,"doc":""},{"name":"headlines","type":"string[]","optional":true,"doc":""},{"name":"descriptions","type":"string[]","optional":true,"doc":""},{"name":"sitelinks","type":"string[]","optional":true,"doc":""},{"name":"device","type":"\"mobile\" | \"desktop\"","optional":true,"doc":""},{"name":"onDevice","type":"(d: string) => void","optional":true,"doc":""},{"name":"sponsoredLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BalanceCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"sub","type":"ReactNode","optional":true,"doc":""},{"name":"logo","type":"ReactNode","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"balance","type":"number","optional":false,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"perDay","type":"number","optional":true,"doc":""},{"name":"low","type":"number","optional":true,"doc":""},{"name":"spark","type":"number[]","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"onTopUp","type":"() => void","optional":true,"doc":""},{"name":"topUpLabel","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ServiceMonitor":[{"name":"items","type":"{ id?: string; name: string; status: Status; statusLabel?: string; latency?: number | null; errorRate?: number | null; checked?: string; history?: (Status | \"none\")[]; }[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RunHistory":[{"name":"items","type":"{ id: string | number; name: string; status: Status; started?: string; duration?: string; by?: string; counts?: { value: number; label: string; tone?: Tone }[]; detail?: ReactNode; }[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"WebhookEndpoint":[{"name":"url","type":"string","optional":false,"doc":""},{"name":"events","type":"string[]","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"successRate","type":"number","optional":true,"doc":""},{"name":"enabled","type":"boolean","optional":true,"doc":""},{"name":"onToggle","type":"() => void","optional":true,"doc":""},{"name":"secret","type":"ReactNode","optional":true,"doc":""},{"name":"deliveries","type":"{ id: string | number; code?: number; event: string; time?: string; ms?: number }[]","optional":true,"doc":""},{"name":"onRetry","type":"(d: NonNullable<WebhookEndpointProps[\"deliveries\"]>[number]) => void","optional":true,"doc":""},{"name":"menu","type":"MenuProps[\"items\"]","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BackupList":[{"name":"items","type":"{ id: string | number; name?: string; time?: string; kind?: string; where?: string; retention?: string; size?: string; status?: Status; }[]","optional":false,"doc":""},{"name":"onDownload","type":"(b: NonNullable<BackupListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"onRestore","type":"(b: NonNullable<BackupListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BlockList":[{"name":"items","type":"{ id: string | number; value: string; kind?: string; reason?: string; by?: string; until?: string; }[]","optional":false,"doc":""},{"name":"onAdd","type":"() => void","optional":true,"doc":""},{"name":"onRemove","type":"(b: NonNullable<BlockListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"addLabel","type":"string","optional":true,"doc":""},{"name":"removeLabel","type":"string","optional":true,"doc":""},{"name":"searchPlaceholder","type":"string","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ReviewCard":[{"name":"author","type":"string","optional":false,"doc":""},{"name":"verified","type":"boolean","optional":true,"doc":""},{"name":"product","type":"string","optional":true,"doc":""},{"name":"date","type":"string","optional":true,"doc":""},{"name":"rating","type":"number","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"string","optional":false,"doc":""},{"name":"state","type":"\"pending\" | \"published\" | \"hidden\" | \"flagged\"","optional":true,"doc":""},{"name":"stateLabel","type":"string","optional":true,"doc":""},{"name":"highlight","type":"string[]","optional":true,"doc":""},{"name":"reply","type":"string","optional":true,"doc":""},{"name":"replyLabel","type":"string","optional":true,"doc":""},{"name":"onPublish","type":"() => void","optional":true,"doc":""},{"name":"onHide","type":"() => void","optional":true,"doc":""},{"name":"onReply","type":"(text: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CouponCard":[{"name":"code","type":"string","optional":false,"doc":""},{"name":"kind","type":"\"percent\" | \"fixed\"","optional":true,"doc":""},{"name":"value","type":"number","optional":false,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"used","type":"number","optional":true,"doc":""},{"name":"limit","type":"number","optional":true,"doc":""},{"name":"until","type":"string","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"revenue","type":"number","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"onCopy","type":"() => void","optional":true,"doc":""},{"name":"menu","type":"MenuProps[\"items\"]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LocaleField":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"locales","type":"{ code: string; label?: string; flag?: string }[]","optional":false,"doc":""},{"name":"value","type":"Record<string, string>","optional":true,"doc":""},{"name":"defaultValue","type":"Record<string, string>","optional":true,"doc":""},{"name":"onChange","type":"(v: Record<string, string>) => void","optional":true,"doc":""},{"name":"source","type":"string","optional":true,"doc":""},{"name":"active","type":"string","optional":true,"doc":""},{"name":"multiline","type":"boolean","optional":true,"doc":""},{"name":"rows","type":"number","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"onTranslate","type":"(text: string, to: string) => string | Promise<string>","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LocaleSwitcher":[{"name":"languages","type":"{ code: string; label: string; short?: string; flag?: string }[]","optional":true,"doc":""},{"name":"language","type":"string","optional":true,"doc":""},{"name":"onLanguage","type":"(code: string) => void","optional":true,"doc":""},{"name":"currencies","type":"string[]","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"onCurrency","type":"(c: string) => void","optional":true,"doc":""},{"name":"variant","type":"\"frame\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"WorkspaceSwitcher":[{"name":"items","type":"{ id: string; name: string; sub?: string; tint?: Tint; logo?: ReactNode }[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(id: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"variant","type":"\"frame\"","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ExportDialog":[{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"count","type":"number","optional":true,"doc":""},{"name":"formats","type":"string[]","optional":true,"doc":""},{"name":"format","type":"string","optional":true,"doc":""},{"name":"columns","type":"{ key: string; label: string; default?: boolean }[]","optional":true,"doc":""},{"name":"ranges","type":"boolean","optional":true,"doc":""},{"name":"range","type":"{ from: Date | null; to: Date | null } | null","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"onExport","type":"( o: { format: string; columns: string[]; range: DateRange | null }, progress: (pct: number) => void, ) => void | Promise<void>","optional":true,"doc":""},{"name":"onDownload","type":"() => void","optional":true,"doc":""},{"name":"download","type":"ReactNode","optional":true,"doc":""},{"name":"doneText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TwoFactorSetup":[{"name":"qr","type":"ReactNode","optional":true,"doc":""},{"name":"secret","type":"string","optional":false,"doc":""},{"name":"onVerify","type":"(code: string) => boolean | Promise<boolean>","optional":true,"doc":""},{"name":"codes","type":"string[]","optional":true,"doc":""},{"name":"onDownload","type":"() => void","optional":true,"doc":""},{"name":"onDone","type":"() => void","optional":true,"doc":""},{"name":"step","type":"0 | 1 | 2","optional":true,"doc":""},{"name":"scanText","type":"string","optional":true,"doc":""},{"name":"codesText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ImageVariants":[{"name":"variants","type":"ImageVariant[]","optional":false,"doc":""},{"name":"selected","type":"string | number","optional":true,"doc":""},{"name":"onSelect","type":"(id: string | number) => void","optional":true,"doc":""},{"name":"alt","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"actions","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Gallery":[{"name":"items","type":"GalleryItem[]","optional":false,"doc":""},{"name":"loading","type":"\"lazy\" | \"eager\"","optional":true,"doc":"Loading of the main image: `\"lazy\"` (default) waits until the gallery is near the viewport;\nuse `\"eager\"` for a gallery at the top of the page. Thumbnails always load lazily."},{"name":"referrerPolicy","type":"ImageReferrerPolicy","optional":true,"doc":"`referrerPolicy` of every image, e.g. `\"no-referrer\"` for photos from other hosts."},{"name":"start","type":"number","optional":true,"doc":""},{"name":"ratio","type":"string","optional":true,"doc":""},{"name":"thumbs","type":"boolean","optional":true,"doc":""},{"name":"fullscreen","type":"boolean","optional":true,"doc":""},{"name":"badge","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BeforeAfter":[{"name":"before","type":"MediaPane","optional":false,"doc":""},{"name":"after","type":"MediaPane","optional":false,"doc":""},{"name":"start","type":"number","optional":true,"doc":""},{"name":"ratio","type":"string","optional":true,"doc":""},{"name":"beforeLabel","type":"string","optional":true,"doc":""},{"name":"afterLabel","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the slider (default \"Compare before and after\")."},{"name":"valueText","type":"(percent: number) => string","optional":true,"doc":"What screen readers hear for a position (0–100, the share of \"before\"). Default:\n\"Before 40%, After 60%\", with your `beforeLabel`/`afterLabel`."},{"name":"loading","type":"\"lazy\" | \"eager\"","optional":true,"doc":"Loading of both images (default `\"lazy\"`)."},{"name":"referrerPolicy","type":"ImageReferrerPolicy","optional":true,"doc":"`referrerPolicy` of both images."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ListingCard":[{"name":"listing","type":"Listing","optional":false,"doc":""},{"name":"layout","type":"\"card\" | \"row\"","optional":true,"doc":""},{"name":"selected","type":"boolean","optional":true,"doc":""},{"name":"onSelect","type":"(l: Listing) => void","optional":true,"doc":""},{"name":"onWish","type":"(l: Listing) => void","optional":true,"doc":""},{"name":"statusOptions","type":"StatusSelectProps[\"options\"]","optional":true,"doc":""},{"name":"onStatus","type":"(l: Listing, status: string) => void","optional":true,"doc":""},{"name":"onOpen","type":"(l: Listing) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ResultsToolbar":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"total","type":"number","optional":false,"doc":""},{"name":"meta","type":"string","optional":true,"doc":""},{"name":"selected","type":"number","optional":true,"doc":""},{"name":"onSelectAll","type":"(all: boolean) => void","optional":true,"doc":""},{"name":"bulkActions","type":"{ label: string; icon?: IconName; variant?: ButtonProps[\"variant\"]; busy?: boolean; busyLabel?: string; onClick?: () => void; }[]","optional":true,"doc":""},{"name":"sortOptions","type":"{ value: string; label: string }[]","optional":true,"doc":""},{"name":"sort","type":"string","optional":true,"doc":""},{"name":"onSort","type":"(v: string) => void","optional":true,"doc":""},{"name":"views","type":"(\"grid\" | \"list\" | \"map\")[]","optional":true,"doc":""},{"name":"view","type":"string","optional":true,"doc":""},{"name":"onView","type":"(v: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SavedSearch":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"criteria","type":"ReactNode[]","optional":true,"doc":""},{"name":"lastRun","type":"string","optional":true,"doc":""},{"name":"frequency","type":"string","optional":true,"doc":""},{"name":"newCount","type":"number","optional":true,"doc":""},{"name":"enabled","type":"boolean","optional":true,"doc":""},{"name":"onToggle","type":"() => void","optional":true,"doc":""},{"name":"onEdit","type":"() => void","optional":true,"doc":""},{"name":"onOpen","type":"() => void","optional":true,"doc":""},{"name":"openLabel","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ValuationCard":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"low","type":"number","optional":false,"doc":""},{"name":"high","type":"number","optional":false,"doc":""},{"name":"asking","type":"number","optional":true,"doc":""},{"name":"perSqm","type":"number","optional":true,"doc":""},{"name":"compare","type":"string","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"confidence","type":"number","optional":true,"doc":""},{"name":"confidenceLabel","type":"string","optional":true,"doc":""},{"name":"facts","type":"KeyValueItem[]","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EnergyClass":[{"name":"value","type":"string","optional":false,"doc":""},{"name":"kwh","type":"number","optional":true,"doc":""},{"name":"kind","type":"string","optional":true,"doc":""},{"name":"scale","type":"string[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatLauncher":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"context","type":"ReactNode","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"onOpenChange","type":"(open: boolean) => void","optional":true,"doc":""},{"name":"unread","type":"number","optional":true,"doc":""},{"name":"headerAction","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"composer","type":"ReactNode","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CookieConsent":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"policy","type":"ReactNode","optional":true,"doc":""},{"name":"categories","type":"{ id: string; label: string; text?: string; required?: boolean; default?: boolean; }[]","optional":true,"doc":""},{"name":"onSave","type":"(choices: Record<string, boolean>) => void","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AnnouncementBar":[{"name":"children","type":"ReactNode","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"icon","type":"IconName | false","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"onDismiss","type":"(() => void) | false","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Accordion":[{"name":"items","type":"AccordionItem[]","optional":false,"doc":""},{"name":"multiple","type":"boolean","optional":true,"doc":"Several sections open at once (default: one at a time)."},{"name":"value","type":"string[]","optional":true,"doc":"Open section ids (controlled)."},{"name":"defaultValue","type":"string[]","optional":true,"doc":""},{"name":"onChange","type":"(open: string[]) => void","optional":true,"doc":""},{"name":"variant","type":"\"cards\" | \"flush\"","optional":true,"doc":"`cards`: each section is a card; `flush`: hairlines only (inside a panel)."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Separator":[{"name":"orientation","type":"\"horizontal\" | \"vertical\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Text in the middle of the line (\"or\")."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AspectRatio":[{"name":"ratio","type":"number","optional":true,"doc":"Width divided by height (default 16 / 9)."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Label":[{"name":"htmlFor","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":"Muted text after the label (\"optional\")."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Toggle":[{"name":"pressed","type":"boolean","optional":true,"doc":""},{"name":"defaultPressed","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(pressed: boolean) => void","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name; required when the toggle shows only an icon."},{"name":"size","type":"\"sm\" | \"md\"","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ToggleGroup":[{"name":"items","type":"(string | Option)[]","optional":false,"doc":""},{"name":"type","type":"\"single\" | \"multiple\"","optional":true,"doc":"`single`: at most one pressed; `multiple`: any number."},{"name":"value","type":"string[]","optional":true,"doc":""},{"name":"defaultValue","type":"string[]","optional":true,"doc":""},{"name":"onChange","type":"(value: string[]) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name of the group."},{"name":"size","type":"\"sm\" | \"md\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ButtonGroup":[{"name":"label","type":"string","optional":true,"doc":"Accessible name of the group."},{"name":"orientation","type":"\"horizontal\" | \"vertical\"","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ScrollArea":[{"name":"height","type":"number | string","optional":true,"doc":"Fixed height of the scrolling box."},{"name":"maxHeight","type":"number | string","optional":true,"doc":""},{"name":"orientation","type":"\"vertical\" | \"horizontal\" | \"both\"","optional":true,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name; the box is a focusable region so it scrolls from the keyboard."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"HoverCard":[{"name":"trigger","type":"ReactNode","optional":false,"doc":"The element that opens the card on hover and keyboard focus."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\"","optional":true,"doc":""},{"name":"align","type":"\"start\" | \"center\" | \"end\"","optional":true,"doc":""},{"name":"openDelay","type":"number","optional":true,"doc":""},{"name":"closeDelay","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Carousel":[{"name":"slides","type":"ReactNode[]","optional":false,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name of the carousel."},{"name":"perView","type":"number","optional":true,"doc":"Slides visible at once on wide screens (1 on phones)."},{"name":"loop","type":"boolean","optional":true,"doc":"Next on the last slide goes back to the first."},{"name":"dots","type":"boolean","optional":true,"doc":"Dots under the slides."},{"name":"index","type":"number","optional":true,"doc":""},{"name":"defaultIndex","type":"number","optional":true,"doc":""},{"name":"onChange","type":"(index: number) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Form":[{"name":"rules","type":"Record<string, FormRule>","optional":true,"doc":"Rules by field `name`. Fields show their message under the input."},{"name":"onSubmit","type":"(values: FormValues) => void","optional":true,"doc":"Called with the values (by `name`) when every rule passes."},{"name":"onInvalid","type":"(errors: Record<string, string>) => void","optional":true,"doc":"Called with the messages when the form is submitted with errors."},{"name":"errors","type":"Record<string, string>","optional":true,"doc":"Messages from the server, by field name (for example after a failed save)."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SelectMenu":[{"name":"options","type":"(string | SelectMenuOption)[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(value: string) => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Menubar":[{"name":"menus","type":"MenubarMenu[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the bar."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"NavigationMenu":[{"name":"items","type":"NavigationMenuItem[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the navigation."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Sidebar":[{"name":"groups","type":"SidebarGroup[]","optional":false,"doc":""},{"name":"current","type":"string","optional":true,"doc":"Id (or label) of the current page."},{"name":"onPick","type":"(id: string) => void","optional":true,"doc":""},{"name":"collapsed","type":"boolean","optional":true,"doc":"Icons only."},{"name":"defaultCollapsed","type":"boolean","optional":true,"doc":""},{"name":"onCollapsedChange","type":"(collapsed: boolean) => void","optional":true,"doc":""},{"name":"header","type":"ReactNode","optional":true,"doc":"Top of the sidebar (brand, workspace switcher)."},{"name":"footer","type":"ReactNode","optional":true,"doc":"Bottom of the sidebar (account, settings)."},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the navigation."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ResizablePanels":[{"name":"panels","type":"ReactNode[]","optional":false,"doc":""},{"name":"defaultSizes","type":"number[]","optional":true,"doc":"Start sizes in percent (default: equal)."},{"name":"min","type":"number","optional":true,"doc":"Smallest size of a panel in percent (default 10)."},{"name":"orientation","type":"\"horizontal\" | \"vertical\"","optional":true,"doc":""},{"name":"storageKey","type":"string","optional":true,"doc":"Remembers the sizes in localStorage under this key."},{"name":"labels","type":"string[]","optional":true,"doc":"Accessible names of the panels, used for the resize handles."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}]},"exports":{"admin":["RangeSlider","BarList","StatusSelect","TilePicker","LiveFeed","MapView","BalanceCard","ServiceMonitor","RunHistory","WebhookEndpoint","BackupList","BlockList","ReviewCard","CouponCard","LocaleField","LocaleSwitcher","WorkspaceSwitcher","ExportDialog","TwoFactorSetup","ColorField"],"agents":["AgentCard","AgentDetails","ChatList","ChatWorkspace","SearchHits","ThinkingLine","FeedbackRating","ActivityFeed","DailyBriefing","BudgetStop","MemoryViewer","OverrideDiff","TriggerList","ScheduleCalendar","OrgChart","VoiceMode"],"app-chrome":["MobileTabBar","PushNotification","ConfigPRSummary","ModuleRegistry"],"approvals":["ApprovalCard","ApprovalInbox"],"building-blocks":["Kbd","KeyValue","Spinner","Collapsible","ContextMenu","SplitPane","ResizablePanels","TreeView","InlineEdit"],"charts":["BarChart","LineChart","Sparkline","Donut","Legend","StackedBarChart","AreaChart","Heatmap","Funnel","Gauge"],"chat":["RichText","CodeBlock","TypingDots","AgentMark","ChatMessage","ToolCall","Reasoning","PromptStarters","ModelPicker","Composer","AgentStatus","RunTrace","UsageMeter"],"commerce":["ProductArt","money","Price","Rating","StockBadge","ProductCard","VariantPicker","CartLine","OrderSummary","CheckoutSteps","OrderTimeline","PlanCard"],"data-display":["CountUp","StatTile","Card","EventCard","Panel","PageHeader","Stagger","Table","ListRow","Timeline","FileRow","KanbanColumn","Progress","ProgressRing"],"data-table":["FilterBar","DataTable"],"developer":["LogView","CodeViewer","SecretField","MoneyInput","SettingsLayout","ShortcutsOverlay","StatCell","UptimeBar","Meter"],"feedback":["Skeleton","Menu","Modal","Toast","EmptyState","Alert","ConfirmDialog","Toaster"],"forms":["FormField","TextField","RadioGroup","Segmented","Combobox","TagInput","Calendar","DatePicker","NumberInput","Slider","OtpInput","FileDrop"],"foundation":["Button","IconButton","Chip","Badge","TintIcon","Avatar","AvatarStack"],"inputs":["Input","Select","Textarea","SearchField","Checkbox","Switch"],"form":["Form","useFieldError"],"select-menu":["SelectMenu"],"mail":["AccountSwitcher","InboxList","MailThread","SignatureEditor","AttachmentPreview"],"media":["ImageVariants","Gallery","BeforeAfter"],"navigation":["Tabs","Breadcrumb","Pagination","Menubar","NavigationMenu","Sidebar"],"operations":["ConnectionCard","ConnectionBanner","ServerCard","DeployPipeline","DnsRecordTable","DomainList","MailAuthStatus","LedgerTable","PnLStatement","ReceiptSuggestion"],"overlays":["Popover","Tooltip"],"previews":["EmailPreview","SearchAdPreview"],"primitives":["Accordion","Separator","AspectRatio","Label","Toggle","ToggleGroup","ButtonGroup","ScrollArea","HoverCard","Carousel"],"real-estate":["ListingCard","ResultsToolbar","SavedSearch","ValuationCard","EnergyClass"],"rich-text-editor":["RichTextEditor"],"shell":["TopBar","SideRail","Dock","FloatingLayout","Ambient","NAV","AppShell"],"site":["ChatLauncher","CookieConsent","AnnouncementBar"],"status":["StatusShape","StatusIndicator"],"system":["StatusScreen","AuthCard"],"workspace":["SubNav","Drawer","CommandPalette","NotificationList","Flow","FileThumb","FileCard","Wizard","PermissionMatrix","InvoicePreview","TransactionRow"]},"utilities":["cx","durationToken","initials","reduced","formatNumber","formatPercent","formatMoney","formatDate","formatLike","parseFormatted","numberSeparators","LOCALE","CURRENCY","WEEK_START","DEFAULT_STRINGS","SHARED_STRINGS","ADMIN_STRINGS","AGENTS_STRINGS","APP_CHROME_STRINGS","APPROVALS_STRINGS","BUILDING_BLOCKS_STRINGS","CHARTS_STRINGS","CHAT_STRINGS","COMMERCE_STRINGS","DATA_DISPLAY_STRINGS","DATA_TABLE_STRINGS","DEVELOPER_STRINGS","FEEDBACK_STRINGS","FORM_STRINGS","FORMS_STRINGS","MAIL_STRINGS","MEDIA_STRINGS","NAVIGATION_STRINGS","OPERATIONS_STRINGS","PREVIEWS_STRINGS","PRIMITIVES_STRINGS","REAL_ESTATE_STRINGS","RICH_TEXT_EDITOR_STRINGS","SELECT_MENU_STRINGS","SHELL_STRINGS","SITE_STRINGS","WORKSPACE_STRINGS","mergeStrings","mergeMessages","UIProvider","useUI","useUIStrings","useStrings","useFormat","formattersFor","Portal","startViewTransition","useMotion","usePresence","leaving","THEME_VARS","THEME_PRESETS","themeCss","themeStyle","parseThemeCss","brandTheme","foregroundFor","FOCUSABLE","useControlled","useId","useKeepInView","useScrollFocus","useMounted","useOutside","useTrap","useWidth","toast","subscribeToasts","getToasts","getServerToasts","ICON_PATHS","ICON_NAMES","Icon"],"icons":["thumbUp","thumbDown","arrowUp","heart","tag","cart","package","truck","plus","minus","check","x","more","search","bell","clock","eye","edit","trash","users","user","home","chart","mail","grid","share","menu","play","calendar","file","card","swap","send","download","book","chevronDown","chevronLeft","chevronRight","arrowUpRight","sparkle","filter","settings","lock","link","paperclip","star","flag","trend","globe","inbox","logout","moon","bot","code","branch","terminal","server","key","database","refresh","pause","archive","reply","shield","help","cpu","zap","folder","image","pin","map","upload","copy","bed","ruler","wallet","ban","list","layers","bold","italic","underline","olist","heading","quote","undo","redo","monitor","phone","expand","sun","cookie","webhook"]}
1
+ {"version":"0.3.1","site":"https://ui.twentyfirst.media","kits":[{"name":"halo","label":"Halo","package":"@twentyfirstmedia/halo","tagline":"Soft, rounded and light-first: big white cards on a warm canvas, five pastel tints and ink as the only accent.","defaultTheme":"system","defaultDensity":"comfortable","tokens":{"color":[{"name":"background","value":{"light":"#f4f3f0","dark":"#0f0f10"},"usage":"Theme: page background behind every panel (shadcn `background`)."},{"name":"foreground","value":{"light":"#141414","dark":"#f2f1ed"},"usage":"Theme: default text and icons (shadcn `foreground`)."},{"name":"card","value":{"light":"#ffffff","dark":"#1a1a1c"},"usage":"Theme: cards, panels, inputs and the workspace sheet (shadcn `card`)."},{"name":"card-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on cards."},{"name":"popover","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: menus, popovers, select lists, command palette, hover cards, dialogs."},{"name":"popover-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on popovers."},{"name":"primary","value":{"light":"#141414","dark":"#f2f1ed"},"usage":"Theme: the brand colour. Primary buttons, checked boxes, switches, selected chips and days, progress, active marks."},{"name":"primary-foreground","value":{"light":"#ffffff","dark":"#141414"},"usage":"Theme: text and icons on `primary`."},{"name":"primary-hover","value":{"light":"{primary}","dark":"{primary}"},"usage":"Theme: `primary` under the pointer."},{"name":"secondary","value":{"light":"#ecebe7","dark":"#232326"},"usage":"Theme: secondary actions. Soft buttons and icon buttons, neutral chips and badges."},{"name":"secondary-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `secondary`."},{"name":"muted","value":{"light":"#ecebe7","dark":"#232326"},"usage":"Theme: quiet fills. Tracks of segmented controls, tabs and progress, search fields, code, placeholders."},{"name":"muted-foreground","value":{"light":"#5f5e5a","dark":"#a9a8a3"},"usage":"Theme: secondary text, captions, metadata."},{"name":"accent","value":{"light":"#ecebe7","dark":"#232326"},"usage":"Theme: highlight under the pointer or keyboard. Menu items, list rows, options, ghost buttons, nav items."},{"name":"accent-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `accent`."},{"name":"destructive","value":{"light":"#8a2b23","dark":"#f2a8a0"},"usage":"Theme: destructive actions and errors. Danger buttons, error text and icons."},{"name":"destructive-foreground","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: text on solid `destructive`."},{"name":"border","value":{"light":"#e5e4df","dark":"#2c2c30"},"usage":"Theme: hairlines and dividers between surfaces."},{"name":"input","value":{"light":"#a8a7a1","dark":"#5a5a60"},"usage":"Theme: borders of fields, checkboxes and radios, and strong lines."},{"name":"ring","value":{"light":"#2f6fe0","dark":"#8fb8ff"},"usage":"Theme: keyboard focus rings."},{"name":"chart-1","value":{"light":"{sky-ink}","dark":"{sky-ink}"},"usage":"Theme: chart series 1 (sky series)."},{"name":"chart-2","value":{"light":"{mint-ink}","dark":"{mint-ink}"},"usage":"Theme: chart series 2 (mint series)."},{"name":"chart-3","value":{"light":"{butter-ink}","dark":"{butter-ink}"},"usage":"Theme: chart series 3 (butter series)."},{"name":"chart-4","value":{"light":"{lilac-ink}","dark":"{lilac-ink}"},"usage":"Theme: chart series 4 (lilac series)."},{"name":"chart-5","value":{"light":"{blush-ink}","dark":"{blush-ink}"},"usage":"Theme: chart series 5 (blush series)."},{"name":"sidebar","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: Sidebar background."},{"name":"sidebar-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: Sidebar text."},{"name":"sidebar-primary","value":{"light":"{primary}","dark":"{primary}"},"usage":"Theme: Sidebar emphasis (current item mark, badges)."},{"name":"sidebar-primary-foreground","value":{"light":"{primary-foreground}","dark":"{primary-foreground}"},"usage":"Theme: text on `sidebar-primary`."},{"name":"sidebar-accent","value":{"light":"{accent}","dark":"{accent}"},"usage":"Theme: Sidebar item under the pointer and the current item."},{"name":"sidebar-accent-foreground","value":{"light":"{accent-foreground}","dark":"{accent-foreground}"},"usage":"Theme: text on `sidebar-accent`."},{"name":"sidebar-border","value":{"light":"{border}","dark":"{border}"},"usage":"Theme: Sidebar edges and dividers."},{"name":"sidebar-ring","value":{"light":"{ring}","dark":"{ring}"},"usage":"Theme: focus rings inside the Sidebar."},{"name":"canvas","value":{"light":"{background}","dark":"{background}"},"usage":"Page background behind every panel and card."},{"name":"surface","value":{"light":"{card}","dark":"{card}"},"usage":"Cards, panels, inputs and the main workspace sheet."},{"name":"surface-sunken","value":{"light":"{muted}","dark":"{muted}"},"usage":"Search fields, segmented-tab tracks, quiet chips and icon-button fills on `surface`."},{"name":"frame","value":{"light":"#121212","dark":"#050505"},"usage":"The dark app frame: top bar, dock and the outer shell around the workspace."},{"name":"on-frame","value":{"light":"#f4f3f0","dark":"#f2f1ed"},"usage":"Text and icons on `frame`."},{"name":"on-frame-muted","value":{"light":"#a3a29d","dark":"#a3a29d"},"usage":"Secondary text and idle nav icons on `frame` (7:1+)."},{"name":"ink","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Headings, numbers and body copy on `surface`, `canvas`, `surface-sunken` and every tint."},{"name":"ink-muted","value":{"light":"{muted-foreground}","dark":"{muted-foreground}"},"usage":"Descriptions, meta lines and labels on `surface` and `canvas` (4.5:1+ in both themes)."},{"name":"ink-subtle","value":{"light":"color-mix(in oklab, {muted-foreground} 91%, {background})","dark":"color-mix(in oklab, {muted-foreground} 84%, {background})"},"usage":"Placeholders, disabled text and the quietest meta; 4.5:1 on surface and surface-sunken in both themes, still clearly lighter than ink-muted."},{"name":"line","value":{"light":"{border}","dark":"{border}"},"usage":"Hairline dividers and table rules; decorative, never the only boundary of a control."},{"name":"line-strong","value":{"light":"{input}","dark":"{input}"},"usage":"Dashed placeholder tiles and input borders (3:1 on `surface`)."},{"name":"sky","value":{"light":"#cfe3fb","dark":"#1e3550"},"usage":"Info tint: stat tiles, event cards, the 'scheduled' state. Text on it is `ink`."},{"name":"sky-ink","value":{"light":"#1f4f8a","dark":"#9cc6f7"},"usage":"Icons and labels on `sky` or as an icon colour on `surface`."},{"name":"mint","value":{"light":"#d6efc3","dark":"#24401a"},"usage":"Success tint: published/live states, positive stat tiles. Text on it is `ink`."},{"name":"mint-ink","value":{"light":"#2f5d16","dark":"#a8db86"},"usage":"Icons and labels on `mint`; success marks on `surface`."},{"name":"lilac","value":{"light":"#e9cdf4","dark":"#3d2547"},"usage":"Highlight tint: the one featured card in a grid, lesson events. Text on it is `ink`."},{"name":"lilac-ink","value":{"light":"#6a2a82","dark":"#dba6ee"},"usage":"Icons and labels on `lilac`."},{"name":"butter","value":{"light":"#fbe7aa","dark":"#4a3a10"},"usage":"Money and attention tint: revenue tiles, task events, pending states. Text on it is `ink`."},{"name":"butter-ink","value":{"light":"#6b4d00","dark":"#f3d374"},"usage":"Icons and labels on `butter`."},{"name":"blush","value":{"light":"#f8d6d2","dark":"#4a2320"},"usage":"Danger and support tint: errors, tickets, destructive confirms. Text on it is `ink`."},{"name":"blush-ink","value":{"light":"#8a2b23","dark":"#f2a8a0"},"usage":"Icons and labels on `blush`; destructive text on `surface`."},{"name":"focus","value":{"light":"{ring}","dark":"{ring}"},"usage":"2px focus ring on every interactive element (3:1+ on `surface`, `canvas` and every tint)."},{"name":"count","value":{"light":"#d6efc3","dark":"#d6efc3"},"usage":"Count pills on nav items and tabs (unread, open approvals)."},{"name":"on-count","value":{"light":"#141414","dark":"#141414"},"usage":"Text on `count`."},{"name":"on-frame-success","value":{"light":"#a8db86","dark":"#a8db86"},"usage":"Success marks on frame: the brand dot, ok lines in logs."},{"name":"on-frame-danger","value":{"light":"#f2a8a0","dark":"#f2a8a0"},"usage":"Error lines in logs and other danger marks on frame."},{"name":"scrim","value":{"light":"rgba(10, 10, 10, 0.32)","dark":"rgba(10, 10, 10, 0.32)"},"usage":"Backdrop behind drawers."},{"name":"scrim-strong","value":{"light":"rgba(10, 10, 10, 0.38)","dark":"rgba(10, 10, 10, 0.38)"},"usage":"Backdrop behind modals and the command palette."},{"name":"glass","value":{"light":"color-mix(in srgb, {card} 72%, transparent)","dark":"color-mix(in srgb, {card} 70%, transparent)"},"usage":"Glass surfaces: panels, cards, docks and map overlays floating over content. Opaque enough that body text stays at 4.5:1 or more over any backdrop."},{"name":"glass-strong","value":{"light":"color-mix(in srgb, {card} 86%, transparent)","dark":"color-mix(in srgb, {card} 86%, transparent)"},"usage":"Glass for text-heavy floating layers: menus, popovers, dialogs, drawers."},{"name":"glass-control","value":{"light":"color-mix(in srgb, {card} 62%, transparent)","dark":"color-mix(in srgb, {foreground} 10%, transparent)"},"usage":"Inputs, secondary buttons and chips on glass (replaces surface inside a glass surface)."},{"name":"glass-well","value":{"light":"color-mix(in srgb, {foreground} 6%, transparent)","dark":"color-mix(in srgb, {foreground} 6%, transparent)"},"usage":"Tab tracks, badges, search fields and nested cards on glass (replaces surface-sunken)."},{"name":"glass-hover","value":{"light":"color-mix(in srgb, {foreground} 8%, transparent)","dark":"color-mix(in srgb, {foreground} 12%, transparent)"},"usage":"Hover and selection highlight on glass (replaces accent)."},{"name":"glass-border","value":{"light":"rgba(255, 255, 255, 0.6)","dark":"rgba(255, 255, 255, 0.1)"},"usage":"The edge of a glass surface."},{"name":"glass-highlight","value":{"light":"rgba(255, 255, 255, 0.95)","dark":"rgba(255, 255, 255, 0.18)"},"usage":"Light catching the top edge of a glass surface."},{"name":"glass-sheen","value":{"light":"rgba(255, 255, 255, 0.5)","dark":"rgba(255, 255, 255, 0.05)"},"usage":"Soft light falling from the top of a glass surface."},{"name":"ambient-base","value":{"light":"{background}","dark":"{background}"},"usage":"Ground of the Ambient background, under the colour fields."},{"name":"ambient-1","value":{"light":"color-mix(in oklab, {sky} 72%, {sky-ink})","dark":"{sky}"},"usage":"First colour field of the Ambient gradient."},{"name":"ambient-2","value":{"light":"color-mix(in oklab, {mint} 72%, {mint-ink})","dark":"{mint}"},"usage":"Second colour field of the Ambient gradient."},{"name":"ambient-3","value":{"light":"color-mix(in oklab, {lilac} 72%, {lilac-ink})","dark":"{lilac}"},"usage":"Third colour field of the Ambient gradient."},{"name":"ambient-4","value":{"light":"color-mix(in oklab, {butter} 72%, {butter-ink})","dark":"{butter}"},"usage":"Fourth colour field of the Ambient gradient."},{"name":"ambient-scrim","value":{"light":"rgba(255, 255, 255, 0.28)","dark":"rgba(10, 10, 10, 0.45)"},"usage":"Softens a photo or video under glass so the page keeps its calm."},{"name":"bg-000","value":{"light":"{canvas}","dark":"{canvas}"},"usage":"Canvas: app background behind panels and grids."},{"name":"bg-100","value":{"light":"{surface}","dark":"{surface}"},"usage":"Surface: cards, panels, cells, dialogs."},{"name":"bg-200","value":{"light":"{surface-sunken}","dark":"{surface-sunken}"},"usage":"Raised or sunken fill: hover, tracks, kbd keys, skeletons."},{"name":"bg-300","value":{"light":"{line}","dark":"{line}"},"usage":"Selected fill: active nav item, pressed segment."},{"name":"fg-100","value":{"light":"{ink}","dark":"{ink}"},"usage":"Primary text and icons."},{"name":"fg-200","value":{"light":"{ink-muted}","dark":"{ink-muted}"},"usage":"Secondary text: descriptions, inactive nav."},{"name":"fg-300","value":{"light":"{ink-muted}","dark":"{ink-muted}"},"usage":"Muted text: meta, timestamps, column headers."},{"name":"fg-400","value":{"light":"{ink-subtle}","dark":"{ink-subtle}"},"usage":"Disabled text and placeholders."},{"name":"line-100","value":{"light":"{line}","dark":"{line}"},"usage":"Faint hairline between rows."},{"name":"line-200","value":{"light":"{line}","dark":"{line}"},"usage":"Structural hairline between cells and around overlays."},{"name":"line-300","value":{"light":"{line-strong}","dark":"{line-strong}"},"usage":"Control borders (3:1)."},{"name":"chart-grid","value":{"light":"{line}","dark":"{line}"},"usage":"Chart gridlines."},{"name":"chart-sky","value":{"light":"{sky}","dark":"{sky-ink}"},"usage":"Chart bars and segments tinted sky: the tint in light, its -ink partner in dark."},{"name":"chart-mint","value":{"light":"{mint}","dark":"{mint-ink}"},"usage":"Chart bars and segments tinted mint: the tint in light, its -ink partner in dark."},{"name":"chart-lilac","value":{"light":"{lilac}","dark":"{lilac-ink}"},"usage":"Chart bars and segments tinted lilac: the tint in light, its -ink partner in dark."},{"name":"chart-butter","value":{"light":"{butter}","dark":"{butter-ink}"},"usage":"Chart bars and segments tinted butter: the tint in light, its -ink partner in dark."},{"name":"chart-blush","value":{"light":"{blush}","dark":"{blush-ink}"},"usage":"Chart bars and segments tinted blush: the tint in light, its -ink partner in dark."},{"name":"ok","value":{"light":"#2f5d16","dark":"#a8db86"},"usage":"Status ok: done, online, valid (dot). Alias of `mint-ink`."},{"name":"ok-bg","value":{"light":"#d6efc3","dark":"#24401a"},"usage":"Fill behind ok. Alias of `mint`."},{"name":"info","value":{"light":"#1f4f8a","dark":"#9cc6f7"},"usage":"Status running: working, syncing (pulsing dot). Alias of `sky-ink`."},{"name":"info-bg","value":{"light":"#cfe3fb","dark":"#1e3550"},"usage":"Fill behind info. Alias of `sky`."},{"name":"warn","value":{"light":"#6b4d00","dark":"#f3d374"},"usage":"Status warn: paused, expiring (triangle). Alias of `butter-ink`."},{"name":"warn-bg","value":{"light":"#fbe7aa","dark":"#4a3a10"},"usage":"Fill behind warn. Alias of `butter`."},{"name":"danger","value":{"light":"{destructive}","dark":"{destructive}"},"usage":"Status danger: failed, offline (square). Alias of `blush-ink`."},{"name":"danger-bg","value":{"light":"#f8d6d2","dark":"#4a2320"},"usage":"Fill behind danger. Alias of `blush`."},{"name":"signal","value":{"light":"#6a2a82","dark":"#dba6ee"},"usage":"Status signal: waits for a human (diamond). The only state that may draw attention unasked. Alias of `lilac-ink`."},{"name":"signal-bg","value":{"light":"#e9cdf4","dark":"#3d2547"},"usage":"Fill behind signal, e.g. the approval header. Alias of `lilac`."},{"name":"idle","value":{"light":"#5f5e5a","dark":"#a9a8a3"},"usage":"Status idle: ready, inactive (ring). Alias of `ink-muted`."},{"name":"diff-add","value":{"light":"#2f5d16","dark":"#a8db86"},"usage":"Added lines. Alias of `mint-ink`."},{"name":"diff-add-bg","value":{"light":"#d6efc3","dark":"#24401a"},"usage":"Fill of added lines. Alias of `mint`."},{"name":"diff-del","value":{"light":"#8a2b23","dark":"#f2a8a0"},"usage":"Removed lines. Alias of `blush-ink`."},{"name":"diff-del-bg","value":{"light":"#f8d6d2","dark":"#4a2320"},"usage":"Fill of removed lines. Alias of `blush`."},{"name":"paper","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Document previews (invoices, PDFs, mails) stay white in both themes."},{"name":"paper-ink","value":{"light":"#141414","dark":"#141414"},"usage":"Text on `paper`."},{"name":"frame-hover","value":{"light":"rgba(255, 255, 255, 0.08)","dark":"rgba(255, 255, 255, 0.08)"},"usage":"Hover fill on the dark frame (top bar, dock, code blocks). Same in both themes."},{"name":"frame-active","value":{"light":"rgba(255, 255, 255, 0.12)","dark":"rgba(255, 255, 255, 0.12)"},"usage":"Active or selected fill on the dark frame. Same in both themes."},{"name":"frame-strong","value":{"light":"rgba(255, 255, 255, 0.18)","dark":"rgba(255, 255, 255, 0.18)"},"usage":"Strong fill or divider on the dark frame. Same in both themes."},{"name":"on-frame-strong","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Highlighted text on the frame (log highlights). Same in both themes."},{"name":"on-frame-warn","value":{"light":"#f3d374","dark":"#f3d374"},"usage":"Warnings and numbers in logs and code on the frame. Same in both themes."},{"name":"on-frame-signal","value":{"light":"#d4b8f0","dark":"#d4b8f0"},"usage":"Keywords in code on the frame. Same in both themes."},{"name":"ink-hover","value":{"light":"rgba(0, 0, 0, 0.06)","dark":"rgba(0, 0, 0, 0.06)"},"usage":"Hover fill on tinted surfaces such as alerts. Same in both themes."},{"name":"hairline","value":{"light":"rgba(0, 0, 0, 0.12)","dark":"rgba(0, 0, 0, 0.12)"},"usage":"Thin outline around swatches and colour wells. Same in both themes."},{"name":"media-scrim","value":{"light":"rgba(10, 10, 10, 0.6)","dark":"rgba(10, 10, 10, 0.6)"},"usage":"Labels and counters on top of photos. Same in both themes."},{"name":"media-ink","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Text and lines on top of photos. Same in both themes."},{"name":"media-shadow","value":{"light":"rgba(0, 0, 0, 0.3)","dark":"rgba(0, 0, 0, 0.3)"},"usage":"Shadow behind lines drawn on photos. Same in both themes."},{"name":"lightbox","value":{"light":"rgba(8, 8, 8, 0.92)","dark":"rgba(8, 8, 8, 0.92)"},"usage":"Full-screen image viewer background. Same in both themes."},{"name":"lightbox-ink","value":{"light":"rgba(255, 255, 255, 0.8)","dark":"rgba(255, 255, 255, 0.8)"},"usage":"Captions in the image viewer. Same in both themes."},{"name":"paper-muted","value":{"light":"#706f6a","dark":"#706f6a"},"usage":"Secondary text on paper (4.5:1 on white)."},{"name":"paper-subtle","value":{"light":"#6b6a65","dark":"#6b6a65"},"usage":"Small print on `paper`. Same in both themes."},{"name":"serp-ink","value":{"light":"#202124","dark":"#202124"},"usage":"Search ad preview: text. Same in both themes."},{"name":"serp-muted","value":{"light":"#4d5156","dark":"#4d5156"},"usage":"Search ad preview: description and URL. Same in both themes."},{"name":"serp-link","value":{"light":"#1a0dab","dark":"#1a0dab"},"usage":"Search ad preview: headline link. Same in both themes."},{"name":"serp-chip","value":{"light":"#f1f3f4","dark":"#f1f3f4"},"usage":"Search ad preview: favicon chip. Same in both themes."}],"shadow":[{"name":"shadow-card","value":{"light":"0 1px 2px rgba(20,20,20,0.04), 0 6px 20px rgba(20,20,20,0.05)","dark":"0 1px 2px rgba(0,0,0,0.4)"},"usage":"Resting cards on `canvas`."},{"name":"shadow-lift","value":{"light":"0 2px 4px rgba(20,20,20,0.06), 0 14px 32px rgba(20,20,20,0.10)","dark":"0 2px 6px rgba(0,0,0,0.5), 0 14px 32px rgba(0,0,0,0.45)"},"usage":"Hovered card, open menu."},{"name":"shadow-float","value":{"light":"0 16px 40px rgba(0,0,0,0.22)","dark":"0 16px 40px rgba(0,0,0,0.6)"},"usage":"The floating dock, toasts, menus and modals."},{"name":"shadow-overlay","value":{"light":"{shadow-float}","dark":"{shadow-float}"},"usage":"Floating layers: menus, popovers, dialogs, toasts."},{"name":"shadow-glass","value":{"light":"0 1px 2px rgba(20, 20, 40, 0.06), 0 12px 40px rgba(20, 20, 40, 0.14)","dark":"0 1px 2px rgba(0, 0, 0, 0.3), 0 16px 48px rgba(0, 0, 0, 0.45)"},"usage":"Shadow under glass surfaces."}],"component":[{"name":"button-radius","value":"{radius-pill}","usage":"Corners of buttons (Button, IconButton). One knob for a theme's button shape."},{"name":"field-radius","value":"{radius-pill}","usage":"Corners of inputs and selects."},{"name":"badge-radius","value":"{radius-pill}","usage":"Corners of badges and chips."},{"name":"panel-bg","value":"{surface}","usage":"Background of a panel: a cell of the hairline grid, a section, a side column."},{"name":"panel-border","value":"transparent","usage":"Border colour of a panel standing on its own; transparent where panels float."},{"name":"panel-radius","value":"{radius-lg}","usage":"Corners of a panel. Pro: square cells of the hairline grid."},{"name":"panel-shadow","value":"{shadow-card}","usage":"Resting shadow of a panel. Pro: none, the hairlines divide."},{"name":"panel-pad","value":"{space-6}","usage":"Inner padding of a panel."},{"name":"panel-gap","value":"{space-4}","usage":"Gap between panels in a grid: the hairline in Pro, air between cards in Halo."},{"name":"glass-filter","value":{"light":"blur(24px) saturate(180%) brightness(1.05)","dark":"blur(24px) saturate(160%) brightness(0.85)"},"usage":"backdrop-filter of glass surfaces: blur, colour lift and a nudge towards the theme's side."},{"name":"glass-tint","value":"62%","usage":"How strongly a tint (StatTile, tinted Card) colours the glass it sits on."}],"space":[{"name":"space-0-5","value":"2px","usage":"Icon-to-badge nudges, hairline offsets."},{"name":"space-1","value":"4px","usage":"Tight pairs (label + value); flush-panel row gap. Micro 2/6 only inside badges and tags."},{"name":"space-2","value":"8px","usage":"Inline gap: buttons in a group, icon-to-label; flush panel inset."},{"name":"space-3","value":"12px","usage":"Gap between stacked lines inside a card."},{"name":"space-4","value":"16px","usage":"Grid gap between cards; compact tile inset; row and cell padding; control padding-x."},{"name":"space-5","value":"20px","usage":"Card inset."},{"name":"space-6","value":"24px","usage":"Panel inset; gap between page sections."},{"name":"space-8","value":"32px","usage":"Workspace sheet padding."},{"name":"space-10","value":"40px","usage":"Outer margin of the app frame on large screens."},{"name":"space-12","value":"48px","usage":"Empty-state padding."},{"name":"space-16","value":"64px","usage":"Login and onboarding vertical rhythm."}],"radius":[{"name":"radius","value":"16px","usage":"Theme: the base corner radius; the scale below derives from it (shadcn `radius`)."},{"name":"radius-sm","value":"calc(var(--radius) * 0.625)","usage":"Inputs inside cards, table row hovers, small tiles."},{"name":"radius-md","value":"var(--radius)","usage":"Stat tiles, event cards, menus."},{"name":"radius-lg","value":"calc(var(--radius) * 1.375)","usage":"Content cards and side panels."},{"name":"radius-xl","value":"calc(var(--radius) * 1.875)","usage":"The workspace sheet and the app frame."},{"name":"radius-pill","value":"999px","usage":"Buttons, chips, badges, tabs, search, icon buttons and the dock."},{"name":"radius-0","value":"0px","usage":"Square corners."},{"name":"radius-1","value":"{radius-sm}","usage":"Small controls and tiles."},{"name":"radius-full","value":"9999px","usage":"Circles: status dots, radios, avatars."}],"motion":[{"name":"dur-fast","value":"120ms","usage":"Press, hover colour changes, chip toggles."},{"name":"dur-base","value":"220ms","usage":"Tab indicator slide, menu open, card lift."},{"name":"dur-enter","value":"520ms","usage":"Entrance of cards, tiles, panels and rows."},{"name":"dur-slow","value":"900ms","usage":"Number count-up, chart bars growing, lines drawing, progress fills."},{"name":"stagger-step","value":"55ms","usage":"Delay added per item in a staggered entrance (cards, rows, bars)."},{"name":"dur-exit","value":"160ms","usage":"Overlays leaving: menus, popovers, dialogs, drawers, toasts."},{"name":"motion-time","value":"1","usage":"Multiplier for choreography delays (when the dock, side panels and chart data come in)."},{"name":"motion-travel","value":"1","usage":"Multiplier for how far things travel when they come in."},{"name":"motion-blur","value":"0px","usage":"Blur things come out of as they rise (expressive Halo only)."},{"name":"ease-out","value":"cubic-bezier(0.22, 1, 0.36, 1)","usage":"Entrances, count-ups and draw-ins: fast start, soft landing."},{"name":"ease-spring","value":"cubic-bezier(0.34, 1.56, 0.64, 1)","usage":"Small playful overshoot: dock, toast, modal, badge pop."},{"name":"ease-in-out","value":"cubic-bezier(0.65, 0, 0.35, 1)","usage":"Things that move between two resting places: tab indicator, switch knob."},{"name":"duration-fast","value":"{dur-fast}","usage":"Hover and press feedback."},{"name":"duration-base","value":"{dur-base}","usage":"Overlays entering."},{"name":"ease-standard","value":"{ease-out}","usage":"Default curve."}],"size":[{"name":"control-sm","value":"32px","usage":"Small buttons and inputs."},{"name":"control-md","value":"40px","usage":"Buttons, inputs, selects and search."},{"name":"control-lg","value":"48px","usage":"Large controls."},{"name":"row-dense","value":"40px","usage":"Row height in compact density."},{"name":"row-comfy","value":"52px","usage":"Row height in comfortable density."},{"name":"control-dense","value":"{control-sm}","usage":"Control height in compact density."},{"name":"control-comfy","value":"{control-md}","usage":"Control height in comfortable density."},{"name":"topbar-h","value":"60px","usage":"Top bar height."},{"name":"sidebar-w","value":"44px","usage":"Side rail width."},{"name":"sidebar-w-collapsed","value":"44px","usage":"Side rail width, collapsed."},{"name":"subnav-w","value":"208px","usage":"SubNav width."},{"name":"side-panel-w","value":"304px","usage":"Side panel width."},{"name":"icon-sm","value":"14px","usage":"Icons in chips, badges and event bars."},{"name":"icon-md","value":"16px","usage":"Default icon size."},{"name":"icon-lg","value":"20px","usage":"Icons in navigation."},{"name":"line-width","value":"1px","usage":"Borders and rules."}]}},{"name":"pro","label":"Pro","package":"@twentyfirstmedia/pro","tagline":"Sharp, dense and dark-first: monochrome cells on a hairline grid, square corners, Geist, and colour only for state.","defaultTheme":"dark","defaultDensity":"compact","tokens":{"color":[{"name":"background","value":{"light":"#f6f6f7","dark":"#09090b"},"usage":"Theme: page background behind every panel (shadcn `background`)."},{"name":"foreground","value":{"light":"#111114","dark":"#ededf0"},"usage":"Theme: default text and icons (shadcn `foreground`)."},{"name":"card","value":{"light":"#ffffff","dark":"#0e0e11"},"usage":"Theme: cards, panels, inputs and the workspace sheet (shadcn `card`)."},{"name":"card-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on cards."},{"name":"popover","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: menus, popovers, select lists, command palette, hover cards, dialogs."},{"name":"popover-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on popovers."},{"name":"primary","value":{"light":"#111114","dark":"#ededf0"},"usage":"Theme: the brand colour. Primary buttons, checked boxes, switches, selected chips and days, progress, active marks."},{"name":"primary-foreground","value":{"light":"#ffffff","dark":"#09090b"},"usage":"Theme: text and icons on `primary`."},{"name":"primary-hover","value":{"light":"color-mix(in oklab, {primary} 70%, #000000)","dark":"color-mix(in oklab, {primary} 70%, #ffffff)"},"usage":"Theme: `primary` under the pointer."},{"name":"secondary","value":{"light":"#f0f0f2","dark":"#16161a"},"usage":"Theme: secondary actions. Soft buttons and icon buttons, neutral chips and badges."},{"name":"secondary-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `secondary`."},{"name":"muted","value":{"light":"#f0f0f2","dark":"#16161a"},"usage":"Theme: quiet fills. Tracks of segmented controls, tabs and progress, search fields, code, placeholders."},{"name":"muted-foreground","value":{"light":"#4f4f58","dark":"#a6a6b0"},"usage":"Theme: secondary text, captions, metadata."},{"name":"accent","value":{"light":"#f0f0f2","dark":"#16161a"},"usage":"Theme: highlight under the pointer or keyboard. Menu items, list rows, options, ghost buttons, nav items."},{"name":"accent-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: text on `accent`."},{"name":"destructive","value":{"light":"#b0301f","dark":"#ef6f61"},"usage":"Theme: destructive actions and errors. Danger buttons, error text and icons."},{"name":"destructive-foreground","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: text on solid `destructive`."},{"name":"border","value":{"light":"#dfdfe3","dark":"#26262c"},"usage":"Theme: hairlines and dividers between surfaces."},{"name":"input","value":{"light":"#8a8a94","dark":"#62626d"},"usage":"Theme: borders of fields, checkboxes and radios, and strong lines."},{"name":"ring","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: keyboard focus rings."},{"name":"chart-1","value":{"light":"{sky-ink}","dark":"{sky-ink}"},"usage":"Theme: chart series 1 (sky series)."},{"name":"chart-2","value":{"light":"{mint-ink}","dark":"{mint-ink}"},"usage":"Theme: chart series 2 (mint series)."},{"name":"chart-3","value":{"light":"{butter-ink}","dark":"{butter-ink}"},"usage":"Theme: chart series 3 (butter series)."},{"name":"chart-4","value":{"light":"{lilac-ink}","dark":"{lilac-ink}"},"usage":"Theme: chart series 4 (lilac series)."},{"name":"chart-5","value":{"light":"{blush-ink}","dark":"{blush-ink}"},"usage":"Theme: chart series 5 (blush series)."},{"name":"sidebar","value":{"light":"{card}","dark":"{card}"},"usage":"Theme: Sidebar background."},{"name":"sidebar-foreground","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Theme: Sidebar text."},{"name":"sidebar-primary","value":{"light":"{primary}","dark":"{primary}"},"usage":"Theme: Sidebar emphasis (current item mark, badges)."},{"name":"sidebar-primary-foreground","value":{"light":"{primary-foreground}","dark":"{primary-foreground}"},"usage":"Theme: text on `sidebar-primary`."},{"name":"sidebar-accent","value":{"light":"{accent}","dark":"{accent}"},"usage":"Theme: Sidebar item under the pointer and the current item."},{"name":"sidebar-accent-foreground","value":{"light":"{accent-foreground}","dark":"{accent-foreground}"},"usage":"Theme: text on `sidebar-accent`."},{"name":"sidebar-border","value":{"light":"{border}","dark":"{border}"},"usage":"Theme: Sidebar edges and dividers."},{"name":"sidebar-ring","value":{"light":"{ring}","dark":"{ring}"},"usage":"Theme: focus rings inside the Sidebar."},{"name":"canvas","value":{"light":"{bg-000}","dark":"{bg-000}"},"usage":"Page background behind every panel and card."},{"name":"surface","value":{"light":"{bg-100}","dark":"{bg-100}"},"usage":"Cards, panels, inputs and the main workspace sheet."},{"name":"surface-sunken","value":{"light":"{bg-200}","dark":"{bg-200}"},"usage":"Search fields, segmented-tab tracks, quiet chips and icon-button fills on `surface`."},{"name":"frame","value":{"light":"{bg-100}","dark":"{bg-100}"},"usage":"The dark app frame: top bar, dock and the outer shell around the workspace."},{"name":"on-frame","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Text and icons on `frame`."},{"name":"on-frame-muted","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Secondary text and idle nav icons on `frame` (7:1+)."},{"name":"ink","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Headings, numbers and body copy on `surface`, `canvas`, `surface-sunken` and every tint."},{"name":"ink-muted","value":{"light":"{fg-200}","dark":"{fg-200}"},"usage":"Descriptions, meta lines and labels on `surface` and `canvas` (4.5:1+ in both themes)."},{"name":"ink-subtle","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Placeholders, disabled text and the quietest meta; 4.5:1 on surface and surface-sunken in both themes, still clearly lighter than ink-muted."},{"name":"line","value":{"light":"{line-200}","dark":"{line-200}"},"usage":"Hairline dividers and table rules; decorative, never the only boundary of a control."},{"name":"line-strong","value":{"light":"{line-300}","dark":"{line-300}"},"usage":"Dashed placeholder tiles and input borders (3:1 on `surface`)."},{"name":"sky","value":{"light":"{info-bg}","dark":"{info-bg}"},"usage":"Info tint: stat tiles, event cards, the 'scheduled' state. Text on it is `ink`."},{"name":"sky-ink","value":{"light":"{info}","dark":"{info}"},"usage":"Icons and labels on `sky` or as an icon colour on `surface`."},{"name":"mint","value":{"light":"{ok-bg}","dark":"{ok-bg}"},"usage":"Success tint: published/live states, positive stat tiles. Text on it is `ink`."},{"name":"mint-ink","value":{"light":"{ok}","dark":"{ok}"},"usage":"Icons and labels on `mint`; success marks on `surface`."},{"name":"lilac","value":{"light":"{signal-bg}","dark":"{signal-bg}"},"usage":"Highlight tint: the one featured card in a grid, lesson events. Text on it is `ink`."},{"name":"lilac-ink","value":{"light":"{signal}","dark":"{signal}"},"usage":"Icons and labels on `lilac`."},{"name":"butter","value":{"light":"{warn-bg}","dark":"{warn-bg}"},"usage":"Money and attention tint: revenue tiles, task events, pending states. Text on it is `ink`."},{"name":"butter-ink","value":{"light":"{warn}","dark":"{warn}"},"usage":"Icons and labels on `butter`."},{"name":"blush","value":{"light":"{danger-bg}","dark":"{danger-bg}"},"usage":"Danger and support tint: errors, tickets, destructive confirms. Text on it is `ink`."},{"name":"blush-ink","value":{"light":"{danger}","dark":"{danger}"},"usage":"Icons and labels on `blush`; destructive text on `surface`."},{"name":"focus","value":{"light":"{ring}","dark":"{ring}"},"usage":"Focus ring: 2px solid outline, 1px offset, on every interactive element."},{"name":"count","value":{"light":"{signal}","dark":"{signal}"},"usage":"Count pills on nav items and tabs (unread, open approvals)."},{"name":"on-count","value":{"light":"{bg-000}","dark":"{bg-000}"},"usage":"Text on `count`."},{"name":"on-frame-success","value":{"light":"{ok}","dark":"{ok}"},"usage":"Success marks on frame: the brand dot, ok lines in logs."},{"name":"on-frame-danger","value":{"light":"{danger}","dark":"{danger}"},"usage":"Error lines in logs and other danger marks on frame."},{"name":"scrim","value":{"light":"rgba(17,17,20,0.36)","dark":"rgba(0,0,0,0.62)"},"usage":"Backdrop behind dialogs, sheets and the command palette."},{"name":"scrim-strong","value":{"light":"{scrim}","dark":"{scrim}"},"usage":"Backdrop behind modals and the command palette."},{"name":"glass","value":{"light":"color-mix(in srgb, {card} 74%, transparent)","dark":"color-mix(in srgb, {card} 72%, transparent)"},"usage":"Glass surfaces: panels, cards, docks and map overlays floating over content. Opaque enough that body text stays at 4.5:1 or more over any backdrop."},{"name":"glass-strong","value":{"light":"color-mix(in srgb, {card} 88%, transparent)","dark":"color-mix(in srgb, {card} 88%, transparent)"},"usage":"Glass for text-heavy floating layers: menus, popovers, dialogs, drawers."},{"name":"glass-control","value":{"light":"color-mix(in srgb, {card} 55%, transparent)","dark":"color-mix(in srgb, {foreground} 7%, transparent)"},"usage":"Inputs, secondary buttons and chips on glass (replaces surface inside a glass surface)."},{"name":"glass-well","value":{"light":"color-mix(in srgb, {foreground} 5%, transparent)","dark":"color-mix(in srgb, {foreground} 5%, transparent)"},"usage":"Tab tracks, badges, search fields and nested cards on glass (replaces surface-sunken)."},{"name":"glass-hover","value":{"light":"color-mix(in srgb, {foreground} 7%, transparent)","dark":"color-mix(in srgb, {foreground} 9%, transparent)"},"usage":"Hover and selection highlight on glass (replaces accent)."},{"name":"glass-border","value":{"light":"color-mix(in srgb, {foreground} 12%, transparent)","dark":"color-mix(in srgb, {foreground} 14%, transparent)"},"usage":"The hairline edge of a glass surface."},{"name":"glass-highlight","value":{"light":"rgba(255, 255, 255, 0.7)","dark":"rgba(255, 255, 255, 0.07)"},"usage":"Light catching the top edge of a glass surface."},{"name":"glass-sheen","value":{"light":"transparent","dark":"transparent"},"usage":"Pro glass is flat: no sheen."},{"name":"ambient-base","value":{"light":"{background}","dark":"{background}"},"usage":"Ground of the Ambient background, under the colour fields."},{"name":"ambient-1","value":{"light":"color-mix(in oklab, {chart-1} 22%, {background})","dark":"color-mix(in oklab, {chart-1} 30%, {background})"},"usage":"First colour field of the Ambient gradient (a faint aurora in Pro)."},{"name":"ambient-2","value":{"light":"color-mix(in oklab, {chart-4} 18%, {background})","dark":"color-mix(in oklab, {chart-4} 26%, {background})"},"usage":"Second colour field of the Ambient gradient."},{"name":"ambient-3","value":{"light":"color-mix(in oklab, {chart-2} 16%, {background})","dark":"color-mix(in oklab, {chart-2} 22%, {background})"},"usage":"Third colour field of the Ambient gradient."},{"name":"ambient-4","value":{"light":"color-mix(in oklab, {foreground} 6%, {background})","dark":"color-mix(in oklab, {foreground} 8%, {background})"},"usage":"Fourth colour field of the Ambient gradient."},{"name":"ambient-scrim","value":{"light":"rgba(246, 246, 247, 0.4)","dark":"rgba(9, 9, 11, 0.55)"},"usage":"Softens a photo or video under glass so the page keeps its calm."},{"name":"bg-000","value":{"light":"{background}","dark":"{background}"},"usage":"Canvas: app background, behind hairline grids, inputs on surfaces."},{"name":"bg-100","value":{"light":"{card}","dark":"{card}"},"usage":"Surface: every grid cell, panel, table, sidebar, dialog."},{"name":"bg-200","value":{"light":"{muted}","dark":"{muted}"},"usage":"Raised: row and button hover, kbd keys, skeletons, code blocks."},{"name":"bg-300","value":{"light":"color-mix(in oklab, {foreground} 3%, {muted})","dark":"color-mix(in oklab, {foreground} 5%, {muted})"},"usage":"Selected: active nav item, selected row, pressed segment, open menu trigger."},{"name":"fg-100","value":{"light":"{foreground}","dark":"{foreground}"},"usage":"Primary text and icons on every surface (bg-000 to bg-300)."},{"name":"fg-200","value":{"light":"{muted-foreground}","dark":"{muted-foreground}"},"usage":"Secondary text: descriptions, table cells that are not the row's key, inactive nav items. On bg-000 to bg-300."},{"name":"fg-300","value":{"light":"color-mix(in oklab, {muted-foreground} 92%, {background})","dark":"color-mix(in oklab, {muted-foreground} 90%, {background})"},"usage":"Muted text: meta, timestamps, column headers, eyebrows. On bg-000 to bg-300."},{"name":"fg-400","value":{"light":"color-mix(in oklab, {muted-foreground} 47%, {background})","dark":"color-mix(in oklab, {muted-foreground} 57%, {background})"},"usage":"Disabled text and placeholders only. Below 4.5:1 by design; never for information."},{"name":"line-100","value":{"light":"color-mix(in oklab, {border} 59%, {card})","dark":"color-mix(in oklab, {border} 52%, {card})"},"usage":"Faint hairline: row separators inside tables and lists, background grid."},{"name":"line-200","value":{"light":"{border}","dark":"{border}"},"usage":"Hairline: the structural 1px grid between cells, panel and overlay borders, table header rule. Decorative, not a control boundary."},{"name":"line-300","value":{"light":"{input}","dark":"{input}"},"usage":"Control border (3:1 on bg-000, bg-100, bg-200): inputs, checkboxes, secondary buttons, crosshair marks."},{"name":"chart-grid","value":{"light":"{line-100}","dark":"{line-100}"},"usage":"Chart gridlines."},{"name":"chart-sky","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Chart series tinted sky: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-mint","value":{"light":"color-mix(in oklab, {foreground} 38%, {card})","dark":"color-mix(in oklab, {foreground} 48%, {card})"},"usage":"Chart series tinted mint: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-lilac","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Chart series tinted lilac: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-butter","value":{"light":"{fg-200}","dark":"{fg-200}"},"usage":"Chart series tinted butter: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"chart-blush","value":{"light":"{line-300}","dark":"{line-300}"},"usage":"Chart series tinted blush: a step of the monochrome ramp (charts carry no decorative colour)."},{"name":"ok","value":{"light":"#177447","dark":"#5cc28f"},"usage":"Success / done / healthy: text and status shapes (circle). On bg-000 to bg-300."},{"name":"ok-bg","value":{"light":"rgba(23,116,71,0.09)","dark":"rgba(92,194,143,0.12)"},"usage":"Tint behind ok badges, banners and diff additions."},{"name":"info","value":{"light":"#2856b0","dark":"#7aa7f5"},"usage":"Running / in progress / neutral notices. Shape: pulsing dot."},{"name":"info-bg","value":{"light":"rgba(40,86,176,0.08)","dark":"rgba(122,167,245,0.12)"},"usage":"Tint behind info badges and banners."},{"name":"warn","value":{"light":"#765600","dark":"#d9b44a"},"usage":"Warning: budget at 80 %, expiring domains, degraded checks. Shape: triangle."},{"name":"warn-bg","value":{"light":"rgba(118,86,0,0.09)","dark":"rgba(217,180,74,0.12)"},"usage":"Tint behind warning badges and banners."},{"name":"danger","value":{"light":"{destructive}","dark":"{destructive}"},"usage":"Error, failed run, destructive actions, budget stop. Shape: square."},{"name":"danger-bg","value":{"light":"rgba(176,48,31,0.08)","dark":"rgba(239,111,97,0.12)"},"usage":"Tint behind error badges, banners, diff removals."},{"name":"signal","value":{"light":"#9a4807","dark":"#f2994a"},"usage":"Needs a human: approval waiting, beacons, the approval count. Shape: diamond. The only hue that may draw the eye unprompted."},{"name":"signal-bg","value":{"light":"rgba(154,72,7,0.09)","dark":"rgba(242,153,74,0.14)"},"usage":"Tint behind approval cards' header and signal badges."},{"name":"idle","value":{"light":"{fg-300}","dark":"{fg-300}"},"usage":"Status idle: ready, inactive (ring). Alias of `ink-muted`."},{"name":"diff-add","value":{"light":"{ok}","dark":"{ok}"},"usage":"Added lines in override diffs."},{"name":"diff-add-bg","value":{"light":"{ok-bg}","dark":"{ok-bg}"},"usage":"Fill of added lines. Alias of `mint`."},{"name":"diff-del","value":{"light":"{danger}","dark":"{danger}"},"usage":"Removed lines in override diffs."},{"name":"diff-del-bg","value":{"light":"{danger-bg}","dark":"{danger-bg}"},"usage":"Fill of removed lines. Alias of `blush`."},{"name":"paper","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Document previews (invoices, PDFs): paper is white in both themes."},{"name":"paper-ink","value":{"light":"#111114","dark":"#111114"},"usage":"Text on paper."},{"name":"frame-hover","value":{"light":"{bg-200}","dark":"{bg-200}"},"usage":"Hover fill on the dark frame (top bar, dock, code blocks). Same in both themes."},{"name":"frame-active","value":{"light":"{bg-300}","dark":"{bg-300}"},"usage":"Active or selected fill on the dark frame. Same in both themes."},{"name":"frame-strong","value":{"light":"{bg-300}","dark":"{bg-300}"},"usage":"Strong fill or divider on the dark frame. Same in both themes."},{"name":"on-frame-strong","value":{"light":"{fg-100}","dark":"{fg-100}"},"usage":"Highlighted text on the frame (log highlights). Same in both themes."},{"name":"on-frame-warn","value":{"light":"{warn}","dark":"{warn}"},"usage":"Warnings and numbers in logs and code on the frame. Same in both themes."},{"name":"on-frame-signal","value":{"light":"{signal}","dark":"{signal}"},"usage":"Keywords in code on the frame. Same in both themes."},{"name":"ink-hover","value":{"light":"{bg-200}","dark":"{bg-200}"},"usage":"Hover fill on tinted surfaces such as alerts. Same in both themes."},{"name":"hairline","value":{"light":"{line-200}","dark":"{line-200}"},"usage":"Thin outline around swatches and colour wells. Same in both themes."},{"name":"media-scrim","value":{"light":"rgba(10, 10, 10, 0.6)","dark":"rgba(10, 10, 10, 0.6)"},"usage":"Labels and counters on top of photos. Same in both themes."},{"name":"media-ink","value":{"light":"#ffffff","dark":"#ffffff"},"usage":"Text and lines on top of photos. Same in both themes."},{"name":"media-shadow","value":{"light":"rgba(0, 0, 0, 0.3)","dark":"rgba(0, 0, 0, 0.3)"},"usage":"Shadow behind lines drawn on photos. Same in both themes."},{"name":"lightbox","value":{"light":"rgba(8, 8, 8, 0.92)","dark":"rgba(8, 8, 8, 0.92)"},"usage":"Full-screen image viewer background. Same in both themes."},{"name":"lightbox-ink","value":{"light":"rgba(255, 255, 255, 0.8)","dark":"rgba(255, 255, 255, 0.8)"},"usage":"Captions in the image viewer. Same in both themes."},{"name":"paper-muted","value":{"light":"#706f6a","dark":"#706f6a"},"usage":"Secondary text on paper (4.5:1 on white)."},{"name":"paper-subtle","value":{"light":"#6b6a65","dark":"#6b6a65"},"usage":"Small print on `paper`. Same in both themes."},{"name":"serp-ink","value":{"light":"#202124","dark":"#202124"},"usage":"Search ad preview: text. Same in both themes."},{"name":"serp-muted","value":{"light":"#4d5156","dark":"#4d5156"},"usage":"Search ad preview: description and URL. Same in both themes."},{"name":"serp-link","value":{"light":"#1a0dab","dark":"#1a0dab"},"usage":"Search ad preview: headline link. Same in both themes."},{"name":"serp-chip","value":{"light":"#f1f3f4","dark":"#f1f3f4"},"usage":"Search ad preview: favicon chip. Same in both themes."}],"shadow":[{"name":"shadow-card","value":{"light":"none","dark":"none"},"usage":"Resting cards. Pro: none, hairlines divide."},{"name":"shadow-lift","value":{"light":"none","dark":"none"},"usage":"Hovered card. Pro: none."},{"name":"shadow-float","value":{"light":"0 16px 40px rgba(17,17,20,0.12)","dark":"0 16px 40px rgba(0,0,0,0.55)"},"usage":"Floating layers: dock, toasts, menus and dialogs."},{"name":"shadow-overlay","value":{"light":"0 16px 40px rgba(17,17,20,0.12)","dark":"0 16px 40px rgba(0,0,0,0.55)"},"usage":"Only floating layers: menus, popovers, dialogs, toasts, command palette. Always paired with a line-200 border."},{"name":"shadow-glass","value":{"light":"0 8px 24px rgba(17, 17, 20, 0.1)","dark":"0 12px 32px rgba(0, 0, 0, 0.5)"},"usage":"Shadow under glass surfaces."}],"component":[{"name":"button-radius","value":"{radius-1}","usage":"Corners of buttons (Button, IconButton). One knob for a theme's button shape."},{"name":"field-radius","value":"{radius-1}","usage":"Corners of inputs and selects."},{"name":"badge-radius","value":"{radius-1}","usage":"Corners of badges and chips."},{"name":"panel-bg","value":"{bg-100}","usage":"Background of a panel: a cell of the hairline grid, a section, a side column."},{"name":"panel-border","value":"{line-200}","usage":"Border colour of a panel standing on its own; transparent where panels float."},{"name":"panel-radius","value":"{radius-0}","usage":"Corners of a panel. Pro: square cells of the hairline grid."},{"name":"panel-shadow","value":"none","usage":"Resting shadow of a panel. Pro: none, the hairlines divide."},{"name":"panel-pad","value":"{space-4}","usage":"Inner padding of a panel."},{"name":"panel-gap","value":"1px","usage":"Gap between panels in a grid: the hairline in Pro, air between cards in Halo."},{"name":"glass-filter","value":{"light":"blur(16px) saturate(130%) brightness(1.04)","dark":"blur(16px) saturate(120%) brightness(0.8)"},"usage":"backdrop-filter of glass surfaces: blur, colour lift and a nudge towards the theme's side."},{"name":"glass-tint","value":"40%","usage":"How strongly a tint (StatTile, tinted Card) colours the glass it sits on."}],"space":[{"name":"space-0-5","value":"2px","usage":"Icon-to-badge nudges, hairline offsets."},{"name":"space-1","value":"4px","usage":"Gap inside badges and kbd groups."},{"name":"space-2","value":"8px","usage":"Gap between icon and label, between buttons in a group."},{"name":"space-3","value":"12px","usage":"Compact horizontal cell and control padding."},{"name":"space-4","value":"16px","usage":"Grid cell padding, comfortable control padding, page gutter on mobile."},{"name":"space-5","value":"20px","usage":"Dialog body padding."},{"name":"space-6","value":"24px","usage":"Cell padding on overview grids; background-grid pitch."},{"name":"space-8","value":"32px","usage":"Gap between stacked sections inside a panel."},{"name":"space-10","value":"40px","usage":"Outer margin of the app frame on large screens."},{"name":"space-12","value":"48px","usage":"Empty-state padding."},{"name":"space-16","value":"64px","usage":"Login and onboarding vertical rhythm."}],"radius":[{"name":"radius","value":"2px","usage":"Theme: the base corner radius; Pro is square at 2px, larger values round cards and menus too (shadcn `radius`)."},{"name":"radius-sm","value":"{radius-1}","usage":"Inputs inside cells, row hovers, small tiles."},{"name":"radius-md","value":"calc((var(--radius) - 2px) * 1)","usage":"Stat cells, event cards, menus: square in Pro."},{"name":"radius-lg","value":"calc((var(--radius) - 2px) * 1.375)","usage":"Cards and panels: square cells of the hairline grid."},{"name":"radius-xl","value":"calc((var(--radius) - 2px) * 1.875)","usage":"The workspace sheet and app frame: square in Pro."},{"name":"radius-pill","value":"{radius-1}","usage":"Buttons, chips, badges, tabs and inputs: 2px in Pro."},{"name":"radius-0","value":"0px","usage":"Default for everything structural: cells, panels, tables, dialogs, sheets, cards."},{"name":"radius-1","value":"var(--radius)","usage":"Controls: buttons, inputs, badges, kbd, switches, tooltips."},{"name":"radius-full","value":"9999px","usage":"Only status dots and radio buttons."}],"motion":[{"name":"dur-fast","value":"{duration-fast}","usage":"Press, hover colour changes, chip toggles."},{"name":"dur-base","value":"{duration-base}","usage":"Tab indicator slide, menu open, card lift."},{"name":"dur-enter","value":"{duration-base}","usage":"Entrances: short and quiet in Pro."},{"name":"dur-slow","value":"{duration-base}","usage":"Count-ups, chart draw-ins and progress fills: short in Pro."},{"name":"stagger-step","value":"0ms","usage":"No staggered entrances in Pro."},{"name":"dur-exit","value":"{duration-fast}","usage":"Overlays leaving: menus, popovers, dialogs, drawers, toasts."},{"name":"motion-time","value":"1","usage":"Multiplier for choreography delays (when the dock, side panels and chart data come in)."},{"name":"motion-travel","value":"1","usage":"Multiplier for how far things travel when they come in."},{"name":"motion-blur","value":"0px","usage":"Blur things come out of as they rise (expressive Halo only)."},{"name":"ease-out","value":"{ease-standard}","usage":"Entrances."},{"name":"ease-spring","value":"{ease-standard}","usage":"No overshoot in Pro: the standard curve."},{"name":"ease-in-out","value":"{ease-standard}","usage":"Things moving between two resting places."},{"name":"duration-fast","value":"90ms","usage":"Hover and press feedback."},{"name":"duration-base","value":"140ms","usage":"Menus, popovers, sheets, toasts entering."},{"name":"ease-standard","value":"cubic-bezier(0.2, 0, 0, 1)","usage":"The only easing curve."}],"size":[{"name":"control-sm","value":"24px","usage":"Small buttons in dense toolbars."},{"name":"control-md","value":"{control-dense}","usage":"Default control height (follows density in components)."},{"name":"control-lg","value":"{control-comfy}","usage":"Large controls."},{"name":"row-dense","value":"28px","usage":"Row height for tables, lists and nav items in compact density."},{"name":"row-comfy","value":"36px","usage":"Row height in comfortable density."},{"name":"control-dense","value":"28px","usage":"Button and input height in compact density."},{"name":"control-comfy","value":"34px","usage":"Button and input height in comfortable density."},{"name":"topbar-h","value":"44px","usage":"Top bar height."},{"name":"sidebar-w","value":"232px","usage":"Sidebar width, expanded."},{"name":"sidebar-w-collapsed","value":"52px","usage":"Sidebar width, icon-only."},{"name":"subnav-w","value":"{sidebar-w}","usage":"SubNav width."},{"name":"side-panel-w","value":"320px","usage":"Side panel width."},{"name":"icon-sm","value":"14px","usage":"Icons inside badges and dense table cells."},{"name":"icon-md","value":"16px","usage":"Default Lucide icon size."},{"name":"icon-lg","value":"16px","usage":"Icons in navigation: same as icon-md in Pro."},{"name":"line-width","value":"1px","usage":"Every border and grid line (Pro calls it hairline)."}]}}],"guides":[{"slug":"introduction","title":"Introduction","description":"{{Kit}} is one of the two Twentyfirst UI kits: React components, tokens and complete screens with the same API as {{Other}}.","markdown":"Twentyfirst UI is two React UI kits with one component API.\n\n- **Halo** is soft, rounded and light-first: big white cards on a warm canvas, five pastel tints that carry meaning and ink as the only accent.\n- **Pro** is sharp, dense and dark-first: monochrome cells on a visible hairline grid, square corners, Geist, and colour only for state.\n\nBoth kits have exactly the same components, props, markup, classes and tokens. Only tokens and CSS differ, so an app can load both and switch at runtime with `data-kit`, without changing markup or losing state.\n\n## What is in the box\n\n- {{componentCount}} components on {{componentPages}} pages: foundations, navigation, forms, data, charts, overlays, AI (chat, agents, approvals), commerce, admin, real estate and media.\n- {{patternCount}} patterns: complete screens built only from kit parts.\n- Tokens for colour, type, space, radius, shadow and motion in light and dark, as CSS variables and as a Tailwind v4 preset.\n- A CLI that sets up an app and copies components when you want to own them, and an MCP server so coding agents know every component.\n\n## Principles\n\n- **Mobile first.** Every component works from a 320px screen up: shells collapse, tables scroll, dialogs become sheets and inputs never zoom.\n- **Accessible.** Keyboard paths, focus rings and ARIA follow the WAI-ARIA practices; axe (WCAG 2.1 AA) runs on every example of both kits, light and dark.\n- **Tokens only.** No colour literals in component CSS.\n- **Private.** Fonts are self-hosted, nothing phones home and components make no requests of their own.\n- **English, formatted with Intl.** Built-in text is plain English and overridable through props or `UIProvider messages`; numbers, money, percentages and dates go through `formatNumber`, `formatMoney`, `formatPercent` and `formatDate`, in en-US or the `UIProvider locale` ([Text and locale](/{{kit}}/text/)).\n\n## Quick start\n\n```sh\nnpx @twentyfirstmedia/ui init --kit {{kit}}\n```\n\n```tsx\nimport \"@twentyfirstmedia/{{kit}}/styles.css\";\nimport { UIProvider, Button } from \"@twentyfirstmedia/{{kit}}\";\n\nexport function App() {\n return (\n <UIProvider>\n <Button variant=\"primary\">Save</Button>\n </UIProvider>\n );\n}\n```\n\nNext: [Installation](/{{kit}}/installation/), [CLI](/{{kit}}/cli/), [Theming and tokens](/{{kit}}/theming/), [AI agents and MCP](/{{kit}}/ai/) and [all components](/{{kit}}/components/).\n"},{"slug":"installation","title":"Installation","description":"Install one kit from npm, import its styles once and wrap the app in UIProvider. React 18.3 or 19; works with Next.js, Vite and any React setup.","markdown":"## 1. The kit\n\nThe packages are on npm under the MIT license. Install a kit with the CLI (it also sets up styles, the provider and the Next.js layout) or by hand:\n\n```sh\nnpx @twentyfirstmedia/ui init --kit {{kit}}\n# or\npnpm add @twentyfirstmedia/{{kit}}\n```\n\n## 2. Styles and provider\n\nImport `styles.css` once. It brings the self-hosted fonts, the tokens (light, dark, density) and every component style; no `data-kit` attribute is needed.\n\n```tsx\nimport \"@twentyfirstmedia/{{kit}}/styles.css\";\nimport { UIProvider, Button } from \"@twentyfirstmedia/{{kit}}\";\n\nexport function App() {\n return (\n <UIProvider theme=\"system\">\n <Button variant=\"primary\">Save</Button>\n </UIProvider>\n );\n}\n```\n\n## Tailwind v4\n\nThe kit ships a preset that maps every token to Tailwind and clears the default palette, so utilities can only use kit values.\n\n```css\n@import \"tailwindcss\";\n@import \"@twentyfirstmedia/{{kit}}/tailwind.css\";\n```\n\n## Both kits in one app\n\nImport `@twentyfirstmedia/ui-core/themes.css` instead (both kits, scoped under `[data-kit=\"…\"]`) and switch with `<UIProvider kit=\"pro\">` or the attribute on `<html>`. Markup, props and state stay as they are.\n\n```tsx\nimport \"@twentyfirstmedia/ui-core/themes.css\";\nimport { UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider kit={userPrefersPro ? \"pro\" : \"halo\"}>{children}</UIProvider>;\n```\n\n## Coding agents\n\nGive Claude Code, Cursor or VS Code the kit's docs with the MCP server, see [AI agents and MCP](/{{kit}}/ai/):\n\n```sh\nnpx @twentyfirstmedia/ui mcp init\n```\n"},{"slug":"cli","title":"CLI","description":"npx @twentyfirstmedia/ui sets up a kit, copies components into your app when you want to change them and runs the MCP server for coding agents.","markdown":"## init\n\nInstalls the kit, imports its styles (and the Tailwind preset when the app uses Tailwind), creates `components/ui-root.tsx` with the `UIProvider`, wraps the Next.js layout and sets `data-kit` on `<html>`. Running it again changes nothing.\n\n```sh\nnpx @twentyfirstmedia/ui init --kit {{kit}}\nnpx @twentyfirstmedia/ui init --kit {{kit}} --both # load both kits and switch at runtime\n```\n\n## add\n\nCopies a component, with the private helpers it needs, into `components/ui`. Everything public stays an import from `@twentyfirstmedia/ui-core`, so the copy keeps working in both kits.\n\n```sh\nnpx @twentyfirstmedia/ui add ApprovalCard DataTable\n```\n\n## list and diff\n\n`list` shows every component by group and marks the ones you copied. `diff` compares your copy with the current package source, so updates are easy to merge by hand.\n\n```sh\nnpx @twentyfirstmedia/ui list\nnpx @twentyfirstmedia/ui diff ApprovalCard\n```\n\n## mcp\n\n`mcp` runs the MCP server on stdio; `mcp init` writes the config for Claude Code, Cursor and VS Code and adds the kit rules to `AGENTS.md`. See [AI agents and MCP](/{{kit}}/ai/).\n\n```sh\nnpx @twentyfirstmedia/ui mcp init --kit {{kit}}\nnpx @twentyfirstmedia/ui mcp init --client cursor --no-rules\n```\n"},{"slug":"theming","title":"Theming and tokens","description":"Theme both kits like shadcn/ui: the same variables (primary, secondary, muted, accent, destructive, border, ring, radius, charts, sidebar) restyle every component, light and dark.","markdown":"Both kits use shadcn/ui's theme variables. A theme is any CSS that sets them, so a theme from ui.shadcn.com/themes, tweakcn or your own design system works as it is, and every component follows it: buttons, fields, menus, tables, charts, the Sidebar, in Halo and in Pro, light and dark.\n\n```css\n@import \"@twentyfirstmedia/{{kit}}/styles.css\";\n\n:root {\n --primary: oklch(0.546 0.245 262.881);\n --primary-foreground: oklch(0.985 0 0);\n --ring: oklch(0.546 0.245 262.881);\n --radius: 0.75rem;\n}\n.dark,\n[data-theme=\"dark\"] {\n --primary: oklch(0.707 0.165 254.624);\n --primary-foreground: oklch(0.145 0 0);\n}\n```\n\nThe kits define their variables with zero specificity, so your theme wins in any order. Dark mode follows `data-theme=\"dark\"` or the `.dark` class on `<html>` (next-themes works as it is), and `data-theme=\"system\"` follows the operating system.\n\n## The variables\n\n| Variable | Used for |\n| --- | --- |\n| `--background`, `--foreground` | Page background and default text |\n| `--card`, `--card-foreground` | Cards, panels, fields, the workspace sheet |\n| `--popover`, `--popover-foreground` | Menus, popovers, select lists, command palette, hover cards, dialogs |\n| `--primary`, `--primary-foreground` | Primary buttons, checked boxes and radios, switches, selected chips and days, progress, current marks |\n| `--secondary`, `--secondary-foreground` | Soft buttons and icon buttons |\n| `--muted`, `--muted-foreground` | Quiet fills (tracks, search fields, code) and secondary text |\n| `--accent`, `--accent-foreground` | The highlight under the pointer or keyboard: menu items, rows, options, nav items |\n| `--destructive`, `--destructive-foreground` | Danger buttons, error text and icons |\n| `--border`, `--input`, `--ring` | Hairlines, field and checkbox borders, focus rings |\n| `--radius` | The base corner radius; every corner derives from it |\n| `--chart-1` … `--chart-5` | Chart series (lines, dots, legends) |\n| `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` | The Sidebar |\n\nThe kits' own names (`--canvas`, `--surface`, `--ink`, `--line`, Pro's `--bg-000` … `--fg-400` scale) point at these, and the in-between shades are mixed from them, so you only set the variables above. Outside the theme on purpose: the five tints and status colours (they carry meaning), Halo's dark app frame, paper (e-mail and invoice previews) and media overlays. Halo draws chart bars in soft fills of the series colours (`--chart-sky` … `--chart-blush`); the theme builder and `twentyfirst-ui theme` set them for you when the charts change.\n\n## Presets, CLI and builder\n\nSix brand presets keep each kit's neutrals and change the brand colour: blue, violet, emerald, orange, rose and teal.\n\n```css\n@import \"@twentyfirstmedia/{{kit}}/styles.css\";\n@import \"@twentyfirstmedia/{{kit}}/themes/violet.css\";\n/* both kits loaded: @twentyfirstmedia/ui-core/themes/violet.css */\n```\n\nThe CLI writes a theme into your app and imports it: a preset, a brand colour (light, optionally dark) or a shadcn theme file.\n\n```sh\nnpx @twentyfirstmedia/ui theme violet\nnpx @twentyfirstmedia/ui theme \"#2563eb,#60a5fa\"\nnpx @twentyfirstmedia/ui theme ./shadcn-theme.css\n```\n\nThe [theme builder](/{{kit}}/themes/) shows a theme live on real components and gives you the CSS.\n\n## Themes at runtime\n\nFor a brand per customer or a colour the user picks, `UIProvider` takes the same variables. One set applies in both modes; `{ light, dark }` picks by the current theme.\n\n```tsx\nimport { UIProvider, brandTheme } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider tokens={{ primary: tenant.color, ring: tenant.color }}>{children}</UIProvider>;\n<UIProvider tokens={brandTheme(\"#2563eb\", \"#60a5fa\")}>{children}</UIProvider>;\n```\n\n`themeCss(theme, { kit })` writes the CSS for a theme (for a server-rendered `<style>`), `parseThemeCss(css)` reads a shadcn theme, and `brandTheme(color)` picks readable text for a brand colour.\n\n## Glass\n\nFor UI that floats over a map, a 3D scene, a video or photos, surfaces can be glass: translucent, blurred and colour-lifted over what is behind, with light catching the top edge. Halo's glass is soft and milky, Pro's is dark with a hairline edge.\n\n```tsx\nimport { FloatingLayout, MapView, Panel, SearchField, UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n// a layout for UI over a background: four slots, glass by default\n<FloatingLayout\n height={720}\n background={<MapView background={<YourMap />} points={places} height=\"100%\" />}\n top={<SearchField placeholder=\"Search places\" />}\n start={<Panel title=\"Layers\">…</Panel>}\n end={<Panel title=\"Harbour Museum\">…</Panel>}\n/>;\n\n// one surface\n<Panel surface=\"glass\" title=\"Layers\">…</Panel>;\n\n// everything floating in a subtree: panels, cards, dock, map legend, menus, popovers, dialogs\n<UIProvider scope=\"subtree\" surface=\"glass\">…</UIProvider>;\n```\n\n- Inside a glass region, top-level Panels, Cards, the Dock and map legends and controls turn into glass, and so do loose controls (search field, chips, icon and secondary buttons, segmented controls). Menus, popovers, dialogs and drawers take a stronger fill because they carry more text. A surface nested in a glass surface stays clear instead of stacking glass on glass; `surface=\"solid\"` opts one out.\n- What sits on glass follows it: inputs, tab tracks, badges, hover and selection highlights turn translucent too.\n- The fills keep body text at 4.5:1 or more over any backdrop. Reduced transparency and forced colours switch to solid surfaces, more contrast to the stronger fill, and browsers without backdrop blur get the stronger fill too.\n- The glass tokens are themable like every other colour: `--glass`, `--glass-strong`, `--glass-control`, `--glass-well`, `--glass-hover`, `--glass-border`, `--glass-highlight`, `--glass-sheen`, `--shadow-glass` and `--glass-filter` (the backdrop filter). By default they derive from `--card` and `--foreground`, so a theme carries over.\n- Without React: `class=\"tf-glass\"` on a surface, or `data-surface=\"glass\"` on any container.\n\nSee the [map pattern](/{{kit}}/patterns/MapScreen/) for a full screen.\n\n### Glass dashboards\n\nA whole app can be glass: put `Ambient` behind it and wrap it in `<UIProvider surface=\"glass\">`. The `AppShell` turns into glass too (top bar, rail, sidebar; the sheet clears so the background shows through), and tinted tiles (`StatTile tint=\"sky\"`, tinted Cards) lay their tint over the glass instead of an opaque block.\n\n```tsx\nimport { Ambient, AppShell, StatTile, UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider surface=\"glass\">\n <Ambient fixed />\n <AppShell brand=\"Bloom\">\n <StatTile value=\"7h 42m\" label=\"Sleep\" tint=\"sky\" />\n …\n </AppShell>\n</UIProvider>;\n```\n\n`Ambient` draws soft colour fields from `--ambient-1…4` (pastels in Halo, a faint aurora in Pro) that drift slowly; `colors` takes your own, `src` a photo or (with `video`) a video under a calming scrim, and children your own layer. The drift stops with motion off and pauses off screen. With `fixed`, keep `body` transparent so the background shows. On glass, the faintest text steps up one shade so secondary text stays readable. See the [wellbeing pattern](/{{kit}}/patterns/WellbeingScreen/).\n\n## Tailwind\n\nThe preset maps the variables to shadcn's utility names, so `bg-primary text-primary-foreground`, `bg-muted text-muted-foreground`, `border-border`, `ring-ring` and `bg-sidebar` work, plus the kits' own names (`bg-surface`, `text-ink`).\n\n## Light, dark and density\n\nTheme and density are attributes: `data-theme=\"light|dark|system\"` and `data-density=\"compact|comfortable\"` on `<html>` or any subtree, or the `theme` and `density` props of `UIProvider`. Halo follows the system theme and is comfortable by default; Pro is dark and compact by default.\n\n```tsx\n<UIProvider theme=\"dark\" density=\"compact\">…</UIProvider>\n```\n\n## Layout and breakpoints\n\nThe kits are mobile first: every component works from a 320px wide screen up, and from 1181px on the full desktop layout applies. Phones (up to 760px) get one column: the shell stacks, rail and page nav become scrolling rows, the dock sticks to the bottom, tables scroll inside their box, dialogs open as bottom sheets and popovers stay on screen. Tablets (761–1180px) move asides below the main column. On touch screens text fields use 16px so browsers do not zoom in, and a compact kit (Pro) switches to comfortable hit targets unless `data-density` is set.\n\nFor your own grids, `tf-grid` takes its columns from custom properties: `--cols` on desktop, `--cols-md` on tablets (defaults to `--cols`) and `--cols-sm` on phones (defaults to one column).\n\n```tsx\n<div\n className=\"tf-grid\"\n style={{ \"--cols\": \"repeat(4, minmax(0, 1fr))\", \"--cols-sm\": \"1fr 1fr\" } as React.CSSProperties}\n>\n <StatTile value=\"18\" label=\"Projects\" />\n …\n</div>\n```\n\n## Control sizes\n\nEvery control sits on one height scale: Halo 32 / 40 / 48px (sm / md / lg), Pro 24 / 28 / 34px. Mix sizes only on purpose; a row of controls uses one size.\n"},{"slug":"themes","title":"Theme builder","description":"Build a shadcn-style theme for {{Kit}} live on real components: presets, brand colours, radius and imported shadcn themes, with the CSS to copy.","markdown":"Pick a preset, change any colour or paste a theme from ui.shadcn.com/themes or tweakcn; the preview uses the real components of the kit. Light and dark are edited separately: switch the theme in the header. \"Apply to the whole site\" lets you browse every component page in your theme (in this tab only).\n\n## Use the result\n\nSave the CSS as `theme.css` and import it once, after the kit's styles, or let the CLI write and import it:\n\n```sh\nnpx @twentyfirstmedia/ui theme ./theme.css\nnpx @twentyfirstmedia/ui theme violet\n```\n\nIn React you can also pass a theme at runtime: `<UIProvider tokens={theme}>`. See [Theming and tokens](/{{kit}}/theming/) for every variable.\n"},{"slug":"motion","title":"Motion","description":"Choreographed page entrances, data that draws itself, layers that animate in and out, page transitions and reveal on scroll, with one switch: off, standard or expressive.","markdown":"Both kits move with purpose: the shell settles in, cards rise one after another, charts draw and numbers count up once their card is there, and menus, dialogs, drawers and toasts animate in and out. One switch sets how much:\n\n| Level | Halo | Pro |\n| --- | --- | --- |\n| `standard` (default) | Soft and springy: 520ms entrances, 55ms between cards | Quick and quiet: 140ms, no stagger, no springs |\n| `expressive` | Orchestrated: 760ms entrances, 90ms between cards, more travel, a short focus-in blur | Crisp: 360ms entrances, 45ms between cards, sharp easing, no blur or springs |\n| `off` | Nothing moves; values and layers appear at once | The same |\n\n```tsx\nimport { UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider motion=\"expressive\">{children}</UIProvider>;\n// a setting in your app\n<UIProvider motion={settings.reduceMotion ? \"off\" : \"standard\"}>{children}</UIProvider>;\n```\n\nThe level is an attribute, so it works without React too: `data-motion=\"expressive\"` on `<html>` or any subtree. The reader's reduced-motion setting always wins and turns motion off, whatever the app sets.\n\n## The choreography\n\n- **Shell**: the top bar drops in, side panels slide in, the dock springs up and its buttons pop in turn.\n- **Content**: wrap a page's sections in `Stagger` and they rise one after another. Below the fold a `Stagger` waits until it scrolls into view (`reveal={false}` plays at once).\n- **Data**: bars grow, lines draw, areas and dots fade in, gauges and progress bars fill and `CountUp` counts, each waiting for the card it sits in. New values count from the old one.\n- **Layers**: popovers, menus, tooltips, hover cards, comboboxes, dialogs, drawers, the command palette and toasts animate out as well as in. `Modal`, `Drawer` and `CommandPalette` take an optional `open` prop for that; without it, render them only while open, as before.\n\n```tsx\n<Stagger className=\"tf-grid\" style={{ \"--cols\": \"repeat(3, minmax(0, 1fr))\" } as CSSProperties}>\n <StatTile value=\"1,284\" label=\"Orders\" />\n <Panel title=\"Revenue\">\n <LineChart labels={days} series={series} />\n </Panel>\n …\n</Stagger>\n<Drawer open={open} title=\"Order 1042\" onClose={() => setOpen(false)}>…</Drawer>\n```\n\n## Page transitions\n\n`startViewTransition(update)` runs a route change or a big update as a view transition: the old page fades out and the new one rises in, in the kit's motion. Browsers without view transitions, and motion `off`, get a plain update.\n\n```tsx\nimport { startViewTransition } from \"@twentyfirstmedia/{{kit}}\";\n\nstartViewTransition(() => router.push(\"/orders\"));\n```\n\nThe kits style `::view-transition-old(root)` and `::view-transition-new(root)`, so transitions started another way (Next.js `experimental.viewTransition` with React's `<ViewTransition>`, Astro, plain `document.startViewTransition`) look the same.\n\n## Your own animations\n\n- `useMotion()` returns the level in effect (`\"off\"` while the reader asks for reduced motion).\n- `usePresence(open)` keeps your own layer mounted while it animates out: render while `mounted`, spread `leaving(closing)` on its root (sets `data-closing` and `inert`).\n- The motion tokens drive everything: `--dur-fast`, `--dur-base`, `--dur-enter`, `--dur-slow`, `--dur-exit`, `--stagger-step`, `--ease-out`, `--ease-spring`, `--motion-time` (delays), `--motion-travel` (distances) and `--motion-blur`. Set them like any other token for a motion style of your own.\n\nOnly `transform`, `opacity` and the short expressive blur animate; layout never moves under the reader.\n"},{"slug":"text","title":"Text and locale","description":"Built-in text is English by default; replace any of it with UIProvider messages, and format numbers, money and dates for any locale with UIProvider locale.","markdown":"The kits ship plain English and no translations. Every text a component shows on its own (accessible names, button texts, placeholders, status words, validation messages) can be replaced, and numbers, money, percentages and dates follow a locale you set. Props such as `title`, `label` or `placeholder` on a component still win over both.\n\n```tsx\nimport { UIProvider } from \"@twentyfirstmedia/{{kit}}\";\n\n<UIProvider\n locale=\"de-DE\"\n messages={{\n close: \"Schließen\",\n send: \"Senden\",\n composerHint: \"Enter zum Senden · Shift+Enter für eine neue Zeile\",\n selectedCount: (n, formatted) => `${formatted ?? n} ausgewählt`,\n agentStatus: { Running: \"Läuft\", Idle: \"Bereit\" },\n }}\n>\n {children}\n</UIProvider>;\n```\n\n## Messages\n\n- `messages` is partial: what you leave out stays English. Nested providers merge on top of the outer one, so a section of the app can change a few strings.\n- Strings with a value in them are functions (`` removeItem: (name) => `${name} entfernen` ``), so the word order is yours. A few take React nodes where the value is highlighted (`typeToConfirm`, `freeShippingIn`).\n- Status words (`agentStatus`, `orderStatus`, `reviewState`, `riskFamily`, `flowStep`) are groups keyed by the status value, which stays English in your data. Groups merge one level deep.\n- `DEFAULT_STRINGS` holds every key with its English text; the `UIStrings` type documents each one. Type your own set as `UIMessages` to get completion and checks.\n- `useUIStrings()` returns the strings that apply where it is called, for your own components.\n- Server components (`StatusScreen`, `AuthCard`) cannot read the provider; their texts are props.\n\nSet `lang` on `<html>` (or the element around a translated part) yourself, so screen readers pronounce the text in the right language.\n\n## Locale\n\n`locale` is a BCP 47 tag (default `en-US`). It applies to every number, amount, percentage and date a component formats, to calendar month and weekday names, and to `CountUp` and `StatTile`, which read string values in that locale: in `de-DE`, `\"13,05 €/m²\"` counts up to 13.05 and keeps its unit.\n\nThe helpers take the locale as their last argument, and `useFormat()` returns them bound to the provider's locale:\n\n```tsx\nimport { formatMoney, useFormat } from \"@twentyfirstmedia/{{kit}}\";\n\nformatMoney(1290, \"EUR\", undefined, \"de-DE\"); // \"1.290 €\"\n\nfunction Price({ value }: { value: number }) {\n const { formatMoney } = useFormat();\n return <b>{formatMoney(value)}</b>;\n}\n```\n\nMoney defaults to euros; pass a currency per call. Calendars start on Monday.\n"},{"slug":"ai","title":"AI agents and MCP","description":"Give Claude Code, Cursor, VS Code and other coding agents the complete kit docs, so they build with real components instead of guessing.","markdown":"The kits are newer than most models' training data, and the exact props matter. Two things fix that: an MCP server that answers from the docs of the exact version you installed, and plain Markdown for every page of this site.\n\n## MCP server\n\nThe CLI includes an MCP server (stdio, no network access, the docs are bundled). One command sets it up for the clients you use and adds the kit rules to `AGENTS.md`:\n\n```sh\nnpx @twentyfirstmedia/ui mcp init --kit {{kit}}\n```\n\nIt writes `.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor) and `.vscode/mcp.json` (VS Code). Pick clients with `--client claude,cursor,vscode` and skip the rules with `--no-rules`.\n\nOr add it by hand. Claude Code:\n\n```sh\nclaude mcp add twentyfirst-ui -- npx -y @twentyfirstmedia/ui mcp --kit {{kit}}\n```\n\nCursor (`.cursor/mcp.json`), Windsurf and Claude Desktop use the same shape:\n\n```json\n{\n \"mcpServers\": {\n \"twentyfirst-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@twentyfirstmedia/ui\", \"mcp\", \"--kit\", \"{{kit}}\"]\n }\n }\n}\n```\n\nVS Code (`.vscode/mcp.json`):\n\n```json\n{\n \"servers\": {\n \"twentyfirst-ui\": {\n \"type\": \"stdio\",\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@twentyfirstmedia/ui\", \"mcp\", \"--kit\", \"{{kit}}\"]\n }\n }\n}\n```\n\nCodex (`~/.codex/config.toml`):\n\n```toml\n[mcp_servers.twentyfirst-ui]\ncommand = \"npx\"\nargs = [\"-y\", \"@twentyfirstmedia/ui\", \"mcp\", \"--kit\", \"{{kit}}\"]\n```\n\n### Tools\n\n| Tool | What it returns |\n| --- | --- |\n| `search_docs` | Components, patterns, guides, tokens and icons that match a query |\n| `list_components` | Every component page by group with a one-line summary |\n| `get_component` | Description, import line, example code and props of a component |\n| `get_pattern` | A complete screen built from kit parts, with its code |\n| `get_guide` | Installation, CLI, theming, status and approvals, this page |\n| `get_tokens` | Token names with Halo and Pro values, by group |\n| `list_icons` | Every valid icon name |\n| `check_usage` | Checks code: unknown components, props and icons, missing required props, colour literals |\n\nEvery page is also a resource (`twentyfirst-ui://components/ApprovalCard`, `twentyfirst-ui://guides/theming`), and the prompts `build_screen` and `review_ui` start the two most common jobs.\n\n## Rules for agents\n\n`mcp init` adds these to `AGENTS.md` (and `CLAUDE.md` when it exists); you can copy them by hand:\n\n```md\n{{rules}}\n```\n\n## Markdown and llms.txt\n\n- [/llms.txt]({{site}}/llms.txt): what the kits are, the rules, and a link to every page.\n- [/llms-full.txt]({{site}}/llms-full.txt): every guide and component page in one file.\n- Every page has a Markdown twin: append `.md` to its path, e.g. [/{{kit}}/components/ApprovalCard.md]({{site}}/{{kit}}/components/ApprovalCard.md). The \"Markdown\" button at the top of each page copies it.\n"},{"slug":"html","title":"Plain HTML and other frameworks","description":"Every component as a custom element (<tf-button>, <tf-data-table> …) for plain HTML, Vue, Svelte, Angular, Astro and server templates. No React setup needed.","markdown":"`@twentyfirstmedia/elements` turns every component into a custom element with the same props: `Button` is `<tf-button>`, `DataTable` is `<tf-data-table>`, `UIProvider` is `<tf-provider>`. React is bundled inside, so the page needs no build step and no React tooling. The elements use no shadow DOM: the {{Kit}} stylesheet styles them like everywhere else.\n\n## From a CDN\n\nLoad the {{Kit}} CSS and the elements, then write HTML:\n\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/{{kit}}/dist/styles.css\">\n<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/elements/dist/elements.js\"></script>\n\n<tf-panel title=\"Revenue\" subtitle=\"Last 7 days\">\n <tf-button slot=\"action\" variant=\"ghost\" size=\"sm\">Export</tf-button>\n <tf-bar-chart label=\"Revenue per day\"\n data='[{\"label\":\"Mon\",\"value\":32},{\"label\":\"Tue\",\"value\":48},{\"label\":\"Wed\",\"value\":41}]'>\n </tf-bar-chart>\n</tf-panel>\n```\n\nunpkg works the same way (`https://unpkg.com/@twentyfirstmedia/elements/dist/elements.js`). For a classic `<script>` without `type=\"module\"` use `dist/elements.global.js`; it exposes `window.TwentyfirstElements` with `toast` and the `Intl` formatters. Pin a version in production, for example `@twentyfirstmedia/elements@0.1`.\n\nSelf-host when you can: the CDN is the only external request. The components themselves never load anything, and the fonts come with the stylesheet.\n\n## From npm\n\n```sh\nnpm install @twentyfirstmedia/elements @twentyfirstmedia/{{kit}}\n```\n\n```js\nimport \"@twentyfirstmedia/{{kit}}/styles.css\";\nimport { toast } from \"@twentyfirstmedia/elements\"; // defines every tf-* element\n```\n\nThe `@twentyfirstmedia/{{kit}}` package is only needed for its stylesheet; the elements bring their own copy of the components and React.\n\n## Attributes, properties, events and slots\n\n| Component API | As an element |\n| --- | --- |\n| `variant=\"ghost\"`, `size=\"sm\"` | the same attribute |\n| `defaultValue`, `iconRight` | kebab-case: `default-value`, `icon-right` |\n| `disabled`, `selectable` (boolean) | present means true, absent means unset, `\"false\"` means false |\n| `max={8}` (number) | `max=\"8\"` |\n| `items`, `columns`, `rows` (arrays, objects) | a property, `el.items = [...]`, or JSON in the attribute: `items='[\"Day\",\"Week\"]'`; lists of strings also take `items=\"Day, Week\"` |\n| `format`, `renderExpanded`, `onVerify` (functions) | properties only: `el.format = (v) => ...` |\n| `onChange`, `onDecide`, `onClose` | DOM events `change`, `decide`, `close` (the name without `on`, lowercase) |\n| `children` | the element's content: text, HTML and other tf-* elements |\n| `action`, `trace`, `corner` (ReactNode props) | children with `slot=\"action\"`; a DOM node set as a property works too |\n| `actions` (a list of nodes) | every child with `slot=\"actions\"` is one item |\n| `panels` on Tabs (a record of nodes) | children with `slot=\"panels:Overview\"` |\n| `className`, `style` | the `class` and `style` attributes |\n\nEvents bubble, are composed and can be cancelled. `event.detail` holds the callback's argument, or an array when the callback takes several (`decide` gives `[key, text]`). Where a component would pass a React change event, the detail is the control's value (`checked` for switches and checkboxes).\n\n```js\nconst tabs = document.querySelector(\"tf-tabs\");\ntabs.addEventListener(\"change\", (e) => console.log(\"now on\", e.detail));\n\nconst card = document.querySelector(\"tf-approval-card\");\ncard.addEventListener(\"decide\", (e) => {\n const [key, text] = e.detail;\n card.decision = { key, by: \"Mara\", at: \"09:41\", text };\n});\n```\n\nA few rules make plain HTML pleasant:\n\n- **Uncontrolled by default.** Without `value` or `checked`, Tabs, Segmented, Switch and friends keep their own state. Set the attribute to control them: the element then follows the user's input like a native control, unless a listener calls `event.preventDefault()`.\n- **Optional callbacks follow listeners.** Some components only show a part when the callback is set, such as the dismiss button of `<tf-alert>`. The element passes those callbacks while something listens for the event on the element or a tf-* element around it.\n- **Closing hides the element.** Modal, Drawer, CommandPalette and Alert set the `hidden` attribute after `close` (or `dismiss`) unless the event is cancelled. Remove `hidden` to show them again. `<tf-modal>` opens as a dialog over the page: focus moves in and stays there, Escape and the backdrop close it, and focus returns to where it was.\n- **Menus close their popover.** A `<tf-menu>` inside `<tf-popover>` closes it when an item runs or on Escape.\n- **Nested elements share context.** A tf-* element inside another one renders in its parent's React tree, so `<tf-text-field name=\"email\">` inside `<tf-form>` shows the form's validation message.\n- **Layout is unchanged.** Each element is `display: contents`; the component's root is the flex or grid item. The `id` attribute stays on the element, and `title`, `role` and `lang` move into the component.\n\nContent is read when the element first renders; children added later with `append` are picked up as well. Content that is not shown at the moment (a closed popover, a hidden modal) stays in the document, inside the element, so `getElementById` keeps working.\n\n## Toasts and helpers\n\n```html\n<tf-toaster></tf-toaster>\n<script type=\"module\">\n import { toast, formatMoney } from \"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/elements/dist/elements.js\";\n toast({ text: `Paid ${formatMoney(1290)}`, tone: \"success\" });\n</script>\n```\n\n## Kit switching and theming\n\nWith one kit's `styles.css` no attribute is needed. To switch between Halo and Pro at runtime, load `@twentyfirstmedia/ui-core/dist/themes.css` (both kits) and set `data-kit` on `<html>`. Theme and density work the same way in both setups:\n\n```html\n<html data-kit=\"{{kit}}\" data-theme=\"system\">\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@twentyfirstmedia/ui-core/dist/themes.css\">\n\n<script type=\"module\">\n document.documentElement.dataset.kit = \"{{other}}\";\n document.documentElement.dataset.theme = \"dark\";\n</script>\n```\n\n`<tf-provider kit=\"{{kit}}\" theme=\"dark\" density=\"compact\">` does the same declaratively; with `scope=\"subtree\"` it applies to its content only. Every colour, radius, shadow and spacing is a CSS variable, so your own CSS can retheme the kit (see [Theming and tokens](/{{kit}}/theming/)):\n\n```css\n:root {\n --primary: var(--lilac-ink);\n --radius-lg: 12px;\n}\n```\n\n## Vue\n\nTell the compiler that `tf-*` tags are custom elements. Vue binds arrays and objects as properties and listens with `@`:\n\n```js\n// vite.config.js\nimport vue from \"@vitejs/plugin-vue\";\nexport default {\n plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith(\"tf-\") } } })],\n};\n```\n\n```html\n<script setup>\nimport \"@twentyfirstmedia/elements\";\nimport { ref } from \"vue\";\nconst tab = ref(\"Overview\");\n</script>\n\n<template>\n <tf-tabs :items=\"['Overview', 'Activity']\" :value=\"tab\" @change=\"tab = $event.detail\" />\n</template>\n```\n\n## Svelte\n\n```html\n<script>\n import \"@twentyfirstmedia/elements\";\n let checked = $state(false);\n</script>\n\n<tf-switch checked={checked} onchange={(e) => (checked = e.detail)}>Email me updates</tf-switch>\n<tf-data-table columns={columns} rows={rows} selectable></tf-data-table>\n```\n\n## Angular\n\nAllow custom elements in the component and bind properties with `[ ]` and events with `( )`:\n\n```ts\nimport { Component, CUSTOM_ELEMENTS_SCHEMA } from \"@angular/core\";\nimport \"@twentyfirstmedia/elements\";\n\n@Component({\n selector: \"app-team\",\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n template: `\n <tf-tabs [items]=\"tabs\" (change)=\"onTab($event)\"></tf-tabs>\n <tf-data-table [columns]=\"columns\" [rows]=\"rows\" selectable></tf-data-table>\n `,\n})\nexport class TeamComponent {\n tabs = [\"Members\", \"Invites\"];\n tab = \"Members\";\n columns = [{ key: \"name\", label: \"Name\" }];\n rows = [{ id: 1, name: \"Ava Vogel\" }];\n onTab(e: Event) {\n this.tab = (e as CustomEvent<string>).detail;\n }\n}\n```\n\n## Astro, Rails, Django and other server templates\n\nRender the tags on the server and load the elements once in the layout. Pass data as JSON in attributes:\n\n```html\n---\n// Astro\nconst items = [\"Overview\", \"Activity\"];\n---\n<tf-tabs items={JSON.stringify(items)}></tf-tabs>\n<script>\n import \"@twentyfirstmedia/elements\";\n</script>\n```\n\n```html\n<!-- Django: escape the JSON for the attribute -->\n<tf-bar-chart label=\"Signups\" data=\"{{ signups_json }}\"></tf-bar-chart>\n```\n\n## Static markup with the kit classes\n\nFor markup that needs no behaviour, the kit classes work without any script: the stylesheet alone is enough.\n\n```html\n<section class=\"tf-panel\">\n <div class=\"tf-row\">\n <button class=\"tf-btn tf-btn-primary\" type=\"button\">Save</button>\n <button class=\"tf-btn tf-btn-ghost\" type=\"button\">Cancel</button>\n <span class=\"tf-badge tf-badge-success\"><span class=\"tf-badge-dot\"></span>Live</span>\n </div>\n</section>\n```\n\nInteractive components (tabs, menus, dialogs, tables) need the elements for their keyboard paths and ARIA.\n\nFor UI over a map, a video or photos, `class=\"tf-glass\"` turns a surface into glass, and `data-surface=\"glass\"` on a container turns everything floating inside into glass (see [Glass](/{{kit}}/theming/#glass)):\n\n```html\n<div data-surface=\"glass\" style=\"position: relative\">\n <canvas id=\"map\"></canvas>\n <section class=\"tf-panel\" style=\"position: absolute; top: 16px; left: 16px\">Layers</section>\n</div>\n```\n\n## Editor support and types\n\nThe package ships a Custom Elements Manifest (`custom-elements.json`) for autocompletion of tags, attributes, events and slots, and VS Code HTML data:\n\n```json\n{ \"html.customData\": [\"./node_modules/@twentyfirstmedia/elements/dist/vscode.html-custom-data.json\"] }\n```\n\nIn TypeScript, `document.querySelector(\"tf-data-table\")` is typed with every property of `DataTable`.\n"},{"slug":"status-and-approvals","title":"Status and approvals","description":"One state model and one way to ask a human, in both kits.","markdown":"State is shape plus colour, so it reads without colour vision: **ok** = dot, **running** = pulsing dot, **signal** = diamond (waits for a human), **warn** = triangle, **danger** = square, **idle** = ring. One status word always maps to the same tone on every page. Only `signal` may draw attention unasked; warnings and errors never animate.\n\nEverything an agent may not do alone goes through `ApprovalCard` with four answers in this order: **A** yes, as shown · **B** yes, with a change · **C** no, discard · **D** different instruction. The card shows the risk family, agent and project, age and escalation time, and the exact payload that will run. A–D work as keys. Once decided it collapses to one line with letter, person and time. `ApprovalInbox` lists open approvals oldest first; J and K move.\n\nSee [StatusIndicator](/{{kit}}/components/StatusIndicator/), [ApprovalCard](/{{kit}}/components/ApprovalCard/) and [ApprovalInbox](/{{kit}}/components/ApprovalInbox/) for the props.\n"},{"slug":"changelog","title":"Changelog","description":"What changed in each release of the Halo and Pro kits, the shared core, the CLI and the custom elements. All five packages are versioned together.","markdown":"## 0.3.1\n\n### Patch Changes\n\n- [#35](https://github.com/twentyfirstmedia/ui/pull/35) [`3a25cd6`](https://github.com/twentyfirstmedia/ui/commit/3a25cd6e9162a69465a5e7794b2f38c970f4ff08) Thanks [@robintrepte](https://github.com/robintrepte)! - `FloatingLayout` on a phone keeps the height it is given: the stacked slot grid counts its padding into the inherited height (`box-sizing: border-box`). Before, the layout grew by twice the padding, and with a fixed height the bottom slot was cut off at the edge. Apps that worked around it with `.tf-floating-ui { box-sizing: border-box; }` can drop that rule.\n\n## 0.3.0\n\n### Minor Changes\n\n- [#30](https://github.com/twentyfirstmedia/ui/pull/30) [`bef588d`](https://github.com/twentyfirstmedia/ui/commit/bef588d0fb521a5b458113095a7952222efe3c63) Thanks [@robintrepte](https://github.com/robintrepte)! - `Button` and `IconButton` pass native attributes on to their `<button>` (or, with `asChild`, to the child): `id`, `name`, `value`, `form`, `title`, `tabIndex`, `autoFocus`, `aria-label`, `aria-labelledby`, `aria-describedby`, `aria-expanded`, `aria-controls`, `aria-haspopup`, `aria-pressed`, `aria-current` (documented as `NativeButtonProps`), plus any other `<button>` attribute, `data-*`, event handlers and, in React 19, `ref`. `type` also takes `\"reset\"`, and `IconButton` takes `type`. On `IconButton`, `aria-label` or `title` override the ones `label` sets. With `asChild`, `onClick` and the other attributes now reach the child as well.\n \n In `@twentyfirstmedia/elements`, `<tf-button>` and `<tf-icon-button>` take these as attributes; `aria-*` attributes move from the host to the inner button. The docs and the MCP server list them with both components.\n\n- [#28](https://github.com/twentyfirstmedia/ui/pull/28) [`ef1090f`](https://github.com/twentyfirstmedia/ui/commit/ef1090f32204a227ed0ad717e74ea057f6d4da90) Thanks [@robintrepte](https://github.com/robintrepte)! - `FileDrop` hands over the real files.\n \n - New `onFiles(files: File[])` with the `File` objects the person picked or dropped, after `accept` and `multiple`. Added list entries carry their `file` too, so `onChange` gets them as well.\n - Progress only when the app reports it: with `onFiles`, nothing is simulated; set `progress` on controlled `files` to show a bar (now a labelled `progressbar`). An uncontrolled list with `onFiles` shows each file with its size only.\n - `accept` is checked on drop as well; `onReject` receives what did not match. `onRemove` reports removed or cancelled files.\n - `simulateProgress` keeps the old demo animation; it stays the default without `onFiles`, so existing code behaves as before. Running simulations stop on unmount.\n\n- [#31](https://github.com/twentyfirstmedia/ui/pull/31) [`ece54b1`](https://github.com/twentyfirstmedia/ui/commit/ece54b18c3594f595386608e12c93190aa41d5bb) Thanks [@robintrepte](https://github.com/robintrepte)! - `Gallery` and `BeforeAfter` load images lazily and take `loading` and `referrerPolicy`.\n \n - `Gallery`: the main image loads lazily by default (`loading=\"eager\"` for a gallery at the top of the page), thumbnails always lazily, all images decode asynchronously; `referrerPolicy` applies to every image (new type `ImageReferrerPolicy`).\n - `BeforeAfter`: the slider has an `aria-valuetext` (\"Before 40%, After 60%\", with `beforeLabel`/`afterLabel`), overridable with `valueText(percent)`; its accessible name is `label`. Page Up/Down, Home and End move the slider; arrow keys no longer scroll the page while it has focus. Both images take `loading` (default lazy) and `referrerPolicy`.\n\n- [#29](https://github.com/twentyfirstmedia/ui/pull/29) [`4cf034a`](https://github.com/twentyfirstmedia/ui/commit/4cf034af9d2a7eafae5281252e6f7857a74f8540) Thanks [@robintrepte](https://github.com/robintrepte)! - `Pagination` can be controlled: `page` with `onChange(page)` (1-based), or `defaultPage` with an optional `onChange` for an uncontrolled pager. `page` without `onChange` stays the start page, as before. Clicking the current page or past either end no longer reports a change.\n\n- [#33](https://github.com/twentyfirstmedia/ui/pull/33) [`d408a82`](https://github.com/twentyfirstmedia/ui/commit/d408a82cc5ab0acf5401c56118acf36e62516a81) Thanks [@robintrepte](https://github.com/robintrepte)! - Every built-in string is overridable, and numbers follow a locale.\n \n - `UIProvider messages`: replace any text a component shows on its own (accessible names such as \"Close\", \"Send\", \"Previous image\", \"Select row\", button texts, placeholders, status words, validation messages). Partial and nested; props such as `title` or `label` still win. English stays the default and no translations ship. `useUIStrings()`, `DEFAULT_STRINGS`, the `UIStrings` and `UIMessages` types, and one exported group per component file (`MEDIA_STRINGS` …).\n - `UIProvider locale` (BCP 47, default `en-US`): every number, amount, percentage and date in components, calendar month and weekday names. `useFormat()` returns the helpers bound to it; `formatNumber`, `formatMoney`, `formatPercent` and `formatDate` take an optional locale as their last argument.\n - `CountUp` and `StatTile` read string values in the provider locale (\"13,05 €/m²\" in de-DE counts to 13.05 instead of showing \"1,305\"). New helpers `parseFormatted`, `formatLike`, `numberSeparators`.\n - `SearchField` takes `label` (accessible name).\n - Fixed: `VoiceMode` with `stateLabel` showed \"Listening…\".\n\n- [#32](https://github.com/twentyfirstmedia/ui/pull/32) [`3b0992d`](https://github.com/twentyfirstmedia/ui/commit/3b0992dd44ef4f864e96c9735d55a9dbb18b8747) Thanks [@robintrepte](https://github.com/robintrepte)! - `TextField` passes native constraints to its input: `min`, `max`, `step`, `minLength`, `maxLength`, `pattern`, `inputMode`, `readOnly` and `autoFocus` (next to the existing `name`, `required` and `id`), all typed and documented. `PlanCard` takes `disabled` for its button.\n\n## 0.2.1\n\n### Patch Changes\n\n- [#26](https://github.com/twentyfirstmedia/ui/pull/26) [`863179b`](https://github.com/twentyfirstmedia/ui/commit/863179b194fcd8e43bac596039ef555cc294d03e) Thanks [@robintrepte](https://github.com/robintrepte)! - Published through npm Trusted Publishing: the release workflow no longer uses an npm token, and every package carries a provenance statement linking it to the GitHub Actions run that built it. No code changes.\n\n## 0.2.0\n\n### Minor Changes\n\n- [#24](https://github.com/twentyfirstmedia/ui/pull/24) [`4529ab5`](https://github.com/twentyfirstmedia/ui/commit/4529ab565c0de3dadb52b248585f1d4bb0ae0b3c) Thanks [@robintrepte](https://github.com/robintrepte)! - Whole dashboards in glass, in both kits.\n \n - New `Ambient`: a calm, living background of soft colour fields from the kit (pastels in Halo, a faint aurora in Pro) that drift slowly, or a photo or video under a scrim, or your own layer. Still with motion off, paused off screen. New tokens `--ambient-base`, `--ambient-1…4`, `--ambient-scrim`.\n - In a glass region, `AppShell` turns into glass (top bar, rail, sidebar; the sheet clears), and `StatTile` and tinted cards lay their tint over the glass (`--glass-tint`).\n - On glass, the faintest text steps up one shade so secondary text stays readable.\n - `AppShell` applies `className`.\n - New pattern: a wellbeing dashboard in glass.\n\n- [#22](https://github.com/twentyfirstmedia/ui/pull/22) [`beb3043`](https://github.com/twentyfirstmedia/ui/commit/beb30433be7abc8ed1adabf0bae2fc4de526e8f7) Thanks [@robintrepte](https://github.com/robintrepte)! - Glass surfaces for UI that floats over maps, 3D scenes, video or photos, in both kits: translucent, blurred and colour-lifted, soft and milky in Halo, dark with a hairline edge in Pro.\n \n - `surface=\"glass\"` on `Panel`, `Card`, `Dock` and `MapView` (legend, controls, popup), and `<UIProvider surface=\"glass\">` for everything floating in a subtree: top-level surfaces, loose controls, menus, popovers, dialogs and drawers. What sits on glass (inputs, tab tracks, badges, highlights) turns translucent too; `surface=\"solid\"` opts one out. Without React: `class=\"tf-glass\"` or `data-surface=\"glass\"`.\n - New `FloatingLayout`: four slots (`top`, `start`, `end`, `bottom`) over a full-bleed `background`, glass by default; on phones the slots stack and the layout grows with them.\n - New glass tokens in both kits (`--glass`, `--glass-strong`, `--glass-control`, `--glass-well`, `--glass-hover`, `--glass-border`, `--glass-highlight`, `--glass-sheen`, `--shadow-glass`, `--glass-filter`), derived from the theme. Body text on glass keeps 4.5:1 over any backdrop; reduced transparency and forced colours switch to solid surfaces, more contrast to the stronger fill.\n - `MapView` takes `height=\"100%\"` (any CSS length).\n - `Dock` now applies `className` and `style`.\n - A `UIProvider` subtree with its own `tokens` keeps the outer theme, so derived colours follow the tokens even without a `theme` prop.\n - New pattern: a city map with glass controls.\n\n- [#22](https://github.com/twentyfirstmedia/ui/pull/22) [`beb3043`](https://github.com/twentyfirstmedia/ui/commit/beb30433be7abc8ed1adabf0bae2fc4de526e8f7) Thanks [@robintrepte](https://github.com/robintrepte)! - Motion with one switch, in both kits.\n \n - `<UIProvider motion=\"off\" | \"standard\" | \"expressive\">` (or `data-motion`): `expressive` gives orchestrated page entrances (Halo: longer, more travel, a short focus-in blur; Pro: crisp and staggered), `off` stops everything, e.g. for a setting in your app. The reader's reduced-motion setting always turns motion off.\n - Choreography: charts, progress bars and `CountUp` wait for the card they sit in; a `Stagger` below the fold waits until it scrolls into view (`reveal={false}` plays at once).\n - Layers animate out as well as in: popovers, tooltips, hover cards, comboboxes, dialogs, drawers, the command palette and toasts. `Modal`, `Drawer` and `CommandPalette` take an optional `open` for that.\n - `startViewTransition(update)` for page transitions (the kits style `::view-transition`), `useMotion()` for the level in effect, `usePresence(open)` and `leaving()` for your own layers.\n - New motion tokens: `--dur-exit`, `--motion-time`, `--motion-travel`, `--motion-blur`.\n - Fixed: with `themes.css` or `scoped.css`, keyframes defined in another file of the kit never ran (spinners, skeleton shimmer, pulses, pop-ins).\n\n### Patch Changes\n\n- [#25](https://github.com/twentyfirstmedia/ui/pull/25) [`688f141`](https://github.com/twentyfirstmedia/ui/commit/688f141210ed82c5c10b375b38c9d06bbc7a7bf2) Thanks [@robintrepte](https://github.com/robintrepte)! - Glass app shell: it keeps the layout of the solid shell and turns its frame into glass, with the top bar and rail sitting on it as they sit on the solid frame (they no longer float as separate pieces). A `ToggleGroup` is only as wide as its items.\n\n## 0.1.1\n\nNo changes in this release.\n\n## 0.1.0\n\n### Minor Changes\n\n- [#12](https://github.com/twentyfirstmedia/ui/pull/12) [`2cf6196`](https://github.com/twentyfirstmedia/ui/commit/2cf6196cc8e81afa0668ce36e35dfe01501e44ea) Thanks [@robintrepte](https://github.com/robintrepte)! - Consistency pass over every component in both kits and themes. The app shell responds to its own width (container queries); kit boxes no longer depend on the host's box-sizing; controls and code use the kit fonts; bold stays on the type scale; status shapes, list icons, stat values, pagination and invoice numbers line up; segmented controls and tabs no longer stretch or break mid-word. Gauge, Funnel and ProgressRing now fill when reduced motion is on. Pro follows its reference more closely: decorative tints and chart series are monochrome, bars are flat, in-flow cards use hairlines, badges sit on the corner. New: `formatPercent`.\n\n- [#8](https://github.com/twentyfirstmedia/ui/pull/8) [`2db7b83`](https://github.com/twentyfirstmedia/ui/commit/2db7b83266449602d1209c346b44ad62a66c416d) Thanks [@robintrepte](https://github.com/robintrepte)! - First release: every component of the Halo reference in `ui-core`, the Halo and Pro kits with the same components, props and markup (switchable at runtime via `data-kit`), tokens and a Tailwind v4 preset per kit, self-hosted fonts and the CLI (`init`, `add`, `list`, `diff`).\n\n- [#10](https://github.com/twentyfirstmedia/ui/pull/10) [`532f835`](https://github.com/twentyfirstmedia/ui/commit/532f83532d4ce4098733020d34277299c025fa08) Thanks [@robintrepte](https://github.com/robintrepte)! - Mobile first: every component works from 320px up in both kits (desktop from 1181px unchanged). The shell, rail and page nav reflow on phones, tables scroll in their own box, dialogs open as bottom sheets, popovers and tooltips stay on screen, text fields use 16px on touch screens and Pro switches to comfortable hit targets on touch unless `data-density` is set. New: `tf-grid` columns via `--cols` / `--cols-md` / `--cols-sm`, `tf-sheet-nav` / `tf-sheet-aside`, `tf-table-scroll`, `useKeepInView`, `useScrollFocus`, `Tooltip` `defaultOpen`.\n\n- [#16](https://github.com/twentyfirstmedia/ui/pull/16) [`350ca53`](https://github.com/twentyfirstmedia/ui/commit/350ca53ea6b1a4dd77759449ac4b56bb8ed2a62a) Thanks [@robintrepte](https://github.com/robintrepte)! - Open source under the MIT license and published on npmjs.com: install with `npm i @twentyfirstmedia/halo` (or `pro`), no registry setup or token. `twentyfirst-ui init` no longer writes an `.npmrc`.\n\n- [#13](https://github.com/twentyfirstmedia/ui/pull/13) [`559cb2d`](https://github.com/twentyfirstmedia/ui/commit/559cb2df5ac851e71ad0f3808b1442f7f79a6401) Thanks [@robintrepte](https://github.com/robintrepte)! - English only: built-in text is plain English and overridable through props; numbers, money, percentages and dates format with Intl in en-US (`formatNumber`, `formatMoney`, `formatPercent`, `formatDate`).\n \n New components in both kits: Accordion, Separator, AspectRatio, Label, Toggle, ToggleGroup, ButtonGroup, ScrollArea, HoverCard, Carousel, Menubar, NavigationMenu, Sidebar, SelectMenu, Form and ResizablePanels. Menu gains keyboard navigation, typeahead, submenus, checkbox/radio and disabled items; Tabs render panels; Drawer takes `side`; Popover flips when there is no room; CommandPalette takes `loading`, `empty` and `onQueryChange`; Button and IconButton take `asChild`; inputs take `id`, `name` and `required`.\n\n- [#18](https://github.com/twentyfirstmedia/ui/pull/18) [`0f19447`](https://github.com/twentyfirstmedia/ui/commit/0f19447291603a148759d10b7a2bf13ea7e95cb6) Thanks [@robintrepte](https://github.com/robintrepte)! - shadcn-style theming in both kits. The kits use shadcn/ui's theme variables (`background`, `foreground`, `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `destructive`, `border`, `input`, `ring`, `radius`, `chart-1` … `chart-5`, `sidebar-*`) and every component reads them, so a pasted shadcn theme restyles everything, light and dark. The old `--accent`/`--on-accent`/`--accent-hover` are now `--primary`/`--primary-foreground`/`--primary-hover`; `--accent` is the highlight under the pointer, as in shadcn. Token blocks have zero specificity and dark mode also follows the `.dark` class. New: `themeCss`, `parseThemeCss`, `brandTheme`, `THEME_PRESETS` and preset CSS files (`themes/blue.css` …), `UIProvider tokens`, `twentyfirst-ui theme`, Tailwind utilities with shadcn's names.\n\n### Patch Changes\n\n- [#15](https://github.com/twentyfirstmedia/ui/pull/15) [`2c5ddb6`](https://github.com/twentyfirstmedia/ui/commit/2c5ddb6715c7c296a3379ea241158218185e9262) Thanks [@robintrepte](https://github.com/robintrepte)! - The CLI ships an MCP server for coding agents: `twentyfirst-ui mcp` (stdio, offline, the docs are bundled) with the tools `search_docs`, `list_components`, `get_component`, `get_pattern`, `get_guide`, `get_tokens`, `list_icons` and `check_usage`, every page as a resource, and the prompts `build_screen` and `review_ui`. `twentyfirst-ui mcp init` registers it for Claude Code, Cursor and VS Code and adds the kit rules to `AGENTS.md`.\n\n- [#11](https://github.com/twentyfirstmedia/ui/pull/11) [`acec782`](https://github.com/twentyfirstmedia/ui/commit/acec782abd5f1d188f9ee5e7c8eb563e6c450031) Thanks [@robintrepte](https://github.com/robintrepte)! - Icons are centred: 36 icon drawings were off the middle of their 24px grid (up to 1.75 units), visible in round icon buttons and tinted icons. They are now centred on their bounding box; shapes are unchanged, and `play` stays optically centred.\n"}],"components":[{"name":"ActivityFeed","title":"Activity Feed","group":"AI","summary":"What happened and what needs you: ActivityFeed (timeline with status shapes) and DailyBriefing (the morning summary).","description":"What happened and what needs you: `ActivityFeed` (timeline with status shapes) and `DailyBriefing` (the morning summary).\n\nActivityFeed: `groups` ({title, items}) or `items` ({time, status, actor, text, link}).\nDailyBriefing: `title`, `time`, `tint`, `summary`, `attention` ({text, status, onClick}), `suggestions` (strings), `attentionTitle`, `suggestionsTitle`.\n","components":["ActivityFeed","DailyBriefing"],"code":"import * as React from \"react\";\nimport { ActivityFeed, DailyBriefing } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <ActivityFeed\n groups={[\n {\n title: \"Today\",\n items: [\n {\n time: \"09:41\",\n status: \"signal\",\n actor: \"Atlas\",\n text: \"asked to send a reply to Northwind\",\n },\n {\n time: \"09:12\",\n status: \"warn\",\n actor: \"Ads\",\n text: \"reached 82% of the monthly budget\",\n },\n {\n time: \"08:02\",\n status: \"ok\",\n actor: \"Ledger\",\n text: \"matched 31 payments\",\n },\n ],\n },\n {\n title: \"Yesterday\",\n items: [\n {\n time: \"18:02\",\n status: \"danger\",\n actor: \"Echo\",\n text: \"stopped: the docs connection expired\",\n },\n ],\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <DailyBriefing\n title=\"Main Brain\"\n time=\"Today, 08:00\"\n summary=\"26 runs overnight, 22 succeeded. €4.18 spent, well under budget. Two drafts wait for you and the docs connection needs a new token.\"\n attention={[\n {\n text: \"Approve the reply to Northwind\",\n },\n {\n text: \"Reconnect the docs workspace\",\n status: \"danger\",\n },\n ]}\n suggestions={[\n \"Move the weekly report to Monday 09:00\",\n \"Give Ledger read access to the new bank account\",\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/ActivityFeed"},{"name":"AgentCard","title":"Agent Card","group":"AI","summary":"One agent at a glance (AgentCard) and everything about it beside a chat (AgentDetails).","description":"One agent at a glance (`AgentCard`) and everything about it beside a chat (`AgentDetails`).\n\nAgentCard: `name`, `role`, `tint`, `status` + `statusLabel`, `model`, `cost`, `last`, `onClick`, `onChat`.\nAgentDetails: `name`, `role`, `tint`, `status` + `statusLabel`, `sections` ({title, meta, items (KeyValue) or children, open}), `actions`.\n","components":["AgentCard","AgentDetails","Button"],"code":"import * as React from \"react\";\nimport { AgentCard, AgentDetails, Button } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr 300px\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <AgentCard\n name=\"Atlas\"\n role=\"Support triage\"\n tint=\"sky\"\n status=\"running\"\n statusLabel=\"Working\"\n model=\"Claude Sonnet 5.5\"\n cost=\"€1.84\"\n last=\"Drafted a reply to Northwind\"\n onChat={() => {}}\n onClick={() => {}}\n />\n <AgentCard\n name=\"Ledger\"\n role=\"Bookkeeping\"\n tint=\"butter\"\n status=\"signal\"\n statusLabel=\"Waiting for approval\"\n model=\"Claude Haiku 5.5\"\n cost=\"€0.22\"\n last=\"Proposed 3 bookings\"\n />\n <div className=\"tf-panel\">\n <AgentDetails\n name=\"Atlas\"\n role=\"Support triage · Northwind\"\n tint=\"sky\"\n status=\"running\"\n statusLabel=\"Working\"\n sections={[\n {\n title: \"Numbers\",\n items: [\n [\"Model\", \"Claude Sonnet 5.5\"],\n [\"Today\", \"€1.84\", true],\n [\"This month\", \"€38.20\", true],\n [\"Runs\", \"142\"],\n ],\n },\n {\n title: \"Tools\",\n meta: \"3\",\n items: [\n [\"Mailbox\", \"read, draft\"],\n [\"Knowledge base\", \"read\"],\n ],\n },\n {\n title: \"Memory\",\n open: false,\n children: \"Prefers short replies.\",\n },\n ]}\n actions={[\n <Button key=\"c\" variant=\"soft\" icon=\"settings\">\n Configure\n </Button>,\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/AgentCard"},{"name":"Alert","title":"Alert","group":"Overlays","summary":"Inline banner for a state the page is in: a warning, a failure, a note.","description":"Inline banner for a state the page is in: a warning, a failure, a note. Lives in the page, unlike a toast.\n\nProps: `tone` (\"info\" | \"success\" | \"warning\" | \"danger\" | \"highlight\"), `title`, children (one sentence), `icon` (override), `action` (one small Button), `onDismiss`.\n\n- One alert per page section, at the top of what it concerns. Two warnings: merge them.\n- Title says the state, body says the consequence or the next step.\n- Warning and danger are announced to screen readers; info and success are polite.\n- Never use an alert for something that already happened and needs no action: that's a toast.\n","components":["Alert","Button"],"code":"import { Alert, Button } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <Alert tone=\"info\" title=\"Bank feed syncs every 4 hours\" onDismiss={() => {}}>\n New transactions show up here and get matched automatically.\n </Alert>\n <Alert tone=\"success\" title=\"Domain verified\">\n halo.app is ready to send mail.\n </Alert>\n <Alert\n tone=\"warning\"\n title=\"halo.dev expires on May 4, 2026\"\n action={\n <Button size=\"sm\" variant=\"secondary\">\n Turn on auto-renew\n </Button>\n }\n >\n Auto-renew is off. If it lapses, mail on this domain stops.\n </Alert>\n <Alert\n tone=\"danger\"\n title=\"Payment failed\"\n action={\n <Button size=\"sm\" variant=\"secondary\">\n Update card\n </Button>\n }\n >\n We couldn’t charge the card ending 4242. We retry in 3 days.\n </Alert>\n <Alert tone=\"highlight\" title=\"3 agent drafts need your review\" />\n </div>\n );\n}\nexport default App;\n","path":"components/Alert"},{"name":"ApprovalCard","title":"Approval Card","group":"AI","summary":"The human-in-the-loop decision: what an agent wants to do, the exact payload, and the four answers A–D.","description":"The human-in-the-loop decision: what an agent wants to do, the exact payload, and the four answers A–D.\n\nProps: `title` (the action as one sentence), `text` (why), children (the payload, verbatim), `agent`, `project`, `family` (\"money\" | \"infra\" | \"comms\" | \"marketing\" | \"apps\" | \"legal\") or `familyLabel`, `age`, `escalation`, `trace` (node), `note`, `heading`, `onDecide(key, text)`, `decision` ({key, by, at, text}: renders the one-line record), `options` (override A–D; each {key, label, input}), `approveLabel` (relabels A), `compact`. Legacy `onApprove` / `onDeny` / `onEdit` and `state` still work.\n\n- Always four answers in this order: **A** yes, as shown · **B** yes, with a change · **C** no, discard · **D** different instruction. B and D open a text field; ⌘/Ctrl+Enter sends.\n- A–D work as keys while the card has focus.\n- The payload is exactly what will run: the full mail, the amount and recipient, the command, the DNS change before and after. Never a summary.\n- Show the risk family, the age and the escalation time. Nothing runs automatically when time is up.\n- After a decision the card collapses to one line: letter, who, when (and the change or instruction).\n- The A–D wording can be changed through `options`.\n","components":["ApprovalCard"],"code":"import * as React from \"react\";\nimport { ApprovalCard } from \"@twentyfirstmedia/ui-core\";\nimport type { ApprovalDecision } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const d = React.useState<ApprovalDecision | null>(null);\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <ApprovalCard\n agent=\"Atlas\"\n project=\"Support\"\n family=\"comms\"\n age=\"4 min\"\n title=\"Send this email to ida@northwind.io\"\n text=\"Atlas is set to drafts only, so it needs your OK.\"\n escalation=\"Escalates to the owner in 1:52 h\"\n trace={<a href=\"#\">Open trace</a>}\n decision={d[0]}\n onDecide={(k, t) => {\n d[1]({\n key: k,\n by: \"Mara Ellis\",\n at: \"09:41\",\n text: t,\n });\n }}\n >\n <div className=\"tf-approval-mail\">\n {\n \"Hi Ida,\\n\\nhappy to add 40 seats before your renewal on May 1. The quote is attached.\\n\\nBest,\\nMara\"\n }\n </div>\n </ApprovalCard>\n <ApprovalCard\n agent=\"Ledger\"\n project=\"Finance\"\n family=\"money\"\n title=\"Refund €10.00 to the card ending 4242\"\n decision={{\n key: \"C\",\n by: \"Lukas\",\n at: \"Yesterday 18:02\",\n }}\n />\n <ApprovalCard\n agent=\"Scout\"\n project=\"Domains\"\n family=\"infra\"\n title=\"Add TXT record ops-test\"\n decision={{\n key: \"B\",\n by: \"Mara Ellis\",\n at: \"08:10\",\n text: \"Use TTL 300 instead\",\n }}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ApprovalCard"},{"name":"ApprovalInbox","title":"Approval Inbox","group":"AI","summary":"All open approvals across projects, oldest first, next to the selected ApprovalCard.","description":"All open approvals across projects, oldest first, next to the selected ApprovalCard.\n\nProps: `items` ({id, title, agent, project, family, familyLabel, age, ageMin, urgent}), `selected`, `onSelect(id)`, `emptyTitle`, `emptyText`, `label`.\n\n- Oldest first: the one closest to escalation is on top; `urgent` turns its age into a warning.\n- J / K or ↓ / ↑ move the selection; the card next to it takes A–D.\n","components":["ApprovalInbox"],"code":"import * as React from \"react\";\nimport { ApprovalInbox } from \"@twentyfirstmedia/ui-core\";\nimport type { ApprovalInboxItem } from \"@twentyfirstmedia/ui-core\";\n\nconst items: ApprovalInboxItem[] = [\n {\n id: 1,\n title: \"Send reply to Northwind\",\n agent: \"Atlas\",\n project: \"Support\",\n family: \"comms\",\n age: \"2 h 10 min\",\n ageMin: 130,\n urgent: true,\n },\n {\n id: 2,\n title: \"Refund €10.00 to card ending 4242\",\n agent: \"Ledger\",\n project: \"Chatteo\",\n family: \"money\",\n age: \"38 min\",\n ageMin: 38,\n },\n {\n id: 3,\n title: \"Set daily ad budget to €30\",\n agent: \"Ads\",\n project: \"Northwind\",\n family: \"marketing\",\n age: \"12 min\",\n ageMin: 12,\n },\n {\n id: 4,\n title: \"Add TXT record ops-test\",\n agent: \"Scout\",\n project: \"Domains\",\n family: \"infra\",\n age: \"3 min\",\n ageMin: 3,\n },\n];\nfunction App() {\n const s = React.useState<string | number>(1);\n return (\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <ApprovalInbox items={items} selected={s[0]} onSelect={s[1]} />\n </div>\n );\n}\nexport default App;\n","path":"components/ApprovalInbox"},{"name":"AreaChart","title":"Area Chart","group":"Charts","summary":"Smooth stacked areas for volume over time, with a crosshair tooltip.","description":"Smooth stacked areas for volume over time, with a crosshair tooltip.\n\nProps: `labels`, `series` ({name, tint, values}), `stacked` (default true), `height`, `width`, `format(v)`, `legend`, `label`.\n\n- Areas are the light tints, edges the `-ink` lines, so the stack reads in both themes.\n- Use stacked areas when the total matters; switch to LineChart when you compare series against each other.\n- Labels thin out automatically past 12 points.\n","components":["AreaChart"],"code":"import { AreaChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">Visitors by channel</h3>\n <p className=\"tf-panel-sub\">Stacked, per week</p>\n </div>\n <AreaChart\n height={180}\n labels={[\"W14\", \"W15\", \"W16\", \"W17\", \"W18\", \"W19\", \"W20\", \"W21\"]}\n format={(v) => (v / 1000).toFixed(1) + \"k\"}\n series={[\n {\n name: \"Organic\",\n tint: \"sky\",\n values: [4200, 4600, 4400, 5100, 5600, 5300, 6100, 6500],\n },\n {\n name: \"Newsletter\",\n tint: \"butter\",\n values: [1800, 2600, 1900, 2100, 3400, 2200, 2400, 3900],\n },\n {\n name: \"Partners\",\n tint: \"mint\",\n values: [1200, 1300, 1500, 1400, 1600, 1900, 1800, 2100],\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/AreaChart"},{"name":"BackupList","title":"Backup List","group":"Admin","summary":"Admin safety lists: BackupList (snapshots with restore) and BlockList (blocked IPs, emails, accounts).","description":"Admin safety lists: `BackupList` (snapshots with restore) and `BlockList` (blocked IPs, emails, accounts).\n\nBackupList: `items` ({id, name, kind, where, retention, size, status}), `onDownload`, `onRestore`, `footer`. Restore is always confirmed (ConfirmDialog) by the app.\nBlockList: `items` ({id, value, kind, reason, by, until}), `onAdd`, `onRemove`, `addLabel`, `removeLabel`, `searchPlaceholder`, `emptyText`.\n","components":["BackupList","BlockList"],"code":"import * as React from \"react\";\nimport { BackupList, BlockList } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <BackupList\n onDownload={() => {}}\n onRestore={() => {}}\n footer=\"Kept for 30 days · restore test passed Nov 2\"\n items={[\n {\n id: 1,\n name: \"Today 03:00\",\n kind: \"Database\",\n where: \"R2 Frankfurt\",\n size: \"1.84 GB\",\n status: \"ok\",\n },\n {\n id: 2,\n name: \"Yesterday 03:00\",\n kind: \"Database\",\n where: \"R2 Frankfurt\",\n size: \"1.83 GB\",\n status: \"ok\",\n },\n {\n id: 3,\n name: \"Sun 03:00\",\n kind: \"Files\",\n where: \"R2 Frankfurt\",\n size: \"12.4 GB\",\n status: \"warn\",\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <BlockList\n onAdd={() => {}}\n onRemove={() => {}}\n items={[\n {\n id: 1,\n value: \"185.220.101.4\",\n kind: \"IP\",\n reason: \"Card testing\",\n by: \"Fraud rule\",\n until: \"Nov 15\",\n },\n {\n id: 2,\n value: \"spam@mailinator.com\",\n kind: \"Email\",\n reason: \"Chargeback\",\n by: \"Lukas\",\n },\n {\n id: 3,\n value: \"@growthhacker_xx\",\n kind: \"Account\",\n reason: \"Abuse\",\n by: \"Mara Ellis\",\n until: \"Dec 1\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/BackupList"},{"name":"BalanceCard","title":"Balance Card","group":"Admin","summary":"Prepaid balances at providers and the health of the services the shop depends on: BalanceCard and ServiceMonitor.","description":"Prepaid balances at providers and the health of the services the shop depends on: `BalanceCard` and `ServiceMonitor`.\n\nBalanceCard: `name`, `sub`, `logo` or `icon` + `tint`, `balance`, `currency`, `perDay` (burn rate → \"lasts about n days\"), `low` (warning threshold), `spark`, `status` / `statusLabel` (override), `onTopUp`, `topUpLabel`, `action`.\nServiceMonitor: `items` ({id, name, status, statusLabel, latency (ms), errorRate (%), checked, history (statuses, oldest first)}), `label`.\n","components":["BalanceCard","ServiceMonitor"],"code":"import * as React from \"react\";\nimport { BalanceCard, ServiceMonitor } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <BalanceCard\n name=\"Provider A\"\n sub=\"Followers, likes\"\n balance={1840.5}\n perDay={212}\n low={500}\n spark={[2400, 2300, 2150, 2020, 1990, 1910, 1840]}\n onTopUp={() => {}}\n />\n <BalanceCard\n name=\"Provider B\"\n sub=\"Views\"\n balance={310.2}\n perDay={96}\n low={400}\n spark={[900, 780, 640, 520, 470, 380, 310]}\n onTopUp={() => {}}\n />\n <BalanceCard\n name=\"SMS gateway\"\n sub=\"Verification codes\"\n balance={0}\n perDay={4.1}\n low={20}\n icon=\"phone\"\n tint=\"sky\"\n onTopUp={() => {}}\n />\n </div>\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <ServiceMonitor\n items={[\n {\n name: \"Checkout\",\n status: \"ok\",\n statusLabel: \"Operational\",\n latency: 182,\n errorRate: 0.2,\n checked: \"30 s ago\",\n history: [\"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\", \"ok\"],\n },\n {\n name: \"Provider A API\",\n status: \"warn\",\n statusLabel: \"Slow\",\n latency: 2310,\n errorRate: 3.4,\n checked: \"30 s ago\",\n history: [\n \"ok\",\n \"ok\",\n \"ok\",\n \"warn\",\n \"ok\",\n \"ok\",\n \"warn\",\n \"warn\",\n \"ok\",\n \"warn\",\n \"warn\",\n \"warn\",\n ],\n },\n {\n name: \"Mail delivery\",\n status: \"danger\",\n statusLabel: \"Down\",\n latency: null,\n errorRate: 100,\n checked: \"1 min ago\",\n history: [\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"ok\",\n \"danger\",\n \"danger\",\n \"danger\",\n ],\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/BalanceCard"},{"name":"BarChart","title":"Bar Chart","group":"Charts","summary":"","description":"","components":["BarChart"],"code":"import { BarChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n maxWidth: 620,\n }}\n >\n <BarChart\n data={[\n {\n label: \"Mon\",\n value: 32,\n },\n {\n label: \"Tue\",\n value: 48,\n },\n {\n label: \"Wed\",\n value: 41,\n },\n {\n label: \"Thu\",\n value: 66,\n tint: \"lilac\",\n },\n {\n label: \"Fri\",\n value: 58,\n },\n {\n label: \"Sat\",\n value: 22,\n },\n {\n label: \"Sun\",\n value: 18,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/BarChart"},{"name":"Card","title":"Card","group":"Data","summary":"","description":"","components":["Badge","Button","Card","IconButton","Stagger","TintIcon"],"code":"import * as React from \"react\";\nimport { Badge, Button, Card, IconButton, Stagger, TintIcon } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n >\n <Card\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new workspace admins.\"\n meta={[\"Mara Ellis\", \"12 steps\", \"348 users\"]}\n corner={<TintIcon icon=\"book\" tint=\"mint\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={[\n <IconButton key={1} icon=\"more\" label=\"More\" size=\"sm\" />,\n <IconButton key={2} icon=\"x\" label=\"Archive\" size=\"sm\" />,\n <IconButton key={3} icon=\"check\" label=\"Approve\" size=\"sm\" variant=\"solid\" />,\n ]}\n />\n <Card\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table for annual plans.\"\n corner={<IconButton icon=\"play\" label=\"Preview\" size=\"sm\" variant=\"outline\" />}\n status={<Badge tone=\"neutral\">86 viewing</Badge>}\n actions={[<IconButton key={1} icon=\"edit\" label=\"Edit\" size=\"sm\" variant=\"solid\" />]}\n />\n <Card\n title=\"Invoice #2041 is overdue\"\n description=\"Net-30 passed four days ago; reminder bounced.\"\n meta={[\"Rui Costa\", \"High priority\"]}\n corner={<TintIcon icon=\"mail\" tint=\"blush\" />}\n status={<Badge tone=\"warning\">Open</Badge>}\n actions={[\n <Button key={1} size=\"sm\" variant=\"soft\">\n Reply\n </Button>,\n ]}\n />\n </Stagger>\n );\n}\n","path":"components/Card"},{"name":"ChatLauncher","title":"Chat Launcher","group":"Overlays","summary":"Site-wide chrome for customer-facing apps: ChatLauncher (floating assistant with page context), CookieConsent and AnnouncementBar.","description":"Site-wide chrome for customer-facing apps: `ChatLauncher` (floating assistant with page context), `CookieConsent` and `AnnouncementBar`.\n\nChatLauncher: `title`, `context` (what the assistant is looking at), `tint`, `open` / `onOpenChange`, `unread`, `headerAction`, children (the thread), `composer`, `inline` (for previews; fixed bottom-right by default).\nCookieConsent: `title`, `text`, `policy` (link), `categories` ({id, label, text, required, default}), `onSave(choices)`, `open`, `inline`. \"Accept all\" and \"Only necessary\" carry equal weight.\nAnnouncementBar: children, `tint`, `icon`, `action`, `onDismiss` (false hides the close button).\n","components":["AnnouncementBar","ChatLauncher","ChatMessage","Composer","CookieConsent","PromptStarters"],"code":"import * as React from \"react\";\nimport {\n AnnouncementBar,\n ChatLauncher,\n ChatMessage,\n Composer,\n CookieConsent,\n PromptStarters,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const o = React.useState(true);\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <AnnouncementBar tint=\"butter\" action={<a href=\"#\">See plans</a>}>\n Free plan: 3 stagings left this month.\n </AnnouncementBar>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"end\",\n } as React.CSSProperties\n }\n >\n <CookieConsent\n inline\n policy={<a href=\"#\">Privacy policy</a>}\n categories={[\n {\n id: \"necessary\",\n label: \"Necessary\",\n text: \"Sign-in, security, cart\",\n required: true,\n },\n {\n id: \"stats\",\n label: \"Statistics\",\n text: \"Anonymous usage numbers\",\n },\n {\n id: \"marketing\",\n label: \"Marketing\",\n text: \"Ads and remarketing\",\n },\n ]}\n onSave={() => {}}\n />\n <div\n style={{\n display: \"flex\",\n justifyContent: \"flex-end\",\n }}\n >\n <ChatLauncher\n inline\n open={o[0]}\n onOpenChange={o[1]}\n title=\"Homefield assistant\"\n context=\"Looking at: Loft in HafenCity\"\n composer={<Composer placeholder=\"Ask about this listing…\" />}\n >\n <ChatMessage\n role=\"assistant\"\n name=\"Assistant\"\n text=\"This loft is about **4% above** the estimated value. Want me to draft a first message to the agent?\"\n />\n <PromptStarters\n items={[\n {\n title: \"Draft a message\",\n icon: \"mail\",\n },\n {\n title: \"Compare with similar\",\n icon: \"chart\",\n },\n ]}\n />\n </ChatLauncher>\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/ChatLauncher"},{"name":"ChatMessage","title":"Chat Message","group":"AI","summary":"One turn in a conversation with an agent: user bubble on the right, agent reply as rich text on the left.","description":"One turn in a conversation with an agent: user bubble on the right, agent reply as rich text on the left.\n\nProps: `role` (\"user\" | \"assistant\" | \"system\"), `name`, `time`, `model`, `text` (Markdown subset: paragraphs, **bold**, `code`, - lists, 1. lists, ### headings, links, ``` code blocks), `streaming` (blinking caret, no actions yet), `tint` (agent color), `sources` ({title, icon, url | onClick}), `attachments` ({name, size}), `usage` (tokens · cost text), `onRetry`, `onFeedback(\"up\" | \"down\")`, `actions` (false hides the row), `before` / `after` (slots for tool calls, reasoning, approvals).\n\n- User messages are ink pills, agent replies are plain text on the surface: the agent's words get the space.\n- Actions (copy, regenerate, thumbs) appear once streaming ends.\n- Number sources in the order they were used; keep titles short.\n- `RichText` and `CodeBlock` are exported on their own for docs, notes and logs.\n","components":["ChatMessage"],"code":"import { ChatMessage } from \"@twentyfirstmedia/ui-core\";\n\nconst md =\n 'Three invoices are overdue, **€2,520** in total:\\n\\n- **#2041** Harbor & Co · €860\\n- **#2058** Pinegate · €1,240\\n- **#2063** Orbit Foods · €420\\n\\nThe fix for the reminder job:\\n\\n```ts\\nconst due = invoices.filter(i => i.dueAt < now && !i.paid);\\nawait sendReminders(due, { tone: \"friendly\" });\\n```';\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <ChatMessage\n role=\"user\"\n name=\"Mara Ellis\"\n text=\"Which invoices are overdue? Fix the reminder job too.\"\n attachments={[\n {\n name: \"reminders.ts\",\n size: \"4 KB\",\n },\n ]}\n />\n <ChatMessage\n role=\"assistant\"\n name=\"Ledger\"\n tint=\"butter\"\n time=\"09:41\"\n model=\"Claude Sonnet 5.5\"\n text={md}\n sources={[\n {\n title: \"Invoices · due\",\n icon: \"card\",\n },\n {\n title: \"jobs/reminders.ts\",\n icon: \"code\",\n },\n ]}\n usage=\"1,842 tokens · €0.01\"\n onRetry={() => {}}\n />\n <ChatMessage role=\"system\" text=\"Ledger switched to Claude Haiku 5.5\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Ledger\"\n tint=\"butter\"\n streaming\n text={\"Sending reminders now. Harbor & Co first because\"}\n />\n </div>\n );\n}\n","path":"components/ChatMessage"},{"name":"ChatWorkspace","title":"Chat Workspace","group":"AI","summary":"Messenger-style workspace for agents and people: ChatWorkspace (list | thread | details), ChatList, SearchHits, ThinkingLine, FeedbackRating.","description":"Messenger-style workspace for agents and people: `ChatWorkspace` (list | thread | details), `ChatList`, `SearchHits`, `ThinkingLine`, `FeedbackRating`.\n\nChatWorkspace: `list`, `thread`, `details` (third column, hidden on narrow screens).\nChatList: `groups` ({title, mark (tint), items: {id, name, sub, time, tint, avatar, status, unread, pinned}}), `selected`, `onSelect`, `onSearch`, `filters`, `searchPlaceholder`. Order: pinned first, then one group per project, then people.\nSearchHits: `items` ({id, chat, time, who, text}), `query` (marked), `onOpen`.\nThinkingLine: `text` (\"Reading 3 mails…\"). FeedbackRating: `value` / `onRate`, `onSubmit(value, comment)`, `label`, `placeholder`, `thanks`, `askOnUp`.\n","components":["ChatList","ChatMessage","ChatWorkspace","FeedbackRating","SearchHits","ThinkingLine"],"code":"import * as React from \"react\";\nimport {\n ChatList,\n ChatMessage,\n ChatWorkspace,\n FeedbackRating,\n SearchHits,\n ThinkingLine,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { ChatListProps } from \"@twentyfirstmedia/ui-core\";\n\nconst groups: ChatListProps[\"groups\"] = [\n {\n title: \"Pinned\",\n items: [\n {\n id: \"mb\",\n name: \"Main Brain\",\n sub: \"Morning briefing is ready\",\n time: \"08:00\",\n tint: \"lilac\",\n status: \"ok\",\n pinned: true,\n },\n ],\n },\n {\n title: \"Northwind\",\n mark: \"sky\",\n items: [\n {\n id: \"atlas\",\n name: \"Atlas\",\n sub: \"Waiting for your approval\",\n time: \"09:41\",\n tint: \"sky\",\n status: \"signal\",\n unread: 1,\n },\n {\n id: \"ads\",\n name: \"Ads\",\n sub: \"Budget at 82%\",\n time: \"09:12\",\n tint: \"mint\",\n status: \"warn\",\n },\n ],\n },\n {\n title: \"People\",\n items: [\n {\n id: \"lukas\",\n name: \"Lukas Weber\",\n sub: \"Sounds good\",\n time: \"Mon\",\n avatar: true,\n },\n ],\n },\n];\nfunction App() {\n const s = React.useState(\"atlas\");\n return (\n <div\n style={{\n height: 580,\n }}\n >\n <ChatWorkspace\n className=\"demo-fill\"\n list={<ChatList groups={groups} selected={s[0]} onSelect={s[1]} />}\n thread={\n <div\n className=\"tf-col\"\n style={{\n gap: 20,\n }}\n >\n <ChatMessage role=\"user\" text=\"Draft a reply to Northwind\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Atlas\"\n tint=\"sky\"\n time=\"09:41\"\n text=\"Drafted. It quotes **40 seats at €24** prorated to May 1.\"\n />\n <ThinkingLine text=\"Reading 3 messages…\" />\n <FeedbackRating label=\"Was this useful?\" />\n </div>\n }\n details={\n <SearchHits\n query=\"seats\"\n items={[\n {\n id: 1,\n chat: \"Atlas\",\n time: \"09:41\",\n who: \"Atlas\",\n text: \"It quotes 40 seats at €24 prorated to May 1.\",\n },\n {\n id: 2,\n chat: \"Main Brain\",\n time: \"Mon\",\n text: \"Northwind asked about seats twice this quarter.\",\n },\n ]}\n />\n }\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ChatWorkspace"},{"name":"Combobox","title":"Combobox","group":"Forms","summary":"Searchable select for long lists, single or multiple, with optional create.","description":"Searchable select for long lists, single or multiple, with optional create. `TagInput` is the free-text sibling for tags.\n\nCombobox props: `label`, `options` (strings or {value, label, hint, icon, avatar}), `value`/`defaultValue`, `onChange`, `multiple`, `creatable`, `placeholder`, `hint`, `error`, `required`.\nTagInput props: `label`, `value`/`defaultValue` (string[]), `onChange`, `placeholder`, `hint`.\n\n- Keyboard: ↑ ↓ to move, Enter to pick, Esc to close, Backspace removes the last token.\n- Up to five fixed options without search: use RadioGroup or a native Select instead.\n- Selected items in `multiple` show as tokens inside the field; keep their labels short.\n- Show a `hint` on people (team, email) so two people with the same name can be told apart.\n","components":["Combobox","TagInput"],"code":"import * as React from \"react\";\nimport { Combobox, TagInput } from \"@twentyfirstmedia/ui-core\";\n\nconst TZ = [\n \"Europe/Berlin (UTC+1)\",\n \"Europe/London (UTC+0)\",\n \"Asia/Dubai (UTC+4)\",\n \"Asia/Singapore (UTC+8)\",\n \"America/New_York (UTC−5)\",\n \"America/Los_Angeles (UTC−8)\",\n];\nconst PEOPLE = [\n {\n value: \"ava\",\n label: \"Ava Vogel\",\n hint: \"Engineering\",\n avatar: true,\n },\n {\n value: \"ben\",\n label: \"Ben Okafor\",\n hint: \"Sales\",\n avatar: true,\n },\n {\n value: \"clara\",\n label: \"Clara Haddad\",\n hint: \"Design\",\n avatar: true,\n },\n {\n value: \"elif\",\n label: \"Elif Sato\",\n hint: \"Engineering\",\n avatar: true,\n },\n];\nfunction App() {\n const t = React.useState<string | string[] | null>(\"Asia/Dubai (UTC+4)\"),\n p = React.useState<string | string[] | null>([\"ava\", \"clara\"]),\n tags = React.useState([\"urgent\", \"q3\"]);\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignContent: \"start\",\n } as React.CSSProperties\n }\n >\n <Combobox\n label=\"Time zone\"\n options={TZ}\n value={t[0]}\n onChange={t[1]}\n placeholder=\"Search time zones\"\n />\n <Combobox\n label=\"Reviewers\"\n multiple\n options={PEOPLE}\n value={p[0]}\n onChange={p[1]}\n placeholder=\"Add people\"\n />\n <Combobox\n label=\"Label\"\n creatable\n options={[\"Customers\", \"Agents\", \"Finance\"]}\n placeholder=\"Pick or create\"\n hint=\"Type a new name to create it\"\n />\n <TagInput label=\"Tags\" value={tags[0]} onChange={tags[1]} />\n </div>\n );\n}\nexport default App;\n","path":"components/Combobox"},{"name":"CommandPalette","title":"Command Palette","group":"Overlays","summary":"","description":"","components":["CommandPalette"],"code":"import { CommandPalette } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 430,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <CommandPalette\n inline\n onClose={() => {}}\n items={[\n {\n id: 1,\n group: \"Pages\",\n label: \"Invoices\",\n hint: \"Finance\",\n icon: \"card\",\n },\n {\n id: 2,\n group: \"Pages\",\n label: \"Pipeline\",\n hint: \"Customers\",\n icon: \"trend\",\n },\n {\n id: 3,\n group: \"Actions\",\n label: \"New invoice\",\n icon: \"card\",\n tint: \"butter\",\n },\n {\n id: 4,\n group: \"Actions\",\n label: \"Compose mail\",\n icon: \"edit\",\n tint: \"sky\",\n },\n {\n id: 5,\n group: \"Actions\",\n label: \"New agent\",\n icon: \"bot\",\n tint: \"lilac\",\n },\n {\n id: 6,\n group: \"Records\",\n label: \"Northwind\",\n hint: \"Customer · Team\",\n icon: \"users\",\n tint: \"sky\",\n searchOnly: true,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/CommandPalette"},{"name":"Composer","title":"Composer","group":"AI","summary":"Message input for agents: grows with the text, attachments, slash commands, a model picker, and Send that turns into Stop while the agent works.","description":"Message input for agents: grows with the text, attachments, slash commands, a model picker, and Send that turns into Stop while the agent works.\n\nComposer props: `value` / `onChange`, `onSend(text, attachments)`, `busy`, `onStop`, `placeholder`, `models` ({id, label, hint}) + `model` + `onModel`, `commands` ({name, hint, icon, insert}) + `onCommand`, `attachments` + `onAttach(FileList)` + `onRemoveAttachment(i)`, `hint`, `left` (extra tools).\nAlso here: `PromptStarters` (`items` {icon, tint, title, text, prompt}, `onPick`), `ModelPicker`, `AgentStatus` (`status`, `step`), `UsageMeter` (`used`, `limit`, `format`, `label`, `caption`), `AgentMark`, `TypingDots`.\n\n- Enter sends, Shift+Enter adds a line; typing \"/\" opens commands (↑ ↓ Enter).\n- Drop files anywhere on the composer to attach them.\n- Stop must always work: cancel streaming and mark running tools as stopped.\n- Show starters only on an empty conversation, three or four, written as things people actually ask.\n","components":["AgentStatus","Composer","PromptStarters","UsageMeter"],"code":"import * as React from \"react\";\nimport { AgentStatus, Composer, PromptStarters, UsageMeter } from \"@twentyfirstmedia/ui-core\";\n\nconst MODELS = [\n {\n id: \"opus\",\n label: \"Claude Opus 5.5\",\n hint: \"Deepest reasoning\",\n },\n {\n id: \"sonnet\",\n label: \"Claude Sonnet 5.5\",\n hint: \"Balanced default\",\n },\n {\n id: \"haiku\",\n label: \"Claude Haiku 5.5\",\n hint: \"Fast and cheap\",\n },\n];\nfunction App() {\n const m = React.useState(\"sonnet\"),\n f = React.useState([\n {\n name: \"Q1-report.pdf\",\n },\n ]),\n busy = React.useState(false);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <PromptStarters\n items={[\n {\n icon: \"edit\",\n tint: \"sky\",\n title: \"Draft a reply\",\n text: \"To the latest customer email\",\n },\n {\n icon: \"file\",\n tint: \"mint\",\n title: \"Summarize today\",\n text: \"Runs, approvals, costs\",\n },\n {\n icon: \"search\",\n tint: \"butter\",\n title: \"Ask the docs\",\n text: \"Search the knowledge base\",\n },\n ]}\n />\n <Composer\n placeholder=\"Ask Atlas something, or type / for commands\"\n models={MODELS}\n model={m[0]}\n onModel={m[1]}\n attachments={f[0]}\n onAttach={(fl) => {\n f[1](f[0].concat(Array.prototype.slice.call(fl)));\n }}\n onRemoveAttachment={(i) => {\n f[1](f[0].filter((_, k) => k !== i));\n }}\n commands={[\n {\n name: \"summarize\",\n hint: \"Summarize today\",\n icon: \"file\",\n },\n {\n name: \"draft\",\n hint: \"Draft a reply\",\n icon: \"edit\",\n },\n {\n name: \"clear\",\n hint: \"New conversation\",\n icon: \"trash\",\n },\n ]}\n busy={busy[0]}\n onSend={() => {\n busy[1](true);\n setTimeout(() => {\n busy[1](false);\n }, 2500);\n }}\n onStop={() => {\n busy[1](false);\n }}\n />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <AgentStatus\n status={busy[0] ? \"Thinking\" : \"Idle\"}\n step={busy[0] ? \"Reading 3 messages\" : \"Drafts only\"}\n />\n <div\n style={{\n width: 200,\n }}\n >\n <UsageMeter label=\"Context\" used={21} limit={200} format={(v) => v + \"k\"} />\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/Composer"},{"name":"ConfigPRSummary","title":"Config PR Summary","group":"AI","summary":"Platform structure: ConfigPRSummary (a proposed config change in plain language) and ModuleRegistry (installed modules).","description":"Platform structure: `ConfigPRSummary` (a proposed config change in plain language) and `ModuleRegistry` (installed modules).\n\nConfigPRSummary: `title`, `meta`, `status` + `statusLabel`, `summary`, `items` ({kind: \"project\" | \"team\" | \"agent\" | \"tool\" | \"trigger\" | \"file\", name, detail, op: \"add\" | \"change\" | \"remove\"}), `actions`.\nModuleRegistry: `items` ({id, name, version, text, icon, core, enabled, adds}), `onToggle(module)`, `coreLabel`.\n","components":["Button","ConfigPRSummary","ModuleRegistry"],"code":"import * as React from \"react\";\nimport { Button, ConfigPRSummary, ModuleRegistry } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <ConfigPRSummary\n title=\"New team: Support for Chatteo\"\n meta=\"Proposed by Main Brain · PR #212\"\n status=\"signal\"\n statusLabel=\"Needs review\"\n summary=\"Adds a support team with two agents. Both start in drafts-only mode.\"\n items={[\n {\n kind: \"team\",\n name: \"chatteo/support\",\n },\n {\n kind: \"agent\",\n name: \"Iris\",\n detail: \"Mailbox (read, draft) · Knowledge base\",\n },\n {\n kind: \"agent\",\n name: \"Nora\",\n detail: \"WhatsApp (reply within 24 h)\",\n },\n {\n kind: \"trigger\",\n name: \"New mail to hello@chatteo.de\",\n op: \"change\",\n },\n ]}\n actions={[\n <Button key=\"v\" variant=\"secondary\" size=\"sm\">\n View PR\n </Button>,\n <Button key=\"m\" size=\"sm\" icon=\"check\">\n Approve\n </Button>,\n ]}\n />\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <ModuleRegistry\n onToggle={() => {}}\n items={[\n {\n id: \"chats\",\n name: \"Chats\",\n version: \"1.4.0\",\n text: \"Agents and people in one place\",\n core: true,\n icon: \"mail\",\n },\n {\n id: \"crm\",\n name: \"CRM\",\n version: \"0.9.2\",\n text: \"Companies, contacts, pipeline\",\n enabled: true,\n icon: \"users\",\n adds: [\"Nav\", \"Tool\", \"Jobs\"],\n },\n {\n id: \"brand\",\n name: \"Brand studio\",\n version: \"0.6.0\",\n text: \"Brand packages for customers\",\n enabled: false,\n icon: \"sparkle\",\n adds: [\"Nav\", \"Tool\"],\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/ConfigPRSummary"},{"name":"ConfirmDialog","title":"Confirm Dialog","group":"Overlays","summary":"Asks before a destructive or hard-to-undo action.","description":"Asks before a destructive or hard-to-undo action. Focus starts on Cancel; Esc cancels.\n\nProps: `open`, `title` (a question naming the thing), `text` (what happens), `danger`, `icon`, `confirmLabel` (a verb: \"Delete domain\", never \"OK\"), `cancelLabel`, `confirmText` (type-to-confirm), `onConfirm`, `onCancel`.\n\n- Prefer undo over confirm: removing a list item is instant with an Undo toast; confirm only when undo is impossible or the blast radius is large.\n- Use `confirmText` only for irreversible deletes of whole things (a domain, a workspace).\n- Renders into document.body, so it sits above every stacking context.\n","components":["ConfirmDialog"],"code":"import { ConfirmDialog } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 360,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n transform: \"translateZ(0)\",\n }}\n >\n <ConfirmDialog\n open\n danger\n title=\"Delete halo.dev?\"\n text=\"DNS records, mailboxes and certificates for this domain are removed. This can’t be undone.\"\n confirmText=\"halo.dev\"\n confirmLabel=\"Delete domain\"\n onCancel={() => {}}\n onConfirm={() => {}}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ConfirmDialog"},{"name":"ConnectionCard","title":"Connection Card","group":"Data","summary":"Connected services and the live connection: ConnectionCard and ConnectionBanner.","description":"Connected services and the live connection: `ConnectionCard` and `ConnectionBanner`.\n\nConnectionCard: `name`, `icon` or `logo` (node, e.g. the service's mark), `text`, `status` (\"connected\" | \"expiring\" | \"error\" | \"missing\"), `statusLabel`, `action`.\nConnectionBanner: `state` (\"reconnecting\" | \"offline\" | \"online\" hides it), `seconds`, `text`, `onRetry`. Thin bar under the top bar.\n","components":["Button","ConnectionBanner","ConnectionCard"],"code":"import { Button, ConnectionBanner, ConnectionCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <ConnectionBanner state=\"reconnecting\" seconds={4} onRetry={() => {}} />\n <ConnectionCard\n name=\"Stripe · FollowerX GmbH\"\n icon=\"card\"\n text=\"Payments and refunds · 3 agents\"\n status=\"connected\"\n statusLabel=\"Connected\"\n action={\n <Button size=\"sm\" variant=\"soft\">\n Manage\n </Button>\n }\n />\n <ConnectionCard\n name=\"Google Ads\"\n icon=\"trend\"\n text=\"Budgets and campaigns · 1 agent\"\n status=\"expiring\"\n statusLabel=\"Expires in 5 days\"\n action={<Button size=\"sm\">Renew</Button>}\n />\n <ConnectionCard\n name=\"Docs workspace\"\n icon=\"book\"\n text=\"Release notes · Echo\"\n status=\"error\"\n statusLabel=\"Error\"\n action={<Button size=\"sm\">Reconnect</Button>}\n />\n <ConnectionBanner state=\"offline\" />\n </div>\n );\n}\n","path":"components/ConnectionCard"},{"name":"CouponCard","title":"Coupon Card","group":"Commerce","summary":"Discount codes with their usage and what they earned.","description":"Discount codes with their usage and what they earned.\n\nProps: `code`, `kind` (\"percent\" | \"fixed\"), `value` (percent or amount), `currency`, `used`, `limit`, `until`, `min` (minimum order), `revenue`, `text`, `status` + `statusLabel`, `onCopy`, `menu`.\n","components":["CouponCard"],"code":"import * as React from \"react\";\nimport { CouponCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <CouponCard\n code=\"SPRING20\"\n kind=\"percent\"\n value={20}\n used={312}\n limit={500}\n until=\"Apr 30\"\n min={10}\n revenue={8420}\n text=\"All Instagram packages\"\n menu={[\n {\n label: \"Edit\",\n icon: \"edit\",\n },\n {\n label: \"Duplicate\",\n icon: \"copy\",\n },\n \"-\",\n {\n label: \"Deactivate\",\n icon: \"pause\",\n danger: true,\n },\n ]}\n />\n <CouponCard\n code=\"WELCOME5\"\n kind=\"fixed\"\n value={5}\n used={1204}\n text=\"First order\"\n revenue={21890}\n />\n <CouponCard\n code=\"BLACKFRIDAY\"\n kind=\"percent\"\n value={30}\n used={1000}\n limit={1000}\n until=\"Nov 29\"\n statusLabel=\"Used up\"\n />\n </div>\n );\n}\n","path":"components/CouponCard"},{"name":"DataTable","title":"Data Table","group":"Data","summary":"Table with the features list pages need: sorting, row selection with a bulk action bar, column toggles, sticky header and expandable rows.","description":"Table with the features list pages need: sorting, row selection with a bulk action bar, column toggles, sticky header and expandable rows. `FilterBar` sits above it.\n\nDataTable props: `columns` ({key, label, render, align, sortable, sortValue(row), hideable, hidden, width}), `rows`, `rowKey` (\"id\"), `selectable`, `bulkActions` ({label, icon, danger, onClick(rows, clear)}), `renderExpanded(row)`, `stickyHeader`, `maxHeight`, `defaultSort` ({key, dir}), `toolbar`, `columnToggle` (false hides), `empty`.\nFilterBar props: `filters` ({key, label, icon, options}), `value` / `onChange` ({key: option}), `search` (placeholder), `query` / `onQuery`, `right`.\n\n- Sort cycles ascending → descending → off; the arrow only shows on the sorted or hovered column.\n- The bulk bar floats at the bottom of the table while rows are selected; destructive actions are red and go through an Undo toast or ConfirmDialog.\n- Active filters are ink chips you can click to change or × to remove. \"Clear all\" appears from two filters on.\n- The header checkbox shows a dash when some, not all, rows are selected.\n- Put it in `tf-panel tf-panel-flush` so cells line up at 24px with other panels.\n","components":["Avatar","Badge","DataTable","FilterBar"],"code":"import * as React from \"react\";\nimport { Avatar, Badge, DataTable, FilterBar } from \"@twentyfirstmedia/ui-core\";\nimport type { Tone } from \"@twentyfirstmedia/ui-core\";\n\nconst ROWS = [\n {\n id: 1,\n name: \"Ava Vogel\",\n email: \"ava@halo.app\",\n role: \"Editor\",\n team: \"Engineering\",\n status: \"Active\",\n seats: 3,\n },\n {\n id: 2,\n name: \"Ben Okafor\",\n email: \"ben@halo.app\",\n role: \"Viewer\",\n team: \"Sales\",\n status: \"Invited\",\n seats: 1,\n },\n {\n id: 3,\n name: \"Clara Haddad\",\n email: \"clara@halo.app\",\n role: \"Admin\",\n team: \"Design\",\n status: \"Active\",\n seats: 5,\n },\n {\n id: 4,\n name: \"Elif Sato\",\n email: \"elif@halo.app\",\n role: \"Editor\",\n team: \"Engineering\",\n status: \"Away\",\n seats: 2,\n },\n {\n id: 5,\n name: \"Jonah Petit\",\n email: \"jonah@halo.app\",\n role: \"Editor\",\n team: \"Growth\",\n status: \"Active\",\n seats: 4,\n },\n];\nconst TONE: Record<string, Tone> = {\n Active: \"success\",\n Invited: \"info\",\n Away: \"warning\",\n};\nfunction App() {\n const f = React.useState<Record<string, string>>({\n Team: \"Engineering\",\n }),\n q = React.useState(\"\");\n const rows = ROWS.filter(\n (r) =>\n (!f[0].Team || r.team === f[0].Team) &&\n (!f[0].Status || r.status === f[0].Status) &&\n (!q[0] || r.name.toLowerCase().indexOf(q[0].toLowerCase()) >= 0),\n );\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <FilterBar\n search=\"Search people\"\n query={q[0]}\n onQuery={q[1]}\n value={f[0]}\n onChange={f[1]}\n filters={[\n {\n key: \"Team\",\n label: \"Team\",\n options: [\"Design\", \"Engineering\", \"Growth\", \"Sales\"],\n },\n {\n key: \"Status\",\n label: \"Status\",\n options: [\"Active\", \"Invited\", \"Away\"],\n },\n ]}\n />\n <div className=\"tf-panel tf-panel-flush\">\n <DataTable\n rows={rows}\n selectable\n stickyHeader\n defaultSort={{\n key: \"name\",\n dir: \"asc\",\n }}\n toolbar={<span className=\"tf-card-desc\">{rows.length + \" of \" + ROWS.length}</span>}\n bulkActions={[\n {\n label: \"Change role\",\n icon: \"edit\",\n onClick: () => {},\n },\n {\n label: \"Remove\",\n icon: \"trash\",\n danger: true,\n onClick: (r, clear) => {\n clear();\n },\n },\n ]}\n renderExpanded={(r) => (\n <span className=\"tf-card-desc\">{r.email + \" · \" + r.seats + \" seats assigned\"}</span>\n )}\n columns={[\n {\n key: \"name\",\n label: \"Name\",\n sortable: true,\n hideable: false,\n render: (r) => (\n <span\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Avatar name={r.name} size={28} />\n <b>{r.name}</b>\n </span>\n ),\n },\n {\n key: \"role\",\n label: \"Role\",\n sortable: true,\n },\n {\n key: \"team\",\n label: \"Team\",\n sortable: true,\n },\n {\n key: \"status\",\n label: \"Status\",\n sortable: true,\n render: (r) => <Badge tone={TONE[r.status]}>{r.status}</Badge>,\n },\n {\n key: \"seats\",\n label: \"Seats\",\n align: \"right\",\n sortable: true,\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/DataTable"},{"name":"DatePicker","title":"Date Picker","group":"Forms","summary":"Date field that opens a month calendar in a Popover.","description":"Date field that opens a month calendar in a Popover. `range` picks a from–to span and adds presets.\n\nProps: `label`, `value`/`defaultValue` (Date, or {from, to} with `range`), `onChange`, `range`, `presets` (false to hide), `min`, `max`, `today`, `placeholder`, `hint`, `error`, `required`. `Calendar` is the bare month grid for inline use.\n\n- Weeks start on Monday; dates read DD.MM.YYYY. Today has a dot under the number.\n- A range closes after the second click; a single date closes after one.\n- Presets cover the ranges people actually pick (7, 30, 90 days, this year); keep them to four.\n","components":["Calendar","DatePicker"],"code":"import * as React from \"react\";\nimport { Calendar, DatePicker } from \"@twentyfirstmedia/ui-core\";\nimport type { DateRange } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const d = React.useState<Date | DateRange | null>(new Date(2026, 3, 16)),\n r = React.useState<Date | DateRange | null>({\n from: new Date(2026, 3, 6),\n to: new Date(2026, 3, 16),\n });\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignContent: \"start\",\n } as React.CSSProperties\n }\n >\n <DatePicker label=\"Due date\" value={d[0]} onChange={d[1]} today={new Date(2026, 3, 16)} />\n <DatePicker\n label=\"Report range\"\n range\n value={r[0]}\n onChange={r[1]}\n today={new Date(2026, 3, 16)}\n hint=\"Presets on the left\"\n />\n <div\n style={{\n gridColumn: \"1 / -1\",\n }}\n >\n <div\n className=\"tf-popover\"\n style={{\n position: \"static\",\n width: 476,\n maxWidth: \"100%\",\n animation: \"none\",\n }}\n >\n <div className=\"tf-dp with-presets\">\n <div className=\"tf-dp-presets\">\n {[\"Last 7 days\", \"Last 30 days\", \"Last 90 days\", \"This year\"].map((x, i) => (\n <button key={x} type=\"button\" className={\"tf-dp-preset\" + (i === 0 ? \" on\" : \"\")}>\n {x}\n </button>\n ))}\n </div>\n <Calendar range value={r[0]} today={new Date(2026, 3, 16)} onChange={r[1]} />\n </div>\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/DatePicker"},{"name":"DnsRecordTable","title":"Dns Record Table","group":"Data","summary":"Domains and DNS: DnsRecordTable, DomainList (sorted by what needs attention) and MailAuthStatus (MX, SPF, DKIM, DMARC lights).","description":"Domains and DNS: `DnsRecordTable`, `DomainList` (sorted by what needs attention) and `MailAuthStatus` (MX, SPF, DKIM, DMARC lights).\n\nDnsRecordTable: `records` ({id, type, name, content, ttl, proxy: true | false | null}), `rowMenu(record)` (Menu items), `selectable`, `bulkActions`, `toolbar`. Built on DataTable.\nDomainList: `items` ({name, registrar, dns, days, ssl, sslLabel}), `onOpen`. Under 30 days warns, under 7 days is danger.\nMailAuthStatus: `rows` ({domain, mx, spf, dkim, dmarc: {status, label}}), `columns`, `onOpen(row, column)`.\n","components":["DnsRecordTable","DomainList","MailAuthStatus"],"code":"import * as React from \"react\";\nimport { DnsRecordTable, DomainList, MailAuthStatus } from \"@twentyfirstmedia/ui-core\";\n\nconst recs = [\n {\n id: 1,\n type: \"A\",\n name: \"@\",\n content: \"95.217.14.80\",\n ttl: \"Auto\",\n proxy: true,\n },\n {\n id: 2,\n type: \"CNAME\",\n name: \"www\",\n content: \"northwind.de\",\n ttl: \"Auto\",\n proxy: true,\n },\n {\n id: 3,\n type: \"MX\",\n name: \"@\",\n content: \"10 mx.halo-mail.net\",\n ttl: \"3600\",\n proxy: null,\n },\n {\n id: 4,\n type: \"TXT\",\n name: \"@\",\n content: \"v=spf1 include:_spf.halo-mail.net ~all\",\n ttl: \"3600\",\n proxy: null,\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush\">\n <DnsRecordTable\n records={recs}\n rowMenu={() => [\n {\n label: \"Edit\",\n icon: \"edit\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <DomainList\n items={[\n {\n name: \"halo.dev\",\n registrar: \"Cloudflare\",\n dns: \"Cloudflare\",\n days: 18,\n ssl: \"ok\",\n sslLabel: \"Valid\",\n },\n {\n name: \"northwind.de\",\n registrar: \"one.com\",\n dns: \"Cloudflare\",\n days: 210,\n ssl: \"ok\",\n sslLabel: \"Valid\",\n },\n {\n name: \"chatteo.de\",\n registrar: \"one.com\",\n dns: \"one.com\",\n days: 5,\n ssl: \"warn\",\n sslLabel: \"Renewing\",\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <MailAuthStatus\n rows={[\n {\n domain: \"northwind.de\",\n mx: {\n status: \"ok\",\n label: \"ok\",\n },\n spf: {\n status: \"ok\",\n label: \"ok\",\n },\n dkim: {\n status: \"ok\",\n label: \"ok\",\n },\n dmarc: {\n status: \"warn\",\n label: \"soft\",\n },\n },\n {\n domain: \"chatteo.de\",\n mx: {\n status: \"ok\",\n label: \"ok\",\n },\n spf: {\n status: \"danger\",\n label: \"missing\",\n },\n dkim: {\n status: \"idle\",\n label: \"—\",\n },\n dmarc: {\n status: \"danger\",\n label: \"missing\",\n },\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\n","path":"components/DnsRecordTable"},{"name":"Drawer","title":"Drawer","group":"Overlays","summary":"Right-side sheet for a record's details, sliding in over the page.","description":"Right-side sheet for a record's details, sliding in over the page.\n\nProps: `title`, `subtitle`, `avatar` (name for an Avatar), `onClose`, `width`, `inline` (render inside a positioned container, for previews), children.\n\n- Use for a record you look at without leaving the list: a customer, an invoice, a run.\n- Traps keyboard focus, closes on Esc and backdrop click, returns focus to the opener.\n- Put Tabs right under the badges when the record has more than one view.\n","components":["Badge","Drawer","StatTile","Tabs"],"code":"import * as React from \"react\";\nimport { Badge, Drawer, StatTile, Tabs } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 480,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <Drawer\n inline\n title=\"Northwind\"\n subtitle=\"Logistics · Hamburg · customer since 2021\"\n avatar=\"Northwind\"\n onClose={() => {}}\n >\n <div className=\"tf-row\">\n <Badge tone=\"success\">Good</Badge>\n <Badge tone=\"info\" dot={false}>\n Team · 24 seats\n </Badge>\n </div>\n <Tabs block items={[\"Overview\", \"Invoices\", \"Mail\"]} />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€1,240\" label=\"MRR\" />\n <StatTile tint=\"mint\" value=\"€14,880\" label=\"Paid to date\" />\n </div>\n </Drawer>\n </div>\n );\n}\n","path":"components/Drawer"},{"name":"EmailPreview","title":"Email Preview","group":"Admin","summary":"How a mail will look in the inbox and when opened, on desktop and phone.","description":"How a mail will look in the inbox and when opened, on desktop and phone.\n\nProps: `fromName`, `from`, `subject`, `preheader`, `html` (rendered in a sandboxed iframe, no scripts) or children, `device` / `onDevice` (\"desktop\" | \"mobile\"), `checks` ({status, label}).\n\n- The body sits on `paper` and stays white in dark mode, like most mail clients.\n","components":["EmailPreview"],"code":"import { EmailPreview } from \"@twentyfirstmedia/ui-core\";\n\nconst html =\n '<div style=\"font-family:Arial,sans-serif;max-width:560px;margin:0 auto;padding:32px 24px;color:#141414\"><div style=\"font-weight:800;font-size:20px;margin-bottom:24px\">Northwind</div><h1 style=\"font-size:24px;margin:0 0 12px\">Your order is on its way</h1><p style=\"font-size:15px;line-height:24px;color:#444\">Hi Ida, your 1,000 Instagram followers start arriving within the next hour. Delivery spreads over 24 hours so it looks natural.</p><a style=\"display:inline-block;margin-top:16px;padding:12px 20px;border-radius:999px;background:#141414;color:#fff;text-decoration:none;font-weight:700\">Track order</a></div>';\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <EmailPreview\n from=\"hello@northwind.de\"\n fromName=\"Northwind\"\n subject=\"Your order is on its way\"\n preheader=\"Delivery starts within the hour\"\n html={html}\n checks={[\n {\n status: \"ok\",\n label: \"Links work\",\n },\n {\n status: \"ok\",\n label: \"Unsubscribe link\",\n },\n {\n status: \"warn\",\n label: \"Subject is 46 characters, may cut on phones\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/EmailPreview"},{"name":"EventCard","title":"Event Card","group":"Data","summary":"","description":"","components":["EventCard"],"code":"import { EventCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n maxWidth: 300,\n }}\n >\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Tue, Apr 14\" time=\"10:30\">\n Roadmap check-in with design and growth; bring last sprint numbers.\n </EventCard>\n <EventCard kind=\"deadline\" label=\"Deadline\" date=\"Fri, Apr 17\" time=\"17:00\">\n Send Q2 invoices to all annual customers.\n </EventCard>\n </div>\n );\n}\n","path":"components/EventCard"},{"name":"ExportDialog","title":"Export Dialog","group":"Overlays","summary":"Exporting a filtered list: format, period, columns, progress and download.","description":"Exporting a filtered list: format, period, columns, progress and download.\n\nProps: `open` / `onClose` (or `inline`), `title`, `text`, `count`, `formats`, `columns` ({key, label, default: false = off}), `ranges` (false hides the period), `range`, `note`, `onExport(options, progress)` (return a promise; call `progress(0–100)`), `onDownload` or `download` (node), `doneText`.\n\n- Columns with personal data start unchecked.\n","components":["ExportDialog"],"code":"import { ExportDialog } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n display: \"grid\",\n placeItems: \"center\",\n padding: 24,\n background: \"var(--canvas)\",\n borderRadius: \"var(--radius-lg)\",\n }}\n >\n <ExportDialog\n inline\n title=\"Export orders\"\n count={4812}\n formats={[\"csv\", \"xlsx\", \"json\"]}\n columns={[\n {\n key: \"id\",\n label: \"Order number\",\n },\n {\n key: \"date\",\n label: \"Date\",\n },\n {\n key: \"customer\",\n label: \"Customer\",\n },\n {\n key: \"product\",\n label: \"Product\",\n },\n {\n key: \"net\",\n label: \"Net\",\n },\n {\n key: \"vat\",\n label: \"VAT\",\n },\n {\n key: \"email\",\n label: \"Email\",\n default: false,\n },\n {\n key: \"ip\",\n label: \"IP address\",\n default: false,\n },\n ]}\n note=\"Personal data is only included when you pick those columns.\"\n onExport={(o, prog) =>\n new Promise((r) => {\n let x = 0;\n const t = setInterval(() => {\n x += 20;\n prog(x);\n if (x >= 100) {\n clearInterval(t);\n r();\n }\n }, 300);\n })\n }\n onClose={() => {}}\n />\n </div>\n );\n}\n","path":"components/ExportDialog"},{"name":"FileDrop","title":"File Drop","group":"Forms","summary":"Dashed drop zone plus an upload list.","description":"Dashed drop zone plus an upload list. Click, Enter or drop to add files.\n\n- `onFiles(files)` hands over the real `File` objects the person picked or dropped, after `accept` and `multiple`; upload them yourself. Dropped files that do not match `accept` go to `onReject`; say so with an inline Alert, not a toast.\n- Progress is yours to report: control `files` (`{ id, name, size, progress, file }`) and set `progress` from your upload. Below 100 a row shows a progress bar and a cancel button, at 100 it reads \"uploaded\". With `onFiles` and no `files`, rows show only the size. `onRemove` tells you a file was removed or its upload cancelled.\n- Without `onFiles` the list animates made-up progress, as a demo of the look (`simulateProgress` turns that on or off).\n- The zone turns mint while a file is held over it, so people know the drop will land.\n- Say what is accepted in the hint (types, size).\n","components":["FileDrop"],"code":"import * as React from \"react\";\nimport { FileDrop } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const f = React.useState([\n {\n id: \"a\",\n name: \"Receipt-April-Hetzner.pdf\",\n size: 184320,\n progress: 100,\n },\n {\n id: \"b\",\n name: \"Team-offsite-photos.zip\",\n size: 24117248,\n progress: 46,\n },\n ]);\n return (\n <div className=\"tf-panel\">\n <FileDrop\n files={f[0]}\n onChange={f[1]}\n hint=\"PDF, PNG or JPG up to 10 MB · drag several at once\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/FileDrop"},{"name":"Funnel","title":"Funnel","group":"Charts","summary":"Horizontal funnel: each step's share of the first, plus step-to-step conversion on the right.","description":"Horizontal funnel: each step's share of the first, plus step-to-step conversion on the right.\n\nProps: `steps` ({label, value, tint}), `tint` (default \"sky\"), `format(v)`.\n\n- Three to six steps, named as actions people take (\"Started trial\"), in order.\n- The right column is the conversion from the step above; that's the number people act on.\n","components":["Funnel"],"code":"import { Funnel } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">Trial to paid</h3>\n <p className=\"tf-panel-sub\">Where people drop off</p>\n </div>\n <Funnel\n steps={[\n {\n label: \"Visited pricing\",\n value: 7495,\n },\n {\n label: \"Started trial\",\n value: 3609,\n },\n {\n label: \"Invited a teammate\",\n value: 2221,\n },\n {\n label: \"Became paying\",\n value: 1388,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/Funnel"},{"name":"Gallery","title":"Gallery","group":"Media","summary":"Images: Gallery (photos with thumbnails and full screen), BeforeAfter (drag to compare, e.g.","description":"Images: `Gallery` (photos with thumbnails and full screen), `BeforeAfter` (drag to compare, e.g. virtual staging or upscaling) and `ImageVariants` (the sizes and formats generated from one image).\n\nGallery: `items` ({src, alt, caption, art, tint}), `start`, `ratio`, `thumbs`, `fullscreen`, `badge`, `loading`, `referrerPolicy`. Arrow keys and Esc work in full screen.\nBeforeAfter: `before`, `after` ({src, alt} or {art}), `start` (0–100), `ratio`, `beforeLabel`, `afterLabel`, `label`, `valueText`, `loading`, `referrerPolicy`. Drag, click or use ← → (Page Up/Down for bigger steps, Home and End for the ends).\nImageVariants: `variants` ({id, label, src, size, format, weight, ratio, status}), `selected` / `onSelect`, `alt`, `tint`, `actions`.\n\n- Images load lazily (`loading=\"eager\"` for a gallery at the top of the page; thumbnails always load lazily) and decode off the main thread.\n- `referrerPolicy=\"no-referrer\"` keeps your page address from hosts the photos come from.\n- The compare slider tells screen readers both shares (\"Before 40%, After 60%\", with your labels); `valueText` words it your way.\n","components":["BeforeAfter","Gallery","ImageVariants"],"code":"import * as React from \"react\";\nimport { BeforeAfter, Gallery, ImageVariants } from \"@twentyfirstmedia/ui-core\";\nimport type { Tint } from \"@twentyfirstmedia/ui-core\";\n\nconst art = (t: Tint, s: string) => (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(160deg, var(--\" + t + \") 0%, var(--surface) 120%)\",\n display: \"grid\",\n placeItems: \"center\",\n color: \"var(--\" + t + \"-ink)\",\n font: \"var(--type-title-weight) 14px/1 var(--font-sans)\",\n }}\n >\n {s}\n </div>\n);\nconst before = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #ddd8cf 0 62%, #b9ad9c 62%)\",\n }}\n />\n ),\n};\nconst after = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #e9e2d6 0 62%, #a68b6a 62%)\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n left: \"18%\",\n bottom: \"22%\",\n width: \"38%\",\n height: \"22%\",\n borderRadius: \"var(--radius-md)\",\n background: \"#5b6b5a\",\n }}\n />\n <div\n style={{\n position: \"absolute\",\n right: \"16%\",\n bottom: \"38%\",\n width: \"14%\",\n height: \"30%\",\n borderRadius: \"var(--radius-sm)\",\n background: \"#3d3a36\",\n }}\n />\n </div>\n ),\n};\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <Gallery\n badge=\"Virtual staging\"\n items={[\n {\n art: art(\"sky\", \"Living room\"),\n caption: \"Living room\",\n },\n {\n art: art(\"mint\", \"Kitchen\"),\n },\n {\n art: art(\"butter\", \"Bedroom\"),\n },\n {\n art: art(\"lilac\", \"Balcony\"),\n },\n {\n art: art(\"blush\", \"Bathroom\"),\n },\n ]}\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Virtual staging</b>\n <BeforeAfter before={before} after={after} afterLabel=\"Staged · Scandinavian\" />\n </div>\n <div className=\"tf-panel\">\n <ImageVariants\n tint=\"sky\"\n variants={[\n {\n id: 1,\n label: \"Original\",\n size: \"4032 × 3024\",\n format: \"JPEG\",\n weight: \"3.8 MB\",\n ratio: \"4 / 3\",\n },\n {\n id: 2,\n label: \"Listing\",\n size: \"1600 × 1200\",\n format: \"WebP\",\n weight: \"214 KB\",\n ratio: \"4 / 3\",\n status: \"ok\",\n },\n {\n id: 3,\n label: \"Square\",\n size: \"1080 × 1080\",\n format: \"WebP\",\n weight: \"168 KB\",\n ratio: \"1 / 1\",\n status: \"ok\",\n },\n {\n id: 4,\n label: \"Upscaled 2×\",\n size: \"8064 × 6048\",\n format: \"PNG\",\n status: \"running\",\n ratio: \"4 / 3\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\n","path":"components/Gallery"},{"name":"Gauge","title":"Gauge","group":"Charts","summary":"Half-ring showing one value against a maximum: a goal, a budget, a quota.","description":"Half-ring showing one value against a maximum: a goal, a budget, a quota.\n\nProps: `value`, `max`, `caption`, `format(v)`, `tint`, or `thresholds` ([warn, danger] switches mint → butter → blush), `size`, `stroke`, `showRange`, `label`.\n\n- One gauge per idea; never put gauges in a row to compare things — use a BarChart.\n- Use `thresholds` for limits (budgets, storage), a fixed `tint` for goals where more is better.\n","components":["Gauge"],"code":"import { Gauge } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n flexDirection: \"row\",\n justifyContent: \"space-around\",\n flexWrap: \"wrap\",\n gap: 24,\n }}\n >\n <Gauge\n value={60}\n max={75}\n caption=\"80% of monthly target\"\n format={(v) => \"€\" + v + \"k\"}\n tint=\"mint\"\n />\n <Gauge\n value={72}\n max={100}\n thresholds={[60, 85]}\n caption=\"Agent budget used\"\n format={(v) => v + \"%\"}\n />\n <Gauge\n value={91}\n max={100}\n thresholds={[60, 85]}\n caption=\"Storage used\"\n format={(v) => v + \"%\"}\n />\n </div>\n );\n}\n","path":"components/Gauge"},{"name":"Heatmap","title":"Heatmap","group":"Charts","summary":"Grid of cells shaded by value, for patterns across two dimensions (weekday × hour, week × day).","description":"Grid of cells shaded by value, for patterns across two dimensions (weekday × hour, week × day).\n\nProps: `rows`, `cols` (labels), `values` (rows × cols numbers), `colEvery` (label every nth column), `caption`, `format(v)`, `label`.\n\n- Five steps of one hue (mint-ink mixed into the surface), so it reads by lightness in both themes and for color-blind viewers.\n- The footer shows the hovered cell's exact value; keep the Less–More scale visible.\n","components":["Heatmap"],"code":"import { Heatmap } from \"@twentyfirstmedia/ui-core\";\n\nconst vals = [0, 1, 2, 3, 4, 5, 6].map((d) =>\n Array.from(\n {\n length: 24,\n },\n (_, hr) => {\n const w = d < 5 ? 1 : 0.35;\n return Math.round(\n 48 *\n w *\n (Math.exp(-Math.pow((hr - 11) / 3.2, 2)) +\n 0.7 * Math.exp(-Math.pow((hr - 16) / 2.6, 2))) +\n ((d * 31 + hr * 17) % 7),\n );\n },\n ),\n);\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">When people sign up</h3>\n <p className=\"tf-panel-sub\">By weekday and hour</p>\n </div>\n <Heatmap\n rows={[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]}\n cols={Array.from(\n {\n length: 24,\n },\n (_, i) => (i < 10 ? \"0\" : \"\") + i,\n )}\n colEvery={3}\n values={vals}\n caption=\"Hover a cell for the count\"\n format={(v) => v + \" sign-ups\"}\n />\n </div>\n );\n}\n","path":"components/Heatmap"},{"name":"InboxList","title":"Inbox List","group":"Data","summary":"Webmail pieces: AccountSwitcher (one login, every address), InboxList (with agent state per mail), MailThread, SignatureEditor, AttachmentPreview.","description":"Webmail pieces: `AccountSwitcher` (one login, every address), `InboxList` (with agent state per mail), `MailThread`, `SignatureEditor`, `AttachmentPreview`.\n\nAccountSwitcher: `accounts` ({address, domain, unread}), `value` (null = all inboxes), `onChange`, `unread`, `allLabel`.\nInboxList: `items` ({id, from, time, subject, snippet, unread, to, agent: {label, tone}, tags}), `selected`, `onOpen`.\nMailThread: `subject`, `messages` ({id, from, to, time, snippet, body, agent, tint, agentNote, attachments}), `reply` (node). Oldest collapsed, newest open; agent mails carry a signal edge.\nSignatureEditor: `value` / `onChange` ({name, role, company, phone, website}), `fields`, `note`, `extra`.\nAttachmentPreview: `name`, `meta`, children (the document on paper), `facts` (KeyValue), `found`, `actions`, `onClose`.\n","components":["AccountSwitcher","InboxList","MailThread"],"code":"import * as React from \"react\";\nimport { AccountSwitcher, InboxList, MailThread } from \"@twentyfirstmedia/ui-core\";\n\nconst accounts = [\n {\n address: \"support@northwind.de\",\n domain: \"northwind.de\",\n unread: 3,\n },\n {\n address: \"hello@chatteo.de\",\n domain: \"chatteo.de\",\n unread: 1,\n },\n {\n address: \"robin@twentyfirst.media\",\n domain: \"twentyfirst.media\",\n },\n];\nfunction App() {\n const a = React.useState<string | null>(null),\n s = React.useState<string | number>(1);\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"360px 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <AccountSwitcher accounts={accounts} value={a[0]} onChange={a[1]} unread={4} />\n <InboxList\n selected={s[0]}\n onOpen={(m) => {\n s[1](m.id);\n }}\n items={[\n {\n id: 1,\n from: \"Ida Nolan\",\n time: \"08:40\",\n subject: \"Seat upgrade before renewal\",\n snippet: \"We are growing faster than planned and need 40 more seats.\",\n unread: true,\n to: \"support@\",\n agent: {\n label: \"Draft ready\",\n tone: \"highlight\",\n },\n },\n {\n id: 2,\n from: \"Hetzner\",\n time: \"07:12\",\n subject: \"Invoice 2026-0412\",\n snippet: \"Your invoice is attached.\",\n tags: [\"Receipt\"],\n },\n ]}\n />\n </div>\n <div className=\"tf-panel\">\n <MailThread\n subject=\"Seat upgrade before renewal\"\n messages={[\n {\n id: 1,\n from: \"Ida Nolan\",\n to: \"to support@northwind.de\",\n time: \"Mon 16:20\",\n snippet: \"Hi! Quick question about seats…\",\n body: \"Hi! Quick question about seats before our renewal.\",\n },\n {\n id: 2,\n from: \"Atlas\",\n agent: true,\n tint: \"sky\",\n agentNote: \"sent after approval\",\n to: \"to ida@northwind.io\",\n time: \"Tue 09:41\",\n body: \"Hi Ida,\\n\\nhappy to add 40 seats before your renewal on May 1.\\n\\nBest,\\nMara\",\n attachments: [\n {\n name: \"Quote-0412.pdf\",\n size: \"84 KB\",\n },\n ],\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/InboxList"},{"name":"KanbanColumn","title":"Kanban Column","group":"Data","summary":"","description":"","components":["AvatarStack","Badge","Card","KanbanColumn"],"code":"import { AvatarStack, Badge, Card, KanbanColumn } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n maxWidth: 300,\n }}\n >\n <KanbanColumn title=\"In review\" dot=\"lilac\">\n <Card\n title=\"Billing settings flow\"\n meta={[\"Due Fri\"]}\n status={<Badge tone=\"highlight\">Review</Badge>}\n actions={[<AvatarStack key={1} names={[\"Mara Ellis\", \"Sun Lee\"]} size={24} />]}\n />\n <Card\n title=\"Empty states for reports\"\n meta={[\"Due Mon\"]}\n status={<Badge tone=\"warning\">Changes</Badge>}\n />\n </KanbanColumn>\n </div>\n );\n}\n","path":"components/KanbanColumn"},{"name":"KeyValue","title":"Key Value","group":"Foundations","summary":"Small building blocks for detail panels: KeyValue (label/value list), Kbd (keyboard keys), Spinner, Collapsible.","description":"Small building blocks for detail panels: `KeyValue` (label/value list), `Kbd` (keyboard keys), `Spinner`, `Collapsible`.\n\nKeyValue: `items` ([label, value, mono] or {k, v, mono}), `dense`. Empty values show a dash.\nKbd: `keys` (array). Spinner: `size` (\"lg\"), `label`. Collapsible: `title`, `meta`, `defaultOpen` / `open` + `onOpenChange`, children.\n\n- Keep keys short nouns; values that are codes, IDs or addresses use `mono`.\n- Show a spinner only after ~300 ms; inside a button it replaces the icon.\n","components":["Collapsible","Kbd","KeyValue","Spinner","StatusIndicator"],"code":"import * as React from \"react\";\nimport { Collapsible, Kbd, KeyValue, Spinner, StatusIndicator } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <KeyValue\n items={[\n [\"Server\", \"ax41-fsn1\"],\n [\"Address\", \"95.217.14.80\", true],\n [\"Kernel\", \"6.8.0-45\"],\n [\"Backups\", <StatusIndicator status=\"ok\" label=\"Nightly · 03:00\" />],\n [\"Notes\", null],\n ]}\n />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-row\">\n <span>Search</span>\n <Kbd keys={[\"⌘\", \"K\"]} />\n <span>Approve</span>\n <Kbd keys={[\"A\"]} />\n <Spinner />\n <Spinner size=\"lg\" />\n </div>\n <Collapsible title=\"Advanced settings\" meta=\"3 changed\" defaultOpen>\n <KeyValue\n dense\n items={[\n [\"Timeout\", \"30 s\"],\n [\"Retries\", \"3\"],\n ]}\n />\n </Collapsible>\n <Collapsible title=\"Raw payload\">Hidden until opened</Collapsible>\n </div>\n </div>\n );\n}\n","path":"components/KeyValue"},{"name":"LedgerTable","title":"Ledger Table","group":"Data","summary":"Bookkeeping: LedgerTable (bookings with project and agent suggestions), PnLStatement and ReceiptSuggestion.","description":"Bookkeeping: `LedgerTable` (bookings with project and agent suggestions), `PnLStatement` and `ReceiptSuggestion`.\n\nLedgerTable: `rows` ({id, date, ref, text, category, project, projectTint, net, vat, gross, suggested, status, statusLabel}), `categories` (makes the category editable in place) + `onEdit(row, change)`, `currency`, `selectable`, `bulkActions`, `toolbar`, `maxHeight`.\nPnLStatement: `columns` (months), `rows` ({kind: \"section\" | \"subtotal\" | \"total\", label, values}), `currency`, `total` (false hides it).\nReceiptSuggestion: `file`, `source`, `fields` ({label, value, mono, confidence 0–1}), `suggestion`, `onConfirm`, `onDiscard`, `confirmLabel`, `discardLabel`, children.\n\n- Agent suggestions carry the signal diamond until a person confirms them.\n- Negative amounts are blush; amounts use the locale's currency format.\n","components":["LedgerTable","PnLStatement","ReceiptSuggestion"],"code":"import * as React from \"react\";\nimport { LedgerTable, PnLStatement, ReceiptSuggestion } from \"@twentyfirstmedia/ui-core\";\nimport type { LedgerRow } from \"@twentyfirstmedia/ui-core\";\n\nconst rows: LedgerRow[] = [\n {\n id: 1,\n date: \"Apr 2\",\n ref: \"RE-0412\",\n text: \"Hetzner Online GmbH\",\n category: \"Hosting\",\n project: \"Northwind\",\n projectTint: \"sky\",\n net: 84.03,\n vat: 15.97,\n gross: 100.0,\n status: \"ok\",\n statusLabel: \"Booked\",\n },\n {\n id: 2,\n date: \"Apr 3\",\n ref: \"ST-2231\",\n text: \"Stripe payout\",\n category: \"Revenue\",\n project: \"Chatteo\",\n projectTint: \"mint\",\n net: 4210.0,\n vat: 0,\n gross: 4210.0,\n status: \"ok\",\n statusLabel: \"Booked\",\n },\n {\n id: 3,\n date: \"Apr 4\",\n ref: \"MAIL-88\",\n text: \"Google Ireland Ltd.\",\n category: \"Marketing\",\n project: \"Northwind\",\n projectTint: \"sky\",\n net: -320.0,\n vat: 0,\n gross: -320.0,\n suggested: \"Ledger\",\n status: \"signal\",\n statusLabel: \"Suggested\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush\">\n <LedgerTable\n rows={rows}\n selectable\n categories={[\"Hosting\", \"Revenue\", \"Marketing\", \"Software\"]}\n bulkActions={[\n {\n label: \"Confirm\",\n icon: \"check\",\n onClick: (r, c) => {\n c();\n },\n },\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.4fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <PnLStatement\n columns={[\"Jan\", \"Feb\", \"Mar\"]}\n rows={[\n {\n kind: \"section\",\n label: \"Revenue\",\n },\n {\n label: \"Subscriptions\",\n values: [18200, 19400, 21100],\n },\n {\n label: \"One-off\",\n values: [2100, 1800, 2600],\n },\n {\n kind: \"section\",\n label: \"Costs\",\n },\n {\n label: \"Hosting\",\n values: [-820, -840, -910],\n },\n {\n label: \"Marketing\",\n values: [-4200, -3900, -5100],\n },\n {\n kind: \"total\",\n label: \"Result\",\n values: [15280, 16460, 17690],\n },\n ]}\n />\n </div>\n <ReceiptSuggestion\n file=\"Rechnung_2026-0412.pdf\"\n source=\"From billing@hetzner.com · Apr 2\"\n fields={[\n {\n label: \"Vendor\",\n value: \"Hetzner Online GmbH\",\n confidence: 0.99,\n },\n {\n label: \"Date\",\n value: \"Apr 2, 2026\",\n mono: true,\n confidence: 0.98,\n },\n {\n label: \"Gross\",\n value: \"€100.00\",\n mono: true,\n confidence: 0.97,\n },\n {\n label: \"VAT\",\n value: \"19%\",\n confidence: 0.72,\n },\n ]}\n suggestion=\"Hosting · Northwind · as every month\"\n onConfirm={() => {}}\n onDiscard={() => {}}\n />\n </div>\n </div>\n );\n}\n","path":"components/LedgerTable"},{"name":"LineChart","title":"Line Chart","group":"Charts","summary":"","description":"","components":["LineChart"],"code":"import { LineChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n maxWidth: 620,\n }}\n >\n <LineChart\n labels={[\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"]}\n format={(v) => \"€\" + Math.round(v / 1000) + \"k\"}\n series={[\n {\n values: [18000, 22000, 21000, 29000, 34000, 41000],\n tint: \"mint\",\n area: true,\n },\n {\n values: [12000, 14000, 17000, 16000, 21000, 24000],\n tint: \"sky\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/LineChart"},{"name":"ListRow","title":"List Row","group":"Data","summary":"","description":"","components":["ListRow"],"code":"import { ListRow } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-list\"\n style={{\n maxWidth: 380,\n }}\n >\n <ListRow\n avatar=\"Jonah Petit\"\n title=\"Jonah Petit\"\n subtitle=\"Can we move the review to Thursday?\"\n meta=\"09:12\"\n unread\n active\n />\n <ListRow\n avatar=\"Sun Lee\"\n title=\"Sun Lee\"\n subtitle=\"Payout file for March is attached\"\n meta=\"Yesterday\"\n />\n <ListRow\n icon=\"bell\"\n tint=\"butter\"\n title=\"Billing\"\n subtitle=\"Card ending 4417 expires soon\"\n meta=\"Mon\"\n />\n </div>\n );\n}\n","path":"components/ListRow"},{"name":"ListingCard","title":"Listing Card","group":"Real estate","summary":"Property listings from portals or your own stock: ListingCard (grid card or list row) and ResultsToolbar (count, selection, bulk actions, sort, view).","description":"Property listings from portals or your own stock: `ListingCard` (grid card or list row) and `ResultsToolbar` (count, selection, bulk actions, sort, view).\n\nListingCard: `listing` ({id, title, price (null = on request), rent, currency, pricePerSqm, area, rooms, place, image, photos, source, seller (\"private\" | \"agent\"), sellerLabel, phone, isNew, age, status, wished}), `layout` (\"card\" | \"row\"), `selected` + `onSelect`, `onWish`, `statusOptions` + `onStatus` (StatusSelect), `onOpen`.\nResultsToolbar: `title`, `total`, `meta`, `selected`, `onSelectAll(all)`, `bulkActions` ({label, icon, variant, busy, busyLabel, onClick}; shown while something is selected), `sortOptions` + `sort` + `onSort`, `views` (\"grid\" | \"list\" | \"map\") + `view` + `onView`.\n\n- Area in m², money and counts follow `setLocale`.\n","components":["ListingCard","ResultsToolbar"],"code":"import * as React from \"react\";\nimport { ListingCard, ResultsToolbar } from \"@twentyfirstmedia/ui-core\";\nimport type { Listing, StatusSelectProps } from \"@twentyfirstmedia/ui-core\";\n\nconst st: StatusSelectProps[\"options\"] = [\n {\n value: \"unread\",\n label: \"New\",\n status: \"running\",\n },\n {\n value: \"read\",\n label: \"Seen\",\n status: \"idle\",\n },\n {\n value: \"contacted\",\n label: \"Contacted\",\n status: \"ok\",\n },\n];\nconst L0: Listing[] = [\n {\n id: 1,\n title: \"Bright 3-room flat with balcony near Isebek canal\",\n price: 649000,\n pricePerSqm: 7726,\n area: 84,\n rooms: 3,\n place: \"Eppendorf, Hamburg\",\n source: \"ImmoScout\",\n seller: \"private\",\n phone: true,\n photos: 14,\n isNew: true,\n age: \"2 h\",\n status: \"unread\",\n tint: \"sky\",\n },\n {\n id: 2,\n title: \"Loft in HafenCity with water view\",\n price: 1190000,\n pricePerSqm: 10625,\n area: 112,\n rooms: 2.5,\n place: \"HafenCity, Hamburg\",\n source: \"Kleinanzeigen\",\n seller: \"agent\",\n photos: 22,\n age: \"1 d\",\n status: \"contacted\",\n tint: \"lilac\",\n wished: true,\n },\n {\n id: 3,\n title: \"2-room flat, renovated old building\",\n price: 1480,\n rent: true,\n area: 58,\n rooms: 2,\n place: \"Altona, Hamburg\",\n source: \"Immowelt\",\n seller: \"agent\",\n photos: 9,\n age: \"3 d\",\n status: \"read\",\n tint: \"mint\",\n },\n];\nfunction App() {\n const sel = React.useState<(string | number)[]>([1]),\n list = React.useState(L0),\n view = React.useState(\"grid\"),\n sort = React.useState(\"new\");\n function tog(x: Listing) {\n sel[1](sel[0].indexOf(x.id) > -1 ? sel[0].filter((i) => i !== x.id) : sel[0].concat([x.id]));\n }\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <ResultsToolbar\n title=\"Hamburg · flats to buy\"\n total={128}\n meta=\"updated 06:00\"\n selected={sel[0].length}\n onSelectAll={(all) => {\n sel[1](all ? list[0].map((x) => x.id) : []);\n }}\n bulkActions={[\n {\n label: \"Send to CRM\",\n icon: \"send\",\n variant: \"primary\",\n },\n {\n label: \"Mark as seen\",\n icon: \"eye\",\n },\n ]}\n sortOptions={[\n {\n value: \"new\",\n label: \"Newest\",\n },\n {\n value: \"price\",\n label: \"Price\",\n },\n {\n value: \"sqm\",\n label: \"Price per m²\",\n },\n ]}\n sort={sort[0]}\n onSort={sort[1]}\n views={[\"grid\", \"list\", \"map\"]}\n view={view[0]}\n onView={view[1]}\n />\n <div\n className=\"tf-grid\"\n style={\n view[0] === \"list\"\n ? {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 12,\n }\n : ({\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties)\n }\n >\n {list[0].map((x) => (\n <ListingCard\n key={x.id}\n listing={x}\n layout={view[0] === \"list\" ? \"row\" : \"card\"}\n selected={sel[0].indexOf(x.id) > -1}\n onSelect={tog}\n onWish={() => {}}\n statusOptions={st}\n onStatus={(l, s) => {\n list[1](\n list[0].map((y) =>\n y.id === l.id\n ? Object.assign({}, y, {\n status: s,\n })\n : y,\n ),\n );\n }}\n />\n ))}\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/ListingCard"},{"name":"LiveFeed","title":"Live Feed","group":"Data","summary":"Real-time activity: LiveFeed (events as they happen, with pause and filters) and BarList (ranked values with inline bars, e.g.","description":"Real-time activity: `LiveFeed` (events as they happen, with pause and filters) and `BarList` (ranked values with inline bars, e.g. top countries or services).\n\nLiveFeed: `items` ({id, type, icon, tint, title, text, amount, currency, time, fresh}), `types` ({value, label} filter chips), `paused` / `onPause`, `maxHeight`, `emptyText`. While paused, new events are held back and counted.\nBarList: `items` ({id, label, mark, value, share (0–1), tint}), `format`, `onPick(item)`, `label`.\n\n- Show at most ~50 events; older ones belong in a table.\n- `fresh` slides a row in once; it never blinks or repeats.\n","components":["BarList","LiveFeed"],"code":"import * as React from \"react\";\nimport { BarList, LiveFeed, formatMoney } from \"@twentyfirstmedia/ui-core\";\nimport type { LiveFeedItem, LiveFeedProps } from \"@twentyfirstmedia/ui-core\";\n\nconst types: LiveFeedProps[\"types\"] = [\n {\n value: \"order\",\n label: \"Orders\",\n },\n {\n value: \"payment\",\n label: \"Payments\",\n },\n {\n value: \"error\",\n label: \"Errors\",\n },\n];\nconst seed: LiveFeedItem[] = [\n {\n id: 1,\n type: \"order\",\n icon: \"cart\",\n tint: \"mint\",\n title: \"1,000 Instagram followers\",\n text: \"Order #48213 · Germany · PayPal\",\n amount: 12.9,\n time: \"2s\",\n },\n {\n id: 2,\n type: \"payment\",\n icon: \"card\",\n tint: \"sky\",\n title: \"Payment received\",\n text: \"Stripe · card ending 4242\",\n amount: 39,\n time: \"14s\",\n },\n {\n id: 3,\n type: \"error\",\n icon: \"zap\",\n tint: \"blush\",\n title: \"Provider timeout\",\n text: \"SMM provider B · retried\",\n time: \"41s\",\n },\n {\n id: 4,\n type: \"order\",\n icon: \"cart\",\n tint: \"mint\",\n title: \"5,000 TikTok views\",\n text: \"Order #48212 · Austria\",\n amount: 4.9,\n time: \"1m\",\n },\n];\nfunction App() {\n const it = React.useState<LiveFeedItem[]>(seed);\n React.useEffect(() => {\n let n = 100;\n const t = setInterval(() => {\n n++;\n it[1]((l): LiveFeedItem[] => [\n {\n id: n,\n type: \"order\",\n icon: \"cart\",\n tint: \"mint\",\n title: ((n % 3) + 1) * 500 + \" starter kits\",\n text: \"Order #\" + (48213 + n - 100) + \" · Switzerland\",\n amount: 2.9 + (n % 5),\n time: \"now\",\n fresh: true,\n },\n ...l.slice(0, 7),\n ]);\n }, 2600);\n return () => {\n clearInterval(t);\n };\n }, []);\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.4fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <LiveFeed items={it[0]} types={types} maxHeight={420} />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Revenue by country</b>\n <BarList\n format={(v) => formatMoney(v)}\n items={[\n {\n label: \"Germany\",\n mark: \"🇩🇪\",\n value: 18240,\n share: 0.52,\n },\n {\n label: \"Austria\",\n mark: \"🇦🇹\",\n value: 6120,\n share: 0.17,\n },\n {\n label: \"Switzerland\",\n mark: \"🇨🇭\",\n value: 5480,\n share: 0.16,\n tint: \"mint\",\n },\n {\n label: \"Netherlands\",\n mark: \"🇳🇱\",\n value: 2210,\n share: 0.06,\n tint: \"butter\",\n },\n {\n label: \"Other\",\n value: 3100,\n share: 0.09,\n tint: \"lilac\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/LiveFeed"},{"name":"LocaleField","title":"Locale Field","group":"Forms","summary":"Content in several languages and switching context: LocaleField, LocaleSwitcher, WorkspaceSwitcher.","description":"Content in several languages and switching context: `LocaleField`, `LocaleSwitcher`, `WorkspaceSwitcher`.\n\nLocaleField: `label`, `locales` ({code, label, flag}), `value` / `defaultValue` ({[code]: text}) + `onChange`, `source` (language others copy from; default the first), `active`, `multiline`, `rows`, `onTranslate(text, to)` (return or resolve the translation), `hint`, `error`. Missing languages carry a warn diamond; empty fields show the source text as placeholder.\nLocaleSwitcher: `languages` ({code, label, short, flag}), `language` / `onLanguage`, `currencies`, `currency` / `onCurrency`, `variant` (\"frame\" for the dark top bar).\nWorkspaceSwitcher: `items` ({id, name, sub, tint, logo}), `value` / `onChange`, `label`, `footer`, `variant` (\"frame\").\n","components":["LocaleField","LocaleSwitcher","WorkspaceSwitcher"],"code":"import * as React from \"react\";\nimport { LocaleField, LocaleSwitcher, WorkspaceSwitcher } from \"@twentyfirstmedia/ui-core\";\n\nconst langs = [\n {\n code: \"de\",\n label: \"Deutsch\",\n short: \"de\",\n flag: \"🇩🇪\",\n },\n {\n code: \"en\",\n label: \"English\",\n short: \"en\",\n flag: \"🇬🇧\",\n },\n {\n code: \"fr\",\n label: \"Français\",\n short: \"fr\",\n flag: \"🇫🇷\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.3fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 20,\n }}\n >\n <LocaleField\n label=\"Product name\"\n locales={[\n {\n code: \"de\",\n label: \"DE\",\n },\n {\n code: \"en\",\n label: \"EN\",\n },\n {\n code: \"fr\",\n label: \"FR\",\n },\n {\n code: \"es\",\n label: \"ES\",\n },\n ]}\n defaultValue={{\n de: \"Instagram Follower kaufen\",\n en: \"Buy Instagram followers\",\n }}\n onTranslate={(t, to) => \"[\" + to + \"] \" + t}\n />\n <LocaleField\n label=\"Short description\"\n multiline\n active=\"en\"\n locales={[\n {\n code: \"de\",\n label: \"DE\",\n },\n {\n code: \"en\",\n label: \"EN\",\n },\n ]}\n defaultValue={{\n de: \"Echte Follower, Lieferung in einer Stunde.\",\n }}\n />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n alignItems: \"flex-start\",\n }}\n >\n <WorkspaceSwitcher\n label=\"Shops\"\n items={[\n {\n id: \"fp\",\n name: \"Northwind\",\n sub: \"northwind.de\",\n tint: \"sky\",\n },\n {\n id: \"fm\",\n name: \"FollowerMarket\",\n sub: \"followermarket.com\",\n tint: \"mint\",\n },\n {\n id: \"fr\",\n name: \"FollowerRocket\",\n sub: \"followerrocket.de\",\n tint: \"butter\",\n },\n ]}\n />\n <LocaleSwitcher languages={langs} currencies={[\"EUR\", \"CHF\", \"USD\"]} />\n </div>\n </div>\n );\n}\n","path":"components/LocaleField"},{"name":"LogView","title":"Log View","group":"Foundations","summary":"Monospace output: LogView for logs with line numbers and levels, CodeViewer for JSON, YAML and code.","description":"Monospace output: `LogView` for logs with line numbers and levels, `CodeViewer` for JSON, YAML and code.\n\nLogView: `lines` (strings or {time, text, level: \"ok\" | \"warn\" | \"err\" | \"hl\", fresh}), `title`, `right`, `numbers` (false hides them), `follow` (default true: sticks to the newest line unless the pointer is over it), `maxHeight`.\nCodeViewer: `value` (string or object, pretty-printed as JSON), `lang`. Built on CodeBlock with copy.\n","components":["CodeViewer","LogView"],"code":"import { CodeViewer, LogView } from \"@twentyfirstmedia/ui-core\";\nimport type { LogLine } from \"@twentyfirstmedia/ui-core\";\n\nconst lines: LogLine[] = [\n {\n time: \"09:41:02\",\n text: \"pull ghcr.io/halo/web:8f2a91c\",\n level: \"hl\",\n },\n {\n time: \"09:41:09\",\n text: \"build 146 modules transformed\",\n },\n {\n time: \"09:41:22\",\n text: \"test 146 passed, 0 failed\",\n level: \"ok\",\n },\n {\n time: \"09:41:23\",\n text: \"warn bundle is 512 kB, above the 500 kB budget\",\n level: \"warn\",\n },\n {\n time: \"09:41:30\",\n text: \"health GET /v1/health → 503\",\n level: \"err\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <LogView title=\"deploy #482\" lines={lines} />\n <CodeViewer\n value={{\n tool: \"cloudflare.dns\",\n action: \"create\",\n record: {\n type: \"TXT\",\n name: \"ops-test\",\n ttl: 300,\n },\n }}\n />\n </div>\n );\n}\n","path":"components/LogView"},{"name":"MapView","title":"Map View","group":"Data","summary":"Points on a map: price pins for listings or bubbles for where orders come from.","description":"Points on a map: price pins for listings or bubbles for where orders come from. The map itself (tiles from MapLibre, Leaflet or Google, an SVG or an image) goes in `background`; Halo draws pins, popups, legend and controls on top in its own style.\n\nProps: `points` ({id, label, sub, lat, lng, value, text (pin label), tint, pulse}), `bounds` ({n, s, w, e}; what the background shows), `background` (node), `bubbles` (size by value instead of pins), `selected` / `onSelect`, `renderPopup(point)`, `legend`, `controls`, `height`, `label`.\n\n- With a real map library, pass its projection's bounds or render the pins through its overlay API with the same classes (`tf-map-pin`, `tf-map-pop`).\n- Price pins show the short price; the popup carries the details.\n","components":["IconButton","MapView"],"code":"import * as React from \"react\";\nimport { IconButton, MapView } from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\nconst world: MapPoint[] = [\n {\n id: \"de\",\n label: \"Germany\",\n lat: 51,\n lng: 10,\n value: 820,\n sub: \"820 orders today\",\n },\n {\n id: \"at\",\n label: \"Austria\",\n lat: 47.5,\n lng: 14.5,\n value: 260,\n tint: \"mint\",\n },\n {\n id: \"ch\",\n label: \"Switzerland\",\n lat: 46.8,\n lng: 8.2,\n value: 210,\n tint: \"mint\",\n },\n {\n id: \"us\",\n label: \"United States\",\n lat: 39,\n lng: -98,\n value: 140,\n tint: \"butter\",\n },\n {\n id: \"ae\",\n label: \"UAE\",\n lat: 24,\n lng: 54,\n value: 90,\n tint: \"lilac\",\n pulse: true,\n },\n {\n id: \"br\",\n label: \"Brazil\",\n lat: -12,\n lng: -52,\n value: 60,\n tint: \"blush\",\n },\n];\nconst city: MapPoint[] = [\n {\n id: 1,\n label: \"3-room flat, Eppendorf\",\n sub: \"€649,000 · 84 m²\",\n lat: 53.59,\n lng: 9.98,\n text: \"€649k\",\n },\n {\n id: 2,\n label: \"Loft, HafenCity\",\n sub: \"€1,190,000 · 112 m²\",\n lat: 53.541,\n lng: 10.0,\n text: \"€1.19m\",\n tint: \"lilac\",\n },\n {\n id: 3,\n label: \"2-room flat, Altona\",\n sub: \"€1,480 / month\",\n lat: 53.55,\n lng: 9.935,\n text: \"€1,480\",\n tint: \"mint\",\n },\n {\n id: 4,\n label: \"House, Blankenese\",\n lat: 53.565,\n lng: 9.81,\n text: \"€2.4m\",\n tint: \"butter\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Orders by country</b>\n <MapView bubbles points={world} height={360} legend=\"Bubble size = orders today\" />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Listings</b>\n <MapView\n points={city}\n bounds={{\n n: 53.62,\n s: 53.52,\n w: 9.78,\n e: 10.06,\n }}\n height={360}\n selected={2}\n controls={[\n <IconButton key={1} icon=\"plus\" label=\"Zoom in\" size=\"sm\" />,\n <IconButton key={2} icon=\"minus\" label=\"Zoom out\" size=\"sm\" />,\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/MapView"},{"name":"MemoryViewer","title":"Memory Viewer","group":"AI","summary":"What an agent remembers (MemoryViewer) and how its runtime settings differ from code (OverrideDiff).","description":"What an agent remembers (`MemoryViewer`) and how its runtime settings differ from code (`OverrideDiff`).\n\nMemoryViewer: `items` ({id, scope, source, date, text}), `onEdit(item)`, `onDelete(item)`. Search and scope chips built in.\nOverrideDiff: `title`, `versions`, `by`, `before`, `after` (text, diffed by line), `onDiscard`, `onPromote`, `discardLabel`, `promoteLabel`.\n","components":["MemoryViewer","OverrideDiff"],"code":"import * as React from \"react\";\nimport { MemoryViewer, OverrideDiff } from \"@twentyfirstmedia/ui-core\";\n\nconst mem = [\n {\n id: 1,\n scope: \"Agent\",\n source: \"Chat\",\n date: \"Apr 12\",\n text: \"Ida at Northwind prefers short replies and a quote attached.\",\n },\n {\n id: 2,\n scope: \"Project\",\n source: \"Feedback\",\n date: \"Apr 9\",\n text: \"Never promise refunds; hand them to a person.\",\n },\n {\n id: 3,\n scope: \"Global\",\n source: \"Import\",\n date: \"Apr 2\",\n text: \"Company is FollowerX GmbH, Hamburg.\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <MemoryViewer items={mem} onEdit={() => {}} onDelete={() => {}} />\n </div>\n <OverrideDiff\n title=\"Instruction\"\n versions=\"Code default v1 → Override v3\"\n by=\"Main Brain after feedback\"\n before={\"You are Atlas, the support agent.\\nBe friendly.\\nAnswer within one day.\"}\n after={\n \"You are Atlas, the support agent.\\nBe brief and friendly.\\nAnswer within one day.\\nNever promise refunds.\"\n }\n onDiscard={() => {}}\n onPromote={() => {}}\n />\n </div>\n );\n}\n","path":"components/MemoryViewer"},{"name":"Menu","title":"Menu","group":"Overlays","summary":"","description":"","components":["Menu"],"code":"import * as React from \"react\";\nimport { Menu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n const [grid, setGrid] = React.useState(true);\n const [density, setDensity] = React.useState(\"comfortable\");\n return (\n <div className=\"tf-row\" style={{ gap: 24, alignItems: \"flex-start\", paddingBottom: 120 }}>\n <Menu\n items={[\n {\n label: \"Open\",\n icon: \"eye\",\n shortcut: \"↵\",\n },\n {\n label: \"Rename\",\n icon: \"edit\",\n shortcut: \"R\",\n },\n {\n label: \"Duplicate\",\n icon: \"file\",\n },\n {\n label: \"Share link\",\n icon: \"link\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n />\n <Menu\n label=\"View\"\n items={[\n { label: \"Show grid\", type: \"checkbox\", checked: grid, onClick: () => setGrid(!grid) },\n { label: \"Show minimap\", type: \"checkbox\", checked: false },\n \"-\",\n {\n label: \"Comfortable\",\n type: \"radio\",\n group: \"density\",\n checked: density === \"comfortable\",\n onClick: () => setDensity(\"comfortable\"),\n },\n {\n label: \"Compact\",\n type: \"radio\",\n group: \"density\",\n checked: density === \"compact\",\n onClick: () => setDensity(\"compact\"),\n },\n \"-\",\n {\n label: \"Sort by\",\n icon: \"list\",\n items: [\n { label: \"Name\", type: \"radio\", checked: true },\n { label: \"Last change\", type: \"radio\" },\n { label: \"Size\", type: \"radio\" },\n ],\n },\n { label: \"Export as PDF\", icon: \"download\", disabled: true },\n ]}\n />\n </div>\n );\n}\n","path":"components/Menu"},{"name":"MobileTabBar","title":"Mobile Tab Bar","group":"Overlays","summary":"Phone chrome: MobileTabBar (bottom navigation) and PushNotification (how an approval arrives on the lock screen).","description":"Phone chrome: `MobileTabBar` (bottom navigation) and `PushNotification` (how an approval arrives on the lock screen).\n\nMobileTabBar: `items` ({id, label, icon, count}), `active`, `onPick`, `label`.\nPushNotification: `app`, `time`, `title`, `text`, `actions` ({label, onClick}). Only A can be answered from the notification; B–D open the app.\n","components":["MobileTabBar","PushNotification"],"code":"import * as React from \"react\";\nimport { MobileTabBar, PushNotification } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const a = React.useState(\"approvals\");\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n alignItems: \"center\",\n }}\n >\n <PushNotification\n app=\"Halo\"\n time=\"now\"\n title=\"Approval needed · Atlas\"\n text=\"Send this email to ida@northwind.io?\"\n actions={[\n {\n label: \"A · Run it\",\n },\n {\n label: \"Open\",\n },\n ]}\n />\n <div\n style={{\n width: 390,\n boxSizing: \"border-box\",\n maxWidth: \"100%\",\n }}\n >\n <MobileTabBar\n active={a[0]}\n onPick={a[1]}\n items={[\n {\n id: \"home\",\n label: \"Home\",\n icon: \"home\",\n },\n {\n id: \"approvals\",\n label: \"Approvals\",\n icon: \"check\",\n count: 3,\n },\n {\n id: \"mail\",\n label: \"Mail\",\n icon: \"mail\",\n },\n {\n id: \"more\",\n label: \"More\",\n icon: \"menu\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/MobileTabBar"},{"name":"Modal","title":"Modal","group":"Overlays","summary":"","description":"","components":["Button","Input","Modal"],"code":"import { Button, Input, Modal } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n height: 340,\n position: \"relative\",\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <Modal\n style={{\n position: \"absolute\",\n inset: 0,\n }}\n icon=\"users\"\n tint=\"sky\"\n title=\"Invite teammates\"\n text=\"They get access to every project in this workspace.\"\n actions={[\n <Button key={1} variant=\"ghost\">\n Cancel\n </Button>,\n <Button key={2} icon=\"send\">\n Send invites\n </Button>,\n ]}\n >\n <Input label=\"Email addresses\" placeholder=\"name@company.com, …\" />\n </Modal>\n </div>\n );\n}\n","path":"components/Modal"},{"name":"NotificationList","title":"Notification List","group":"Overlays","summary":"","description":"","components":["NotificationList"],"code":"import { NotificationList } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n width: 380,\n boxSizing: \"border-box\",\n maxWidth: \"100%\",\n background: \"var(--surface)\",\n borderRadius: \"var(--radius-lg)\",\n padding: 8,\n boxShadow: \"var(--shadow-float)\",\n }}\n >\n <NotificationList\n items={[\n {\n id: 1,\n icon: \"bot\",\n tint: \"lilac\",\n title: \"Atlas needs your approval\",\n text: \"4 reply drafts are waiting\",\n time: \"07:55\",\n unread: true,\n },\n {\n id: 2,\n icon: \"flag\",\n tint: \"blush\",\n title: \"Invoice #2041 is overdue\",\n text: \"Harbor & Co · €860\",\n time: \"07:30\",\n unread: true,\n },\n {\n id: 3,\n icon: \"zap\",\n tint: \"mint\",\n title: \"halo-web is live\",\n text: \"Add seat selector to checkout\",\n time: \"Yesterday\",\n },\n {\n id: 4,\n icon: \"users\",\n tint: \"sky\",\n title: \"Ava Brandt joined\",\n text: \"Invited as Viewer\",\n time: \"Mon\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/NotificationList"},{"name":"NumberInput","title":"Number Input","group":"Forms","summary":"Number entry three ways: NumberInput with − / + steppers, Slider for a value on a known scale, OtpInput for one-time codes.","description":"Number entry three ways: `NumberInput` with − / + steppers, `Slider` for a value on a known scale, `OtpInput` for one-time codes.\n\nNumberInput: `label`, `value`/`defaultValue`, `onChange(n)`, `min`, `max`, `step`, `unit`, `hint`.\nSlider: `label`, `min`, `max`, `step`, `value`/`defaultValue`, `onChange(n)`, `format(v)`, `marks` (false hides min/max), `hint`.\nOtpInput: `length` (6), `onComplete(code)`, `error` (shakes and turns red), `label`.\n\n- NumberInput for small exact counts (seats, days); arrow keys step too.\n- Slider when the range matters more than the exact value (budgets, thresholds); always show the value bubble.\n- OtpInput moves focus forward as you type, back on Backspace, and fills every box on paste.\n","components":["NumberInput","OtpInput","Slider"],"code":"import * as React from \"react\";\nimport { NumberInput, OtpInput, Slider } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const s = React.useState(12),\n b = React.useState(120);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <div\n style={{\n display: \"flex\",\n gap: 32,\n flexWrap: \"wrap\",\n alignItems: \"flex-start\",\n }}\n >\n <NumberInput\n label=\"Seats\"\n value={s[0]}\n onChange={s[1]}\n min={1}\n max={200}\n hint=\"€24 per seat\"\n />\n <NumberInput label=\"VAT\" defaultValue={19} min={0} max={25} unit=\"%\" />\n <div className=\"tf-field\">\n <span className=\"tf-field-label\">Verification code</span>\n <OtpInput onComplete={() => {}} />\n <span className=\"tf-field-hint\">Paste works too</span>\n </div>\n </div>\n <Slider\n label=\"Monthly agent budget\"\n min={0}\n max={500}\n step={10}\n value={b[0]}\n onChange={b[1]}\n format={(v) => \"€\" + v}\n hint=\"Agents pause and ask you once they reach it\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/NumberInput"},{"name":"OrderSummary","title":"Order Summary","group":"Commerce","summary":"Cart and checkout pieces: CartLine rows, the OrderSummary with discount code and free-shipping progress, and CheckoutSteps.","description":"Cart and checkout pieces: `CartLine` rows, the `OrderSummary` with discount code and free-shipping progress, and `CheckoutSteps`.\n\nCartLine props: `name`, `variant`, `note`, `art` + `tint`, `price`, `qty`, `onQty(n)`, `onRemove` (the − button becomes a bin at 1).\nOrderSummary props: `lines` ({name, variant, art, tint, price, qty}), `showLines`, `discount` ({code, pct | amount, label}), `onCoupon(code)` (return false to show \"isn’t valid\"; null removes), `shipping` (fixed) or `freeShippingFrom` (default 75, else €4.90), `vat` (19, prices include it), `title`, `action` (the CTA).\nCheckoutSteps props: `steps` (Cart, Details, Shipping, Payment), `current`, `onPick` (go back only).\n\n- Keep the summary in a sticky side column on desktop; it moves under the form on phones.\n- Show what's left for free shipping; it is the strongest nudge in a cart.\n- Removing a line shows an Undo toast instead of a confirm.\n","components":["Button","CartLine","CheckoutSteps","OrderSummary"],"code":"import * as React from \"react\";\nimport { Button, CartLine, CheckoutSteps, OrderSummary } from \"@twentyfirstmedia/ui-core\";\nimport type { CartLineProps, Discount } from \"@twentyfirstmedia/ui-core\";\n\ntype OrderLine = Omit<CartLineProps, \"onQty\" | \"onRemove\"> & { key: string };\n\nfunction App() {\n const lines = React.useState<OrderLine[]>([\n {\n key: \"a\",\n name: \"Halo Cotton Tee\",\n variant: \"M · Sky\",\n art: \"tee\",\n tint: \"sky\",\n price: 32,\n qty: 1,\n },\n {\n key: \"b\",\n name: \"Studio Mug\",\n art: \"mug\",\n tint: \"butter\",\n price: 18,\n qty: 2,\n note: \"Only 6 left\",\n },\n ]),\n d = React.useState<Discount | null>(null),\n step = React.useState(1);\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <CheckoutSteps current={step[0]} onPick={step[1]} />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1fr) 340px\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n {lines[0].map((l) => (\n <CartLine\n {...l}\n key={l.key}\n onQty={(n) => {\n lines[1](\n lines[0].map((z) =>\n z.key === l.key\n ? Object.assign({}, z, {\n qty: n,\n })\n : z,\n ),\n );\n }}\n onRemove={() => {\n lines[1](lines[0].filter((z) => z.key !== l.key));\n }}\n />\n ))}\n </div>\n <div className=\"tf-panel\">\n <OrderSummary\n lines={lines[0]}\n showLines\n discount={d[0]}\n freeShippingFrom={75}\n onCoupon={(c) => {\n if (c === null) return d[1](null);\n if (c !== \"HALO10\") return false;\n d[1]({\n code: \"HALO10\",\n pct: 10,\n label: \"10% off everything\",\n });\n }}\n action={<Button icon=\"lock\">Pay now</Button>}\n />\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/OrderSummary"},{"name":"OrderTimeline","title":"Order Timeline","group":"Commerce","summary":"Five-step fulfilment progress for an order: Placed, Paid, Packed, Shipped, Delivered.","description":"Five-step fulfilment progress for an order: Placed, Paid, Packed, Shipped, Delivered.\n\nProps: `status` (one of the five, or \"Refunded\" / \"Cancelled\" which dims the line and shows a badge), `times` ({step: text}).\n\n- Use the same five words as the order status badges, so list and detail agree.\n- The current step gets a sky ring and a clock until it's done.\n","components":["OrderTimeline"],"code":"import { OrderTimeline } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 32,\n }}\n >\n <OrderTimeline\n status=\"Shipped\"\n times={{\n Placed: \"Apr 12, 09:14\",\n Paid: \"Apr 12, 09:15\",\n Packed: \"Apr 12, 15:40\",\n Shipped: \"Apr 13, 08:02\",\n }}\n />\n <OrderTimeline\n status=\"Paid\"\n times={{\n Placed: \"Just now\",\n Paid: \"Just now\",\n }}\n />\n <OrderTimeline status=\"Refunded\" />\n </div>\n );\n}\n","path":"components/OrderTimeline"},{"name":"OrgChart","title":"Org Chart","group":"AI","summary":"The hierarchy as a chart: orchestrator, leads per team, executing agents and people.","description":"The hierarchy as a chart: orchestrator, leads per team, executing agents and people.\n\nProps: `root` ({name, role, tint, status, avatar, children}). Scrolls sideways when wide.\n","components":["OrgChart"],"code":"import { OrgChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <OrgChart\n root={{\n name: \"Main Brain\",\n role: \"Orchestrator\",\n tint: \"lilac\",\n status: \"ok\",\n children: [\n {\n name: \"Atlas\",\n role: \"Support lead\",\n tint: \"sky\",\n status: \"running\",\n children: [\n {\n name: \"Triage\",\n role: \"Tags mail\",\n tint: \"sky\",\n status: \"idle\",\n },\n {\n name: \"Drafts\",\n role: \"Writes replies\",\n tint: \"sky\",\n status: \"signal\",\n },\n ],\n },\n {\n name: \"Ledger\",\n role: \"Finance lead\",\n tint: \"butter\",\n status: \"ok\",\n },\n {\n name: \"Robin\",\n role: \"Owner\",\n avatar: true,\n },\n ],\n }}\n />\n </div>\n );\n}\n","path":"components/OrgChart"},{"name":"Panel","title":"Panel","group":"Data","summary":"","description":"","components":["EventCard","IconButton","Panel","Tabs"],"code":"import { EventCard, IconButton, Panel, Tabs } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n maxWidth: 340,\n background: \"var(--canvas)\",\n padding: 12,\n borderRadius: \"var(--radius-lg)\",\n }}\n >\n <Panel\n title=\"Schedule\"\n subtitle=\"5 items waiting · 2 today\"\n action={<IconButton icon=\"plus\" label=\"Add\" />}\n >\n <Tabs block items={[\"Events\", \"Approvals\", \"History\"]} />\n <EventCard kind=\"review\" label=\"Design review\" date=\"Wed, Apr 15\" time=\"14:00\">\n Walk through the new billing settings flow.\n </EventCard>\n </Panel>\n </div>\n );\n}\n","path":"components/Panel"},{"name":"PlanCard","title":"Plan Card","group":"Commerce","summary":"Pricing tier for plan pages and upgrade dialogs.","description":"Pricing tier for plan pages and upgrade dialogs. Wrap several in `.tf-plans` for an even grid.\n\nProps: `name`, `text`, `price` (number, 0 = \"Free\", null = \"Let’s talk\"), `was` (struck price, e.g. monthly vs yearly), `per`, `features` (strings; prefix \"-\" for not included), `featured` (dark card + flag), `flag`, `current` (disables the button), `disabled` (disables the button, e.g. while a checkout starts or when the plan is not available), `cta`, `onPick`.\n\n- One featured plan, the one most customers should pick. It inverts to the frame color.\n- List the same features in the same order on every card, so rows can be compared; mark missing ones instead of leaving them out.\n- Pair with a Segmented Monthly / Yearly switch that states the saving.\n","components":["PlanCard"],"code":"import { PlanCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-plans\">\n <PlanCard\n name=\"Starter\"\n price={9}\n was={12}\n per=\"per seat / month, billed yearly\"\n text=\"For small teams getting organised\"\n features={[\n \"3 projects\",\n \"1 agent · Haiku only\",\n \"Shop with up to 50 products\",\n \"-Approvals and audit log\",\n \"-SSO\",\n ]}\n />\n <PlanCard\n name=\"Team\"\n price={24}\n was={29}\n per=\"per seat / month, billed yearly\"\n text=\"Everything a growing company runs on\"\n featured\n features={[\n \"Unlimited projects\",\n \"10 agents · all models\",\n \"Unlimited products, 3 channels\",\n \"Approvals and audit log\",\n \"-SSO\",\n ]}\n />\n <PlanCard\n name=\"Business\"\n price={null}\n text=\"Security, control and an SLA\"\n cta=\"Talk to sales\"\n current={false}\n features={[\"Everything in Team\", \"SSO and SCIM\", \"EU data residency\", \"99.9% uptime SLA\"]}\n />\n </div>\n );\n}\n","path":"components/PlanCard"},{"name":"ProductCard","title":"Product Card","group":"Commerce","summary":"Product tile for catalogs and grids, plus the pieces it is made of: Price, Rating, StockBadge and ProductArt.","description":"Product tile for catalogs and grids, plus the pieces it is made of: `Price`, `Rating`, `StockBadge` and `ProductArt`.\n\nProductCard props: `name`, `meta`, `art` + `tint` (or swap in your own image inside `.tf-product-media`), `price`, `compareAt`, `rating`, `reviews`, `badge` + `badgeTone`, `stock`, `wished` + `onWish`, `added` + `onAdd`, `onClick`.\nPrice: `value`, `compareAt` (shows the struck price and −%), `size` (\"lg\"), `currency`, `showSave`. Rating: `value`, `count`, `size`. StockBadge: `stock`, `low` (10), `compact`. ProductArt: `kind` (tee, mug, bottle, cap, tote, sneaker, watch, lamp), `tint`, `size`.\n\n- One badge per card at most (Bestseller, New, Sold out). Sale is shown by the price, not a badge.\n- Quick-add only for products without variants; otherwise open the product page.\n- Low stock is a warning badge with the number left; out of stock disables add.\n","components":["Price","ProductArt","ProductCard","Rating","StockBadge"],"code":"import * as React from \"react\";\nimport { Price, ProductArt, ProductCard, Rating, StockBadge } from \"@twentyfirstmedia/ui-core\";\nimport type { ProductCardProps } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const w = React.useState<string[]>([\"p2\"]),\n a = React.useState<string[]>([]);\n const P: (ProductCardProps & { id: string })[] = [\n {\n id: \"p1\",\n name: \"Halo Cotton Tee\",\n meta: \"Apparel\",\n art: \"tee\",\n tint: \"sky\",\n price: 32,\n compareAt: 39,\n rating: 4.7,\n reviews: 128,\n badge: \"Bestseller\",\n },\n {\n id: \"p2\",\n name: \"Studio Mug\",\n meta: \"Home\",\n art: \"mug\",\n tint: \"butter\",\n price: 18,\n rating: 4.9,\n reviews: 96,\n },\n {\n id: \"p3\",\n name: \"Field Bottle\",\n meta: \"Outdoor\",\n art: \"bottle\",\n tint: \"mint\",\n price: 28,\n rating: 4.6,\n reviews: 61,\n badge: \"New\",\n },\n {\n id: \"p4\",\n name: \"Canvas Tote\",\n meta: \"Bags\",\n art: \"tote\",\n tint: \"lilac\",\n price: 24,\n rating: 4.8,\n reviews: 143,\n stock: 0,\n badge: \"Sold out\",\n badgeTone: \"neutral\",\n },\n ];\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n {P.map((x) => (\n <ProductCard\n key={x.id}\n {...x}\n wished={w[0].indexOf(x.id) > -1}\n added={a[0].indexOf(x.id) > -1}\n onWish={() => {\n w[1](w[0].indexOf(x.id) > -1 ? w[0].filter((i) => i !== x.id) : w[0].concat([x.id]));\n }}\n onAdd={() => {\n a[1](a[0].concat([x.id]));\n }}\n />\n ))}\n <div\n className=\"tf-panel\"\n style={{\n gridColumn: \"1 / -1\",\n flexDirection: \"row\",\n flexWrap: \"wrap\",\n gap: 24,\n alignItems: \"center\",\n }}\n >\n <Price value={32} compareAt={39} size=\"lg\" />\n <Rating value={4.6} count={61} />\n <StockBadge stock={84} />\n <StockBadge stock={6} />\n <StockBadge stock={0} />\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 8,\n }}\n >\n {ProductArt.kinds.map((k, i) => (\n <ProductArt\n key={k}\n kind={k}\n tint={([\"sky\", \"mint\", \"butter\", \"lilac\", \"blush\"] as const)[i % 5]}\n size={40}\n />\n ))}\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/ProductCard"},{"name":"RadioGroup","title":"Radio Group","group":"Forms","summary":"One choice out of a few, always visible.","description":"One choice out of a few, always visible. `RadioGroup` for 2–5 options with labels and hints, `Segmented` for 2–4 short options that switch a value inline.\n\nRadioGroup props: `label`, `options` (strings or {value, label, hint, disabled}), `value`/`defaultValue`, `onChange(value)`, `cards` (bordered tiles in a grid), `hint`.\nSegmented props: `options` (strings or {value, label, icon}), `value`/`defaultValue`, `onChange(value)`, `size` (\"sm\"), `block`, `label` (accessible name).\n\n- Six or more options, or options that need search: use Combobox.\n- Segmented sets a value (billing period, density); Tabs switch views of content. Don't mix them up.\n- `cards` when each option needs a sentence to explain it (plans, themes).\n","components":["RadioGroup","Segmented"],"code":"import * as React from \"react\";\nimport { RadioGroup, Segmented } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const a = React.useState(\"Team\"),\n b = React.useState(\"Monthly\");\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <RadioGroup\n label=\"Plan\"\n cards\n value={a[0]}\n onChange={a[1]}\n options={[\n {\n value: \"Starter\",\n label: \"Starter\",\n hint: \"€9 per seat · 3 projects\",\n },\n {\n value: \"Team\",\n label: \"Team\",\n hint: \"€24 per seat · unlimited\",\n },\n {\n value: \"Business\",\n label: \"Business\",\n hint: \"SSO, audit log, SLA\",\n },\n ]}\n />\n <div\n style={{\n display: \"flex\",\n gap: 48,\n flexWrap: \"wrap\",\n alignItems: \"flex-start\",\n }}\n >\n <RadioGroup\n label=\"Invoice reminders\"\n defaultValue=\"due\"\n options={[\n {\n value: \"never\",\n label: \"Never\",\n },\n {\n value: \"due\",\n label: \"On the due date\",\n },\n {\n value: \"weekly\",\n label: \"Weekly until paid\",\n hint: \"Stops when the bank feed matches it\",\n },\n ]}\n />\n <div className=\"tf-field\">\n <span className=\"tf-field-label\">Billing</span>\n <Segmented label=\"Billing\" value={b[0]} onChange={b[1]} options={[\"Monthly\", \"Yearly\"]} />\n </div>\n <div className=\"tf-field\">\n <span className=\"tf-field-label\">View</span>\n <Segmented\n label=\"View\"\n size=\"sm\"\n defaultValue=\"grid\"\n options={[\n {\n value: \"grid\",\n label: \"Grid\",\n icon: \"grid\",\n },\n {\n value: \"list\",\n label: \"List\",\n icon: \"menu\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/RadioGroup"},{"name":"RangeSlider","title":"Range Slider","group":"Forms","summary":"Form pieces added for search filters and admin settings: RangeSlider (two thumbs), StatusSelect (a status pill that opens a list), TilePicker (pick one or many visual options) and ColorField (hex input with contrast check).","description":"Form pieces added for search filters and admin settings: `RangeSlider` (two thumbs), `StatusSelect` (a status pill that opens a list), `TilePicker` (pick one or many visual options) and `ColorField` (hex input with contrast check).\n\nRangeSlider: `label`, `min`, `max`, `step`, `value` / `defaultValue` ([low, high]) + `onChange`, `unit`, `inputs` (false shows values instead of fields), `openEnd` (adds \"+\" at max), `format`.\nStatusSelect: `options` ({value, label, status}), `value` / `onChange`, `size` (\"sm\"), `label`.\nTilePicker: `options` ({value, label, meta, image | icon | art, tint, color, disabled}), `value` / `onChange`, `multiple`, `min` (tile width), `label`.\nColorField: `label`, `value` / `onChange`, `against` (colour to check contrast with; false hides it), `hint`.\n","components":["ColorField","RangeSlider","StatusSelect","TilePicker"],"code":"import * as React from \"react\";\nimport { ColorField, RangeSlider, StatusSelect, TilePicker } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const r = React.useState<[number, number]>([300000, 650000]),\n t = React.useState<string | string[]>(\"scandi\"),\n s = React.useState(\"read\");\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 20,\n }}\n >\n <RangeSlider\n label=\"Price\"\n min={0}\n max={1500000}\n step={10000}\n value={r[0]}\n onChange={r[1]}\n unit=\"€\"\n />\n <RangeSlider\n label=\"Living area\"\n min={20}\n max={300}\n defaultValue={[60, 140]}\n inputs={false}\n openEnd\n format={(v) => v + \" m²\"}\n />\n <div className=\"tf-row\">\n <StatusSelect\n value={s[0]}\n onChange={s[1]}\n label=\"Status\"\n options={[\n {\n value: \"unread\",\n label: \"Unread\",\n status: \"running\",\n },\n {\n value: \"read\",\n label: \"Read\",\n status: \"idle\",\n },\n {\n value: \"contacted\",\n label: \"Contacted\",\n status: \"ok\",\n },\n {\n value: \"rejected\",\n label: \"Not a fit\",\n status: \"danger\",\n },\n ]}\n />\n <ColorField value=\"#2f6fe0\" against=\"#ffffff\" />\n </div>\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Room style</b>\n <TilePicker\n value={t[0]}\n onChange={(v) => t[1](v ?? \"\")}\n label=\"Room style\"\n min={120}\n options={[\n {\n value: \"scandi\",\n label: \"Scandinavian\",\n tint: \"sky\",\n icon: \"home\",\n },\n {\n value: \"modern\",\n label: \"Modern\",\n tint: \"mint\",\n icon: \"grid\",\n },\n {\n value: \"industrial\",\n label: \"Industrial\",\n tint: \"butter\",\n icon: \"layers\",\n },\n {\n value: \"classic\",\n label: \"Classic\",\n tint: \"blush\",\n icon: \"star\",\n meta: \"Pro\",\n },\n {\n value: \"japandi\",\n label: \"Japandi\",\n tint: \"lilac\",\n icon: \"sun\",\n },\n {\n value: \"boho\",\n label: \"Boho\",\n disabled: true,\n icon: \"sparkle\",\n meta: \"Soon\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/RangeSlider"},{"name":"ReviewCard","title":"Review Card","group":"Commerce","summary":"Moderating customer reviews before and after they go live.","description":"Moderating customer reviews before and after they go live.\n\nProps: `author`, `verified`, `product`, `date`, `rating`, `title`, `text`, `state` (\"pending\" | \"published\" | \"hidden\" | \"flagged\"), `stateLabel`, `highlight` (words to mark, e.g. refund, scam), `reply`, `onPublish`, `onHide`, `onReply(text)`, `replyLabel`.\n\n- Pending reviews carry the signal edge until someone decides.\n- Hiding is reversible; deleting a review is not offered here.\n","components":["ReviewCard"],"code":"import * as React from \"react\";\nimport { ReviewCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <ReviewCard\n author=\"Jonas P.\"\n verified\n product=\"1,000 followers\"\n date=\"2 days ago\"\n rating={2}\n state=\"pending\"\n title=\"Slower than promised\"\n text=\"Arrived after two days instead of one hour. Support answered quickly though, so two stars. Would be great to get a refund for the delay.\"\n highlight={[\"refund\"]}\n onPublish={() => {}}\n onHide={() => {}}\n onReply={() => {}}\n />\n <ReviewCard\n author=\"Ida N.\"\n product=\"TikTok views\"\n date=\"Apr 12\"\n rating={5}\n state=\"published\"\n text=\"Fast, simple, worked exactly as described.\"\n reply=\"Thanks Ida, glad it worked!\"\n onHide={() => {}}\n onReply={() => {}}\n />\n </div>\n );\n}\n","path":"components/ReviewCard"},{"name":"RichTextEditor","title":"Rich Text Editor","group":"Forms","summary":"Formatted text for newsletters, product descriptions, legal pages and canned replies: toolbar, placeholder fields and a character count.","description":"Formatted text for newsletters, product descriptions, legal pages and canned replies: toolbar, placeholder fields and a character count.\n\nProps: `label`, `defaultValue` (HTML), `onChange(html)`, `placeholder`, `variables` ({key, label}: inserted as `{key}`), `maxLength`, `minHeight`, `footer`, `hint`, `error`, `disabled`.\n\n- The toolbar keeps to bold, italic, underline, heading, quote, lists, link, undo/redo. Anything richer belongs in a dedicated editor.\n- Built on Selection/Range, no `execCommand`. Shortcuts: ⌘/Ctrl B, I, U, K (link), Z and ⇧Z (undo, redo).\n- Links open a small field instead of a browser prompt; addresses without a scheme get https://.\n- Pasting inserts plain text; `onChange` returns HTML limited to p, h3, blockquote, ul, ol, li, b, i, u, a and br.\n- Still sanitize on the server before you send or store it.\n","components":["RichTextEditor"],"code":"import { RichTextEditor } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <RichTextEditor\n label=\"Newsletter text\"\n placeholder=\"Write something…\"\n maxLength={2000}\n defaultValue=\"<h3>Your boost is live</h3><p>Hi {first_name}, your <b>1,000 followers</b> start arriving within the next hour.</p><ul><li>Delivery spreads over 24 h</li><li>Refill guarantee for 30 days</li></ul>\"\n variables={[\n {\n key: \"first_name\",\n label: \"First name\",\n },\n {\n key: \"order_id\",\n label: \"Order number\",\n },\n {\n key: \"coupon\",\n label: \"Coupon code\",\n },\n ]}\n hint=\"Fields in braces are filled per recipient.\"\n />\n </div>\n );\n}\n","path":"components/RichTextEditor"},{"name":"RunHistory","title":"Run History","group":"Admin","summary":"Background work: RunHistory (scraping jobs, cron, imports) and WebhookEndpoint (an outgoing or incoming hook with its deliveries).","description":"Background work: `RunHistory` (scraping jobs, cron, imports) and `WebhookEndpoint` (an outgoing or incoming hook with its deliveries).\n\nRunHistory: `items` ({id, name, status, started, duration, by, counts: {value, label, tone}, detail (node; makes the row expandable)}), `label`.\nWebhookEndpoint: `url`, `events`, `status` + `statusLabel`, `successRate`, `enabled` + `onToggle`, `secret` (node, e.g. SecretField), `deliveries` ({id, code, event, time, ms}), `onRetry(delivery)`, `menu`, `defaultOpen`.\n","components":["LogView","RunHistory","WebhookEndpoint"],"code":"import { LogView, RunHistory, WebhookEndpoint } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <RunHistory\n items={[\n {\n id: 1,\n name: \"ImmoScout · Hamburg\",\n status: \"ok\",\n started: \"Today 06:00\",\n duration: \"4 m 12 s\",\n counts: [\n {\n value: 1284,\n label: \"checked\",\n },\n {\n value: 37,\n label: \"new\",\n tone: \"success\",\n },\n ],\n },\n {\n id: 2,\n name: \"Kleinanzeigen · Hamburg\",\n status: \"warn\",\n started: \"Today 06:00\",\n duration: \"7 m 40 s\",\n counts: [\n {\n value: 922,\n label: \"checked\",\n },\n {\n value: 12,\n label: \"new\",\n tone: \"success\",\n },\n {\n value: 18,\n label: \"failed\",\n tone: \"danger\",\n },\n ],\n detail: (\n <LogView\n title=\"errors\"\n lines={[\n {\n time: \"06:03:11\",\n text: \"HTTP 429 on page 14, waited 30 s\",\n level: \"warn\",\n },\n {\n time: \"06:05:40\",\n text: \"18 detail pages without price, skipped\",\n level: \"warn\",\n },\n ]}\n />\n ),\n },\n {\n id: 3,\n name: \"Daily cleanup\",\n status: \"danger\",\n started: \"Yesterday 03:00\",\n duration: \"12 s\",\n by: \"cron\",\n counts: [\n {\n value: 1,\n label: \"error\",\n tone: \"danger\",\n },\n ],\n detail: \"Database connection refused.\",\n },\n ]}\n />\n </div>\n <WebhookEndpoint\n url=\"https://api.homefield.com/hooks/stripe\"\n events={[\"checkout.session.completed\", \"invoice.paid\", \"customer.subscription.deleted\"]}\n status=\"ok\"\n statusLabel=\"Active\"\n successRate={98.7}\n onToggle={() => {}}\n onRetry={() => {}}\n defaultOpen\n menu={[\n {\n label: \"Send test event\",\n icon: \"send\",\n },\n {\n label: \"Rotate secret\",\n icon: \"key\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n deliveries={[\n {\n id: 1,\n code: 200,\n event: \"invoice.paid\",\n time: \"09:41\",\n ms: 182,\n },\n {\n id: 2,\n code: 500,\n event: \"checkout.session.completed\",\n time: \"09:12\",\n ms: 3012,\n },\n {\n id: 3,\n code: 200,\n event: \"customer.subscription.deleted\",\n time: \"Yesterday\",\n ms: 240,\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/RunHistory"},{"name":"RunTrace","title":"Run Trace","group":"AI","summary":"Waterfall of one agent run: every model call, tool call, search and approval, with timing bars and expandable details.","description":"Waterfall of one agent run: every model call, tool call, search and approval, with timing bars and expandable details.\n\nProps: `steps` ({kind: \"model\" | \"tool\" | \"search\" | \"memory\" | \"approval\" | \"message\" | \"error\", label, meta, start (ms), ms, detail (string or node), status (\"done\" | \"running\" | \"error\")}), `defaultOpen` (index), `footer`.\n\n- The bar shows when a step ran inside the whole run, so slow tools are obvious at a glance.\n- Errors are blush; a running step pulses. Put model, tokens and cost in the footer.\n- Use it in run history and when an approval asks \"why does the agent want this?\".\n","components":["RunTrace"],"code":"import { RunTrace } from \"@twentyfirstmedia/ui-core\";\nimport type { TraceStep } from \"@twentyfirstmedia/ui-core\";\n\nconst steps: TraceStep[] = [\n {\n kind: \"message\",\n label: \"Task received\",\n meta: \"Triage 12 new support emails\",\n start: 0,\n ms: 30,\n detail: \"Trigger: schedule · every hour\",\n },\n {\n kind: \"model\",\n label: \"Planned the work\",\n meta: \"Claude Sonnet 5.5 · 2,140 tokens\",\n start: 70,\n ms: 1300,\n detail: \"1. Read new mail\\n2. Tag each thread\\n3. Draft replies for billing questions\",\n },\n {\n kind: \"tool\",\n label: \"Mailbox\",\n meta: \"Fetched 12 messages\",\n start: 1410,\n ms: 620,\n },\n {\n kind: \"search\",\n label: \"Knowledge base\",\n meta: \"Looked up 4 articles\",\n start: 2070,\n ms: 480,\n },\n {\n kind: \"model\",\n label: \"Wrote 4 drafts\",\n meta: \"9,820 tokens\",\n start: 2590,\n ms: 3100,\n },\n {\n kind: \"approval\",\n label: \"Waiting for approval\",\n meta: \"Autonomy: Drafts only\",\n start: 5730,\n ms: 0,\n status: \"running\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel tf-panel-flush\"\n style={{\n padding: 16,\n }}\n >\n <RunTrace\n steps={steps}\n defaultOpen={1}\n footer={\n <>\n <span>\n {\"Model \"}\n <b>Claude Sonnet 5.5</b>\n </span>\n <span>\n {\"Tokens \"}\n <b>11,960</b>\n </span>\n <span>\n {\"Cost \"}\n <b>€0.036</b>\n </span>\n </>\n }\n />\n </div>\n );\n}\n","path":"components/RunTrace"},{"name":"SearchAdPreview","title":"Search Ad Preview","group":"Admin","summary":"A text ad as it appears in search results, on phone and desktop, with the length limits checked live.","description":"A text ad as it appears in search results, on phone and desktop, with the length limits checked live.\n\nProps: `name`, `domain`, `paths` (up to 2), `headlines` (up to 3 shown), `descriptions` (up to 2), `sitelinks`, `device` / `onDevice`, `sponsoredLabel`.\n\n- Limits: headline 30, description 90, path 15 characters; anything longer is listed as a warning under the preview.\n- The preview imitates the result layout in neutral colours, not the search engine's brand.\n","components":["SearchAdPreview","TextField"],"code":"import * as React from \"react\";\nimport { SearchAdPreview, TextField } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const hl = React.useState([\n \"Real Instagram Followers\",\n \"Delivery in 1 Hour\",\n \"Trusted by 50,000 Creators\",\n ]),\n ds = React.useState([\n \"Grow your account with real followers. Secure payment, 30-day refill guarantee, German support.\",\n \"Start from €2.90. No password needed.\",\n ]);\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n {hl[0].map((v, i) => (\n <TextField\n key={i}\n label={\"Headline \" + (i + 1)}\n value={v}\n onChange={(e) => {\n const n = hl[0].slice();\n n[i] = e.target.value;\n hl[1](n);\n }}\n hint={v.length + \" / 30\"}\n />\n ))}\n {ds[0].map((v, i) => (\n <TextField\n key={\"d\" + i}\n label={\"Description \" + (i + 1)}\n value={v}\n onChange={(e) => {\n const n = ds[0].slice();\n n[i] = e.target.value;\n ds[1](n);\n }}\n hint={v.length + \" / 90\"}\n />\n ))}\n </div>\n <div className=\"tf-panel\">\n <SearchAdPreview\n name=\"Northwind\"\n domain=\"northwind.de\"\n paths={[\"instagram\", \"followers\"]}\n headlines={hl[0]}\n descriptions={ds[0]}\n sitelinks={[\"Buy likes\", \"Pricing\", \"Refill guarantee\", \"Support\"]}\n />\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/SearchAdPreview"},{"name":"SecretField","title":"Secret Field","group":"Forms","summary":"SecretField shows a stored credential without exposing it; MoneyInput takes amounts in the locale's format with a VAT rate.","description":"`SecretField` shows a stored credential without exposing it; `MoneyInput` takes amounts in the locale's format with a VAT rate.\n\nSecretField: `prefix`, `last4`, `value` (shown only after `onReveal` resolves truthy; ask for a passkey there), `meta`, `onCopy`, `onRotate`.\nMoneyInput: `label`, `value` / `onChange(number)`, `currency`, `vat` + `onVat` + `vatOptions`, `showGross`, `hint`, `error`. Parses and formats with setLocale.\n\n- Never render a full secret by default; reveal is a step-up action.\n- Amounts are right-aligned tabular mono so columns line up.\n","components":["MoneyInput","SecretField"],"code":"import * as React from \"react\";\nimport { MoneyInput, SecretField } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const vat = React.useState(\"19\"),\n v = React.useState<number | null>(1240);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <SecretField\n prefix=\"sk_live_\"\n last4=\"4f2a\"\n meta=\"Added Mar 12\"\n onReveal={() => true}\n value=\"sk_live_51Hx…demo…4f2a\"\n onCopy={() => {}}\n onRotate={() => {}}\n />\n <MoneyInput\n label=\"Amount\"\n value={v[0]}\n onChange={v[1]}\n vat={vat[0]}\n onVat={vat[1]}\n vatOptions={[\n {\n value: \"19\",\n label: \"19%\",\n },\n {\n value: \"7\",\n label: \"7%\",\n },\n {\n value: \"0\",\n label: \"0%\",\n },\n {\n value: \"rc\",\n label: \"Reverse charge\",\n },\n ]}\n showGross\n hint=\"Net, as on the invoice\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/SecretField"},{"name":"SettingsLayout","title":"Settings Layout","group":"Foundations","summary":"Standard settings page: a label column with one sentence on what it does, controls on the right, one row per topic.","description":"Standard settings page: a label column with one sentence on what it does, controls on the right, one row per topic.\n\nProps: `sections` ({id, title, text, badge, children}), `footer`. Stacks on narrow screens.\n","components":["Button","SecretField","Segmented","SettingsLayout","Switch"],"code":"import { Button, SecretField, Segmented, SettingsLayout, Switch } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <SettingsLayout\n sections={[\n {\n title: \"Appearance\",\n text: \"Theme and density for this browser.\",\n children: <Segmented defaultValue=\"System\" options={[\"System\", \"Light\", \"Dark\"]} />,\n },\n {\n title: \"Notifications\",\n text: \"What reaches your phone.\",\n children: [\n <Switch key={1} spread defaultChecked>\n Approvals waiting on me\n </Switch>,\n <Switch key={2} spread>\n Weekly digest\n </Switch>,\n ],\n },\n {\n title: \"API key\",\n text: \"Used by the CLI.\",\n children: <SecretField prefix=\"hq_\" last4=\"9c1e\" onCopy={() => {}} />,\n },\n ]}\n footer={<Button icon=\"check\">Save changes</Button>}\n />\n </div>\n );\n}\n","path":"components/SettingsLayout"},{"name":"ShortcutsOverlay","title":"Shortcuts Overlay","group":"Overlays","summary":"The \"?\" overlay listing every keyboard shortcut, grouped by area.","description":"The \"?\" overlay listing every keyboard shortcut, grouped by area.\n\nProps: `open`, `onClose`, `title`, `groups` ({title, items: [label, keys[]]}). Esc closes.\n","components":["Button","ShortcutsOverlay"],"code":"import * as React from \"react\";\nimport { Button, ShortcutsOverlay } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const o = React.useState(true);\n return (\n <div\n style={{\n position: \"relative\",\n height: 460,\n }}\n >\n <Button\n onClick={() => {\n o[1](true);\n }}\n >\n Show shortcuts (?)\n </Button>\n <ShortcutsOverlay\n open={o[0]}\n onClose={() => {\n o[1](false);\n }}\n groups={[\n {\n title: \"Global\",\n items: [\n [\"Search\", [\"⌘\", \"K\"]],\n [\"Shortcuts\", [\"?\"]],\n ],\n },\n {\n title: \"Navigation\",\n items: [\n [\"Go to chats\", [\"G\", \"C\"]],\n [\"Go to approvals\", [\"G\", \"A\"]],\n ],\n },\n {\n title: \"Approvals\",\n items: [\n [\"Run as shown\", [\"A\"]],\n [\"With a change\", [\"B\"]],\n [\"Discard\", [\"C\"]],\n [\"Other instruction\", [\"D\"]],\n [\"Next / previous\", [\"J\", \"K\"]],\n ],\n },\n ]}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/ShortcutsOverlay"},{"name":"SplitPane","title":"Split Pane","group":"Foundations","summary":"Resizable list–detail layout.","description":"Resizable list–detail layout. The divider is the hairline itself with a wider invisible grab area.\n\nProps: `left`, `right`, `defaultSize` (px), `min`, `max`, `storageKey` (remembers the width in this browser).\n\n- Use for mail, approvals and anything with a list and a reading pane. On phones stack the panes instead.\n","components":["InboxList","SplitPane"],"code":"import { InboxList, SplitPane } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel tf-panel-flush\"\n style={{\n height: 280,\n padding: 0,\n overflow: \"hidden\",\n }}\n >\n <SplitPane\n defaultSize={320}\n min={220}\n max={600}\n storageKey=\"demo\"\n left={\n <div\n style={{\n padding: 16,\n }}\n >\n <InboxList\n selected={1}\n items={[\n {\n id: 1,\n from: \"Ida Nolan\",\n time: \"08:40\",\n subject: \"Seat upgrade before renewal\",\n snippet: \"We are growing faster than planned…\",\n unread: true,\n },\n {\n id: 2,\n from: \"Jonas Petit\",\n time: \"Yesterday\",\n subject: \"Pricing review → Thursday?\",\n snippet: \"Growth wants to join…\",\n },\n ]}\n />\n </div>\n }\n right={\n <div\n style={{\n padding: 24,\n }}\n >\n <b>Drag the divider</b>\n <p className=\"tf-card-desc\">\n Double-click resets. ← → move it when focused. The width is remembered per storageKey.\n </p>\n </div>\n }\n />\n </div>\n );\n}\n","path":"components/SplitPane"},{"name":"StackedBarChart","title":"Stacked Bar Chart","group":"Charts","summary":"Bars split into parts of a whole per period, with a hover tooltip and legend.","description":"Bars split into parts of a whole per period, with a hover tooltip and legend.\n\nProps: `labels`, `series` ({name, tint, values}), `height`, `width`, `barWidth`, `format(v)`, `legend` (false hides), `label`.\n\n- Three series at most, ordered bottom-up from the steadiest to the most volatile.\n- Segments use the tints' `-ink` colors with a 2px gap, so neighbours differ in lightness, not hue alone.\n- Hovering a period dims the others and shows totals per series, top series first.\n","components":["StackedBarChart"],"code":"import { StackedBarChart } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <div>\n <h3 className=\"tf-panel-title\">Revenue by plan</h3>\n <p className=\"tf-panel-sub\">Hover a bar for the split</p>\n </div>\n <StackedBarChart\n height={180}\n labels={[\"Nov\", \"Dec\", \"Jan\", \"Feb\", \"Mar\", \"Apr\"]}\n format={(v) => \"€\" + (v / 1000).toFixed(1) + \"k\"}\n series={[\n {\n name: \"Starter\",\n tint: \"sky\",\n values: [3100, 3400, 3300, 3900, 4200, 4500],\n },\n {\n name: \"Team\",\n tint: \"mint\",\n values: [9800, 10400, 11200, 11900, 12800, 13600],\n },\n {\n name: \"Business\",\n tint: \"lilac\",\n values: [5200, 5900, 6100, 6600, 7400, 8100],\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/StackedBarChart"},{"name":"StatCell","title":"Stat Cell","group":"Data","summary":"A KPI with its comparison: label, value, ▲/▼ delta that names what it compares to, optional sparkline.","description":"A KPI with its comparison: label, value, ▲/▼ delta that names what it compares to, optional sparkline.\n\nProps: `label`, `value`, `delta` (percent, signed), `deltaText` (override), `compare` (\"vs last Friday\"), `invert` (down is good: refunds, failures, cost), `spark` (numbers), `icon` + `iconTint`, `tint`.\n\n- Use 3–5 in a row, only when those numbers are the point of the screen.\n- Always say what the delta compares to; good is mint, bad is blush, decided by `invert`.\n","components":["StatCell"],"code":"import * as React from \"react\";\nimport { StatCell } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <StatCell\n label=\"Revenue today\"\n value=\"€4,812\"\n delta={12.4}\n compare=\"vs last Friday\"\n spark={[3, 4, 4, 6, 5, 7, 8]}\n icon=\"card\"\n iconTint=\"butter\"\n />\n <StatCell\n label=\"Orders\"\n value=\"128\"\n delta={4.1}\n compare=\"vs last Friday\"\n spark={[5, 6, 5, 7, 6, 8, 8]}\n />\n <StatCell\n label=\"Refund rate\"\n value=\"1.2%\"\n delta={-0.4}\n compare=\"vs last week\"\n invert\n spark={[4, 4, 3, 3, 2, 2, 2]}\n />\n <StatCell\n label=\"Failed orders\"\n value=\"7\"\n delta={40}\n compare=\"vs yesterday\"\n invert\n spark={[1, 2, 1, 3, 2, 4, 5]}\n tint=\"blush\"\n />\n </div>\n );\n}\n","path":"components/StatCell"},{"name":"StatTile","title":"Stat Tile","group":"Data","summary":"","description":"","components":["Stagger","StatTile"],"code":"import * as React from \"react\";\nimport { Stagger, StatTile } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"128\" label=\"Active members\" />\n <StatTile tint=\"mint\" value=\"24\" label=\"Live projects\" delta=\"+3 this week\" />\n <StatTile tint=\"placeholder\" value=\"9\" label=\"Needs review\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Revenue (MTD)\" />\n </Stagger>\n );\n}\n","path":"components/StatTile"},{"name":"StatusIndicator","title":"Status Indicator","group":"Foundations","summary":"State as shape and colour together, so it reads at a glance and without colour vision.","description":"State as shape and colour together, so it reads at a glance and without colour vision.\n\nProps: `status` (\"ok\" | \"running\" | \"signal\" | \"warn\" | \"danger\" | \"idle\"), `label`, `size` (\"sm\"). `StatusShape` is the bare shape.\n\n| status | shape | token | typical words |\n| --- | --- | --- | --- |\n| ok | dot | `ok` | Done, Online, Valid, Booked |\n| running | pulsing dot | `info` | Working, Syncing |\n| signal | diamond | `signal` | Waiting for approval, Needs a human |\n| warn | triangle | `warn` | Paused · budget, Expiring |\n| danger | square | `danger` | Failed, Offline |\n| idle | ring | `idle` | Ready, Inactive |\n\n- Always shape plus a word; drop the word only in dense columns whose header already names the state.\n- Only `signal` may draw attention unasked. Warnings and errors stay in their own area and never animate.\n- Badges use the same shapes: success dot, info dot, highlight diamond, warning triangle, danger square, neutral ring. Badge tones also accept the status names (ok, running, signal, warn, idle).\n","components":["Badge","StatusIndicator"],"code":"import * as React from \"react\";\nimport { Badge, StatusIndicator } from \"@twentyfirstmedia/ui-core\";\nimport type { Status, Tone } from \"@twentyfirstmedia/ui-core\";\n\nconst S: [Status, string][] = [\n [\"ok\", \"Done\"],\n [\"running\", \"Working\"],\n [\"signal\", \"Waiting for approval\"],\n [\"warn\", \"Paused · budget\"],\n [\"danger\", \"Failed\"],\n [\"idle\", \"Ready\"],\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n {S.map((s) => (\n <StatusIndicator key={s[0]} status={s[0]} label={s[1]} />\n ))}\n </div>\n <div className=\"tf-row\">\n {(\n [\n [\"success\", \"Live\"],\n [\"info\", \"Pending\"],\n [\"highlight\", \"Needs review\"],\n [\"warning\", \"Expiring\"],\n [\"danger\", \"Overdue\"],\n [\"neutral\", \"Draft\"],\n ] as [Tone, string][]\n ).map((b) => (\n <Badge key={b[0]} tone={b[0]}>\n {b[1]}\n </Badge>\n ))}\n </div>\n </div>\n );\n}\n","path":"components/StatusIndicator"},{"name":"Table","title":"Table","group":"Data","summary":"Quiet data table: uppercase captions, hairline rows, right-aligned tabular numbers.","description":"Quiet data table: uppercase captions, hairline rows, right-aligned tabular numbers.\n\nProps: `columns` ({key, label, align, render}), `rows`. Place inside a `surface` Panel; numbers right-aligned; status as Badges.\n","components":["Avatar","Badge","Table"],"code":"import { Avatar, Badge, Table } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n background: \"var(--surface)\",\n borderRadius: \"var(--radius-lg)\",\n padding: 8,\n }}\n >\n <Table\n columns={[\n {\n key: \"name\",\n label: \"Customer\",\n render: (r) => (\n <span className=\"tf-row\">\n <Avatar name={r.name} size={26} />\n {r.name}\n </span>\n ),\n },\n {\n key: \"plan\",\n label: \"Plan\",\n },\n {\n key: \"status\",\n label: \"Status\",\n render: (r) => <Badge tone={r.tone}>{r.status}</Badge>,\n },\n {\n key: \"mrr\",\n label: \"MRR\",\n align: \"right\",\n },\n ]}\n rows={[\n {\n name: \"Northwind\",\n plan: \"Team\",\n status: \"Active\",\n tone: \"success\",\n mrr: \"€1,240\",\n },\n {\n name: \"Blue Fern\",\n plan: \"Starter\",\n status: \"Trial\",\n tone: \"info\",\n mrr: \"€0\",\n },\n {\n name: \"Copperline\",\n plan: \"Business\",\n status: \"Past due\",\n tone: \"warning\",\n mrr: \"€3,900\",\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/Table"},{"name":"TextField","title":"Text Field","group":"Forms","summary":"Text input with label, required or optional mark, hint, and inline validation (error or success).","description":"Text input with label, required or optional mark, hint, and inline validation (error or success).\n\nProps: `label`, `required`, `optional`, `hint`, `error`, `success`, `icon` (leading IconName), `suffix` (unit text), `type`, `value`, `defaultValue`, `onChange`, `onBlur`, `disabled`, `readOnly`, `placeholder`, `autoComplete`, `autoFocus`, `id`, `name`.\n\nNative constraints go straight to the input, so the browser checks them and `Form` reads them: `min`, `max` and `step` (for `type=\"number\"`, dates and times), `minLength`, `maxLength`, `pattern`, and `inputMode` for the touch keyboard.\n\n```tsx\n<TextField label=\"Seats\" name=\"seats\" type=\"number\" min={1} max={50} step={1} required />\n```\n\n- `name` is the value's key in `Form` and where its validation message shows; `required` marks the label and sets the native attribute.\n- Validate on blur, not on every key: show the error once the person leaves the field, then clear it live as they fix it.\n- Error text says what is wrong in plain words (\"That doesn’t look like an email address\"), never \"Invalid input\".\n- Use `success` sparingly, for checks that took effort (a free username, a verified domain).\n- Mark the minority: if most fields are required, mark the optional ones instead.\n- `FormField` is the same shell without an input, for wrapping custom controls.\n","components":["TextField"],"code":"import * as React from \"react\";\nimport { TextField } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const e = React.useState(\"mara@\"),\n n = React.useState(\"Mara Ellis\");\n const bad = !/^[^@\\s]+@[^@\\s]+\\.[a-z]{2,}$/i.test(e[0]);\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <TextField\n label=\"Full name\"\n required\n value={n[0]}\n onChange={(x) => {\n n[1](x.target.value);\n }}\n success={n[0].length > 1 ? \"Looks good\" : null}\n />\n <TextField\n label=\"Email\"\n required\n icon=\"mail\"\n value={e[0]}\n onChange={(x) => {\n e[1](x.target.value);\n }}\n error={bad ? \"That doesn’t look like an email address\" : null}\n />\n <TextField label=\"Company\" optional placeholder=\"Acme GmbH\" hint=\"Shown on invoices\" />\n <TextField label=\"Monthly budget\" suffix=\"EUR\" defaultValue=\"1,200\" />\n <TextField\n label=\"Workspace URL\"\n defaultValue=\"halo.app/acme\"\n disabled\n hint=\"Ask an owner to change it\"\n />\n </div>\n );\n}\nexport default App;\n","path":"components/TextField"},{"name":"Toaster","title":"Toaster","group":"Overlays","summary":"Short confirmation that something happened, with optional Undo.","description":"Short confirmation that something happened, with optional Undo. Call `toast(...)` anywhere; render `<Toaster/>` once.\n\n`toast(text)` or `toast({ text, tone, icon, undo, action: {label, onClick}, duration })` returns an id; `toast.dismiss(id)`.\nToaster props: `position` (\"bottom\" default, \"top\").\n\n- Tone follows the status map: success for done, info for started, danger for removed or failed.\n- Offer `undo` for every reversible destructive action instead of a confirm dialog; Undo toasts last 6 s, others 4 s.\n- Hover pauses the timer; the thin line at the bottom shows time left.\n- One sentence, past tense, names the thing: \"Invoice #2097 sent\", not \"Success!\".\n","components":["Button","Toaster"],"code":"import * as React from \"react\";\nimport { Button, Toaster, toast } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n React.useEffect(() => {\n toast({\n text: \"Invoice #2097 sent to Northwind\",\n tone: \"success\",\n duration: 600000,\n });\n toast({\n text: \"2 people removed\",\n tone: \"danger\",\n icon: \"trash\",\n undo: () => {\n toast({\n text: \"Restored\",\n tone: \"info\",\n duration: 600000,\n });\n },\n duration: 600000,\n });\n }, []);\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <div className=\"tf-row\">\n <Button\n variant=\"soft\"\n onClick={() => {\n toast(\"Profile saved\");\n }}\n >\n Success\n </Button>\n <Button\n variant=\"soft\"\n onClick={() => {\n toast({\n text: \"Build started for halo-web\",\n tone: \"info\",\n });\n }}\n >\n Info\n </Button>\n <Button\n variant=\"soft\"\n onClick={() => {\n toast({\n text: \"Project archived\",\n undo: () => {\n toast(\"Restored\");\n },\n });\n }}\n >\n With undo\n </Button>\n </div>\n <p\n className=\"tf-card-desc\"\n style={{\n margin: 0,\n }}\n >\n Toasts stack bottom-center above the dock, three at most. Hover pauses the timer.\n </p>\n <Toaster />\n </div>\n );\n}\nexport default App;\n","path":"components/Toaster"},{"name":"ToolCall","title":"Tool Call","group":"AI","summary":"Shows what an agent did between your question and its answer.","description":"Shows what an agent did between your question and its answer. `ToolCall` is one tool use; `Reasoning` is the agent's plan.\n\nToolCall props: `name`, `icon`, `status` (\"running\" | \"done\" | \"error\" | \"waiting\"), `summary` (one line, past tense when done), `duration`, `input`, `output` (string or object, shown as JSON), `defaultOpen`.\nReasoning props: `steps` (string[]) or `text`, `duration`, `streaming` (shimmering \"Thinking…\"), `defaultOpen`.\n\n- Collapsed by default: the one-line summary is what people read; details are there when they doubt it.\n- Status icon colors follow the status map: mint done, sky running, butter waiting, blush error.\n- Put them in ChatMessage's `before` slot, reasoning first, tools in the order they ran.\n","components":["Reasoning","ToolCall"],"code":"import { Reasoning, ToolCall } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 8,\n }}\n >\n <Reasoning\n steps={[\n \"Find the latest thread from Northwind\",\n \"Check seat pricing and the renewal date\",\n \"Draft a short reply, ask before sending\",\n ]}\n duration=\"1.4 s\"\n defaultOpen\n />\n <ToolCall\n name=\"Mailbox\"\n icon=\"mail\"\n status=\"done\"\n summary=\"Read 3 messages in “Seat upgrade”\"\n duration=\"0.8 s\"\n defaultOpen\n input={{\n query: \"from:ida@northwind.io\",\n limit: 3,\n }}\n output={{\n messages: 3,\n latest: \"We need 40 more seats before May 1.\",\n }}\n />\n <ToolCall\n name=\"Knowledge base\"\n icon=\"book\"\n status=\"running\"\n summary=\"Searching “seat upgrade pricing”\"\n />\n <ToolCall\n name=\"Run checks\"\n icon=\"terminal\"\n status=\"error\"\n summary=\"146 passed · 1 type error\"\n duration=\"4.2 s\"\n output=\"Seats.tsx:42 Type 'string' is not assignable to type 'number'.\"\n />\n <ToolCall\n name=\"Deployments\"\n icon=\"zap\"\n status=\"waiting\"\n summary=\"Needs approval to deploy to production\"\n />\n <Reasoning streaming />\n </div>\n );\n}\n","path":"components/ToolCall"},{"name":"Tooltip","title":"Tooltip","group":"Overlays","summary":"Tooltip names an icon-only control on hover or focus; Popover holds a small interactive panel anchored to a trigger.","description":"`Tooltip` names an icon-only control on hover or focus; `Popover` holds a small interactive panel anchored to a trigger.\n\nTooltip props: `label`, `shortcut`, `side` (\"top\" | \"bottom\" | \"left\" | \"right\"), one child element.\nPopover props: `trigger` (element, or `({open, toggle}) => element`), `open`/`onOpenChange` (optional control), `align` (\"start\" | \"end\"), `side` (\"top\"), `width`, `flush` (no padding, for Menus), `block`, children or `({close}) => children`.\n\n- Tooltips only add a name or a shortcut. Anything you must read or click goes in a Popover.\n- Tooltips appear after 350 ms on hover, immediately on keyboard focus, and close on Esc.\n- Popovers close on outside click and Esc. Use a Modal when the task needs more than a few fields.\n","components":["Button","Combobox","Icon","Popover","Tooltip"],"code":"import { Button, Combobox, Icon, Popover, Tooltip } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 40,\n paddingTop: 56,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n }}\n >\n <Tooltip label=\"Archive\" shortcut=\"E\">\n <button type=\"button\" className=\"tf-iconbtn tf-iconbtn-soft\" aria-label=\"Archive\">\n <Icon name=\"inbox\" />\n </button>\n </Tooltip>\n <Tooltip label=\"Star\">\n <button type=\"button\" className=\"tf-iconbtn tf-iconbtn-soft\" aria-label=\"Star\">\n <Icon name=\"star\" />\n </button>\n </Tooltip>\n <Tooltip label=\"Opens in a new tab\" side=\"bottom\">\n <button type=\"button\" className=\"tf-btn tf-btn-secondary\">\n View invoice\n </button>\n </Tooltip>\n <Tooltip label=\"Copy share link\" shortcut=\"⌘L\" defaultOpen>\n <button type=\"button\" className=\"tf-iconbtn tf-iconbtn-soft\" aria-label=\"Share\">\n <Icon name=\"share\" />\n </button>\n </Tooltip>\n </div>\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n alignItems: \"flex-start\",\n // room for the open popover, so the preview shows it whole\n minHeight: 260,\n }}\n >\n <Popover\n open\n width={280}\n trigger={\n <button type=\"button\" className=\"tf-btn tf-btn-soft\">\n <Icon name=\"users\" size={15} />\n Assign\n </button>\n }\n >\n <div\n className=\"tf-col\"\n style={{\n gap: 12,\n }}\n >\n <b>Assign to</b>\n <Combobox\n options={[\"Ava Vogel\", \"Ben Okafor\", \"Clara Haddad\"]}\n placeholder=\"Search people\"\n />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"flex-end\",\n }}\n >\n <Button size=\"sm\">Assign</Button>\n </div>\n </div>\n </Popover>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/Tooltip"},{"name":"TreeView","title":"Tree View","group":"Foundations","summary":"Hierarchies, in-place editing and object menus: TreeView, InlineEdit, ContextMenu.","description":"Hierarchies, in-place editing and object menus: `TreeView`, `InlineEdit`, `ContextMenu`.\n\nTreeView: `items` ({id, label, icon, meta, children}), `defaultOpen` (ids), `selected` + `onSelect`, `label`. Arrow keys open, close and move; meta sits right in mono.\nInlineEdit: `value`, `onSave(v)`, `options` (select instead of text), `mono`, `type`, `display(v)`, `align`. Double-click or Enter edits, Enter saves, Esc cancels.\nContextMenu: `items` (Menu items), children (the target). Right-click or a long press opens it at the pointer; destructive actions last.\n","components":["ContextMenu","InlineEdit","KeyValue","TreeView"],"code":"import * as React from \"react\";\nimport { ContextMenu, InlineEdit, KeyValue, TreeView } from \"@twentyfirstmedia/ui-core\";\n\nconst tree = [\n {\n id: \"fx\",\n label: \"FollowerX GmbH\",\n meta: \"€48,210\",\n children: [\n {\n id: \"fp\",\n label: \"Northwind\",\n meta: \"€31,400\",\n children: [\n {\n id: \"ads\",\n label: \"Ads\",\n meta: \"€9,800\",\n },\n {\n id: \"smm\",\n label: \"SMM providers\",\n meta: \"€14,100\",\n },\n ],\n },\n {\n id: \"ch\",\n label: \"Chatteo\",\n meta: \"€12,610\",\n },\n ],\n },\n {\n id: \"tv\",\n label: \"Trepte Ventures UG\",\n meta: \"€4,200\",\n },\n];\nfunction App() {\n const s = React.useState(\"ads\");\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n padding: 12,\n }}\n >\n <TreeView\n items={tree}\n defaultOpen={[\"fx\", \"fp\"]}\n selected={s[0]}\n onSelect={s[1]}\n label=\"Cost centres\"\n />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <span className=\"tf-card-desc\">Double-click or Enter to edit</span>\n <KeyValue\n items={[\n [\n \"Category\",\n <InlineEdit value=\"Software\" options={[\"Software\", \"Hosting\", \"Marketing\"]} />,\n ],\n [\"Amount\", <InlineEdit value=\"1,240.00\" mono />],\n ]}\n />\n <ContextMenu\n items={[\n {\n label: \"Open\",\n icon: \"eye\",\n },\n {\n label: \"Rename\",\n icon: \"edit\",\n shortcut: \"R\",\n },\n \"-\",\n {\n label: \"Delete\",\n icon: \"trash\",\n danger: true,\n },\n ]}\n >\n <div\n style={{\n padding: 24,\n borderRadius: \"var(--radius-md)\",\n background: \"var(--canvas)\",\n textAlign: \"center\",\n }}\n >\n Right-click or long-press here\n </div>\n </ContextMenu>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"components/TreeView"},{"name":"TriggerList","title":"Trigger List","group":"AI","summary":"What starts agents and when: TriggerList and the week view ScheduleCalendar.","description":"What starts agents and when: `TriggerList` and the week view `ScheduleCalendar`.\n\nTriggerList: `items` ({id, type: \"schedule\" | \"event\" | \"webhook\" | \"mail\", rule, agent, by, next, enabled}), `onToggle(item)`, `onRun(item)`.\nScheduleCalendar: `events` ({day 0–6, start, end (hours), label, sub, tint, self}), `from`, `to`, `days` (labels; defaults to the locale's weekdays), `onOpen(event)`. Events an agent created for itself get a signal outline.\n","components":["ScheduleCalendar","TriggerList"],"code":"import { ScheduleCalendar, TriggerList } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <TriggerList\n onToggle={() => {}}\n onRun={() => {}}\n items={[\n {\n id: 1,\n type: \"schedule\",\n rule: \"Mon–Fri 08:00\",\n agent: \"Main Brain\",\n by: \"Config\",\n next: \"Tomorrow 08:00\",\n },\n {\n id: 2,\n type: \"mail\",\n rule: \"New mail to support@\",\n agent: \"Atlas\",\n by: \"Mara Ellis\",\n next: \"On event\",\n },\n {\n id: 3,\n type: \"webhook\",\n rule: \"Stripe dispute opened\",\n agent: \"Ledger\",\n by: \"Config\",\n next: \"On event\",\n enabled: false,\n },\n ]}\n />\n </div>\n <div className=\"tf-panel tf-scroll-x\">\n <ScheduleCalendar\n from={7}\n to={19}\n days={[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]}\n events={[\n {\n day: 0,\n start: 8,\n end: 9,\n label: \"Briefing\",\n tint: \"lilac\",\n },\n {\n day: 1,\n start: 8,\n end: 9,\n label: \"Briefing\",\n tint: \"lilac\",\n },\n {\n day: 2,\n start: 10,\n end: 12,\n label: \"Weekly report\",\n sub: \"Ledger\",\n tint: \"butter\",\n },\n {\n day: 4,\n start: 14,\n end: 15,\n label: \"Follow-ups\",\n tint: \"sky\",\n self: true,\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/TriggerList"},{"name":"TwoFactorSetup","title":"Two Factor Setup","group":"Forms","summary":"Turning on two-factor sign-in: scan, confirm with a code, save recovery codes.","description":"Turning on two-factor sign-in: scan, confirm with a code, save recovery codes.\n\nProps: `qr` (node: render the otpauth URI with your QR library), `secret`, `onVerify(code)` (return or resolve true/false), `codes` (recovery codes), `onDownload`, `onDone`, `step`, `scanText`, `codesText`.\n\n- The last step stays locked until the person confirms they saved the codes. (In this preview the code 123456 works.)\n","components":["TwoFactorSetup"],"code":"import { TwoFactorSetup } from \"@twentyfirstmedia/ui-core\";\nfunction qr() {\n const cells = [],\n n = 25;\n let seed = 7;\n for (let y = 0; y < n; y++)\n for (let x = 0; x < n; x++) {\n seed = (seed * 9301 + 49297) % 233280;\n const f = (x < 7 && y < 7) || (x > 17 && y < 7) || (x < 7 && y > 17);\n const on = f\n ? (x % 6 === 0 || y % 6 === 0 || (x % 6 > 1 && x % 6 < 5 && y % 6 > 1 && y % 6 < 5)) &&\n !(x === 7 || y === 7)\n : seed / 233280 > 0.5;\n if (on) cells.push(<rect key={x + \"-\" + y} x={x} y={y} width={1} height={1} />);\n }\n return (\n <svg\n viewBox=\"0 0 25 25\"\n width={144}\n height={144}\n fill=\"currentColor\"\n shapeRendering=\"crispEdges\"\n >\n {cells}\n </svg>\n );\n}\nexport default function Preview() {\n return (\n <div className=\"tf-panel\">\n <TwoFactorSetup\n qr={qr()}\n secret=\"JBSWY3DPEHPK3PXP\"\n codes={[\n \"8f2a-91c4\",\n \"3b7e-0d55\",\n \"a1c9-6f20\",\n \"7d44-e8b1\",\n \"c0f3-2a97\",\n \"5e18-b3d6\",\n \"9b62-4c0e\",\n \"e7a5-1f83\",\n ]}\n onVerify={(c) => c === \"123456\"}\n onDownload={() => {}}\n onDone={() => {}}\n />\n </div>\n );\n}\n","path":"components/TwoFactorSetup"},{"name":"UptimeBar","title":"Uptime Bar","group":"Data","summary":"Health at a glance: UptimeBar (a bar per day), Meter (usage against a limit), ServerCard, DeployPipeline.","description":"Health at a glance: `UptimeBar` (a bar per day), `Meter` (usage against a limit), `ServerCard`, `DeployPipeline`.\n\nUptimeBar: `days` (\"ok\" | \"warn\" | \"danger\" | \"none\" or {status, label, note}), `uptime` (percent), `caption`. Hover shows the day.\nMeter: `label`, `value`, `max`, `text`, `warn` (75), `danger` (90), `tone`.\nServerCard: `name`, `provider`, `address`, `logo`, `status`, `statusLabel`, `meters` ({label, value, max, text, unknown}; value null shows \"Unknown\"), `uptime` (UptimeBar props), `foot`.\nDeployPipeline: `stages` ({name, status: \"done\" | \"running\" | \"signal\" | \"danger\" | \"idle\", duration, action}).\n","components":["DeployPipeline","Meter","ServerCard","UptimeBar"],"code":"import * as React from \"react\";\nimport { DeployPipeline, Meter, ServerCard, UptimeBar } from \"@twentyfirstmedia/ui-core\";\nimport type { UptimeDay } from \"@twentyfirstmedia/ui-core\";\n\nconst days: UptimeDay[] = [];\nfor (let i = 0; i < 90; i++)\n days.push(\n i === 61\n ? {\n status: \"danger\",\n note: \"14 min down\",\n }\n : i === 40 || i === 77\n ? {\n status: \"warn\",\n note: \"slow responses\",\n }\n : {\n status: i < 6 ? \"none\" : \"ok\",\n },\n );\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <ServerCard\n name=\"ax41-fsn1\"\n provider=\"Hetzner\"\n address=\"95.217.14.80\"\n status=\"ok\"\n statusLabel=\"Online\"\n meters={[\n {\n label: \"CPU\",\n value: 34,\n text: \"34%\",\n },\n {\n label: \"RAM\",\n value: 81,\n text: \"52 / 64 GB\",\n },\n {\n label: \"Disk\",\n value: null,\n },\n ]}\n foot=\"Last deploy 2 h ago · #482\"\n />\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <b>northwind.de</b>\n <UptimeBar days={days} uptime={99.96} />\n <Meter label=\"Budget\" value={82} text=\"€820 / €1,000\" />\n <DeployPipeline\n stages={[\n {\n name: \"Pull\",\n status: \"done\",\n duration: \"4 s\",\n },\n {\n name: \"Build\",\n status: \"done\",\n duration: \"1 m 12 s\",\n },\n {\n name: \"Approve\",\n status: \"signal\",\n },\n {\n name: \"Release\",\n },\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"components/UptimeBar"},{"name":"ValuationCard","title":"Valuation Card","group":"Real estate","summary":"Real-estate figures: ValuationCard (estimate with range and asking price), SavedSearch (a search that runs on its own and reports new hits) and EnergyClass (the energy label scale).","description":"Real-estate figures: `ValuationCard` (estimate with range and asking price), `SavedSearch` (a search that runs on its own and reports new hits) and `EnergyClass` (the energy label scale).\n\nValuationCard: `value`, `low`, `high`, `asking`, `perSqm`, `compare`, `currency`, `confidence` (0–1) or `confidenceLabel`, `facts` (KeyValue items), `note`, `title`, `action`.\nSavedSearch: `name`, `criteria` (strings), `lastRun`, `frequency`, `newCount`, `enabled` + `onToggle`, `onEdit`, `onOpen`, `openLabel`, `icon`, `tint`.\nEnergyClass: `value`, `kwh`, `kind`, `scale` (default A+…H).\n","components":["EnergyClass","SavedSearch","ValuationCard"],"code":"import * as React from \"react\";\nimport { EnergyClass, SavedSearch, ValuationCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <ValuationCard\n value={612000}\n low={571000}\n high={655000}\n asking={649000}\n perSqm={7286}\n compare=\"district average €6,940\"\n confidence={0.78}\n facts={[\n [\"Comparables\", \"42 sales, last 18 months\"],\n [\"Method\", \"Comparable sales\"],\n [\"As of\", \"Nov 8, 2026\"],\n ]}\n note=\"An estimate, not an appraisal.\"\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <SavedSearch\n name=\"Eppendorf 3 rooms\"\n criteria={[\n \"Buy\",\n \"Hamburg-Eppendorf\",\n \"€400k–700k\",\n \"70–110 m²\",\n \"3+ rooms\",\n \"Private sellers\",\n ]}\n lastRun=\"06:00\"\n frequency=\"Daily mail\"\n newCount={4}\n onToggle={() => {}}\n onEdit={() => {}}\n onOpen={() => {}}\n />\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Energy</b>\n <EnergyClass value=\"C\" kwh={92} kind=\"Consumption · gas\" />\n </div>\n </div>\n </div>\n );\n}\n","path":"components/ValuationCard"},{"name":"VariantPicker","title":"Variant Picker","group":"Commerce","summary":"Pick a product option: colour swatches or text pills (sizes, materials).","description":"Pick a product option: colour swatches or text pills (sizes, materials).\n\nProps: `label`, `type` (\"color\" for swatches), `options` (strings or {value, label, color, disabled}), `value` / `defaultValue`, `onChange`.\n\n- The legend repeats the current choice (\"Colour Sky\") so swatches don't need text.\n- Sold-out options stay visible but struck through and disabled, so people know the size exists.\n","components":["VariantPicker"],"code":"import { VariantPicker } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel\"\n style={{\n gap: 24,\n }}\n >\n <VariantPicker\n label=\"Colour\"\n type=\"color\"\n defaultValue=\"Sky\"\n options={[\n {\n value: \"Sky\",\n label: \"Sky\",\n color: \"#9fc3f0\",\n },\n {\n value: \"Ink\",\n label: \"Ink\",\n color: \"#1d1d1f\",\n },\n {\n value: \"Bone\",\n label: \"Bone\",\n color: \"#ece6d9\",\n },\n {\n value: \"Moss\",\n label: \"Moss\",\n color: \"#7d9a5e\",\n disabled: true,\n },\n ]}\n />\n <VariantPicker\n label=\"Size\"\n defaultValue=\"M\"\n options={[\n {\n value: \"XS\",\n label: \"XS\",\n disabled: true,\n },\n \"S\",\n \"M\",\n \"L\",\n \"XL\",\n ]}\n />\n </div>\n );\n}\n","path":"components/VariantPicker"},{"name":"VoiceMode","title":"Voice Mode","group":"AI","summary":"Talking to an agent instead of typing: same agent, same tools, same approvals, same thread.","description":"Talking to an agent instead of typing: same agent, same tools, same approvals, same thread.\n\nProps: `name`, `tint`, `state` (\"listening\" | \"thinking\" | \"speaking\" | \"idle\"), `stateLabel`, `transcript`, `muted`, `onMute`, `onEnd`.\n\n- Approvals asked by voice still show the ApprovalCard; money needs the passkey on screen.\n","components":["Segmented","VoiceMode"],"code":"import * as React from \"react\";\nimport { Segmented, VoiceMode } from \"@twentyfirstmedia/ui-core\";\nimport type { VoiceModeProps } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const s = React.useState<VoiceModeProps[\"state\"]>(\"listening\");\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <Segmented\n value={s[0]}\n onChange={(v) => s[1](v as VoiceModeProps[\"state\"])}\n options={[\n { value: \"listening\", label: \"Listening\" },\n { value: \"thinking\", label: \"Thinking\" },\n { value: \"speaking\", label: \"Speaking\" },\n { value: \"idle\", label: \"Idle\" },\n ]}\n />\n <VoiceMode\n name=\"Atlas\"\n tint=\"sky\"\n state={s[0]}\n transcript={s[0] === \"speaking\" ? \"“I drafted the reply. Should I send it?”\" : null}\n onMute={() => {}}\n onEnd={() => {}}\n />\n </div>\n );\n}\nexport default App;\n","path":"components/VoiceMode"},{"name":"Accordion","title":"Accordion","group":"Foundations","summary":"Stacked sections that open and close.","description":"Stacked sections that open and close. One section at a time by default, or several with `multiple`.\n\n- `cards` (default) shows each section as a card; `flush` uses hairlines, for use inside a panel.\n- Keyboard: Tab reaches each header, ↑/↓ move between headers, Home/End jump, Enter or Space toggles.\n- Headers are buttons with `aria-expanded`; panels are regions labelled by their header.\n","components":["Accordion","Badge","Panel"],"code":"import * as React from \"react\";\nimport { Accordion, Badge, Panel } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", gap: 24, alignItems: \"start\" } as React.CSSProperties}\n >\n <Accordion\n defaultValue={[\"billing\"]}\n items={[\n {\n id: \"billing\",\n title: \"How does billing work?\",\n content:\n \"You pay per seat and month. Seats added mid-cycle are prorated to the next invoice.\",\n },\n {\n id: \"export\",\n title: \"Can I export my data?\",\n content: \"Yes. Every table exports to CSV, and the API returns the same fields.\",\n },\n {\n id: \"sso\",\n title: \"Do you support single sign-on?\",\n meta: <Badge tone=\"info\">Team plan</Badge>,\n content: \"SAML and OIDC are included from the Team plan up.\",\n },\n { id: \"old\", title: \"Legacy importer\", disabled: true, content: \"\" },\n ]}\n />\n <Panel title=\"Settings\">\n <Accordion\n variant=\"flush\"\n multiple\n defaultValue={[\"n\", \"p\"]}\n items={[\n {\n id: \"n\",\n icon: \"bell\",\n title: \"Notifications\",\n content: \"Approvals and overdue invoices reach you by mail and push.\",\n },\n {\n id: \"p\",\n icon: \"lock\",\n title: \"Privacy\",\n content: \"Workspace data stays in the EU region.\",\n },\n {\n id: \"a\",\n icon: \"code\",\n title: \"API access\",\n content: \"Two active keys. Rotate them from Developer settings.\",\n },\n ]}\n />\n </Panel>\n </div>\n );\n}\n","path":"components/Accordion"},{"name":"Separator","title":"Separator","group":"Foundations","summary":"Small layout primitives.","description":"Small layout primitives.\n\n- **Separator**: a hairline between groups, horizontal or vertical, optionally with a label in the middle (\"or\").\n- **Label**: the field label on its own, with a required mark or a muted hint (\"optional\").\n- **AspectRatio**: a box that keeps its ratio (16 : 9 by default) for images, video and placeholders.\n","components":["Separator","Label","AspectRatio","Input","Button"],"code":"import * as React from \"react\";\nimport { AspectRatio, Button, Input, Label, Separator } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", gap: 24, alignItems: \"start\" } as React.CSSProperties}\n >\n <div className=\"tf-panel\" style={{ gap: 16 }}>\n <div className=\"tf-col\" style={{ gap: 8 }}>\n <Label htmlFor=\"sep-mail\" required>\n Work email\n </Label>\n <Input id=\"sep-mail\" placeholder=\"you@company.com\" />\n </div>\n <div className=\"tf-col\" style={{ gap: 8 }}>\n <Label htmlFor=\"sep-team\" hint=\"optional\">\n Team name\n </Label>\n <Input id=\"sep-team\" placeholder=\"Design\" />\n </div>\n <Separator label=\"or\" />\n <Button variant=\"secondary\" icon=\"globe\">\n Continue with Google\n </Button>\n <div className=\"tf-row\" style={{ gap: 12, alignItems: \"center\", flexWrap: \"nowrap\" }}>\n <span className=\"tf-card-desc\">Docs</span>\n <Separator orientation=\"vertical\" />\n <span className=\"tf-card-desc\">Status</span>\n <Separator orientation=\"vertical\" />\n <span className=\"tf-card-desc\">Changelog</span>\n </div>\n </div>\n <div className=\"tf-panel\" style={{ gap: 12 }}>\n <AspectRatio ratio={16 / 9}>\n <div\n className=\"tf-gallery-ph tf-t-sky\"\n style={{ height: \"100%\", display: \"grid\", placeItems: \"center\" }}\n >\n 16 : 9\n </div>\n </AspectRatio>\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", \"--cols-sm\": \"1fr 1fr\", gap: 12 } as React.CSSProperties}\n >\n <AspectRatio ratio={1}>\n <div\n className=\"tf-gallery-ph tf-t-mint\"\n style={{ height: \"100%\", display: \"grid\", placeItems: \"center\" }}\n >\n 1 : 1\n </div>\n </AspectRatio>\n <AspectRatio ratio={4 / 5}>\n <div\n className=\"tf-gallery-ph tf-t-butter\"\n style={{ height: \"100%\", display: \"grid\", placeItems: \"center\" }}\n >\n 4 : 5\n </div>\n </AspectRatio>\n </div>\n </div>\n </div>\n );\n}\n","path":"components/Separator"},{"name":"ButtonGroup","title":"Button Group","group":"Foundations","summary":"Attached buttons that act as one control: undo/redo, zoom, or a split button whose arrow opens a menu.","description":"Attached buttons that act as one control: undo/redo, zoom, or a split button whose arrow opens a menu.\n\n- Children are regular `Button`, `IconButton` or a `Popover` trigger; the group only joins their corners.\n- `label` names the group for assistive tech; `orientation=\"vertical\"` stacks them.\n","components":["ButtonGroup","Button","IconButton","Popover","Menu"],"code":"import { Button, ButtonGroup, IconButton, Menu, Popover } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ gap: 20, paddingBottom: 200 }}>\n <div className=\"tf-row\" style={{ gap: 16 }}>\n <ButtonGroup label=\"History\">\n <Button variant=\"secondary\" icon=\"undo\">\n Undo\n </Button>\n <Button variant=\"secondary\" icon=\"redo\">\n Redo\n </Button>\n </ButtonGroup>\n <ButtonGroup label=\"Zoom\">\n <IconButton icon=\"minus\" label=\"Zoom out\" variant=\"outline\" />\n <Button variant=\"secondary\">100%</Button>\n <IconButton icon=\"plus\" label=\"Zoom in\" variant=\"outline\" />\n </ButtonGroup>\n </div>\n <ButtonGroup label=\"Publish\">\n <Button icon=\"send\">Publish</Button>\n <Popover\n open\n flush\n align=\"end\"\n trigger={<IconButton icon=\"chevronDown\" label=\"More publish options\" variant=\"solid\" />}\n >\n <Menu\n items={[\n { label: \"Schedule…\", icon: \"calendar\" },\n { label: \"Publish to staging\", icon: \"upload\" },\n \"-\",\n { label: \"Save as draft\", icon: \"file\" },\n ]}\n />\n </Popover>\n </ButtonGroup>\n </div>\n );\n}\n","path":"components/ButtonGroup"},{"name":"ScrollArea","title":"Scroll Area","group":"Foundations","summary":"A scrolling box with thin scrollbars in the kit's colours, vertical, horizontal or both.","description":"A scrolling box with thin scrollbars in the kit's colours, vertical, horizontal or both.\n\n- The area is a focusable region with a name (`label`), so it scrolls with the arrow keys.\n- Scrolling stays inside the box (no scroll chaining into the page).\n","components":["ScrollArea","Avatar","Separator"],"code":"import * as React from \"react\";\nimport { Avatar, ScrollArea, Separator } from \"@twentyfirstmedia/ui-core\";\n\nconst PEOPLE = [\n \"Mara Ellis\",\n \"Jonah Petit\",\n \"Sun Lee\",\n \"Rui Costa\",\n \"Ava Brandt\",\n \"Ida Nolan\",\n \"Theo Marsh\",\n \"Clara Haddad\",\n \"Ben Okafor\",\n \"Lena Vogt\",\n \"Omar Said\",\n \"Pia Lund\",\n];\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={{ \"--cols\": \"320px 1fr\", gap: 24, alignItems: \"start\" } as React.CSSProperties}\n >\n <div className=\"tf-panel\" style={{ gap: 12, padding: 16 }}>\n <b>Team · {PEOPLE.length}</b>\n <ScrollArea label=\"Team members\" height={240}>\n <div className=\"tf-col\" style={{ gap: 4 }}>\n {PEOPLE.map((n) => (\n <div\n key={n}\n className=\"tf-row\"\n style={{ gap: 12, padding: \"6px 4px\", flexWrap: \"nowrap\" }}\n >\n <Avatar name={n} size={28} />\n <span>{n}</span>\n </div>\n ))}\n </div>\n </ScrollArea>\n </div>\n <div className=\"tf-panel\" style={{ gap: 12, padding: 16 }}>\n <b>Releases</b>\n <ScrollArea label=\"Releases\" orientation=\"horizontal\">\n <div className=\"tf-row\" style={{ gap: 12, flexWrap: \"nowrap\", paddingBottom: 8 }}>\n {[\"v2.4\", \"v2.3\", \"v2.2\", \"v2.1\", \"v2.0\", \"v1.9\", \"v1.8\"].map((v, i) => (\n <div key={v} className=\"tf-card\" style={{ minWidth: 160, minHeight: 0, gap: 4 }}>\n <b>{v}</b>\n <span className=\"tf-card-desc\">\n {[\"Apr\", \"Mar\", \"Feb\", \"Jan\", \"Dec\", \"Nov\", \"Oct\"][i]} release\n </span>\n </div>\n ))}\n </div>\n </ScrollArea>\n <Separator />\n <span className=\"tf-card-desc\">\n Scroll sideways, or focus the area and use the arrow keys.\n </span>\n </div>\n </div>\n );\n}\n","path":"components/ScrollArea"},{"name":"ToggleGroup","title":"Toggle Group","group":"Forms","summary":"On/off buttons.","description":"On/off buttons. A **Toggle** is a single pressed state (star, pin, follow). A **ToggleGroup** joins\nseveral: `type=\"single\"` allows at most one, `type=\"multiple\"` any number (text styles, filters).\n\n- Pressed state is `aria-pressed`; icon-only toggles take their name from `label` (or the option's `hint`).\n- ←/→, Home and End move between the toggles of a group.\n- For choosing exactly one of a few views, `Segmented` is the closer fit.\n","components":["ToggleGroup","Toggle"],"code":"import * as React from \"react\";\nimport { Toggle, ToggleGroup } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n const [marks, setMarks] = React.useState([\"bold\"]);\n return (\n <div className=\"tf-panel\" style={{ gap: 20 }}>\n <div className=\"tf-row\" style={{ gap: 12 }}>\n <ToggleGroup\n label=\"Text style\"\n type=\"multiple\"\n value={marks}\n onChange={setMarks}\n items={[\n { value: \"bold\", label: \"\", icon: \"bold\", hint: \"Bold\" },\n { value: \"list\", label: \"\", icon: \"list\", hint: \"List\" },\n { value: \"quote\", label: \"\", icon: \"quote\", hint: \"Quote\" },\n ]}\n />\n <ToggleGroup\n label=\"View\"\n defaultValue={[\"week\"]}\n items={[\"Day\", \"Week\", \"Month\"].map((x) => ({ value: x.toLowerCase(), label: x }))}\n />\n </div>\n <div className=\"tf-row\" style={{ gap: 8 }}>\n <Toggle icon=\"star\" label=\"Starred\" defaultPressed />\n <Toggle icon=\"pin\" label=\"Pinned\" />\n <Toggle icon=\"bell\">Follow thread</Toggle>\n <Toggle size=\"sm\" icon=\"eye\" disabled>\n Watching\n </Toggle>\n </div>\n </div>\n );\n}\n","path":"components/ToggleGroup"},{"name":"HoverCard","title":"Hover Card","group":"Overlays","summary":"A preview card that opens when the pointer rests on a trigger (a name, a link) or when it gets keyboard focus, and closes when the pointer or focus leaves or on Escape.","description":"A preview card that opens when the pointer rests on a trigger (a name, a link) or when it gets keyboard\nfocus, and closes when the pointer or focus leaves or on Escape.\n\n- Use it for supplementary details only; the trigger must still lead somewhere on its own.\n- `openDelay` and `closeDelay` tune the timing; the card stays inside the screen on narrow layouts.\n","components":["HoverCard","Avatar","Badge"],"code":"import { Avatar, Badge, HoverCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ gap: 16, paddingBottom: 180 }}>\n <p style={{ margin: 0 }}>\n Assigned to{\" \"}\n <HoverCard trigger={<a href=\"#\">@mara</a>} side=\"bottom\" align=\"start\">\n <div className=\"tf-row\" style={{ gap: 12, flexWrap: \"nowrap\", alignItems: \"flex-start\" }}>\n <Avatar name=\"Mara Ellis\" size={40} />\n <div className=\"tf-col\" style={{ gap: 4 }}>\n <b>Mara Ellis</b>\n <span className=\"tf-card-desc\">Owner · Leadership · joined Mar 2024</span>\n <div className=\"tf-row\" style={{ gap: 6 }}>\n <Badge tone=\"success\">Online</Badge>\n <Badge>Berlin</Badge>\n </div>\n </div>\n </div>\n </HoverCard>{\" \"}\n by the pricing workflow. Hover or focus the name to see the card.\n </p>\n </div>\n );\n}\n","path":"components/HoverCard"},{"name":"Carousel","title":"Carousel","group":"Media","summary":"A row of slides with previous/next buttons and dots.","description":"A row of slides with previous/next buttons and dots. Swipe or scroll on touch, ←/→ on the keyboard.\n\n- `perView` shows several slides on wide screens; phones always show one (with a peek of the next).\n- `loop` wraps around at the ends.\n- The carousel is a labelled region; each slide is announced as \"n of total\".\n","components":["Carousel","ProductArt"],"code":"import { Carousel, ProductArt } from \"@twentyfirstmedia/ui-core\";\n\nconst ITEMS = [\n [\"tee\", \"sky\", \"Halo Cotton Tee\", \"€32\"],\n [\"mug\", \"butter\", \"Studio Mug\", \"€18\"],\n [\"bottle\", \"mint\", \"Field Bottle\", \"€28\"],\n [\"tote\", \"lilac\", \"Canvas Tote\", \"€24\"],\n [\"cap\", \"blush\", \"Trail Cap\", \"€22\"],\n] as const;\n\nexport default function Preview() {\n return (\n <div className=\"tf-col\" style={{ gap: 32 }}>\n <Carousel\n label=\"Featured products\"\n perView={3}\n slides={ITEMS.map(([k, t, name, price]) => (\n <div key={k} className=\"tf-card\" style={{ minHeight: 0, gap: 8 }}>\n <ProductArt kind={k} tint={t} />\n <b>{name}</b>\n <span className=\"tf-card-desc\">{price}</span>\n </div>\n ))}\n />\n <Carousel\n label=\"Onboarding\"\n loop\n slides={[\"Invite your team\", \"Connect your mailbox\", \"Approve your first draft\"].map(\n (t, i) => (\n <div\n key={t}\n className=\"tf-panel\"\n style={{\n minHeight: 160,\n justifyContent: \"center\",\n alignItems: \"center\",\n textAlign: \"center\",\n }}\n >\n <span className=\"tf-card-desc\">Step {i + 1} of 3</span>\n <b style={{ fontSize: 20 }}>{t}</b>\n </div>\n ),\n )}\n />\n </div>\n );\n}\n","path":"components/Carousel"},{"name":"Ambient","title":"Ambient","group":"Navigation","summary":"A calm, living background for glass UI: soft colour fields from the kit (pastels in Halo, a faint aurora in Pro) that drift slowly, a photo or video under a scrim, or your own layer.","description":"A calm, living background for glass UI: soft colour fields from the kit (pastels in Halo, a faint aurora in Pro) that drift slowly, a photo or video under a scrim, or your own layer.\n\n- Put it first inside a container: it fills the container (or the window with `fixed`) and sits behind the content.\n- With `<UIProvider surface=\"glass\">` around it, panels, cards, tiles, the app shell and floating layers turn into glass over it; text keeps 4.5:1 over any backdrop.\n- The fields drift only with `transform`, stop with motion off or reduced motion and pause off screen. `animate={false}` keeps them still.\n- `colors` takes any CSS colours (brand colours, `var(--chart-1)`); by default the kit's `--ambient-1…4`, themable like every token.\n- `src` shows a photo, `src` with `video` a muted looping video; children replace both with your own layer (canvas, WebGL, a map).\n","components":["Ambient","Badge","Button","Panel","StatTile","UIProvider"],"code":"import * as React from \"react\";\nimport { Ambient, Badge, Button, Panel, StatTile, UIProvider } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <UIProvider scope=\"subtree\" surface=\"glass\">\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(auto-fit, minmax(min(100%, 240px), 1fr))\",\n gap: 16,\n padding: 24,\n borderRadius: \"var(--radius-xl)\",\n overflow: \"hidden\",\n }}\n >\n <Ambient />\n <StatTile value=\"7h 42m\" label=\"Sleep\" tint=\"sky\" delta=\"+18 min\" />\n <StatTile value=\"12 days\" label=\"Streak\" tint=\"butter\" />\n <Panel\n title=\"Breathe\"\n subtitle=\"Four minutes, box breathing\"\n action={<Badge tone=\"success\">New</Badge>}\n >\n <Button variant=\"primary\" icon=\"play\">\n Start\n </Button>\n </Panel>\n </div>\n </UIProvider>\n );\n}\n","path":"components/Ambient"},{"name":"Menubar","title":"Menubar","group":"Navigation","summary":"A desktop-style row of menus (File, Edit, View).","description":"A desktop-style row of menus (File, Edit, View). Each menu is a regular `Menu`, so items can have\nicons, shortcuts, checkbox and radio states and submenus.\n\n- Keyboard: ←/→ move between menus (and switch the open one), ↓/Enter/Space open, ↑/↓ move inside,\n → and ← open and close submenus, Escape closes and returns to the bar.\n- Moving the pointer across the bar switches menus once one is open.\n","components":["Menubar","Menu"],"code":"import { Menubar } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div style={{ minHeight: 260 }}>\n <Menubar\n label=\"Editor\"\n menus={[\n {\n label: \"File\",\n items: [\n { label: \"New page\", icon: \"file\", shortcut: \"⌘N\" },\n { label: \"Open…\", icon: \"folder\", shortcut: \"⌘O\" },\n {\n label: \"Export\",\n icon: \"download\",\n items: [{ label: \"PDF\" }, { label: \"Markdown\" }, { label: \"HTML\" }],\n },\n \"-\",\n { label: \"Close\", shortcut: \"⌘W\" },\n ],\n },\n {\n label: \"Edit\",\n items: [\n { label: \"Undo\", icon: \"undo\", shortcut: \"⌘Z\" },\n { label: \"Redo\", icon: \"redo\", shortcut: \"⇧⌘Z\" },\n \"-\",\n { label: \"Find…\", icon: \"search\", shortcut: \"⌘F\" },\n ],\n },\n {\n label: \"View\",\n items: [\n { label: \"Show sidebar\", type: \"checkbox\", checked: true },\n { label: \"Show comments\", type: \"checkbox\" },\n \"-\",\n { label: \"Zoom in\", shortcut: \"⌘+\" },\n { label: \"Zoom out\", shortcut: \"⌘−\" },\n ],\n },\n {\n label: \"Help\",\n items: [{ label: \"Keyboard shortcuts\", shortcut: \"?\" }, { label: \"What's new\" }],\n },\n ]}\n />\n </div>\n );\n}\n","path":"components/Menubar"},{"name":"NavigationMenu","title":"Navigation Menu","group":"Navigation","summary":"Site navigation for marketing pages: plain links and items that open a panel of links with icons and descriptions, optionally with a highlighted card.","description":"Site navigation for marketing pages: plain links and items that open a panel of links with icons\nand descriptions, optionally with a highlighted card.\n\n- Panels open on hover and on click; Escape closes the panel and returns focus to its trigger.\n- Use `TopBar` for app navigation inside the product and this for public pages.\n","components":["NavigationMenu","Button"],"code":"import { Button, NavigationMenu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ gap: 16, minHeight: 320 }}>\n <div className=\"tf-row\" style={{ justifyContent: \"space-between\", gap: 16 }}>\n <NavigationMenu\n label=\"Site\"\n items={[\n {\n label: \"Product\",\n feature: (\n <>\n <b>Halo Workspace</b>\n <span className=\"tf-card-desc\">\n One place for approvals, billing and the team.\n </span>\n </>\n ),\n links: [\n { label: \"Approvals\", icon: \"check\", description: \"Agents ask, people decide.\" },\n { label: \"Billing\", icon: \"card\", description: \"Invoices, plans and payouts.\" },\n { label: \"Analytics\", icon: \"chart\", description: \"Growth week over week.\" },\n ],\n },\n {\n label: \"Resources\",\n links: [\n { label: \"Docs\", icon: \"book\", description: \"Guides and API reference.\" },\n { label: \"Changelog\", icon: \"file\", description: \"What shipped this month.\" },\n ],\n },\n { label: \"Pricing\", href: \"#\" },\n ]}\n />\n <Button asChild size=\"sm\">\n <a href=\"#\">Get started</a>\n </Button>\n </div>\n </div>\n );\n}\n","path":"components/NavigationMenu"},{"name":"Tabs","title":"Tabs","group":"Navigation","summary":"A tab bar that switches views.","description":"A tab bar that switches views. With `panels` it renders the panels too, wired with the tab,\ntablist and tabpanel roles; without, it is a bar you connect to your own content.\n\n- Keyboard: ←/→ move between tabs and select them, Home/End jump; only the selected tab is in the\n tab order.\n- `block` stretches the tabs to the full width.\n","components":["Tabs","Panel","KeyValue"],"code":"import { KeyValue, Panel, Tabs } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <Panel title=\"Northwind\">\n <Tabs\n label=\"Customer\"\n items={[\"Overview\", \"Invoices\", \"Notes\"]}\n panels={{\n Overview: (\n <KeyValue\n items={[\n { k: \"Plan\", v: \"Team · 24 seats\" },\n { k: \"MRR\", v: \"€1,240\" },\n { k: \"Renews\", v: \"May 1, 2026\" },\n ]}\n />\n ),\n Invoices: <p className=\"tf-card-desc\">Three invoices this year, all paid.</p>,\n Notes: <p className=\"tf-card-desc\">Wants 40 more seats before the renewal.</p>,\n }}\n />\n </Panel>\n );\n}\n","path":"components/Tabs"},{"name":"Sidebar","title":"Sidebar","group":"Navigation","summary":"App navigation down the side: groups with headings, counts, one level of nested pages and a collapsed mode with icons only.","description":"App navigation down the side: groups with headings, counts, one level of nested pages and a\ncollapsed mode with icons only.\n\n- The collapse button sits in the footer next to anything you pass (an account, settings).\n- Collapsed, labels stay available as tooltips and to assistive tech.\n- On phones the sidebar takes the full width; place it in a `Drawer` (`side=\"left\"`) for an\n off-canvas menu.\n","components":["Sidebar","Avatar"],"code":"import * as React from \"react\";\nimport { Avatar, Sidebar } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n const [page, setPage] = React.useState(\"invoices\");\n const groups = [\n {\n items: [\n { id: \"home\", label: \"Home\", icon: \"home\" as const },\n { id: \"inbox\", label: \"Inbox\", icon: \"mail\" as const, count: 4 },\n ],\n },\n {\n label: \"Finance\",\n items: [\n {\n id: \"billing\",\n label: \"Billing\",\n icon: \"card\" as const,\n items: [\n { id: \"invoices\", label: \"Invoices\", icon: \"file\" as const },\n { id: \"payouts\", label: \"Payouts\", icon: \"download\" as const },\n ],\n },\n { id: \"reports\", label: \"Reports\", icon: \"chart\" as const },\n ],\n },\n ];\n const head = (\n <div className=\"tf-row\" style={{ gap: 8, padding: \"4px 4px 0\", flexWrap: \"nowrap\" }}>\n <span className=\"tf-brand\" style={{ color: \"var(--ink)\" }}>\n Halo\n <span className=\"tf-brand-dot\" />\n </span>\n </div>\n );\n return (\n <div className=\"tf-row\" style={{ gap: 24, alignItems: \"stretch\", flexWrap: \"wrap\" }}>\n <div style={{ height: 420, display: \"flex\" }}>\n <Sidebar\n label=\"Workspace\"\n groups={groups}\n current={page}\n onPick={setPage}\n header={head}\n footer={<Avatar name=\"Mara Ellis\" size={28} />}\n />\n </div>\n <div style={{ height: 420, display: \"flex\" }}>\n <Sidebar\n label=\"Workspace (collapsed)\"\n groups={groups}\n current={page}\n onPick={setPage}\n defaultCollapsed\n />\n </div>\n </div>\n );\n}\n","path":"components/Sidebar"},{"name":"FloatingLayout","title":"Floating Layout","group":"Navigation","summary":"UI floating over a full-bleed background: a map, a 3D scene, a video or a photo.","description":"UI floating over a full-bleed background: a map, a 3D scene, a video or a photo. Four slots sit along the edges (`top`, `start`, `end`, `bottom`) and everything in them is glass by default, translucent and blurred over what is behind.\n\n- The background stays interactive wherever no slot sits on it: drag the map, click a pin.\n- Below 720px of width the slots stack (top, start, the background, end, bottom) and the layout grows with them instead of scrolling inside.\n- `surface=\"solid\"` turns the glass off; `surface=\"solid\"` on a single Panel or Card opts just that one out.\n- Panels, cards, the dock, map legends and controls, and loose controls (search field, chips, icon buttons, segmented controls) turn into glass inside it; menus, popovers and dialogs take the stronger glass fill.\n- Text on glass keeps 4.5:1 over any backdrop. Reduced transparency, more contrast and forced colours fall back to solid surfaces.\n","components":["Badge","FloatingLayout","IconButton","KeyValue","MapView","Panel","SearchField","Segmented"],"code":"import * as React from \"react\";\nimport {\n Badge,\n FloatingLayout,\n IconButton,\n KeyValue,\n MapView,\n Panel,\n SearchField,\n Segmented,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\nconst hubs: MapPoint[] = [\n { id: \"ham\", label: \"Hamburg\", lat: 53.5, lng: 10, value: 840, sub: \"840 parcels today\" },\n { id: \"rot\", label: \"Rotterdam\", lat: 51.9, lng: 4.5, value: 610, tint: \"mint\" },\n { id: \"nyc\", label: \"New York\", lat: 40.7, lng: -74, value: 380, tint: \"butter\" },\n { id: \"dxb\", label: \"Dubai\", lat: 25.2, lng: 55.3, value: 290, tint: \"lilac\", pulse: true },\n { id: \"sgp\", label: \"Singapore\", lat: 1.3, lng: 103.8, value: 450, tint: \"blush\" },\n];\n\nexport default function Preview() {\n const [view, setView] = React.useState(\"Today\");\n return (\n <FloatingLayout\n label=\"Shipments\"\n height={520}\n background={<MapView label=\"Shipments by hub\" bubbles points={hubs} height=\"100%\" />}\n top={\n <>\n <SearchField placeholder=\"Search hubs\" style={{ flex: \"1 1 220px\", maxWidth: 360 }} />\n <IconButton icon=\"filter\" label=\"Filters\" variant=\"outline\" />\n </>\n }\n end={\n <Panel\n title=\"Hamburg\"\n subtitle=\"Hub\"\n action={\n <Badge tone=\"success\" dot>\n On time\n </Badge>\n }\n >\n <KeyValue\n items={[\n [\"Parcels\", \"840\"],\n [\"Delayed\", \"12\"],\n [\"Next truck\", \"14:20\"],\n ]}\n />\n </Panel>\n }\n bottom={\n <Segmented\n label=\"Period\"\n options={[\"Today\", \"Week\", \"Month\"]}\n value={view}\n onChange={setView}\n />\n }\n />\n );\n}\n","path":"components/FloatingLayout"},{"name":"SelectMenu","title":"Select Menu","group":"Forms","summary":"A styled select: the button shows the choice, the list opens below it with groups, icons, hints and disabled options.","description":"A styled select: the button shows the choice, the list opens below it with groups, icons, hints\nand disabled options. Use the native `Select` when the system picker is preferred (long lists on\nphones).\n\n- Keyboard: ↑/↓/Enter/Space open, arrows move, Home/End jump, typing jumps to a match, Enter picks,\n Escape closes.\n- Inside a `Form` it submits its value under `name` and shows its validation message.\n","components":["SelectMenu"],"code":"import * as React from \"react\";\nimport { SelectMenu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-panel tf-grid\"\n style={{ \"--cols\": \"1fr 1fr\", gap: 20, paddingBottom: 260 } as React.CSSProperties}\n >\n <SelectMenu\n label=\"Assignee\"\n placeholder=\"Choose a person\"\n options={[\n { value: \"mara\", label: \"Mara Ellis\", hint: \"Owner\", group: \"Leadership\" },\n { value: \"jonah\", label: \"Jonah Petit\", hint: \"Design\", group: \"Team\" },\n { value: \"sun\", label: \"Sun Lee\", hint: \"Growth\", group: \"Team\" },\n { value: \"rui\", label: \"Rui Costa\", hint: \"Finance\", group: \"Team\" },\n { value: \"ava\", label: \"Ava Brandt\", hint: \"Invited\", group: \"Team\", disabled: true },\n ]}\n />\n <SelectMenu\n label=\"Region\"\n defaultValue=\"eu\"\n hint=\"Where workspace data is stored\"\n options={[\n { value: \"eu\", label: \"Europe (Frankfurt)\", icon: \"globe\" },\n { value: \"us\", label: \"United States (Ohio)\", icon: \"globe\" },\n { value: \"ap\", label: \"Asia Pacific (Sydney)\", icon: \"globe\" },\n ]}\n />\n </div>\n );\n}\n","path":"components/SelectMenu"},{"name":"Form","title":"Form","group":"Forms","summary":"Validation for native forms.","description":"Validation for native forms. Give `rules` by field `name`; on submit the form checks them, shows\neach message under its field, marks the field `aria-invalid` and focuses the first one. After the\nfirst submit, fields re-check as they change.\n\n- Rules: `required`, `email`, `minLength`/`maxLength`, `min`/`max`, `pattern`, and `validate` for\n anything else. Messages can be set per rule.\n- `onSubmit` receives the values by name; `errors` shows messages from the server.\n- Works with `TextField`, `Input`, `Select`, `Textarea`, `SelectMenu`, `Checkbox` and `Switch`.\n","components":["Form","TextField","SelectMenu","Textarea","Checkbox","Button"],"code":"import * as React from \"react\";\nimport {\n Button,\n Checkbox,\n Form,\n SelectMenu,\n TextField,\n Textarea,\n toast,\n} from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ maxWidth: 520 }}>\n <Form\n rules={{\n name: { required: \"Tell us your name\" },\n email: { required: true, email: true },\n team: { required: \"Pick a team\" },\n message: { minLength: 20, message: \"Write at least 20 characters\" },\n terms: { required: \"Accept the terms to continue\" },\n }}\n onSubmit={(v) => toast({ text: `Thanks, ${v.name}` })}\n >\n <TextField name=\"name\" label=\"Name\" placeholder=\"Mara Ellis\" required />\n <TextField\n name=\"email\"\n label=\"Work email\"\n type=\"email\"\n placeholder=\"you@company.com\"\n required\n />\n <SelectMenu\n name=\"team\"\n label=\"Team\"\n placeholder=\"Choose a team\"\n required\n options={[\"Design\", \"Engineering\", \"Growth\", \"Finance\"]}\n />\n <Textarea name=\"message\" label=\"Message\" placeholder=\"What do you need?\" rows={3} />\n <Checkbox name=\"terms\" value=\"yes\">\n I accept the terms\n </Checkbox>\n <div className=\"tf-row\" style={{ justifyContent: \"flex-end\" }}>\n <Button type=\"submit\">Send</Button>\n </div>\n </Form>\n </div>\n );\n}\n","path":"components/Form"},{"name":"Sheet","title":"Sheet","group":"Overlays","summary":"A Drawer that slides in from any edge: side=\"right\" (default), \"left\", \"top\" or \"bottom\".","description":"A `Drawer` that slides in from any edge: `side=\"right\"` (default), `\"left\"`, `\"top\"` or\n`\"bottom\"`. Bottom sheets suit filters and quick actions on phones; left sheets hold navigation.\n\n- Focus moves into the sheet and stays there until it closes; Escape and a click outside close it.\n","components":["Drawer","SelectMenu","Button"],"code":"import * as React from \"react\";\nimport { Button, Drawer, SelectMenu } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n style={{\n position: \"relative\",\n height: 420,\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n background: \"var(--canvas)\",\n }}\n >\n <Drawer\n inline\n side=\"bottom\"\n title=\"Filters\"\n subtitle=\"12 of 48 listings match\"\n onClose={() => {}}\n >\n <div className=\"tf-grid\" style={{ \"--cols\": \"1fr 1fr\", gap: 16 } as React.CSSProperties}>\n <SelectMenu label=\"Rooms\" defaultValue=\"3\" options={[\"1\", \"2\", \"3\", \"4+\"]} />\n <SelectMenu label=\"Sort by\" defaultValue=\"Newest\" options={[\"Newest\", \"Price\", \"Size\"]} />\n </div>\n <div className=\"tf-row\" style={{ justifyContent: \"flex-end\" }}>\n <Button variant=\"ghost\">Reset</Button>\n <Button>Show 12 listings</Button>\n </div>\n </Drawer>\n </div>\n );\n}\n","path":"components/Sheet"},{"name":"ResizablePanels","title":"Resizable Panels","group":"Foundations","summary":"Two or more panes with drag handles between them, side by side or stacked; panels can nest.","description":"Two or more panes with drag handles between them, side by side or stacked; panels can nest.\n\n- Handles are separators with a value: ←/→ (↑/↓ when stacked) resize by 2%, Home/End jump to the\n limits, double-click resets to equal sizes.\n- `min` keeps every pane above a share of the whole; `storageKey` remembers the sizes.\n","components":["ResizablePanels"],"code":"import { ResizablePanels } from \"@twentyfirstmedia/ui-core\";\n\nconst pane = (title: string, text: string) => (\n <div className=\"tf-col\" style={{ gap: 6, padding: 16 }}>\n <b>{title}</b>\n <span className=\"tf-card-desc\">{text}</span>\n </div>\n);\n\nexport default function Preview() {\n return (\n <div className=\"tf-panel\" style={{ padding: 0, height: 320, overflow: \"hidden\" }}>\n <ResizablePanels\n labels={[\"Folders\", \"Messages\"]}\n defaultSizes={[22, 38, 40]}\n panels={[\n pane(\"Folders\", \"Inbox, Sent, Archive\"),\n pane(\"Messages\", \"Drag a handle, or focus it and use the arrow keys.\"),\n <ResizablePanels\n key=\"r\"\n orientation=\"vertical\"\n labels={[\"Thread\"]}\n defaultSizes={[60, 40]}\n panels={[pane(\"Thread\", \"Northwind · Seat upgrade\"), pane(\"Reply\", \"Write a reply…\")]}\n />,\n ]}\n />\n </div>\n );\n}\n","path":"components/ResizablePanels"}],"patterns":[{"name":"AIChatScreen","title":"AI Chat","group":"Pages","summary":"A complete screen built from AgentMark, AgentStatus, ApprovalCard, ChatMessage and 6 more components.","description":"","components":["AgentMark","AgentStatus","ApprovalCard","ChatMessage","Composer","Icon","Reasoning","SubNav","ToolCall","TopBar","UsageMeter"],"code":"import {\n AgentMark,\n AgentStatus,\n ApprovalCard,\n ChatMessage,\n Composer,\n Reasoning,\n ToolCall,\n UsageMeter,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nconst md = \"I drafted a reply to **Ida Nolan**. It quotes **40 seats at €24** prorated to May 1.\";\nfunction App() {\n return (\n <Shell\n area=\"Agents\"\n current=\"Chat\"\n action={{\n label: \"New agent\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"Fleet\",\n label: \"Fleet\",\n icon: \"bot\",\n },\n {\n id: \"Chat\",\n label: \"Chat\",\n icon: \"mail\",\n },\n {\n id: \"Builder\",\n label: \"Builder\",\n icon: \"settings\",\n },\n {\n id: \"Runs\",\n label: \"Runs\",\n icon: \"refresh\",\n count: 2,\n },\n {\n id: \"Knowledge\",\n label: \"Knowledge\",\n icon: \"book\",\n },\n ]}\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n paddingBottom: 16,\n borderBottom: \"1px solid var(--line)\",\n }}\n >\n <AgentMark tint=\"sky\" live size={40} />\n <div\n style={{\n flex: 1,\n }}\n >\n <div\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Atlas\n </div>\n <AgentStatus status=\"Waiting for approval\" step=\"Drafts only\" />\n </div>\n <div\n style={{\n width: 160,\n }}\n >\n <UsageMeter label=\"Context\" used={19} limit={200} format={(v) => v + \"k\"} />\n </div>\n </div>\n <ChatMessage role=\"user\" text=\"Draft a reply to Northwind about the seat upgrade\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Atlas\"\n tint=\"sky\"\n time=\"09:41\"\n model=\"Claude Sonnet 5.5\"\n text={md}\n sources={[\n {\n title: \"Seat upgrades · pricing v4\",\n },\n {\n title: \"Thread: Seat upgrade\",\n icon: \"mail\",\n },\n ]}\n usage=\"1,527 tokens · €0.06\"\n before={\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <Reasoning\n duration=\"1.4 s\"\n steps={[\"Find the thread\", \"Check pricing\", \"Draft, then ask\"]}\n />\n <ToolCall name=\"Mailbox\" icon=\"mail\" summary=\"Read 3 messages\" duration=\"0.8 s\" />\n <ToolCall\n name=\"Knowledge base\"\n icon=\"book\"\n summary=\"Found “Seat upgrades”\"\n duration=\"0.6 s\"\n />\n </div>\n }\n after={\n <ApprovalCard\n title=\"Send this email to ida@northwind.io?\"\n text=\"Atlas is set to “Drafts only”.\"\n approveLabel=\"Send email\"\n onEdit={() => {}}\n >\n {\n \"Hi Ida,\\n\\nhappy to add 40 seats before your renewal on May 1. The quote is attached.\\n\\nBest, Mara\"\n }\n </ApprovalCard>\n }\n />\n <Composer\n placeholder=\"Ask Atlas something, or type / for commands\"\n models={[\n {\n id: \"s\",\n label: \"Claude Sonnet 5.5\",\n },\n ]}\n model=\"s\"\n onAttach={() => {}}\n commands={[\n {\n name: \"clear\",\n },\n ]}\n />\n </div>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/AIChatScreen"},{"name":"AgentBuilderScreen","title":"Agent Builder","group":"Pages","summary":"A complete screen built from Button, ChatMessage, Combobox, Composer and 7 more components.","description":"","components":["Button","ChatMessage","Combobox","Composer","Icon","PageHeader","Panel","RadioGroup","Segmented","Slider","SubNav","TopBar"],"code":"import * as React from \"react\";\nimport {\n Button,\n ChatMessage,\n Combobox,\n Composer,\n PageHeader,\n Panel,\n RadioGroup,\n Segmented,\n Slider,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n return (\n <Shell\n area=\"Agents\"\n current=\"Builder\"\n action={{\n label: \"New agent\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"Fleet\",\n label: \"Fleet\",\n icon: \"bot\",\n },\n {\n id: \"Chat\",\n label: \"Chat\",\n icon: \"mail\",\n },\n {\n id: \"Builder\",\n label: \"Builder\",\n icon: \"settings\",\n },\n {\n id: \"Runs\",\n label: \"Runs\",\n icon: \"refresh\",\n },\n ]}\n >\n <PageHeader\n crumbs={[\"Agents\", \"Atlas\"]}\n title=\"Configure Atlas\"\n subtitle=\"Support triage · changes apply to new runs\"\n actions={[\n <Button key=\"s\" icon=\"check\">\n Save changes\n </Button>,\n ]}\n />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1fr) 340px\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <Panel title=\"Instructions\" subtitle=\"What it does, how it talks, what it must never do\">\n <textarea\n className=\"tf-input\"\n aria-label=\"Instructions\"\n rows={5}\n style={{\n fontFamily: \"var(--font-mono)\",\n fontSize: 13,\n }}\n defaultValue={\n \"You are Atlas, the support triage agent for Halo.\\n- Tag new mail and draft replies.\\n- Never promise refunds; hand those to a person.\"\n }\n />\n </Panel>\n <Panel title=\"Model\">\n <RadioGroup\n cards\n defaultValue=\"Sonnet\"\n options={[\n {\n value: \"Opus\",\n label: \"Opus 5.5\",\n hint: \"Deepest · €15 / M\",\n },\n {\n value: \"Sonnet\",\n label: \"Sonnet 5.5\",\n hint: \"Balanced · €3 / M\",\n },\n {\n value: \"Haiku\",\n label: \"Haiku 5.5\",\n hint: \"Fast · €1 / M\",\n },\n ]}\n />\n </Panel>\n <Panel title=\"Guardrails\">\n <Segmented\n defaultValue=\"Drafts only\"\n options={[\"Drafts only\", \"Acts, then reports\", \"Fully autonomous\"]}\n />\n <Combobox\n label=\"Always ask before\"\n multiple\n defaultValue={[\"Sending email\", \"Spending money\"]}\n options={[\"Sending email\", \"Spending money\", \"Deleting anything\"]}\n />\n <Slider\n label=\"Monthly budget\"\n min={0}\n max={500}\n defaultValue={120}\n format={(v) => \"€\" + v}\n />\n </Panel>\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 16,\n }}\n >\n <div>\n <h3 className=\"tf-panel-title\">Test</h3>\n <p className=\"tf-panel-sub\">Uses your unsaved setup</p>\n </div>\n <ChatMessage role=\"user\" text=\"Can I get a refund?\" />\n <ChatMessage\n role=\"assistant\"\n name=\"Atlas\"\n tint=\"sky\"\n actions={false}\n text=\"I’ll pass this to a person, since refunds need a human. You’ll hear back today.\"\n />\n <Composer placeholder=\"Test a message…\" hint=\"\" />\n </div>\n </div>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/AgentBuilderScreen"},{"name":"AnalyticsScreen","title":"Analytics","group":"Pages","summary":"A complete screen built from AppShell, BarChart, Button, CountUp and 10 more components.","description":"","components":["AppShell","BarChart","Button","CountUp","Donut","IconButton","LineChart","PageHeader","Panel","Sparkline","Stagger","Table","Tabs","TintIcon"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n BarChart,\n Button,\n CountUp,\n Donut,\n LineChart,\n PageHeader,\n Panel,\n Sparkline,\n Stagger,\n Table,\n Tabs,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { IconName, Tint } from \"@twentyfirstmedia/ui-core\";\n\ninterface KpiProps {\n label: string;\n value: string;\n delta: string;\n up?: boolean;\n icon: IconName;\n tint: Tint;\n spark: number[];\n}\n\nfunction Kpi(p: KpiProps) {\n return (\n <div\n className=\"tf-card\"\n style={{\n minHeight: 0,\n gap: 8,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span className=\"tf-card-desc\">{p.label}</span>\n <TintIcon icon={p.icon} tint={p.tint} />\n </div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n alignItems: \"flex-end\",\n }}\n >\n <div>\n <CountUp className=\"tf-stat-value\" value={p.value} />\n <div\n style={{\n font: \"var(--type-label-weight) 12px/16px var(--font-sans)\",\n color: \"var(--\" + (p.up ? \"mint\" : \"blush\") + \"-ink)\",\n marginTop: 2,\n }}\n >\n {p.delta}\n </div>\n </div>\n <Sparkline values={p.spark} tint={p.up ? \"mint\" : \"blush\"} width={96} height={34} />\n </div>\n </div>\n );\n}\nfunction App() {\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Reports\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Analytics\"\n subtitle=\"How the workspace is growing, week over week\"\n actions={[\n <Tabs\n key=\"t\"\n items={[\"7 days\", \"30 days\", \"Quarter\", \"Year\"]}\n defaultValue=\"30 days\"\n />,\n <Button key=\"e\" variant=\"secondary\" icon=\"download\">\n Export\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <Kpi\n label=\"Revenue\"\n value=\"€41,280\"\n delta=\"+18.2% vs last month\"\n up\n icon=\"card\"\n tint=\"butter\"\n spark={[18, 22, 21, 29, 34, 38, 41]}\n />\n <Kpi\n label=\"Active members\"\n value=\"1,284\"\n delta=\"+6.4%\"\n up\n icon=\"users\"\n tint=\"sky\"\n spark={[9, 10, 10, 11, 12, 12, 13]}\n />\n <Kpi\n label=\"Conversion\"\n value=\"4.8%\"\n delta=\"+0.6 pts\"\n up\n icon=\"trend\"\n tint=\"mint\"\n spark={[3.9, 4.1, 4.0, 4.4, 4.5, 4.6, 4.8]}\n />\n <Kpi\n label=\"Churn\"\n value=\"1.9%\"\n delta=\"+0.3 pts\"\n up={false}\n icon=\"flag\"\n tint=\"blush\"\n spark={[1.4, 1.5, 1.5, 1.6, 1.8, 1.7, 1.9]}\n />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.6fr 1fr\",\n } as React.CSSProperties\n }\n delay={300}\n >\n <Panel\n title=\"Revenue\"\n subtitle=\"Subscriptions vs. services\"\n action={\n <div className=\"tf-legend\">\n <span>\n <i\n style={{\n background: \"var(--mint-ink)\",\n }}\n />\n Subscriptions\n </span>\n <span>\n <i\n style={{\n background: \"var(--sky-ink)\",\n }}\n />\n Services\n </span>\n </div>\n }\n >\n <LineChart\n height={190}\n labels={[\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\"]}\n format={(v) => \"€\" + Math.round(v / 1000) + \"k\"}\n series={[\n {\n values: [18000, 22000, 21000, 29000, 34000, 38000, 41000],\n tint: \"mint\",\n area: true,\n },\n {\n values: [9000, 11000, 14000, 13000, 17000, 19000, 23000],\n tint: \"sky\",\n },\n ]}\n />\n </Panel>\n <Panel title=\"Plan mix\" subtitle=\"1,284 paying members\">\n <Donut\n size={150}\n stroke={20}\n center=\"1,284\"\n caption=\"members\"\n data={[\n {\n label: \"Team\",\n value: 54,\n tint: \"sky\",\n },\n {\n label: \"Starter\",\n value: 26,\n tint: \"mint\",\n },\n {\n label: \"Business\",\n value: 14,\n tint: \"lilac\",\n },\n {\n label: \"Trial\",\n value: 6,\n tint: \"butter\",\n },\n ]}\n />\n </Panel>\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1.2fr\",\n } as React.CSSProperties\n }\n delay={480}\n >\n <Panel title=\"Sign-ups this week\" subtitle=\"Thursday was the launch email\">\n <BarChart\n height={150}\n data={[\n {\n label: \"Mon\",\n value: 32,\n },\n {\n label: \"Tue\",\n value: 48,\n },\n {\n label: \"Wed\",\n value: 41,\n },\n {\n label: \"Thu\",\n value: 86,\n tint: \"lilac\",\n },\n {\n label: \"Fri\",\n value: 58,\n },\n {\n label: \"Sat\",\n value: 22,\n },\n {\n label: \"Sun\",\n value: 18,\n },\n ]}\n />\n </Panel>\n <Panel title=\"Top channels\">\n <Table\n columns={[\n {\n key: \"ch\",\n label: \"Channel\",\n render: (r) => (\n <span className=\"tf-row\" style={{ flexWrap: \"nowrap\" }}>\n <TintIcon icon={r.icon} tint={r.tint} />\n {r.ch}\n </span>\n ),\n },\n {\n key: \"v\",\n label: \"Visits\",\n align: \"right\",\n },\n {\n key: \"c\",\n label: \"Conv.\",\n align: \"right\",\n },\n {\n key: \"t\",\n label: \"Trend\",\n align: \"right\",\n render: (r) => (\n <Sparkline values={r.s} tint={r.up ? \"mint\" : \"blush\"} width={70} height={22} />\n ),\n },\n ]}\n rows={[\n {\n ch: \"Organic search\",\n icon: \"search\",\n tint: \"sky\",\n v: \"24,180\",\n c: \"5.2%\",\n s: [3, 4, 4, 5, 6, 6, 7],\n up: true,\n },\n {\n ch: \"Newsletter\",\n icon: \"mail\",\n tint: \"butter\",\n v: \"9,402\",\n c: \"7.9%\",\n s: [2, 2, 3, 5, 4, 6, 7],\n up: true,\n },\n {\n ch: \"Partners\",\n icon: \"users\",\n tint: \"mint\",\n v: \"6,115\",\n c: \"6.1%\",\n s: [5, 5, 4, 5, 6, 6, 6],\n up: true,\n },\n {\n ch: \"Paid social\",\n icon: \"globe\",\n tint: \"blush\",\n v: \"5,870\",\n c: \"2.3%\",\n s: [6, 5, 5, 4, 4, 3, 3],\n up: false,\n },\n ]}\n />\n </Panel>\n </Stagger>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/AnalyticsScreen"},{"name":"AuthCard","title":"Auth Card","group":"Pages","summary":"Centered card for sign-in, sign-up, password reset, two-step codes and invites.","description":"Centered card for sign-in, sign-up, password reset, two-step codes and invites.\n\nProps: `title`, `text`, `icon` + `tint` (for non-sign-in steps), `brand`, `foot` (the one secondary link), children (fields and one primary Button).\n\n- One task per card, one primary button. Secondary paths go in the footer.\n- After \"Send reset link\", swap to a confirmation card that doesn't reveal whether the account exists.\n- Put it on the `canvas` color with a small \"Back\" link at top-left.\n","components":["AuthCard","Button","OtpInput","TextField"],"code":"import * as React from \"react\";\nimport { AuthCard, Button, OtpInput, TextField } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <AuthCard\n brand=\"Halo\"\n title=\"Welcome back\"\n text=\"Sign in to the Halo workspace.\"\n foot=\"New here? Create a workspace\"\n >\n <TextField label=\"Email\" icon=\"mail\" defaultValue=\"mara@halo.app\" />\n <TextField label=\"Password\" type=\"password\" icon=\"lock\" defaultValue=\"secret-pass\" />\n <Button>Sign in</Button>\n </AuthCard>\n <AuthCard\n brand=\"Halo\"\n icon=\"key\"\n tint=\"butter\"\n title=\"Reset your password\"\n text=\"We’ll email you a link to choose a new one.\"\n foot=\"Back to sign in\"\n >\n <TextField label=\"Email\" icon=\"mail\" defaultValue=\"mara@halo.app\" />\n <Button icon=\"send\">Send reset link</Button>\n </AuthCard>\n <AuthCard\n brand=\"Halo\"\n icon=\"shield\"\n tint=\"sky\"\n title=\"Enter your code\"\n text=\"Type the 6-digit code from your authenticator app.\"\n foot=\"Use a recovery code instead\"\n >\n <OtpInput />\n </AuthCard>\n </div>\n );\n}\n","path":"patterns/AuthCard"},{"name":"BillingScreen","title":"Billing","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, BarChart and 9 more components.","description":"","components":["AppShell","Avatar","Badge","BarChart","Button","Icon","IconButton","PageHeader","Panel","Progress","ProgressRing","Stagger","StatTile","Table"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n BarChart,\n Button,\n Icon,\n IconButton,\n PageHeader,\n Panel,\n Progress,\n ProgressRing,\n Stagger,\n StatTile,\n Table,\n} from \"@twentyfirstmedia/ui-core\";\n\nconst INV = [\n {\n id: \"#2044\",\n c: \"Northwind\",\n d: \"Apr 12, 2026\",\n a: \"€1,240.00\",\n s: \"Paid\",\n tone: \"success\",\n },\n {\n id: \"#2043\",\n c: \"Copperline\",\n d: \"Apr 10, 2026\",\n a: \"€3,900.00\",\n s: \"Due\",\n tone: \"info\",\n },\n {\n id: \"#2042\",\n c: \"Blue Fern\",\n d: \"Apr 8, 2026\",\n a: \"€420.00\",\n s: \"Paid\",\n tone: \"success\",\n },\n {\n id: \"#2041\",\n c: \"Harbor & Co\",\n d: \"Mar 2, 2026\",\n a: \"€860.00\",\n s: \"Overdue\",\n tone: \"danger\",\n },\n {\n id: \"#2040\",\n c: \"Lumen Labs\",\n d: \"Feb 28, 2026\",\n a: \"€2,150.00\",\n s: \"Paid\",\n tone: \"success\",\n },\n];\nfunction App() {\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Billing\"\n side={\n <Panel side title=\"Your plan\" subtitle=\"Renews on May 1, 2026\">\n <div\n className=\"tf-card tf-bg-lilac\"\n style={{\n minHeight: 0,\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Team\n </span>\n <Badge tone=\"neutral\" dot={false}>\n Annual\n </Badge>\n </div>\n <div>\n <span className=\"tf-stat-value\">€96</span>\n <span className=\"tf-card-desc\">{\" / seat / month\"}</span>\n </div>\n <Button variant=\"primary\" iconRight=\"arrowUpRight\">\n Upgrade to Business\n </Button>\n </div>\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n flexWrap: \"nowrap\",\n }}\n >\n <ProgressRing value={90} tint=\"butter\" size={76} stroke={8} />\n <div>\n <div\n style={{\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n 38 of 42 seats\n </div>\n <div className=\"tf-card-desc\">Add seats before inviting more people.</div>\n </div>\n </div>\n <h3\n className=\"tf-card-title\"\n style={{\n fontSize: 16,\n }}\n >\n Usage\n </h3>\n <div className=\"tf-col\">\n <div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n fontSize: 13,\n fontWeight: \"var(--type-label-weight)\",\n marginBottom: 8,\n }}\n >\n <span>Storage</span>\n <span>62 / 100 GB</span>\n </div>\n <Progress value={62} tint=\"sky\" />\n </div>\n <div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n fontSize: 13,\n fontWeight: \"var(--type-label-weight)\",\n marginBottom: 8,\n }}\n >\n <span>API calls</span>\n <span>41k / 50k</span>\n </div>\n <Progress value={82} tint=\"butter\" />\n </div>\n </div>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Billing\"\n subtitle=\"Invoices, payments and your subscription\"\n actions={[\n <Button key=\"e\" variant=\"secondary\" icon=\"download\">\n Export CSV\n </Button>,\n <Button key=\"n\" icon=\"plus\">\n New invoice\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Collected (MTD)\" />\n <StatTile tint=\"sky\" value=\"€12,480\" label=\"Outstanding\" />\n <StatTile tint=\"blush\" value=\"3\" label=\"Overdue invoices\" />\n <StatTile tint=\"mint\" value=\"98.2%\" label=\"On-time rate\" />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n delay={280}\n >\n <div\n className=\"tf-card\"\n style={{\n minHeight: 0,\n background: \"var(--frame)\",\n color: \"var(--on-frame)\",\n }}\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span\n style={{\n font: \"var(--type-label-weight) 12px/16px var(--font-sans)\",\n color: \"var(--on-frame-muted)\",\n }}\n >\n Default payment method\n </span>\n <Icon name=\"card\" size={20} />\n </div>\n <div\n style={{\n font: \"var(--type-title-weight) 20px/26px var(--font-sans)\",\n letterSpacing: \".08em\",\n }}\n >\n •••• •••• •••• 4417\n </div>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n color: \"var(--on-frame-muted)\",\n fontSize: 13,\n }}\n >\n <span>Mara Ellis</span>\n <span>Expires 04/26</span>\n </div>\n </div>\n <Panel title=\"Collected per month\">\n <BarChart\n height={92}\n barWidth={22}\n format={(v) => \"€\" + Math.round(v / 1000) + \"k\"}\n data={[\n {\n label: \"Nov\",\n value: 31000,\n },\n {\n label: \"Dec\",\n value: 28000,\n },\n {\n label: \"Jan\",\n value: 36000,\n },\n {\n label: \"Feb\",\n value: 39000,\n },\n {\n label: \"Mar\",\n value: 44000,\n },\n {\n label: \"Apr\",\n value: 48210,\n tint: \"butter\",\n },\n ]}\n />\n </Panel>\n </Stagger>\n <div\n className=\"tf-panel tf-panel-flush tf-in\"\n style={\n {\n \"--d\": \"420ms\",\n } as React.CSSProperties\n }\n >\n <Table\n columns={[\n {\n key: \"id\",\n label: \"Invoice\",\n },\n {\n key: \"c\",\n label: \"Customer\",\n render: (r) => (\n <span className=\"tf-row\">\n <Avatar name={r.c} size={26} />\n {r.c}\n </span>\n ),\n },\n {\n key: \"d\",\n label: \"Issued\",\n },\n {\n key: \"s\",\n label: \"Status\",\n render: (r) => <Badge tone={r.tone}>{r.s}</Badge>,\n },\n {\n key: \"a\",\n label: \"Amount\",\n align: \"right\",\n },\n {\n key: \"x\",\n label: \"\",\n align: \"right\",\n render: () => <IconButton icon=\"download\" label=\"Download PDF\" size=\"sm\" />,\n },\n ]}\n rows={INV}\n />\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/BillingScreen"},{"name":"BoardScreen","title":"Board","group":"Pages","summary":"A complete screen built from AppShell, AvatarStack, Badge, Button and 6 more components.","description":"","components":["AppShell","AvatarStack","Badge","Button","Card","IconButton","KanbanColumn","PageHeader","Progress","Tabs"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n AvatarStack,\n Badge,\n Button,\n Card,\n KanbanColumn,\n PageHeader,\n Progress,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { Tint } from \"@twentyfirstmedia/ui-core\";\n\ninterface TaskProps {\n t: string;\n m?: string[];\n b?: React.ReactNode;\n who: string[];\n tint?: Tint;\n p?: number;\n}\n\nfunction T(p: TaskProps) {\n return (\n <Card\n onClick={() => {}}\n tint={p.tint}\n title={p.t}\n meta={p.m}\n status={p.b}\n actions={[<AvatarStack key={1} names={p.who} size={24} />]}\n >\n {p.p != null && <Progress value={p.p} tint=\"mint\" label=\"Progress\" />}\n </Card>\n );\n}\nfunction App() {\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n crumbs={[\"Projects\", \"Pricing page refresh\"]}\n title=\"Sprint 14 board\"\n subtitle=\"18 tasks · ends Friday\"\n actions={[\n <AvatarStack\n key=\"a\"\n names={[\n \"Mara Ellis\",\n \"Jonah Petit\",\n \"Sun Lee\",\n \"Rui Costa\",\n \"Ida Nolan\",\n \"Ava Brandt\",\n ]}\n />,\n <Tabs key=\"t\" items={[\"Board\", \"List\", \"Timeline\"]} />,\n <Button key=\"n\" icon=\"plus\">\n New task\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(4, minmax(260px, 85%))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 16,\n alignItems: \"start\",\n overflowX: \"auto\",\n } as React.CSSProperties\n }\n >\n <KanbanColumn title=\"To do\" dot=\"sky\" delay={120}>\n <T\n t=\"Write FAQ for annual plans\"\n m={[\"Docs\", \"Due Thu\"]}\n b={<Badge>Copy</Badge>}\n who={[\"Ava Brandt\"]}\n />\n <T\n t=\"Localise tiers for DE and FR\"\n m={[\"Growth\"]}\n b={<Badge tone=\"info\">i18n</Badge>}\n who={[\"Sun Lee\", \"Ida Nolan\"]}\n />\n <T\n t=\"Plan migration emails\"\n m={[\"Lifecycle\"]}\n b={<Badge>Email</Badge>}\n who={[\"Rui Costa\"]}\n />\n </KanbanColumn>\n <KanbanColumn title=\"In progress\" dot=\"butter\" delay={220}>\n <T\n tint=\"lilac\"\n t=\"Comparison table component\"\n m={[\"Design system\", \"3 of 5 states\"]}\n b={<Badge tone=\"highlight\">Focus</Badge>}\n who={[\"Jonah Petit\", \"Mara Ellis\"]}\n p={60}\n />\n <T\n t=\"Checkout seat selector\"\n m={[\"Billing\"]}\n b={<Badge tone=\"warning\">Blocked</Badge>}\n who={[\"Rui Costa\"]}\n p={30}\n />\n </KanbanColumn>\n <KanbanColumn title=\"In review\" dot=\"lilac\" delay={320}>\n <T\n t=\"Billing settings flow\"\n m={[\"Due Fri\"]}\n b={<Badge tone=\"highlight\">Review</Badge>}\n who={[\"Mara Ellis\", \"Sun Lee\"]}\n />\n <T\n t=\"Empty states for reports\"\n m={[\"Due Mon\"]}\n b={<Badge tone=\"warning\">Changes</Badge>}\n who={[\"Jonah Petit\"]}\n />\n </KanbanColumn>\n <KanbanColumn title=\"Done\" dot=\"mint\" delay={420}>\n <T\n t=\"Pricing research recap\"\n m={[\"Shipped Tue\"]}\n b={<Badge tone=\"success\">Done</Badge>}\n who={[\"Sun Lee\"]}\n />\n <T\n t=\"New tier names\"\n m={[\"Shipped Mon\"]}\n b={<Badge tone=\"success\">Done</Badge>}\n who={[\"Mara Ellis\"]}\n />\n <T\n t=\"Analytics events for upgrade\"\n m={[\"Shipped Mon\"]}\n b={<Badge tone=\"success\">Done</Badge>}\n who={[\"Ida Nolan\"]}\n />\n </KanbanColumn>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/BoardScreen"},{"name":"CalendarScreen","title":"Calendar","group":"Pages","summary":"A complete screen built from AppShell, Button, EventCard, IconButton and 4 more components.","description":"","components":["AppShell","Button","EventCard","IconButton","PageHeader","Panel","Stagger","Tabs"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Button,\n EventCard,\n IconButton,\n PageHeader,\n Panel,\n Stagger,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\n\nconst DAYS = [\"Mon 13\", \"Tue 14\", \"Wed 15\", \"Thu 16\", \"Fri 17\"];\nconst EV = [\n {\n d: 0,\n s: 9,\n e: 10,\n t: \"Standup\",\n tint: \"sky\",\n who: \"Team\",\n },\n {\n d: 0,\n s: 13,\n e: 14.5,\n t: \"Hiring panel\",\n tint: \"lilac\",\n who: \"Design\",\n },\n {\n d: 1,\n s: 10.5,\n e: 12,\n t: \"Team sync\",\n tint: \"sky\",\n who: \"Growth + Design\",\n },\n {\n d: 1,\n s: 15,\n e: 16,\n t: \"Partner call\",\n tint: \"mint\",\n who: \"Northwind\",\n },\n {\n d: 2,\n s: 9,\n e: 10,\n t: \"Standup\",\n tint: \"sky\",\n who: \"Team\",\n },\n {\n d: 2,\n s: 14,\n e: 15.5,\n t: \"Design review\",\n tint: \"lilac\",\n who: \"Billing flow\",\n },\n {\n d: 3,\n s: 11,\n e: 12.5,\n t: \"Pricing review\",\n tint: \"butter\",\n who: \"Moved from Wed\",\n },\n {\n d: 3,\n s: 16,\n e: 17,\n t: \"Incident retro\",\n tint: \"blush\",\n who: \"Webhooks #88\",\n },\n {\n d: 4,\n s: 9,\n e: 10,\n t: \"Standup\",\n tint: \"sky\",\n who: \"Team\",\n },\n {\n d: 4,\n s: 15.5,\n e: 17,\n t: \"Q2 invoices due\",\n tint: \"butter\",\n who: \"Finance\",\n },\n];\nconst START = 8,\n END = 18,\n ROW = 48;\nfunction App() {\n const hours = [];\n for (let i = START; i <= END; i++) hours.push(i);\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Calendar\"\n dock={false}\n side={\n <Panel side title=\"Up next\" subtitle=\"Thursday, April 16\">\n <div\n className=\"tf-card tf-in\"\n style={\n {\n minHeight: 0,\n gap: 12,\n padding: 16,\n \"--d\": \"200ms\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <span\n style={{\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n April 2026\n </span>\n <span\n className=\"tf-row\"\n style={{\n gap: 4,\n }}\n >\n <IconButton icon=\"chevronLeft\" label=\"Previous month\" size=\"sm\" />\n <IconButton icon=\"chevronRight\" label=\"Next month\" size=\"sm\" />\n </span>\n </div>\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(7, 1fr)\",\n gap: 4,\n textAlign: \"center\",\n font: \"var(--type-label-weight) 11px/28px var(--font-sans)\",\n }}\n >\n {[\"M\", \"T\", \"W\", \"T\", \"F\", \"S\", \"S\"].map((d, i) => (\n <span\n key={\"h\" + i}\n style={{\n color: \"var(--ink-muted)\",\n }}\n >\n {d}\n </span>\n ))}\n {([null, null] as (number | null)[])\n .concat(\n Array.from(\n {\n length: 30,\n },\n (_, i) => i + 1,\n ),\n )\n .map((n, i) => {\n const on = n != null && n >= 13 && n <= 17,\n today = n === 16;\n return (\n <span\n key={i}\n style={{\n borderRadius: \"var(--radius-pill)\",\n background: today ? \"var(--primary)\" : on ? \"var(--sky)\" : \"transparent\",\n color: today ? \"var(--primary-foreground)\" : \"var(--ink)\",\n }}\n >\n {n || \"\"}\n </span>\n );\n })}\n </div>\n </div>\n <Stagger className=\"tf-col\" delay={320}>\n <EventCard\n kind=\"review\"\n label=\"Pricing review\"\n date=\"Thu, Apr 16\"\n time=\"11:00\"\n tint=\"butter\"\n icon=\"card\"\n >\n Moved from Wednesday so Growth can join.\n </EventCard>\n <EventCard kind=\"incident\" label=\"Incident retro\" date=\"Thu, Apr 16\" time=\"16:00\">\n What delayed EU webhooks and how we stop it.\n </EventCard>\n </Stagger>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Calendar\"\n subtitle=\"Week 16 · 10 events\"\n actions={[\n <Tabs key=\"t\" items={[\"Day\", \"Week\", \"Month\"]} defaultValue=\"Week\" />,\n <Button key=\"n\" icon=\"plus\">\n New event\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"120ms\",\n padding: 16,\n gap: 0,\n overflowX: \"auto\",\n } as React.CSSProperties\n }\n tabIndex={0}\n role=\"region\"\n aria-label=\"Week view\"\n >\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"48px repeat(5, 1fr)\",\n minWidth: 560,\n gap: 8,\n paddingBottom: 12,\n }}\n >\n <span />\n {DAYS.map((d, i) => (\n <div\n key={d}\n style={{\n textAlign: \"center\",\n font: \"var(--type-title-weight) 13px/18px var(--font-sans)\",\n padding: \"8px 0\",\n borderRadius: \"var(--radius-pill)\",\n background: i === 3 ? \"var(--primary)\" : \"transparent\",\n color: i === 3 ? \"var(--primary-foreground)\" : \"var(--ink)\",\n }}\n >\n {d}\n </div>\n ))}\n </div>\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"48px repeat(5, 1fr)\",\n minWidth: 560,\n gap: 8,\n position: \"relative\",\n }}\n >\n <div>\n {hours.slice(0, -1).map((hr) => (\n <div\n key={hr}\n style={{\n height: ROW,\n font: \"var(--type-label-weight) 11px/1 var(--font-sans)\",\n color: \"var(--ink-muted)\",\n }}\n >\n {(hr < 10 ? \"0\" : \"\") + hr + \":00\"}\n </div>\n ))}\n </div>\n {DAYS.map((d, di) => (\n <div\n key={d}\n style={{\n position: \"relative\",\n height: ROW * (END - START),\n backgroundImage:\n \"repeating-linear-gradient(to bottom, var(--line) 0 1px, transparent 1px \" +\n ROW +\n \"px)\",\n borderRadius: \"var(--radius-sm)\",\n }}\n >\n {EV.filter((e) => e.d === di).map((e, i) => (\n <div\n key={i}\n className={\"tf-in tf-bg-\" + e.tint}\n style={\n {\n \"--d\": 200 + di * 70 + i * 60 + \"ms\",\n position: \"absolute\",\n left: 4,\n right: 4,\n top: (e.s - START) * ROW + 2,\n height: (e.e - e.s) * ROW - 4,\n borderRadius: \"var(--radius-sm)\",\n padding: \"8px 12px\",\n boxSizing: \"border-box\",\n overflow: \"hidden\",\n } as React.CSSProperties\n }\n >\n <div\n style={{\n font: \"var(--type-title-weight) 12px/16px var(--font-sans)\",\n }}\n >\n {e.t}\n </div>\n <div\n style={{\n font: \"var(--type-body-weight) 11px/15px var(--font-sans)\",\n opacity: 0.78,\n }}\n >\n {e.who}\n </div>\n </div>\n ))}\n </div>\n ))}\n </div>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/CalendarScreen"},{"name":"CustomersScreen","title":"Customers","group":"Pages","summary":"A complete screen built from Avatar, Badge, Drawer, IconButton and 9 more components.","description":"","components":["Avatar","Badge","Drawer","Icon","IconButton","ListRow","PageHeader","SearchField","Stagger","StatTile","SubNav","Table","Tabs","TopBar"],"code":"import * as React from \"react\";\nimport {\n Avatar,\n Badge,\n Drawer,\n ListRow,\n PageHeader,\n SearchField,\n Stagger,\n StatTile,\n Table,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\nimport type { Tone } from \"@twentyfirstmedia/ui-core\";\n\nconst CO: [string, string, string, number, string][] = [\n [\"Northwind\", \"Logistics · Hamburg\", \"Team\", 1240, \"Good\"],\n [\"Kestrel Bank\", \"Banking · Frankfurt\", \"Business\", 3880, \"Good\"],\n [\"Copperline\", \"Manufacturing · Munich\", \"Starter\", 190, \"Watch\"],\n [\"Harbor & Co\", \"Retail · Cologne\", \"Team\", 1468, \"At risk\"],\n [\"Lumen Labs\", \"Research · Vienna\", \"Business\", 4076, \"Good\"],\n [\"Orbit Foods\", \"Food · Zurich\", \"Team\", 1445, \"Good\"],\n];\nconst HT: Record<string, Tone> = {\n Good: \"success\",\n Watch: \"warning\",\n \"At risk\": \"danger\",\n};\nfunction App() {\n return (\n <Shell\n area=\"Customers\"\n current=\"companies\"\n action={{\n label: \"New deal\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"companies\",\n label: \"Companies\",\n icon: \"grid\",\n count: 1,\n },\n {\n id: \"pipeline\",\n label: \"Pipeline\",\n icon: \"trend\",\n count: 2,\n },\n {\n id: \"contacts\",\n label: \"Contacts\",\n icon: \"user\",\n },\n ]}\n overlay={\n <Drawer\n inline\n title=\"Northwind\"\n subtitle=\"Logistics · Hamburg · customer since 2021\"\n avatar=\"Northwind\"\n onClose={() => {}}\n >\n <div className=\"tf-row\">\n <Badge tone=\"success\">Good</Badge>\n <Badge tone=\"info\" dot={false}>\n Team · 24 seats\n </Badge>\n <Badge dot={false}>Owner: Mara Ellis</Badge>\n </div>\n <Tabs block items={[\"Overview\", \"Invoices\", \"Mail\", \"Agent brief\"]} />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€1,240\" label=\"MRR\" />\n <StatTile tint=\"mint\" value=\"€14,880\" label=\"Paid to date\" />\n </div>\n <ListRow\n avatar=\"Ida Nolan\"\n title=\"Ida Nolan\"\n subtitle=\"Head of Ops · ida@northwind.example\"\n />\n <ListRow\n avatar=\"Amara Silva\"\n title=\"Amara Silva\"\n subtitle=\"IT admin · amara@northwind.example\"\n />\n </Drawer>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Companies\"\n subtitle=\"15 customers · example data\"\n actions={[<SearchField key=\"s\" placeholder=\"Search companies\" />]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"butter\" value=\"€33,067\" label=\"Monthly recurring revenue\" />\n <StatTile tint=\"sky\" value=\"15\" label=\"Customers\" />\n <StatTile tint=\"placeholder\" value=\"5\" label=\"Need attention\" />\n <StatTile tint=\"mint\" value=\"€182,600\" label=\"Open pipeline\" />\n </Stagger>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n padding: 12,\n \"--d\": \"260ms\",\n } as React.CSSProperties\n }\n >\n <Table\n columns={[\n {\n key: \"0\",\n label: \"Company\",\n render: (r) => (\n <span\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Avatar name={r[0]} size={30} />\n <span>\n <b>{r[0]}</b>\n <div className=\"tf-card-desc\">{r[1]}</div>\n </span>\n </span>\n ),\n },\n {\n key: \"2\",\n label: \"Plan\",\n render: (r) => (\n <Badge\n tone={r[2] === \"Business\" ? \"highlight\" : r[2] === \"Team\" ? \"info\" : \"neutral\"}\n dot={false}\n >\n {r[2]}\n </Badge>\n ),\n },\n {\n key: \"3\",\n label: \"MRR\",\n align: \"right\",\n render: (r) => \"€\" + r[3].toLocaleString(\"en-US\"),\n },\n {\n key: \"4\",\n label: \"Health\",\n render: (r) => <Badge tone={HT[r[4]]}>{r[4]}</Badge>,\n },\n ]}\n rows={CO}\n />\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/CustomersScreen"},{"name":"Dashboard","title":"Dashboard","group":"Pages","summary":"A complete screen built from AppShell, Badge, Button, Card and 10 more components.","description":"","components":["AppShell","Badge","Button","Card","Chip","EventCard","IconButton","PageHeader","Panel","SearchField","Stagger","StatTile","Tabs","TintIcon"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Badge,\n Button,\n Card,\n Chip,\n EventCard,\n IconButton,\n PageHeader,\n Panel,\n SearchField,\n Stagger,\n StatTile,\n Tabs,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport { cardActs } from \"./_workspace\";\n\nfunction App() {\n const f = React.useState(\"All\");\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n side={\n <Panel\n side\n title=\"Schedule\"\n subtitle=\"5 items waiting · 2 today\"\n action={<IconButton icon=\"plus\" label=\"Add event\" />}\n >\n <Tabs block items={[\"Events\", \"Approvals\", \"History\"]} />\n <Stagger className=\"tf-col\" delay={300}>\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Tue, Apr 14\" time=\"10:30\">\n Roadmap check-in with design and growth; bring last sprint numbers.\n </EventCard>\n <EventCard kind=\"review\" label=\"Design review\" date=\"Wed, Apr 15\" time=\"14:00\">\n Walk through the new billing settings flow.\n </EventCard>\n <EventCard kind=\"deadline\" label=\"Deadline\" date=\"Fri, Apr 17\" time=\"17:00\">\n Send Q2 invoices to all annual customers.\n </EventCard>\n </Stagger>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Projects\"\n subtitle=\"Plan, ship and retire work across the workspace\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search people, projects, invoices\" shortcut=\"⌘K\" />,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"All projects\" />\n <StatTile tint=\"mint\" value=\"11\" label=\"Live\" delta=\"+2 this week\" />\n <StatTile tint=\"placeholder\" value=\"4\" label=\"Drafts\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Revenue (MTD)\" />\n </Stagger>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"260ms\",\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 12,\n alignItems: \"center\",\n justifyContent: \"space-between\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-row\">\n {[\"All\", \"Live\", \"Draft\", \"Archived\"].map((t) => (\n <Chip\n key={t}\n selected={f[0] === t}\n onClick={() => {\n f[1](t);\n }}\n >\n {t}\n </Chip>\n ))}\n </div>\n <Button icon=\"plus\">New project</Button>\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n delay={320}\n >\n <Card\n onClick={() => {}}\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new workspace admins.\"\n meta={[\"Mara Ellis\", \"12 steps\", \"348 users\"]}\n corner={<TintIcon icon=\"book\" tint=\"mint\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <Card\n onClick={() => {}}\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table for annual plans.\"\n meta={[\"Jonah Petit\", \"6 sections\"]}\n corner={<IconButton icon=\"play\" label=\"Preview\" size=\"sm\" variant=\"outline\" />}\n status={<Badge tone=\"neutral\">86 viewing</Badge>}\n actions={[<IconButton key={1} icon=\"edit\" label=\"Edit\" size=\"sm\" variant=\"solid\" />]}\n />\n <Card\n onClick={() => {}}\n title=\"Partner referral program\"\n description=\"Track invites, payouts and partner tiers.\"\n meta={[\"Sun Lee\", \"4 steps\", \"57 partners\"]}\n corner={<TintIcon icon=\"users\" tint=\"sky\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <Card\n onClick={() => {}}\n title=\"Invoice #2041 is overdue\"\n description=\"Net-30 passed four days ago; the reminder bounced.\"\n meta={[\"Rui Costa\", \"High priority\"]}\n corner={<TintIcon icon=\"mail\" tint=\"blush\" />}\n status={<Badge tone=\"warning\">Open</Badge>}\n actions={[\n <Button key={1} size=\"sm\" variant=\"soft\">\n Reply\n </Button>,\n ]}\n />\n <Card\n onClick={() => {}}\n title=\"Help center migration\"\n description=\"Move 140 articles to the new docs layout.\"\n meta={[\"Ava Brandt\", \"0 published\"]}\n corner={<TintIcon icon=\"file\" tint=\"mint\" />}\n status={<Badge>Draft</Badge>}\n actions={cardActs()}\n />\n <Card\n onClick={() => {}}\n title=\"Annual plan upgrade request\"\n description=\"Northwind wants 40 more seats before renewal.\"\n meta={[\"Ida Nolan\", \"Normal priority\"]}\n corner={<TintIcon icon=\"card\" tint=\"butter\" />}\n status={<Badge tone=\"info\">Pending</Badge>}\n actions={cardActs()}\n />\n </Stagger>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/Dashboard"},{"name":"FilesScreen","title":"Files","group":"Pages","summary":"A complete screen built from Chip, CommandPalette, FileCard, IconButton and 6 more components.","description":"","components":["Chip","CommandPalette","FileCard","Icon","IconButton","PageHeader","SearchField","Stagger","SubNav","Tabs","TopBar"],"code":"import * as React from \"react\";\nimport {\n Chip,\n CommandPalette,\n FileCard,\n PageHeader,\n SearchField,\n Stagger,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\nimport type { FileKind } from \"@twentyfirstmedia/ui-core\";\n\nconst F: [string, FileKind, string, string, boolean?][] = [\n [\"Northwind – master agreement.pdf\", \"pdf\", \"412 KB\", \"Today\", true],\n [\"Kestrel Bank – proposal v3.pdf\", \"pdf\", \"1.8 MB\", \"Yesterday\", true],\n [\"DPA template (DSGVO).doc\", \"doc\", \"96 KB\", \"Mon\"],\n [\"pricing-tiers.fig\", \"fig\", \"18 MB\", \"Today\", true],\n [\"dashboard-shots.png\", \"img\", \"2.4 MB\", \"Yesterday\"],\n [\"brand-assets.zip\", \"zip\", \"36 MB\", \"Apr 2\"],\n [\"support-macros.sheet\", \"sheet\", \"58 KB\", \"Yesterday\"],\n [\"pnl-q1-2026.sheet\", \"sheet\", \"92 KB\", \"Apr 1\", true],\n];\nfunction App() {\n return (\n <Shell\n area=\"Files\"\n current=\"files\"\n action={{\n label: \"Upload\",\n icon: \"paperclip\",\n }}\n pages={[\n {\n id: \"files\",\n label: \"All files\",\n icon: \"folder\",\n },\n ]}\n overlay={\n <CommandPalette\n inline\n onClose={() => {}}\n items={[\n {\n id: 1,\n group: \"Pages\",\n label: \"All files\",\n hint: \"Files\",\n icon: \"folder\",\n },\n {\n id: 2,\n group: \"Actions\",\n label: \"Upload files\",\n icon: \"paperclip\",\n tint: \"butter\",\n },\n {\n id: 3,\n group: \"Actions\",\n label: \"New invoice\",\n icon: \"card\",\n tint: \"butter\",\n },\n {\n id: 4,\n group: \"Actions\",\n label: \"Compose mail\",\n icon: \"edit\",\n tint: \"sky\",\n },\n {\n id: 5,\n group: \"Actions\",\n label: \"New agent\",\n icon: \"bot\",\n tint: \"lilac\",\n },\n ]}\n />\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Files\"\n subtitle=\"15 files · drop files anywhere to upload\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search files\" />,\n <Tabs key=\"t\" items={[\"Grid\", \"List\"]} />,\n ]}\n />\n </div>\n <div\n className=\"tf-row tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n {[\"All\", \"Contracts\", \"Receipts\", \"Design\", \"Agent knowledge\", \"Exports\"].map((t, i) => (\n <Chip key={t} icon={i ? \"folder\" : undefined} selected={i === 0}>\n {t}\n </Chip>\n ))}\n </div>\n <Stagger\n className=\"tf-grid\"\n style={{\n gridTemplateColumns: \"repeat(auto-fill, minmax(180px, 1fr))\",\n }}\n delay={140}\n >\n {F.map((f) => (\n <FileCard key={f[0]} name={f[0]} kind={f[1]} size={f[2]} meta={f[3]} shared={f[4]} />\n ))}\n </Stagger>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/FilesScreen"},{"name":"InboxScreen","title":"Inbox","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, Button and 9 more components.","description":"","components":["AppShell","Avatar","Badge","Button","FileRow","IconButton","ListRow","PageHeader","Panel","SearchField","Stagger","StatTile","Tabs"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n Button,\n FileRow,\n IconButton,\n ListRow,\n PageHeader,\n Panel,\n SearchField,\n Stagger,\n StatTile,\n Tabs,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { ListRowProps } from \"@twentyfirstmedia/ui-core\";\n\nfunction Msg(p: {\n who: string;\n time: string;\n d: number;\n me?: boolean;\n children?: React.ReactNode;\n}) {\n return (\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": p.d + \"ms\",\n display: \"flex\",\n gap: 12,\n flexDirection: p.me ? \"row-reverse\" : \"row\",\n alignItems: \"flex-end\",\n } as React.CSSProperties\n }\n >\n <Avatar name={p.who} size={28} />\n <div\n style={{\n maxWidth: \"72%\",\n padding: \"12px 16px\",\n borderRadius: \"var(--radius-md)\",\n borderBottomLeftRadius: p.me ? 18 : 6,\n borderBottomRightRadius: p.me ? 6 : 18,\n background: p.me ? \"var(--primary)\" : \"var(--surface-sunken)\",\n color: p.me ? \"var(--primary-foreground)\" : \"var(--ink)\",\n font: \"var(--type-body-weight) 13px/20px var(--font-sans)\",\n }}\n >\n {p.children}\n <div\n style={{\n font: \"var(--type-body-weight) 11px/14px var(--font-sans)\",\n opacity: 0.65,\n marginTop: 4,\n }}\n >\n {p.time}\n </div>\n </div>\n </div>\n );\n}\nfunction App() {\n const sel = React.useState(0);\n const list: ListRowProps[] = [\n {\n avatar: \"Jonah Petit\",\n title: \"Jonah Petit\",\n subtitle: \"Can we move the pricing review to Thursday?\",\n meta: \"09:12\",\n unread: true,\n },\n {\n avatar: \"Northwind\",\n title: \"Northwind · Ida\",\n subtitle: \"Seat upgrade before renewal — 40 more\",\n meta: \"08:40\",\n unread: true,\n },\n {\n icon: \"bell\",\n tint: \"butter\",\n title: \"Billing\",\n subtitle: \"Card ending 4417 expires this month\",\n meta: \"Yesterday\",\n },\n {\n avatar: \"Sun Lee\",\n title: \"Sun Lee\",\n subtitle: \"Payout file for March is attached\",\n meta: \"Yesterday\",\n },\n {\n avatar: \"Blue Fern\",\n title: \"Blue Fern · Leo\",\n subtitle: \"Thanks, the export works now!\",\n meta: \"Mon\",\n },\n {\n icon: \"flag\",\n tint: \"blush\",\n title: \"Incident #88\",\n subtitle: \"Webhooks delayed in EU region\",\n meta: \"Sun\",\n },\n ];\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Inbox\"\n dock={false}\n side={\n <Panel side title=\"Northwind\" subtitle=\"Team plan · since 2023\">\n <div className=\"tf-row\">\n <Badge tone=\"success\">Active</Badge>\n <Badge tone=\"info\">Renews May 1</Badge>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 12,\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€1,240\" label=\"MRR\" />\n <StatTile tint=\"sky\" value=\"24\" label=\"Seats\" />\n </div>\n <h3\n className=\"tf-card-title\"\n style={{\n fontSize: 16,\n }}\n >\n Attachments\n </h3>\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n delay={300}\n >\n <FileRow name=\"seat-quote.pdf\" meta=\"182 KB · PDF\" kind=\"pdf\" />\n <FileRow name=\"usage-q1.xlsx\" meta=\"96 KB · Sheet\" kind=\"sheet\" />\n </Stagger>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Inbox\"\n subtitle=\"2 unread · 4 waiting on you\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search messages\" />,\n <Button key=\"n\" icon=\"edit\">\n Compose\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"320px 1fr\",\n gap: 16,\n minHeight: 0,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <Tabs block items={[\"All\", \"Unread\", \"Assigned\"]} />\n <Stagger className=\"tf-list\" delay={120}>\n {list.map((it, i) => (\n <ListRow\n key={i}\n {...it}\n active={sel[0] === i}\n onClick={() => {\n sel[1](i);\n }}\n />\n ))}\n </Stagger>\n </div>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"160ms\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-header\"\n style={{\n alignItems: \"center\",\n }}\n >\n <div className=\"tf-row\">\n <Avatar name=\"Jonah Petit\" size={36} />\n <div>\n <div\n className=\"tf-card-title\"\n style={{\n padding: 0,\n fontSize: 16,\n }}\n >\n Jonah Petit\n </div>\n <div className=\"tf-card-desc\">Pricing page refresh</div>\n </div>\n </div>\n <div className=\"tf-row\">\n <IconButton icon=\"star\" label=\"Star\" />\n <IconButton icon=\"inbox\" label=\"Archive\" />\n <IconButton icon=\"more\" label=\"More\" />\n </div>\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 12,\n padding: \"8px 0\",\n }}\n >\n <Msg who=\"Jonah Petit\" time=\"09:02\" d={260}>\n Morning! The comparison table is ready for a look.\n </Msg>\n <Msg who=\"Jonah Petit\" time=\"09:12\" d={360}>\n Can we move the pricing review to Thursday? Growth wants to join.\n </Msg>\n <Msg who=\"Mara Ellis\" me time=\"09:20\" d={460}>\n Thursday works. I will move the event and add Sun.\n </Msg>\n </div>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"560ms\",\n marginTop: \"auto\",\n display: \"flex\",\n gap: 8,\n alignItems: \"center\",\n padding: \"4px 4px 4px 20px\",\n borderRadius: \"var(--radius-pill)\",\n background: \"var(--surface-sunken)\",\n } as React.CSSProperties\n }\n >\n <IconButton icon=\"paperclip\" label=\"Attach\" variant=\"outline\" />\n <input\n placeholder=\"Write a reply…\"\n aria-label=\"Reply\"\n style={{\n flex: 1,\n minWidth: 0,\n border: 0,\n background: \"transparent\",\n outline: \"none\",\n font: \"var(--type-body-weight) 13px var(--font-sans)\",\n color: \"var(--ink)\",\n }}\n />\n <Button icon=\"send\">Send</Button>\n </div>\n </div>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/InboxScreen"},{"name":"InviteModalScreen","title":"Invite Modal","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, Button and 8 more components.","description":"","components":["AppShell","Avatar","Badge","Button","Card","IconButton","Input","Modal","PageHeader","Select","StatTile","Toast"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n Button,\n Card,\n Input,\n Modal,\n PageHeader,\n Select,\n StatTile,\n Toast,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n style={{\n position: \"relative\",\n }}\n >\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"People\"\n dock={false}\n >\n <PageHeader\n title=\"People\"\n subtitle=\"Everyone with access to this workspace\"\n actions={[\n <Button key=\"b\" icon=\"users\">\n Invite\n </Button>,\n ]}\n />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"38\" label=\"Members\" />\n <StatTile tint=\"mint\" value=\"31\" label=\"Active today\" />\n <StatTile tint=\"placeholder\" value=\"3\" label=\"Pending invites\" />\n <StatTile tint=\"lilac\" value=\"6\" label=\"Admins\" />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n >\n {[\"Mara Ellis\", \"Jonah Petit\", \"Sun Lee\", \"Rui Costa\", \"Ida Nolan\", \"Ava Brandt\"].map(\n (n) => (\n <Card\n key={n}\n title={n}\n description=\"Member\"\n corner={<Avatar name={n} size={32} />}\n status={<Badge tone=\"success\">Active</Badge>}\n />\n ),\n )}\n </div>\n </AppShell>\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n borderRadius: \"var(--radius-xl)\",\n overflow: \"hidden\",\n }}\n >\n <Modal\n style={{\n position: \"absolute\",\n inset: 0,\n }}\n icon=\"users\"\n tint=\"sky\"\n title=\"Invite teammates\"\n text=\"They get access to every project in this workspace.\"\n actions={[\n <Button key={1} variant=\"ghost\">\n Cancel\n </Button>,\n <Button key={2} icon=\"send\">\n Send 3 invites\n </Button>,\n ]}\n >\n <Input\n label=\"Email addresses\"\n defaultValue=\"leo@bluefern.io, nina@bluefern.io, ops@bluefern.io\"\n />\n <Select label=\"Role\" options={[\"Editor\", \"Viewer\", \"Admin\"]} />\n <div className=\"tf-row\">\n <Badge tone=\"info\">3 seats left after this</Badge>\n <Badge>Expires in 7 days</Badge>\n </div>\n </Modal>\n </div>\n <div\n style={{\n position: \"absolute\",\n right: 40,\n bottom: 40,\n }}\n >\n <Toast action=\"Undo\">Ava Brandt was removed</Toast>\n </div>\n </div>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/InviteModalScreen"},{"name":"InvoiceScreen","title":"Invoice","group":"Pages","summary":"A complete screen built from Button, IconButton, InvoicePreview, PageHeader and 5 more components.","description":"","components":["Button","Icon","IconButton","InvoicePreview","PageHeader","Select","SubNav","TintIcon","TopBar","TransactionRow"],"code":"import * as React from \"react\";\nimport {\n Button,\n InvoicePreview,\n PageHeader,\n Select,\n TintIcon,\n TransactionRow,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n const lines = [\n {\n d: \"Halo Team plan · 40 seats · May 2026\",\n q: 40,\n u: 24,\n v: 19,\n },\n {\n d: \"Onboarding workshop (half day)\",\n q: 1,\n u: 650,\n v: 19,\n },\n ];\n return (\n <Shell\n area=\"Finance\"\n current=\"invoices\"\n action={{\n label: \"New invoice\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"overview\",\n label: \"Overview\",\n icon: \"chart\",\n },\n {\n id: \"invoices\",\n label: \"Invoices\",\n icon: \"card\",\n count: 4,\n },\n {\n id: \"expenses\",\n label: \"Expenses\",\n icon: \"file\",\n count: 4,\n },\n {\n id: \"bank\",\n label: \"Bank\",\n icon: \"swap\",\n count: 4,\n },\n {\n id: \"vat\",\n label: \"VAT return\",\n icon: \"flag\",\n },\n {\n id: \"pnl\",\n label: \"Profit & loss\",\n icon: \"trend\",\n },\n ]}\n >\n <div className=\"tf-in\">\n <PageHeader\n crumbs={[\"Finance\", \"Invoices\", \"New\"]}\n title=\"Invoice #2097\"\n subtitle=\"Totals update as you type\"\n actions={[\n <Button key=\"d\" variant=\"secondary\">\n Save draft\n </Button>,\n <Button key=\"s\" icon=\"send\">\n {\"Create & send\"}\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1.1fr) minmax(0, 1fr)\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n <Select label=\"Customer\" options={[\"Northwind\", \"Kestrel Bank\"]} />\n <Select label=\"Payment terms\" options={[\"14 days\", \"30 days\"]} />\n </div>\n {lines.map((l, i) => (\n <div key={i} className=\"tf-file\">\n <TintIcon icon=\"card\" tint=\"butter\" />\n <div\n style={{\n flex: 1,\n minWidth: 0,\n }}\n >\n <div className=\"tf-file-name\">{l.d}</div>\n <div className=\"tf-file-meta\">{l.q + \" × €\" + l.u + \" · VAT \" + l.v + \"%\"}</div>\n </div>\n <b>{\"€\" + (l.q * l.u).toLocaleString(\"en-US\") + \".00\"}</b>\n </div>\n ))}\n <Button variant=\"soft\" icon=\"plus\">\n Add line\n </Button>\n </div>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"160ms\",\n } as React.CSSProperties\n }\n >\n <InvoicePreview\n brand=\"Halo\"\n number={2097}\n from=\"Halo Workspace GmbH · Example Street 1 · 20457 Hamburg\"\n to=\"Northwind\"\n issued=\"Apr 16, 2026\"\n due=\"Apr 30, 2026\"\n lines={lines}\n note=\"Thank you for your business. Please pay within 14 days.\"\n />\n </div>\n </div>\n <div\n className=\"tf-col tf-in\"\n style={\n {\n gap: 8,\n \"--d\": \"240ms\",\n } as React.CSSProperties\n }\n >\n <h3\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Bank: to match\n </h3>\n <TransactionRow\n text=\"STRIPE PAYOUT 0415\"\n date=\"Apr 15, 2026\"\n amount={2318.4}\n hint=\"Payout for 3 card payments: invoices #2093, #2094, #2095\"\n />\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/InvoiceScreen"},{"name":"LoadingScreen","title":"Loading","group":"Pages","summary":"A complete screen built from AppShell, Badge, Button, Card and 7 more components.","description":"","components":["AppShell","Badge","Button","Card","EmptyState","IconButton","PageHeader","Skeleton","Stagger","StatTile","TintIcon"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Badge,\n Button,\n Card,\n EmptyState,\n PageHeader,\n Skeleton,\n Stagger,\n StatTile,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport { cardActs } from \"./_workspace\";\n\nfunction SkelCard() {\n return (\n <div className=\"tf-card\">\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <Skeleton variant=\"title\" width=\"55%\" />\n <Skeleton variant=\"circle\" width={30} />\n </div>\n <Skeleton />\n <Skeleton width=\"70%\" />\n <div className=\"tf-card-foot\">\n <Skeleton width={70} height={26} />\n <div className=\"tf-row\">\n <Skeleton variant=\"circle\" width={30} />\n <Skeleton variant=\"circle\" width={30} />\n </div>\n </div>\n </div>\n );\n}\nfunction App() {\n const done = React.useState(false);\n React.useEffect(() => {\n const t = setTimeout(() => {\n done[1](true);\n }, 2200);\n return () => {\n clearTimeout(t);\n };\n }, []);\n if (done[0])\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Projects\"\n subtitle=\"Loaded — content replaces the skeleton with the normal entrance\"\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"All projects\" />\n <StatTile tint=\"mint\" value=\"11\" label=\"Live\" />\n <StatTile tint=\"placeholder\" value=\"4\" label=\"Drafts\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Revenue (MTD)\" />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n delay={200}\n >\n <Card\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new workspace admins.\"\n corner={<TintIcon icon=\"book\" tint=\"mint\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <Card\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table.\"\n status={<Badge>86 viewing</Badge>}\n />\n <Card\n title=\"Partner referral program\"\n description=\"Track invites, payouts and tiers.\"\n corner={<TintIcon icon=\"users\" tint=\"sky\" />}\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n />\n <EmptyState\n title=\"No archived projects\"\n text=\"Projects you retire land here, with their history kept.\"\n icon=\"folder\"\n tint=\"butter\"\n action={\n <Button variant=\"soft\" size=\"sm\">\n View all\n </Button>\n }\n />\n </Stagger>\n </AppShell>\n );\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n dock={false}\n >\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <Skeleton variant=\"title\" width={220} height={28} />\n <Skeleton width={320} />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n {[0, 1, 2, 3].map((i) => (\n <Skeleton key={i} variant=\"block\" height={84} />\n ))}\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, 1fr)\",\n } as React.CSSProperties\n }\n >\n {[0, 1, 2, 3, 4, 5].map((i) => (\n <SkelCard key={i} />\n ))}\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/LoadingScreen"},{"name":"MapScreen","title":"Map","group":"Pages","summary":"A city map with glass controls floating over it: search and filters on top, map layers on the start side, the selected place with directions on the end side, zoom buttons and a dock.","description":"A city map with glass controls floating over it: search and filters on top, map layers on the start side, the selected place with directions on the end side, zoom buttons and a dock.\n\n- Built from `FloatingLayout` and `MapView`; the map is drawn from the kit's colours, so it follows the kit and the theme. Put your own map (MapLibre, Mapbox, Cesium, three.js) into `background` instead.\n- Everything floating is glass: translucent, blurred and readable over any backdrop.\n","components":["Badge","Button","ButtonGroup","Chip","Dock","FloatingLayout","IconButton","KeyValue","MapView","Panel","SearchField","Segmented","Switch"],"code":"import * as React from \"react\";\nimport {\n Badge,\n Button,\n ButtonGroup,\n Chip,\n Dock,\n FloatingLayout,\n IconButton,\n KeyValue,\n MapView,\n Panel,\n SearchField,\n Segmented,\n Switch,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\n/** A city drawn from the kit's colours, so it follows the kit and the theme. */\nfunction CityMap() {\n const streets = [80, 190, 300, 410, 520, 630, 740];\n const avenues = [90, 230, 370, 510, 650, 790, 930, 1070];\n return (\n <svg viewBox=\"0 0 1200 800\" preserveAspectRatio=\"xMidYMid slice\" aria-hidden>\n <rect width=\"1200\" height=\"800\" fill=\"var(--background)\" />\n {avenues.flatMap((x, i) =>\n streets.map((y, j) => (\n <rect\n key={`${i}-${j}`}\n x={x + 12}\n y={y + 12}\n width={116}\n height={86}\n rx={6}\n fill=\"var(--muted)\"\n />\n )),\n )}\n <rect x=\"652\" y=\"82\" width=\"256\" height=\"196\" rx=\"18\" fill=\"var(--mint)\" />\n <rect x=\"92\" y=\"522\" width=\"256\" height=\"196\" rx=\"18\" fill=\"var(--mint)\" />\n <circle cx=\"790\" cy=\"180\" r=\"34\" fill=\"var(--sky)\" />\n <path\n d=\"M-40 470 C 180 400, 320 560, 520 470 S 860 330, 1240 430 L 1240 560 C 900 470, 720 610, 520 600 S 160 540, -40 610 Z\"\n fill=\"var(--sky)\"\n />\n <g stroke=\"var(--card)\" strokeWidth=\"10\" strokeLinecap=\"round\">\n {streets.map((y) => (\n <line key={y} x1=\"0\" x2=\"1200\" y1={y + 6} y2={y + 6} />\n ))}\n {avenues.map((x) => (\n <line key={x} x1={x + 6} x2={x + 6} y1=\"0\" y2=\"800\" />\n ))}\n </g>\n <g stroke=\"var(--butter)\" strokeWidth=\"16\" strokeLinecap=\"round\" fill=\"none\">\n <path d=\"M0 196 L 1200 196\" />\n <path d=\"M516 0 L 516 800\" />\n <path d=\"M150 800 L 660 330 L 1200 120\" />\n </g>\n </svg>\n );\n}\n\nconst places: MapPoint[] = [\n {\n id: \"cafe\",\n label: \"Kaffeerösterei\",\n sub: \"Café · open until 18:00\",\n lat: 72,\n lng: 44,\n text: \"Café\",\n tint: \"butter\",\n },\n {\n id: \"museum\",\n label: \"Harbour Museum\",\n sub: \"Museum · €12\",\n lat: 50,\n lng: 54,\n text: \"Museum\",\n tint: \"lilac\",\n },\n {\n id: \"market\",\n label: \"Fish Market\",\n sub: \"Market · Sundays 5–9:30\",\n lat: 24,\n lng: 40,\n text: \"Market\",\n tint: \"blush\",\n },\n {\n id: \"park\",\n label: \"Planten un Blomen\",\n sub: \"Park\",\n lat: 32,\n lng: 60,\n text: \"Park\",\n tint: \"mint\",\n },\n];\n\nexport default function Preview() {\n const [selected, setSelected] = React.useState<MapPoint[\"id\"] | null>(\"museum\");\n const [mode, setMode] = React.useState(\"Explore\");\n const place = places.find((p) => p.id === selected);\n return (\n <FloatingLayout\n label=\"City map\"\n height={720}\n background={\n <MapView\n label=\"Map of the harbour district\"\n background={<CityMap />}\n bounds={{ n: 100, s: 0, w: 0, e: 100 }}\n points={places}\n selected={selected}\n onSelect={setSelected}\n height=\"100%\"\n />\n }\n top={\n <>\n <SearchField\n placeholder=\"Search places and addresses\"\n style={{ flex: \"1 1 260px\", maxWidth: 420 }}\n />\n <Chip>Coffee</Chip>\n <Chip>Museums</Chip>\n <Chip selected>Open now</Chip>\n </>\n }\n start={\n <Panel title=\"Layers\" subtitle=\"What the map shows\">\n <Segmented\n label=\"Map style\"\n options={[\"Explore\", \"Transit\", \"Satellite\"]}\n value={mode}\n onChange={setMode}\n block\n />\n <Switch defaultChecked>Traffic</Switch>\n <Switch defaultChecked>Parks and water</Switch>\n <Switch>Bike lanes</Switch>\n </Panel>\n }\n end={\n <div style={{ display: \"grid\", gap: 12 }}>\n <ButtonGroup orientation=\"vertical\" label=\"Zoom\" style={{ justifySelf: \"end\" }}>\n <IconButton icon=\"plus\" label=\"Zoom in\" variant=\"outline\" />\n <IconButton icon=\"minus\" label=\"Zoom out\" variant=\"outline\" />\n </ButtonGroup>\n {place && (\n <Panel\n title={place.label}\n subtitle={typeof place.sub === \"string\" ? place.sub : undefined}\n action={\n <Badge tone=\"success\" dot>\n Open\n </Badge>\n }\n >\n <KeyValue\n items={[\n [\"Walk\", \"12 min\"],\n [\"Distance\", \"850 m\"],\n [\"Rating\", \"4.7 of 5\"],\n ]}\n />\n <div className=\"tf-row\" style={{ gap: 8 }}>\n <Button variant=\"primary\" icon=\"send\">\n Directions\n </Button>\n <Button variant=\"secondary\" icon=\"star\">\n Save\n </Button>\n </div>\n </Panel>\n )}\n </div>\n }\n bottom={\n <Dock\n label=\"Map tools\"\n items={[\n { icon: \"search\", label: \"Search\", tint: \"sky\" },\n { icon: \"star\", label: \"Saved\", tint: \"butter\" },\n { icon: \"clock\", label: \"Recent\", tint: \"mint\" },\n { icon: \"share\", label: \"Share\", tint: \"lilac\" },\n ]}\n />\n }\n />\n );\n}\n","path":"patterns/MapScreen"},{"name":"MobileScreens","title":"Mobile Screens","group":"Pages","summary":"A complete screen built from Avatar, Badge, Card, Chip and 7 more components.","description":"","components":["Avatar","Badge","Card","Chip","Dock","EventCard","IconButton","ListRow","SearchField","Stagger","StatTile"],"code":"import * as React from \"react\";\nimport {\n Avatar,\n Badge,\n Card,\n Chip,\n Dock,\n EventCard,\n IconButton,\n ListRow,\n SearchField,\n Stagger,\n StatTile,\n} from \"@twentyfirstmedia/ui-core\";\nimport { cardActs } from \"./_workspace\";\n\nfunction Phone(p: { d: number; tab: number; children?: React.ReactNode }) {\n return (\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": p.d + \"ms\",\n width: 360,\n maxWidth: \"100%\",\n height: 760,\n borderRadius: 44,\n background: \"var(--frame)\",\n padding: 10,\n boxSizing: \"border-box\",\n boxShadow: \"var(--shadow-float)\",\n flex: \"none\",\n scrollSnapAlign: \"center\",\n } as React.CSSProperties\n }\n >\n <div\n style={{\n height: \"100%\",\n borderRadius: 36,\n background: \"var(--canvas)\",\n overflow: \"hidden\",\n display: \"flex\",\n flexDirection: \"column\",\n position: \"relative\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"16px 20px 8px\",\n font: \"var(--type-title-weight) 13px var(--font-sans)\",\n }}\n >\n <span>9:41</span>\n <span\n style={{\n width: 90,\n height: 24,\n borderRadius: \"var(--radius-pill)\",\n background: \"var(--frame)\",\n }}\n />\n <span>•••</span>\n </div>\n <div\n style={{\n flex: 1,\n overflow: \"hidden\",\n padding: \"8px 16px 90px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: 16,\n }}\n >\n {p.children}\n </div>\n <div\n style={{\n position: \"absolute\",\n left: 0,\n right: 0,\n bottom: 16,\n display: \"flex\",\n justifyContent: \"center\",\n }}\n >\n <Dock\n items={[\n {\n label: \"Home\",\n icon: \"home\",\n tint: p.tab === 0 ? \"sky\" : \"plain\",\n },\n {\n label: \"Projects\",\n icon: \"grid\",\n tint: p.tab === 1 ? \"mint\" : \"plain\",\n },\n {\n label: \"Inbox\",\n icon: \"mail\",\n tint: p.tab === 2 ? \"lilac\" : \"plain\",\n },\n {\n label: \"New\",\n icon: \"plus\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}\nfunction App() {\n return (\n <div\n style={{\n display: \"flex\",\n gap: 28,\n justifyContent: \"safe center\",\n overflowX: \"auto\",\n scrollSnapType: \"x mandatory\",\n padding: \"20px 0\",\n background: \"var(--canvas)\",\n borderRadius: \"var(--radius-xl)\",\n }}\n >\n <Phone d={0} tab={0}>\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <div>\n <div className=\"tf-card-desc\">Good morning</div>\n <div\n className=\"tf-header-title\"\n style={{\n fontSize: 24,\n }}\n >\n Mara\n </div>\n </div>\n <Avatar name=\"Mara Ellis\" size={40} />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 12,\n } as React.CSSProperties\n }\n delay={150}\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"Projects\" />\n <StatTile tint=\"butter\" value=\"€48k\" label=\"Revenue\" />\n <StatTile tint=\"mint\" value=\"11\" label=\"Live\" />\n <StatTile tint=\"placeholder\" value=\"4\" label=\"Review\" />\n </Stagger>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"400ms\",\n } as React.CSSProperties\n }\n >\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Today\" time=\"10:30\" actions={false}>\n Roadmap check-in with design and growth.\n </EventCard>\n </div>\n </Phone>\n <Phone d={120} tab={1}>\n <div\n className=\"tf-header-title\"\n style={{\n fontSize: 24,\n }}\n >\n Projects\n </div>\n <div\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Chip selected>All</Chip>\n <Chip>Live</Chip>\n <Chip>Draft</Chip>\n </div>\n <Stagger className=\"tf-col\" delay={250}>\n <Card\n tint=\"lilac\"\n title=\"Pricing page refresh\"\n description=\"New tiers and a comparison table.\"\n status={<Badge>86 viewing</Badge>}\n actions={[<IconButton key={1} icon=\"edit\" label=\"Edit\" size=\"sm\" variant=\"solid\" />]}\n style={{\n minHeight: 0,\n }}\n />\n <Card\n title=\"Quarterly onboarding\"\n description=\"Welcome flow for new admins.\"\n status={<Badge tone=\"success\">Live</Badge>}\n actions={cardActs()}\n style={{\n minHeight: 0,\n }}\n />\n <Card\n title=\"Partner referrals\"\n description=\"Invites, payouts, tiers.\"\n status={<Badge tone=\"success\">Live</Badge>}\n style={{\n minHeight: 0,\n }}\n />\n </Stagger>\n </Phone>\n <Phone d={240} tab={2}>\n <div\n className=\"tf-header-title\"\n style={{\n fontSize: 24,\n }}\n >\n Inbox\n </div>\n <SearchField\n placeholder=\"Search messages\"\n style={{\n minWidth: 0,\n }}\n />\n <Stagger className=\"tf-list\" delay={350}>\n <ListRow\n avatar=\"Jonah Petit\"\n title=\"Jonah Petit\"\n subtitle=\"Move the review to Thursday?\"\n meta=\"09:12\"\n unread\n />\n <ListRow\n avatar=\"Northwind\"\n title=\"Northwind\"\n subtitle=\"Seat upgrade before renewal\"\n meta=\"08:40\"\n unread\n />\n <ListRow\n icon=\"bell\"\n tint=\"butter\"\n title=\"Billing\"\n subtitle=\"Card ending 4417 expires\"\n meta=\"Yest.\"\n />\n <ListRow avatar=\"Sun Lee\" title=\"Sun Lee\" subtitle=\"Payout file attached\" meta=\"Yest.\" />\n <ListRow\n icon=\"flag\"\n tint=\"blush\"\n title=\"Incident #88\"\n subtitle=\"Webhooks delayed in EU\"\n meta=\"Sun\"\n />\n </Stagger>\n </Phone>\n </div>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/MobileScreens"},{"name":"PeopleScreen","title":"People","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Badge, Button and 11 more components.","description":"","components":["AppShell","Avatar","Badge","Button","Chip","Donut","IconButton","PageHeader","Pagination","Panel","SearchField","Stagger","StatTile","Table","Timeline"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Badge,\n Button,\n Chip,\n Donut,\n IconButton,\n PageHeader,\n Pagination,\n Panel,\n SearchField,\n Stagger,\n StatTile,\n Table,\n Timeline,\n} from \"@twentyfirstmedia/ui-core\";\n\nconst MEMBERS = [\n {\n n: \"Mara Ellis\",\n e: \"mara@halo.app\",\n r: \"Owner\",\n t: \"Leadership\",\n s: \"Active\",\n tone: \"success\",\n a: \"Now\",\n },\n {\n n: \"Jonah Petit\",\n e: \"jonah@halo.app\",\n r: \"Admin\",\n t: \"Design\",\n s: \"Active\",\n tone: \"success\",\n a: \"5 min ago\",\n },\n {\n n: \"Sun Lee\",\n e: \"sun@halo.app\",\n r: \"Editor\",\n t: \"Growth\",\n s: \"Active\",\n tone: \"success\",\n a: \"1 h ago\",\n },\n {\n n: \"Rui Costa\",\n e: \"rui@halo.app\",\n r: \"Editor\",\n t: \"Finance\",\n s: \"Away\",\n tone: \"warning\",\n a: \"Yesterday\",\n },\n {\n n: \"Ava Brandt\",\n e: \"ava@halo.app\",\n r: \"Viewer\",\n t: \"Support\",\n s: \"Invited\",\n tone: \"info\",\n a: \"—\",\n },\n {\n n: \"Ida Nolan\",\n e: \"ida@halo.app\",\n r: \"Editor\",\n t: \"Sales\",\n s: \"Active\",\n tone: \"success\",\n a: \"3 h ago\",\n },\n {\n n: \"Theo Marsh\",\n e: \"theo@halo.app\",\n r: \"Viewer\",\n t: \"Design\",\n s: \"Suspended\",\n tone: \"danger\",\n a: \"12 days ago\",\n },\n];\nfunction App() {\n const f = React.useState(\"Everyone\");\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"People\"\n side={\n <Panel side title=\"Team\" subtitle=\"42 seats · 38 used\">\n <Donut\n size={120}\n stroke={16}\n legend={false}\n center=\"38\"\n caption=\"of 42\"\n data={[\n {\n label: \"Design\",\n value: 9,\n tint: \"lilac\",\n },\n {\n label: \"Growth\",\n value: 11,\n tint: \"mint\",\n },\n {\n label: \"Support\",\n value: 8,\n tint: \"sky\",\n },\n {\n label: \"Finance\",\n value: 4,\n tint: \"butter\",\n },\n {\n label: \"Sales\",\n value: 6,\n tint: \"blush\",\n },\n ]}\n />\n <div className=\"tf-legend\">\n {[\n [\"Design\", \"lilac\"],\n [\"Growth\", \"mint\"],\n [\"Support\", \"sky\"],\n [\"Finance\", \"butter\"],\n [\"Sales\", \"blush\"],\n ].map((x) => (\n <span key={x[0]}>\n <i\n style={{\n background: \"var(--\" + x[1] + \"-ink)\",\n }}\n />\n {x[0]}\n </span>\n ))}\n </div>\n <h3\n className=\"tf-card-title\"\n style={{\n fontSize: 16,\n marginTop: 8,\n }}\n >\n Recent activity\n </h3>\n <Timeline\n delay={400}\n items={[\n {\n avatar: \"Jonah Petit\",\n text: (\n <span>\n <b>Jonah</b>\n {\" changed Ava to Viewer\"}\n </span>\n ),\n time: \"10 min ago\",\n },\n {\n icon: \"send\",\n tint: \"sky\",\n text: \"3 invites sent to support@\",\n time: \"1 h ago\",\n },\n {\n avatar: \"Mara Ellis\",\n text: (\n <span>\n <b>Mara</b>\n {\" suspended Theo Marsh\"}\n </span>\n ),\n time: \"Mon\",\n },\n ]}\n />\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"People\"\n subtitle=\"Everyone with access to this workspace\"\n actions={[\n <SearchField key=\"s\" placeholder=\"Search by name or email\" />,\n <Button key=\"b\" icon=\"users\">\n Invite\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n delay={80}\n >\n <StatTile tint=\"sky\" value=\"38\" label=\"Members\" />\n <StatTile tint=\"mint\" value=\"31\" label=\"Active today\" />\n <StatTile tint=\"placeholder\" value=\"3\" label=\"Pending invites\" />\n <StatTile tint=\"lilac\" value=\"6\" label=\"Admins\" />\n </Stagger>\n <div\n className=\"tf-in tf-row\"\n style={\n {\n \"--d\": \"240ms\",\n } as React.CSSProperties\n }\n >\n {[\"Everyone\", \"Admins\", \"Editors\", \"Viewers\", \"Invited\"].map((t) => (\n <Chip\n key={t}\n selected={f[0] === t}\n onClick={() => {\n f[1](t);\n }}\n >\n {t}\n </Chip>\n ))}\n </div>\n <div\n className=\"tf-panel tf-panel-flush tf-in\"\n style={\n {\n \"--d\": \"300ms\",\n } as React.CSSProperties\n }\n >\n <Table\n columns={[\n {\n key: \"n\",\n label: \"Name\",\n render: (r) => (\n <span\n className=\"tf-row\"\n style={{\n flexWrap: \"nowrap\",\n }}\n >\n <Avatar name={r.n} size={32} />\n <span>\n <div\n style={{\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n {r.n}\n </div>\n <div\n style={{\n fontSize: 12,\n color: \"var(--ink-muted)\",\n }}\n >\n {r.e}\n </div>\n </span>\n </span>\n ),\n },\n {\n key: \"r\",\n label: \"Role\",\n },\n {\n key: \"t\",\n label: \"Team\",\n },\n {\n key: \"s\",\n label: \"Status\",\n render: (r) => <Badge tone={r.tone}>{r.s}</Badge>,\n },\n {\n key: \"a\",\n label: \"Last active\",\n },\n {\n key: \"x\",\n label: \"\",\n align: \"right\",\n render: () => <IconButton icon=\"more\" label=\"Member options\" size=\"sm\" />,\n },\n ]}\n rows={MEMBERS}\n />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n padding: \"8px 16px\",\n }}\n >\n <span className=\"tf-card-desc\">Showing 7 of 38</span>\n <Pagination total={6} page={1} />\n </div>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/PeopleScreen"},{"name":"PricingScreen","title":"Pricing","group":"Pages","summary":"A complete screen built from PageHeader, Panel, PlanCard, Segmented and 3 more components.","description":"","components":["Icon","PageHeader","Panel","PlanCard","Segmented","SubNav","TopBar","UsageMeter"],"code":"import { PageHeader, Panel, PlanCard, Segmented, UsageMeter } from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n return (\n <Shell\n area=\"Account\"\n current={\"Plan & billing\"}\n pages={[\n {\n id: \"Profile\",\n label: \"Profile & theme\",\n icon: \"user\",\n },\n {\n id: \"Roles\",\n label: \"Roles & access\",\n icon: \"lock\",\n },\n {\n id: \"Plan & billing\",\n label: \"Plan & billing\",\n icon: \"card\",\n },\n ]}\n >\n <PageHeader\n title={\"Plan & billing\"}\n subtitle=\"You’re on Team · renews on May 1, 2026\"\n actions={[\n <Segmented\n key=\"y\"\n defaultValue=\"Yearly\"\n options={[\n \"Monthly\",\n {\n value: \"Yearly\",\n label: \"Yearly · save 17%\",\n },\n ]}\n />,\n ]}\n />\n <div className=\"tf-plans\">\n <PlanCard\n name=\"Starter\"\n price={9}\n was={12}\n text=\"For small teams\"\n features={[\"3 projects\", \"1 agent\", \"-Audit log\"]}\n />\n <PlanCard\n name=\"Team\"\n price={24}\n was={29}\n featured\n current\n text=\"For growing companies\"\n features={[\"Unlimited projects\", \"10 agents\", \"Audit log\"]}\n />\n <PlanCard\n name=\"Business\"\n price={null}\n cta=\"Talk to sales\"\n text=\"Security and an SLA\"\n features={[\"Everything in Team\", \"SSO and SCIM\", \"SLA\"]}\n />\n </div>\n <Panel title=\"Usage this month\">\n <UsageMeter\n label=\"Agent usage\"\n used={84}\n limit={120}\n format={(v) => \"€\" + v}\n caption=\"Included up to €120\"\n />\n </Panel>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/PricingScreen"},{"name":"ProjectScreen","title":"Project","group":"Pages","summary":"A complete screen built from AppShell, Avatar, AvatarStack, Badge and 11 more components.","description":"","components":["AppShell","Avatar","AvatarStack","Badge","Button","Checkbox","FileRow","IconButton","PageHeader","Panel","ProgressRing","Stagger","StatTile","Tabs","Timeline"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n AvatarStack,\n Badge,\n Button,\n Checkbox,\n FileRow,\n PageHeader,\n Panel,\n ProgressRing,\n Stagger,\n StatTile,\n Tabs,\n Timeline,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n // task, done, assignee\n const tasks: [string, boolean, string][] = [\n [\"Draft new tier names\", true, \"Mara Ellis\"],\n [\"Comparison table component\", true, \"Jonah Petit\"],\n [\"Localise tiers for DE and FR\", false, \"Sun Lee\"],\n [\"Checkout seat selector\", false, \"Rui Costa\"],\n [\"Write FAQ for annual plans\", false, \"Ava Brandt\"],\n ];\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Projects\"\n side={\n <Panel side title=\"Activity\" subtitle=\"Last 7 days\">\n <Timeline\n delay={300}\n items={[\n {\n avatar: \"Mara Ellis\",\n text: (\n <span>\n <b>Mara</b>\n {\" approved the tier names\"}\n </span>\n ),\n time: \"2 min ago\",\n },\n {\n avatar: \"Jonah Petit\",\n text: (\n <span>\n <b>Jonah</b>\n {\" moved Comparison table to review\"}\n </span>\n ),\n time: \"1 h ago\",\n },\n {\n icon: \"sparkle\",\n tint: \"mint\",\n text: \"Preview deployed to staging\",\n time: \"Yesterday\",\n },\n {\n avatar: \"Rui Costa\",\n text: (\n <span>\n <b>Rui</b>\n {\" flagged the seat selector as blocked\"}\n </span>\n ),\n time: \"Mon\",\n },\n {\n icon: \"flag\",\n tint: \"blush\",\n text: \"Deadline set to Fri, Apr 17\",\n time: \"Mon\",\n },\n ]}\n />\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n crumbs={[\"Projects\", \"Pricing page refresh\"]}\n title=\"Pricing page refresh\"\n subtitle=\"New tiers and a comparison table for annual plans\"\n actions={[\n <AvatarStack key=\"a\" names={[\"Mara Ellis\", \"Jonah Petit\", \"Sun Lee\", \"Rui Costa\"]} />,\n <Button key=\"s\" variant=\"secondary\" icon=\"share\">\n Share\n </Button>,\n <Button key=\"p\" icon=\"play\">\n Preview\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"100ms\",\n } as React.CSSProperties\n }\n >\n <Tabs items={[\"Overview\", \"Tasks\", \"Files\", \"Settings\"]} />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.3fr 1fr\",\n } as React.CSSProperties\n }\n delay={160}\n >\n <div\n className=\"tf-card tf-bg-lilac\"\n style={{\n minHeight: 0,\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: 24,\n }}\n >\n <ProgressRing value={64} size={104} stroke={11} caption=\"done\" />\n <div\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n >\n <span\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n On track for Friday\n </span>\n <span className=\"tf-card-desc\">\n 11 of 18 tasks done. One blocker on checkout needs a decision from billing.\n </span>\n <div className=\"tf-row\">\n <Badge tone=\"success\">On track</Badge>\n <Badge tone=\"warning\">1 blocker</Badge>\n </div>\n </div>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 12,\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"18\" label=\"Tasks\" />\n <StatTile tint=\"butter\" value=\"3\" label=\"Days left\" />\n <StatTile tint=\"mint\" value=\"24\" label=\"Comments\" />\n <StatTile tint=\"placeholder\" value=\"2\" label=\"In review\" />\n </div>\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1.3fr 1fr\",\n } as React.CSSProperties\n }\n delay={320}\n >\n <Panel\n title=\"Milestone tasks\"\n action={\n <Button size=\"sm\" variant=\"soft\" icon=\"plus\">\n Add\n </Button>\n }\n >\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 4,\n }}\n delay={420}\n >\n {tasks.map((t) => (\n <div\n key={t[0]}\n className=\"tf-row-item\"\n style={{\n gridTemplateColumns: \"auto 1fr auto\",\n cursor: \"default\",\n }}\n >\n <Checkbox defaultChecked={t[1]} label={t[0]} />\n <span\n style={{\n fontWeight: \"var(--type-label-weight)\",\n fontSize: 14,\n textDecoration: t[1] ? \"line-through\" : \"none\",\n color: t[1] ? \"var(--ink-muted)\" : \"var(--ink)\",\n }}\n >\n {t[0]}\n </span>\n <Avatar name={t[2]} size={24} />\n </div>\n ))}\n </Stagger>\n </Panel>\n <Panel title=\"Files\" subtitle=\"4 attachments\">\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 8,\n }}\n delay={480}\n >\n <FileRow name=\"brief-v4.pdf\" meta=\"2.4 MB · PDF\" kind=\"pdf\" />\n <FileRow name=\"pricing-tiers.fig\" meta=\"18 MB · Design\" kind=\"fig\" />\n <FileRow name=\"q2-forecast.xlsx\" meta=\"640 KB · Sheet\" kind=\"sheet\" />\n </Stagger>\n </Panel>\n </Stagger>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/ProjectScreen"},{"name":"SettingsScreen","title":"Settings","group":"Pages","summary":"A complete screen built from AppShell, Avatar, Button, IconButton and 7 more components.","description":"","components":["AppShell","Avatar","Button","IconButton","Input","ListRow","PageHeader","Panel","Select","Stagger","Switch"],"code":"import * as React from \"react\";\nimport {\n AppShell,\n Avatar,\n Button,\n Input,\n ListRow,\n PageHeader,\n Panel,\n Select,\n Stagger,\n Switch,\n} from \"@twentyfirstmedia/ui-core\";\nimport type { IconName, Tint } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const sec = React.useState(0);\n const nav: [string, IconName, Tint | \"plain\"][] = [\n [\"Profile\", \"user\", \"sky\"],\n [\"Workspace\", \"grid\", \"mint\"],\n [\"Notifications\", \"bell\", \"butter\"],\n [\"Security\", \"lock\", \"lilac\"],\n [\"Integrations\", \"link\", \"plain\"],\n [\"Billing\", \"card\", \"blush\"],\n ];\n return (\n <AppShell\n brand=\"Halo\"\n tag=\"Admin\"\n user={{ name: \"Mara Ellis\", email: \"mara@halo.app\" }}\n active=\"Settings\"\n dock={false}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Settings\"\n subtitle=\"Manage your profile and how Halo works for your team\"\n actions={[\n <Button key=\"c\" variant=\"ghost\">\n Discard\n </Button>,\n <Button key=\"s\" icon=\"check\">\n Save changes\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"240px 1fr\",\n gap: 24,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel tf-panel-flush tf-panel-list\">\n <Stagger className=\"tf-list\" delay={100}>\n {nav.map((n, i) => (\n <ListRow\n key={n[0]}\n icon={n[1]}\n tint={n[2]}\n title={n[0]}\n active={sec[0] === i}\n onClick={() => {\n sec[1](i);\n }}\n />\n ))}\n </Stagger>\n </div>\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n delay={160}\n >\n <Panel title=\"Profile\" subtitle=\"Shown to everyone in the workspace\">\n <div\n className=\"tf-row\"\n style={{\n gap: 16,\n }}\n >\n <Avatar name=\"Mara Ellis\" size={64} />\n <Button variant=\"secondary\" size=\"sm\">\n Upload photo\n </Button>\n <Button variant=\"ghost\" size=\"sm\">\n Remove\n </Button>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <Input label=\"Full name\" defaultValue=\"Mara Ellis\" />\n <Input\n label=\"Email\"\n defaultValue=\"mara@halo.app\"\n hint=\"Used for sign-in and receipts.\"\n />\n <Select label=\"Role\" options={[\"Owner\", \"Admin\", \"Editor\", \"Viewer\"]} />\n <Select\n label=\"Time zone\"\n options={[\n \"Europe/Berlin (UTC+2)\",\n \"Asia/Dubai (UTC+4)\",\n \"America/New_York (UTC−4)\",\n ]}\n />\n </div>\n </Panel>\n <Panel title=\"Notifications\" subtitle=\"Choose what reaches your inbox\">\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <Switch spread defaultChecked>\n Approvals waiting on me\n </Switch>\n <Switch spread defaultChecked>\n Invoices paid or overdue\n </Switch>\n <Switch spread>Weekly analytics digest</Switch>\n <Switch spread>Product updates from Halo</Switch>\n </div>\n </Panel>\n <div\n className=\"tf-card tf-bg-blush\"\n style={{\n minHeight: 0,\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <div>\n <div\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Delete workspace\n </div>\n <div className=\"tf-card-desc\">\n Removes every project, file and invoice. This cannot be undone.\n </div>\n </div>\n <Button variant=\"secondary\" icon=\"trash\">\n Delete…\n </Button>\n </div>\n </Stagger>\n </div>\n </AppShell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/SettingsScreen"},{"name":"SetupScreen","title":"Setup","group":"Pages","summary":"A complete screen built from Button, IconButton, PageHeader, StatTile and 4 more components.","description":"","components":["Button","Icon","IconButton","PageHeader","StatTile","SubNav","TintIcon","TopBar","Wizard"],"code":"import * as React from \"react\";\nimport { Button, PageHeader, StatTile, TintIcon, Wizard } from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n const c = React.useState(2);\n return (\n <Shell\n area=\"Workspace\"\n current=\"overview\"\n action={{\n label: \"New project\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"overview\",\n label: \"Overview\",\n icon: \"home\",\n },\n {\n id: \"board\",\n label: \"Projects\",\n icon: \"grid\",\n count: 7,\n },\n {\n id: \"calendar\",\n label: \"Calendar\",\n icon: \"calendar\",\n },\n {\n id: \"people\",\n label: \"People\",\n icon: \"users\",\n },\n {\n id: \"reports\",\n label: \"Reports\",\n icon: \"chart\",\n },\n ]}\n overlay={\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n borderRadius: \"var(--radius-xl)\",\n overflow: \"hidden\",\n }}\n >\n <div\n className=\"tf-modal-wrap\"\n style={{\n position: \"absolute\",\n inset: 0,\n }}\n >\n <div className=\"tf-backdrop\" />\n <Wizard\n brand=\"Halo\"\n current={c[0]}\n onPick={c[1]}\n done={{\n 0: true,\n 1: true,\n }}\n steps={[\n {\n label: \"Name your workspace\",\n icon: \"grid\",\n },\n {\n label: \"Invite your team\",\n icon: \"users\",\n },\n {\n label: \"Connect your mailbox\",\n icon: \"mail\",\n },\n {\n label: \"Connect a domain\",\n icon: \"globe\",\n },\n {\n label: \"Connect your bank\",\n icon: \"card\",\n },\n {\n label: \"Create your first agent\",\n icon: \"bot\",\n },\n ]}\n footer={[\n <Button\n key=\"b\"\n variant=\"ghost\"\n icon=\"chevronLeft\"\n onClick={() => {\n c[1](Math.max(0, c[0] - 1));\n }}\n >\n Back\n </Button>,\n <Button\n key=\"n\"\n iconRight=\"chevronRight\"\n onClick={() => {\n c[1](Math.min(5, c[0] + 1));\n }}\n >\n Continue\n </Button>,\n ]}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n } as React.CSSProperties\n }\n >\n {[\"Google Workspace\", \"Microsoft 365\", \"IMAP / SMTP\", \"Halo Mail\"].map((m, i) => (\n <div\n key={m}\n className=\"tf-file\"\n style={\n i === 0\n ? {\n boxShadow: \"inset 0 0 0 2px var(--ink)\",\n background: \"var(--surface)\",\n }\n : undefined\n }\n >\n <TintIcon icon=\"mail\" tint={i === 0 ? \"mint\" : \"plain\"} />\n <b>{m}</b>\n </div>\n ))}\n </div>\n </Wizard>\n </div>\n </div>\n }\n >\n <PageHeader title=\"Good morning, Mara\" subtitle=\"Everything that needs a decision today\" />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"sky\" value=\"15\" label=\"All projects\" />\n <StatTile tint=\"mint\" value=\"8\" label=\"Live\" />\n <StatTile tint=\"placeholder\" value=\"3\" label=\"Needs review\" />\n <StatTile tint=\"butter\" value=\"€32,669\" label=\"Revenue (MTD)\" />\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/SetupScreen"},{"name":"ShopScreen","title":"Shop","group":"Pages","summary":"A complete screen built from Alert, Button, Funnel, PageHeader and 5 more components.","description":"","components":["Alert","Button","Funnel","Icon","PageHeader","Panel","StackedBarChart","StatTile","SubNav","TopBar"],"code":"import * as React from \"react\";\nimport {\n Alert,\n Button,\n Funnel,\n PageHeader,\n Panel,\n StackedBarChart,\n StatTile,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\nimport type { SubNavItem } from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n const pages: SubNavItem[] = [\n {\n id: \"Overview\",\n label: \"Overview\",\n icon: \"chart\",\n },\n {\n id: \"Products\",\n label: \"Products\",\n icon: \"package\",\n },\n {\n id: \"Orders\",\n label: \"Orders\",\n icon: \"truck\",\n count: 8,\n },\n {\n id: \"Storefront\",\n label: \"Storefront\",\n icon: \"cart\",\n },\n ];\n return (\n <Shell\n area=\"Shop\"\n current=\"Overview\"\n action={{\n label: \"Add product\",\n icon: \"plus\",\n }}\n pages={pages}\n >\n <PageHeader\n title=\"Shop\"\n subtitle=\"Halo Goods · last 7 days\"\n actions={[\n <Button key=\"v\" variant=\"secondary\" icon=\"arrowUpRight\">\n View store\n </Button>,\n ]}\n />\n <Alert\n tone=\"warning\"\n title=\"3 products are running low\"\n action={\n <Button size=\"sm\" variant=\"secondary\">\n Review stock\n </Button>\n }\n >\n Studio Mug (6 left), Canvas Tote (sold out), Court Sneaker (9 left).\n </Alert>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-sm\": \"repeat(2, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n } as React.CSSProperties\n }\n >\n <StatTile tint=\"butter\" value=\"€5,592\" label=\"Revenue\" />\n <StatTile tint=\"sky\" value=\"36\" label=\"Orders\" />\n <StatTile tint=\"mint\" value=\"€155\" label=\"Average order\" />\n <StatTile tint=\"placeholder\" value=\"11\" label=\"To fulfil\" />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1.6fr) minmax(0, 1fr)\",\n } as React.CSSProperties\n }\n >\n <Panel title=\"Sales by channel\">\n <StackedBarChart\n height={160}\n labels={[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]}\n format={(v) => \"€\" + Math.round(v)}\n series={[\n {\n name: \"Online store\",\n tint: \"sky\",\n values: [420, 380, 510, 460, 620, 840, 760],\n },\n {\n name: \"Instagram\",\n tint: \"lilac\",\n values: [120, 160, 140, 210, 260, 380, 300],\n },\n {\n name: \"Marketplace\",\n tint: \"butter\",\n values: [90, 110, 80, 130, 150, 170, 160],\n },\n ]}\n />\n </Panel>\n <Panel title=\"Conversion\">\n <Funnel\n steps={[\n {\n label: \"Visited\",\n value: 8420,\n },\n {\n label: \"Viewed product\",\n value: 3910,\n },\n {\n label: \"Added to cart\",\n value: 812,\n },\n {\n label: \"Ordered\",\n value: 214,\n },\n ]}\n />\n </Panel>\n </div>\n </Shell>\n );\n}\nexport default App;\n","path":"patterns/ShopScreen"},{"name":"SignInScreen","title":"Sign In","group":"Pages","summary":"A complete screen built from AvatarStack, Badge, Button, Checkbox and 6 more components.","description":"","components":["AvatarStack","Badge","Button","Checkbox","EventCard","IconButton","Input","Progress","Stagger","StatTile"],"code":"import * as React from \"react\";\nimport {\n AvatarStack,\n Badge,\n Button,\n Checkbox,\n EventCard,\n Input,\n Progress,\n Stagger,\n StatTile,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n className=\"tf-shell\"\n style={{\n padding: 8,\n }}\n >\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1.1fr\",\n gap: 8,\n minHeight: 760,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-sheet\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"space-between\",\n padding: \"clamp(20px, 6vw, 48px)\",\n gridTemplateColumns: \"none\",\n }}\n >\n <span\n className=\"tf-brand tf-in\"\n style={{\n color: \"var(--ink)\",\n fontSize: 20,\n }}\n >\n Halo\n <span className=\"tf-brand-dot\" />\n </span>\n <div\n className=\"tf-col\"\n style={{\n gap: 18,\n maxWidth: 380,\n alignSelf: \"center\",\n width: \"100%\",\n }}\n >\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n <h1\n className=\"tf-header-title\"\n style={{\n fontSize: 34,\n lineHeight: \"40px\",\n }}\n >\n Welcome back\n </h1>\n <p\n className=\"tf-header-sub\"\n style={{\n fontSize: 14,\n }}\n >\n Sign in to pick up where your team left off.\n </p>\n </div>\n <Stagger\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n delay={160}\n >\n <Button variant=\"secondary\" icon=\"globe\">\n Continue with Google\n </Button>\n <div\n className=\"tf-row\"\n style={{\n gap: 12,\n color: \"var(--ink-muted)\",\n fontSize: 12,\n fontWeight: \"var(--type-label-weight)\",\n }}\n >\n <span\n style={{\n flex: 1,\n height: 1,\n background: \"var(--line)\",\n }}\n />\n or with email\n <span\n style={{\n flex: 1,\n height: 1,\n background: \"var(--line)\",\n }}\n />\n </div>\n <Input label=\"Work email\" placeholder=\"you@company.com\" />\n <Input label=\"Password\" type=\"password\" placeholder=\"••••••••••\" />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <Checkbox defaultChecked>Keep me signed in</Checkbox>\n <a\n href=\"#\"\n style={{\n font: \"var(--type-label-weight) 12px var(--font-sans)\",\n color: \"var(--ink)\",\n }}\n >\n Forgot password?\n </a>\n </div>\n <Button iconRight=\"arrowUpRight\">Sign in</Button>\n </Stagger>\n </div>\n <span\n className=\"tf-card-desc tf-in\"\n style={\n {\n \"--d\": \"500ms\",\n } as React.CSSProperties\n }\n >\n {\"New to Halo? \"}\n <a\n href=\"#\"\n style={{\n color: \"var(--ink)\",\n fontWeight: \"var(--type-title-weight)\",\n }}\n >\n Create a workspace\n </a>\n </span>\n </div>\n <div\n style={{\n position: \"relative\",\n borderRadius: \"var(--radius-lg)\",\n overflow: \"hidden\",\n padding: \"clamp(20px, 5vw, 40px)\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n gap: 16,\n }}\n >\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n \"--cols-sm\": \"1fr 1fr\",\n gap: 16,\n marginBottom: \"auto\",\n } as React.CSSProperties\n }\n delay={200}\n >\n <StatTile tint=\"sky\" value=\"1,284\" label=\"Members this month\" />\n <StatTile tint=\"butter\" value=\"€48,210\" label=\"Collected\" />\n <div\n className=\"tf-card tf-bg-lilac\"\n style={{\n gridColumn: \"span 2\",\n minHeight: 0,\n }}\n >\n <span\n className=\"tf-card-title\"\n style={{\n padding: 0,\n }}\n >\n Pricing page refresh\n </span>\n <Progress value={64} label=\"Progress\" />\n <div\n className=\"tf-row\"\n style={{\n justifyContent: \"space-between\",\n }}\n >\n <Badge tone=\"success\" live>\n On track\n </Badge>\n <AvatarStack names={[\"Mara Ellis\", \"Jonah Petit\", \"Sun Lee\"]} size={26} />\n </div>\n </div>\n <EventCard kind=\"meeting\" label=\"Team sync\" date=\"Tue\" time=\"10:30\" actions={false}>\n Roadmap check-in.\n </EventCard>\n <EventCard kind=\"launch\" label=\"Launch\" date=\"Fri\" time=\"17:00\" actions={false}>\n Annual plans go live.\n </EventCard>\n </Stagger>\n <div\n className=\"tf-in\"\n style={\n {\n \"--d\": \"700ms\",\n color: \"var(--on-frame)\",\n } as React.CSSProperties\n }\n >\n <div\n style={{\n font: \"var(--type-title-weight) 26px/32px var(--font-sans)\",\n letterSpacing: \"-0.015em\",\n }}\n >\n Every decision your team is waiting on, in one calm place.\n </div>\n <div\n style={{\n color: \"var(--on-frame-muted)\",\n marginTop: 8,\n fontSize: 13,\n }}\n >\n Projects, people, billing and schedule — side by side.\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/SignInScreen"},{"name":"StatusScreen","title":"Status","group":"Pages","summary":"Full-page states for errors and interruptions: code \"404\", \"500\", \"403\", \"maintenance\", \"offline\", or your own title / text / icon / tint.","description":"Full-page states for errors and interruptions: `code` \"404\", \"500\", \"403\", \"maintenance\", \"offline\", or your own `title` / `text` / `icon` / `tint`.\n\nProps: `code`, `title`, `text`, `icon`, `tint`, `extra` (search field, progress), `actions` (defaults to Back to overview + Contact support), `onHome`, `onContact`, `reference` (request id for support).\n\n- Say what happened, that the person's data is safe, and what to do next, in that order.\n- Show a request reference on server errors so support can find it.\n- Keep the TopBar when you can (404, 403); go full screen only when the app itself is down.\n","components":["Button","StatusScreen","UsageMeter"],"code":"import * as React from \"react\";\nimport { Button, StatusScreen, UsageMeter } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <StatusScreen code=\"404\" />\n </div>\n <div className=\"tf-panel\">\n <StatusScreen code=\"500\" reference=\"req_8f2a91c4\" />\n </div>\n <div className=\"tf-panel\">\n <StatusScreen\n code=\"maintenance\"\n extra={\n <div\n style={{\n width: \"100%\",\n maxWidth: 300,\n }}\n >\n <UsageMeter label=\"Upgrade progress\" used={72} limit={100} format={(v) => v + \"%\"} />\n </div>\n }\n />\n </div>\n <div className=\"tf-panel\">\n <StatusScreen\n code=\"offline\"\n actions={[\n <Button key=\"r\" icon=\"refresh\">\n Try again\n </Button>,\n ]}\n />\n </div>\n </div>\n );\n}\n","path":"patterns/StatusScreen"},{"name":"StorefrontScreen","title":"Storefront","group":"Pages","summary":"A complete screen built from Breadcrumb, Button, IconButton, Price and 6 more components.","description":"","components":["Breadcrumb","Button","Icon","IconButton","Price","ProductArt","Rating","StockBadge","SubNav","TopBar","VariantPicker"],"code":"import * as React from \"react\";\nimport {\n Breadcrumb,\n Button,\n IconButton,\n Price,\n ProductArt,\n Rating,\n StockBadge,\n VariantPicker,\n} from \"@twentyfirstmedia/ui-core\";\n\nfunction App() {\n return (\n <div\n style={{\n borderRadius: \"var(--radius-xl)\",\n background: \"var(--canvas)\",\n overflow: \"hidden\",\n boxShadow: \"inset 0 0 0 1px var(--line)\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"16px 24px\",\n background: \"var(--surface)\",\n borderBottom: \"1px solid var(--line)\",\n }}\n >\n <b\n style={{\n font: \"var(--type-title-weight) 20px/26px var(--font-sans)\",\n }}\n >\n Halo Goods\n </b>\n <div className=\"tf-row\">\n <IconButton icon=\"heart\" label=\"Saved\" />\n <Button icon=\"cart\">Cart</Button>\n </div>\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 1.1fr) minmax(0, 1fr)\",\n gap: 32,\n padding: 24,\n } as React.CSSProperties\n }\n >\n <div className=\"tf-col\">\n <ProductArt kind=\"tee\" tint=\"sky\" />\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(4, 1fr)\",\n \"--cols-sm\": \"repeat(4, minmax(0, 1fr))\",\n \"--cols-md\": \"repeat(2, minmax(0, 1fr))\",\n gap: 12,\n } as React.CSSProperties\n }\n >\n {([\"sky\", \"mint\", \"butter\", \"lilac\"] as const).map((t) => (\n <ProductArt key={t} kind=\"tee\" tint={t} />\n ))}\n </div>\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 20,\n }}\n >\n <Breadcrumb items={[\"Shop\", \"Apparel\", \"Halo Cotton Tee\"]} />\n <h1\n style={{\n margin: 0,\n font: \"var(--type-title-weight) 28px/34px var(--font-sans)\",\n }}\n >\n Halo Cotton Tee\n </h1>\n <Rating value={4.7} count={128} />\n <Price value={32} compareAt={39} size=\"lg\" />\n <VariantPicker\n label=\"Colour\"\n type=\"color\"\n defaultValue=\"Sky\"\n options={[\n {\n value: \"Sky\",\n label: \"Sky\",\n color: \"#9fc3f0\",\n },\n {\n value: \"Ink\",\n label: \"Ink\",\n color: \"#1d1d1f\",\n },\n {\n value: \"Bone\",\n label: \"Bone\",\n color: \"#ece6d9\",\n },\n ]}\n />\n <VariantPicker\n label=\"Size\"\n defaultValue=\"M\"\n options={[\n {\n value: \"XS\",\n label: \"XS\",\n disabled: true,\n },\n \"S\",\n \"M\",\n \"L\",\n \"XL\",\n ]}\n />\n <StockBadge stock={84} />\n <Button icon=\"cart\">Add to cart · €32</Button>\n </div>\n </div>\n </div>\n );\n}\nexport default App;\n","path":"patterns/StorefrontScreen"},{"name":"WellbeingScreen","title":"Wellbeing","group":"Pages","summary":"A wellbeing dashboard in glass: sleep, streak and steps as tinted glass tiles, last nights' sleep, today's goal, mindful minutes, mood and habits, and a journal prompt, over the kit's ambient colours.","description":"A wellbeing dashboard in glass: sleep, streak and steps as tinted glass tiles, last nights' sleep, today's goal, mindful minutes, mood and habits, and a journal prompt, over the kit's ambient colours.\n\n- The whole app shell is glass: top bar, rail, panels, tiles and dock float over `Ambient`.\n- Built from `UIProvider surface=\"glass\"`, `Ambient`, `AppShell`, `StatTile`, `BarChart`, `ProgressRing`, `AreaChart`, `Segmented` and `Checkbox`.\n","components":["Ambient","AppShell","AreaChart","BarChart","Button","Checkbox","PageHeader","Panel","ProgressRing","Segmented","Stagger","StatTile","Textarea","UIProvider"],"code":"import * as React from \"react\";\nimport {\n Ambient,\n AppShell,\n AreaChart,\n BarChart,\n Button,\n Checkbox,\n PageHeader,\n Panel,\n ProgressRing,\n Segmented,\n Stagger,\n StatTile,\n Textarea,\n UIProvider,\n} from \"@twentyfirstmedia/ui-core\";\n\n/** A wellbeing dashboard in glass over the kit's ambient colours. */\nfunction App() {\n return (\n <UIProvider scope=\"subtree\" surface=\"glass\">\n <Ambient />\n <AppShell\n brand=\"Bloom\"\n tag=\"Daily\"\n active=\"Today\"\n user={{ name: \"Mara Ellis\", email: \"mara@bloom.app\" }}\n nav={[\n { label: \"Today\", icon: \"sun\" },\n { label: \"Sleep\", icon: \"moon\" },\n { label: \"Habits\", icon: \"check\" },\n { label: \"Journal\", icon: \"book\" },\n ]}\n railTop={[\n { label: \"Today\", icon: \"home\", active: true },\n { label: \"Trends\", icon: \"trend\" },\n { label: \"Reminders\", icon: \"bell\", count: 2 },\n ]}\n railBottom={[{ label: \"Settings\", icon: \"settings\" }]}\n dockItems={[\n { icon: \"heart\", label: \"Log mood\", tint: \"blush\" },\n { icon: \"moon\", label: \"Log sleep\", tint: \"sky\" },\n { icon: \"zap\", label: \"Start a workout\", tint: \"butter\" },\n { icon: \"sparkle\", label: \"Breathe\", tint: \"lilac\" },\n ]}\n side={\n <Panel side title=\"Journal\" subtitle=\"One line is enough\">\n <Textarea\n label=\"What are you grateful for today?\"\n rows={4}\n defaultValue=\"Coffee on the balcony before everyone woke up.\"\n />\n <Button variant=\"primary\" icon=\"check\">\n Save entry\n </Button>\n </Panel>\n }\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Good morning, Mara\"\n subtitle=\"Thursday · You slept 7 h 42 min\"\n actions={[\n <Button key=\"log\" variant=\"secondary\" icon=\"plus\">\n Log activity\n </Button>,\n ]}\n />\n </div>\n <Stagger\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n \"--cols-sm\": \"1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <StatTile value=\"7h 42m\" label=\"Sleep\" tint=\"sky\" delta=\"+18 min\" />\n <StatTile value=\"12 days\" label=\"Streak\" tint=\"butter\" delta=\"Best 21\" />\n <StatTile value=\"8,240\" label=\"Steps\" tint=\"mint\" delta=\"82% of goal\" />\n </Stagger>\n <Stagger\n className=\"tf-grid\"\n delay={200}\n style={\n {\n \"--cols\": \"minmax(0, 3fr) minmax(0, 2fr)\",\n \"--cols-sm\": \"1fr\",\n gap: 16,\n marginTop: 16,\n } as React.CSSProperties\n }\n >\n <Panel title=\"Sleep\" subtitle=\"Last 7 nights, in hours\">\n <BarChart\n label=\"Hours of sleep\"\n height={150}\n data={[\n { label: \"Fri\", value: 6.8, tint: \"sky\" },\n { label: \"Sat\", value: 8.1, tint: \"sky\" },\n { label: \"Sun\", value: 7.9, tint: \"sky\" },\n { label: \"Mon\", value: 6.4, tint: \"lilac\" },\n { label: \"Tue\", value: 7.2, tint: \"sky\" },\n { label: \"Wed\", value: 7.5, tint: \"sky\" },\n { label: \"Thu\", value: 7.7, tint: \"mint\" },\n ]}\n />\n </Panel>\n <Panel title=\"Today's goal\" subtitle=\"Move, breathe, reflect\">\n <div style={{ display: \"grid\", placeItems: \"center\", padding: \"8px 0\" }}>\n <ProgressRing\n value={72}\n tint=\"mint\"\n caption=\"of today\"\n label=\"Daily goal\"\n size={132}\n />\n </div>\n </Panel>\n <Panel title=\"Mindful minutes\" subtitle=\"This week\">\n <AreaChart\n label=\"Mindful minutes\"\n labels={[\"Fri\", \"Sat\", \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\"]}\n series={[{ name: \"Minutes\", values: [6, 12, 15, 4, 10, 14, 18], tint: \"lilac\" }]}\n height={130}\n />\n </Panel>\n <Panel title=\"How do you feel?\" subtitle=\"3 of 4 habits done\">\n <Segmented\n label=\"Mood\"\n block\n defaultValue=\"Calm\"\n options={[\"Tired\", \"Calm\", \"Great\"]}\n />\n <Checkbox defaultChecked>Drink 2 l of water</Checkbox>\n <Checkbox defaultChecked>10 minutes outside</Checkbox>\n <Checkbox defaultChecked>Stretch after lunch</Checkbox>\n <Checkbox>Phone away at 22:00</Checkbox>\n </Panel>\n </Stagger>\n </AppShell>\n </UIProvider>\n );\n}\n\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/WellbeingScreen"},{"name":"WorkflowScreen","title":"Workflow","group":"Pages","summary":"A complete screen built from Badge, Button, Flow, IconButton and 6 more components.","description":"","components":["Badge","Button","Flow","Icon","IconButton","ListRow","PageHeader","Stagger","SubNav","TintIcon","TopBar"],"code":"import * as React from \"react\";\nimport {\n Badge,\n Button,\n Flow,\n IconButton,\n ListRow,\n PageHeader,\n Stagger,\n TintIcon,\n} from \"@twentyfirstmedia/ui-core\";\nimport { Shell } from \"./_workspace\";\n\nfunction App() {\n const run = React.useState<number | null>(null);\n React.useEffect(() => {\n if (run[0] == null || run[0] === 3 || run[0] >= 5) return;\n const t = setTimeout(() => {\n run[1]((run[0] ?? 0) + 1);\n }, 800);\n return () => {\n clearTimeout(t);\n };\n }, [run[0]]);\n return (\n <Shell\n area=\"Agents\"\n current=\"workflows\"\n action={{\n label: \"New agent\",\n icon: \"plus\",\n }}\n pages={[\n {\n id: \"fleet\",\n label: \"Fleet\",\n icon: \"bot\",\n },\n {\n id: \"chat\",\n label: \"Chat\",\n icon: \"mail\",\n },\n {\n id: \"workflows\",\n label: \"Workflows\",\n icon: \"zap\",\n },\n {\n id: \"runs\",\n label: \"Runs\",\n icon: \"refresh\",\n count: 2,\n },\n {\n id: \"knowledge\",\n label: \"Knowledge\",\n icon: \"book\",\n },\n {\n id: \"usage\",\n label: \"Usage & cost\",\n icon: \"chart\",\n },\n ]}\n >\n <div className=\"tf-in\">\n <PageHeader\n title=\"Workflows\"\n subtitle=\"Chain agents, approvals and actions\"\n actions={[\n <Button key=\"n\" icon=\"plus\">\n New workflow\n </Button>,\n ]}\n />\n </div>\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"minmax(0, 300px) minmax(0, 1fr)\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel tf-panel-flush tf-in\"\n style={\n {\n \"--d\": \"80ms\",\n } as React.CSSProperties\n }\n >\n <Stagger className=\"tf-list\">\n <ListRow\n active\n leading={<TintIcon icon=\"zap\" tint=\"mint\" />}\n title=\"Support replies\"\n subtitle=\"5 steps · 214 runs\"\n trailing={<Badge tone=\"success\">On</Badge>}\n />\n <ListRow\n leading={<TintIcon icon=\"zap\" tint=\"mint\" />}\n title=\"Receipt to booking\"\n subtitle=\"5 steps · 96 runs\"\n trailing={<Badge tone=\"success\">On</Badge>}\n />\n <ListRow\n leading={<TintIcon icon=\"zap\" tint=\"plain\" />}\n title=\"Lead research\"\n subtitle=\"3 steps · 37 runs\"\n trailing={<Badge>Off</Badge>}\n />\n </Stagger>\n </div>\n <div\n className=\"tf-panel tf-in\"\n style={\n {\n \"--d\": \"160ms\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-header\"\n style={{\n alignItems: \"center\",\n }}\n >\n <h2 className=\"tf-panel-title\">Support replies</h2>\n <Button\n variant=\"soft\"\n icon=\"play\"\n disabled={run[0] != null && run[0] < 5}\n onClick={() => {\n run[1](0);\n }}\n >\n {run[0] != null && run[0] < 5 ? \"Running…\" : \"Test run\"}\n </Button>\n </div>\n <Flow\n active={run[0]}\n waiting={run[0] === 3}\n onApprove={() => {\n run[1](4);\n }}\n onReject={() => {\n run[1](null);\n }}\n tools={() => [\n <IconButton key=\"e\" icon=\"edit\" label=\"Edit step\" size=\"sm\" />,\n <IconButton key=\"t\" icon=\"trash\" label=\"Remove step\" size=\"sm\" />,\n ]}\n steps={[\n {\n kind: \"trigger\",\n text: \"New email arrives in Inbox\",\n },\n {\n kind: \"condition\",\n text: \"Label is Customers\",\n },\n {\n kind: \"agent\",\n text: \"Atlas drafts a reply from the knowledge base\",\n },\n {\n kind: \"approval\",\n text: \"Mara approves the draft\",\n },\n {\n kind: \"action\",\n text: \"Send the reply and log it on the company\",\n },\n ]}\n />\n </div>\n </div>\n </Shell>\n );\n}\nexport default function Preview() {\n return <App />;\n}\n","path":"patterns/WorkflowScreen"},{"name":"real-estate","title":"Real estate","group":"Pages","summary":"A listing search page: results toolbar with bulk actions, listings as cards or rows, a map with price pins, valuation, energy class and the photo gallery.","description":"A listing search page: results toolbar with bulk actions, listings as cards or rows, a map with price pins, valuation, energy class and the photo gallery.","components":["ListingCard","ResultsToolbar","IconButton","MapView","EnergyClass","SavedSearch","ValuationCard","BeforeAfter","Gallery","ImageVariants"],"code":"// ── ListingCard ──\nimport * as React from \"react\";\nimport { ListingCard, ResultsToolbar } from \"@twentyfirstmedia/ui-core\";\nimport type { Listing, StatusSelectProps } from \"@twentyfirstmedia/ui-core\";\n\nconst st: StatusSelectProps[\"options\"] = [\n {\n value: \"unread\",\n label: \"New\",\n status: \"running\",\n },\n {\n value: \"read\",\n label: \"Seen\",\n status: \"idle\",\n },\n {\n value: \"contacted\",\n label: \"Contacted\",\n status: \"ok\",\n },\n];\nconst L0: Listing[] = [\n {\n id: 1,\n title: \"Bright 3-room flat with balcony near Isebek canal\",\n price: 649000,\n pricePerSqm: 7726,\n area: 84,\n rooms: 3,\n place: \"Eppendorf, Hamburg\",\n source: \"ImmoScout\",\n seller: \"private\",\n phone: true,\n photos: 14,\n isNew: true,\n age: \"2 h\",\n status: \"unread\",\n tint: \"sky\",\n },\n {\n id: 2,\n title: \"Loft in HafenCity with water view\",\n price: 1190000,\n pricePerSqm: 10625,\n area: 112,\n rooms: 2.5,\n place: \"HafenCity, Hamburg\",\n source: \"Kleinanzeigen\",\n seller: \"agent\",\n photos: 22,\n age: \"1 d\",\n status: \"contacted\",\n tint: \"lilac\",\n wished: true,\n },\n {\n id: 3,\n title: \"2-room flat, renovated old building\",\n price: 1480,\n rent: true,\n area: 58,\n rooms: 2,\n place: \"Altona, Hamburg\",\n source: \"Immowelt\",\n seller: \"agent\",\n photos: 9,\n age: \"3 d\",\n status: \"read\",\n tint: \"mint\",\n },\n];\nfunction App() {\n const sel = React.useState<(string | number)[]>([1]),\n list = React.useState(L0),\n view = React.useState(\"grid\"),\n sort = React.useState(\"new\");\n function tog(x: Listing) {\n sel[1](sel[0].indexOf(x.id) > -1 ? sel[0].filter((i) => i !== x.id) : sel[0].concat([x.id]));\n }\n return (\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <ResultsToolbar\n title=\"Hamburg · flats to buy\"\n total={128}\n meta=\"updated 06:00\"\n selected={sel[0].length}\n onSelectAll={(all) => {\n sel[1](all ? list[0].map((x) => x.id) : []);\n }}\n bulkActions={[\n {\n label: \"Send to CRM\",\n icon: \"send\",\n variant: \"primary\",\n },\n {\n label: \"Mark as seen\",\n icon: \"eye\",\n },\n ]}\n sortOptions={[\n {\n value: \"new\",\n label: \"Newest\",\n },\n {\n value: \"price\",\n label: \"Price\",\n },\n {\n value: \"sqm\",\n label: \"Price per m²\",\n },\n ]}\n sort={sort[0]}\n onSort={sort[1]}\n views={[\"grid\", \"list\", \"map\"]}\n view={view[0]}\n onView={view[1]}\n />\n <div\n className=\"tf-grid\"\n style={\n view[0] === \"list\"\n ? {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 12,\n }\n : ({\n \"--cols\": \"repeat(3, minmax(0, 1fr))\",\n gap: 16,\n } as React.CSSProperties)\n }\n >\n {list[0].map((x) => (\n <ListingCard\n key={x.id}\n listing={x}\n layout={view[0] === \"list\" ? \"row\" : \"card\"}\n selected={sel[0].indexOf(x.id) > -1}\n onSelect={tog}\n onWish={() => {}}\n statusOptions={st}\n onStatus={(l, s) => {\n list[1](\n list[0].map((y) =>\n y.id === l.id\n ? Object.assign({}, y, {\n status: s,\n })\n : y,\n ),\n );\n }}\n />\n ))}\n </div>\n </div>\n );\n}\nexport default App;\n\n// ── MapView ──\nimport * as React from \"react\";\nimport { IconButton, MapView } from \"@twentyfirstmedia/ui-core\";\nimport type { MapPoint } from \"@twentyfirstmedia/ui-core\";\n\nconst world: MapPoint[] = [\n {\n id: \"de\",\n label: \"Germany\",\n lat: 51,\n lng: 10,\n value: 820,\n sub: \"820 orders today\",\n },\n {\n id: \"at\",\n label: \"Austria\",\n lat: 47.5,\n lng: 14.5,\n value: 260,\n tint: \"mint\",\n },\n {\n id: \"ch\",\n label: \"Switzerland\",\n lat: 46.8,\n lng: 8.2,\n value: 210,\n tint: \"mint\",\n },\n {\n id: \"us\",\n label: \"United States\",\n lat: 39,\n lng: -98,\n value: 140,\n tint: \"butter\",\n },\n {\n id: \"ae\",\n label: \"UAE\",\n lat: 24,\n lng: 54,\n value: 90,\n tint: \"lilac\",\n pulse: true,\n },\n {\n id: \"br\",\n label: \"Brazil\",\n lat: -12,\n lng: -52,\n value: 60,\n tint: \"blush\",\n },\n];\nconst city: MapPoint[] = [\n {\n id: 1,\n label: \"3-room flat, Eppendorf\",\n sub: \"€649,000 · 84 m²\",\n lat: 53.59,\n lng: 9.98,\n text: \"€649k\",\n },\n {\n id: 2,\n label: \"Loft, HafenCity\",\n sub: \"€1,190,000 · 112 m²\",\n lat: 53.541,\n lng: 10.0,\n text: \"€1.19m\",\n tint: \"lilac\",\n },\n {\n id: 3,\n label: \"2-room flat, Altona\",\n sub: \"€1,480 / month\",\n lat: 53.55,\n lng: 9.935,\n text: \"€1,480\",\n tint: \"mint\",\n },\n {\n id: 4,\n label: \"House, Blankenese\",\n lat: 53.565,\n lng: 9.81,\n text: \"€2.4m\",\n tint: \"butter\",\n },\n];\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Orders by country</b>\n <MapView bubbles points={world} height={360} legend=\"Bubble size = orders today\" />\n </div>\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Listings</b>\n <MapView\n points={city}\n bounds={{\n n: 53.62,\n s: 53.52,\n w: 9.78,\n e: 10.06,\n }}\n height={360}\n selected={2}\n controls={[\n <IconButton key={1} icon=\"plus\" label=\"Zoom in\" size=\"sm\" />,\n <IconButton key={2} icon=\"minus\" label=\"Zoom out\" size=\"sm\" />,\n ]}\n />\n </div>\n </div>\n );\n}\n\n// ── ValuationCard ──\nimport * as React from \"react\";\nimport { EnergyClass, SavedSearch, ValuationCard } from \"@twentyfirstmedia/ui-core\";\n\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div className=\"tf-panel\">\n <ValuationCard\n value={612000}\n low={571000}\n high={655000}\n asking={649000}\n perSqm={7286}\n compare=\"district average €6,940\"\n confidence={0.78}\n facts={[\n [\"Comparables\", \"42 sales, last 18 months\"],\n [\"Method\", \"Comparable sales\"],\n [\"As of\", \"Nov 8, 2026\"],\n ]}\n note=\"An estimate, not an appraisal.\"\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <SavedSearch\n name=\"Eppendorf 3 rooms\"\n criteria={[\n \"Buy\",\n \"Hamburg-Eppendorf\",\n \"€400k–700k\",\n \"70–110 m²\",\n \"3+ rooms\",\n \"Private sellers\",\n ]}\n lastRun=\"06:00\"\n frequency=\"Daily mail\"\n newCount={4}\n onToggle={() => {}}\n onEdit={() => {}}\n onOpen={() => {}}\n />\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Energy</b>\n <EnergyClass value=\"C\" kwh={92} kind=\"Consumption · gas\" />\n </div>\n </div>\n </div>\n );\n}\n\n// ── Gallery ──\nimport * as React from \"react\";\nimport { BeforeAfter, Gallery, ImageVariants } from \"@twentyfirstmedia/ui-core\";\nimport type { Tint } from \"@twentyfirstmedia/ui-core\";\n\nconst art = (t: Tint, s: string) => (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(160deg, var(--\" + t + \") 0%, var(--surface) 120%)\",\n display: \"grid\",\n placeItems: \"center\",\n color: \"var(--\" + t + \"-ink)\",\n font: \"var(--type-title-weight) 14px/1 var(--font-sans)\",\n }}\n >\n {s}\n </div>\n);\nconst before = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #ddd8cf 0 62%, #b9ad9c 62%)\",\n }}\n />\n ),\n};\nconst after = {\n art: (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n background: \"linear-gradient(180deg, #e9e2d6 0 62%, #a68b6a 62%)\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n left: \"18%\",\n bottom: \"22%\",\n width: \"38%\",\n height: \"22%\",\n borderRadius: \"var(--radius-md)\",\n background: \"#5b6b5a\",\n }}\n />\n <div\n style={{\n position: \"absolute\",\n right: \"16%\",\n bottom: \"38%\",\n width: \"14%\",\n height: \"30%\",\n borderRadius: \"var(--radius-sm)\",\n background: \"#3d3a36\",\n }}\n />\n </div>\n ),\n};\nexport default function Preview() {\n return (\n <div\n className=\"tf-grid\"\n style={\n {\n \"--cols\": \"1fr 1fr\",\n gap: 16,\n alignItems: \"start\",\n } as React.CSSProperties\n }\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <Gallery\n badge=\"Virtual staging\"\n items={[\n {\n art: art(\"sky\", \"Living room\"),\n caption: \"Living room\",\n },\n {\n art: art(\"mint\", \"Kitchen\"),\n },\n {\n art: art(\"butter\", \"Bedroom\"),\n },\n {\n art: art(\"lilac\", \"Balcony\"),\n },\n {\n art: art(\"blush\", \"Bathroom\"),\n },\n ]}\n />\n </div>\n <div\n className=\"tf-col\"\n style={{\n gap: 16,\n }}\n >\n <div\n className=\"tf-panel\"\n style={{\n gap: 12,\n }}\n >\n <b>Virtual staging</b>\n <BeforeAfter before={before} after={after} afterLabel=\"Staged · Scandinavian\" />\n </div>\n <div className=\"tf-panel\">\n <ImageVariants\n tint=\"sky\"\n variants={[\n {\n id: 1,\n label: \"Original\",\n size: \"4032 × 3024\",\n format: \"JPEG\",\n weight: \"3.8 MB\",\n ratio: \"4 / 3\",\n },\n {\n id: 2,\n label: \"Listing\",\n size: \"1600 × 1200\",\n format: \"WebP\",\n weight: \"214 KB\",\n ratio: \"4 / 3\",\n status: \"ok\",\n },\n {\n id: 3,\n label: \"Square\",\n size: \"1080 × 1080\",\n format: \"WebP\",\n weight: \"168 KB\",\n ratio: \"1 / 1\",\n status: \"ok\",\n },\n {\n id: 4,\n label: \"Upscaled 2×\",\n size: \"8064 × 6048\",\n format: \"PNG\",\n status: \"running\",\n ratio: \"4 / 3\",\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n}","path":"patterns/real-estate"}],"props":{"Base":[{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":""}],"Icon":[{"name":"name","type":"IconName","optional":false,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Button":[{"name":"variant","type":"\"primary\" | \"secondary\" | \"soft\" | \"ghost\" | \"danger\" | \"danger-solid\"","optional":true,"doc":""},{"name":"size","type":"\"md\" | \"sm\"","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"iconRight","type":"IconName","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"type","type":"\"button\" | \"submit\" | \"reset\"","optional":true,"doc":""},{"name":"onClick","type":"(e: MouseEvent<HTMLButtonElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"asChild","type":"boolean","optional":true,"doc":"Render the single child element (a link, a router Link) with the button's look instead of a <button>."},{"name":"id","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Form field name and value, sent with the form when this button submits it."},{"name":"value","type":"string","optional":true,"doc":""},{"name":"form","type":"string","optional":true,"doc":"The id of the form this button belongs to, when it sits outside it."},{"name":"title","type":"string","optional":true,"doc":""},{"name":"tabIndex","type":"number","optional":true,"doc":""},{"name":"autoFocus","type":"boolean","optional":true,"doc":""},{"name":"aria-label","type":"string","optional":true,"doc":""},{"name":"aria-labelledby","type":"string","optional":true,"doc":""},{"name":"aria-describedby","type":"string","optional":true,"doc":""},{"name":"aria-expanded","type":"boolean | \"true\" | \"false\"","optional":true,"doc":"For disclosure and menu triggers: whether what the button controls is open."},{"name":"aria-controls","type":"string","optional":true,"doc":""},{"name":"aria-haspopup","type":"boolean | \"true\" | \"false\" | \"menu\" | \"listbox\" | \"tree\" | \"grid\" | \"dialog\"","optional":true,"doc":""},{"name":"aria-pressed","type":"boolean | \"true\" | \"false\" | \"mixed\"","optional":true,"doc":"For toggle buttons."},{"name":"aria-current","type":"boolean | \"true\" | \"false\" | \"page\" | \"step\" | \"location\" | \"date\" | \"time\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"IconButton":[{"name":"icon","type":"IconName","optional":false,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name and tooltip (`aria-label` and `title`); `aria-label` or `title` override them."},{"name":"variant","type":"\"soft\" | \"solid\" | \"outline\" | \"ghost\"","optional":true,"doc":""},{"name":"size","type":"\"sm\" | \"md\" | \"lg\"","optional":true,"doc":""},{"name":"type","type":"\"button\" | \"submit\" | \"reset\"","optional":true,"doc":""},{"name":"onClick","type":"(e: MouseEvent<HTMLButtonElement>) => void","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"asChild","type":"boolean","optional":true,"doc":"Render the single child element (a link) with the button's look; the icon is placed inside it."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"id","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Form field name and value, sent with the form when this button submits it."},{"name":"value","type":"string","optional":true,"doc":""},{"name":"form","type":"string","optional":true,"doc":"The id of the form this button belongs to, when it sits outside it."},{"name":"title","type":"string","optional":true,"doc":""},{"name":"tabIndex","type":"number","optional":true,"doc":""},{"name":"autoFocus","type":"boolean","optional":true,"doc":""},{"name":"aria-label","type":"string","optional":true,"doc":""},{"name":"aria-labelledby","type":"string","optional":true,"doc":""},{"name":"aria-describedby","type":"string","optional":true,"doc":""},{"name":"aria-expanded","type":"boolean | \"true\" | \"false\"","optional":true,"doc":"For disclosure and menu triggers: whether what the button controls is open."},{"name":"aria-controls","type":"string","optional":true,"doc":""},{"name":"aria-haspopup","type":"boolean | \"true\" | \"false\" | \"menu\" | \"listbox\" | \"tree\" | \"grid\" | \"dialog\"","optional":true,"doc":""},{"name":"aria-pressed","type":"boolean | \"true\" | \"false\" | \"mixed\"","optional":true,"doc":"For toggle buttons."},{"name":"aria-current","type":"boolean | \"true\" | \"false\" | \"page\" | \"step\" | \"location\" | \"date\" | \"time\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Chip":[{"name":"selected","type":"boolean","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"count","type":"number","optional":true,"doc":""},{"name":"onClick","type":"(e: MouseEvent<HTMLButtonElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Badge":[{"name":"tone","type":"Tone","optional":true,"doc":""},{"name":"dot","type":"boolean","optional":true,"doc":""},{"name":"live","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TintIcon":[{"name":"icon","type":"IconName","optional":false,"doc":""},{"name":"tint","type":"Tint | \"plain\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Avatar":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"src","type":"string","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"tint","type":"Tint | \"surface-sunken\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AvatarStack":[{"name":"names","type":"string[]","optional":false,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Field":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Form field name: the value's key in `Form`, and where its validation message shows."},{"name":"id","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Input":[{"name":"type","type":"string","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Select":[{"name":"options","type":"string[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLSelectElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Textarea":[{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"rows","type":"number","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLTextAreaElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SearchField":[{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name (default: the placeholder, or \"Search\")."},{"name":"shortcut","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Checkbox":[{"name":"name","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"checked","type":"boolean","optional":true,"doc":""},{"name":"defaultChecked","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Switch":[{"name":"name","type":"string","optional":true,"doc":""},{"name":"checked","type":"boolean","optional":true,"doc":""},{"name":"defaultChecked","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"spread","type":"boolean","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Tabs":[{"name":"items","type":"string[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"block","type":"boolean","optional":true,"doc":""},{"name":"panels","type":"Record<string, ReactNode>","optional":true,"doc":"Content per tab; renders the tab panels under the bar, wired with tab/tabpanel roles."},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the tab list."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatTile":[{"name":"value","type":"ReactNode","optional":false,"doc":""},{"name":"label","type":"string","optional":false,"doc":""},{"name":"tint","type":"Tint | \"surface\" | \"placeholder\"","optional":true,"doc":""},{"name":"delta","type":"string","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Card":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"description","type":"string","optional":true,"doc":""},{"name":"meta","type":"string[]","optional":true,"doc":""},{"name":"corner","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"tint","type":"Tint | \"surface\"","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"eyebrow","type":"ReactNode","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: translucent glass over what is behind (maps, 3D, video, photos); `\"solid\"` opts out inside a glass `UIProvider`."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EventCard":[{"name":"kind","type":"\"meeting\" | \"deadline\" | \"review\" | \"launch\" | \"incident\"","optional":true,"doc":""},{"name":"label","type":"string","optional":false,"doc":""},{"name":"date","type":"string","optional":false,"doc":""},{"name":"time","type":"string","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"actions","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Panel":[{"name":"side","type":"boolean","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"subtitle","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: translucent glass over what is behind (maps, 3D, video, photos); `\"solid\"` opts out inside a glass `UIProvider`."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PageHeader":[{"name":"crumbs","type":"string[]","optional":true,"doc":""},{"name":"title","type":"string","optional":false,"doc":""},{"name":"subtitle","type":"string","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TopBar":[{"name":"brand","type":"string","optional":true,"doc":""},{"name":"tag","type":"string","optional":true,"doc":""},{"name":"items","type":"NavItem[]","optional":false,"doc":""},{"name":"active","type":"string","optional":true,"doc":""},{"name":"user","type":"{ name: string; email?: string }","optional":true,"doc":""},{"name":"onNavigate","type":"(label: string) => void","optional":true,"doc":""},{"name":"extra","type":"ReactNode","optional":true,"doc":""},{"name":"userOpen","type":"boolean","optional":true,"doc":""},{"name":"onUser","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SideRail":[{"name":"top","type":"RailItem[]","optional":true,"doc":""},{"name":"bottom","type":"RailItem[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FloatingLayout":[{"name":"background","type":"ReactNode","optional":true,"doc":"What the UI floats over, filling the layout: a map, a 3D canvas, a video, a photo."},{"name":"top","type":"ReactNode","optional":true,"doc":"Bar along the top edge (search, filters, title)."},{"name":"start","type":"ReactNode","optional":true,"doc":"Column on the start side (layers, lists, settings)."},{"name":"end","type":"ReactNode","optional":true,"doc":"Column on the end side (details of the selected thing)."},{"name":"bottom","type":"ReactNode","optional":true,"doc":"Bar along the bottom edge (dock, playback, a summary)."},{"name":"height","type":"number | string","optional":true,"doc":"Height of the layout (default 640). Below 720px of width the slots stack: top, start, end, bottom."},{"name":"surface","type":"Surface","optional":true,"doc":"Glass for every surface in the slots (default) or solid surfaces."},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the region."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Ambient":[{"name":"colors","type":"string[]","optional":true,"doc":"Colours of the soft fields (any CSS colour); default the kit's `--ambient-1…4`."},{"name":"src","type":"string","optional":true,"doc":"A photo (or, with `video`, a video) instead of the fields, under a scrim that keeps it calm."},{"name":"video","type":"boolean","optional":true,"doc":"Treat `src` as a video (muted, looping, still with motion off)."},{"name":"animate","type":"boolean","optional":true,"doc":"Let the fields drift slowly (default true). Still with motion off; paused off screen."},{"name":"fixed","type":"boolean","optional":true,"doc":"Cover the window behind the page instead of the nearest container."},{"name":"children","type":"ReactNode","optional":true,"doc":"Your own layer (a canvas, WebGL, a map) instead of fields or media."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Dock":[{"name":"items","type":"DockItem[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: translucent glass over what is behind (maps, 3D, video, photos); `\"solid\"` opts out inside a glass `UIProvider`."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Table":[{"name":"columns","type":"TableColumn[]","optional":false,"doc":""},{"name":"rows","type":"Record<string, any>[]","optional":false,"doc":""},{"name":"animate","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Progress":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"tint","type":"\"sky\" | \"mint\" | \"butter\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Toast":[{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"action","type":"string","optional":true,"doc":""},{"name":"onAction","type":"() => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EmptyState":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Breadcrumb":[{"name":"items","type":"string[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Pagination":[{"name":"total","type":"number","optional":false,"doc":"Number of pages."},{"name":"page","type":"number","optional":true,"doc":"The current page (1-based). Controlled together with `onChange`; without `onChange` it is\nonly the start page, as in earlier versions (prefer `defaultPage` for that)."},{"name":"defaultPage","type":"number","optional":true,"doc":"Start page when uncontrolled (default 1)."},{"name":"onChange","type":"(page: number) => void","optional":true,"doc":"A page was picked (previous, next or a number); receives the new 1-based page."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AppShell":[{"name":"active","type":"string","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"tag","type":"string","optional":true,"doc":""},{"name":"nav","type":"NavItem[]","optional":true,"doc":""},{"name":"user","type":"{ name: string; email?: string }","optional":true,"doc":""},{"name":"onNavigate","type":"(label: string) => void","optional":true,"doc":""},{"name":"topExtra","type":"ReactNode","optional":true,"doc":""},{"name":"sheetStyle","type":"CSSProperties","optional":true,"doc":""},{"name":"contentKey","type":"string | number","optional":true,"doc":""},{"name":"side","type":"ReactNode","optional":true,"doc":""},{"name":"dock","type":"boolean","optional":true,"doc":""},{"name":"dockItems","type":"DockProps[\"items\"]","optional":true,"doc":""},{"name":"railTop","type":"RailItem[]","optional":true,"doc":""},{"name":"railBottom","type":"RailItem[]","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Stagger":[{"name":"as","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":"Extra delay before the first child, in ms."},{"name":"reveal","type":"boolean","optional":true,"doc":"Below the fold, wait until the group scrolls into view (default true)."},{"name":"children","type":"ReactNode","optional":true,"doc":""}],"CountUp":[{"name":"value","type":"string | number","optional":false,"doc":""},{"name":"duration","type":"number","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ListRow":[{"name":"avatar","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint | \"plain\"","optional":true,"doc":""},{"name":"leading","type":"ReactNode","optional":true,"doc":""},{"name":"title","type":"ReactNode","optional":false,"doc":""},{"name":"subtitle","type":"ReactNode","optional":true,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"trailing","type":"ReactNode","optional":true,"doc":""},{"name":"unread","type":"boolean","optional":true,"doc":""},{"name":"active","type":"boolean","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Timeline":[{"name":"items","type":"{ avatar?: string; icon?: IconName; tint?: Tint | \"plain\"; text: ReactNode; time: string; }[]","optional":false,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileRow":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"meta","type":"string","optional":false,"doc":""},{"name":"kind","type":"\"pdf\" | \"fig\" | \"sheet\" | \"doc\" | \"zip\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"KanbanColumn":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"dot","type":"Tint","optional":true,"doc":""},{"name":"delay","type":"number","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"onAdd","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ProgressRing":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"stroke","type":"number","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BarChart":[{"name":"data","type":"{ label: string; value: number; tint?: Tint | \"ink\" }[]","optional":false,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"barWidth","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LineChart":[{"name":"labels","type":"string[]","optional":false,"doc":""},{"name":"series","type":"{ values: number[]; tint?: Tint | \"ink\"; area?: boolean }[]","optional":false,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Sparkline":[{"name":"values","type":"number[]","optional":false,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Donut":[{"name":"data","type":"{ label: string; value: number; tint?: Tint | \"ink\" }[]","optional":false,"doc":""},{"name":"center","type":"string","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"stroke","type":"number","optional":true,"doc":""},{"name":"legend","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Skeleton":[{"name":"variant","type":"\"text\" | \"title\" | \"circle\" | \"block\"","optional":true,"doc":""},{"name":"width","type":"number | string","optional":true,"doc":""},{"name":"height","type":"number | string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Menu":[{"name":"items","type":"(MenuItem | \"-\")[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the menu."},{"name":"autoFocus","type":"boolean","optional":true,"doc":"Focus the first item on mount (menus opened from a button or the keyboard)."},{"name":"onClose","type":"() => void","optional":true,"doc":"Called after an item runs, and on Escape, so the owner can close the menu."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Modal":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":"Optional: with `open` the component stays mounted while it animates out; without it, render it only while open."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."}],"SubNav":[{"name":"eyebrow","type":"string","optional":true,"doc":""},{"name":"action","type":"{ label: string; icon?: IconName; onClick?: () => void }","optional":true,"doc":""},{"name":"items","type":"SubNavItem[]","optional":false,"doc":""},{"name":"current","type":"string","optional":true,"doc":""},{"name":"onPick","type":"(id: string) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Drawer":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"subtitle","type":"ReactNode","optional":true,"doc":""},{"name":"avatar","type":"string","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"width","type":"number | string","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":"Optional: with `open` the component stays mounted while it animates out; without it, render it only while open."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CommandPalette":[{"name":"items","type":"PaletteItem[]","optional":false,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"loading","type":"boolean","optional":true,"doc":"Results are on their way (async search): shows a loading row."},{"name":"empty","type":"ReactNode","optional":true,"doc":"Shown when nothing matches (default: \"Nothing matches …\")."},{"name":"onQueryChange","type":"(query: string) => void","optional":true,"doc":"Called with the query as the user types, for searching on a server."},{"name":"open","type":"boolean","optional":true,"doc":"Optional: with `open` the component stays mounted while it animates out; without it, render it only while open."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"NotificationList":[{"name":"items","type":"NotificationItem[]","optional":false,"doc":""},{"name":"onOpen","type":"(n: NotificationItem) => void","optional":true,"doc":""},{"name":"onReadAll","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Flow":[{"name":"steps","type":"FlowStep[]","optional":false,"doc":""},{"name":"active","type":"number | null","optional":true,"doc":""},{"name":"waiting","type":"boolean","optional":true,"doc":""},{"name":"onApprove","type":"() => void","optional":true,"doc":""},{"name":"onReject","type":"() => void","optional":true,"doc":""},{"name":"tools","type":"(step: FlowStep, index: number) => ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"kind","type":"FileKind","optional":false,"doc":""},{"name":"size","type":"string","optional":true,"doc":""},{"name":"meta","type":"string","optional":true,"doc":""},{"name":"shared","type":"boolean","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileThumb":[{"name":"kind","type":"FileKind","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"big","type":"boolean","optional":true,"doc":""},{"name":"entries","type":"string[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Wizard":[{"name":"steps","type":"{ id?: string; label: string; icon?: IconName }[]","optional":false,"doc":""},{"name":"current","type":"number","optional":true,"doc":""},{"name":"done","type":"Record<string, boolean>","optional":true,"doc":""},{"name":"onPick","type":"(i: number) => void","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PermissionMatrix":[{"name":"roles","type":"{ name: string; people?: number; locked?: boolean; levels: (0 | 1 | 2 | 3)[] }[]","optional":false,"doc":""},{"name":"areas","type":"string[]","optional":false,"doc":""},{"name":"onChange","type":"(roleIndex: number, areaIndex: number, nextLevel: number) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"InvoicePreview":[{"name":"number","type":"string | number","optional":false,"doc":""},{"name":"from","type":"string","optional":true,"doc":""},{"name":"to","type":"string","optional":false,"doc":""},{"name":"issued","type":"string","optional":false,"doc":""},{"name":"due","type":"string","optional":false,"doc":""},{"name":"lines","type":"{ d: string; q: number; u: number; v: number }[]","optional":false,"doc":""},{"name":"note","type":"string","optional":true,"doc":""},{"name":"kind","type":"string","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"format","type":"(n: number) => string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TransactionRow":[{"name":"text","type":"string","optional":false,"doc":""},{"name":"date","type":"string","optional":false,"doc":""},{"name":"amount","type":"number","optional":false,"doc":""},{"name":"matched","type":"boolean","optional":true,"doc":""},{"name":"matchRef","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"onMatch","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FormField":[{"name":"id","type":"string","optional":true,"doc":""},{"name":"name","type":"string","optional":true,"doc":"Field name inside a `Form`: the field shows that name's validation message."},{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"optional","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"success","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TextField":[{"name":"type","type":"string","optional":true,"doc":"Input type (`\"text\"` by default): `\"email\"`, `\"number\"`, `\"tel\"`, `\"url\"`, `\"password\"` …"},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"suffix","type":"ReactNode","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"readOnly","type":"boolean","optional":true,"doc":""},{"name":"autoComplete","type":"string","optional":true,"doc":""},{"name":"autoFocus","type":"boolean","optional":true,"doc":""},{"name":"min","type":"number | string","optional":true,"doc":"Smallest value for `type=\"number\"`, `\"date\"` and the like (native `min`)."},{"name":"max","type":"number | string","optional":true,"doc":"Largest value (native `max`)."},{"name":"step","type":"number | string","optional":true,"doc":"Step between valid values (native `step`), e.g. `1` for whole numbers or `\"any\"`."},{"name":"minLength","type":"number","optional":true,"doc":"Fewest characters (native `minlength`)."},{"name":"maxLength","type":"number","optional":true,"doc":"Most characters (native `maxlength`)."},{"name":"pattern","type":"string","optional":true,"doc":"Pattern the value must match (native `pattern`)."},{"name":"inputMode","type":"\"none\" | \"text\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\" | \"search\"","optional":true,"doc":"Virtual keyboard on touch devices (native `inputmode`), e.g. `\"numeric\"` or `\"decimal\"`."},{"name":"onChange","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"onBlur","type":"(e: ChangeEvent<HTMLInputElement>) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""}],"RadioGroup":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"options","type":"(string | Option)[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"cards","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Segmented":[{"name":"options","type":"(string | Option)[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"size","type":"\"sm\"","optional":true,"doc":""},{"name":"block","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Combobox":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"options","type":"(string | Option)[]","optional":false,"doc":""},{"name":"value","type":"string | string[] | null","optional":true,"doc":""},{"name":"defaultValue","type":"string | string[]","optional":true,"doc":""},{"name":"onChange","type":"(v: string | string[] | null) => void","optional":true,"doc":""},{"name":"multiple","type":"boolean","optional":true,"doc":""},{"name":"creatable","type":"boolean","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TagInput":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"value","type":"string[]","optional":true,"doc":""},{"name":"defaultValue","type":"string[]","optional":true,"doc":""},{"name":"onChange","type":"(v: string[]) => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DatePicker":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"range","type":"boolean","optional":true,"doc":""},{"name":"value","type":"Date | DateRange | null","optional":true,"doc":""},{"name":"defaultValue","type":"Date | DateRange","optional":true,"doc":""},{"name":"onChange","type":"(v: Date | DateRange | null) => void","optional":true,"doc":""},{"name":"presets","type":"boolean","optional":true,"doc":""},{"name":"min","type":"Date","optional":true,"doc":""},{"name":"max","type":"Date","optional":true,"doc":""},{"name":"today","type":"Date","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Calendar":[{"name":"range","type":"boolean","optional":true,"doc":""},{"name":"value","type":"Date | DateRange | null","optional":true,"doc":""},{"name":"onChange","type":"(v: Date | DateRange) => void","optional":true,"doc":""},{"name":"today","type":"Date","optional":true,"doc":""},{"name":"min","type":"Date","optional":true,"doc":""},{"name":"max","type":"Date","optional":true,"doc":""},{"name":"month","type":"Date","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"NumberInput":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"value","type":"number","optional":true,"doc":""},{"name":"defaultValue","type":"number","optional":true,"doc":""},{"name":"onChange","type":"(n: number) => void","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"step","type":"number","optional":true,"doc":""},{"name":"unit","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Slider":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"step","type":"number","optional":true,"doc":""},{"name":"value","type":"number","optional":true,"doc":""},{"name":"defaultValue","type":"number","optional":true,"doc":""},{"name":"onChange","type":"(n: number) => void","optional":true,"doc":""},{"name":"format","type":"(n: number) => string","optional":true,"doc":""},{"name":"marks","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OtpInput":[{"name":"length","type":"number","optional":true,"doc":""},{"name":"onComplete","type":"(code: string) => void","optional":true,"doc":""},{"name":"error","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FileDrop":[{"name":"files","type":"UploadFile[]","optional":true,"doc":"The listed files (controlled); set `progress` per file to show real upload progress."},{"name":"onChange","type":"(f: UploadFile[]) => void","optional":true,"doc":"The list changed: files added (with their `file`) or removed."},{"name":"onFiles","type":"(files: File[]) => void","optional":true,"doc":"The person picked or dropped files: the real `File` objects, after `accept` and `multiple`.\nUpload them yourself. To show progress, control `files` and set each `progress`; an\nuncontrolled list shows them with their size only."},{"name":"onReject","type":"(files: File[]) => void","optional":true,"doc":"Dropped files that do not match `accept` (show an inline Alert)."},{"name":"onRemove","type":"(f: UploadFile) => void","optional":true,"doc":"A listed file was removed or its upload cancelled."},{"name":"simulateProgress","type":"boolean","optional":true,"doc":"Animate made-up progress for added files, as a demo of the look. Default: on without\n`onFiles` (as before), off with it."},{"name":"title","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"accept","type":"string","optional":true,"doc":"Accepted types, as for `<input type=\"file\">` (\"image/*,.pdf\"); also checked on drop."},{"name":"multiple","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Tooltip":[{"name":"label","type":"ReactNode","optional":false,"doc":""},{"name":"shortcut","type":"string","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\" | \"left\" | \"right\"","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":"Starts open (for demos and onboarding hints); it still closes on blur and Escape."},{"name":"children","type":"ReactNode","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Popover":[{"name":"trigger","type":"ReactNode | ((s: { open: boolean; toggle: () => void }) => ReactNode)","optional":false,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"onOpenChange","type":"(o: boolean) => void","optional":true,"doc":""},{"name":"align","type":"\"start\" | \"end\"","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\"","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"flush","type":"boolean","optional":true,"doc":""},{"name":"block","type":"boolean","optional":true,"doc":""},{"name":"role","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode | ((s: { close: () => void }) => ReactNode)","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Alert":[{"name":"tone","type":"Exclude<Tone, \"neutral\">","optional":true,"doc":""},{"name":"title","type":"ReactNode","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"onDismiss","type":"() => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConfirmDialog":[{"name":"open","type":"boolean","optional":false,"doc":""},{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"danger","type":"boolean","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"confirmLabel","type":"string","optional":true,"doc":""},{"name":"cancelLabel","type":"string","optional":true,"doc":""},{"name":"confirmText","type":"string","optional":true,"doc":""},{"name":"onConfirm","type":"() => void","optional":false,"doc":""},{"name":"onCancel","type":"() => void","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Toaster":[{"name":"position","type":"\"bottom\" | \"top\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FilterBar":[{"name":"filters","type":"FilterDef[]","optional":false,"doc":""},{"name":"value","type":"Record<string, string>","optional":false,"doc":""},{"name":"onChange","type":"(v: Record<string, string>) => void","optional":false,"doc":""},{"name":"search","type":"string","optional":true,"doc":""},{"name":"query","type":"string","optional":true,"doc":""},{"name":"onQuery","type":"(q: string) => void","optional":true,"doc":""},{"name":"right","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DataTable":[{"name":"columns","type":"DataColumn<R>[]","optional":false,"doc":""},{"name":"rows","type":"R[]","optional":false,"doc":""},{"name":"rowKey","type":"string","optional":true,"doc":""},{"name":"selectable","type":"boolean","optional":true,"doc":""},{"name":"bulkActions","type":"{ label: string; icon?: IconName; danger?: boolean; onClick: (rows: R[], clear: () => void) => void; }[]","optional":true,"doc":""},{"name":"renderExpanded","type":"(row: R) => ReactNode","optional":true,"doc":""},{"name":"stickyHeader","type":"boolean","optional":true,"doc":""},{"name":"maxHeight","type":"number | string","optional":true,"doc":""},{"name":"defaultSort","type":"{ key: string; dir: \"asc\" | \"desc\" }","optional":true,"doc":""},{"name":"toolbar","type":"ReactNode","optional":true,"doc":""},{"name":"columnToggle","type":"boolean","optional":true,"doc":""},{"name":"empty","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StackedBarChart":[{"name":"labels","type":"string[]","optional":false,"doc":""},{"name":"series","type":"Series[]","optional":false,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"barWidth","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"legend","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AreaChart":[{"name":"labels","type":"string[]","optional":false,"doc":""},{"name":"series","type":"Series[]","optional":false,"doc":""},{"name":"stacked","type":"boolean","optional":true,"doc":""},{"name":"height","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"legend","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Heatmap":[{"name":"rows","type":"string[]","optional":false,"doc":""},{"name":"cols","type":"string[]","optional":false,"doc":""},{"name":"values","type":"number[][]","optional":false,"doc":""},{"name":"colEvery","type":"number","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Funnel":[{"name":"steps","type":"{ label: string; value: number; tint?: Tint }[]","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Gauge":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"tint","type":"Tint | \"ink\"","optional":true,"doc":""},{"name":"thresholds","type":"[number, number]","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"stroke","type":"number","optional":true,"doc":""},{"name":"showRange","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Legend":[{"name":"items","type":"{ name?: string; label?: string; tint?: Tint | \"ink\" }[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatMessage":[{"name":"role","type":"\"user\" | \"assistant\" | \"system\"","optional":false,"doc":""},{"name":"name","type":"string","optional":true,"doc":""},{"name":"time","type":"string","optional":true,"doc":""},{"name":"model","type":"string","optional":true,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"streaming","type":"boolean","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"sources","type":"Source[]","optional":true,"doc":""},{"name":"attachments","type":"{ name: string; size?: string; kind?: string }[]","optional":true,"doc":""},{"name":"usage","type":"string","optional":true,"doc":""},{"name":"onRetry","type":"() => void","optional":true,"doc":""},{"name":"onFeedback","type":"(v: \"up\" | \"down\") => void","optional":true,"doc":""},{"name":"actions","type":"boolean","optional":true,"doc":""},{"name":"before","type":"ReactNode","optional":true,"doc":""},{"name":"after","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"feedback","type":"\"up\" | \"down\" | null","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RichText":[{"name":"text","type":"string","optional":false,"doc":""},{"name":"streaming","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CodeBlock":[{"name":"code","type":"string","optional":false,"doc":""},{"name":"lang","type":"string","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ToolCall":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"status","type":"\"running\" | \"done\" | \"error\" | \"waiting\"","optional":true,"doc":""},{"name":"summary","type":"ReactNode","optional":true,"doc":""},{"name":"duration","type":"string","optional":true,"doc":""},{"name":"input","type":"unknown","optional":true,"doc":""},{"name":"output","type":"unknown","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Reasoning":[{"name":"steps","type":"string[]","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"duration","type":"string","optional":true,"doc":""},{"name":"streaming","type":"boolean","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PromptStarters":[{"name":"items","type":"{ icon?: IconName; tint?: Tint; title: string; text?: string; prompt?: string }[]","optional":false,"doc":""},{"name":"onPick","type":"(prompt: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ModelPicker":[{"name":"models","type":"ModelOption[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(id: string) => void","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Composer":[{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"onSend","type":"(text: string, files: (File | { name: string })[]) => void","optional":true,"doc":""},{"name":"busy","type":"boolean","optional":true,"doc":""},{"name":"onStop","type":"() => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"models","type":"ModelOption[]","optional":true,"doc":""},{"name":"model","type":"string","optional":true,"doc":""},{"name":"onModel","type":"(id: string) => void","optional":true,"doc":""},{"name":"commands","type":"ComposerCommand[]","optional":true,"doc":""},{"name":"onCommand","type":"(c: ComposerCommand) => void","optional":true,"doc":""},{"name":"attachments","type":"(File | { name: string })[]","optional":true,"doc":""},{"name":"onAttach","type":"(files: FileList) => void","optional":true,"doc":""},{"name":"onRemoveAttachment","type":"(i: number) => void","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"left","type":"ReactNode","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentStatus":[{"name":"status","type":"\"Running\" | \"Thinking\" | \"Waiting for approval\" | \"Idle\" | \"Paused\" | \"Error\" | string","optional":false,"doc":""},{"name":"step","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentMark":[{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"live","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RunTrace":[{"name":"steps","type":"TraceStep[]","optional":false,"doc":""},{"name":"defaultOpen","type":"number | null","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"UsageMeter":[{"name":"used","type":"number","optional":false,"doc":""},{"name":"limit","type":"number","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"caption","type":"string","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ProductArt":[{"name":"kind","type":"ArtKind","optional":false,"doc":""},{"name":"tint","type":"Tint | \"plain\"","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Price":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"compareAt","type":"number","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"size","type":"\"lg\"","optional":true,"doc":""},{"name":"showSave","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Rating":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"count","type":"number","optional":true,"doc":""},{"name":"size","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StockBadge":[{"name":"stock","type":"number","optional":false,"doc":""},{"name":"low","type":"number","optional":true,"doc":""},{"name":"compact","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ProductCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"meta","type":"string","optional":true,"doc":""},{"name":"art","type":"ArtKind","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"price","type":"number","optional":false,"doc":""},{"name":"compareAt","type":"number","optional":true,"doc":""},{"name":"rating","type":"number","optional":true,"doc":""},{"name":"reviews","type":"number","optional":true,"doc":""},{"name":"badge","type":"string","optional":true,"doc":""},{"name":"badgeTone","type":"Tone","optional":true,"doc":""},{"name":"stock","type":"number","optional":true,"doc":""},{"name":"wished","type":"boolean","optional":true,"doc":""},{"name":"onWish","type":"() => void","optional":true,"doc":""},{"name":"added","type":"boolean","optional":true,"doc":""},{"name":"onAdd","type":"() => void","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"VariantPicker":[{"name":"label","type":"string","optional":false,"doc":""},{"name":"type","type":"\"color\"","optional":true,"doc":""},{"name":"options","type":"(string | { value: string; label?: string; color?: string; disabled?: boolean })[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CartLine":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"variant","type":"string","optional":true,"doc":""},{"name":"note","type":"string","optional":true,"doc":""},{"name":"art","type":"ArtKind","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"price","type":"number","optional":false,"doc":""},{"name":"qty","type":"number","optional":false,"doc":""},{"name":"onQty","type":"(n: number) => void","optional":true,"doc":""},{"name":"onRemove","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OrderSummary":[{"name":"lines","type":"{ name: string; variant?: string; art: ArtKind; tint?: Tint; price: number; qty: number; }[]","optional":false,"doc":""},{"name":"showLines","type":"boolean","optional":true,"doc":""},{"name":"discount","type":"Discount | null","optional":true,"doc":""},{"name":"onCoupon","type":"(code: string | null) => boolean | void","optional":true,"doc":""},{"name":"shipping","type":"number | null","optional":true,"doc":""},{"name":"freeShippingFrom","type":"number","optional":true,"doc":""},{"name":"vat","type":"number","optional":true,"doc":""},{"name":"title","type":"string | false","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CheckoutSteps":[{"name":"steps","type":"string[]","optional":true,"doc":""},{"name":"current","type":"number","optional":true,"doc":""},{"name":"onPick","type":"(i: number) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OrderTimeline":[{"name":"status","type":"\"Placed\" | \"Paid\" | \"Packed\" | \"Shipped\" | \"Delivered\" | \"Refunded\" | \"Cancelled\"","optional":false,"doc":""},{"name":"times","type":"Record<string, string>","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PlanCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"price","type":"number | null","optional":false,"doc":""},{"name":"was","type":"number","optional":true,"doc":""},{"name":"per","type":"string","optional":true,"doc":""},{"name":"features","type":"string[]","optional":true,"doc":""},{"name":"featured","type":"boolean","optional":true,"doc":""},{"name":"flag","type":"string","optional":true,"doc":""},{"name":"current","type":"boolean","optional":true,"doc":""},{"name":"cta","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":"Disable the button, e.g. while a checkout starts or when the plan cannot be picked."},{"name":"onPick","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusScreen":[{"name":"code","type":"\"404\" | \"500\" | \"403\" | \"maintenance\" | \"offline\" | string","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"extra","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"onHome","type":"() => void","optional":true,"doc":""},{"name":"onContact","type":"() => void","optional":true,"doc":""},{"name":"reference","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AuthCard":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"brand","type":"string","optional":true,"doc":""},{"name":"foot","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusShape":[{"name":"status","type":"Status","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusIndicator":[{"name":"status","type":"Status","optional":false,"doc":""},{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"size","type":"\"sm\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ApprovalCard":[{"name":"title","type":"ReactNode","optional":false,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"agent","type":"string","optional":true,"doc":""},{"name":"project","type":"string","optional":true,"doc":""},{"name":"family","type":"RiskFamily","optional":true,"doc":""},{"name":"familyLabel","type":"string","optional":true,"doc":""},{"name":"age","type":"string","optional":true,"doc":""},{"name":"escalation","type":"ReactNode","optional":true,"doc":""},{"name":"trace","type":"ReactNode","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"heading","type":"string","optional":true,"doc":""},{"name":"options","type":"ApprovalOption[]","optional":true,"doc":""},{"name":"approveLabel","type":"string","optional":true,"doc":""},{"name":"onDecide","type":"(key: ApprovalKey, text?: string) => void","optional":true,"doc":""},{"name":"decision","type":"ApprovalDecision | null","optional":true,"doc":""},{"name":"compact","type":"boolean","optional":true,"doc":""},{"name":"onApprove","type":"() => void","optional":true,"doc":""},{"name":"onDeny","type":"() => void","optional":true,"doc":""},{"name":"onEdit","type":"(text: string) => void","optional":true,"doc":""},{"name":"state","type":"\"approved\" | \"denied\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ApprovalInbox":[{"name":"items","type":"ApprovalInboxItem[]","optional":false,"doc":""},{"name":"selected","type":"ApprovalInboxItem[\"id\"]","optional":true,"doc":""},{"name":"onSelect","type":"(id: ApprovalInboxItem[\"id\"]) => void","optional":true,"doc":""},{"name":"emptyTitle","type":"string","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Kbd":[{"name":"keys","type":"string[]","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"KeyValue":[{"name":"items","type":"KeyValueItem[]","optional":false,"doc":""},{"name":"dense","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Spinner":[{"name":"size","type":"\"lg\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Collapsible":[{"name":"title","type":"ReactNode","optional":false,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"onOpenChange","type":"(open: boolean) => void","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ContextMenu":[{"name":"items","type":"(MenuItem | \"-\")[]","optional":false,"doc":""},{"name":"children","type":"ReactNode","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SplitPane":[{"name":"left","type":"ReactNode","optional":false,"doc":""},{"name":"right","type":"ReactNode","optional":false,"doc":""},{"name":"defaultSize","type":"number","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"storageKey","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TreeView":[{"name":"items","type":"TreeItem[]","optional":false,"doc":""},{"name":"defaultOpen","type":"string[]","optional":true,"doc":""},{"name":"selected","type":"string","optional":true,"doc":""},{"name":"onSelect","type":"(id: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"InlineEdit":[{"name":"value","type":"string","optional":false,"doc":""},{"name":"onSave","type":"(v: string) => void","optional":true,"doc":""},{"name":"options","type":"string[]","optional":true,"doc":""},{"name":"mono","type":"boolean","optional":true,"doc":""},{"name":"type","type":"string","optional":true,"doc":""},{"name":"display","type":"(v: string) => ReactNode","optional":true,"doc":""},{"name":"align","type":"\"left\" | \"right\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LogView":[{"name":"lines","type":"LogLine[]","optional":false,"doc":""},{"name":"title","type":"ReactNode","optional":true,"doc":""},{"name":"right","type":"ReactNode","optional":true,"doc":""},{"name":"numbers","type":"boolean","optional":true,"doc":""},{"name":"follow","type":"boolean","optional":true,"doc":""},{"name":"maxHeight","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CodeViewer":[{"name":"value","type":"string | object","optional":false,"doc":""},{"name":"lang","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SecretField":[{"name":"prefix","type":"string","optional":true,"doc":""},{"name":"last4","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"onReveal","type":"() => boolean | Promise<boolean>","optional":true,"doc":""},{"name":"onCopy","type":"() => void","optional":true,"doc":""},{"name":"onRotate","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MoneyInput":[{"name":"label","type":"string","optional":true,"doc":""},{"name":"value","type":"number | null","optional":true,"doc":""},{"name":"onChange","type":"(v: number | null) => void","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"vat","type":"string","optional":true,"doc":""},{"name":"onVat","type":"(v: string) => void","optional":true,"doc":""},{"name":"vatOptions","type":"{ value: string; label: string; rate?: number }[]","optional":true,"doc":""},{"name":"showGross","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"defaultValue","type":"number | null","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SettingsLayout":[{"name":"sections","type":"{ id?: string; title: ReactNode; text?: ReactNode; badge?: ReactNode; children?: ReactNode; }[]","optional":false,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ShortcutsOverlay":[{"name":"open","type":"boolean","optional":false,"doc":""},{"name":"onClose","type":"() => void","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"groups","type":"{ title: string; items: [string, string[]][] }[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatCell":[{"name":"label","type":"ReactNode","optional":false,"doc":""},{"name":"value","type":"ReactNode","optional":false,"doc":""},{"name":"delta","type":"number","optional":true,"doc":""},{"name":"deltaText","type":"string","optional":true,"doc":""},{"name":"compare","type":"string","optional":true,"doc":""},{"name":"invert","type":"boolean","optional":true,"doc":""},{"name":"spark","type":"number[]","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"iconTint","type":"Tint","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"UptimeBar":[{"name":"days","type":"UptimeDay[]","optional":false,"doc":""},{"name":"uptime","type":"number","optional":true,"doc":""},{"name":"caption","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Meter":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"value","type":"number | null","optional":false,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"warn","type":"number","optional":true,"doc":""},{"name":"danger","type":"number","optional":true,"doc":""},{"name":"tone","type":"Tint","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"role","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"model","type":"string","optional":true,"doc":""},{"name":"cost","type":"string","optional":true,"doc":""},{"name":"last","type":"ReactNode","optional":true,"doc":""},{"name":"onClick","type":"() => void","optional":true,"doc":""},{"name":"onChat","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AgentDetails":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"role","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"sections","type":"{ title: string; meta?: ReactNode; items?: KeyValueItem[]; children?: ReactNode; open?: boolean; }[]","optional":true,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatList":[{"name":"groups","type":"{ title: string; mark?: Tint; items: ChatListItem[] }[]","optional":false,"doc":""},{"name":"selected","type":"string","optional":true,"doc":""},{"name":"onSelect","type":"(id: string) => void","optional":true,"doc":""},{"name":"onSearch","type":"(q: string) => void","optional":true,"doc":""},{"name":"filters","type":"ReactNode","optional":true,"doc":""},{"name":"searchPlaceholder","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatWorkspace":[{"name":"list","type":"ReactNode","optional":false,"doc":""},{"name":"thread","type":"ReactNode","optional":false,"doc":""},{"name":"details","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SearchHits":[{"name":"items","type":"{ id: string | number; chat: string; time?: string; who?: string; text: string }[]","optional":false,"doc":""},{"name":"query","type":"string","optional":true,"doc":""},{"name":"onOpen","type":"(item: NonNullable<SearchHitsProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ThinkingLine":[{"name":"text","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"FeedbackRating":[{"name":"value","type":"\"up\" | \"down\" | null","optional":true,"doc":""},{"name":"onRate","type":"(v: \"up\" | \"down\") => void","optional":true,"doc":""},{"name":"onSubmit","type":"(v: \"up\" | \"down\", comment?: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"thanks","type":"string","optional":true,"doc":""},{"name":"askOnUp","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ActivityFeed":[{"name":"groups","type":"{ title?: string; items: ActivityItem[] }[]","optional":true,"doc":""},{"name":"items","type":"ActivityItem[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DailyBriefing":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"time","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"summary","type":"ReactNode","optional":false,"doc":""},{"name":"attention","type":"{ text: ReactNode; status?: Status; onClick?: () => void }[]","optional":true,"doc":""},{"name":"suggestions","type":"ReactNode[]","optional":true,"doc":""},{"name":"attentionTitle","type":"string","optional":true,"doc":""},{"name":"suggestionsTitle","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BudgetStop":[{"name":"spent","type":"number","optional":false,"doc":""},{"name":"budget","type":"number","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"paused","type":"ReactNode","optional":true,"doc":""},{"name":"running","type":"ReactNode","optional":true,"doc":""},{"name":"onRaise","type":"() => void","optional":true,"doc":""},{"name":"actionLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MemoryViewer":[{"name":"items","type":"{ id: string | number; scope: string; source?: string; date?: string; text: string }[]","optional":false,"doc":""},{"name":"onEdit","type":"(item: NonNullable<MemoryViewerProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"onDelete","type":"(item: NonNullable<MemoryViewerProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OverrideDiff":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"versions","type":"ReactNode","optional":true,"doc":""},{"name":"by","type":"ReactNode","optional":true,"doc":""},{"name":"before","type":"string","optional":false,"doc":""},{"name":"after","type":"string","optional":false,"doc":""},{"name":"onDiscard","type":"() => void","optional":true,"doc":""},{"name":"onPromote","type":"() => void","optional":true,"doc":""},{"name":"discardLabel","type":"string","optional":true,"doc":""},{"name":"promoteLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TriggerList":[{"name":"items","type":"{ id: string | number; type: \"schedule\" | \"event\" | \"webhook\" | \"mail\"; rule: ReactNode; agent?: string; by?: string; next?: string; enabled?: boolean; }[]","optional":false,"doc":""},{"name":"onToggle","type":"(item: NonNullable<TriggerListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"onRun","type":"(item: NonNullable<TriggerListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ScheduleCalendar":[{"name":"events","type":"{ day: number; start: number; end: number; label: string; sub?: string; tint?: Tint; self?: boolean; }[]","optional":false,"doc":""},{"name":"from","type":"number","optional":true,"doc":""},{"name":"to","type":"number","optional":true,"doc":""},{"name":"days","type":"string[]","optional":true,"doc":""},{"name":"onOpen","type":"(e: NonNullable<ScheduleCalendarProps[\"events\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"OrgChart":[{"name":"root","type":"OrgNode","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"VoiceMode":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"state","type":"\"listening\" | \"thinking\" | \"speaking\" | \"idle\"","optional":false,"doc":""},{"name":"stateLabel","type":"string","optional":true,"doc":""},{"name":"transcript","type":"ReactNode","optional":true,"doc":""},{"name":"muted","type":"boolean","optional":true,"doc":""},{"name":"onMute","type":"() => void","optional":true,"doc":""},{"name":"onEnd","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConnectionCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"logo","type":"ReactNode","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"\"connected\" | \"expiring\" | \"error\" | \"missing\"","optional":false,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConnectionBanner":[{"name":"state","type":"\"reconnecting\" | \"offline\" | \"online\"","optional":false,"doc":""},{"name":"seconds","type":"number","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"onRetry","type":"() => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ServerCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"provider","type":"string","optional":true,"doc":""},{"name":"address","type":"string","optional":true,"doc":""},{"name":"logo","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"meters","type":"{ label: string; value: number | null; max?: number; text?: ReactNode; unknown?: string; }[]","optional":true,"doc":""},{"name":"uptime","type":"UptimeBarProps","optional":true,"doc":""},{"name":"foot","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DeployPipeline":[{"name":"stages","type":"{ name: string; status?: \"done\" | \"running\" | \"signal\" | \"danger\" | \"idle\"; duration?: string; action?: ReactNode; }[]","optional":false,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DnsRecordTable":[{"name":"records","type":"DnsRecord[]","optional":false,"doc":""},{"name":"rowMenu","type":"(r: DnsRecord) => (MenuItem | \"-\")[]","optional":true,"doc":""},{"name":"selectable","type":"boolean","optional":true,"doc":""},{"name":"bulkActions","type":"BulkAction<DnsRecord>[]","optional":true,"doc":""},{"name":"toolbar","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"DomainList":[{"name":"items","type":"{ name: string; registrar?: string; dns?: string; days?: number; ssl?: Status; sslLabel?: string; }[]","optional":false,"doc":""},{"name":"onOpen","type":"(d: NonNullable<DomainListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MailAuthStatus":[{"name":"rows","type":"{ domain: string; mx?: { status: Status; label?: string }; spf?: { status: Status; label?: string }; dkim?: { status: Status; label?: string }; dmarc?: { status: Status; label?: string }; }[]","optional":false,"doc":""},{"name":"columns","type":"string[]","optional":true,"doc":""},{"name":"onOpen","type":"(row: NonNullable<MailAuthStatusProps[\"rows\"]>[number], column: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LedgerTable":[{"name":"rows","type":"LedgerRow[]","optional":false,"doc":""},{"name":"categories","type":"string[]","optional":true,"doc":""},{"name":"onEdit","type":"(row: LedgerRow, change: Partial<LedgerRow>) => void","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"selectable","type":"boolean","optional":true,"doc":""},{"name":"bulkActions","type":"BulkAction<LedgerRow>[]","optional":true,"doc":""},{"name":"toolbar","type":"ReactNode","optional":true,"doc":""},{"name":"maxHeight","type":"number","optional":true,"doc":""},{"name":"columnToggle","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PnLStatement":[{"name":"columns","type":"string[]","optional":false,"doc":""},{"name":"rows","type":"{ kind?: \"section\" | \"subtotal\" | \"total\"; label: string; values?: number[] }[]","optional":false,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"total","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ReceiptSuggestion":[{"name":"file","type":"string","optional":false,"doc":""},{"name":"source","type":"ReactNode","optional":true,"doc":""},{"name":"fields","type":"{ label: string; value: ReactNode; mono?: boolean; confidence?: number }[]","optional":false,"doc":""},{"name":"suggestion","type":"ReactNode","optional":true,"doc":""},{"name":"onConfirm","type":"() => void","optional":true,"doc":""},{"name":"onDiscard","type":"() => void","optional":true,"doc":""},{"name":"confirmLabel","type":"string","optional":true,"doc":""},{"name":"discardLabel","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AccountSwitcher":[{"name":"accounts","type":"{ address: string; domain?: string; unread?: number }[]","optional":false,"doc":""},{"name":"value","type":"string | null","optional":true,"doc":""},{"name":"onChange","type":"(address: string | null) => void","optional":true,"doc":""},{"name":"unread","type":"number","optional":true,"doc":""},{"name":"allLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"InboxList":[{"name":"items","type":"{ id: string | number; from: string; time?: string; subject: string; snippet?: string; unread?: boolean; to?: string; agent?: { label: string; tone?: Tone }; tags?: string[]; }[]","optional":false,"doc":""},{"name":"selected","type":"string | number","optional":true,"doc":""},{"name":"onOpen","type":"(m: NonNullable<InboxListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MailThread":[{"name":"subject","type":"string","optional":false,"doc":""},{"name":"messages","type":"{ id: string | number; from: string; to?: string; time?: string; snippet?: string; body: ReactNode; agent?: boolean; tint?: Tint; agentNote?: string; attachments?: { name: string; size?: string }[]; }[]","optional":false,"doc":""},{"name":"reply","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SignatureEditor":[{"name":"value","type":"Record<string, string>","optional":true,"doc":""},{"name":"onChange","type":"(v: Record<string, string>) => void","optional":true,"doc":""},{"name":"fields","type":"{ key: string; label: string }[]","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"extra","type":"ReactNode","optional":true,"doc":""},{"name":"defaultValue","type":"Record<string, string>","optional":true,"doc":""},{"name":"previewLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AttachmentPreview":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"facts","type":"KeyValueItem[]","optional":true,"doc":""},{"name":"found","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"ReactNode","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"width","type":"number | string","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MobileTabBar":[{"name":"items","type":"{ id: string; label: string; icon: IconName; count?: number }[]","optional":false,"doc":""},{"name":"active","type":"string","optional":true,"doc":""},{"name":"onPick","type":"(id: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"PushNotification":[{"name":"app","type":"string","optional":true,"doc":""},{"name":"time","type":"string","optional":true,"doc":""},{"name":"title","type":"string","optional":false,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"actions","type":"{ label: string; onClick?: () => void }[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ConfigPRSummary":[{"name":"title","type":"string","optional":false,"doc":""},{"name":"meta","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"summary","type":"ReactNode","optional":true,"doc":""},{"name":"items","type":"{ kind: \"project\" | \"team\" | \"agent\" | \"tool\" | \"trigger\" | \"file\"; name: string; detail?: ReactNode; op?: \"add\" | \"change\" | \"remove\"; }[]","optional":false,"doc":""},{"name":"actions","type":"ReactNode[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ModuleRegistry":[{"name":"items","type":"{ id: string; name: string; version?: string; text?: ReactNode; icon?: IconName; core?: boolean; enabled?: boolean; adds?: string[]; }[]","optional":false,"doc":""},{"name":"onToggle","type":"(m: NonNullable<ModuleRegistryProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"coreLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RangeSlider":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"max","type":"number","optional":true,"doc":""},{"name":"step","type":"number","optional":true,"doc":""},{"name":"value","type":"[number, number]","optional":true,"doc":""},{"name":"defaultValue","type":"[number, number]","optional":true,"doc":""},{"name":"onChange","type":"(v: [number, number]) => void","optional":true,"doc":""},{"name":"unit","type":"string","optional":true,"doc":""},{"name":"inputs","type":"boolean","optional":true,"doc":""},{"name":"openEnd","type":"boolean","optional":true,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BarList":[{"name":"items","type":"{ id?: string; label: ReactNode; mark?: ReactNode; value: number; share?: number; tint?: Tint; }[]","optional":false,"doc":""},{"name":"format","type":"(v: number) => string","optional":true,"doc":""},{"name":"onPick","type":"(item: NonNullable<BarListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"StatusSelect":[{"name":"options","type":"{ value: string; label: string; status?: Status }[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"size","type":"\"sm\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TilePicker":[{"name":"options","type":"{ value: string; label: ReactNode; meta?: ReactNode; image?: string; icon?: IconName; art?: ReactNode; tint?: Tint; color?: string; disabled?: boolean; }[]","optional":false,"doc":""},{"name":"value","type":"string | string[]","optional":true,"doc":""},{"name":"defaultValue","type":"string | string[]","optional":true,"doc":""},{"name":"onChange","type":"(v: string | string[] | null) => void","optional":true,"doc":""},{"name":"multiple","type":"boolean","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ColorField":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(v: string) => void","optional":true,"doc":""},{"name":"against","type":"string | false","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"MapView":[{"name":"points","type":"MapPoint[]","optional":true,"doc":""},{"name":"bounds","type":"{ n: number; s: number; w: number; e: number }","optional":true,"doc":""},{"name":"background","type":"ReactNode","optional":true,"doc":""},{"name":"bubbles","type":"boolean","optional":true,"doc":""},{"name":"selected","type":"MapPoint[\"id\"] | null","optional":true,"doc":""},{"name":"onSelect","type":"(id: MapPoint[\"id\"] | null) => void","optional":true,"doc":""},{"name":"renderPopup","type":"(p: MapPoint) => ReactNode","optional":true,"doc":""},{"name":"legend","type":"ReactNode","optional":true,"doc":""},{"name":"controls","type":"ReactNode","optional":true,"doc":""},{"name":"height","type":"number | string","optional":true,"doc":"Height in px (default 320) or any CSS length, e.g. \"100%\" to fill a FloatingLayout."},{"name":"label","type":"string","optional":true,"doc":""},{"name":"surface","type":"Surface","optional":true,"doc":"`\"glass\"`: the legend, controls and popup float as glass over the map (for a live map in `background`)."},{"name":"className","type":"string","optional":true,"doc":""},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LiveFeed":[{"name":"items","type":"LiveFeedItem[]","optional":false,"doc":""},{"name":"types","type":"{ value: string; label: string }[]","optional":true,"doc":""},{"name":"paused","type":"boolean","optional":true,"doc":""},{"name":"onPause","type":"(paused: boolean) => void","optional":true,"doc":""},{"name":"maxHeight","type":"number","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RichTextEditor":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(html: string) => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"variables","type":"{ key: string; label: string }[]","optional":true,"doc":""},{"name":"maxLength","type":"number","optional":true,"doc":""},{"name":"minHeight","type":"number","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EmailPreview":[{"name":"from","type":"string","optional":true,"doc":""},{"name":"fromName","type":"string","optional":true,"doc":""},{"name":"subject","type":"string","optional":true,"doc":""},{"name":"preheader","type":"string","optional":true,"doc":""},{"name":"html","type":"string","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"device","type":"\"desktop\" | \"mobile\"","optional":true,"doc":""},{"name":"onDevice","type":"(d: string) => void","optional":true,"doc":""},{"name":"checks","type":"{ status: Status; label: ReactNode }[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SearchAdPreview":[{"name":"name","type":"string","optional":true,"doc":""},{"name":"domain","type":"string","optional":false,"doc":""},{"name":"paths","type":"string[]","optional":true,"doc":""},{"name":"headlines","type":"string[]","optional":true,"doc":""},{"name":"descriptions","type":"string[]","optional":true,"doc":""},{"name":"sitelinks","type":"string[]","optional":true,"doc":""},{"name":"device","type":"\"mobile\" | \"desktop\"","optional":true,"doc":""},{"name":"onDevice","type":"(d: string) => void","optional":true,"doc":""},{"name":"sponsoredLabel","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BalanceCard":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"sub","type":"ReactNode","optional":true,"doc":""},{"name":"logo","type":"ReactNode","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"balance","type":"number","optional":false,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"perDay","type":"number","optional":true,"doc":""},{"name":"low","type":"number","optional":true,"doc":""},{"name":"spark","type":"number[]","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"onTopUp","type":"() => void","optional":true,"doc":""},{"name":"topUpLabel","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ServiceMonitor":[{"name":"items","type":"{ id?: string; name: string; status: Status; statusLabel?: string; latency?: number | null; errorRate?: number | null; checked?: string; history?: (Status | \"none\")[]; }[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"RunHistory":[{"name":"items","type":"{ id: string | number; name: string; status: Status; started?: string; duration?: string; by?: string; counts?: { value: number; label: string; tone?: Tone }[]; detail?: ReactNode; }[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"WebhookEndpoint":[{"name":"url","type":"string","optional":false,"doc":""},{"name":"events","type":"string[]","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"successRate","type":"number","optional":true,"doc":""},{"name":"enabled","type":"boolean","optional":true,"doc":""},{"name":"onToggle","type":"() => void","optional":true,"doc":""},{"name":"secret","type":"ReactNode","optional":true,"doc":""},{"name":"deliveries","type":"{ id: string | number; code?: number; event: string; time?: string; ms?: number }[]","optional":true,"doc":""},{"name":"onRetry","type":"(d: NonNullable<WebhookEndpointProps[\"deliveries\"]>[number]) => void","optional":true,"doc":""},{"name":"menu","type":"MenuProps[\"items\"]","optional":true,"doc":""},{"name":"defaultOpen","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BackupList":[{"name":"items","type":"{ id: string | number; name?: string; time?: string; kind?: string; where?: string; retention?: string; size?: string; status?: Status; }[]","optional":false,"doc":""},{"name":"onDownload","type":"(b: NonNullable<BackupListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"onRestore","type":"(b: NonNullable<BackupListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BlockList":[{"name":"items","type":"{ id: string | number; value: string; kind?: string; reason?: string; by?: string; until?: string; }[]","optional":false,"doc":""},{"name":"onAdd","type":"() => void","optional":true,"doc":""},{"name":"onRemove","type":"(b: NonNullable<BlockListProps[\"items\"]>[number]) => void","optional":true,"doc":""},{"name":"addLabel","type":"string","optional":true,"doc":""},{"name":"removeLabel","type":"string","optional":true,"doc":""},{"name":"searchPlaceholder","type":"string","optional":true,"doc":""},{"name":"emptyText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ReviewCard":[{"name":"author","type":"string","optional":false,"doc":""},{"name":"verified","type":"boolean","optional":true,"doc":""},{"name":"product","type":"string","optional":true,"doc":""},{"name":"date","type":"string","optional":true,"doc":""},{"name":"rating","type":"number","optional":false,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"string","optional":false,"doc":""},{"name":"state","type":"\"pending\" | \"published\" | \"hidden\" | \"flagged\"","optional":true,"doc":""},{"name":"stateLabel","type":"string","optional":true,"doc":""},{"name":"highlight","type":"string[]","optional":true,"doc":""},{"name":"reply","type":"string","optional":true,"doc":""},{"name":"replyLabel","type":"string","optional":true,"doc":""},{"name":"onPublish","type":"() => void","optional":true,"doc":""},{"name":"onHide","type":"() => void","optional":true,"doc":""},{"name":"onReply","type":"(text: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CouponCard":[{"name":"code","type":"string","optional":false,"doc":""},{"name":"kind","type":"\"percent\" | \"fixed\"","optional":true,"doc":""},{"name":"value","type":"number","optional":false,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"used","type":"number","optional":true,"doc":""},{"name":"limit","type":"number","optional":true,"doc":""},{"name":"until","type":"string","optional":true,"doc":""},{"name":"min","type":"number","optional":true,"doc":""},{"name":"revenue","type":"number","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"status","type":"Status","optional":true,"doc":""},{"name":"statusLabel","type":"string","optional":true,"doc":""},{"name":"onCopy","type":"() => void","optional":true,"doc":""},{"name":"menu","type":"MenuProps[\"items\"]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LocaleField":[{"name":"label","type":"ReactNode","optional":true,"doc":""},{"name":"locales","type":"{ code: string; label?: string; flag?: string }[]","optional":false,"doc":""},{"name":"value","type":"Record<string, string>","optional":true,"doc":""},{"name":"defaultValue","type":"Record<string, string>","optional":true,"doc":""},{"name":"onChange","type":"(v: Record<string, string>) => void","optional":true,"doc":""},{"name":"source","type":"string","optional":true,"doc":""},{"name":"active","type":"string","optional":true,"doc":""},{"name":"multiline","type":"boolean","optional":true,"doc":""},{"name":"rows","type":"number","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"onTranslate","type":"(text: string, to: string) => string | Promise<string>","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":""},{"name":"error","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"LocaleSwitcher":[{"name":"languages","type":"{ code: string; label: string; short?: string; flag?: string }[]","optional":true,"doc":""},{"name":"language","type":"string","optional":true,"doc":""},{"name":"onLanguage","type":"(code: string) => void","optional":true,"doc":""},{"name":"currencies","type":"string[]","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"onCurrency","type":"(c: string) => void","optional":true,"doc":""},{"name":"variant","type":"\"frame\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"WorkspaceSwitcher":[{"name":"items","type":"{ id: string; name: string; sub?: string; tint?: Tint; logo?: ReactNode }[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(id: string) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"footer","type":"ReactNode","optional":true,"doc":""},{"name":"variant","type":"\"frame\"","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ExportDialog":[{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"onClose","type":"() => void","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"count","type":"number","optional":true,"doc":""},{"name":"formats","type":"string[]","optional":true,"doc":""},{"name":"format","type":"string","optional":true,"doc":""},{"name":"columns","type":"{ key: string; label: string; default?: boolean }[]","optional":true,"doc":""},{"name":"ranges","type":"boolean","optional":true,"doc":""},{"name":"range","type":"{ from: Date | null; to: Date | null } | null","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"onExport","type":"( o: { format: string; columns: string[]; range: DateRange | null }, progress: (pct: number) => void, ) => void | Promise<void>","optional":true,"doc":""},{"name":"onDownload","type":"() => void","optional":true,"doc":""},{"name":"download","type":"ReactNode","optional":true,"doc":""},{"name":"doneText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"TwoFactorSetup":[{"name":"qr","type":"ReactNode","optional":true,"doc":""},{"name":"secret","type":"string","optional":false,"doc":""},{"name":"onVerify","type":"(code: string) => boolean | Promise<boolean>","optional":true,"doc":""},{"name":"codes","type":"string[]","optional":true,"doc":""},{"name":"onDownload","type":"() => void","optional":true,"doc":""},{"name":"onDone","type":"() => void","optional":true,"doc":""},{"name":"step","type":"0 | 1 | 2","optional":true,"doc":""},{"name":"scanText","type":"string","optional":true,"doc":""},{"name":"codesText","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ImageVariants":[{"name":"variants","type":"ImageVariant[]","optional":false,"doc":""},{"name":"selected","type":"string | number","optional":true,"doc":""},{"name":"onSelect","type":"(id: string | number) => void","optional":true,"doc":""},{"name":"alt","type":"string","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"actions","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Gallery":[{"name":"items","type":"GalleryItem[]","optional":false,"doc":""},{"name":"loading","type":"\"lazy\" | \"eager\"","optional":true,"doc":"Loading of the main image: `\"lazy\"` (default) waits until the gallery is near the viewport;\nuse `\"eager\"` for a gallery at the top of the page. Thumbnails always load lazily."},{"name":"referrerPolicy","type":"ImageReferrerPolicy","optional":true,"doc":"`referrerPolicy` of every image, e.g. `\"no-referrer\"` for photos from other hosts."},{"name":"start","type":"number","optional":true,"doc":""},{"name":"ratio","type":"string","optional":true,"doc":""},{"name":"thumbs","type":"boolean","optional":true,"doc":""},{"name":"fullscreen","type":"boolean","optional":true,"doc":""},{"name":"badge","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"BeforeAfter":[{"name":"before","type":"MediaPane","optional":false,"doc":""},{"name":"after","type":"MediaPane","optional":false,"doc":""},{"name":"start","type":"number","optional":true,"doc":""},{"name":"ratio","type":"string","optional":true,"doc":""},{"name":"beforeLabel","type":"string","optional":true,"doc":""},{"name":"afterLabel","type":"string","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the slider (default \"Compare before and after\")."},{"name":"valueText","type":"(percent: number) => string","optional":true,"doc":"What screen readers hear for a position (0–100, the share of \"before\"). Default:\n\"Before 40%, After 60%\", with your `beforeLabel`/`afterLabel`."},{"name":"loading","type":"\"lazy\" | \"eager\"","optional":true,"doc":"Loading of both images (default `\"lazy\"`)."},{"name":"referrerPolicy","type":"ImageReferrerPolicy","optional":true,"doc":"`referrerPolicy` of both images."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ListingCard":[{"name":"listing","type":"Listing","optional":false,"doc":""},{"name":"layout","type":"\"card\" | \"row\"","optional":true,"doc":""},{"name":"selected","type":"boolean","optional":true,"doc":""},{"name":"onSelect","type":"(l: Listing) => void","optional":true,"doc":""},{"name":"onWish","type":"(l: Listing) => void","optional":true,"doc":""},{"name":"statusOptions","type":"StatusSelectProps[\"options\"]","optional":true,"doc":""},{"name":"onStatus","type":"(l: Listing, status: string) => void","optional":true,"doc":""},{"name":"onOpen","type":"(l: Listing) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ResultsToolbar":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"total","type":"number","optional":false,"doc":""},{"name":"meta","type":"string","optional":true,"doc":""},{"name":"selected","type":"number","optional":true,"doc":""},{"name":"onSelectAll","type":"(all: boolean) => void","optional":true,"doc":""},{"name":"bulkActions","type":"{ label: string; icon?: IconName; variant?: ButtonProps[\"variant\"]; busy?: boolean; busyLabel?: string; onClick?: () => void; }[]","optional":true,"doc":""},{"name":"sortOptions","type":"{ value: string; label: string }[]","optional":true,"doc":""},{"name":"sort","type":"string","optional":true,"doc":""},{"name":"onSort","type":"(v: string) => void","optional":true,"doc":""},{"name":"views","type":"(\"grid\" | \"list\" | \"map\")[]","optional":true,"doc":""},{"name":"view","type":"string","optional":true,"doc":""},{"name":"onView","type":"(v: string) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SavedSearch":[{"name":"name","type":"string","optional":false,"doc":""},{"name":"criteria","type":"ReactNode[]","optional":true,"doc":""},{"name":"lastRun","type":"string","optional":true,"doc":""},{"name":"frequency","type":"string","optional":true,"doc":""},{"name":"newCount","type":"number","optional":true,"doc":""},{"name":"enabled","type":"boolean","optional":true,"doc":""},{"name":"onToggle","type":"() => void","optional":true,"doc":""},{"name":"onEdit","type":"() => void","optional":true,"doc":""},{"name":"onOpen","type":"() => void","optional":true,"doc":""},{"name":"openLabel","type":"string","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ValuationCard":[{"name":"value","type":"number","optional":false,"doc":""},{"name":"low","type":"number","optional":false,"doc":""},{"name":"high","type":"number","optional":false,"doc":""},{"name":"asking","type":"number","optional":true,"doc":""},{"name":"perSqm","type":"number","optional":true,"doc":""},{"name":"compare","type":"string","optional":true,"doc":""},{"name":"currency","type":"string","optional":true,"doc":""},{"name":"confidence","type":"number","optional":true,"doc":""},{"name":"confidenceLabel","type":"string","optional":true,"doc":""},{"name":"facts","type":"KeyValueItem[]","optional":true,"doc":""},{"name":"note","type":"ReactNode","optional":true,"doc":""},{"name":"title","type":"string","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"EnergyClass":[{"name":"value","type":"string","optional":false,"doc":""},{"name":"kwh","type":"number","optional":true,"doc":""},{"name":"kind","type":"string","optional":true,"doc":""},{"name":"scale","type":"string[]","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ChatLauncher":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"context","type":"ReactNode","optional":true,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"onOpenChange","type":"(open: boolean) => void","optional":true,"doc":""},{"name":"unread","type":"number","optional":true,"doc":""},{"name":"headerAction","type":"ReactNode","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"composer","type":"ReactNode","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"CookieConsent":[{"name":"title","type":"string","optional":true,"doc":""},{"name":"text","type":"ReactNode","optional":true,"doc":""},{"name":"policy","type":"ReactNode","optional":true,"doc":""},{"name":"categories","type":"{ id: string; label: string; text?: string; required?: boolean; default?: boolean; }[]","optional":true,"doc":""},{"name":"onSave","type":"(choices: Record<string, boolean>) => void","optional":true,"doc":""},{"name":"open","type":"boolean","optional":true,"doc":""},{"name":"inline","type":"boolean","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AnnouncementBar":[{"name":"children","type":"ReactNode","optional":false,"doc":""},{"name":"tint","type":"Tint","optional":true,"doc":""},{"name":"icon","type":"IconName | false","optional":true,"doc":""},{"name":"action","type":"ReactNode","optional":true,"doc":""},{"name":"onDismiss","type":"(() => void) | false","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Accordion":[{"name":"items","type":"AccordionItem[]","optional":false,"doc":""},{"name":"multiple","type":"boolean","optional":true,"doc":"Several sections open at once (default: one at a time)."},{"name":"value","type":"string[]","optional":true,"doc":"Open section ids (controlled)."},{"name":"defaultValue","type":"string[]","optional":true,"doc":""},{"name":"onChange","type":"(open: string[]) => void","optional":true,"doc":""},{"name":"variant","type":"\"cards\" | \"flush\"","optional":true,"doc":"`cards`: each section is a card; `flush`: hairlines only (inside a panel)."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Separator":[{"name":"orientation","type":"\"horizontal\" | \"vertical\"","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Text in the middle of the line (\"or\")."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"AspectRatio":[{"name":"ratio","type":"number","optional":true,"doc":"Width divided by height (default 16 / 9)."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Label":[{"name":"htmlFor","type":"string","optional":true,"doc":""},{"name":"required","type":"boolean","optional":true,"doc":""},{"name":"hint","type":"ReactNode","optional":true,"doc":"Muted text after the label (\"optional\")."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Toggle":[{"name":"pressed","type":"boolean","optional":true,"doc":""},{"name":"defaultPressed","type":"boolean","optional":true,"doc":""},{"name":"onChange","type":"(pressed: boolean) => void","optional":true,"doc":""},{"name":"icon","type":"IconName","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name; required when the toggle shows only an icon."},{"name":"size","type":"\"sm\" | \"md\"","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ToggleGroup":[{"name":"items","type":"(string | Option)[]","optional":false,"doc":""},{"name":"type","type":"\"single\" | \"multiple\"","optional":true,"doc":"`single`: at most one pressed; `multiple`: any number."},{"name":"value","type":"string[]","optional":true,"doc":""},{"name":"defaultValue","type":"string[]","optional":true,"doc":""},{"name":"onChange","type":"(value: string[]) => void","optional":true,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name of the group."},{"name":"size","type":"\"sm\" | \"md\"","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ButtonGroup":[{"name":"label","type":"string","optional":true,"doc":"Accessible name of the group."},{"name":"orientation","type":"\"horizontal\" | \"vertical\"","optional":true,"doc":""},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ScrollArea":[{"name":"height","type":"number | string","optional":true,"doc":"Fixed height of the scrolling box."},{"name":"maxHeight","type":"number | string","optional":true,"doc":""},{"name":"orientation","type":"\"vertical\" | \"horizontal\" | \"both\"","optional":true,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name; the box is a focusable region so it scrolls from the keyboard."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"HoverCard":[{"name":"trigger","type":"ReactNode","optional":false,"doc":"The element that opens the card on hover and keyboard focus."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"side","type":"\"top\" | \"bottom\"","optional":true,"doc":""},{"name":"align","type":"\"start\" | \"center\" | \"end\"","optional":true,"doc":""},{"name":"openDelay","type":"number","optional":true,"doc":""},{"name":"closeDelay","type":"number","optional":true,"doc":""},{"name":"width","type":"number","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Carousel":[{"name":"slides","type":"ReactNode[]","optional":false,"doc":""},{"name":"label","type":"string","optional":false,"doc":"Accessible name of the carousel."},{"name":"perView","type":"number","optional":true,"doc":"Slides visible at once on wide screens (1 on phones)."},{"name":"loop","type":"boolean","optional":true,"doc":"Next on the last slide goes back to the first."},{"name":"dots","type":"boolean","optional":true,"doc":"Dots under the slides."},{"name":"index","type":"number","optional":true,"doc":""},{"name":"defaultIndex","type":"number","optional":true,"doc":""},{"name":"onChange","type":"(index: number) => void","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Form":[{"name":"rules","type":"Record<string, FormRule>","optional":true,"doc":"Rules by field `name`. Fields show their message under the input."},{"name":"onSubmit","type":"(values: FormValues) => void","optional":true,"doc":"Called with the values (by `name`) when every rule passes."},{"name":"onInvalid","type":"(errors: Record<string, string>) => void","optional":true,"doc":"Called with the messages when the form is submitted with errors."},{"name":"errors","type":"Record<string, string>","optional":true,"doc":"Messages from the server, by field name (for example after a failed save)."},{"name":"children","type":"ReactNode","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"SelectMenu":[{"name":"options","type":"(string | SelectMenuOption)[]","optional":false,"doc":""},{"name":"value","type":"string","optional":true,"doc":""},{"name":"defaultValue","type":"string","optional":true,"doc":""},{"name":"onChange","type":"(value: string) => void","optional":true,"doc":""},{"name":"placeholder","type":"string","optional":true,"doc":""},{"name":"disabled","type":"boolean","optional":true,"doc":""},{"name":"label","type":"string","optional":true,"doc":""},{"name":"hint","type":"string","optional":true,"doc":""},{"name":"error","type":"string","optional":true,"doc":""},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Menubar":[{"name":"menus","type":"MenubarMenu[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the bar."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"NavigationMenu":[{"name":"items","type":"NavigationMenuItem[]","optional":false,"doc":""},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the navigation."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"Sidebar":[{"name":"groups","type":"SidebarGroup[]","optional":false,"doc":""},{"name":"current","type":"string","optional":true,"doc":"Id (or label) of the current page."},{"name":"onPick","type":"(id: string) => void","optional":true,"doc":""},{"name":"collapsed","type":"boolean","optional":true,"doc":"Icons only."},{"name":"defaultCollapsed","type":"boolean","optional":true,"doc":""},{"name":"onCollapsedChange","type":"(collapsed: boolean) => void","optional":true,"doc":""},{"name":"header","type":"ReactNode","optional":true,"doc":"Top of the sidebar (brand, workspace switcher)."},{"name":"footer","type":"ReactNode","optional":true,"doc":"Bottom of the sidebar (account, settings)."},{"name":"label","type":"string","optional":true,"doc":"Accessible name of the navigation."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}],"ResizablePanels":[{"name":"panels","type":"ReactNode[]","optional":false,"doc":""},{"name":"defaultSizes","type":"number[]","optional":true,"doc":"Start sizes in percent (default: equal)."},{"name":"min","type":"number","optional":true,"doc":"Smallest size of a panel in percent (default 10)."},{"name":"orientation","type":"\"horizontal\" | \"vertical\"","optional":true,"doc":""},{"name":"storageKey","type":"string","optional":true,"doc":"Remembers the sizes in localStorage under this key."},{"name":"labels","type":"string[]","optional":true,"doc":"Accessible names of the panels, used for the resize handles."},{"name":"className","type":"string","optional":true,"doc":"Extra class on the root element."},{"name":"style","type":"CSSProperties","optional":true,"doc":"Inline style on the root element."}]},"exports":{"admin":["RangeSlider","BarList","StatusSelect","TilePicker","LiveFeed","MapView","BalanceCard","ServiceMonitor","RunHistory","WebhookEndpoint","BackupList","BlockList","ReviewCard","CouponCard","LocaleField","LocaleSwitcher","WorkspaceSwitcher","ExportDialog","TwoFactorSetup","ColorField"],"agents":["AgentCard","AgentDetails","ChatList","ChatWorkspace","SearchHits","ThinkingLine","FeedbackRating","ActivityFeed","DailyBriefing","BudgetStop","MemoryViewer","OverrideDiff","TriggerList","ScheduleCalendar","OrgChart","VoiceMode"],"app-chrome":["MobileTabBar","PushNotification","ConfigPRSummary","ModuleRegistry"],"approvals":["ApprovalCard","ApprovalInbox"],"building-blocks":["Kbd","KeyValue","Spinner","Collapsible","ContextMenu","SplitPane","ResizablePanels","TreeView","InlineEdit"],"charts":["BarChart","LineChart","Sparkline","Donut","Legend","StackedBarChart","AreaChart","Heatmap","Funnel","Gauge"],"chat":["RichText","CodeBlock","TypingDots","AgentMark","ChatMessage","ToolCall","Reasoning","PromptStarters","ModelPicker","Composer","AgentStatus","RunTrace","UsageMeter"],"commerce":["ProductArt","money","Price","Rating","StockBadge","ProductCard","VariantPicker","CartLine","OrderSummary","CheckoutSteps","OrderTimeline","PlanCard"],"data-display":["CountUp","StatTile","Card","EventCard","Panel","PageHeader","Stagger","Table","ListRow","Timeline","FileRow","KanbanColumn","Progress","ProgressRing"],"data-table":["FilterBar","DataTable"],"developer":["LogView","CodeViewer","SecretField","MoneyInput","SettingsLayout","ShortcutsOverlay","StatCell","UptimeBar","Meter"],"feedback":["Skeleton","Menu","Modal","Toast","EmptyState","Alert","ConfirmDialog","Toaster"],"forms":["FormField","TextField","RadioGroup","Segmented","Combobox","TagInput","Calendar","DatePicker","NumberInput","Slider","OtpInput","FileDrop"],"foundation":["Button","IconButton","Chip","Badge","TintIcon","Avatar","AvatarStack"],"inputs":["Input","Select","Textarea","SearchField","Checkbox","Switch"],"form":["Form","useFieldError"],"select-menu":["SelectMenu"],"mail":["AccountSwitcher","InboxList","MailThread","SignatureEditor","AttachmentPreview"],"media":["ImageVariants","Gallery","BeforeAfter"],"navigation":["Tabs","Breadcrumb","Pagination","Menubar","NavigationMenu","Sidebar"],"operations":["ConnectionCard","ConnectionBanner","ServerCard","DeployPipeline","DnsRecordTable","DomainList","MailAuthStatus","LedgerTable","PnLStatement","ReceiptSuggestion"],"overlays":["Popover","Tooltip"],"previews":["EmailPreview","SearchAdPreview"],"primitives":["Accordion","Separator","AspectRatio","Label","Toggle","ToggleGroup","ButtonGroup","ScrollArea","HoverCard","Carousel"],"real-estate":["ListingCard","ResultsToolbar","SavedSearch","ValuationCard","EnergyClass"],"rich-text-editor":["RichTextEditor"],"shell":["TopBar","SideRail","Dock","FloatingLayout","Ambient","NAV","AppShell"],"site":["ChatLauncher","CookieConsent","AnnouncementBar"],"status":["StatusShape","StatusIndicator"],"system":["StatusScreen","AuthCard"],"workspace":["SubNav","Drawer","CommandPalette","NotificationList","Flow","FileThumb","FileCard","Wizard","PermissionMatrix","InvoicePreview","TransactionRow"]},"utilities":["cx","durationToken","initials","reduced","formatNumber","formatPercent","formatMoney","formatDate","formatLike","parseFormatted","numberSeparators","LOCALE","CURRENCY","WEEK_START","DEFAULT_STRINGS","SHARED_STRINGS","ADMIN_STRINGS","AGENTS_STRINGS","APP_CHROME_STRINGS","APPROVALS_STRINGS","BUILDING_BLOCKS_STRINGS","CHARTS_STRINGS","CHAT_STRINGS","COMMERCE_STRINGS","DATA_DISPLAY_STRINGS","DATA_TABLE_STRINGS","DEVELOPER_STRINGS","FEEDBACK_STRINGS","FORM_STRINGS","FORMS_STRINGS","MAIL_STRINGS","MEDIA_STRINGS","NAVIGATION_STRINGS","OPERATIONS_STRINGS","PREVIEWS_STRINGS","PRIMITIVES_STRINGS","REAL_ESTATE_STRINGS","RICH_TEXT_EDITOR_STRINGS","SELECT_MENU_STRINGS","SHELL_STRINGS","SITE_STRINGS","WORKSPACE_STRINGS","mergeStrings","mergeMessages","UIProvider","useUI","useUIStrings","useStrings","useFormat","formattersFor","Portal","startViewTransition","useMotion","usePresence","leaving","THEME_VARS","THEME_PRESETS","themeCss","themeStyle","parseThemeCss","brandTheme","foregroundFor","FOCUSABLE","useControlled","useId","useKeepInView","useScrollFocus","useMounted","useOutside","useTrap","useWidth","toast","subscribeToasts","getToasts","getServerToasts","ICON_PATHS","ICON_NAMES","Icon"],"icons":["thumbUp","thumbDown","arrowUp","heart","tag","cart","package","truck","plus","minus","check","x","more","search","bell","clock","eye","edit","trash","users","user","home","chart","mail","grid","share","menu","play","calendar","file","card","swap","send","download","book","chevronDown","chevronLeft","chevronRight","arrowUpRight","sparkle","filter","settings","lock","link","paperclip","star","flag","trend","globe","inbox","logout","moon","bot","code","branch","terminal","server","key","database","refresh","pause","archive","reply","shield","help","cpu","zap","folder","image","pin","map","upload","copy","bed","ruler","wallet","ban","list","layers","bold","italic","underline","olist","heading","quote","undo","redo","monitor","phone","expand","sun","cookie","webhook"]}