@horon-ai/react 0.1.1 → 0.1.3

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/README.md +2 -2
  2. package/package.json +2 -2
  3. package/src/components/agent-activity.tsx +75 -7
  4. package/src/components/agent-lane.tsx +32 -3
  5. package/src/components/agent-lanes-timeline.tsx +85 -7
  6. package/src/components/agent-panel.tsx +46 -4
  7. package/src/components/agent-steering-panel.tsx +53 -3
  8. package/src/components/approval-card.tsx +34 -2
  9. package/src/components/avatar.tsx +20 -4
  10. package/src/components/badge.tsx +24 -3
  11. package/src/components/banner.tsx +19 -3
  12. package/src/components/bot-instructions-panel.tsx +86 -7
  13. package/src/components/bot-list-row.tsx +13 -1
  14. package/src/components/bot-memory-panel.tsx +58 -3
  15. package/src/components/bottom-sheet.tsx +61 -7
  16. package/src/components/breadcrumb-header.tsx +32 -2
  17. package/src/components/breadcrumb.tsx +31 -1
  18. package/src/components/button.tsx +46 -1
  19. package/src/components/calendar.tsx +76 -6
  20. package/src/components/card.tsx +30 -1
  21. package/src/components/chat-bubble.tsx +32 -3
  22. package/src/components/chat-controls.tsx +60 -7
  23. package/src/components/checkbox.tsx +22 -1
  24. package/src/components/chip.tsx +29 -2
  25. package/src/components/choice-card.tsx +25 -1
  26. package/src/components/code-panel.tsx +28 -3
  27. package/src/components/combobox.tsx +52 -2
  28. package/src/components/command-palette.tsx +65 -1
  29. package/src/components/composer.tsx +28 -1
  30. package/src/components/data-table.tsx +198 -16
  31. package/src/components/date-field.tsx +31 -2
  32. package/src/components/dialog.tsx +45 -3
  33. package/src/components/directory-card.tsx +23 -2
  34. package/src/components/document-extract-panel.tsx +83 -5
  35. package/src/components/empty-state.tsx +21 -2
  36. package/src/components/field.tsx +49 -3
  37. package/src/components/file-drop.tsx +39 -4
  38. package/src/components/filter-bar.tsx +38 -1
  39. package/src/components/flue-background.tsx +88 -4
  40. package/src/components/horon-icon.tsx +50 -3
  41. package/src/components/horon-wordmark.tsx +26 -1
  42. package/src/components/icon-button.tsx +38 -1
  43. package/src/components/input-group.tsx +57 -6
  44. package/src/components/input.tsx +48 -2
  45. package/src/components/integration-row.tsx +43 -2
  46. package/src/components/kbd.tsx +28 -2
  47. package/src/components/knowledge-base-panel.tsx +40 -2
  48. package/src/components/liquid-glass.tsx +16 -0
  49. package/src/components/list-detail.tsx +89 -7
  50. package/src/components/marketplace-card.tsx +40 -4
  51. package/src/components/mcp-server-row.tsx +49 -3
  52. package/src/components/menu.tsx +83 -3
  53. package/src/components/message-extras.tsx +50 -4
  54. package/src/components/modal.tsx +71 -4
  55. package/src/components/orchestrator-panel.tsx +48 -4
  56. package/src/components/page-header.tsx +26 -3
  57. package/src/components/plan-card.tsx +28 -1
  58. package/src/components/plan-step.tsx +41 -1
  59. package/src/components/plugin-row.tsx +61 -3
  60. package/src/components/progress.tsx +32 -2
  61. package/src/components/radio-group.tsx +55 -2
  62. package/src/components/research-run-panel.tsx +73 -3
  63. package/src/components/schedule.tsx +163 -12
  64. package/src/components/segmented-control.tsx +57 -2
  65. package/src/components/select.tsx +60 -0
  66. package/src/components/settings-list.tsx +66 -8
  67. package/src/components/sidebar.tsx +214 -23
  68. package/src/components/skeleton.tsx +61 -4
  69. package/src/components/slider.tsx +30 -0
  70. package/src/components/source-row.tsx +43 -4
  71. package/src/components/spinner.tsx +29 -2
  72. package/src/components/split-button.tsx +37 -3
  73. package/src/components/switch.tsx +31 -1
  74. package/src/components/tab-bar.tsx +47 -2
  75. package/src/components/tabs.tsx +59 -5
  76. package/src/components/task-row.tsx +49 -3
  77. package/src/components/textarea-field.tsx +36 -2
  78. package/src/components/textarea.tsx +35 -2
  79. package/src/components/toast.tsx +52 -3
  80. package/src/components/tool-call.tsx +87 -4
  81. package/src/components/tool-calls-header.tsx +32 -1
  82. package/src/components/tool-permissions.tsx +50 -2
  83. package/src/components/tooltip.tsx +33 -1
  84. package/src/components/top-bar.tsx +35 -2
  85. package/src/components/usage-meter.tsx +28 -2
  86. package/src/components/workspace-menu.tsx +46 -1
  87. package/src/hooks/use-active-indicator.ts +44 -1
  88. package/src/hooks/use-color-scheme.ts +24 -1
  89. package/src/hooks/use-copy-to-clipboard.ts +23 -0
  90. package/src/hooks/use-device.ts +29 -1
  91. package/src/hooks/use-hotkey.ts +21 -0
  92. package/src/hooks/use-media-query.ts +18 -0
  93. package/src/hooks/use-platform.ts +30 -1
  94. package/src/hooks/use-reduced-motion.ts +18 -1
  95. package/src/index.ts +31 -0
  96. package/src/lib/cn.ts +13 -1
  97. package/src/lib/cursor-ghost.ts +16 -6
  98. package/src/lib/platform.ts +71 -4
@@ -6,33 +6,73 @@ import { Button } from "./button";
6
6
  import { CodePanel } from "./code-panel";
7
7
  import { LiquidGlass } from "./liquid-glass";
8
8
 
9
+ /** One selectable row of a {@link ListDetail} list. */
9
10
  export type ListDetailItem = {
11
+ /** Stable identifier; it is the value the selection reports and the React key. */
10
12
  id: string;
13
+ /** Row title, drawn in mono and truncated to one line. */
11
14
  title: ReactNode;
15
+ /** Secondary line under the title, in secondary colour. */
12
16
  meta?: ReactNode;
13
17
  };
14
18
 
19
+ /** A captioned group of {@link ListDetailItem} rows. */
15
20
  export type ListDetailGroup = {
21
+ /** Stable identifier used as the group's React key. */
16
22
  id: string;
23
+ /** Caption above the rows, in 11px semibold secondary text. */
17
24
  label: ReactNode;
25
+ /** Rows of this group, in display order. */
18
26
  items: ListDetailItem[];
19
27
  };
20
28
 
29
+ /** Props for {@link ListDetail}: the groups and one render function for the selected item. */
21
30
  export type ListDetailProps = Omit<ComponentProps<"div">, "className" | "children" | "onChange"> & {
31
+ /** Groups rendered into the left list; the flat order also drives the selection fallback. */
22
32
  groups: ListDetailGroup[];
33
+ /** Controlled selected id. Leave unset to let the component hold the selection itself. */
23
34
  value?: string;
35
+ /** Initial selection when uncontrolled. Falls back to the first item of the first group. */
24
36
  defaultValue?: string;
37
+ /** Fires with the new id whenever a row is clicked, controlled or not. */
25
38
  onValueChange?: (id: string) => void;
26
39
  /** Renders the detail pane for the selected item. */
27
40
  children: (item: ListDetailItem) => ReactNode;
28
- /** `solid` (default) is an opaque surface shell with a default border; `glass` is the thick glass shell. */
41
+ /** `solid` (default) is an opaque surface shell with a default border; `glass` is the thick glass shell. @default "solid" */
29
42
  surface?: "solid" | "glass";
43
+ /** Extra classes on the shell, merged after the built-in ones. */
30
44
  className?: string;
31
45
  };
32
46
 
33
47
  /**
34
48
  * Paper `List and detail` (1W6P-0): a 16px-radius shell, solid or thick glass, with a 250px list on an inset
35
49
  * surface and a detail pane. Selecting a row fills it in 150ms and swaps the detail with a short rise.
50
+ *
51
+ * Selection is controlled or uncontrolled like a native input: pass `value` to
52
+ * own it, or `defaultValue` for the initial row. Below `sm` the two panes stack
53
+ * instead of shrinking the list. The list is a `role="listbox"` whose rows are
54
+ * `role="option"` buttons with `aria-selected`; they respond to clicks and the
55
+ * keyboard's normal button activation.
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * <ListDetail
60
+ * groups={[
61
+ * {
62
+ * id: "sources",
63
+ * label: "Sources",
64
+ * items: [{ id: "readme", title: "README.md", meta: "2.1 KB" }],
65
+ * },
66
+ * ]}
67
+ * >
68
+ * {(item) => (
69
+ * <>
70
+ * <ListDetailHeader title={item.title} description="Indexed 4 minutes ago" />
71
+ * <ListDetailMeta items={[{ id: "chunks", label: "Chunks", value: 42 }]} />
72
+ * </>
73
+ * )}
74
+ * </ListDetail>
75
+ * ```
36
76
  */
37
77
  export function ListDetail({
38
78
  groups,
@@ -103,16 +143,24 @@ export function ListDetail({
103
143
  );
104
144
  }
105
145
 
146
+ /** Props for {@link ListDetailHeader}: a tile icon, title, description and a right-hand action cluster. */
106
147
  export type ListDetailHeaderProps = Omit<ComponentProps<"div">, "className" | "title"> & {
148
+ /** Glyph on the 40px sky tile; the tile only renders when this is set. */
107
149
  icon?: ReactNode;
150
+ /** Detail title, drawn in mono at `label-lg` semibold. */
108
151
  title: ReactNode;
152
+ /** One line under the title, in body-sm secondary colour. */
109
153
  description?: ReactNode;
110
154
  /** Right-hand cluster: a state label, a `Switch`, an icon button. */
111
155
  actions?: ReactNode;
156
+ /** Extra classes on the row, merged after the built-in ones. */
112
157
  className?: string;
113
158
  };
114
159
 
115
- /** Detail header: a 40px sky tile with an 18px icon, title and description, actions on the right. */
160
+ /** Detail header: a 40px sky tile with a 20px icon, title and description, actions on the right.
161
+ *
162
+ * Part of {@link ListDetail}.
163
+ */
116
164
  export function ListDetailHeader({ icon, title, description, actions, className, ...props }: ListDetailHeaderProps) {
117
165
  return (
118
166
  <div className={cn("flex flex-wrap items-center gap-3", className)} {...props}>
@@ -130,12 +178,25 @@ export function ListDetailHeader({ icon, title, description, actions, className,
130
178
  );
131
179
  }
132
180
 
181
+ /** Props for {@link ListDetailMeta}: the label/value pairs of the meta box. */
133
182
  export type ListDetailMetaProps = Omit<ComponentProps<"dl">, "className"> & {
134
- items: { id: string; label: ReactNode; value: ReactNode }[];
183
+ /** Pairs rendered in a two-column grid, label over value. */
184
+ items: {
185
+ /** Stable identifier of the pair; its React key. */
186
+ id: string;
187
+ /** Term above the value, in `label-sm` secondary text. */
188
+ label: ReactNode;
189
+ /** Definition under the label, truncated to one line. */
190
+ value: ReactNode;
191
+ }[];
192
+ /** Extra classes on the `dl`, merged after the built-in ones. */
135
193
  className?: string;
136
194
  };
137
195
 
138
- /** Meta box: a subtle-fill 12px-radius box with equal columns of a label over a 14/20 value. */
196
+ /** Meta box: a subtle-fill 12px-radius box with equal columns of a label over a 14/20 value.
197
+ *
198
+ * Part of {@link ListDetail}.
199
+ */
139
200
  export function ListDetailMeta({ items, className, ...props }: ListDetailMetaProps) {
140
201
  return (
141
202
  <dl
@@ -152,13 +213,21 @@ export function ListDetailMeta({ items, className, ...props }: ListDetailMetaPro
152
213
  );
153
214
  }
154
215
 
216
+ /** Props for {@link ListDetailInstructions}: the block heading, the text and the edit callback. */
155
217
  export type ListDetailInstructionsProps = Omit<ComponentProps<"div">, "className" | "onChange"> & {
218
+ /** Heading above the panel.
219
+ * @default "Instructions" */
156
220
  title?: ReactNode;
157
221
  /** Small chip beside the title, such as the file name. */
158
222
  tag?: ReactNode;
223
+ /** Controlled text. Leave unset to let the component hold the draft itself. */
159
224
  value?: string;
225
+ /** Initial text when uncontrolled.
226
+ * @default "" */
160
227
  defaultValue?: string;
228
+ /** Fires with the full textarea value on every keystroke in edit mode. */
161
229
  onValueChange?: (value: string) => void;
230
+ /** Extra classes on the wrapper, merged after the built-in ones. */
162
231
  className?: string;
163
232
  };
164
233
 
@@ -244,14 +313,21 @@ export function ListDetailInstructions({
244
313
  );
245
314
  }
246
315
 
316
+ /** Props for {@link ListDetailFiles}: the row label and the chips it wraps. */
247
317
  export type ListDetailFilesProps = Omit<ComponentProps<"div">, "className"> & {
318
+ /** Heading above the chips.
319
+ * @default "Files" */
248
320
  label?: ReactNode;
249
321
  /** Chips, usually a small `Button` with an icon. */
250
322
  children: ReactNode;
323
+ /** Extra classes on the wrapper, merged after the built-in ones. */
251
324
  className?: string;
252
325
  };
253
326
 
254
- /** Files row: a 13/18 label over a wrap of 32px glass chips, 6px apart. */
327
+ /** Files row: a 13/18 label over a wrap of 32px glass chips, 6px apart.
328
+ *
329
+ * Part of {@link ListDetail}.
330
+ */
255
331
  export function ListDetailFiles({ label = "Files", children, className, ...props }: ListDetailFilesProps) {
256
332
  return (
257
333
  <div className={cn("flex flex-col gap-2", className)} {...props}>
@@ -261,11 +337,17 @@ export function ListDetailFiles({ label = "Files", children, className, ...props
261
337
  );
262
338
  }
263
339
 
264
- /** Footer: a 1px top rule and 8px-spaced glass buttons; use `<span className="flex-1" />` as the spacer. */
340
+ /** Footer: a 1px top rule and 8px-spaced glass buttons; use `<span className="flex-1" />` as the spacer.
341
+ *
342
+ * Part of {@link ListDetail}.
343
+ */
265
344
  export function ListDetailFooter({
266
345
  className,
267
346
  ...props
268
- }: Omit<ComponentProps<"div">, "className"> & { className?: string }) {
347
+ }: Omit<ComponentProps<"div">, "className"> & {
348
+ /** Extra classes on the row, merged after the built-in ones. */
349
+ className?: string;
350
+ }) {
269
351
  return (
270
352
  <div
271
353
  className={cn("flex flex-wrap items-center gap-2 border-t border-border-default pt-3.5", className)}
@@ -5,23 +5,59 @@ import { cn } from "../lib/cn";
5
5
  import { Button } from "./button";
6
6
  import { LiquidGlass } from "./liquid-glass";
7
7
 
8
+ /** Props for {@link MarketplaceCard}: identity, copy, capability chips and the install action. */
8
9
  export type MarketplaceCardProps = Omit<ComponentProps<"div">, "children"> & {
9
10
  /** Two-letter initials shown on the 40px ink tile. */
10
11
  initials: string;
12
+ /** Card title on the row beside the tile; truncated in one line. */
11
13
  name: ReactNode;
14
+ /** Publisher line under the name, in caption colour. */
12
15
  author?: ReactNode;
16
+ /** Body copy under the header; any length, wrapped by the paragraph. */
13
17
  description?: ReactNode;
14
- /** Capability chips, each with an optional 11px icon. */
15
- chips?: { id: string; label: ReactNode; icon?: ReactNode }[];
18
+ /** Capability chips, each with an optional 12px icon. */
19
+ chips?: {
20
+ /** Stable key for this chip; also its React key. */
21
+ id: string;
22
+ /** Chip text. */
23
+ label: ReactNode;
24
+ /** Optional leading icon, rendered at 12px by the chip's `[&_svg]:size-3`. */
25
+ icon?: ReactNode;
26
+ }[];
27
+ /** Rating value shown after a filled star, such as "4.8". Hides the star when unset. */
16
28
  score?: ReactNode;
29
+ /** Install-count caption, separated from the score by a middle dot when both are set. */
17
30
  installs?: ReactNode;
31
+ /** Renders the Install button in the footer when set; receives the native click event. */
18
32
  onInstall?: () => void;
33
+ /** Text of the Install button.
34
+ * @default "Install" */
19
35
  installLabel?: string;
20
- /** `solid` (default) is an opaque surface card with a default border; `glass` is the thick glass card. */
36
+ /** `solid` (default) is an opaque surface card with a default border; `glass` is the thick glass card. @default "solid" */
21
37
  surface?: "solid" | "glass";
22
38
  };
23
39
 
24
- /** Paper `Marketplace card` (Settings 1VMY-0): solid or thick glass column with tile header, description, chips and a footer with rating and Install. */
40
+ /**
41
+ * Paper `Marketplace card` (Settings 1VMY-0): solid or thick glass column with tile header, description, chips and a footer with rating and Install.
42
+ *
43
+ * The card is a plain `div`, so any extra native attribute (`data-*`, `id`,
44
+ * `onClick`) passes through. With `surface="glass"` it paints a `LiquidGlass`
45
+ * layer and every child is positioned to stay above it.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * <MarketplaceCard
50
+ * initials="GH"
51
+ * name="GitHub"
52
+ * author="by Horon"
53
+ * description="Read repositories and open pull requests."
54
+ * chips={[{ id: "official", label: "Official" }]}
55
+ * score="4.8"
56
+ * installs="12k installs"
57
+ * onInstall={() => install("github")}
58
+ * />
59
+ * ```
60
+ */
25
61
  export function MarketplaceCard({
26
62
  initials,
27
63
  name,
@@ -8,9 +8,24 @@ import { Menu, MenuContent, MenuTrigger } from "./menu";
8
8
 
9
9
  const pillClass = "inline-flex h-5.5 items-center rounded-full px-2 text-label-sm";
10
10
 
11
+ /**
12
+ * Colour of an MCP server's status pill.
13
+ *
14
+ * - `connected` — muted pill with a live dot, the healthy default.
15
+ * - `warning` — danger-tinted pill with a danger dot, for a server that needs attention.
16
+ * - `neutral` — muted pill with a placeholder-coloured dot, for an idle or unknown state.
17
+ */
11
18
  export type McpServerStatusTone = "connected" | "warning" | "neutral";
12
19
 
13
- const statusToneClass: Record<McpServerStatusTone, { pill: string; dot: string }> = {
20
+ const statusToneClass: Record<
21
+ McpServerStatusTone,
22
+ {
23
+ /** Classes of the pill itself: background tint and text colour. */
24
+ pill: string;
25
+ /** Classes of the 6px dot inside the pill: its fill colour. */
26
+ dot: string;
27
+ }
28
+ > = {
14
29
  connected: { pill: "bg-overlay-ink-6 text-text-primary", dot: "bg-status-live" },
15
30
  warning: { pill: "bg-accent-red-500-14 text-text-danger", dot: "bg-text-danger" },
16
31
  neutral: { pill: "bg-overlay-ink-6 text-text-secondary", dot: "bg-text-placeholder" },
@@ -26,29 +41,60 @@ const withKeys = (values: string[] | undefined) => {
26
41
  });
27
42
  };
28
43
 
44
+ /** Props for {@link McpServerRow}: the server identity, its tags and status, and the two actions. */
29
45
  export type McpServerRowProps = Omit<ComponentProps<"div">, "className" | "title"> & {
30
46
  /** Server name; its first two letters fill the 40px tile unless `initials` is set. */
31
47
  name: string;
48
+ /** Overrides the two-letter tile text derived from `name`; up to two characters read best. */
32
49
  initials?: string;
33
50
  /** Tags next to the name, such as "Official". */
34
51
  tags?: string[];
35
52
  /** Tags tinted brand, such as "All devices". */
36
53
  brandTags?: string[];
54
+ /** Body line under the tags, in body-sm secondary colour. */
37
55
  description?: ReactNode;
38
56
  /** Meta line, joined by "·". */
39
57
  meta?: string[];
58
+ /** Text inside the status pill.
59
+ * @default "Connected" */
40
60
  statusLabel?: ReactNode;
41
- /** Colour of the status pill: a live dot by default, a danger tint for a warning, a muted dot for a neutral state. */
61
+ /** Colour of the status pill: a live dot by default, a danger tint for a warning, a muted dot for a neutral state.
62
+ * @default "connected" */
42
63
  statusTone?: McpServerStatusTone;
64
+ /** Text of the Configure button.
65
+ * @default "Configure" */
43
66
  configureLabel?: ReactNode;
67
+ /** Fires when the Configure button is clicked; the button always renders, so pass this to make it do anything. */
44
68
  onConfigure?: () => void;
45
69
  /** Overflow menu items (`MenuItem`). Hides the overflow button when absent. */
46
70
  menu?: ReactNode;
71
+ /** Accessible label of the overflow button.
72
+ * @default "More" */
47
73
  menuLabel?: string;
74
+ /** Extra classes on the row `div`, merged after the built-in ones. */
48
75
  className?: string;
49
76
  };
50
77
 
51
- /** Paper `MCP server row` (1VHS-0): a divided row with a 40px tile, tags, a status pill and two actions. */
78
+ /**
79
+ * Paper `MCP server row` (1VHS-0): a divided row with a 40px tile, tags, a status pill and two actions.
80
+ *
81
+ * A row of a settings list rather than a card: it draws only a bottom border and
82
+ * drops it on the last child. The main cluster wraps (`basis-40`) so the row stays
83
+ * usable in a narrow column; its overflow menu opens from the row and portals to
84
+ * `document.body`.
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * <McpServerRow
89
+ * name="Linear"
90
+ * tags={["Official"]}
91
+ * description="Read issues and projects."
92
+ * meta={["12 tools", "OAuth"]}
93
+ * onConfigure={() => openConfig("linear")}
94
+ * menu={<MenuItem>Reconnect</MenuItem>}
95
+ * />
96
+ * ```
97
+ */
52
98
  export function McpServerRow({
53
99
  name,
54
100
  initials,
@@ -3,15 +3,38 @@ import { createContext, useContext, type ComponentProps, type ReactNode } from "
3
3
 
4
4
  import { cn } from "../lib/cn";
5
5
 
6
- type WithClass<T> = Omit<T, "className"> & { className?: string };
6
+ type WithClass<T> = Omit<T, "className"> & {
7
+ /** Extra classes merged onto the element the part renders. */
8
+ className?: string;
9
+ };
7
10
 
11
+ /**
12
+ * Visual treatment of the popup.
13
+ *
14
+ * - `glass` — the Figma `Menu` on the veil: 20px radius, 40px rows, 220px wide.
15
+ * - `solid` — the Account / Workspace menu that opens over glass chrome: opaque fill, 18px radius, `shadow-popover`, 36px rows, 256px wide.
16
+ * - `paper` — the Paper workspace switcher: default fill, 1px default border, 14px radius, `0 12 32` shadow, 304px wide, 36px rows.
17
+ */
8
18
  export type MenuSurface = "glass" | "solid" | "paper";
9
19
 
10
20
  const SurfaceContext = createContext<MenuSurface>("glass");
11
21
 
22
+ /**
23
+ * Root of a menu; owns open state and the trigger/popup pair. Re-exported from
24
+ * Base UI's `Menu.Root`, so it accepts `open`, `defaultOpen`, `onOpenChange` and
25
+ * `modal`.
26
+ */
12
27
  export const Menu = BaseMenu.Root;
28
+
29
+ /**
30
+ * The element that opens the menu. Base UI gives it `aria-haspopup` and the
31
+ * expanded state; use `render` to make an existing button the trigger.
32
+ *
33
+ * Part of {@link MenuContent}.
34
+ */
13
35
  export const MenuTrigger = BaseMenu.Trigger;
14
36
 
37
+ /** Props for {@link MenuContent}: the surface variant and the Base UI positioner offsets. */
15
38
  export type MenuContentProps = WithClass<ComponentProps<typeof BaseMenu.Popup>> & {
16
39
  /**
17
40
  * `glass` is the Figma `Menu` on the veil (20px radius, 40px rows).
@@ -19,21 +42,58 @@ export type MenuContentProps = WithClass<ComponentProps<typeof BaseMenu.Popup>>
19
42
  * (opaque fill, 18px radius, soft shadow, 36px rows).
20
43
  * `paper` is the Paper workspace switcher: default fill, 1px default border, 14px radius,
21
44
  * 0 12 32 shadow, 304px wide, 36px rows.
45
+ * @default "solid"
22
46
  */
23
47
  surface?: MenuSurface;
48
+ /** Preferred side of the anchor the popup opens on; flips when it does not fit. */
24
49
  side?: ComponentProps<typeof BaseMenu.Positioner>["side"];
50
+ /** Alignment of the popup against the anchor.
51
+ * @default "start" */
25
52
  align?: ComponentProps<typeof BaseMenu.Positioner>["align"];
53
+ /** Gap in px between the anchor and the popup.
54
+ * @default 8 */
26
55
  sideOffset?: number;
56
+ /** Offset in px along the alignment axis. */
27
57
  alignOffset?: number;
28
58
  };
29
59
 
60
+ /**
61
+ * Radius, border, fill and shadow of the popup per {@link MenuSurface}. Width is
62
+ * not part of this map; `MenuContent` applies it directly (`w-55` glass, `w-76`
63
+ * paper, `w-64` solid).
64
+ */
30
65
  export const menuPopupClass = {
31
66
  glass: "rounded-xl veil-overlay",
32
67
  solid: "rounded-[18px] bg-glass-fill-solid shadow-popover",
33
68
  paper: "rounded-[14px] border border-border-default bg-bg-default shadow-[0_12px_32px_#0A142424]",
34
69
  } as const;
35
70
 
36
- /** Figma `Menus and popovers` (88:495): `Menu`, `Account menu` and `Workspace menu`. */
71
+ /**
72
+ * Figma `Menus and popovers` (88:495): `Menu`, `Account menu` and `Workspace menu`.
73
+ *
74
+ * The popup portals to `document.body` through `BaseMenu.Portal`, so it escapes
75
+ * any `overflow: hidden` ancestor and always paints above the page at `z-50`.
76
+ * Base UI handles keyboard navigation, focus return to the trigger and dismissal;
77
+ * the surface prop only changes the look. Its width is capped at the viewport
78
+ * minus 32px so the 304px `paper` menu still fits a 320px screen.
79
+ *
80
+ * @example
81
+ * ```tsx
82
+ * <Menu>
83
+ * <MenuTrigger render={<Button variant="solid" />}>Account</MenuTrigger>
84
+ * <MenuContent align="end">
85
+ * <MenuLabel>Workspaces</MenuLabel>
86
+ * <MenuItem icon={<GearSix />} trailing="⌘," onClick={openSettings}>
87
+ * Settings
88
+ * </MenuItem>
89
+ * <MenuSeparator />
90
+ * <MenuItem icon={<Trash />} danger onClick={remove}>
91
+ * Delete
92
+ * </MenuItem>
93
+ * </MenuContent>
94
+ * </Menu>
95
+ * ```
96
+ */
37
97
  export function MenuContent({
38
98
  surface = "solid",
39
99
  side,
@@ -72,18 +132,27 @@ export function MenuContent({
72
132
  );
73
133
  }
74
134
 
135
+ /** Props for {@link MenuItem}: optional leading icon, description line and trailing shortcut. */
75
136
  export type MenuItemProps = WithClass<ComponentProps<typeof BaseMenu.Item>> & {
76
137
  /** 18px leading icon, or a 28px workspace tile. */
77
138
  icon?: ReactNode;
78
- /** Caption under the label, such as "3 members". */
139
+ /** Caption under the label, such as "3 members". Grows the row to 48px. */
79
140
  description?: ReactNode;
80
141
  /** Right side: a shortcut such as "⌘C", or a check. */
81
142
  trailing?: ReactNode;
143
+ /** Paints the label and icon in `text-text-danger`; pair it with `onClick` or `disabled` for a destructive command. */
82
144
  danger?: boolean;
83
145
  /** Current choice drawn on the brand tint (Paper workspace switcher). */
84
146
  selected?: boolean;
85
147
  };
86
148
 
149
+ /**
150
+ * One row of a {@link MenuContent}: icon, label, optional description and trailing
151
+ * shortcut. It is a Base UI `Menu.Item`, so it closes the menu on click, is
152
+ * disabled through `disabled` and highlighted with the arrow keys.
153
+ *
154
+ * Part of {@link MenuContent}.
155
+ */
87
156
  export function MenuItem({
88
157
  icon,
89
158
  description,
@@ -131,6 +200,17 @@ export function MenuItem({
131
200
  );
132
201
  }
133
202
 
203
+ /**
204
+ * A 1px rule between groups of items, drawn full width inside the popup's own
205
+ * `p-1.5` inset. Its wrapper adds `px-1.5` on glass and `px-2.5` on solid and
206
+ * paper, so the line starts 12px from the popup edge on glass and 16px on the
207
+ * others: on solid and paper it lines up with the row padding (`px-2.5`), while
208
+ * the glass rows are inset further (`px-3`) and the rule runs wider than they do.
209
+ *
210
+ * It renders as a Base UI `Menu.Separator`, so it is hidden from the keyboard.
211
+ *
212
+ * Part of {@link MenuContent}.
213
+ */
134
214
  export function MenuSeparator({ className, ...props }: WithClass<ComponentProps<typeof BaseMenu.Separator>>) {
135
215
  const surface = useContext(SurfaceContext);
136
216
  return (
@@ -7,7 +7,13 @@ import { IconButton } from "./icon-button";
7
7
  import { LiquidGlass } from "./liquid-glass";
8
8
 
9
9
  /** Figma `Date separator` (Message extras 109:667): caption between two 10% ink lines. */
10
- export function DateSeparator({ children, className }: { children: ReactNode; className?: string }) {
10
+ // prettier-ignore
11
+ export function DateSeparator({ children, className }: {
12
+ /** Caption text between the rules, such as "Today" or "Mar 4". */
13
+ children: ReactNode;
14
+ /** Extra classes on the row, merged after the built-in ones. */
15
+ className?: string;
16
+ }) {
11
17
  return (
12
18
  <div role="separator" className={cn("flex items-center gap-3", className)}>
13
19
  <span className="h-px flex-1 bg-overlay-ink-10" />
@@ -17,17 +23,43 @@ export function DateSeparator({ children, className }: { children: ReactNode; cl
17
23
  );
18
24
  }
19
25
 
20
- export type MessageAction = { label: string; icon: ReactNode; onClick?: () => void };
26
+ /** One button of a {@link MessageActions} pill. */
27
+ export type MessageAction = {
28
+ /** Accessible label of the button; also its React key, so keep labels unique within a pill. */
29
+ label: string;
30
+ /** 16px glyph; sized by the pill's `[&>svg]:size-4`. */
31
+ icon: ReactNode;
32
+ /** Fires on click. */
33
+ onClick?: () => void;
34
+ };
21
35
 
22
- /** Figma `Message actions` (109:667): a pill of 30px icon buttons with 16px icons, solid or regular glass. */
36
+ /**
37
+ * Figma `Message actions` (109:667): a pill of 30px icon buttons with 16px icons, solid or regular glass.
38
+ *
39
+ * Hovering a button fills a circle behind the glyph; each button carries its
40
+ * label as `aria-label` and the pill exposes `role="toolbar"`. With
41
+ * `surface="glass"` it paints a `LiquidGlass` layer and the buttons sit above it.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * <MessageActions
46
+ * actions={[
47
+ * { label: "Copy", icon: <Copy />, onClick: () => copy(text) },
48
+ * { label: "Regenerate", icon: <ArrowsClockwise />, onClick: regenerate },
49
+ * ]}
50
+ * />
51
+ * ```
52
+ */
23
53
  export function MessageActions({
24
54
  actions,
25
55
  surface = "solid",
26
56
  className,
27
57
  }: {
58
+ /** Buttons of the pill, rendered in order; each needs a unique label. */
28
59
  actions: MessageAction[];
29
- /** `solid` (default) is an opaque surface pill with a default border; `glass` is the regular glass pill. */
60
+ /** `solid` (default) is an opaque surface pill with a default border; `glass` is the regular glass pill. @default "solid" */
30
61
  surface?: "solid" | "glass";
62
+ /** Extra classes on the pill, merged after the built-in ones. */
31
63
  className?: string;
32
64
  }) {
33
65
  return (
@@ -55,14 +87,20 @@ export function MessageActions({
55
87
  );
56
88
  }
57
89
 
90
+ /** Props for {@link Attachment}: what the file is, how it looks and whether it can be removed. */
58
91
  export type AttachmentProps = {
92
+ /** File name, truncated to one line. */
59
93
  name: ReactNode;
60
94
  /** Caption, such as "PDF · 2.4 MB". */
61
95
  detail?: ReactNode;
62
96
  /** Image thumbnail URL; files show a document icon on a sky tile. */
63
97
  thumbnail?: string;
98
+ /** Renders the 32px remove button when set. */
64
99
  onRemove?: () => void;
100
+ /** Accessible label of the remove button.
101
+ * @default "Remove" */
65
102
  removeLabel?: string;
103
+ /** Extra classes on the card, merged after the built-in ones. */
66
104
  className?: string;
67
105
  };
68
106
 
@@ -103,11 +141,19 @@ export function Attachment({ name, detail, thumbnail, onRemove, removeLabel = "R
103
141
  );
104
142
  }
105
143
 
144
+ /** Props for {@link CodeBlock}: the code text, its language caption and the copy-button labels. */
106
145
  export type CodeBlockProps = Omit<ComponentProps<"div">, "className" | "children"> & {
146
+ /** Source text, rendered verbatim inside a `<code>` in a horizontal scroller. */
107
147
  code: string;
148
+ /** Caption in the header bar, such as "tsx"; the space is kept even when empty. */
108
149
  language?: string;
150
+ /** Copy-button text before the copy succeeds.
151
+ * @default "Copy" */
109
152
  copyLabel?: string;
153
+ /** Copy-button text for 1500ms after a successful copy.
154
+ * @default "Copied" */
110
155
  copiedLabel?: string;
156
+ /** Extra classes on the outer `div`, merged after the built-in ones. */
111
157
  className?: string;
112
158
  };
113
159