@lovett/ui 0.0.4 → 0.0.6

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.
Files changed (59) hide show
  1. package/dist/index.d.ts +2659 -0
  2. package/dist/index.js +14451 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/styles.css +636 -0
  5. package/dist/tokens.css +700 -0
  6. package/package.json +44 -17
  7. package/src/__tests__/button.test.tsx +137 -0
  8. package/src/__tests__/card.test.tsx +103 -0
  9. package/src/__tests__/dead-render.test.tsx +117 -0
  10. package/src/__tests__/input.test.tsx +134 -0
  11. package/src/__tests__/modal.test.tsx +154 -0
  12. package/src/__tests__/page-shell.test.tsx +128 -0
  13. package/src/__tests__/setup.ts +43 -0
  14. package/src/__tests__/token-shape.test.ts +193 -0
  15. package/src/allocation-sparkbar.tsx +90 -0
  16. package/src/card.tsx +1 -1
  17. package/src/collapsible-card.tsx +85 -0
  18. package/src/data-grid/table-body.tsx +8 -1
  19. package/src/dropdown-menu.tsx +1 -1
  20. package/src/floating-status-bar.tsx +112 -0
  21. package/src/folder-tree-picker.tsx +5 -6
  22. package/src/frame-stack.tsx +27 -10
  23. package/src/hero-form-card.tsx +2 -2
  24. package/src/icons/brand.tsx +187 -0
  25. package/src/index.ts +43 -0
  26. package/src/lib/clipboard.ts +14 -0
  27. package/src/lib/color.ts +111 -0
  28. package/src/meta-cell.tsx +52 -0
  29. package/src/meta-previews/MetaFeedCarousel.tsx +1 -1
  30. package/src/meta-previews/MetaFeedPreview.tsx +1 -1
  31. package/src/microsoft-logo.tsx +33 -0
  32. package/src/modal.tsx +77 -6
  33. package/src/pill-button.tsx +23 -5
  34. package/src/profile-section.tsx +40 -9
  35. package/src/sortable-table.tsx +5 -1
  36. package/src/styles.css +74 -0
  37. package/src/tabs.tsx +4 -0
  38. package/src/tag-chip-input.tsx +1 -1
  39. package/src/theme-v2.css +466 -0
  40. package/src/token-badge.tsx +92 -0
  41. package/src/tokens.css +181 -60
  42. package/src/v2/README.md +208 -0
  43. package/src/v2/__demo__/showcase.tsx +1045 -0
  44. package/src/v2/action.tsx +91 -0
  45. package/src/v2/callout.tsx +76 -0
  46. package/src/v2/document-section.tsx +82 -0
  47. package/src/v2/document-shell.tsx +0 -0
  48. package/src/v2/field-row.tsx +113 -0
  49. package/src/v2/icons.tsx +165 -0
  50. package/src/v2/index.ts +147 -0
  51. package/src/v2/layout.tsx +293 -0
  52. package/src/v2/progress-track.tsx +89 -0
  53. package/src/v2/stat-tile.tsx +129 -0
  54. package/src/v2/states.tsx +271 -0
  55. package/src/v2/status-pill.tsx +74 -0
  56. package/src/v2/theme.css +1861 -0
  57. package/src/v2/timeline.tsx +81 -0
  58. package/src/v2/tokens.ts +228 -0
  59. package/src/value-chip.tsx +76 -0
@@ -0,0 +1,2659 @@
1
+ import * as react from 'react';
2
+ import react__default, { HTMLAttributes, ReactNode, ButtonHTMLAttributes, InputHTMLAttributes, TextareaHTMLAttributes, CSSProperties, RefObject, ComponentProps, ComponentType, FormHTMLAttributes, FormEvent } from 'react';
3
+ import { Toaster as Toaster$1 } from 'sonner';
4
+ export { toast } from 'sonner';
5
+ import { DndContext, DragEndEvent } from '@dnd-kit/core';
6
+ import { ClassValue } from 'clsx';
7
+
8
+ /**
9
+ * Card — head/body/foot anatomy.
10
+ *
11
+ * Usage:
12
+ * <Card interactive onClick={...}>
13
+ * <Card.Head>
14
+ * <IconTile bg={...} fg={...}><Icon/></IconTile>
15
+ * <div className="flex-1 min-w-0">
16
+ * <Card.Title>Roof Replacement</Card.Title>
17
+ * <Card.Desc>Full tear-off and replacement...</Card.Desc>
18
+ * </div>
19
+ * </Card.Head>
20
+ * <Card.Body>
21
+ * <TagRow tags={['25-Year Warranty', 'Metal & Asphalt']}/>
22
+ * </Card.Body>
23
+ * <Card.Foot>
24
+ * <Card.Meta>...</Card.Meta>
25
+ * <Card.Actions>...</Card.Actions>
26
+ * </Card.Foot>
27
+ * </Card>
28
+ *
29
+ * `interactive` adds cursor + hover lift + reveal-on-hover for Card.Actions.
30
+ */
31
+ type DivProps = HTMLAttributes<HTMLDivElement>;
32
+ interface CardProps extends DivProps {
33
+ interactive?: boolean;
34
+ }
35
+ declare const CardRoot: react.ForwardRefExoticComponent<CardProps & react.RefAttributes<HTMLDivElement>>;
36
+ declare function Head({ className, ...rest }: DivProps): react.JSX.Element;
37
+ declare function Body({ className, ...rest }: DivProps): react.JSX.Element;
38
+ declare function Tray$1({ className, ...rest }: DivProps): react.JSX.Element;
39
+ declare function Foot({ className, ...rest }: DivProps): react.JSX.Element;
40
+ declare function Title({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
41
+ declare function Desc({ children, className, ...rest }: HTMLAttributes<HTMLDivElement> & {
42
+ clamp?: 2 | 3;
43
+ }): react.JSX.Element;
44
+ declare function Meta({ className, ...rest }: DivProps): react.JSX.Element;
45
+ declare function MetaItem({ icon, children, className, }: {
46
+ icon?: ReactNode;
47
+ children: ReactNode;
48
+ className?: string;
49
+ }): react.JSX.Element;
50
+ declare function Actions({ className, ...rest }: DivProps): react.JSX.Element;
51
+ interface ActionButtonProps$1 extends HTMLAttributes<HTMLButtonElement> {
52
+ danger?: boolean;
53
+ }
54
+ declare const ActionButton$1: react.ForwardRefExoticComponent<ActionButtonProps$1 & react.RefAttributes<HTMLButtonElement>>;
55
+ /**
56
+ * Icon-on-color tile used in card heads. Pass tone colors as inline rgb()
57
+ * strings so they can reference semantic tokens (info/success/etc.).
58
+ */
59
+ declare function IconTile({ bg, fg, size, className, children, }: {
60
+ bg: string;
61
+ fg: string;
62
+ size?: number;
63
+ className?: string;
64
+ children: ReactNode;
65
+ }): react.JSX.Element;
66
+ /** Tag row helper — reads `--tag-bg`/`--border` automatically. */
67
+ declare function TagRow({ tags, max, className, }: {
68
+ tags: string[];
69
+ max?: number;
70
+ className?: string;
71
+ }): react.JSX.Element;
72
+ type CardComponent = typeof CardRoot & {
73
+ Head: typeof Head;
74
+ Body: typeof Body;
75
+ Tray: typeof Tray$1;
76
+ Foot: typeof Foot;
77
+ Title: typeof Title;
78
+ Desc: typeof Desc;
79
+ Meta: typeof Meta;
80
+ MetaItem: typeof MetaItem;
81
+ Actions: typeof Actions;
82
+ ActionButton: typeof ActionButton$1;
83
+ IconTile: typeof IconTile;
84
+ TagRow: typeof TagRow;
85
+ };
86
+ declare const Card: CardComponent;
87
+
88
+ interface CollapsibleCardProps {
89
+ title: ReactNode;
90
+ /** Optional leading icon (rendered before the title). */
91
+ icon?: ReactNode;
92
+ /** Optional header content after the title (e.g. a count or subtitle). */
93
+ meta?: ReactNode;
94
+ /** Right-aligned header controls, kept outside the collapse toggle. */
95
+ actions?: ReactNode;
96
+ /** Start expanded (default) or collapsed. */
97
+ defaultOpen?: boolean;
98
+ /** Body padding utility; pass '' for full-bleed bodies (e.g. tables). */
99
+ contentClassName?: string;
100
+ children: ReactNode;
101
+ }
102
+ declare function CollapsibleCard({ title, icon, meta, actions, defaultOpen, contentClassName, children, }: CollapsibleCardProps): react.JSX.Element;
103
+
104
+ type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'destructive-soft' | 'icon' | 'toolbar';
105
+ type ButtonSize = 'sm' | 'md' | 'lg';
106
+ type ButtonShape = 'default' | 'circle';
107
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
108
+ variant?: ButtonVariant;
109
+ size?: ButtonSize;
110
+ /** ADR-026 D3 — `circle` only takes effect when `variant="icon"`. */
111
+ shape?: ButtonShape;
112
+ /** Show the loading spinner overlay; auto-disables the button. */
113
+ loading?: boolean;
114
+ /** Optional left-side icon. Forces icon-text gap from .btn. */
115
+ leadingIcon?: ReactNode;
116
+ /** Optional right-side icon. */
117
+ trailingIcon?: ReactNode;
118
+ }
119
+ declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLButtonElement>>;
120
+
121
+ interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {
122
+ /** One or more `<Button>` elements. */
123
+ children: ReactNode;
124
+ }
125
+ declare function ButtonGroup({ children, className, role, ...rest }: ButtonGroupProps): react.JSX.Element;
126
+
127
+ type InputSize = 'sm' | 'md' | 'lg';
128
+ interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'> {
129
+ inputSize?: InputSize;
130
+ error?: boolean;
131
+ /** Element rendered before the input — typically an icon. Static, no interaction. */
132
+ leadingAffix?: ReactNode;
133
+ /** Element rendered after the input. Pass an `<button class="affix action">` for clickable. */
134
+ trailingAffix?: ReactNode;
135
+ /** Override classes on the .input-shell wrapper. */
136
+ shellClassName?: string;
137
+ /**
138
+ * Show/hide affordance for `type="password"`. Manages visibility state
139
+ * internally and renders an Eye/EyeOff toggle in the trailing affix slot.
140
+ * No-op when `type !== "password"`. Caller-supplied `trailingAffix`
141
+ * wins — the toggle is only injected when the slot is otherwise empty.
142
+ */
143
+ showPasswordToggle?: boolean;
144
+ /**
145
+ * Numeric variant (ADR-076 D8) — renders the field in the monospace
146
+ * face with `tabular-nums` so digits align as the value changes.
147
+ * Pair with the existing `leadingAffix` (e.g. a `$`) / `trailingAffix`
148
+ * (e.g. a unit suffix) for calculator inputs. No new prefix/suffix
149
+ * props — affixes are the canonical adornment slots.
150
+ */
151
+ numeric?: boolean;
152
+ }
153
+ declare const Input: react.ForwardRefExoticComponent<InputProps & react.RefAttributes<HTMLInputElement>>;
154
+
155
+ interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
156
+ error?: boolean;
157
+ /** Override classes on the .input-shell wrapper. */
158
+ shellClassName?: string;
159
+ }
160
+ declare const Textarea: react.ForwardRefExoticComponent<TextareaProps & react.RefAttributes<HTMLTextAreaElement>>;
161
+
162
+ /**
163
+ * Modal — a centered floating panel on top of a backdrop. Built on
164
+ * `.ds-card-surface` so it inherits the premium card chrome (border,
165
+ * radius, multi-layer shadow with inner highlight in light mode).
166
+ *
167
+ * Two layout modes:
168
+ *
169
+ * 1. Flat children (back-compat) — pass content directly:
170
+ *
171
+ * <Modal title="..." onClose={...}>
172
+ * <p>body</p>
173
+ * <Button>action</Button>
174
+ * </Modal>
175
+ *
176
+ * 2. Shell / Tray pattern (recommended) — body in a recessed tray,
177
+ * action buttons on the outer-card bottom strip below the tray:
178
+ *
179
+ * <Modal title="..." onClose={...}>
180
+ * <Modal.Tray>
181
+ * <p>body content sits in a recessed shelf</p>
182
+ * <textarea ... />
183
+ * </Modal.Tray>
184
+ * <Modal.Footer>
185
+ * <Button variant="ghost" onClick={onClose}>Cancel</Button>
186
+ * <Button>Confirm</Button>
187
+ * </Modal.Footer>
188
+ * </Modal>
189
+ */
190
+ interface ModalProps {
191
+ isOpen: boolean;
192
+ onClose: () => void;
193
+ title: string;
194
+ children: ReactNode;
195
+ className?: string;
196
+ }
197
+ declare function Modal({ isOpen, onClose, title, children, className }: ModalProps): react.JSX.Element | null;
198
+ declare namespace Modal {
199
+ var Tray: ({ className, ...rest }: HTMLAttributes<HTMLDivElement>) => react.JSX.Element;
200
+ var Footer: ({ className, ...rest }: HTMLAttributes<HTMLDivElement>) => react.JSX.Element;
201
+ }
202
+
203
+ type Side$1 = 'right' | 'bottom';
204
+ type Anchor = 'viewport' | 'container';
205
+ interface FloatingDrawerProps {
206
+ isOpen: boolean;
207
+ onClose: () => void;
208
+ side: Side$1;
209
+ /** Width (`side="right"`) or height (`side="bottom"`) in px. Defaults: 520 / 480. */
210
+ size?: number;
211
+ /** When true, render a drag handle along the leading edge of the panel. */
212
+ resizable?: boolean;
213
+ /** Called as the user drags the resize handle. */
214
+ onResize?: (next: number) => void;
215
+ /** Header title — short, e.g. the record's name or the drawer's purpose. */
216
+ title?: ReactNode;
217
+ /** Optional element rendered to the right of the title, before close. */
218
+ titleSlot?: ReactNode;
219
+ /** Optional footer — action buttons. */
220
+ footer?: ReactNode;
221
+ children: ReactNode;
222
+ className?: string;
223
+ /** Optional className on the inner panel for fine-grained body styling. */
224
+ panelClassName?: string;
225
+ /**
226
+ * Modal mode renders a backdrop, locks body scroll, and closes on Esc
227
+ * (default — matches the card-grid drawer use case). Non-modal mode
228
+ * (used by GeoSearch's always-on results panel per Phase 7a) skips
229
+ * the backdrop + scroll lock + Esc handler so the user can interact
230
+ * with content behind the panel.
231
+ */
232
+ modal?: boolean;
233
+ /**
234
+ * Collapsed mode hides the body and footer entirely so the panel
235
+ * shrinks to just the header — no residual flex-1 body div, no
236
+ * seam between header and panel edge. Designed for non-modal,
237
+ * always-on consumers (GeoSearch) where the user toggles the panel
238
+ * between "show table" and "hide table" without dismissing it.
239
+ * The header keeps its border-bottom hidden in this state so it
240
+ * reads as a clean single-bar strip.
241
+ *
242
+ * When collapsed, callers should pass `size` matching the header's
243
+ * natural height (Phase 7a uses 44px); panel border + header chrome
244
+ * fit cleanly inside that height with `box-sizing: border-box`.
245
+ */
246
+ collapsed?: boolean;
247
+ /**
248
+ * `'viewport'` (default) anchors the drawer with `position: fixed` to
249
+ * the browser viewport — the card-grid drawer pattern. `'container'`
250
+ * anchors with `position: absolute` to the nearest positioned ancestor
251
+ * (the parent MUST establish a containing block, e.g.
252
+ * `position: relative`). Container mode scopes the drawer to a lens
253
+ * pane so it doesn't escape the surrounding chrome — used by
254
+ * GeoSearch in Phase 7a.
255
+ */
256
+ anchor?: Anchor;
257
+ /** Whether to render a default close (X) button after `titleSlot`. Default true. */
258
+ showClose?: boolean;
259
+ /** Override the header padding. Default: `px-4 py-2.5` — tighter than
260
+ * the right-drawer's body padding so the action bar feels integrated. */
261
+ headerClassName?: string;
262
+ }
263
+ declare function FloatingDrawer({ isOpen, onClose, side, size, resizable, onResize, title, titleSlot, footer, children, className, panelClassName, modal, anchor, showClose, headerClassName, collapsed, }: FloatingDrawerProps): react.JSX.Element;
264
+ /**
265
+ * DetailSection — small label/value layout for drawer body content.
266
+ * Ports verbatim from brand-ai-extension's detail-drawer.tsx.
267
+ */
268
+ declare function DetailSection({ label, children, className, }: {
269
+ label: string;
270
+ children: ReactNode;
271
+ className?: string;
272
+ }): react.JSX.Element;
273
+
274
+ type BrandLogoTileSize = 'xs' | 'sm' | 'md' | 'lg' | number;
275
+ interface BrandLogoTileProps {
276
+ /** Size token or a raw pixel number. Defaults to `md` (56px). */
277
+ size?: BrandLogoTileSize;
278
+ /** Image URL — logo, favicon, anything `<img>` can render. */
279
+ url?: string | null;
280
+ /**
281
+ * Brand name. Used as `alt` text and as the source for the
282
+ * single-character fallback when no `url` is provided (or the image
283
+ * fails to load).
284
+ */
285
+ name?: string;
286
+ /**
287
+ * Treat the image as a favicon — render it smaller, centred, without
288
+ * stretching to fill the tile. Auto-detected when the URL starts
289
+ * with the Google s2 favicon endpoint; pass `true`/`false` to override.
290
+ */
291
+ isFavicon?: boolean;
292
+ /**
293
+ * Adds a subtle accent-coloured radial glow above the tile. Used on
294
+ * the brand profile rail to give the logo a sense of presence
295
+ * against the slate frame.
296
+ */
297
+ accentGlow?: boolean;
298
+ /** Override the fallback character (defaults to `name[0]`). */
299
+ fallbackChar?: ReactNode;
300
+ /**
301
+ * Override the tile background. Use for contexts that have their own
302
+ * visual identity (e.g. the sidebar switcher's blue gradient). When
303
+ * omitted, the tile picks up the neutral card→overlay gradient that
304
+ * blends with surrounding chrome.
305
+ */
306
+ background?: string;
307
+ /**
308
+ * Override the fallback character / image-area text colour. Pair
309
+ * with `background` for branded variants (white text on coloured
310
+ * gradient). Defaults to the accent token.
311
+ */
312
+ color?: string;
313
+ /** Drop the 1px border (used when the tile sits on a strongly tinted bg). */
314
+ borderless?: boolean;
315
+ className?: string;
316
+ style?: CSSProperties;
317
+ }
318
+ declare function BrandLogoTile({ size, url, name, isFavicon, accentGlow, fallbackChar, background, color, borderless, className, style, }: BrandLogoTileProps): react.JSX.Element;
319
+
320
+ interface ActionButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
321
+ label: string;
322
+ }
323
+ declare function ActionButton({ label, className, children, ...rest }: ActionButtonProps): react.JSX.Element;
324
+ interface FootProps {
325
+ meta?: ReactNode;
326
+ link?: {
327
+ label: string;
328
+ onClick: () => void;
329
+ icon?: ReactNode;
330
+ };
331
+ }
332
+ /** FootItem — a single icon+text item inside the foot. */
333
+ declare function FootItem({ children }: {
334
+ children: ReactNode;
335
+ }): react.JSX.Element;
336
+ interface BaseProps {
337
+ /** DOM id and `data-section-id` for ChipNav scroll-spy. */
338
+ id: string;
339
+ /** Lucide icon (rendered ~17px). */
340
+ icon?: ReactNode;
341
+ /** Section title. */
342
+ title: string;
343
+ /** Section subtitle / description. */
344
+ subtitle?: string;
345
+ /** Optional foot meta + AI-action link. */
346
+ foot?: FootProps;
347
+ /** Extra padding tweak. */
348
+ className?: string;
349
+ /**
350
+ * Render as a SINGLE flat surface — header, divider, body — instead of
351
+ * the default frame-plus-inner-tray.
352
+ *
353
+ * Use this when the section is already inside a <FrameStack>. A framed
354
+ * section nested in a frame gives you three surface tiers (group frame →
355
+ * section frame → tray), which reads as boxes inside boxes. Flat is the
356
+ * shape <SectionCard> uses in the Social Spec builder, and it is what
357
+ * makes a group of sections read as one contained unit rather than as a
358
+ * stack of independent cards.
359
+ *
360
+ * Default (false) is unchanged, so every existing consumer is unaffected.
361
+ */
362
+ flat?: boolean;
363
+ }
364
+ interface StaticSectionProps extends BaseProps {
365
+ /** Right-side header actions (override default pencil/generate). */
366
+ actions?: ReactNode;
367
+ children: ReactNode;
368
+ editable?: undefined;
369
+ }
370
+ interface EditableSectionProps extends BaseProps {
371
+ editable: {
372
+ onSave: () => void | Promise<void>;
373
+ onCancel?: () => void;
374
+ isSaving?: boolean;
375
+ saveDisabled?: boolean;
376
+ /** Controlled editing state. If omitted, state is internal. */
377
+ isEditing?: boolean;
378
+ onEditingChange?: (editing: boolean) => void;
379
+ /** Extra actions before the pencil in view mode (e.g. Generate). */
380
+ extraViewActions?: ReactNode;
381
+ };
382
+ /** Render-prop child receives the current editing state. */
383
+ children: (isEditing: boolean) => ReactNode;
384
+ actions?: undefined;
385
+ }
386
+ type ProfileSectionProps = StaticSectionProps | EditableSectionProps;
387
+ declare function ProfileSection(props: ProfileSectionProps): react.JSX.Element;
388
+ type ProfileSectionComponent = typeof ProfileSection & {
389
+ ActionButton: typeof ActionButton;
390
+ FootItem: typeof FootItem;
391
+ };
392
+ declare const PS: ProfileSectionComponent;
393
+
394
+ interface ChipNavItem {
395
+ id: string;
396
+ label: string;
397
+ }
398
+ interface ChipNavProps {
399
+ items: ChipNavItem[];
400
+ /**
401
+ * Element (ref or CSS selector) used as the scroll container for both
402
+ * the IntersectionObserver root and the smooth-scroll target. Falls
403
+ * back to the document if omitted.
404
+ */
405
+ scrollContainer?: React.RefObject<HTMLElement | null> | string;
406
+ /** Pixel offset for sticky positioning (default: 0 — caller manages). */
407
+ stickyTop?: number;
408
+ /** Pixels above the section to offset when click-scrolling. */
409
+ scrollOffset?: number;
410
+ /**
411
+ * Optional content rendered on the right side of the bar, separated
412
+ * from the chips by a flexible spacer. Use for page-level actions
413
+ * that should stay pinned to the sticky bar (e.g. Export, Generate).
414
+ */
415
+ actions?: ReactNode;
416
+ /** Extra Tailwind classes on the outer wrapper. */
417
+ className?: string;
418
+ /** Inline style on the outer wrapper. */
419
+ style?: CSSProperties;
420
+ /**
421
+ * Background painted on the sticky wrapper so page content scrolling
422
+ * behind the chip pill gets masked cleanly.
423
+ *
424
+ * - `'none'` (default) — wrapper is transparent. Correct inside a Card,
425
+ * inside a tray, or anywhere the chip nav sits over a non-page surface.
426
+ * - `'page'` — wrapper paints `rgb(var(--main-content-bg))`. Correct
427
+ * when the chip nav is mounted directly under the AppHeader, against
428
+ * the page background, and needs to mask scrolling content beneath.
429
+ *
430
+ * Default was page-colored in the original brand-ai port; that bled
431
+ * into Card / Modal / Tray contexts as a discordant horizontal strip.
432
+ * Phase 2 callers that mount ChipNav against the page background must
433
+ * pass `surface="page"`.
434
+ */
435
+ surface?: 'none' | 'page';
436
+ /**
437
+ * Controlled mode: when provided, the parent owns the active chip.
438
+ * Scroll-spy is disabled and `onItemClick` is invoked instead of the
439
+ * default smooth-scroll behavior. Phase 7a GeoSearch uses this to
440
+ * drive mode-switch tabs (not scroll targets).
441
+ */
442
+ activeId?: string;
443
+ /**
444
+ * Optional click handler. When provided, takes precedence over the
445
+ * built-in scroll-to-section behavior. Required for controlled-mode
446
+ * (`activeId`) use.
447
+ */
448
+ onItemClick?: (id: string) => void;
449
+ /**
450
+ * `'sticky'` (default) — the wrapper is `sticky top-{stickyTop}` for
451
+ * the canonical scroll-spy use case.
452
+ * `'static'` — wrapper is `relative` with no inset; the consumer owns
453
+ * positioning. Used by GeoSearch (Phase 7a) to mount ChipNav as a
454
+ * floating island absolute-positioned above the map.
455
+ */
456
+ position?: 'sticky' | 'static';
457
+ /**
458
+ * `'comfortable'` (default, 32px chip height) — the canonical
459
+ * brand-profile lens density.
460
+ * `'compact'` (24px chip height) — slim variant for tight chrome
461
+ * like the `<PageHeaderHost />` center slot, where the surrounding
462
+ * header is only 48px tall. Reduces inner pill padding and chip
463
+ * vertical padding by 2px each.
464
+ */
465
+ density?: 'comfortable' | 'compact';
466
+ }
467
+ declare function ChipNav({ items, scrollContainer, stickyTop, scrollOffset, actions, className, style, surface, activeId: activeIdProp, onItemClick, position, density, }: ChipNavProps): react.JSX.Element;
468
+
469
+ interface CompletionRingProps {
470
+ /** 0–100. Values outside the range are clamped. */
471
+ percent: number;
472
+ /** Outer dimension in px. Default 30. */
473
+ size?: number;
474
+ /** Ring stroke width in px. Default scales with size (size / 12). */
475
+ strokeWidth?: number;
476
+ /**
477
+ * What to render in the middle. Defaults to the percent number with
478
+ * a `%` suffix. Pass `false`/`null` to hide. Pass a node (icon, text)
479
+ * to fully override.
480
+ */
481
+ label?: ReactNode | false;
482
+ /** Convenience: false hides the default label. Same as `label={false}`. */
483
+ showLabel?: boolean;
484
+ /** Track colour CSS value. Defaults to `--surface-overlay-strong`. */
485
+ trackColor?: string;
486
+ /** Fill (progress arc) colour. Defaults to `--accent`. */
487
+ fillColor?: string;
488
+ /** Extra classes / inline style on the wrapper. */
489
+ className?: string;
490
+ style?: CSSProperties;
491
+ }
492
+ declare function CompletionRing({ percent, size, strokeWidth, label, showLabel, trackColor, fillColor, className, style, }: CompletionRingProps): react.JSX.Element;
493
+
494
+ /**
495
+ * MicrosoftLogo — the Microsoft four-square brand mark, for "Sign in with
496
+ * Microsoft" SSO buttons (ADR-124 D8). Lucide has no Microsoft glyph, so this
497
+ * is a bespoke SVG primitive — which `@lovett/ui` is the one allowed home for
498
+ * (CLAUDE.md §2/§6). The four square colours are Microsoft's required brand
499
+ * colours (per their identity guidelines); they are intrinsic to the mark, not
500
+ * theme tokens, exactly like BrandLogoTile's imagery — so they live here as
501
+ * literals rather than `tokens.css` variables.
502
+ */
503
+ interface MicrosoftLogoProps {
504
+ /** Square size in px. Default 18 (matches a control-height button glyph). */
505
+ size?: number;
506
+ className?: string;
507
+ }
508
+ declare function MicrosoftLogo({ size, className }: MicrosoftLogoProps): react.JSX.Element;
509
+
510
+ /**
511
+ * Brand platform glyphs — real mono (currentColor) marks for the ad platforms
512
+ * the Social Spec builds for, replacing approximate Lucide stand-ins (Ghost for
513
+ * Snapchat, Home for Nextdoor, Music for TikTok, Megaphone for Meta). ADR-135.
514
+ *
515
+ * `@lovett/ui` is the one allowed home for bespoke SVG definitions (CLAUDE.md
516
+ * §2/§6) — same rationale as `MicrosoftLogo`. Path data is extracted from the
517
+ * owner's MIT-licensed fork github.com/edwinlov3tt/logos-apps (upstream: the
518
+ * ln-dev7 "logos" collection); each glyph is normalized to a single-colour
519
+ * `currentColor` silhouette so it adapts to its context (active pill = white,
520
+ * inactive tab = grey) — full-colour marks would clash inside the red pill.
521
+ * Extraction choices were picked by visual review; Facebook, Messenger and
522
+ * TikTok silhouettes are true path-boolean derivations of the official marks
523
+ * (disc⊖f, bubble⊖bolt, glitch-layer union — computed offline with paper.js,
524
+ * baked here as static path data).
525
+ *
526
+ * Marks are trademarks of their platforms — used nominatively to refer to the
527
+ * platforms themselves, exactly like the platform-chrome in the ad previews.
528
+ */
529
+ interface BrandIconProps {
530
+ /** Square box size in px; the glyph letterboxes inside (non-square viewBoxes
531
+ * centre via preserveAspectRatio). Default 16 — a tab/affix glyph. */
532
+ size?: number;
533
+ className?: string;
534
+ }
535
+ /** Meta brand glyph (mono, currentColor). */
536
+ declare function BrandMeta({ size, className }: BrandIconProps): react.JSX.Element;
537
+ /** Facebook brand glyph (mono, currentColor). */
538
+ declare function BrandFacebook({ size, className }: BrandIconProps): react.JSX.Element;
539
+ /** Instagram brand glyph (mono, currentColor). */
540
+ declare function BrandInstagram({ size, className }: BrandIconProps): react.JSX.Element;
541
+ /** Messenger brand glyph (mono, currentColor). */
542
+ declare function BrandMessenger({ size, className }: BrandIconProps): react.JSX.Element;
543
+ /** LinkedIn brand glyph (mono, currentColor). */
544
+ declare function BrandLinkedIn({ size, className }: BrandIconProps): react.JSX.Element;
545
+ /** Nextdoor brand glyph (mono, currentColor). */
546
+ declare function BrandNextdoor({ size, className }: BrandIconProps): react.JSX.Element;
547
+ /** Pinterest brand glyph (mono, currentColor). */
548
+ declare function BrandPinterest({ size, className }: BrandIconProps): react.JSX.Element;
549
+ /** Snapchat brand glyph (mono, currentColor). */
550
+ declare function BrandSnapchat({ size, className }: BrandIconProps): react.JSX.Element;
551
+ /** TikTok brand glyph (mono, currentColor). */
552
+ declare function BrandTikTok({ size, className }: BrandIconProps): react.JSX.Element;
553
+ /** Keyed map for registry-style resolution (e.g. the Social Spec tactic icons). */
554
+ declare const BRAND_ICONS: {
555
+ readonly meta: typeof BrandMeta;
556
+ readonly facebook: typeof BrandFacebook;
557
+ readonly instagram: typeof BrandInstagram;
558
+ readonly messenger: typeof BrandMessenger;
559
+ readonly linkedin: typeof BrandLinkedIn;
560
+ readonly nextdoor: typeof BrandNextdoor;
561
+ readonly pinterest: typeof BrandPinterest;
562
+ readonly snapchat: typeof BrandSnapchat;
563
+ readonly tiktok: typeof BrandTikTok;
564
+ };
565
+ type BrandIconKey = keyof typeof BRAND_ICONS;
566
+
567
+ interface ChoroplethProps {
568
+ /** `radius` → concentric circles; `isochrone` → concentric polygons. */
569
+ mode?: 'radius' | 'isochrone';
570
+ className?: string;
571
+ style?: CSSProperties;
572
+ }
573
+ declare function Choropleth({ mode, className, style }: ChoroplethProps): react.JSX.Element;
574
+
575
+ type Crumb = {
576
+ label: string;
577
+ to?: string;
578
+ };
579
+ interface PageShellProps {
580
+ /** Breadcrumb chain — last item renders as the bold current label.
581
+ * For back-compat, `breadcrumb` + `title` are still accepted. */
582
+ crumbs?: Crumb[];
583
+ /** Legacy convenience: single parent crumb (label only). */
584
+ breadcrumb?: string;
585
+ /** Legacy: current-page title. Used as the last crumb when `crumbs` isn't provided. */
586
+ title?: string;
587
+ /** Right-side actions in the page header. */
588
+ actions?: ReactNode;
589
+ /**
590
+ * Content for the horizontally-centered slot in `<PageHeaderHost />`
591
+ * (Phase 7a). Relayed via the slot portal alongside `crumbs` /
592
+ * `actions`. When mounted in slot-host mode, the content sits in a
593
+ * dedicated absolutely-centered slot between the breadcrumb cluster
594
+ * and the trailing chrome, so it stays centered regardless of left/
595
+ * right slot widths. Ignored when PageShell renders its own inline
596
+ * header (no `<PageHeaderHost />` mounted).
597
+ *
598
+ * Phase 7a's GeoSearch lens lifts its mode-tabs ChipNav into this
599
+ * slot so the AppHeader carries: sidebar-toggle | crumbs | tabs |
600
+ * theme-toggle. Other lenses leave it unset.
601
+ */
602
+ headerCenter?: ReactNode;
603
+ children: ReactNode;
604
+ /** Override content padding if needed. Default leaves room for the sticky-blend header. */
605
+ contentClassName?: string;
606
+ }
607
+ declare function PageHeaderSlotProvider({ children }: {
608
+ children: ReactNode;
609
+ }): react.JSX.Element;
610
+ /**
611
+ * Page shell — promoted from brand-ai-extension Phase 2a (ADR-009 Decision 7).
612
+ *
613
+ * Self-contained: provides a scrollable region whose sticky-blend header
614
+ * gains a translucent backdrop-blur background once you scroll past a few
615
+ * px. Breadcrumbs render at top-left, page actions at top-right.
616
+ *
617
+ * When mounted inside <PageHeaderSlotProvider> with a <PageHeaderHost />
618
+ * elsewhere, PageShell portals its header content into the host instead of
619
+ * rendering its own bar — the workspace uses this to merge the lens crumbs
620
+ * onto the same row as the workspace sidebar toggle.
621
+ */
622
+ declare function PageShell({ crumbs, breadcrumb, title, actions, headerCenter, children, contentClassName, }: PageShellProps): react.JSX.Element;
623
+
624
+ /**
625
+ * PageHeaderHost — single workspace-level header bar that the active
626
+ * <PageShell> portals its breadcrumbs/actions into. Renders the `leading`
627
+ * slot (typically a sidebar toggle button) on the same row, so the lens's
628
+ * chrome doesn't stack as a separate sticky band. The `trailing` slot
629
+ * sits past the portal area for persistent right-cluster chrome (e.g.
630
+ * a theme toggle) that should never collide with per-page actions.
631
+ *
632
+ * Sticky-blend: subscribes to the scroll container the active PageShell
633
+ * registered with the slot so the same scroll-past-4px fade-in still fires.
634
+ */
635
+ declare function PageHeaderHost({ leading, center, trailing, padX, }: {
636
+ leading?: ReactNode;
637
+ /**
638
+ * Horizontal padding utility for the header bar. Defaults to `px-3`
639
+ * (the toggle-anchored layout). Callers with no `leading` slot (e.g. a
640
+ * single-lens skin that hides the sidebar toggle) pass a larger inset
641
+ * like `px-7` so the breadcrumbs line up with the content column below
642
+ * instead of hugging the edge. ADR-061 D3.
643
+ */
644
+ padX?: string;
645
+ /**
646
+ * Horizontally-centered slot — Phase 7a (ADR-012 Decision 12
647
+ * deviation). Renders absolutely positioned at the geometric center
648
+ * of the header so width changes in the breadcrumb (left) and
649
+ * trailing (right) clusters never shift its position. The active
650
+ * `<PageShell>` may also portal content into this slot via its
651
+ * `headerCenter` prop; both routes target the same DOM node.
652
+ *
653
+ * Wrapped in a `max-w` so on narrow viewports the center stays
654
+ * within the breadcrumb and trailing clusters' visual lanes
655
+ * (truncates internally before overlapping).
656
+ */
657
+ center?: ReactNode;
658
+ trailing?: ReactNode;
659
+ }): react.JSX.Element;
660
+ /**
661
+ * Scrollable wrapper that exposes its scrollRef via a render prop so
662
+ * the consuming header (PageShell's internal PageHeader) can subscribe
663
+ * to scroll for the stuck-state effect.
664
+ */
665
+ declare function AppScroll({ children, className, }: {
666
+ children: (scrollRef: RefObject<HTMLDivElement | null>) => ReactNode;
667
+ className?: string;
668
+ }): react.JSX.Element;
669
+
670
+ type SonnerToasterProps = ComponentProps<typeof Toaster$1>;
671
+ /**
672
+ * Token-aware `<Toaster />` shell.
673
+ *
674
+ * Defaults: top-right, 12px gap, 5s duration. The toastOptions wire
675
+ * Sonner's internal CSS variables to our `--toast-*` tokens so toasts
676
+ * adopt the design system surface color, border, and text color.
677
+ *
678
+ * Apps can override any prop (position, duration, theme, etc.) by
679
+ * passing it through.
680
+ */
681
+ declare function Toaster(props: SonnerToasterProps): react.JSX.Element;
682
+
683
+ declare function SectionLabel({ children }: {
684
+ children: ReactNode;
685
+ }): react.JSX.Element;
686
+ declare function IdentityLabel({ children }: {
687
+ children: ReactNode;
688
+ }): react.JSX.Element;
689
+ declare function IdentityValue({ children, className, }: {
690
+ children: ReactNode;
691
+ className?: string;
692
+ }): react.JSX.Element;
693
+ declare function TextInput({ value, onChange, placeholder, autoFocus, autoComplete, name, }: {
694
+ value: string;
695
+ onChange: (v: string) => void;
696
+ placeholder?: string | undefined;
697
+ autoFocus?: boolean | undefined;
698
+ /** Forwarded so the browser's autofill / password manager can identify the field. */
699
+ autoComplete?: string | undefined;
700
+ /** Forwarded so the field participates in form submission / autofill heuristics. */
701
+ name?: string | undefined;
702
+ }): react.JSX.Element;
703
+ declare function TextareaInput({ value, onChange, placeholder, autoFocus, autoComplete, name, rows, }: {
704
+ value: string;
705
+ onChange: (v: string) => void;
706
+ placeholder?: string | undefined;
707
+ autoFocus?: boolean | undefined;
708
+ autoComplete?: string | undefined;
709
+ name?: string | undefined;
710
+ rows?: number | undefined;
711
+ }): react.JSX.Element;
712
+ declare function EmptyPlaceholder({ label, description, onAdd, }: {
713
+ label: string;
714
+ description?: string | undefined;
715
+ onAdd?: (() => void) | undefined;
716
+ }): react.JSX.Element;
717
+ declare function ListItem({ children }: {
718
+ children: ReactNode;
719
+ }): react.JSX.Element;
720
+
721
+ type Strategy = 'vertical' | 'horizontal' | 'grid';
722
+ declare function SortableList<T>({ items, getId, onReorder, strategy, children, }: {
723
+ items: T[];
724
+ getId: (item: T, index: number) => string;
725
+ onReorder: (items: T[]) => void;
726
+ strategy?: Strategy;
727
+ children: ReactNode;
728
+ }): react.JSX.Element;
729
+ declare function SortableItem({ id, children, }: {
730
+ id: string;
731
+ children: (props: {
732
+ dragHandleProps: HTMLAttributes<HTMLElement>;
733
+ setNodeRef: (node: HTMLElement | null) => void;
734
+ style: CSSProperties;
735
+ isDragging: boolean;
736
+ }) => ReactNode;
737
+ }): react.JSX.Element;
738
+ declare function DragHandle({ dragHandleProps, className, }: {
739
+ dragHandleProps: HTMLAttributes<HTMLElement>;
740
+ className?: string;
741
+ }): react.JSX.Element;
742
+
743
+ interface EmptyStateProps {
744
+ /** Lucide icon (or any ReactNode) rendered inside the 56×56 tile.
745
+ * Pass at h-7 w-7 (28px) for visual parity with the canonical sites. */
746
+ icon: ReactNode;
747
+ /** Headline text. */
748
+ title: string;
749
+ /** Optional supporting copy. Kept under max-w-md by default. */
750
+ description?: string;
751
+ /** Optional action buttons row. Wraps with gap-2. */
752
+ actions?: ReactNode;
753
+ /** Optional className override on the outer container — use sparingly
754
+ * (e.g. when the parent already provides vertical padding). */
755
+ className?: string;
756
+ }
757
+ declare function EmptyState({ icon, title, description, actions, className, }: EmptyStateProps): react.JSX.Element;
758
+
759
+ interface RadioGroupProps<T extends string = string> {
760
+ /** Form name — required for proper radio semantics + form submission. */
761
+ name: string;
762
+ /** Currently selected value. */
763
+ value: T | null;
764
+ /** Fires when the user picks a different option. */
765
+ onChange: (next: T) => void;
766
+ /** <Radio> children. */
767
+ children: ReactNode;
768
+ /** Visually-hidden label for screen readers. Renders as <legend>. */
769
+ ariaLabel?: string;
770
+ /** Layout direction. Defaults to vertical. */
771
+ orientation?: 'horizontal' | 'vertical';
772
+ /** Extra classes on the wrapping <fieldset>. */
773
+ className?: string;
774
+ /** Disable every child radio. */
775
+ disabled?: boolean;
776
+ }
777
+ declare function RadioGroup<T extends string = string>({ name, value, onChange, children, ariaLabel, orientation, className, disabled, }: RadioGroupProps<T>): react.JSX.Element;
778
+ interface RadioProps {
779
+ /** Value emitted to the group's onChange when picked. */
780
+ value: string;
781
+ /** Visible label. */
782
+ label: ReactNode;
783
+ /** Optional secondary text (smaller, --text-tertiary). */
784
+ description?: ReactNode;
785
+ /** Disable this specific radio (overrides group default). */
786
+ disabled?: boolean;
787
+ /** Extra classes on the outer <label>. */
788
+ className?: string;
789
+ }
790
+ declare const Radio: react.ForwardRefExoticComponent<RadioProps & react.RefAttributes<HTMLInputElement>>;
791
+
792
+ interface OptionTileProps {
793
+ selected: boolean;
794
+ onSelect: () => void;
795
+ inputType: 'radio' | 'checkbox';
796
+ /** Required for radio (shared name across the group). */
797
+ inputName: string | undefined;
798
+ label: ReactNode;
799
+ description: ReactNode | undefined;
800
+ disabled: boolean | undefined;
801
+ className: string | undefined;
802
+ }
803
+ declare function OptionTile({ selected, onSelect, inputType, inputName, label, description, disabled, className, }: OptionTileProps): react.JSX.Element;
804
+ interface OptionTileGroupOption<T extends string> {
805
+ value: T;
806
+ label: ReactNode;
807
+ description?: ReactNode;
808
+ disabled?: boolean;
809
+ }
810
+ type OptionTileGroupProps<T extends string = string> = {
811
+ type: 'radio';
812
+ name: string;
813
+ value: T | null;
814
+ onChange: (next: T) => void;
815
+ options: OptionTileGroupOption<T>[];
816
+ ariaLabel?: string;
817
+ columns?: number;
818
+ className?: string;
819
+ } | {
820
+ type: 'checkbox';
821
+ value: T[];
822
+ onChange: (next: T[]) => void;
823
+ options: OptionTileGroupOption<T>[];
824
+ ariaLabel?: string;
825
+ columns?: number;
826
+ className?: string;
827
+ /** Optional shared `name` attr on the underlying checkboxes (rarely needed). */
828
+ name?: string;
829
+ };
830
+ declare function OptionTileGroup<T extends string = string>(props: OptionTileGroupProps<T>): react.JSX.Element;
831
+
832
+ type MetricCardTone = 'neutral' | 'accent' | 'success' | 'warning' | 'destructive' | 'info';
833
+ interface MetricCardProps {
834
+ /** Headline value (large, semibold, tone-colored). Numbers should
835
+ * use `font-mono` for tabular alignment when relevant. */
836
+ value: ReactNode;
837
+ /** Small descriptive label below the value. */
838
+ label: string;
839
+ /** Optional tone — colors the value text. Background stays neutral. */
840
+ tone?: MetricCardTone;
841
+ /** Optional icon shown to the left of the value (h-5 w-5 expected). */
842
+ icon?: ReactNode;
843
+ /**
844
+ * Where the label sits relative to the value. Default `'bottom'`
845
+ * (value-then-label — the original shape; existing consumers are
846
+ * unchanged). `'top'` renders a small uppercase label above the
847
+ * value — the KPI-chip shape used by Calculator Shell v2 (ADR-076
848
+ * D8). The value size is unchanged in both positions.
849
+ */
850
+ labelPosition?: 'top' | 'bottom';
851
+ /** Optional className override for the outer container. */
852
+ className?: string;
853
+ }
854
+ declare function MetricCard({ value, label, tone, icon, labelPosition, className, }: MetricCardProps): react.JSX.Element;
855
+
856
+ /**
857
+ * ProgressBar — labeled fill bar.
858
+ *
859
+ * Promoted in ADR-020 (Phase 3a Discovery report generation, first
860
+ * committed consumer). Cross-batch consumers: Phase 8 (AI-suggest
861
+ * progress if surfaced), Phase 12 (generation step in wizard).
862
+ *
863
+ * Indeterminate variant for unknown-duration progress (animated
864
+ * stripe sweep); determinate variant for known-percent progress.
865
+ *
866
+ * Token discipline: all colors via public tokens.
867
+ */
868
+ interface ProgressBarProps {
869
+ /** Optional label above the bar. */
870
+ label?: string;
871
+ /** 0-100 percent (determinate). Omit for indeterminate animation. */
872
+ percent?: number;
873
+ /** Optional secondary text shown right of the label. Often "X%" or a status message. */
874
+ status?: string;
875
+ /** Track height in px (default 8). */
876
+ height?: number;
877
+ }
878
+ declare function ProgressBar({ label, percent, status, height }: ProgressBarProps): react.JSX.Element;
879
+
880
+ /**
881
+ * StepLoader — a sequential, multi-step loading indicator (3-5 steps). Each step
882
+ * dwells for a believable duration, then the next begins (completed → check, current
883
+ * → spinner, pending → dot), connected by a vertical rail.
884
+ *
885
+ * The defining behavior: the FINAL step is HELD until the real work reports `done`
886
+ * AND a minimum dwell has elapsed — so a backend that returns faster than usual still
887
+ * plays the full, believable sequence instead of snapping away. Non-final steps always
888
+ * advance on their own timers (independent of `done`).
889
+ *
890
+ * <StepLoader steps={[...]} done={isReady} onComplete={() => show()} />
891
+ */
892
+ interface StepLoaderStep {
893
+ label: string;
894
+ /** Override the default dwell for this step (ms). */
895
+ durationMs?: number;
896
+ }
897
+ interface StepLoaderProps {
898
+ /** The ordered steps (3-5). Strings use the default dwell. */
899
+ steps: ReadonlyArray<string | StepLoaderStep>;
900
+ /** The real work finished — gates completion of the FINAL step only. */
901
+ done?: boolean;
902
+ /** Fired once the sequence has played out AND `done` is true (+ `minLastStepMs`). */
903
+ onComplete?: () => void;
904
+ /** Default per-step dwell (ms) for steps without an explicit duration. */
905
+ defaultStepMs?: number;
906
+ /** Minimum time the final step stays before completing — keeps a fast result believable. */
907
+ minLastStepMs?: number;
908
+ className?: string;
909
+ }
910
+ declare function StepLoader({ steps, done, onComplete, defaultStepMs, minLastStepMs, className, }: StepLoaderProps): react.JSX.Element;
911
+
912
+ type BadgeTone = 'neutral' | 'accent' | 'success' | 'warning' | 'destructive' | 'info';
913
+ interface BadgeProps {
914
+ tone: BadgeTone;
915
+ children: ReactNode;
916
+ /** Optional leading icon — sized 12-14px expected. */
917
+ icon?: ReactNode;
918
+ /** Leading status dot in the tone color (gray-ui chip style). Ignored
919
+ * when `icon` is provided. */
920
+ dot?: boolean;
921
+ /** Size variant. `sm` = compact (10px), `md` = default (11px),
922
+ * `lg` = roomier category-chip (12px, taller). */
923
+ size?: 'sm' | 'md' | 'lg';
924
+ /** Extra classes merged onto the outer span (no longer replaces base). */
925
+ className?: string;
926
+ }
927
+ declare function Badge({ tone, children, icon, dot, size, className, }: BadgeProps): react.JSX.Element;
928
+
929
+ interface TokenBadgeProps {
930
+ /** Public token name to tint from, e.g. `--folder-teal`, `--accent`,
931
+ * `--success`. Must include the leading `--`. */
932
+ colorVar: string;
933
+ children: ReactNode;
934
+ /** `soft` = tinted fill (default). `outline` = transparent fill + tinted
935
+ * border. */
936
+ variant?: 'soft' | 'outline';
937
+ /** Optional leading status dot in the token color. Ignored when `icon` set. */
938
+ dot?: boolean;
939
+ /** Optional leading icon (12–14px expected). */
940
+ icon?: ReactNode;
941
+ /** `sm` (10px) · `md` (11px, default) · `lg` (12px, taller). */
942
+ size?: 'sm' | 'md' | 'lg';
943
+ /** Pill (`full`) vs squared category tag (`tag`, default). */
944
+ shape?: 'tag' | 'pill';
945
+ /** Uppercase + wider tracking for short category codes (PRG, YT…). */
946
+ uppercase?: boolean;
947
+ className?: string;
948
+ }
949
+ declare function TokenBadge({ colorVar, children, variant, dot, icon, size, shape, uppercase, className, }: TokenBadgeProps): react.JSX.Element;
950
+
951
+ interface ValueChipProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value'> {
952
+ children: ReactNode;
953
+ /** Makes the chip copyable. The toast reads "Copied <copyLabel>". */
954
+ copyValue?: string;
955
+ copyLabel?: string;
956
+ /** `sm` for dense rows (10px), `md` default (11px). */
957
+ size?: 'sm' | 'md';
958
+ /** Stretch to fill a flex row — the equal-thirds hex/rgb/hsl layout. */
959
+ fill?: boolean;
960
+ }
961
+ declare const ValueChip: react.ForwardRefExoticComponent<ValueChipProps & react.RefAttributes<HTMLButtonElement>>;
962
+
963
+ interface MetaCellProps extends HTMLAttributes<HTMLDivElement> {
964
+ label: ReactNode;
965
+ children: ReactNode;
966
+ /** Optional leading glyph in the label row. */
967
+ icon?: ReactNode;
968
+ }
969
+ declare const MetaCell: react.ForwardRefExoticComponent<MetaCellProps & react.RefAttributes<HTMLDivElement>>;
970
+
971
+ declare function copyText(value: string, label?: string): Promise<void>;
972
+
973
+ /**
974
+ * Colour maths for brand-palette surfaces — hex to rgb/hsl, WCAG contrast.
975
+ *
976
+ * Promoted from `apps/extension/src/sidepanel/color-utils.ts` when the
977
+ * workspace's Brand Profile grew the same colour cards the extension's
978
+ * Colors tab already had. Two consumers, one implementation — the same
979
+ * rule the platform-chrome tokens were consolidated under.
980
+ *
981
+ * Pure functions, no deps, no DOM.
982
+ */
983
+ type RgbTuple = [number, number, number];
984
+ /** Parse `#abc` or `#aabbcc` (with or without the hash) into an rgb tuple. */
985
+ declare function hexToRgbTuple(hex: string): RgbTuple;
986
+ /** `"24, 119, 242"` — display form, the inside of an `rgb()`. */
987
+ declare function rgbString(hex: string): string;
988
+ /**
989
+ * `"rgb(24, 119, 242)"` — a complete CSS value, for copying.
990
+ *
991
+ * Lives here rather than being assembled at the call site because the
992
+ * token-boundary lint reads `rgb(...)` in app code as a raw colour
993
+ * literal — correctly, since it cannot tell a clipboard string from a
994
+ * style. packages/ui is the exempt substrate, so this is its job.
995
+ */
996
+ declare function rgbCss(hex: string): string;
997
+ /** `"217° 89% 52%"` — display form, not a CSS value. */
998
+ declare function hslString(hex: string): string;
999
+ /** WCAG 2.1 relative luminance (sRGB). */
1000
+ declare function relativeLuminance(hex: string): number;
1001
+ /** Contrast ratio between two relative luminances. */
1002
+ declare function contrastRatio(a: number, b: number): number;
1003
+ /**
1004
+ * Whether black or white text reads better ON this colour, and at what
1005
+ * ratio. Used to pick a legible foreground for a swatch banner.
1006
+ */
1007
+ declare function bestTextOn(hex: string): {
1008
+ onLight: boolean;
1009
+ ratio: number;
1010
+ };
1011
+ interface WcagBadge {
1012
+ label: string;
1013
+ pass: boolean;
1014
+ }
1015
+ /**
1016
+ * The three standard thresholds, scored against the BEST of black-or-white
1017
+ * text on this swatch. It answers "can any text sit on this colour", which
1018
+ * is the useful question for a brand palette — not "is this colour itself
1019
+ * readable", which depends on a background the palette doesn't know.
1020
+ */
1021
+ declare function wcagBadges(hex: string): WcagBadge[];
1022
+
1023
+ /**
1024
+ * AllocationSparkbar — a compact per-period bar strip.
1025
+ *
1026
+ * New in ADR-123 (Budget Flighting redesign). Renders one bar per period
1027
+ * (month), height proportional to the value; a zero/paused period renders
1028
+ * short + dashed in a muted tone. Each bar can tint from its own token (e.g. a
1029
+ * flight-group color), falling back to `baseColorVar`. Optional single-letter
1030
+ * labels sit under each bar.
1031
+ *
1032
+ * Token discipline: colors come only from public token *names* via
1033
+ * `rgb(var(--token) / a)`. No literals. Layout sizes use the spacing scale.
1034
+ */
1035
+ interface AllocationSparkbarProps {
1036
+ /** Per-period amounts. A value of 0 is treated as paused (short + dashed). */
1037
+ values: number[];
1038
+ /** Base token name for normal bars, e.g. `--folder-teal`. Includes `--`. */
1039
+ baseColorVar: string;
1040
+ /** Optional per-period token name (e.g. a flight-group color). `null`/
1041
+ * `undefined` falls back to `baseColorVar`. Length should match `values`. */
1042
+ colorVars?: (string | null | undefined)[];
1043
+ /** Optional short labels under each bar (e.g. `['J','F','M']`). */
1044
+ labels?: string[];
1045
+ /** Optional per-bar tooltip text (native `title`). */
1046
+ titles?: string[];
1047
+ /** Strip height in px. Default 36. */
1048
+ height?: number;
1049
+ className?: string;
1050
+ }
1051
+ declare function AllocationSparkbar({ values, baseColorVar, colorVars, labels, titles, height, className, }: AllocationSparkbarProps): react.JSX.Element;
1052
+
1053
+ type FloatingStatusTone = 'neutral' | 'accent' | 'success' | 'warning' | 'destructive' | 'info';
1054
+ interface FloatingStatusBarProps {
1055
+ /** Tone for the status dot + label color. */
1056
+ tone: FloatingStatusTone;
1057
+ /** Primary status label (tone-colored, bold). */
1058
+ label: ReactNode;
1059
+ /** Optional muted text after the label (e.g. a percentage). */
1060
+ meta?: ReactNode;
1061
+ /** Optional summary node shown after a vertical divider (e.g. `$X → $Y`). */
1062
+ summary?: ReactNode;
1063
+ /** Optional single action element (typically a secondary Button). */
1064
+ action?: ReactNode;
1065
+ /** Override the fixed positioning (e.g. to embed in a story). Default fixed
1066
+ * bottom-center. */
1067
+ className?: string;
1068
+ }
1069
+ declare function FloatingStatusBar({ tone, label, meta, summary, action, className, }: FloatingStatusBarProps): react.JSX.Element;
1070
+
1071
+ interface StatsGridProps {
1072
+ children: ReactNode;
1073
+ /** Minimum column width in px (controls how many cards per row at a
1074
+ * given viewport). Default 220 — comfortable for 4-up at md+. */
1075
+ minColumnWidth?: number;
1076
+ /** Optional className override for the outer container. */
1077
+ className?: string;
1078
+ }
1079
+ declare function StatsGrid({ children, minColumnWidth, className, }: StatsGridProps): react.JSX.Element;
1080
+
1081
+ interface Column<T> {
1082
+ /** Unique id; doubles as the sort key. Use a `keyof T` string or
1083
+ * an arbitrary string if `sortValue` is provided. */
1084
+ id: string;
1085
+ label: string;
1086
+ /** How to render a cell for a row. */
1087
+ accessor: (row: T) => ReactNode;
1088
+ /** Optional explicit value used for sorting (defaults to the
1089
+ * accessor's stringified output for sort comparison). */
1090
+ sortValue?: (row: T) => string | number | null | undefined;
1091
+ sortable?: boolean;
1092
+ /** Right-align + tabular-nums for numeric columns. Default false. */
1093
+ numeric?: boolean;
1094
+ /** Header tooltip text. */
1095
+ tooltip?: string;
1096
+ /** Width as a CSS value (e.g. `120px` or `20%`). */
1097
+ width?: string;
1098
+ }
1099
+ interface SortableTableProps<T> {
1100
+ columns: Column<T>[];
1101
+ rows: T[];
1102
+ /** Function returning a stable React key for each row. */
1103
+ rowKey: (row: T) => string;
1104
+ /** Initial sort. */
1105
+ initialSort?: {
1106
+ columnId: string;
1107
+ dir: 'asc' | 'desc';
1108
+ };
1109
+ /** Row click handler. */
1110
+ onRowClick?: (row: T) => void;
1111
+ /** Rendered when rows.length === 0. */
1112
+ emptyState?: ReactNode;
1113
+ /** Zebra stripe alternating rows. Default true. */
1114
+ zebra?: boolean;
1115
+ /** Compact density (smaller padding). Default false. */
1116
+ compact?: boolean;
1117
+ }
1118
+ declare function SortableTable<T>({ columns, rows, rowKey, initialSort, onRowClick, emptyState, zebra, compact, }: SortableTableProps<T>): react.JSX.Element;
1119
+
1120
+ /**
1121
+ * Pagination — "Showing X-Y of Z" + prev/next buttons.
1122
+ *
1123
+ * Promoted in ADR-022 (Phase 3c Discovery reports table, first
1124
+ * committed consumer). Cross-batch consumers: Phase 3b contacts
1125
+ * (when >50), Phase 8 keywords table, Phase 12 SEMSpec keywords tab.
1126
+ *
1127
+ * Stateless — parent owns currentPage state and provides onPageChange.
1128
+ *
1129
+ * Token discipline: all colors via public tokens.
1130
+ */
1131
+ interface PaginationProps {
1132
+ /** 1-indexed current page. */
1133
+ currentPage: number;
1134
+ /** Items per page. */
1135
+ pageSize: number;
1136
+ /** Total item count across all pages. */
1137
+ totalItems: number;
1138
+ onPageChange: (page: number) => void;
1139
+ }
1140
+ declare function Pagination({ currentPage, pageSize, totalItems, onPageChange, }: PaginationProps): react.JSX.Element | null;
1141
+
1142
+ interface Tab {
1143
+ id: string;
1144
+ label: string;
1145
+ /** Optional Lucide icon (h-3.5 w-3.5 to h-4 w-4 expected). */
1146
+ icon?: ReactNode;
1147
+ /** Optional count badge rendered after the label. */
1148
+ count?: number;
1149
+ disabled?: boolean;
1150
+ /** Accessible name for ICON-ONLY tabs (label rendered empty) — e.g. the
1151
+ * responsive platform switcher's compact tier (ADR-135). */
1152
+ ariaLabel?: string;
1153
+ }
1154
+ interface TabsProps {
1155
+ tabs: Tab[];
1156
+ activeTabId: string;
1157
+ onTabChange: (id: string) => void;
1158
+ /** Compact (12px label) or default (13px). */
1159
+ size?: 'sm' | 'md';
1160
+ /** Underline (default) or pill active-state. */
1161
+ variant?: 'underline' | 'pill';
1162
+ /** Stretch the tablist to full width with equal-width, centred tabs. */
1163
+ stretch?: boolean;
1164
+ className?: string;
1165
+ }
1166
+ declare function Tabs({ tabs, activeTabId, onTabChange, size, variant, stretch, className, }: TabsProps): react.JSX.Element;
1167
+
1168
+ interface PillButtonProps {
1169
+ active: boolean;
1170
+ onClick: () => void;
1171
+ children: ReactNode;
1172
+ /** Size variant. `sm` = compact (12px), `md` = default (13px). */
1173
+ size?: 'sm' | 'md';
1174
+ /** Optional active-state tone. Default = accent. */
1175
+ tone?: BadgeTone;
1176
+ /** Optional leading icon (12-14px). */
1177
+ icon?: ReactNode;
1178
+ disabled?: boolean;
1179
+ }
1180
+ declare function PillButton({ active, onClick, children, size, tone, icon, disabled, }: PillButtonProps): react.JSX.Element;
1181
+
1182
+ interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange' | 'checked'> {
1183
+ checked?: boolean;
1184
+ indeterminate?: boolean;
1185
+ onCheckedChange?: (checked: boolean) => void;
1186
+ }
1187
+ declare const Checkbox: react.ForwardRefExoticComponent<CheckboxProps & react.RefAttributes<HTMLInputElement>>;
1188
+
1189
+ type Align = 'start' | 'center' | 'end';
1190
+ type Side = 'top' | 'bottom';
1191
+ interface DropdownMenuProps {
1192
+ /** Controlled open state. Omit for internal state. */
1193
+ open?: boolean;
1194
+ /** Fires whenever open transitions. */
1195
+ onOpenChange?: (open: boolean) => void;
1196
+ defaultOpen?: boolean;
1197
+ children: ReactNode;
1198
+ }
1199
+ declare function DropdownMenu({ open: controlledOpen, onOpenChange, defaultOpen, children, }: DropdownMenuProps): react.JSX.Element;
1200
+ interface DropdownMenuTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
1201
+ /** Skip the default secondary-Button styling. Useful when wrapping
1202
+ * an existing Button primitive. */
1203
+ unstyled?: boolean;
1204
+ }
1205
+ /**
1206
+ * Default trigger styled per ADR-026 D4 (DQ-4) — inherits the
1207
+ * secondary Button visual (rounded-xl + `--bg-card` fill + 1px
1208
+ * `--border`). Set `unstyled` to wrap your own button shape.
1209
+ */
1210
+ declare function DropdownMenuTrigger({ unstyled, className, onClick, children, ...rest }: DropdownMenuTriggerProps): react.JSX.Element;
1211
+ interface DropdownMenuContentProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
1212
+ children: ReactNode;
1213
+ /** Horizontal alignment relative to the trigger. Default `start`. */
1214
+ align?: Align;
1215
+ /** Pixel offset along the alignment axis. Default 0. */
1216
+ alignOffset?: number;
1217
+ /** Place above (`top`) or below (`bottom`) the trigger. Default `bottom`. */
1218
+ side?: Side;
1219
+ /** Pixel offset along the side axis. Default 6. */
1220
+ sideOffset?: number;
1221
+ }
1222
+ declare function DropdownMenuContent({ children, align, alignOffset, side, sideOffset, className, style, ...rest }: DropdownMenuContentProps): react.ReactPortal | null;
1223
+ interface DropdownMenuItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
1224
+ variant?: 'default' | 'destructive';
1225
+ /** Pad-left compensation so items with no leading icon align with
1226
+ * items that have one. */
1227
+ inset?: boolean;
1228
+ }
1229
+ declare function DropdownMenuItem({ variant, inset, className, onClick, children, ...rest }: DropdownMenuItemProps): react.JSX.Element;
1230
+ interface DropdownMenuCheckboxItemProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> {
1231
+ checked?: boolean;
1232
+ onCheckedChange?: (checked: boolean) => void;
1233
+ }
1234
+ declare function DropdownMenuCheckboxItem({ checked, onCheckedChange, className, children, onClick, disabled, ...rest }: DropdownMenuCheckboxItemProps): react.JSX.Element;
1235
+ declare function DropdownMenuSeparator({ className, ...rest }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
1236
+ interface DropdownMenuLabelProps extends HTMLAttributes<HTMLDivElement> {
1237
+ inset?: boolean;
1238
+ }
1239
+ declare function DropdownMenuLabel({ inset, className, ...rest }: DropdownMenuLabelProps): react.JSX.Element;
1240
+ declare function DropdownMenuGroup({ className, ...rest }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
1241
+
1242
+ /**
1243
+ * DataGrid public types.
1244
+ *
1245
+ * Ported 2026-05-18 from gray-ui-csm
1246
+ * https://github.com/Jason-uxui/gray-ui-csm/blob/baf8346ac0c1160282e0b173de1bffc594d9c9c3/components/data-grid/types.ts
1247
+ * License: MIT (see packages/ui/licenses/gray-ui-csm-LICENSE.txt)
1248
+ * Workspace ADR: ADR-025
1249
+ *
1250
+ * BRING_AS_IS per ADR-025 bucket table — surface API unchanged from
1251
+ * source so the source repo's docs still apply.
1252
+ */
1253
+
1254
+ type DataGridIcon = ComponentType<{
1255
+ className?: string;
1256
+ }>;
1257
+ type DataGridRowBase = {
1258
+ id: string;
1259
+ };
1260
+ type DataGridColumn<ColumnId extends string> = {
1261
+ id: ColumnId;
1262
+ label: string;
1263
+ icon: DataGridIcon;
1264
+ defaultWidth: number;
1265
+ minWidth?: number;
1266
+ /** Per-column sort opt-out (ADR-027). Defaults to true when DataGrid's
1267
+ * `onSortChange` is provided; false when not. Set explicitly to disable
1268
+ * sorting for columns that don't have a meaningful comparator. */
1269
+ sortable?: boolean;
1270
+ /** Pin this column to the left or right edge so it stays visible while
1271
+ * the grid scrolls horizontally. Pinned columns render opaque with a
1272
+ * soft scroll-shadow on their inner edge. Place left-pinned columns
1273
+ * first and right-pinned columns last. The built-in select column is
1274
+ * always pinned left. */
1275
+ pin?: 'left' | 'right';
1276
+ };
1277
+ /** Row-sort state (ADR-027). DataGrid renders the chevron + ARIA per
1278
+ * this state but does NOT sort rows itself — parent passes pre-sorted
1279
+ * `rows`. */
1280
+ type DataGridSortState<ColumnId extends string> = {
1281
+ columnId: ColumnId;
1282
+ dir: 'asc' | 'desc';
1283
+ };
1284
+ type DataGridDrawerPanelProps<Row, ColumnId extends string> = {
1285
+ drawerRow: Row | null;
1286
+ drawerRowIndex: number;
1287
+ drawerRowCount: number;
1288
+ drawerColumn: DataGridColumn<ColumnId> | null;
1289
+ drawerCellValue: ReactNode;
1290
+ getRowLabel: (row: Row) => string;
1291
+ isEditableColumn: (columnId: ColumnId) => boolean;
1292
+ isEmptyValue: (value: ReactNode) => boolean;
1293
+ updateRow: (rowId: string, updater: (row: Row) => Row) => void;
1294
+ openPreviousRow: () => void;
1295
+ openNextRow: () => void;
1296
+ closeDrawer: () => void;
1297
+ /** Whether the drawer should be visible. The default <DrawerPanel>
1298
+ * reads this to drive its <FloatingDrawer isOpen> binding; consumer
1299
+ * render-overrides may use it the same way. Added in the workspace
1300
+ * port (DEVIATION-5) to remove the `@base-ui/react` context coupling
1301
+ * the source relied on. */
1302
+ isOpen: boolean;
1303
+ };
1304
+ type DataGridToolbarRenderProps<ColumnId extends string> = {
1305
+ visibleRowCount: number;
1306
+ selectedRowIds: string[];
1307
+ selectedRowCount: number;
1308
+ allVisibleRowsSelected: boolean;
1309
+ someVisibleRowsSelected: boolean;
1310
+ onToggleAllRows: (checked: boolean) => void;
1311
+ clearSelection: () => void;
1312
+ showSummaries: boolean;
1313
+ onShowSummariesChange: (next: boolean) => void;
1314
+ visibleColumns: DataGridColumn<ColumnId>[];
1315
+ visibleColumnIds: ColumnId[];
1316
+ hiddenColumns: DataGridColumn<ColumnId>[];
1317
+ toggleColumnVisibility: (columnId: ColumnId, visible: boolean) => void;
1318
+ optionsSensors: NonNullable<ComponentProps<typeof DndContext>['sensors']>;
1319
+ onOptionColumnDragEnd: (event: DragEndEvent) => void;
1320
+ optionsDndContextId: string;
1321
+ };
1322
+ type DataGridProps<Row extends DataGridRowBase, ColumnId extends string> = {
1323
+ rows: Row[];
1324
+ columns: DataGridColumn<ColumnId>[];
1325
+ getRowLabel: (row: Row) => string;
1326
+ renderCell: (row: Row, column: DataGridColumn<ColumnId>) => ReactNode;
1327
+ isEditableColumn: (columnId: ColumnId) => boolean;
1328
+ getCellEditValue: (row: Row, columnId: ColumnId) => string;
1329
+ applyCellEdit: (row: Row, columnId: ColumnId, nextValue: string) => Row;
1330
+ getDrawerCellValue: (row: Row, columnId: ColumnId) => ReactNode;
1331
+ canOpenDrawer?: (columnId: ColumnId) => boolean;
1332
+ renderSummary?: (column: DataGridColumn<ColumnId>, visibleRows: Row[]) => ReactNode;
1333
+ renderDrawerPanel?: (props: DataGridDrawerPanelProps<Row, ColumnId>) => ReactNode;
1334
+ renderToolbar?: (props: DataGridToolbarRenderProps<ColumnId>) => ReactNode;
1335
+ onToolbarPropsChange?: (props: DataGridToolbarRenderProps<ColumnId>) => void;
1336
+ onOpenDrawerCell?: (cell: EditingCell<ColumnId>) => void;
1337
+ /** Drawer renders in modal mode (backdrop + Esc + scroll lock).
1338
+ * Defaults to `false` per the source's contextless API. */
1339
+ drawerModal?: boolean;
1340
+ /** Pixel width of the drawer panel; passed through to FloatingDrawer.
1341
+ * Defaults to 520 (the FloatingDrawer side="right" default). */
1342
+ drawerSize?: number;
1343
+ stickySummaryFooter?: boolean;
1344
+ /** Keep the header row pinned to the top while the body scrolls. Renders
1345
+ * opaque with a scroll-shadow once scrolled. Default false. */
1346
+ stickyHeader?: boolean;
1347
+ /** Stretch the table to fill its container width via a trailing flex
1348
+ * "spacer" column that absorbs leftover space — real columns keep their
1349
+ * exact widths, so column resize stays independent. When the columns
1350
+ * outgrow the viewport the spacer collapses to 0 and the grid scrolls.
1351
+ * Default false. */
1352
+ fillWidth?: boolean;
1353
+ fillAvailableHeight?: boolean;
1354
+ tableContainerClassName?: string;
1355
+ onRowsChange?: (rows: Row[]) => void;
1356
+ /** Currently-sorted column + direction (ADR-027). Parent owns the
1357
+ * state and passes pre-sorted `rows`. When `null` or omitted, no
1358
+ * chevron renders (all columns show the unsorted indicator). */
1359
+ sort?: DataGridSortState<ColumnId> | null;
1360
+ /** Header click handler (ADR-027). Receives the clicked column id;
1361
+ * parent decides next direction (typical: toggle if same column,
1362
+ * else default to 'asc'). When omitted, headers are non-interactive
1363
+ * for sort purposes. Drag-to-reorder still works. */
1364
+ onSortChange?: (columnId: ColumnId) => void;
1365
+ };
1366
+ type EditingCell<ColumnId extends string> = {
1367
+ rowId: string;
1368
+ columnId: ColumnId;
1369
+ originRect?: {
1370
+ x: number;
1371
+ y: number;
1372
+ width: number;
1373
+ height: number;
1374
+ };
1375
+ };
1376
+
1377
+ declare function DataGrid<Row extends DataGridRowBase, ColumnId extends string>({ rows, columns, getRowLabel, renderCell, isEditableColumn, getCellEditValue, applyCellEdit, getDrawerCellValue, canOpenDrawer, renderSummary, renderDrawerPanel, renderToolbar, onToolbarPropsChange, onOpenDrawerCell, drawerModal, drawerSize, stickySummaryFooter, stickyHeader, fillWidth, fillAvailableHeight, tableContainerClassName, onRowsChange, sort, onSortChange, }: DataGridProps<Row, ColumnId>): react.JSX.Element;
1378
+
1379
+ interface DataGridToolbarProps {
1380
+ /** Left slot — typically a <SearchBar>. Stretches up to a comfortable
1381
+ * reading width, full-width when stacked. */
1382
+ search?: ReactNode;
1383
+ /** Right slot — filter / columns / view-toggle controls. */
1384
+ children?: ReactNode;
1385
+ className?: string;
1386
+ }
1387
+ declare function DataGridToolbar({ search, children, className, }: DataGridToolbarProps): react.JSX.Element;
1388
+
1389
+ type DataGridColumnOptionsMenuProps<ColumnId extends string> = Pick<DataGridToolbarRenderProps<ColumnId>, 'showSummaries' | 'onShowSummariesChange' | 'visibleColumns' | 'visibleColumnIds' | 'hiddenColumns' | 'toggleColumnVisibility' | 'optionsSensors' | 'onOptionColumnDragEnd' | 'optionsDndContextId'> & {
1390
+ triggerLabel?: string;
1391
+ };
1392
+ declare function DataGridColumnOptionsMenu<ColumnId extends string>({ showSummaries, onShowSummariesChange, visibleColumns, visibleColumnIds, hiddenColumns, toggleColumnVisibility, optionsSensors, onOptionColumnDragEnd, optionsDndContextId, triggerLabel, }: DataGridColumnOptionsMenuProps<ColumnId>): react.JSX.Element;
1393
+
1394
+ type DataGridDragOverlayProps<ColumnId extends string> = {
1395
+ dragOverlayColumn: DataGridColumn<ColumnId> | null;
1396
+ columnWidths: Record<ColumnId, number>;
1397
+ dragOverlayHeight: number;
1398
+ };
1399
+ declare function DataGridDragOverlay<ColumnId extends string>({ dragOverlayColumn, columnWidths, dragOverlayHeight, }: DataGridDragOverlayProps<ColumnId>): react.JSX.Element | null;
1400
+ type DataGridDropIndicatorProps = {
1401
+ dropIndicatorLeft: number | null;
1402
+ dragTableRect: {
1403
+ top: number;
1404
+ height: number;
1405
+ } | null;
1406
+ };
1407
+ declare function DataGridDropIndicator({ dropIndicatorLeft, dragTableRect, }: DataGridDropIndicatorProps): react.JSX.Element | null;
1408
+
1409
+ interface DropdownButtonItem {
1410
+ /** Stable id for keying. If omitted, the index is used (avoid for
1411
+ * long lists where order can change). */
1412
+ id?: string;
1413
+ /** Icon shown in the leading tile (Lucide or any ReactNode). */
1414
+ icon?: ReactNode;
1415
+ /** Primary label of the item. */
1416
+ label: string;
1417
+ /** Optional helper text rendered below the label. */
1418
+ description?: string;
1419
+ /** Visual variant — `default` or `destructive`. */
1420
+ variant?: 'default' | 'destructive';
1421
+ disabled?: boolean;
1422
+ onSelect: () => void;
1423
+ }
1424
+ interface DropdownButtonProps {
1425
+ /** Trigger label (e.g., "New Keyword Plan"). */
1426
+ label: string;
1427
+ /** Trigger variant — defaults to primary to match the screenshot. */
1428
+ variant?: ButtonVariant;
1429
+ /** Trigger size — passes through to the .btn size class. */
1430
+ size?: ButtonSize;
1431
+ /** Optional leading icon on the trigger (e.g., a `+`). */
1432
+ leadingIcon?: ReactNode;
1433
+ /** Override the default chevron-down trailing icon if needed. */
1434
+ trailingIcon?: ReactNode;
1435
+ /** Menu items rendered in order. */
1436
+ items: DropdownButtonItem[];
1437
+ /** Disable the trigger entirely. */
1438
+ disabled?: boolean;
1439
+ /** Show the .is-loading state on the trigger. */
1440
+ loading?: boolean;
1441
+ /** Forwarded to the trigger's className for layout overrides. */
1442
+ className?: string;
1443
+ /** Popover alignment relative to the trigger. Default `end`
1444
+ * (right-aligned) to match the reference screenshot. */
1445
+ align?: DropdownMenuContentProps['align'];
1446
+ /** Popover side — `top` or `bottom`. Default `bottom`. */
1447
+ side?: DropdownMenuContentProps['side'];
1448
+ /** Popover gap from the trigger. Default 6. */
1449
+ sideOffset?: DropdownMenuContentProps['sideOffset'];
1450
+ /** Constrain the popover width. Default `min(20rem, calc(100vw-16px))`. */
1451
+ menuWidth?: string;
1452
+ /** Optional aria-label for the trigger if the visible label is
1453
+ * ambiguous (e.g., icon-only triggers). */
1454
+ triggerAriaLabel?: string;
1455
+ /** Optional id of the currently-active item. When set, that item
1456
+ * renders a trailing check mark — turns the DropdownButton into a
1457
+ * single-select filter/picker. */
1458
+ activeId?: string;
1459
+ /** When true, the trigger gets an accent-tinted treatment — signals
1460
+ * an engaged filter. Pair with `activeId` for filter-chip use. */
1461
+ active?: boolean;
1462
+ }
1463
+ declare function DropdownButton({ label, variant, size, leadingIcon, trailingIcon, items, disabled, loading, className, align, side, sideOffset, menuWidth, triggerAriaLabel, activeId, active, }: DropdownButtonProps): react.JSX.Element;
1464
+
1465
+ interface AccordionProps {
1466
+ /** Ids of the currently-open sections. */
1467
+ openIds: string[];
1468
+ /** Fired with a section id when its header is clicked. */
1469
+ onToggle: (id: string) => void;
1470
+ children: ReactNode;
1471
+ className?: string;
1472
+ }
1473
+ declare function Accordion({ openIds, onToggle, children, className }: AccordionProps): react.JSX.Element;
1474
+ interface AccordionSectionProps {
1475
+ /** Stable id — matched against the Accordion's `openIds`. */
1476
+ id: string;
1477
+ title: string;
1478
+ /** Muted one-line summary under the title. */
1479
+ summary?: ReactNode;
1480
+ /** Optional node right of the title — e.g. a "New" <Badge>. */
1481
+ badge?: ReactNode;
1482
+ /** When true the leading indicator is an accent filled check;
1483
+ * otherwise an empty ring. */
1484
+ complete?: boolean;
1485
+ children: ReactNode;
1486
+ }
1487
+ declare function AccordionSection({ id, title, summary, badge, complete, children, }: AccordionSectionProps): react.JSX.Element;
1488
+ declare const AccordionCompound: typeof Accordion & {
1489
+ Section: typeof AccordionSection;
1490
+ };
1491
+
1492
+ interface MenuButtonItem {
1493
+ /** Stable id for keying. Falls back to the index. */
1494
+ id?: string;
1495
+ /** Optional leading icon (Lucide or any ReactNode), sized ~14px. */
1496
+ icon?: ReactNode;
1497
+ /** Primary label. */
1498
+ label: string;
1499
+ /** Optional right-aligned shortcut hint, e.g. "⌘C". */
1500
+ shortcut?: string;
1501
+ /** Visual variant. `destructive` tints the row red. */
1502
+ variant?: 'default' | 'destructive';
1503
+ disabled?: boolean;
1504
+ onSelect: () => void;
1505
+ }
1506
+ /** A divider between menu items. */
1507
+ interface MenuButtonSeparator {
1508
+ type: 'separator';
1509
+ }
1510
+ type MenuButtonEntry = MenuButtonItem | MenuButtonSeparator;
1511
+ interface MenuButtonProps {
1512
+ /** Trigger label — e.g. "File", "Edit". */
1513
+ label: string;
1514
+ /** Menu entries, in order. */
1515
+ items: MenuButtonEntry[];
1516
+ /** Optional leading icon on the trigger. */
1517
+ leadingIcon?: ReactNode;
1518
+ /** Disable the trigger entirely. */
1519
+ disabled?: boolean;
1520
+ /** Popover alignment relative to the trigger. Default `start`. */
1521
+ align?: DropdownMenuContentProps['align'];
1522
+ /** Popover width. Default `14rem`. */
1523
+ menuWidth?: string;
1524
+ /** Forwarded to the trigger className for layout overrides. */
1525
+ className?: string;
1526
+ /** aria-label for the trigger when the visible label is ambiguous. */
1527
+ triggerAriaLabel?: string;
1528
+ }
1529
+ declare function MenuButton({ label, items, leadingIcon, disabled, align, menuWidth, className, triggerAriaLabel, }: MenuButtonProps): react.JSX.Element;
1530
+
1531
+ /**
1532
+ * CopyField — an input/output field with a copy button pinned to the
1533
+ * top-right corner.
1534
+ *
1535
+ * The workbench input/output pattern from the tool prototypes: a single
1536
+ * field a user types into and that a transform writes its result back
1537
+ * into ("output replaces input"), with a one-click copy affordance
1538
+ * always in reach.
1539
+ *
1540
+ * Wraps <Textarea> (`multiline`, the default) or <Input>. Owns the copy
1541
+ * interaction end-to-end — clipboard write, a transient ✓ confirmation,
1542
+ * and a success/error toast. The field content is right-padded so text
1543
+ * never slides under the button.
1544
+ *
1545
+ * CopyField does NOT render a char/line count — that is caller state;
1546
+ * render it in the surrounding footer.
1547
+ *
1548
+ * Workspace ADR: ADR-031 Decision 2.
1549
+ */
1550
+ interface CopyFieldProps {
1551
+ /** Field value (controlled). */
1552
+ value: string;
1553
+ /** Change handler. Omit for a read-only output field. */
1554
+ onChange?: (value: string) => void;
1555
+ /** Multi-line textarea (default) or single-line input. */
1556
+ multiline?: boolean;
1557
+ /** Textarea row count. Ignored when `multiline` is false. */
1558
+ rows?: number;
1559
+ placeholder?: string;
1560
+ readOnly?: boolean;
1561
+ /** Visible field label — wires `<label htmlFor>`. */
1562
+ label?: string;
1563
+ /** Keep the label for assistive tech but hide it visually. */
1564
+ hideLabel?: boolean;
1565
+ /** Field id. Auto-generated when omitted. */
1566
+ id?: string;
1567
+ /** Toast message on a successful copy. */
1568
+ copyToastMessage?: string;
1569
+ className?: string;
1570
+ }
1571
+ declare function CopyField({ value, onChange, multiline, rows, placeholder, readOnly, label, hideLabel, id, copyToastMessage, className, }: CopyFieldProps): react.JSX.Element;
1572
+
1573
+ /**
1574
+ * TagChipInput — type-to-create chips with autocomplete.
1575
+ *
1576
+ * Behavior:
1577
+ * • Comma or Enter commits the current draft into a chip.
1578
+ * • Backspace on an empty input removes the last chip.
1579
+ * • Autocomplete suggestions surface as a popover; filtered by the
1580
+ * current draft (case-insensitive). Click or arrow-keys + Enter to
1581
+ * pick a suggestion.
1582
+ * • Tags are case-insensitively deduped; the displayed label preserves
1583
+ * the user's first casing per session.
1584
+ * • Submitting a new label (not in suggestions) creates it inline.
1585
+ *
1586
+ * Token discipline:
1587
+ * • Chips inherit Badge tones via tokens.
1588
+ * • Popover uses --popover / --popover-foreground / --shadow-lg.
1589
+ * • No hex, no inline SVG outside Lucide.
1590
+ *
1591
+ * First consumer: Keywords lens (NewSetPage, NewGroupPage). Generic —
1592
+ * promote anywhere a multi-tag input is needed.
1593
+ */
1594
+ interface TagChipInputProps {
1595
+ /** Current list of tag labels. */
1596
+ value: string[];
1597
+ onChange: (next: string[]) => void;
1598
+ /** Existing labels (across all items in scope) used for autocomplete. */
1599
+ suggestions?: string[];
1600
+ placeholder?: string;
1601
+ /** Hard cap on the number of chips. When reached, the input becomes
1602
+ * disabled with a "max reached" hint. */
1603
+ max?: number;
1604
+ /** Maximum length of any single tag. Defaults to 60. */
1605
+ maxLength?: number;
1606
+ /** Disable input + interactions. */
1607
+ disabled?: boolean;
1608
+ /** id attached to the text input — for label-for binding. */
1609
+ id?: string;
1610
+ /** Apply error styling. */
1611
+ error?: boolean;
1612
+ className?: string;
1613
+ }
1614
+ declare function TagChipInput({ value, onChange, suggestions, placeholder, max, maxLength, disabled, id, error, className, }: TagChipInputProps): react.JSX.Element;
1615
+
1616
+ /**
1617
+ * FolderTreePicker — dropdown picker for a nestable folder tree.
1618
+ *
1619
+ * Behavior:
1620
+ * • Trigger renders the selected folder's name (with parent breadcrumb
1621
+ * when nested) or a placeholder when none.
1622
+ * • Popover shows a "(No folder)" option + DFS-ordered tree with
1623
+ * indent by depth.
1624
+ * • Optional inline "+ New folder" path — when the consumer passes
1625
+ * `onCreateFolder`, the popover footer exposes a create form whose
1626
+ * parent defaults to the currently selected folder (or root).
1627
+ *
1628
+ * Generic — accepts a `folders: Array<{ id, name, parentId }>` shape so
1629
+ * any folder-like data structure can use it. First consumer: Keywords
1630
+ * lens NewSetPage / NewGroupPage.
1631
+ *
1632
+ * Token discipline: tokens only (no hex). Trigger inherits the same
1633
+ * `.btn .btn-secondary` chrome as our other dropdown triggers for
1634
+ * visual consistency.
1635
+ */
1636
+ interface FolderTreeNode {
1637
+ id: string;
1638
+ name: string;
1639
+ parentId: string | null;
1640
+ }
1641
+ interface FolderTreePickerProps {
1642
+ /** Flat list of folder nodes (parentId references id). */
1643
+ folders: FolderTreeNode[];
1644
+ /** Currently selected folder id, or null for "no folder" / root. */
1645
+ value: string | null;
1646
+ onChange: (next: string | null) => void;
1647
+ /** Optional inline-create handler. When omitted, the create UI is
1648
+ * hidden — the picker is read-only over the given folders. The
1649
+ * returned id is selected on success. */
1650
+ onCreateFolder?: (input: {
1651
+ name: string;
1652
+ parentId: string | null;
1653
+ }) => Promise<{
1654
+ id: string;
1655
+ }>;
1656
+ /** Placeholder when no folder is selected. */
1657
+ placeholder?: string;
1658
+ /** Disable everything. */
1659
+ disabled?: boolean;
1660
+ /** id for the trigger — supports label-for binding. */
1661
+ id?: string;
1662
+ /** Optional className for the trigger button. */
1663
+ className?: string;
1664
+ /** Apply error styling to the trigger. */
1665
+ error?: boolean;
1666
+ }
1667
+ declare function FolderTreePicker({ folders, value, onChange, onCreateFolder, placeholder, disabled, id, className, error, }: FolderTreePickerProps): react.JSX.Element;
1668
+
1669
+ interface HeroFormCardProps extends Omit<FormHTMLAttributes<HTMLFormElement>, 'title' | 'onSubmit'> {
1670
+ /** Small accent text above the title, e.g. "Brand · Research". */
1671
+ eyebrow?: ReactNode;
1672
+ /** The page-level title. Required. */
1673
+ title: ReactNode;
1674
+ /** Supporting copy under the title. */
1675
+ subtitle?: ReactNode;
1676
+ /** Optional helper text rendered between the body and the footer
1677
+ * (centered, muted). Good for "Name is required · everything else
1678
+ * is optional" affordances. */
1679
+ helperText?: ReactNode;
1680
+ /** Body — typically composed of Section + Field. */
1681
+ children: ReactNode;
1682
+ /** Footer action cluster (right-aligned). */
1683
+ footer?: ReactNode;
1684
+ /** Container max-width in px (default 720). */
1685
+ maxWidth?: number;
1686
+ /** When true the wrapping element is a <form> with onSubmit attached
1687
+ * (default). When false, renders a <div> — useful when an outer form
1688
+ * owns submission. */
1689
+ asForm?: boolean;
1690
+ /** Form submit handler. Ignored when `asForm` is false. */
1691
+ onSubmit?: (event: FormEvent<HTMLFormElement>) => void;
1692
+ className?: string;
1693
+ }
1694
+ interface HeroFormCardSectionProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
1695
+ /** Section heading. Optional — omit for an untitled body. */
1696
+ title?: ReactNode;
1697
+ /** Optional icon node (Lucide or custom). Sized by the consumer. */
1698
+ icon?: ReactNode;
1699
+ /** Optional supporting copy under the section title. */
1700
+ description?: ReactNode;
1701
+ /** Optional content rendered to the RIGHT of the section title row.
1702
+ * Good for inline helper text ("Name is required · everything else
1703
+ * is optional") or action chips. */
1704
+ trailing?: ReactNode;
1705
+ children: ReactNode;
1706
+ }
1707
+ declare function Section({ title, icon, description, trailing, children, className, ...rest }: HeroFormCardSectionProps): react.JSX.Element;
1708
+ interface HeroFormCardFieldProps {
1709
+ /** Visible field label. */
1710
+ label: ReactNode;
1711
+ /** HTML id of the underlying control — wires <label htmlFor>. */
1712
+ htmlFor?: string;
1713
+ required?: boolean;
1714
+ optional?: boolean;
1715
+ /** Inline error string. Renders below the field. */
1716
+ error?: string | null;
1717
+ /** Optional helper hint below the field (when no error). */
1718
+ hint?: ReactNode;
1719
+ children: ReactNode;
1720
+ className?: string;
1721
+ }
1722
+ declare function Field({ label, htmlFor, required, optional, error, hint, children, className, }: HeroFormCardFieldProps): react.JSX.Element;
1723
+ declare function FieldRow({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
1724
+ interface HeroFormCardBannerProps {
1725
+ /** Optional leading icon. */
1726
+ icon?: ReactNode;
1727
+ /** Body content — text + optional inline marks. */
1728
+ children: ReactNode;
1729
+ /** Visual tone. Default `info`. */
1730
+ tone?: 'info' | 'accent' | 'success' | 'warning' | 'destructive';
1731
+ className?: string;
1732
+ }
1733
+ declare function Banner({ icon, children, tone, className, }: HeroFormCardBannerProps): react.JSX.Element;
1734
+ interface HeroFormCardAdvancedProps {
1735
+ summary?: ReactNode;
1736
+ /** Optional icon node next to the summary. */
1737
+ icon?: ReactNode;
1738
+ defaultOpen?: boolean;
1739
+ children: ReactNode;
1740
+ className?: string;
1741
+ }
1742
+ declare function Advanced({ summary, icon, defaultOpen, children, className, }: HeroFormCardAdvancedProps): react.JSX.Element;
1743
+ declare function Footer({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
1744
+ declare const HeroFormCardCompound: react.ForwardRefExoticComponent<HeroFormCardProps & react.RefAttributes<HTMLFormElement>> & {
1745
+ Section: typeof Section;
1746
+ Field: typeof Field;
1747
+ FieldRow: typeof FieldRow;
1748
+ Banner: typeof Banner;
1749
+ Advanced: typeof Advanced;
1750
+ Footer: typeof Footer;
1751
+ };
1752
+
1753
+ interface RangeSliderProps {
1754
+ /** Lower bound of the track. */
1755
+ min: number;
1756
+ /** Upper bound of the track. */
1757
+ max: number;
1758
+ /** Current selection — `[low, high]`. Clamped within `[min, max]`. */
1759
+ value: [number, number];
1760
+ onChange: (next: [number, number]) => void;
1761
+ /** Step granularity. Default 1. */
1762
+ step?: number;
1763
+ /** Optional label rendered above the track. */
1764
+ label?: ReactNode;
1765
+ /** Formats the two end values shown beside the label. Default
1766
+ * `toLocaleString`. */
1767
+ formatValue?: (value: number) => string;
1768
+ /** id base — the two range inputs get `${id}-min` / `${id}-max`. */
1769
+ id?: string;
1770
+ disabled?: boolean;
1771
+ className?: string;
1772
+ }
1773
+ declare function RangeSlider({ min, max, value, onChange, step, label, formatValue, id, disabled, className, }: RangeSliderProps): react.JSX.Element;
1774
+
1775
+ interface SegmentedPillItem<Id extends string = string> {
1776
+ id: Id;
1777
+ label: ReactNode;
1778
+ icon?: ReactNode;
1779
+ disabled?: boolean;
1780
+ /** Optional accessible label override for icon-heavy segments. */
1781
+ ariaLabel?: string;
1782
+ }
1783
+ interface SegmentedPillProps<Id extends string = string> {
1784
+ items: SegmentedPillItem<Id>[];
1785
+ value: Id;
1786
+ onChange: (next: Id) => void;
1787
+ /** Accessible label for the whole group (rendered as aria-label on
1788
+ * the `role="tablist"` container). */
1789
+ ariaLabel?: string;
1790
+ /** Size variant — controls vertical padding + font. Default `md`.
1791
+ * `xs` is for dense inline contexts (e.g. a sort toggle inside a tab bar). */
1792
+ size?: 'xs' | 'sm' | 'md';
1793
+ /** Fill the available width and stretch segments. Default `true`. */
1794
+ stretch?: boolean;
1795
+ className?: string;
1796
+ }
1797
+ declare function SegmentedPill<Id extends string = string>({ items, value, onChange, ariaLabel, size, stretch, className, }: SegmentedPillProps<Id>): react.JSX.Element;
1798
+
1799
+ interface ShellProps extends HTMLAttributes<HTMLDivElement> {
1800
+ /** Max-width for the auto-centered page-level wrapper, in pixels.
1801
+ * Default 760 (tool-page width). New Set / similar form pages use
1802
+ * 880. When `centered` is false, the value is ignored. */
1803
+ maxWidth?: number;
1804
+ /** When true (default), the frame is centered horizontally inside
1805
+ * its parent via `mx-auto` + `max-width`. Pass `false` if you need
1806
+ * the frame to fill its parent (e.g. inside a tight grid cell). */
1807
+ centered?: boolean;
1808
+ }
1809
+ declare const ShellRoot: react.ForwardRefExoticComponent<ShellProps & react.RefAttributes<HTMLDivElement>>;
1810
+ interface ShellTrayProps extends HTMLAttributes<HTMLElement> {
1811
+ /** Visual variant. `default` (white inner pane, used for primary
1812
+ * content) or `compact` (tighter padding for dense rows). */
1813
+ variant?: 'default' | 'compact';
1814
+ }
1815
+ declare const Tray: react.ForwardRefExoticComponent<ShellTrayProps & react.RefAttributes<HTMLElement>>;
1816
+ interface ShellTrayHeaderProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
1817
+ title: ReactNode;
1818
+ /** Right-aligned meta — e.g. "12 lines · 3 dupes" or
1819
+ * "Name is required · everything else is optional". */
1820
+ meta?: ReactNode;
1821
+ /** Optional left-side numeral chip ("01", "02") in accent color.
1822
+ * Skip for tools that don't need step indication. */
1823
+ index?: ReactNode;
1824
+ /** Optional supporting copy beneath the title row. */
1825
+ description?: ReactNode;
1826
+ /** Tighter padding for dense layouts. */
1827
+ compact?: boolean;
1828
+ }
1829
+ declare function TrayHeader({ title, meta, index, description, compact, className, style, ...rest }: ShellTrayHeaderProps): react.JSX.Element;
1830
+ interface ShellTrayBodyProps extends HTMLAttributes<HTMLDivElement> {
1831
+ /** Vertical gap between children. Default `form` (16px). `grid`
1832
+ * (14px) is for tighter sub-card grids. `flush` (0) lets the
1833
+ * consumer manage spacing entirely. */
1834
+ spacing?: 'form' | 'grid' | 'flush';
1835
+ /** Tighter padding for dense layouts. */
1836
+ compact?: boolean;
1837
+ }
1838
+ declare function TrayBody({ spacing, compact, className, style, children, ...rest }: ShellTrayBodyProps): react.JSX.Element;
1839
+ interface ShellTrayFooterProps extends HTMLAttributes<HTMLDivElement> {
1840
+ /** Tighter padding for dense layouts. */
1841
+ compact?: boolean;
1842
+ /** Left-aligned secondary controls (toggles, disclosure, etc.).
1843
+ * Renders inline before any extra children. */
1844
+ left?: ReactNode;
1845
+ /** Right-aligned primary action (button, link, etc.). Pushed to
1846
+ * the right edge via `margin-left: auto`. */
1847
+ right?: ReactNode;
1848
+ }
1849
+ declare function TrayFooter({ compact, left, right, className, style, children, ...rest }: ShellTrayFooterProps): react.JSX.Element;
1850
+ interface ShellSubCardProps extends HTMLAttributes<HTMLDivElement> {
1851
+ /** Optional header content rendered above the body. */
1852
+ header?: ReactNode;
1853
+ }
1854
+ declare function SubCard({ header, className, style, children, ...rest }: ShellSubCardProps): react.JSX.Element;
1855
+ /** Value-text tone for <ShellStat>. Semantic tones map to the public
1856
+ * status tokens — handy for traffic-light KPIs (e.g. competition). */
1857
+ type ShellStatTone = 'default' | 'accent' | 'success' | 'warning' | 'destructive';
1858
+ interface ShellStatProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
1859
+ /** KPI name — rendered in the outer frame, above the tray. */
1860
+ label: ReactNode;
1861
+ /** The metric value — rendered large inside the inner white tray. */
1862
+ value: ReactNode;
1863
+ /** Optional icon shown beside the label in the frame. */
1864
+ icon?: ReactNode;
1865
+ /** Optional supporting text under the value (e.g. "+3 this week"). */
1866
+ hint?: ReactNode;
1867
+ /** Value text tone — `default`, `accent`, or a semantic status
1868
+ * color (`success` / `warning` / `destructive`). */
1869
+ valueTone?: ShellStatTone;
1870
+ }
1871
+ declare function ShellStat({ label, value, icon, hint, valueTone, className, ...rest }: ShellStatProps): react.JSX.Element;
1872
+ type ShellComponent = typeof ShellRoot & {
1873
+ Tray: typeof Tray;
1874
+ TrayHeader: typeof TrayHeader;
1875
+ TrayBody: typeof TrayBody;
1876
+ TrayFooter: typeof TrayFooter;
1877
+ SubCard: typeof SubCard;
1878
+ Stat: typeof ShellStat;
1879
+ };
1880
+ declare const Shell: ShellComponent;
1881
+
1882
+ type FolderColorKey = 'red' | 'amber' | 'emerald' | 'teal' | 'blue' | 'violet' | 'pink' | 'slate' | 'ink';
1883
+ /** All folder colour keys, in palette order — for swatch pickers. */
1884
+ declare const FOLDER_COLOR_KEYS: FolderColorKey[];
1885
+ /** Resolve a colour key to its `rgb(var(--folder-*))` CSS value. */
1886
+ declare function folderColorVar(key: FolderColorKey): string;
1887
+ interface FolderCardProps {
1888
+ name: string;
1889
+ colorKey: FolderColorKey;
1890
+ assetCount: number;
1891
+ /** Subfolder count — omitted or 0 → not shown. */
1892
+ folderCount?: number;
1893
+ selected?: boolean;
1894
+ /** When true, a selection checkbox reveals on hover and stays
1895
+ * visible while selected. */
1896
+ selectable?: boolean;
1897
+ onOpen: () => void;
1898
+ onSelect?: () => void;
1899
+ /** Optional menu affordance (e.g. a kebab trigger), revealed on
1900
+ * hover at the row end. */
1901
+ menuSlot?: ReactNode;
1902
+ className?: string;
1903
+ }
1904
+ declare function FolderCard({ name, colorKey, assetCount, folderCount, selected, selectable, onOpen, onSelect, menuSlot, className, }: FolderCardProps): react.JSX.Element;
1905
+
1906
+ interface BulkActionBarProps {
1907
+ /** Selection count shown in the accent badge. */
1908
+ count: number;
1909
+ /** Optional summary string, e.g. "2 folders · 3 assets". When
1910
+ * omitted, a plain "<count> selected" label is shown. */
1911
+ summary?: string;
1912
+ /** Caller-owned action buttons (icon buttons or labelled Buttons). */
1913
+ children: ReactNode;
1914
+ onClear: () => void;
1915
+ }
1916
+ declare function BulkActionBar({ count, summary, children, onClear, }: BulkActionBarProps): react.JSX.Element;
1917
+
1918
+ /**
1919
+ * FileUploadButton — paperclip trigger that opens a native file picker.
1920
+ * Disables itself when the file limit is reached.
1921
+ *
1922
+ * Promoted from brand-ai-extension (ADR-036 D20).
1923
+ */
1924
+ interface FileUploadButtonProps {
1925
+ onFilesSelected: (files: File[]) => void;
1926
+ disabled?: boolean;
1927
+ fileCount?: number;
1928
+ maxFiles?: number;
1929
+ }
1930
+ declare function FileUploadButton({ onFilesSelected, disabled, fileCount, maxFiles, }: FileUploadButtonProps): react.JSX.Element;
1931
+
1932
+ interface FileDropZoneProps {
1933
+ children: ReactNode;
1934
+ onFilesDropped: (files: File[]) => void;
1935
+ disabled?: boolean;
1936
+ className?: string;
1937
+ }
1938
+ declare function FileDropZone({ children, onFilesDropped, disabled, className, }: FileDropZoneProps): react.JSX.Element;
1939
+
1940
+ /**
1941
+ * useFileUpload — stateful file-selection, validation, text-extraction, and
1942
+ * XHR upload hook with per-file progress tracking.
1943
+ *
1944
+ * Promoted from brand-ai-extension (ADR-036 D20).
1945
+ *
1946
+ * D14 fix: the original hook read `import.meta.env.VITE_API_URL` and
1947
+ * hardcoded a fallback URL. A @lovett/ui primitive must not read env vars or
1948
+ * embed URLs. Consumers pass `uploadUrl` (required) and optional `authHeaders`
1949
+ * so the workspace `lib/api.ts` config can supply both without the primitive
1950
+ * knowing anything about the deployment environment.
1951
+ */
1952
+ interface UploadResult {
1953
+ id: string;
1954
+ filename: string;
1955
+ fileType: string;
1956
+ fileSize: number;
1957
+ fileCategory: string;
1958
+ publicUrl?: string;
1959
+ status: string;
1960
+ }
1961
+ interface PendingFile {
1962
+ id: string;
1963
+ file: File;
1964
+ progress: number;
1965
+ status: 'pending' | 'uploading' | 'uploaded' | 'error';
1966
+ error?: string;
1967
+ previewUrl?: string;
1968
+ extractedText?: string;
1969
+ uploadResult?: UploadResult;
1970
+ }
1971
+ interface UseFileUploadOptions {
1972
+ /**
1973
+ * Absolute URL that accepts POST multipart/form-data uploads.
1974
+ * Supplied by the consuming lens via `lib/api.ts` — never hardcoded here.
1975
+ */
1976
+ uploadUrl: string;
1977
+ /**
1978
+ * Optional HTTP headers forwarded with every XHR upload request
1979
+ * (e.g. `{ Authorization: 'Bearer <token>' }`). Supplied by the
1980
+ * consuming lens; the primitive never reads env vars or auth stores.
1981
+ */
1982
+ authHeaders?: Record<string, string>;
1983
+ /** Conversation ID to associate uploads with an existing conversation. */
1984
+ conversationId?: string | null | undefined;
1985
+ /** Maximum number of files (default: 10). */
1986
+ maxFiles?: number;
1987
+ /** Maximum bytes per file (default: 20 MB). */
1988
+ maxFileSize?: number;
1989
+ /** Called when a file fails to upload. */
1990
+ onError?: (error: Error) => void;
1991
+ }
1992
+ declare function useFileUpload({ uploadUrl, authHeaders, conversationId, maxFiles, maxFileSize, onError, }: UseFileUploadOptions): {
1993
+ pendingFiles: PendingFile[];
1994
+ addFiles: (files: File[]) => Promise<void>;
1995
+ removeFile: (fileId: string) => void;
1996
+ clearFiles: () => void;
1997
+ getUploadedResults: () => UploadResult[];
1998
+ isUploading: boolean;
1999
+ hasFilesUploading: boolean;
2000
+ hasUploadErrors: boolean;
2001
+ allFilesUploaded: boolean;
2002
+ totalProgress: number;
2003
+ sessionId: string;
2004
+ };
2005
+
2006
+ interface FilePreviewGridProps {
2007
+ files: PendingFile[];
2008
+ onRemove: (id: string) => void;
2009
+ className?: string;
2010
+ }
2011
+ declare function FilePreviewGrid({ files, onRemove, className }: FilePreviewGridProps): react.JSX.Element | null;
2012
+
2013
+ interface FilePreviewItemProps {
2014
+ pendingFile: PendingFile;
2015
+ onRemove: (id: string) => void;
2016
+ }
2017
+ declare function FilePreviewItem({ pendingFile, onRemove }: FilePreviewItemProps): react.JSX.Element;
2018
+
2019
+ /**
2020
+ * FileThumbnail — icon-based preview tile for non-image files; renders the
2021
+ * actual image for image files when a preview URL is available.
2022
+ *
2023
+ * Promoted from brand-ai-extension (ADR-036 D20).
2024
+ *
2025
+ * Token-boundary note: the source used Tailwind color utilities (text-red-600,
2026
+ * bg-blue-100, etc.) which are raw color literals. Rewritten to use inline
2027
+ * style with CSS variables from the public token set where possible, and
2028
+ * for the categorical file-type tints we use the --folder-* palette tokens
2029
+ * (ADR-035) which are the closest semantic color set in the design system
2030
+ * for categorical accent use.
2031
+ */
2032
+ interface FileThumbnailProps {
2033
+ file: File;
2034
+ previewUrl?: string;
2035
+ className?: string;
2036
+ }
2037
+ declare function FileThumbnail({ file, previewUrl, className }: FileThumbnailProps): react.JSX.Element;
2038
+
2039
+ interface CodeBlockProps {
2040
+ /** Lowercased language identifier, e.g. "typescript" or "json". */
2041
+ language?: string;
2042
+ /** Raw source text. */
2043
+ children: string;
2044
+ }
2045
+ declare function CodeBlock({ language, children }: CodeBlockProps): react.JSX.Element;
2046
+ /**
2047
+ * MarkdownCode — react-markdown code element adapter.
2048
+ *
2049
+ * Inline code (single backtick, no className) renders as a small chip.
2050
+ * Fenced code (triple backtick, className="language-xxx") delegates to
2051
+ * CodeBlock for syntax highlighting + copy button.
2052
+ */
2053
+ declare function MarkdownCode({ className, children }: HTMLAttributes<HTMLElement>): react.JSX.Element;
2054
+
2055
+ interface MarkdownRendererProps {
2056
+ /** Raw markdown string to render. */
2057
+ children: string;
2058
+ /** Optional className applied to the wrapping div. */
2059
+ className?: string;
2060
+ }
2061
+ /**
2062
+ * MarkdownRenderer — drop-in markdown surface with GFM support.
2063
+ * Renders headings, lists, code (via CodeBlock), tables, task lists,
2064
+ * strikethrough, blockquotes, and links with workspace token styling.
2065
+ */
2066
+ declare function MarkdownRenderer({ children, className }: MarkdownRendererProps): react.JSX.Element;
2067
+
2068
+ interface ResizableHandleProps {
2069
+ /** Which edge of the parent the handle decorates. */
2070
+ side: 'left' | 'right' | 'top' | 'bottom';
2071
+ /** Controlled width/height (px). */
2072
+ value: number;
2073
+ /** Called on every animation-frame-throttled pointermove during drag. */
2074
+ onChange: (next: number) => void;
2075
+ /** Called once on pointerup with the final value. Use for persistence. */
2076
+ onCommit?: (final: number) => void;
2077
+ /** Lower bound (px). */
2078
+ min: number;
2079
+ /** Upper bound (px). */
2080
+ max: number;
2081
+ /** Double-click / Home target. Defaults to `(min + max) / 2`. */
2082
+ defaultValue?: number;
2083
+ /** Keyboard nudge step (px). Default 16. */
2084
+ step?: number;
2085
+ /** ARIA label for the splitter. Required. */
2086
+ 'aria-label': string;
2087
+ /** Disable all interaction. Used by the sidebar's hover-out mode. */
2088
+ disabled?: boolean;
2089
+ className?: string;
2090
+ }
2091
+ declare function ResizableHandle({ side, value, onChange, onCommit, min, max, defaultValue, step, 'aria-label': ariaLabel, disabled, className, }: ResizableHandleProps): react.JSX.Element;
2092
+ interface ResizablePaneProps extends ResizableHandleProps {
2093
+ children: ReactNode;
2094
+ paneClassName?: string;
2095
+ }
2096
+ declare function ResizablePane({ children, paneClassName, ...handleProps }: ResizablePaneProps): react.JSX.Element;
2097
+
2098
+ interface KbdProps {
2099
+ /** Child text — typically a single key glyph or short string. */
2100
+ children: ReactNode;
2101
+ /** Optional size — defaults to 'md'. */
2102
+ size?: 'sm' | 'md';
2103
+ }
2104
+ declare function Kbd({ children, size }: KbdProps): react.JSX.Element;
2105
+
2106
+ interface FrameStackProps extends HTMLAttributes<HTMLDivElement> {
2107
+ /** Visual gap between child cards. Defaults to 10 px to match the
2108
+ * prototype's spec. Override only with a token value
2109
+ * (e.g. `'var(--space-3)'` for 12 px) — never a literal. */
2110
+ gap?: string;
2111
+ /** Outer padding. Defaults to `--tray-inset`, the same inset <Card>
2112
+ * uses, so a framed group and a framed card share one edge. */
2113
+ padding?: string;
2114
+ }
2115
+ declare const FrameStack: react.ForwardRefExoticComponent<FrameStackProps & react.RefAttributes<HTMLDivElement>>;
2116
+
2117
+ interface CalculatorShellProps {
2118
+ /** Display title (e.g. "ROAS Calculator"). Rendered as H1 + last
2119
+ * crumb in <PageShell>. */
2120
+ title: string;
2121
+ /** One-line subtitle below the title. Optional — describes what the
2122
+ * calculator computes. */
2123
+ subtitle?: string;
2124
+ /** Form inputs slot. Typically a vertical stack of @lovett/ui
2125
+ * <Input> primitives wrapped in <label>s with `type="number"`. */
2126
+ inputs: ReactNode;
2127
+ /** Headline result value — large, accent-tinted, tabular-nums. Pass
2128
+ * the FORMATTED string (use `formatCurrency` / `formatPercent` /
2129
+ * `formatRatio` / `formatNumber`). NaN inputs surface as `'—'`. */
2130
+ result: ReactNode;
2131
+ /** Optional formula label (e.g. "ROAS = Revenue ÷ Ad Spend"). Small,
2132
+ * dim, monospace-feel. Render as a <code> node so monospace +
2133
+ * semantics carry. */
2134
+ formula?: ReactNode;
2135
+ /** Optional row of secondary derived stats (smaller, dim). Pass a
2136
+ * node containing 1-3 stat tiles or a single inline span. */
2137
+ secondaryStats?: ReactNode;
2138
+ /** Optional reset to defaults. Renders a secondary "Reset" button
2139
+ * when provided. */
2140
+ onReset?: () => void;
2141
+ /** Optional copy-result value (the formatted result string). Renders
2142
+ * a "Copy result" secondary button when provided. The value is
2143
+ * written to the clipboard on click; the button flashes a checkmark
2144
+ * + emits a success toast on success. Pass `undefined` to omit the
2145
+ * button — typically when the underlying number is NaN. */
2146
+ copyValue?: string | undefined;
2147
+ /** Optional share-link URL — full or path. When provided, renders a
2148
+ * "Copy link" secondary button; click writes the URL to the
2149
+ * clipboard. (No tinyURL-style encoding here — the consumer
2150
+ * pre-builds the URL with encoded inputs.) */
2151
+ shareUrl?: string | undefined;
2152
+ }
2153
+ /**
2154
+ * CalculatorShell — the layout primitive. See file-level JSDoc for the
2155
+ * locked layout / interaction / token rules.
2156
+ */
2157
+ declare function CalculatorShell({ title, subtitle, inputs, result, formula, secondaryStats, onReset, copyValue, shareUrl, }: CalculatorShellProps): react.JSX.Element;
2158
+
2159
+ type CalcLayout = 'shell' | 'open';
2160
+ /**
2161
+ * Brand-profile prefill source (ADR-076 D9). Typed now so the data
2162
+ * connection is a later drop-in, not a refactor.
2163
+ *
2164
+ * TODO(data-layer): source = useActiveBrandProfile().brand — prefill
2165
+ * audience / CPM / etc. from the active brand (canonical brn_ id).
2166
+ */
2167
+ interface CalcPrefill {
2168
+ source: 'none' | 'brand';
2169
+ values?: Record<string, number>;
2170
+ }
2171
+ interface CalculatorShellV2Props {
2172
+ /** Mono uppercase eyebrow (e.g. "Streaming TV · Reality Check"). */
2173
+ eyebrow: string;
2174
+ /** Uppercase title. The last whitespace-delimited word renders in
2175
+ * `--accent`; a single word renders entirely in accent. Pass a
2176
+ * `[lead, accentWord]` tuple for explicit control (D4). */
2177
+ title: string | [string, string];
2178
+ /** Lucide glyph rendered red inside the header tile. */
2179
+ icon: ReactNode;
2180
+ /** Layout — `'shell'` (compact card) or `'open'` (long-form). */
2181
+ layout?: CalcLayout;
2182
+ /** One or more `<ProfileSection>` Frames composing the inputs. */
2183
+ inputs: ReactNode;
2184
+ /** A `<ProfileSection>` Frame wrapping the result block + StatRow. */
2185
+ result: ReactNode;
2186
+ /** Left-aligned muted footer note (also the data-mode signal, D9). */
2187
+ footerNote: ReactNode;
2188
+ /** Right-aligned footer actions (Reset / Save). The Copy action is
2189
+ * shell-owned via `copyReport` (Amendment 1 F) — do not pass a Copy
2190
+ * button here. */
2191
+ actions?: ReactNode;
2192
+ /** Assumptions content (Amendment 1 B): manual-vs-connected inputs, the
2193
+ * "directional planning math" disclaimer, and the config/formula version.
2194
+ * Rendered as a footer hover popover (not a body strip) so it never adds
2195
+ * shell height. Omitted when absent. */
2196
+ assumptions?: ReactNode;
2197
+ /** Structured mini-report for the shell-owned Copy action (Amendment 1
2198
+ * F). A string, or a function returning one (so it captures live state
2199
+ * at click time). When set, the shell renders a Copy button that writes
2200
+ * this to the clipboard. */
2201
+ copyReport?: string | (() => string);
2202
+ /** STUBBED (D9) — typed, not read this ADR. */
2203
+ prefill?: CalcPrefill;
2204
+ /** "vs. benchmark" rail. When absent, the shell renders a muted
2205
+ * "Benchmark unavailable in manual mode" placeholder (Amendment 1 E). */
2206
+ benchmarkSlot?: ReactNode;
2207
+ }
2208
+ declare function CalculatorShellV2({ eyebrow, title, icon, layout, inputs, result, footerNote, actions, assumptions, copyReport, benchmarkSlot, }: CalculatorShellV2Props): react.JSX.Element;
2209
+
2210
+ interface StatRowStep {
2211
+ /** Lucide glyph (~16px). Inherits the dark-frame label colour. */
2212
+ icon: ReactNode;
2213
+ /** Uppercase metric label (rendered ~9.5px). */
2214
+ label: string;
2215
+ /** Pre-formatted figure. Rendered tabular-nums. */
2216
+ value: string;
2217
+ /** The single pivotal tile — rendered in `--accent` instead of
2218
+ * `--brand-ink`. At most one step should set this. */
2219
+ pivotal?: boolean;
2220
+ }
2221
+ interface StatRowProps {
2222
+ steps: StatRowStep[];
2223
+ className?: string;
2224
+ }
2225
+ declare function StatRow({ steps, className }: StatRowProps): react.JSX.Element;
2226
+
2227
+ /**
2228
+ * Slider — single-value numeric slider (ADR-076 D8).
2229
+ *
2230
+ * Distinct from <RangeSlider>, which is dual-thumb (`value: [number,
2231
+ * number]`) and cannot represent a single value. This is the control
2232
+ * the Calculator Shell v2 frequency input uses: an accent fill from the
2233
+ * track start to the current value, a `--bg-card` thumb with a 2px
2234
+ * accent ring, and a transparent native `<input type="range">` overlaid
2235
+ * for keyboard + accessibility (arrows / Home / End come free).
2236
+ *
2237
+ * <Slider value={freq} min={1} max={10} step={0.5}
2238
+ * onChange={setFreq} aria-label="Target frequency" />
2239
+ *
2240
+ * Controlled — the parent owns `value`. Presentational otherwise.
2241
+ */
2242
+ interface SliderProps {
2243
+ value: number;
2244
+ min: number;
2245
+ max: number;
2246
+ /** Step granularity. Default 1. */
2247
+ step?: number;
2248
+ onChange: (value: number) => void;
2249
+ /** Accessible name for the native range input. */
2250
+ 'aria-label'?: string;
2251
+ disabled?: boolean;
2252
+ className?: string;
2253
+ }
2254
+ declare function Slider({ value, min, max, step, onChange, 'aria-label': ariaLabel, disabled, className, }: SliderProps): react.JSX.Element;
2255
+
2256
+ /**
2257
+ * Number-formatting helpers — small set of pure, zero-dep utilities
2258
+ * shared by the `<CalculatorShell>` consumers (ROAS / ROI / CTR / CPC
2259
+ * / CPM / Conversion Rate and the future ad-performance + customer/
2260
+ * business calculators).
2261
+ *
2262
+ * Locked by ADR-030 D6 (the helpers' existence + signatures) and
2263
+ * sharpened by ADR-058 D8 (re-exported from `@lovett/ui` alongside the
2264
+ * promoted `<CalculatorShell>`). Implementation rules:
2265
+ *
2266
+ * • Garbage in → `'—'` out. NaN, ±Infinity, null, undefined all
2267
+ * produce the em-dash string. Calculator UI should never crash on
2268
+ * partial input (empty inputs, zero divisors).
2269
+ * • Uses `Intl.NumberFormat('en-US', ...)` under the hood — proper
2270
+ * locale-aware separators, no manual `toFixed` rounding.
2271
+ * • `formatPercent` expects PERCENT UNITS, not a 0–1 fraction. Pass
2272
+ * `2.45` to get `"2.45%"`. Passing `0.0245` would yield `"0.02%"`.
2273
+ *
2274
+ * Zero runtime deps — `Intl.NumberFormat` is a built-in.
2275
+ */
2276
+ interface FormatOptions {
2277
+ /** Decimal places. Defaults vary per helper (see each helper's JSDoc). */
2278
+ decimals?: number;
2279
+ }
2280
+ /**
2281
+ * Format a number as USD currency.
2282
+ *
2283
+ * formatCurrency(42.5) → "$42.50"
2284
+ * formatCurrency(1234567.89) → "$1,234,567.89"
2285
+ * formatCurrency(NaN) → "—"
2286
+ *
2287
+ * Default decimals: 2.
2288
+ */
2289
+ declare function formatCurrency(n: number, opts?: FormatOptions): string;
2290
+ /**
2291
+ * Format a number as a percent. Input is in PERCENT UNITS (i.e. pass
2292
+ * `2.45` to get `"2.45%"`, NOT `0.0245`). This matches the way the
2293
+ * calculator routes compute their results (e.g. CTR = clicks ÷
2294
+ * impressions × 100 — the × 100 already happened upstream).
2295
+ *
2296
+ * formatPercent(2.45) → "2.45%"
2297
+ * formatPercent(3.2, { decimals: 1 }) → "3.2%"
2298
+ * formatPercent(NaN) → "—"
2299
+ *
2300
+ * Default decimals: 2.
2301
+ */
2302
+ declare function formatPercent(n: number, opts?: FormatOptions): string;
2303
+ /**
2304
+ * Format a number as a ratio with the `x` suffix. Used by ROAS.
2305
+ *
2306
+ * formatRatio(4.25) → "4.25x"
2307
+ * formatRatio(10, { decimals: 0 }) → "10x"
2308
+ * formatRatio(Infinity) → "—"
2309
+ *
2310
+ * Default decimals: 2.
2311
+ */
2312
+ declare function formatRatio(n: number, opts?: FormatOptions): string;
2313
+ /**
2314
+ * Format a number with thousand-separators and configurable decimals.
2315
+ *
2316
+ * formatNumber(1234567) → "1,234,567"
2317
+ * formatNumber(1234.567, { decimals: 2 }) → "1,234.57"
2318
+ * formatNumber(NaN) → "—"
2319
+ *
2320
+ * Default decimals: 0.
2321
+ */
2322
+ declare function formatNumber(n: number, opts?: FormatOptions): string;
2323
+
2324
+ declare function cn(...inputs: ClassValue[]): string;
2325
+
2326
+ /**
2327
+ * Layout constants shared between CSS and JS.
2328
+ *
2329
+ * Some primitives (e.g. ChipNav) need numeric pixel values for
2330
+ * `IntersectionObserver` rootMargin or `position: sticky` `top` style
2331
+ * — those can't read from CSS custom properties. To keep the layout
2332
+ * coherent, this module mirrors the canonical values from `tokens.css`.
2333
+ * If you change one, change the other.
2334
+ *
2335
+ * The corresponding CSS tokens are:
2336
+ * --header-height → HEADER_HEIGHT
2337
+ * --section-scroll-offset → SECTION_SCROLL_OFFSET
2338
+ */
2339
+ /** Height of the sticky AppHeader, in px. Mirrors `--header-height`. */
2340
+ declare const HEADER_HEIGHT = 56;
2341
+ /**
2342
+ * Vertical offset for in-page section anchors (header + chip nav +
2343
+ * breathing room). Mirrors `--section-scroll-offset`.
2344
+ */
2345
+ declare const SECTION_SCROLL_OFFSET = 120;
2346
+
2347
+ interface PageHeroProps {
2348
+ /** Small uppercase label above the title, usually colored with --accent. */
2349
+ eyebrow?: string;
2350
+ title: string;
2351
+ subtitle?: string;
2352
+ /** Right-aligned trailing content — typically a row of action buttons. */
2353
+ actions?: ReactNode;
2354
+ /**
2355
+ * Optional left-side adornment rendered alongside the title — e.g. the
2356
+ * brand profile's logo block. Sits in the title row (not next to the
2357
+ * eyebrow), vertically aligned with the title text. The hero gracefully
2358
+ * collapses if this is `undefined` so pages without an adornment look
2359
+ * unchanged.
2360
+ */
2361
+ leading?: ReactNode;
2362
+ className?: string;
2363
+ }
2364
+ declare function PageHero({ eyebrow, title, subtitle, actions, leading, className, }: PageHeroProps): react.JSX.Element;
2365
+
2366
+ interface SearchBarProps {
2367
+ value: string;
2368
+ onChange: (v: string) => void;
2369
+ placeholder?: string;
2370
+ width?: number | string;
2371
+ className?: string;
2372
+ }
2373
+ declare function SearchBar({ value, onChange, placeholder, width, className, }: SearchBarProps): react.JSX.Element;
2374
+
2375
+ interface FilterBarProps {
2376
+ /**
2377
+ * `framed` (default) = bordered `.ds-card-surface` card, matching the
2378
+ * Assets bar. `bare` = no frame, just the flex row (for bars that sit
2379
+ * inside an already-framed container).
2380
+ */
2381
+ variant?: 'framed' | 'bare';
2382
+ /** Accessible label for the toolbar group (e.g. "Asset filters"). */
2383
+ 'aria-label'?: string;
2384
+ className?: string;
2385
+ children: ReactNode;
2386
+ }
2387
+ declare function FilterBarRoot({ variant, 'aria-label': ariaLabel, className, children, }: FilterBarProps): react.JSX.Element;
2388
+ interface FilterBarSearchProps {
2389
+ value: string;
2390
+ onChange: (next: string) => void;
2391
+ placeholder?: string;
2392
+ /** Required — icon-only field needs an accessible name. */
2393
+ 'aria-label': string;
2394
+ /** Override the default width sizing if a bar needs it. */
2395
+ className?: string;
2396
+ }
2397
+ /** Standardized search field: small `Input` with a leading magnifier. */
2398
+ declare function FilterBarSearch({ value, onChange, placeholder, 'aria-label': ariaLabel, className, }: FilterBarSearchProps): react.JSX.Element;
2399
+ /** Flexible spacer — everything after it is pushed to the right edge. */
2400
+ declare function FilterBarSpacer(): react.JSX.Element;
2401
+ interface FilterBarCountProps {
2402
+ shown: number;
2403
+ total: number;
2404
+ }
2405
+ /** Right-aligned "{shown} of {total}" result count, tabular-nums. */
2406
+ declare function FilterBarCount({ shown, total }: FilterBarCountProps): react.JSX.Element;
2407
+ declare const FilterBar: typeof FilterBarRoot & {
2408
+ Search: typeof FilterBarSearch;
2409
+ Spacer: typeof FilterBarSpacer;
2410
+ Count: typeof FilterBarCount;
2411
+ };
2412
+
2413
+ interface FilterOption<T extends string = string> {
2414
+ value: T;
2415
+ label: string;
2416
+ /** Right-aligned count chip in the menu. */
2417
+ count?: number;
2418
+ /** Optional left-side adornment (icon, color dot). */
2419
+ leading?: ReactNode;
2420
+ }
2421
+ interface FilterDropdownProps<T extends string = string> {
2422
+ label: string;
2423
+ value: T;
2424
+ onChange: (value: T) => void;
2425
+ options: FilterOption<T>[];
2426
+ /** The value treated as "no filter applied" — defaults to "all". When
2427
+ * `value` differs from this, the trigger flips to its active style. */
2428
+ neutralValue?: T;
2429
+ /** Defaults to the option label. */
2430
+ triggerValueLabel?: string;
2431
+ className?: string;
2432
+ align?: 'left' | 'right';
2433
+ }
2434
+ declare function FilterDropdown<T extends string = string>({ label, value, onChange, options, neutralValue, triggerValueLabel, className, align, }: FilterDropdownProps<T>): react.JSX.Element;
2435
+
2436
+ interface DeviceFrameProps {
2437
+ /** Which device bezel to render. */
2438
+ device: 'desktop' | 'mobile';
2439
+ /**
2440
+ * URL shown in the address bar / pill. On desktop, the address bar is
2441
+ * hidden when omitted; on mobile the pill falls back to a neutral
2442
+ * placeholder.
2443
+ */
2444
+ url?: string;
2445
+ /** The page being previewed, rendered inside the (scrollable) screen area. */
2446
+ children: ReactNode;
2447
+ /** Optional className on the outer frame element. */
2448
+ className?: string;
2449
+ }
2450
+ declare function DeviceFrame({ device, url, children, className }: DeviceFrameProps): react.JSX.Element;
2451
+
2452
+ interface CreativeMediaProps {
2453
+ src: string;
2454
+ alt?: string | undefined;
2455
+ mediaType?: 'image' | 'video' | undefined;
2456
+ className?: string | undefined;
2457
+ style?: react__default.CSSProperties | undefined;
2458
+ autoPlay?: boolean | undefined;
2459
+ loop?: boolean | undefined;
2460
+ showPlayOverlay?: boolean | undefined;
2461
+ thumbnailSrc?: string | undefined;
2462
+ onError?: ((e: react__default.SyntheticEvent) => void) | undefined;
2463
+ }
2464
+ declare const CreativeMedia: react__default.FC<CreativeMediaProps>;
2465
+
2466
+ type PreviewPlatform = 'facebook' | 'instagram' | 'messenger';
2467
+ type PreviewPlacement = 'feed' | 'story' | 'reel' | 'inbox' | 'instream' | 'search' | 'rightcolumn' | 'explore';
2468
+ type PreviewDevice = 'mobile' | 'desktop';
2469
+ interface PreviewConfiguration {
2470
+ platform: PreviewPlatform;
2471
+ placement: PreviewPlacement;
2472
+ device: PreviewDevice;
2473
+ format: '1:1' | '4:5' | '9:16';
2474
+ }
2475
+ interface PreviewAdData {
2476
+ adName: string;
2477
+ primaryText: string;
2478
+ headline: string;
2479
+ description: string;
2480
+ callToAction: string;
2481
+ websiteUrl: string;
2482
+ displayLink: string;
2483
+ brandName: string;
2484
+ profileImage: string;
2485
+ creativeImage: string;
2486
+ creativeMediaType?: 'image' | 'video' | undefined;
2487
+ }
2488
+ /**
2489
+ * A single carousel card for the presentational carousel views (ADR-119 D1).
2490
+ * Per-card copy/destination only — the CTA is ad-level/shared (ADR-119 D3).
2491
+ */
2492
+ interface CarouselCardView {
2493
+ key: string;
2494
+ headline?: string | undefined;
2495
+ description?: string | undefined;
2496
+ url?: string | undefined;
2497
+ }
2498
+ /**
2499
+ * Props for the pure presentational carousels (`MetaFeedCarousel`,
2500
+ * `InstagramFeedCarousel`). They own NO data or crop logic: the caller supplies
2501
+ * the card list, the active index + setter, and an image-render slot
2502
+ * (`CropFrame` on the controller-driven form side; a plain <img> on static
2503
+ * surfaces). Because the view imports neither the controller nor the crop
2504
+ * system, it lives in `packages/ui` and earns Meta-exact hex (ADR-119 D1/D5).
2505
+ */
2506
+ interface CarouselViewProps {
2507
+ cards: CarouselCardView[];
2508
+ active: number;
2509
+ onActive: (index: number) => void;
2510
+ renderImage: (index: number) => ReactNode;
2511
+ brandName: string;
2512
+ profileImage?: string | undefined;
2513
+ primaryText?: string | undefined;
2514
+ /** Ad-level CTA, shared across cards (ADR-119 D3). */
2515
+ callToAction: string;
2516
+ }
2517
+ interface PlacementPreview {
2518
+ id: string;
2519
+ platform: PreviewPlatform;
2520
+ placement: PreviewPlacement;
2521
+ label: string;
2522
+ description: string;
2523
+ aspectRatio: '1:1' | '4:5' | '9:16';
2524
+ device: PreviewDevice;
2525
+ enabled: boolean;
2526
+ }
2527
+ declare const PLACEMENT_CONFIGS: PlacementPreview[];
2528
+
2529
+ /**
2530
+ * Facebook Feed — single image (sponsored). GREENFIELD rebuild.
2531
+ *
2532
+ * Not the Creative-Hub export extraction. Faithful to the real FB feed ad chrome
2533
+ * but built cleanly: a 320px card with natural full-width flow (no fixed
2534
+ * 300px / containerWidth-24 inner rows, no divider margins), Meta-exact hex
2535
+ * (self-contained — no design tokens), a system font stack. Layout, top → bottom:
2536
+ * header · primary text · creative · link card · divider · action bar
2537
+ */
2538
+ interface MetaFeedPreviewProps {
2539
+ adData: PreviewAdData;
2540
+ format: '1:1' | '4:5' | '9:16';
2541
+ }
2542
+ declare const MetaFeedPreview: react.NamedExoticComponent<MetaFeedPreviewProps>;
2543
+
2544
+ interface MetaStoryPreviewProps {
2545
+ platform: PreviewPlatform;
2546
+ adData: PreviewAdData;
2547
+ dominantColor?: string | undefined;
2548
+ accentColor?: string | undefined;
2549
+ isLight?: boolean | undefined;
2550
+ }
2551
+ /**
2552
+ * Meta Stories Preview (Facebook/Instagram/Messenger)
2553
+ * Exact styling extracted from Facebook Creative Hub
2554
+ * Reference: creative-spec/references/META_DESIGN_TOKENS.md
2555
+ * Sources: facebook-stories-singleimage-1080x1080.html, instagram-mobile-stories-singleimage-1080x1080.html
2556
+ * Facebook Stories Container: 318x566px
2557
+ * Instagram Stories Container: 320x567px (inner: 318x565px)
2558
+ */
2559
+ declare const MetaStoryPreview: react__default.FC<MetaStoryPreviewProps>;
2560
+
2561
+ interface MetaReelPreviewProps {
2562
+ platform: PreviewPlatform;
2563
+ adData: PreviewAdData;
2564
+ dominantColor?: string | undefined;
2565
+ accentColor?: string | undefined;
2566
+ }
2567
+ /**
2568
+ * Facebook/Instagram Reels Preview
2569
+ * Exact styling extracted from Facebook Creative Hub
2570
+ * Reference: creative-spec/references/META_DESIGN_TOKENS.md
2571
+ * Source: Facebook-Reels-singleimage-1080x1080.html extraction
2572
+ * Container: 320x567px (9:16 ratio)
2573
+ */
2574
+ declare const MetaReelPreview: react__default.FC<MetaReelPreviewProps>;
2575
+
2576
+ interface MetaMessengerPreviewProps {
2577
+ adData: PreviewAdData;
2578
+ }
2579
+ /**
2580
+ * Facebook Messenger Inbox Preview
2581
+ * Exact styling extracted from Facebook Creative Hub
2582
+ * Reference: creative-spec/references/META_DESIGN_TOKENS.md
2583
+ * Source: singleimage-facebook-messenger-inbox.html
2584
+ * Container: 250x444px (outer), 375x142px (inner - Ad view)
2585
+ */
2586
+ declare const MetaMessengerPreview: react__default.FC<MetaMessengerPreviewProps>;
2587
+
2588
+ interface MetaRightColumnPreviewProps {
2589
+ adData: PreviewAdData;
2590
+ }
2591
+ /**
2592
+ * Facebook Desktop Right Column Preview
2593
+ * Exact styling extracted from Facebook Creative Hub
2594
+ * Reference: creative-spec/references/META_DESIGN_TOKENS.md
2595
+ * Source: desktop-right-column.html extraction
2596
+ * Container: 375x206px
2597
+ */
2598
+ declare const MetaRightColumnPreview: react__default.FC<MetaRightColumnPreviewProps>;
2599
+
2600
+ interface MetaSearchPreviewProps {
2601
+ adData: PreviewAdData;
2602
+ }
2603
+ declare const MetaSearchPreview: react__default.FC<MetaSearchPreviewProps>;
2604
+
2605
+ interface MetaInstreamPreviewProps {
2606
+ adData: PreviewAdData;
2607
+ }
2608
+ declare const MetaInstreamPreview: react__default.FC<MetaInstreamPreviewProps>;
2609
+
2610
+ /**
2611
+ * Instagram Feed — single image (sponsored). GREENFIELD rebuild.
2612
+ *
2613
+ * Not the Creative-Hub export extraction. Faithful to the real IG sponsored-post
2614
+ * chrome but built cleanly: a 320px card with natural full-width flow (no fixed
2615
+ * 300px inner rows, no empty placeholder divs), Meta-exact hex (self-contained —
2616
+ * no design tokens), and a system font stack. Layout, top → bottom:
2617
+ * header · creative · CTA bar · action row · caption
2618
+ */
2619
+ interface InstagramFeedPreviewProps {
2620
+ adData: PreviewAdData;
2621
+ format: '1:1' | '4:5' | '9:16';
2622
+ }
2623
+ declare const InstagramFeedPreview: react.NamedExoticComponent<InstagramFeedPreviewProps>;
2624
+
2625
+ interface InstagramExplorePreviewProps {
2626
+ adData: PreviewAdData;
2627
+ }
2628
+ declare const InstagramExplorePreview: react__default.FC<InstagramExplorePreviewProps>;
2629
+
2630
+ declare const MetaFeedCarousel: react.NamedExoticComponent<CarouselViewProps>;
2631
+
2632
+ declare const InstagramFeedCarousel: react.NamedExoticComponent<CarouselViewProps>;
2633
+
2634
+ type index_CarouselCardView = CarouselCardView;
2635
+ type index_CarouselViewProps = CarouselViewProps;
2636
+ declare const index_CreativeMedia: typeof CreativeMedia;
2637
+ declare const index_InstagramExplorePreview: typeof InstagramExplorePreview;
2638
+ declare const index_InstagramFeedCarousel: typeof InstagramFeedCarousel;
2639
+ declare const index_InstagramFeedPreview: typeof InstagramFeedPreview;
2640
+ declare const index_MetaFeedCarousel: typeof MetaFeedCarousel;
2641
+ declare const index_MetaFeedPreview: typeof MetaFeedPreview;
2642
+ declare const index_MetaInstreamPreview: typeof MetaInstreamPreview;
2643
+ declare const index_MetaMessengerPreview: typeof MetaMessengerPreview;
2644
+ declare const index_MetaReelPreview: typeof MetaReelPreview;
2645
+ declare const index_MetaRightColumnPreview: typeof MetaRightColumnPreview;
2646
+ declare const index_MetaSearchPreview: typeof MetaSearchPreview;
2647
+ declare const index_MetaStoryPreview: typeof MetaStoryPreview;
2648
+ declare const index_PLACEMENT_CONFIGS: typeof PLACEMENT_CONFIGS;
2649
+ type index_PlacementPreview = PlacementPreview;
2650
+ type index_PreviewAdData = PreviewAdData;
2651
+ type index_PreviewConfiguration = PreviewConfiguration;
2652
+ type index_PreviewDevice = PreviewDevice;
2653
+ type index_PreviewPlacement = PreviewPlacement;
2654
+ type index_PreviewPlatform = PreviewPlatform;
2655
+ declare namespace index {
2656
+ export { type index_CarouselCardView as CarouselCardView, type index_CarouselViewProps as CarouselViewProps, index_CreativeMedia as CreativeMedia, index_InstagramExplorePreview as InstagramExplorePreview, index_InstagramFeedCarousel as InstagramFeedCarousel, index_InstagramFeedPreview as InstagramFeedPreview, index_MetaFeedCarousel as MetaFeedCarousel, index_MetaFeedPreview as MetaFeedPreview, index_MetaInstreamPreview as MetaInstreamPreview, index_MetaMessengerPreview as MetaMessengerPreview, index_MetaReelPreview as MetaReelPreview, index_MetaRightColumnPreview as MetaRightColumnPreview, index_MetaSearchPreview as MetaSearchPreview, index_MetaStoryPreview as MetaStoryPreview, index_PLACEMENT_CONFIGS as PLACEMENT_CONFIGS, type index_PlacementPreview as PlacementPreview, type index_PreviewAdData as PreviewAdData, type index_PreviewConfiguration as PreviewConfiguration, type index_PreviewDevice as PreviewDevice, type index_PreviewPlacement as PreviewPlacement, type index_PreviewPlatform as PreviewPlatform };
2657
+ }
2658
+
2659
+ export { AccordionCompound as Accordion, type AccordionProps, type AccordionSectionProps, AllocationSparkbar, type AllocationSparkbarProps, AppScroll, BRAND_ICONS, Badge, type BadgeProps, type BadgeTone, BrandFacebook, type BrandIconKey, type BrandIconProps, BrandInstagram, BrandLinkedIn, BrandLogoTile, type BrandLogoTileProps, type BrandLogoTileSize, BrandMessenger, BrandMeta, BrandNextdoor, BrandPinterest, BrandSnapchat, BrandTikTok, BulkActionBar, type BulkActionBarProps, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, type ButtonShape, type ButtonSize, type ButtonVariant, type CalcLayout, type CalcPrefill, CalculatorShell, type CalculatorShellProps, CalculatorShellV2, type CalculatorShellV2Props, Card, Checkbox, type CheckboxProps, ChipNav, type ChipNavItem, Choropleth, type ChoroplethProps, CodeBlock, type CodeBlockProps, CollapsibleCard, type CollapsibleCardProps, CompletionRing, type CompletionRingProps, CopyField, type CopyFieldProps, type Crumb, DataGrid, type DataGridColumn, DataGridColumnOptionsMenu, DataGridDragOverlay, type DataGridDrawerPanelProps, DataGridDropIndicator, type DataGridIcon, type DataGridProps, type DataGridRowBase, type DataGridSortState, DataGridToolbar, type DataGridToolbarProps, type DataGridToolbarRenderProps, DetailSection, DeviceFrame, type DeviceFrameProps, DragHandle, DropdownButton, type DropdownButtonItem, type DropdownButtonProps, DropdownMenu, DropdownMenuCheckboxItem, type DropdownMenuCheckboxItemProps, DropdownMenuContent, type DropdownMenuContentProps, DropdownMenuGroup, DropdownMenuItem, type DropdownMenuItemProps, DropdownMenuLabel, type DropdownMenuLabelProps, type DropdownMenuProps, DropdownMenuSeparator, DropdownMenuTrigger, type DropdownMenuTriggerProps, type EditingCell, EmptyPlaceholder, EmptyState, type EmptyStateProps, FOLDER_COLOR_KEYS, FileDropZone, FilePreviewGrid, FilePreviewItem, FileThumbnail, FileUploadButton, FilterBar, type FilterBarCountProps, type FilterBarProps, type FilterBarSearchProps, FilterDropdown, type FilterDropdownProps, type FilterOption, FloatingDrawer, type FloatingDrawerProps, FloatingStatusBar, type FloatingStatusBarProps, type FloatingStatusTone, FolderCard, type FolderCardProps, type FolderColorKey, type FolderTreeNode, FolderTreePicker, type FolderTreePickerProps, FrameStack, type FrameStackProps, HEADER_HEIGHT, HeroFormCardCompound as HeroFormCard, type HeroFormCardAdvancedProps, type HeroFormCardBannerProps, type HeroFormCardFieldProps, type HeroFormCardProps, type HeroFormCardSectionProps, IconTile, IdentityLabel, IdentityValue, Input, type InputProps, type InputSize, Kbd, type KbdProps, ListItem, MarkdownCode, MarkdownRenderer, type MarkdownRendererProps, MenuButton, type MenuButtonEntry, type MenuButtonItem, type MenuButtonProps, type MenuButtonSeparator, MetaCell, type MetaCellProps, index as MetaPreviews, MetricCard, type MetricCardProps, type MetricCardTone, MicrosoftLogo, type MicrosoftLogoProps, Modal, OptionTile, OptionTileGroup, type OptionTileGroupProps, type OptionTileProps, PLACEMENT_CONFIGS, PageHeaderHost, PageHeaderSlotProvider, PageHero, type PageHeroProps, PageShell, Pagination, type PaginationProps, type PendingFile, PillButton, type PillButtonProps, type PlacementPreview, type PreviewAdData, type PreviewConfiguration, type PreviewDevice, type PreviewPlacement, type PreviewPlatform, PS as ProfileSection, type ProfileSectionProps, ProgressBar, type ProgressBarProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, RangeSlider, type RangeSliderProps, ResizableHandle, type ResizableHandleProps, ResizablePane, type ResizablePaneProps, type RgbTuple, SECTION_SCROLL_OFFSET, SearchBar, type SearchBarProps, SectionLabel, SegmentedPill, type SegmentedPillItem, type SegmentedPillProps, Shell, type ShellProps, ShellStat, type ShellStatProps, type ShellStatTone, type ShellSubCardProps, type ShellTrayBodyProps, type ShellTrayFooterProps, type ShellTrayHeaderProps, type ShellTrayProps, Slider, type SliderProps, SortableItem, SortableList, SortableTable, type Column as SortableTableColumn, type SortableTableProps, StatRow, type StatRowProps, type StatRowStep, StatsGrid, type StatsGridProps, StepLoader, type StepLoaderProps, type StepLoaderStep, type Tab, Tabs, type TabsProps, TagChipInput, type TagChipInputProps, TagRow, TextInput, Textarea, TextareaInput, type TextareaProps, Toaster, TokenBadge, type TokenBadgeProps, type UploadResult, type UseFileUploadOptions, ValueChip, type ValueChipProps, type WcagBadge, bestTextOn, cn, contrastRatio, copyText, folderColorVar, formatCurrency, formatNumber, formatPercent, formatRatio, hexToRgbTuple, hslString, relativeLuminance, rgbCss, rgbString, useFileUpload, wcagBadges };