@horon-ai/react 0.1.0 → 0.1.2

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 (98) hide show
  1. package/package.json +2 -2
  2. package/src/components/agent-activity.tsx +81 -11
  3. package/src/components/agent-lane.tsx +32 -3
  4. package/src/components/agent-lanes-timeline.tsx +85 -7
  5. package/src/components/agent-panel.tsx +53 -7
  6. package/src/components/agent-steering-panel.tsx +73 -21
  7. package/src/components/approval-card.tsx +34 -2
  8. package/src/components/avatar.tsx +20 -4
  9. package/src/components/badge.tsx +24 -3
  10. package/src/components/banner.tsx +19 -3
  11. package/src/components/bot-instructions-panel.tsx +92 -9
  12. package/src/components/bot-list-row.tsx +13 -1
  13. package/src/components/bot-memory-panel.tsx +58 -3
  14. package/src/components/bottom-sheet.tsx +61 -7
  15. package/src/components/breadcrumb-header.tsx +32 -2
  16. package/src/components/breadcrumb.tsx +31 -1
  17. package/src/components/button.tsx +46 -1
  18. package/src/components/calendar.tsx +76 -6
  19. package/src/components/card.tsx +30 -1
  20. package/src/components/chat-bubble.tsx +32 -3
  21. package/src/components/chat-controls.tsx +60 -7
  22. package/src/components/checkbox.tsx +22 -1
  23. package/src/components/chip.tsx +29 -2
  24. package/src/components/choice-card.tsx +25 -1
  25. package/src/components/code-panel.tsx +28 -3
  26. package/src/components/combobox.tsx +52 -2
  27. package/src/components/command-palette.tsx +89 -10
  28. package/src/components/composer.tsx +28 -1
  29. package/src/components/data-table.tsx +198 -16
  30. package/src/components/date-field.tsx +31 -2
  31. package/src/components/dialog.tsx +45 -3
  32. package/src/components/directory-card.tsx +23 -2
  33. package/src/components/document-extract-panel.tsx +83 -5
  34. package/src/components/empty-state.tsx +21 -2
  35. package/src/components/field.tsx +49 -3
  36. package/src/components/file-drop.tsx +39 -4
  37. package/src/components/filter-bar.tsx +39 -2
  38. package/src/components/flue-background.tsx +91 -21
  39. package/src/components/horon-icon.tsx +50 -3
  40. package/src/components/horon-wordmark.tsx +26 -1
  41. package/src/components/icon-button.tsx +38 -1
  42. package/src/components/input-group.tsx +57 -6
  43. package/src/components/input.tsx +48 -2
  44. package/src/components/integration-row.tsx +43 -2
  45. package/src/components/kbd.tsx +37 -4
  46. package/src/components/knowledge-base-panel.tsx +40 -2
  47. package/src/components/liquid-glass.tsx +16 -0
  48. package/src/components/list-detail.tsx +89 -7
  49. package/src/components/marketplace-card.tsx +40 -4
  50. package/src/components/mcp-server-row.tsx +49 -3
  51. package/src/components/menu.tsx +83 -3
  52. package/src/components/message-extras.tsx +54 -18
  53. package/src/components/modal.tsx +71 -4
  54. package/src/components/orchestrator-panel.tsx +48 -4
  55. package/src/components/page-header.tsx +26 -3
  56. package/src/components/plan-card.tsx +28 -1
  57. package/src/components/plan-step.tsx +41 -1
  58. package/src/components/plugin-row.tsx +61 -3
  59. package/src/components/progress.tsx +32 -2
  60. package/src/components/radio-group.tsx +55 -2
  61. package/src/components/research-run-panel.tsx +73 -3
  62. package/src/components/schedule.tsx +163 -12
  63. package/src/components/segmented-control.tsx +58 -3
  64. package/src/components/select.tsx +60 -0
  65. package/src/components/settings-list.tsx +66 -8
  66. package/src/components/sidebar.tsx +214 -23
  67. package/src/components/skeleton.tsx +61 -4
  68. package/src/components/slider.tsx +30 -0
  69. package/src/components/source-row.tsx +43 -4
  70. package/src/components/spinner.tsx +29 -2
  71. package/src/components/split-button.tsx +37 -3
  72. package/src/components/switch.tsx +31 -1
  73. package/src/components/tab-bar.tsx +48 -3
  74. package/src/components/tabs.tsx +59 -5
  75. package/src/components/task-row.tsx +49 -3
  76. package/src/components/textarea-field.tsx +36 -2
  77. package/src/components/textarea.tsx +35 -2
  78. package/src/components/toast.tsx +52 -3
  79. package/src/components/tool-call.tsx +91 -17
  80. package/src/components/tool-calls-header.tsx +32 -1
  81. package/src/components/tool-permissions.tsx +50 -2
  82. package/src/components/tooltip.tsx +33 -1
  83. package/src/components/top-bar.tsx +35 -2
  84. package/src/components/usage-meter.tsx +28 -2
  85. package/src/components/workspace-menu.tsx +46 -1
  86. package/src/hooks/index.ts +8 -0
  87. package/src/{lib → hooks}/use-active-indicator.ts +44 -1
  88. package/src/hooks/use-color-scheme.ts +50 -0
  89. package/src/hooks/use-copy-to-clipboard.ts +55 -0
  90. package/src/hooks/use-device.ts +69 -0
  91. package/src/hooks/use-hotkey.ts +56 -0
  92. package/src/hooks/use-media-query.ts +43 -0
  93. package/src/hooks/use-platform.ts +68 -0
  94. package/src/hooks/use-reduced-motion.ts +25 -0
  95. package/src/index.ts +44 -0
  96. package/src/lib/cn.ts +13 -1
  97. package/src/lib/cursor-ghost.ts +16 -6
  98. package/src/lib/platform.ts +236 -0
@@ -26,19 +26,51 @@ import { Checkbox } from "./checkbox";
26
26
  import { IconButton } from "./icon-button";
27
27
  import { Input } from "./input";
28
28
 
29
- type WithClass<T> = Omit<T, "className"> & { className?: string };
29
+ /** Strips an element's own `className` so a wrapper can merge its classes with the caller's. */
30
+ type WithClass<T> = Omit<T, "className"> & {
31
+ /** Extra classes merged last onto the element, after the component's own classes. */
32
+ className?: string;
33
+ };
30
34
 
35
+ /** Viewport the table is designed for: `desktop` and `tablet` keep 24px card gutters, `mobile` narrows them to 18px. */
31
36
  export type DataTableDevice = "desktop" | "tablet" | "mobile";
32
37
 
33
38
  /**
34
39
  * Figma `Data table`: a solid surface card, 20px radius, 1px default border.
35
40
  * `device` sets the gutters: 24px on desktop and tablet, 18px on mobile (where the table scrolls sideways).
41
+ *
42
+ * The card is the context every part reads: it publishes `data-device`, which the header, cells and footer
43
+ * use to switch their padding, and `group/dt`, so the parts can be laid out in any order inside it.
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * function BotsTable({ bots }: { bots: { id: string; name: string; owner: string }[] }) {
48
+ * const table = useDataTable({
49
+ * data: bots,
50
+ * columns: [
51
+ * { accessorKey: "name", header: "Name" },
52
+ * { accessorKey: "owner", header: "Owner" },
53
+ * ],
54
+ * pageSize: 10,
55
+ * });
56
+ *
57
+ * return (
58
+ * <DataTable>
59
+ * <DataTableView table={table} />
60
+ * <DataTablePagination table={table} />
61
+ * </DataTable>
62
+ * );
63
+ * }
64
+ * ```
36
65
  */
37
66
  export function DataTable({
38
67
  device = "desktop",
39
68
  className,
40
69
  ...props
41
- }: WithClass<ComponentProps<"div">> & { device?: DataTableDevice }) {
70
+ }: WithClass<ComponentProps<"div">> & {
71
+ /** Viewport the table is designed for. @default "desktop" */
72
+ device?: DataTableDevice;
73
+ }) {
42
74
  return (
43
75
  <div
44
76
  data-device={device}
@@ -51,15 +83,24 @@ export function DataTable({
51
83
  );
52
84
  }
53
85
 
86
+ /** Props for {@link DataTableHeader}: an optional leading icon, the heading and trailing actions. */
54
87
  export type DataTableHeaderProps = {
55
88
  /** 16px icon on the 26px brand tile. */
56
89
  icon?: ReactNode;
90
+ /** Card heading, `label-lg` primary text on one truncated line. */
57
91
  title: ReactNode;
58
92
  /** Trailing 16px icon buttons. */
59
93
  actions?: ReactNode;
94
+ /** Extra classes on the header row, merged after the padding classes. */
60
95
  className?: string;
61
96
  };
62
97
 
98
+ /**
99
+ * The card's top row: leading icon tile, title and trailing actions, 24px gutters (18px on mobile).
100
+ *
101
+ * Part of {@link DataTable}. The actions column is `text-secondary` with 16px icons and pulls 6px into the
102
+ * header's vertical padding, so an icon button keeps its own footprint without inflating the row.
103
+ */
63
104
  export function DataTableHeader({ icon, title, actions, className }: DataTableHeaderProps) {
64
105
  return (
65
106
  <div className={cn("flex items-center gap-3 px-6 py-4.5 group-data-[device=mobile]/dt:px-4.5", className)}>
@@ -76,6 +117,12 @@ export function DataTableHeader({ icon, title, actions, className }: DataTableHe
76
117
  );
77
118
  }
78
119
 
120
+ /**
121
+ * The scroll container and the `<table>` inside it, with collapsed borders.
122
+ *
123
+ * Part of {@link DataTable}. On `device="mobile"` the table switches to its min-content width so the
124
+ * wrapper scrolls sideways instead of squeezing columns; above that it fills the card.
125
+ */
79
126
  export function DataTableTable({ className, ...props }: WithClass<ComponentProps<"table">>) {
80
127
  return (
81
128
  <div className="w-full max-w-full min-w-0 overflow-x-auto">
@@ -87,13 +134,21 @@ export function DataTableTable({ className, ...props }: WithClass<ComponentProps
87
134
  );
88
135
  }
89
136
 
90
- /** Header cell: 52px, 14px icon and label-md secondary text. */
137
+ /**
138
+ * Header cell: 52px, 14px icon and label-md secondary text.
139
+ *
140
+ * Part of {@link DataTable}. It draws the top and bottom hairlines and 24px outer gutters (18px on mobile);
141
+ * `DataTableView` passes `aria-sort` here for a sortable column, which is why it stays a plain `<th>`.
142
+ */
91
143
  export function DataTableHead({
92
144
  icon,
93
145
  className,
94
146
  children,
95
147
  ...props
96
- }: WithClass<ComponentProps<"th">> & { icon?: ReactNode }) {
148
+ }: WithClass<ComponentProps<"th">> & {
149
+ /** 14px icon before the label, rendered inside the cell's inline label row. */
150
+ icon?: ReactNode;
151
+ }) {
97
152
  return (
98
153
  <th
99
154
  className={cn(
@@ -110,11 +165,17 @@ export function DataTableHead({
110
165
  );
111
166
  }
112
167
 
168
+ /** Body row: one bottom hairline; `DataTableView` adds the hover, selected and staggered rise classes. */
113
169
  export function DataTableRow({ className, ...props }: WithClass<ComponentProps<"tr">>) {
114
170
  return <tr className={cn("border-b border-border-default", className)} {...props} />;
115
171
  }
116
172
 
117
- /** Body cell: 52px; body-sm secondary text unless the content sets its own. */
173
+ /**
174
+ * Body cell: 52px; body-sm secondary text unless the content sets its own.
175
+ *
176
+ * Part of {@link DataTable}. It keeps text on one line, uses the same 24/18px outer gutters as the head,
177
+ * and turns a direct `<label>` into a flex box so a checkbox and its text line up in the cell.
178
+ */
118
179
  export function DataTableCell({ className, ...props }: WithClass<ComponentProps<"td">>) {
119
180
  return (
120
181
  <td
@@ -127,8 +188,19 @@ export function DataTableCell({ className, ...props }: WithClass<ComponentProps<
127
188
  );
128
189
  }
129
190
 
130
- /** The 24px ink logo tile with a label-sm initial, beside a label-lg name. */
131
- export function DataTableItem({ logo, children }: { logo: ReactNode; children: ReactNode }) {
191
+ /**
192
+ * The 24px ink logo tile with a label-sm initial, beside a label-lg name. Part of {@link DataTable}.
193
+ *
194
+ * prettier-ignore keeps the parameter pattern identical to the original instead of letting the formatter
195
+ * expand the destructured parameter onto separate lines.
196
+ */
197
+ // prettier-ignore
198
+ export function DataTableItem({ logo, children }: {
199
+ /** Tile content: a letter, or an icon the caller sizes (the tile is 24px square). */
200
+ logo: ReactNode;
201
+ /** The row's name, `label-lg` primary text truncated to the cell. */
202
+ children: ReactNode;
203
+ }) {
132
204
  return (
133
205
  <span className="flex items-center gap-2.5">
134
206
  <span className="flex size-6 shrink-0 items-center justify-center rounded-xs bg-surface-ink text-label-sm text-text-on-ink">
@@ -139,12 +211,18 @@ export function DataTableItem({ logo, children }: { logo: ReactNode; children: R
139
211
  );
140
212
  }
141
213
 
142
- /** Figma `Table tag`: brand (sky tint) or neutral (subtle) label, 8px radius. */
214
+ /**
215
+ * Figma `Table tag`: brand (sky tint) or neutral (subtle) label, 8px radius.
216
+ * Part of {@link DataTable}; it renders a non-interactive `span` so it fits inside a cell's text flow.
217
+ */
143
218
  export function DataTableTag({
144
219
  tone = "brand",
145
220
  className,
146
221
  ...props
147
- }: WithClass<ComponentProps<"span">> & { tone?: "brand" | "neutral" }) {
222
+ }: WithClass<ComponentProps<"span">> & {
223
+ /** `brand` is the sky tint with brand text; `neutral` is the subtle fill with secondary text. @default "brand" */
224
+ tone?: "brand" | "neutral";
225
+ }) {
148
226
  return (
149
227
  <span
150
228
  className={cn(
@@ -157,9 +235,22 @@ export function DataTableTag({
157
235
  );
158
236
  }
159
237
 
160
- export type DataTableFooterProps = { onAdd?: () => void; addLabel?: string; hint?: ReactNode; className?: string };
238
+ /** Props for {@link DataTableFooter}: the add-row action and an optional caption hint. */
239
+ export type DataTableFooterProps = {
240
+ /** Fires when the add-row row is pressed; the footer itself adds nothing. */
241
+ onAdd?: () => void;
242
+ /** Text of the add action, `label-md` secondary. @default "Add row" */
243
+ addLabel?: string;
244
+ /** Caption at the right edge, such as a total or a keyboard hint. */
245
+ hint?: ReactNode;
246
+ /** Extra classes on the footer row, merged after the padding classes. */
247
+ className?: string;
248
+ };
161
249
 
162
- /** Footer: "Add row" with a 16px plus and a caption hint on the right. */
250
+ /**
251
+ * Footer: "Add row" with a 16px plus and a caption hint on the right.
252
+ * Part of {@link DataTable}. The add action takes the free width, so the hint stays pinned right; both wrap onto a second line when narrow.
253
+ */
163
254
  export function DataTableFooter({ onAdd, addLabel = "Add row", hint, className }: DataTableFooterProps) {
164
255
  return (
165
256
  <div
@@ -181,13 +272,17 @@ export function DataTableFooter({ onAdd, addLabel = "Add row", hint, className }
181
272
  );
182
273
  }
183
274
 
275
+ /** The `meta` a horon column can carry; `DataTableView` reads it for the head icon and the two class hooks. */
184
276
  export type DataTableColumnMeta = {
185
277
  /** 14px icon before the header label. */
186
278
  icon?: ReactNode;
279
+ /** Extra classes on this column's header cell, such as a width. */
187
280
  headClassName?: string;
281
+ /** Extra classes on each body cell of this column. */
188
282
  cellClassName?: string;
189
283
  };
190
284
 
285
+ /** Empty meta object that gives `columnMeta` its type, so `meta` in a column def is checked against the shape above. */
191
286
  const columnMeta: DataTableColumnMeta = {};
192
287
 
193
288
  /** TanStack Table features every horon data table registers: sorting, filtering, selection, pagination. */
@@ -205,20 +300,54 @@ export const dataTableFeatures = tableFeatures({
205
300
  columnMeta,
206
301
  });
207
302
 
303
+ /** The feature set `dataTableFeatures` builds; the type every column and table instance is parameterised by. */
208
304
  export type DataTableFeatures = typeof dataTableFeatures;
305
+
306
+ /**
307
+ * A column definition bound to the horon feature set and to the row type.
308
+ * @typeParam TData - Shape of one row; derive it from the data array so `accessorKey` stays checked.
309
+ */
209
310
  export type DataTableColumn<TData extends RowData> = ColumnDef<DataTableFeatures, TData>;
311
+
312
+ /**
313
+ * The table instance `useDataTable` returns; pass it to {@link DataTableView}, {@link DataTableSearch} and {@link DataTablePagination}.
314
+ * @typeParam TData - Shape of one row, the same one the columns were built from.
315
+ */
210
316
  export type DataTableInstance<TData extends RowData> = ReactTable<DataTableFeatures, TData>;
211
317
 
318
+ /**
319
+ * Options for {@link useDataTable}.
320
+ * @typeParam TData - Shape of one row.
321
+ */
212
322
  export type UseDataTableOptions<TData extends RowData> = {
323
+ /** Rows to display; the table never mutates them. */
213
324
  data: TData[];
325
+ /** Column definitions, in display order; see {@link DataTableColumn}. */
214
326
  columns: DataTableColumn<TData>[];
327
+ /** Derives a stable row id from the row and its index; the default id is the row index. */
215
328
  getRowId?: (row: TData, index: number) => string;
216
329
  /** Rows per page. Omit it to show every row on one page. */
217
330
  pageSize?: number;
331
+ /** Makes every row selectable, which is what the checkbox column from {@link dataTableSelectColumn} toggles. @default false */
218
332
  enableRowSelection?: boolean;
219
333
  };
220
334
 
221
- /** Builds the TanStack table behind `DataTableView`, `DataTableSearch` and `DataTablePagination`. */
335
+ /**
336
+ * Builds the TanStack table behind `DataTableView`, `DataTableSearch` and `DataTablePagination`.
337
+ *
338
+ * Registers {@link dataTableFeatures} and starts on page 1 with the given `pageSize` (10 by default in
339
+ * state). Without `pageSize` it turns on manual pagination, so the paginated row model is skipped and every
340
+ * row renders.
341
+ *
342
+ * @typeParam TData - Shape of one row.
343
+ * @param options - The table options; see {@link UseDataTableOptions}.
344
+ * @param options.data - Rows to display.
345
+ * @param options.columns - Column definitions, in display order.
346
+ * @param options.getRowId - Derives a stable row id; the row index is used when omitted.
347
+ * @param options.pageSize - Rows per page; omit it to show every row on one page.
348
+ * @param options.enableRowSelection - Makes rows selectable for the checkbox column.
349
+ * @returns A table instance to pass to the table parts.
350
+ */
222
351
  export function useDataTable<TData extends RowData>({
223
352
  data,
224
353
  columns,
@@ -237,7 +366,16 @@ export function useDataTable<TData extends RowData>({
237
366
  });
238
367
  }
239
368
 
240
- /** Leading checkbox column; the header box selects every row on the current page. */
369
+ /**
370
+ * Leading checkbox column; the header box selects every row on the current page.
371
+ *
372
+ * Part of the {@link DataTable} set. The header box reports indeterminate when only some page rows are
373
+ * selected, each cell box is labelled "Select row <n>" and is disabled when the table does not allow
374
+ * selecting that row, and the column is pinned 48px wide and unsortable.
375
+ *
376
+ * @typeParam TData - Shape of one row.
377
+ * @returns A column definition to put first in the `columns` array; it reads the table's own selection state.
378
+ */
241
379
  export function dataTableSelectColumn<TData extends RowData>(): DataTableColumn<TData> {
242
380
  return {
243
381
  id: "select",
@@ -269,13 +407,29 @@ export function dataTableSelectColumn<TData extends RowData>(): DataTableColumn<
269
407
 
270
408
  const ariaSort = { asc: "ascending", desc: "descending" } as const;
271
409
 
410
+ /**
411
+ * Props for {@link DataTableView}.
412
+ * @typeParam TData - Shape of one row.
413
+ */
272
414
  export type DataTableViewProps<TData extends RowData> = {
415
+ /** Instance from {@link useDataTable}. */
273
416
  table: DataTableInstance<TData>;
417
+ /** Cell shown when the filtered rows are empty. @default "No results." */
274
418
  emptyMessage?: ReactNode;
419
+ /** Extra classes on the `<table>`. */
275
420
  className?: string;
276
421
  };
277
422
 
278
- /** Renders a `useDataTable` instance with the Figma head and body cells; sortable heads toggle on click. */
423
+ /**
424
+ * Renders a `useDataTable` instance with the Figma head and body cells; sortable heads toggle on click.
425
+ *
426
+ * A sortable head is a button with a three-state caret (none, ascending, descending) and sets `aria-sort` on
427
+ * the `<th>`; a non-sortable head shows the column's `meta.icon` instead. Rows rise in 30ms apart, hover with
428
+ * a 4% ink wash, and a selected row keeps the wash on its cells. A sort or page change remounts the
429
+ * body so the stagger replays. An empty row model renders one full-width cell with `emptyMessage`.
430
+ *
431
+ * @typeParam TData - Shape of one row, matching the instance.
432
+ */
279
433
  export function DataTableView<TData extends RowData>({
280
434
  table,
281
435
  emptyMessage = "No results.",
@@ -358,13 +512,27 @@ export function DataTableView<TData extends RowData>({
358
512
  );
359
513
  }
360
514
 
515
+ /**
516
+ * Props for {@link DataTableSearch}.
517
+ * @typeParam TData - Shape of one row.
518
+ */
361
519
  export type DataTableSearchProps<TData extends RowData> = {
520
+ /** Instance from {@link useDataTable}. */
362
521
  table: DataTableInstance<TData>;
522
+ /** Field placeholder; it is also the `aria-label`. @default "Search" */
363
523
  placeholder?: string;
524
+ /** Extra classes on the input, merged after `min-w-0`. */
364
525
  className?: string;
365
526
  };
366
527
 
367
- /** Solid search field bound to the table's global filter. */
528
+ /**
529
+ * Solid search field bound to the table's global filter.
530
+ * Part of the {@link DataTable} set: it is a small `Input` with a magnifier, writes every keystroke into the
531
+ * table's global filter and reads the current value back from the table state, so it stays in sync when
532
+ * something else clears the filter.
533
+ *
534
+ * @typeParam TData - Shape of one row.
535
+ */
368
536
  export function DataTableSearch<TData extends RowData>({
369
537
  table,
370
538
  placeholder = "Search",
@@ -384,12 +552,26 @@ export function DataTableSearch<TData extends RowData>({
384
552
  );
385
553
  }
386
554
 
555
+ /**
556
+ * Props for {@link DataTablePagination}.
557
+ * @typeParam TData - Shape of one row.
558
+ */
387
559
  export type DataTablePaginationProps<TData extends RowData> = {
560
+ /** Instance from {@link useDataTable}. */
388
561
  table: DataTableInstance<TData>;
562
+ /** Extra classes on the row, merged after the padding classes. */
389
563
  className?: string;
390
564
  };
391
565
 
392
- /** Row count (or selection count) with previous and next page buttons. */
566
+ /**
567
+ * Row count (or selection count) with previous and next page buttons.
568
+ *
569
+ * Part of the {@link DataTable} set. With a selection it reads "n of m selected", otherwise "n rows"
570
+ * (singular for one); the page label always shows at least one page, and each arrow is disabled at its end
571
+ * of the range. The count follows the filtered rows, not the raw data.
572
+ *
573
+ * @typeParam TData - Shape of one row.
574
+ */
393
575
  export function DataTablePagination<TData extends RowData>({ table, className }: DataTablePaginationProps<TData>) {
394
576
  const total = table.getFilteredRowModel().rows.length;
395
577
  const selected = table.getFilteredSelectedRowModel().rows.length;
@@ -6,20 +6,32 @@ import type { ControlSize } from "../lib/size";
6
6
  import { Calendar, type CalendarProps, type DateRange } from "./calendar";
7
7
  import { fieldBoxClass, fieldSizeClass, fieldTextClass, type InputSurface } from "./input";
8
8
 
9
+ /** Height and text rung of the trigger and the calendar inside: `sm` 32px, `md` 40px, `lg` 48px. */
9
10
  export type DateFieldSize = ControlSize;
11
+ /** Surface of the trigger: `glass` over imagery and glass panels, `solid` on raised surfaces, `solid-on-page` on the page background. */
10
12
  export type DateFieldSurface = InputSurface;
11
13
 
12
14
  type WithoutCalendarSurface<T> = T extends unknown ? Omit<T, "size" | "surface"> : never;
13
15
 
16
+ /**
17
+ * Props for {@link DateField}: every `Calendar` prop except its own `size` and `surface`, plus the trigger's
18
+ * placeholder, format and classes. `value` is the calendar's value too, so pass a `Date` in `single` mode and
19
+ * a `DateRange` in `range` mode; the trigger text is drawn from the same value.
20
+ */
14
21
  export type DateFieldProps = WithoutCalendarSurface<CalendarProps> & {
22
+ /** Rung of the trigger, and of the calendar in the popup. @default "md" */
15
23
  size?: DateFieldSize;
16
- /** `solid` (default) on raised surfaces, `glass` over imagery and glass panels, `solid-on-page` directly on the page background. */
24
+ /** `solid` (default) on raised surfaces, `glass` over imagery and glass panels, `solid-on-page` directly on the page background. @default "solid" */
17
25
  surface?: DateFieldSurface;
26
+ /** Text while nothing is picked; it also styles as placeholder text. @default "Pick a date" */
18
27
  placeholder?: string;
28
+ /** Marks the trigger `aria-invalid` and turns its border danger red. */
19
29
  invalid?: boolean;
30
+ /** Dims the trigger and blocks the popup. */
20
31
  disabled?: boolean;
21
32
  /** Formats the trigger text; defaults to "Oct 20, 2026" and "Oct 20 – Oct 24, 2026". */
22
33
  format?: (value: Date | DateRange) => string;
34
+ /** Extra classes on the trigger button, merged after the field surface and size classes. */
23
35
  triggerClassName?: string;
24
36
  };
25
37
 
@@ -42,7 +54,24 @@ const popupCalendarWidthClass: Record<DateFieldSize, string> = {
42
54
  lg: "w-93.5 max-w-[calc(100vw-2rem)]",
43
55
  };
44
56
 
45
- /** Figma `Date field` (60:244): a 280px field that opens the calendar, in three sizes and two surfaces. */
57
+ /**
58
+ * Figma `Date field` (60:244): a 280px field that opens the calendar, in three sizes and two surfaces.
59
+ *
60
+ * A `Popover` trigger, not an input: the button shows the formatted value or the placeholder and opens the
61
+ * calendar in a portal at the trigger's start edge, 4px below. The popup names its own width (250/314/374px
62
+ * by rung, capped to the viewport) because the calendar fills its parent and a positioner is auto-width.
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * const [date, setDate] = useState<Date | null>(new Date());
67
+ *
68
+ * <DateField
69
+ * value={date}
70
+ * onValueChange={setDate}
71
+ * placeholder="Pick a date"
72
+ * />
73
+ * ```
74
+ */
46
75
  export function DateField({
47
76
  size = "md",
48
77
  surface = "solid",
@@ -4,14 +4,44 @@ import type { ComponentProps, ReactNode } from "react";
4
4
 
5
5
  import { cn } from "../lib/cn";
6
6
 
7
+ /**
8
+ * State holder for a dialog: give it `open` / `onOpenChange` to control it, or neither plus a
9
+ * `defaultOpen` to leave it uncontrolled. It renders nothing itself; the parts below supply the surface.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const [open, setOpen] = useState(false);
14
+ *
15
+ * <Dialog open={open} onOpenChange={setOpen}>
16
+ * <DialogTrigger render={<Button />}>Rename bot</DialogTrigger>
17
+ * <DialogContent
18
+ * title="Rename bot"
19
+ * description="The new name shows up everywhere the bot is mentioned."
20
+ * >
21
+ * <Input defaultValue="Weekly digest" aria-label="Bot name" />
22
+ * <DialogFooter>
23
+ * <DialogClose render={<Button variant="solid" />}>Cancel</DialogClose>
24
+ * <Button onClick={() => setOpen(false)}>Save</Button>
25
+ * </DialogFooter>
26
+ * </DialogContent>
27
+ * </Dialog>
28
+ * ```
29
+ */
7
30
  export const Dialog = BaseDialog.Root;
31
+
32
+ /** The control that opens the dialog. It takes `render` to put the press behaviour on your own button. */
8
33
  export const DialogTrigger = BaseDialog.Trigger;
34
+
35
+ /** The control that closes the dialog: it sets the dialog's open state to false, so a controlled dialog also sees `onOpenChange`. */
9
36
  export const DialogClose = BaseDialog.Close;
10
37
 
38
+ /** Props for {@link DialogContent}: the Base UI popup attributes plus the heading, description and surface choices. */
11
39
  export type DialogContentProps = Omit<ComponentProps<typeof BaseDialog.Popup>, "className" | "title"> & {
40
+ /** Heading rendered as the popup's `title-md` title, which also names the dialog for assistive tech. */
12
41
  title: ReactNode;
42
+ /** Optional `body-md` secondary paragraph under the title, wired to the popup's description. */
13
43
  description?: ReactNode;
14
- /** Label for the close button; hides it when `null`. */
44
+ /** Label for the close button; hides it when `null`. @default "Close" */
15
45
  closeLabel?: string | null;
16
46
  /** Slot marker on the close control, for a surface its own app selects the control by. */
17
47
  closeSlot?: string;
@@ -21,14 +51,21 @@ export type DialogContentProps = Omit<ComponentProps<typeof BaseDialog.Popup>, "
21
51
  * `glass` is the veil dialog (`veil-overlay`). `solid` is the Paper workspace modal:
22
52
  * opaque default fill, 20px radius, 24px padding and a 0 24 64 shadow, no glass layer.
23
53
  * Both surfaces share the 16px block rhythm between title, description and actions.
54
+ * @default "solid"
24
55
  */
25
56
  surface?: "glass" | "solid";
57
+ /** Extra classes on the popup panel, merged after the surface, radius and motion classes. */
26
58
  className?: string;
27
59
  };
28
60
 
29
61
  /**
30
- * Figma `Dialog on scrim` (63:329): a 40% scrim and a 420px veil dialog,
62
+ * Figma `Dialog on scrim` (63:329): the `bg-scrim` ink at 32% alpha and a 420px veil dialog,
31
63
  * 24px padding, title-md heading with a 32px close target, body-md description.
64
+ *
65
+ * Part of {@link Dialog}. Portals to the body over a 6px-blurred scrim and centres through a fixed viewport
66
+ * with 16px gutters, so on a narrow screen the panel keeps its padding and shrinks to the gutters instead;
67
+ * the panel caps at `100dvh - 32px` and scrolls its own content. Base UI owns the focus trap, Escape to
68
+ * close and the scroll lock.
32
69
  */
33
70
  export function DialogContent({
34
71
  title,
@@ -84,10 +121,15 @@ export function DialogContent({
84
121
  /**
85
122
  * The action row: secondary first, the primary action at the right edge, 8px apart.
86
123
  * `w-full` is what right-aligns it — the dialog popup stacks children at their content width.
124
+ *
125
+ * Part of {@link Dialog}. Renders a plain `div`, so it can also close a `BottomSheet` body with the same rhythm.
87
126
  */
88
127
  export function DialogFooter({
89
128
  className,
90
129
  ...props
91
- }: Omit<ComponentProps<"div">, "className"> & { className?: string }) {
130
+ }: Omit<ComponentProps<"div">, "className"> & {
131
+ /** Extra classes on the row, merged after the alignment and gap classes. */
132
+ className?: string;
133
+ }) {
92
134
  return <div className={cn("relative flex w-full flex-wrap items-center justify-end gap-2", className)} {...props} />;
93
135
  }
@@ -5,19 +5,40 @@ import { cn } from "../lib/cn";
5
5
  import { IconButton } from "./icon-button";
6
6
  import { LiquidGlass } from "./liquid-glass";
7
7
 
8
+ /** Props for {@link DirectoryCard}: the tile icon and copy, plus an optional add action. */
8
9
  export type DirectoryCardProps = Omit<ComponentProps<"div">, "children"> & {
9
10
  /** 20px icon shown in inverse on the 40px tile. */
10
11
  icon: ReactNode;
12
+ /** Tile label, `label-md` primary text on one truncated line. */
11
13
  name: ReactNode;
12
14
  /** One line; longer text is clamped. */
13
15
  description?: ReactNode;
16
+ /** Adds an `IconButton` with a plus when set; the card shows no button without it. */
14
17
  onAdd?: () => void;
18
+ /** Accessible name of the add button (`aria-label`), since it shows only a plus icon. @default "Add" */
15
19
  addLabel?: string;
16
- /** `solid` (default) on raised surfaces, `glass` over imagery and glass panels. */
20
+ /** `solid` (default) on raised surfaces, `glass` over imagery and glass panels. @default "solid" */
17
21
  surface?: "solid" | "glass";
18
22
  };
19
23
 
20
- /** Paper `Directory card` (Settings 1VJJ-0): 40px tile, name and one-line description, plus an add button. */
24
+ /**
25
+ * Paper `Directory card` (Settings 1VJJ-0): 40px tile, name and one-line description, plus an add button.
26
+ *
27
+ * The `glass` surface paints the thick glass fill and mounts a `LiquidGlass` layer behind the content, so
28
+ * it belongs only over imagery or a glass panel. The add button inherits the same surface, which keeps the
29
+ * two materials matched.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * <DirectoryCard
34
+ * icon={<Folder />}
35
+ * name="Product"
36
+ * description="Specs, research and launch notes."
37
+ * onAdd={() => openFolderPicker()}
38
+ * addLabel="Connect folder"
39
+ * />
40
+ * ```
41
+ */
21
42
  export function DirectoryCard({
22
43
  icon,
23
44
  name,