@horon-ai/react 0.1.1 → 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 (97) hide show
  1. package/package.json +2 -2
  2. package/src/components/agent-activity.tsx +75 -7
  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 +46 -4
  6. package/src/components/agent-steering-panel.tsx +53 -3
  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 +86 -7
  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 +65 -1
  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 +38 -1
  38. package/src/components/flue-background.tsx +88 -4
  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 +28 -2
  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 +50 -4
  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 +57 -2
  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 +47 -2
  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 +87 -4
  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/use-active-indicator.ts +44 -1
  87. package/src/hooks/use-color-scheme.ts +24 -1
  88. package/src/hooks/use-copy-to-clipboard.ts +23 -0
  89. package/src/hooks/use-device.ts +29 -1
  90. package/src/hooks/use-hotkey.ts +21 -0
  91. package/src/hooks/use-media-query.ts +18 -0
  92. package/src/hooks/use-platform.ts +30 -1
  93. package/src/hooks/use-reduced-motion.ts +18 -1
  94. package/src/index.ts +31 -0
  95. package/src/lib/cn.ts +13 -1
  96. package/src/lib/cursor-ghost.ts +16 -6
  97. package/src/lib/platform.ts +71 -4
@@ -4,26 +4,32 @@ import type { ReactNode } from "react";
4
4
 
5
5
  import { cn } from "../lib/cn";
6
6
 
7
+ /** Diameter of the avatar: `sm` 24px, `md` 32px, `lg` 40px, `xl` 56px. */
7
8
  export type AvatarSize = "sm" | "md" | "lg" | "xl";
8
9
 
10
+ /** Props for {@link Avatar}: the avatar type and size, the image source, fallback content and the live dot. */
9
11
  export type AvatarProps = {
10
- /** `initials` (default), `bot` (robot on the brand tint) or `image`. */
12
+ /** `initials` (default), `bot` (robot on the brand tint) or `image`. @default "initials" */
11
13
  type?: "initials" | "bot" | "image";
14
+ /** Diameter token; also scales the initials, the bot icon and the live dot. @default "md" */
12
15
  size?: AvatarSize;
13
16
  /** Image URL for `type="image"`; initials show while it loads or if it fails. */
14
17
  src?: string;
18
+ /** Alternative text for `type="image"`; pass `""` when the name is already shown next to the avatar. @default "" */
15
19
  alt?: string;
16
20
  /** Initials, or any fallback content. */
17
21
  children?: ReactNode;
18
- /** Shows the live status dot at the bottom right. */
22
+ /** Shows the live status dot at the bottom right. @default false */
19
23
  live?: boolean;
20
- /** Initials colour: `neutral` (default) or `sky`, the Paper account profile avatar (sky-100 fill, sky-700 text). */
24
+ /** Initials colour: `neutral` (default) or `sky`, the Paper account profile avatar (sky-100 fill, sky-700 text). @default "neutral" */
21
25
  tone?: "neutral" | "sky";
22
26
  /**
23
27
  * Border colour of the live dot, so it matches the surface behind it (glass panels, `bg-subtle`, brand tints).
24
28
  * Defaults to `border-bg-surface`.
29
+ * @default "border-bg-surface"
25
30
  */
26
31
  liveRingClassName?: string;
32
+ /** Extra classes merged onto the wrapper, for positioning or a custom size override. */
27
33
  className?: string;
28
34
  };
29
35
 
@@ -55,7 +61,17 @@ const typeClass = {
55
61
  image: "bg-sky-200 text-text-secondary",
56
62
  } as const;
57
63
 
58
- /** Figma `Avatar` (61:347): Initials, Bot and Image in four sizes, with an optional live dot. */
64
+ /**
65
+ * Figma `Avatar` (61:347): Initials, Bot and Image in four sizes, with an optional live dot. Built on Base UI's
66
+ * avatar primitive, so an image that fails to load falls back to the children instead of a broken icon.
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * <Avatar type="image" size="lg" src={user.photoUrl} alt="" live>
71
+ * {initials}
72
+ * </Avatar>
73
+ * ```
74
+ */
59
75
  export function Avatar({
60
76
  type = "initials",
61
77
  size = "md",
@@ -3,16 +3,27 @@ import type { ComponentProps } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import type { ControlSize } from "../lib/size";
5
5
 
6
- /** `surface` is the white (raised surface) pill with a 1px default border and primary text. */
6
+ /** Tint of the pill and its dot; `neutral` is the default.
7
+ *
8
+ * - `neutral`: 6% ink pill with secondary text, the default.
9
+ * - `brand`: sky-tinted pill with brand text, for counts and states tied to the brand.
10
+ * - `danger`: red-tinted pill with danger text, for errors and destructive states.
11
+ * - `solid`: filled brand pill with on-primary text, for the strongest emphasis.
12
+ * - `surface`: the white (raised surface) pill with a 1px default border and primary text.
13
+ */
7
14
  export type BadgeTone = "neutral" | "brand" | "danger" | "solid" | "surface";
15
+ /** Size token shared with the form controls (`sm`, `md`, `lg`); the rendered heights are documented on {@link Badge}. */
8
16
  export type BadgeSize = ControlSize;
9
17
 
18
+ /** Props for {@link Badge}: the tone, size, optional status dot and extra span props. */
10
19
  export type BadgeProps = Omit<ComponentProps<"span">, "className"> & {
20
+ /** Colour treatment of the pill and its dot. @default "neutral" */
11
21
  tone?: BadgeTone;
12
22
  /** Leading status dot; 4px (sm), 6px (md) or 8px (lg). */
13
23
  dot?: boolean;
14
- /** `md` is the 22px Figma Badge; `sm` 20px and `lg` 26px. */
24
+ /** `md` is the 22px Figma Badge; `sm` 20px and `lg` 26px. @default "md" */
15
25
  size?: BadgeSize;
26
+ /** Extra classes merged onto the pill, for margins or alignment. */
16
27
  className?: string;
17
28
  };
18
29
 
@@ -44,7 +55,17 @@ const dotClass: Record<BadgeTone, string> = {
44
55
  surface: "bg-text-primary",
45
56
  };
46
57
 
47
- /** Figma `Badge` (61:283): a 22px translucent pill with label-sm text. */
58
+ /**
59
+ * Figma `Badge` (61:283): a 22px translucent pill with label-sm text. Renders a non-interactive `span`, so it can
60
+ * sit inside buttons, list rows and headings without changing their layout height.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * <Badge tone="brand" dot>
65
+ * Running
66
+ * </Badge>
67
+ * ```
68
+ */
48
69
  export function Badge({ tone = "neutral", dot, size = "md", className, children, ...props }: BadgeProps) {
49
70
  return (
50
71
  <span
@@ -5,18 +5,24 @@ import { cn } from "../lib/cn";
5
5
  import type { ControlSize } from "../lib/size";
6
6
  import { LiquidGlass } from "./liquid-glass";
7
7
 
8
+ /** Severity of the strip: `info` tints the rim and icon brand, `danger` uses the danger rim and role="alert". */
8
9
  export type BannerTone = "info" | "danger";
10
+ /** Size token shared with the form controls; the rendered heights are documented on {@link Banner}. */
9
11
  export type BannerSize = ControlSize;
12
+ /** `solid` paints the raised surface, `glass` layers the glass material for use over imagery. */
10
13
  export type BannerSurface = "solid" | "glass";
11
14
 
15
+ /** Props for {@link Banner}: the tone, size and surface of the strip plus its optional trailing action. */
12
16
  export type BannerProps = Omit<ComponentProps<"div">, "className"> & {
17
+ /** Severity; also picks the icon and the ARIA role. @default "info" */
13
18
  tone?: BannerTone;
14
19
  /** Trailing underlined action, such as a link or button. */
15
20
  action?: ReactNode;
16
- /** Banner size: `sm` 32px minimum, `md` the Figma 48px strip (default), `lg` 56px; scales icon, text and padding. */
21
+ /** Banner size: `sm` 32px minimum, `md` the Figma 48px strip (default), `lg` 56px; scales icon, text and padding. @default "md" */
17
22
  size?: BannerSize;
18
- /** `solid` (default) on raised surfaces, `glass` over imagery and glass panels. */
23
+ /** `solid` (default) on raised surfaces, `glass` over imagery and glass panels. @default "solid" */
19
24
  surface?: BannerSurface;
25
+ /** Extra classes merged onto the strip, for margins or width overrides. */
20
26
  className?: string;
21
27
  };
22
28
 
@@ -59,7 +65,17 @@ const solidEdgeClass: Record<BannerTone, string> = {
59
65
  danger: "border-border-danger",
60
66
  };
61
67
 
62
- /** Figma `Banner` (62:318): a strip with a tinted rim, 16px icon and an underlined action. */
68
+ /**
69
+ * Figma `Banner` (62:318): a strip with a tinted rim, 16px icon and an underlined action. The tone sets the ARIA
70
+ * role — `danger` renders `role="alert"` (announced immediately), `info` renders `role="status"`.
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * <Banner tone="danger" action={<a href="/settings">Review</a>}>
75
+ * Your API key expired.
76
+ * </Banner>
77
+ * ```
78
+ */
63
79
  export function Banner({
64
80
  tone = "info",
65
81
  action,
@@ -6,20 +6,50 @@ import { useActiveIndicator } from "../hooks/use-active-indicator";
6
6
  import { useDevice } from "../hooks/use-device";
7
7
  import { Kbd } from "./kbd";
8
8
 
9
- type WithClass<T> = Omit<T, "className"> & { className?: string };
9
+ /** Drops the base type's `className` so the panel can own it. */
10
+ type WithClass<T> = Omit<T, "className"> & {
11
+ /** Extra classes merged onto the root element. */
12
+ className?: string;
13
+ };
10
14
 
15
+ /** Props for {@link PanelPillTabs}: the tab items, the controlled or default selection and the change handler. */
11
16
  export type PanelPillTabsProps = {
12
- items: { value: string; label: ReactNode }[];
17
+ /** Tabs in display order; the first one is the default selection. */
18
+ items: {
19
+ /** Value handed to `onValueChange` and compared against `value`. */
20
+ value: string;
21
+ /** Tab label. */
22
+ label: ReactNode;
23
+ }[];
24
+ /** Controlled selected value; omit it to let the track keep its own selection. */
13
25
  value?: string;
26
+ /** Selection used before the first click, falling back to the first item. */
14
27
  defaultValue?: string;
28
+ /** Fires with the next value on click or arrow-key navigation. */
15
29
  onValueChange?: (value: string) => void;
30
+ /** Accessible name of the tab list. */
16
31
  "aria-label"?: string;
32
+ /** Extra classes merged onto the track, for width overrides. */
17
33
  className?: string;
18
34
  };
19
35
 
20
36
  /**
21
- * The 5% ink pill track shared by the Bot panels (Paper `YNQ-1`): 3px padding, 2px gap, 28px options with
37
+ * The 5% ink pill track shared by the Bot panels (Paper `YNQ-1`): 3px padding, 2px gap, 30px options with
22
38
  * 12px inset and a 10% ink selection that slides between options in 280ms.
39
+ *
40
+ * Works controlled (`value`) or uncontrolled (`defaultValue`); the arrow keys wrap around, move the selection
41
+ * and focus the newly selected tab, while only the selected tab stays in the tab order.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * <PanelPillTabs
46
+ * aria-label="Instructions view"
47
+ * items={[
48
+ * { value: "instructions", label: "Instructions" },
49
+ * { value: "example", label: "Example" },
50
+ * ]}
51
+ * />
52
+ * ```
23
53
  */
24
54
  export function PanelPillTabs({ items, value, defaultValue, onValueChange, className, ...props }: PanelPillTabsProps) {
25
55
  const [inner, setInner] = useState(defaultValue ?? items[0]?.value ?? "");
@@ -82,6 +112,7 @@ export function PanelPillTabs({ items, value, defaultValue, onValueChange, class
82
112
  );
83
113
  }
84
114
 
115
+ /** Chip tint: `sky` the default light blue, `brand` the deeper sky-700, `neutral` grey. */
85
116
  export type InstructionVariableTone = "sky" | "brand" | "neutral";
86
117
 
87
118
  const variableToneClass: Record<InstructionVariableTone, string> = {
@@ -97,26 +128,53 @@ const variableDotClass: Record<InstructionVariableTone, string> = {
97
128
  };
98
129
 
99
130
  /** One piece of an instruction line: plain text, or a `{{variable}}` chip. */
100
- export type InstructionPart = string | { variable: string; tone?: InstructionVariableTone };
131
+ export type InstructionPart =
132
+ | string
133
+ | {
134
+ /** Variable name, rendered inside the chip without the braces. */
135
+ variable: string;
136
+ /** Chip tint. @default "sky" */
137
+ tone?: InstructionVariableTone;
138
+ };
101
139
 
102
- export type InstructionVariable = { name: string; tone?: InstructionVariableTone; value: string };
140
+ /** A test value for one `{{variable}}` in the prompt, shown under the instructions. */
141
+ export type InstructionVariable = {
142
+ /** Variable name shown above the test value. */
143
+ name: string;
144
+ /** Dot tint matching the chip in the prompt. @default "sky" */
145
+ tone?: InstructionVariableTone;
146
+ /** Test value used when the instruction is run. */
147
+ value: string;
148
+ };
103
149
 
150
+ /** Props for {@link BotInstructionsPanel}: the bot, the prompt lines, the optional example, variables and scenarios, and the run handler. */
104
151
  export type BotInstructionsPanelProps = WithClass<Omit<ComponentProps<"section">, "title">> & {
152
+ /** Panel heading. @default "Instructions" */
105
153
  title?: ReactNode;
106
154
  /** Version label in the header pill, for example `v4`. */
107
155
  version?: string;
108
156
  /** Status next to the version, rendered with a live dot. */
109
157
  versionStatus?: string;
110
- bot: { initial: string; name: string };
111
- /** The editable prompt, one array of parts per line. */
158
+ /** Bot whose prompt is shown; the initial fills the 18px tile. */
159
+ bot: {
160
+ /** One-character tile label. */
161
+ initial: string;
162
+ /** Bot name next to the tile. */
163
+ name: string;
164
+ };
165
+ /** The prompt text, one array of parts per line, rendered read-only. */
112
166
  instructions: InstructionPart[][];
113
167
  /** Shown under the Example tab instead of the prompt. */
114
168
  example?: ReactNode;
169
+ /** Prompt length shown at the right of the tab row; hidden when undefined. */
115
170
  tokenCount?: number;
116
171
  /** Test values for each variable in the prompt. */
117
172
  variables?: InstructionVariable[];
173
+ /** Test scenarios offered as pills under the prompt. */
118
174
  scenarios?: string[];
175
+ /** Scenario selected on first render, falling back to the first scenario. */
119
176
  defaultScenario?: string;
177
+ /** Fires with the selected scenario (or undefined) when Run is pressed; the panel does not run anything itself. */
120
178
  onRun?: (scenario: string | undefined) => void;
121
179
  /** The last run's output, or a hint until there is one. */
122
180
  output?: ReactNode;
@@ -130,13 +188,17 @@ function partKey(part: InstructionPart) {
130
188
  return typeof part === "string" ? part : part.variable;
131
189
  }
132
190
 
191
+ /** Inline rounded chip that stands in for a `{{variable}}` inside a prompt line. */
133
192
  function VariableChip({
134
193
  tone = "sky",
135
194
  className,
136
195
  children,
137
196
  }: {
197
+ /** Chip tint. @default "sky" */
138
198
  tone?: InstructionVariableTone;
199
+ /** Extra classes merged onto the chip. */
139
200
  className?: string;
201
+ /** Variable name shown without braces. */
140
202
  children: ReactNode;
141
203
  }) {
142
204
  return (
@@ -153,6 +215,23 @@ function Divider() {
153
215
  /**
154
216
  * Bot instructions panel (Paper `YN3-1` / `GOZ-0`): a 24px-radius surface with the bot header, an
155
217
  * Instructions / Example tab pair over the prompt text with `{{variable}}` chips, and a test-run block.
218
+ *
219
+ * The tab and the selected scenario are local state; the panel only reports a run through `onRun`. Output is
220
+ * re-keyed per run so it animates in every time. A caret blinks at the end of the prompt while the prompt area
221
+ * has focus. The Run button carries a `Mod+Enter` chip — ⌘↵ on macOS and iOS, Ctrl+Enter elsewhere — which is
222
+ * hidden on touch devices that show no keyboard hints.
223
+ *
224
+ * @example
225
+ * ```tsx
226
+ * <BotInstructionsPanel
227
+ * version="v4"
228
+ * bot={{ initial: "R", name: "Researcher" }}
229
+ * instructions={[["Summarise ", { variable: "sheet" }, " for the team."]]}
230
+ * variables={[{ name: "sheet", value: "Q3 metrics" }]}
231
+ * scenarios={["Weekly digest", "Board update"]}
232
+ * onRun={(scenario) => run(scenario)}
233
+ * />
234
+ * ```
156
235
  */
157
236
  export function BotInstructionsPanel({
158
237
  title = "Instructions",
@@ -3,7 +3,9 @@ import type { ComponentProps, ReactNode } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { Avatar } from "./avatar";
5
5
 
6
+ /** Props for {@link BotListRow}: the bot name, last-activity time and preview, the live and selected states. */
6
7
  export type BotListRowProps = Omit<ComponentProps<"button">, "className"> & {
8
+ /** Bot display name, truncated with an ellipsis when it does not fit. */
7
9
  name: ReactNode;
8
10
  /** Last activity time, such as "8:41 AM". */
9
11
  time?: ReactNode;
@@ -11,11 +13,21 @@ export type BotListRowProps = Omit<ComponentProps<"button">, "className"> & {
11
13
  preview?: ReactNode;
12
14
  /** Running bots show the live dot on the avatar. */
13
15
  live?: boolean;
16
+ /** The row for the bot currently open in the panel; paints the 10% ink selected fill and sets `aria-pressed`. */
14
17
  selected?: boolean;
18
+ /** Extra classes merged onto the button, for margins or width overrides. */
15
19
  className?: string;
16
20
  };
17
21
 
18
- /** Figma `Bot list row` (64:456): a flat 12px-padded row; hover 5% ink, selected 10% ink. */
22
+ /**
23
+ * Figma `Bot list row` (64:456): a flat 12px-padded row; hover 5% ink, selected 10% ink. The whole row is the
24
+ * button, so the accessible name comes from its text content (name, time and preview).
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * <BotListRow name="Researcher" time="8:41 AM" preview="Pulled the Q3 numbers" live selected />
29
+ * ```
30
+ */
19
31
  export function BotListRow({ name, time, preview, live, selected, className, ...props }: BotListRowProps) {
20
32
  return (
21
33
  <button
@@ -5,17 +5,28 @@ import { cn } from "../lib/cn";
5
5
  import { PanelPillTabs } from "./bot-instructions-panel";
6
6
  import { Switch } from "./switch";
7
7
 
8
- type WithClass<T> = Omit<T, "className"> & { className?: string };
8
+ type WithClass<T> = Omit<T, "className"> & {
9
+ /** Extra classes merged onto the root element. */
10
+ className?: string;
11
+ };
9
12
 
13
+ /** One switch row of {@link BotMemoryPanel}: a label, an optional description and the Switch state. */
10
14
  export type BotMemorySetting = {
15
+ /** Stable key of the row, passed back to `onSettingChange`. */
11
16
  id: string;
17
+ /** Row title, also used as the switch's `aria-label`. */
12
18
  label: string;
19
+ /** Optional second line under the label, e.g. what the setting does. */
13
20
  description?: string;
21
+ /** Initial switch state; the switch is uncontrolled after mount. */
14
22
  defaultChecked?: boolean;
15
23
  };
16
24
 
25
+ /** One remembered item of {@link BotMemoryPanel}, grouped under its `group` heading. */
17
26
  export type BotMemoryItem = {
27
+ /** Stable key of the memory. */
18
28
  id: string;
29
+ /** The remembered text, on the first line of the row. */
19
30
  text: string;
20
31
  /** Where the memory came from, for example `From a chat · Oct 8`. */
21
32
  source: string;
@@ -25,25 +36,47 @@ export type BotMemoryItem = {
25
36
  fresh?: boolean;
26
37
  };
27
38
 
28
- export type BotMemoryToast = { title: string; message?: string };
39
+ /** The transient "Memory updated" pill of {@link BotMemoryPanel}: a title and an optional second line. */
40
+ export type BotMemoryToast = {
41
+ /** Bold pill text, e.g. "Memory updated". */
42
+ title: string;
43
+ /** Optional detail shown after the title, e.g. what was saved. */
44
+ message?: string;
45
+ };
29
46
 
47
+ /** Props of {@link BotMemoryPanel}. */
30
48
  export type BotMemoryPanelProps = WithClass<Omit<ComponentProps<"section">, "title">> & {
49
+ /**
50
+ * Heading of the panel.
51
+ * @default "Memory"
52
+ */
31
53
  title?: ReactNode;
54
+ /** Muted second line under the heading, e.g. how many memories are stored. */
32
55
  subtitle?: ReactNode;
33
56
  /** Fill of the memory meter, 0 to 100. */
34
57
  usage: number;
58
+ /** Switch rows shown under the header, in order. */
35
59
  settings?: BotMemorySetting[];
60
+ /** All memories; they are grouped by `group` and filtered by the pill tabs. */
36
61
  memories: BotMemoryItem[];
37
62
  /** The "Memory updated" pill; it enters with a pop and leaves through its close button. */
38
63
  toast?: BotMemoryToast;
64
+ /** Fires once the closing pill has finished its exit animation. */
39
65
  onToastDismiss?: () => void;
66
+ /** "Add memory" at the bottom of the panel. */
40
67
  onAdd?: () => void;
68
+ /** Fires when a settings switch is flipped, with the setting `id` and the new state. */
41
69
  onSettingChange?: (id: string, checked: boolean) => void;
42
70
  /** Makes the search button actionable; without it the icon is a plain decoration. */
43
71
  onSearch?: () => void;
44
72
  };
45
73
 
46
- function Divider({ inset }: { inset?: boolean }) {
74
+ /** Hairline separator between panel rows. */
75
+ // prettier-ignore
76
+ function Divider({ inset }: {
77
+ /** Indents the rule by 20px to match the surrounding rows. */
78
+ inset?: boolean;
79
+ }) {
47
80
  return (
48
81
  <div className={cn("flex self-stretch", inset && "px-5")}>
49
82
  <div role="separator" className="h-px flex-1 bg-overlay-ink-8" />
@@ -54,6 +87,28 @@ function Divider({ inset }: { inset?: boolean }) {
54
87
  /**
55
88
  * Bot memory panel (Paper `ZO2-1` / `HPY-0`): a 24px-radius surface with a memory meter, two switch rows,
56
89
  * a filter pill track, a transient "Memory updated" pill and the grouped memories.
90
+ *
91
+ * The meter clamps `usage` to 0–100 and exposes it as a `progressbar`. The filter tabs are built from the
92
+ * memories' `group` values plus an "All" tab and are component state; each row rises in with a 30ms
93
+ * stagger. The toast pill shows while a `toast` is passed, plays its exit animation when its close button
94
+ * is pressed, re-enters with a pop for a new toast object, and calls `onToastDismiss` when the exit
95
+ * animation ends.
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * import { BotMemoryPanel } from "@horon-ai/react";
100
+ *
101
+ * function Example() {
102
+ * return (
103
+ * <BotMemoryPanel
104
+ * usage={42}
105
+ * settings={[{ id: "auto", label: "Memory", description: "Let the assistant remember chats", defaultChecked: true }]}
106
+ * memories={[{ id: "m1", text: "Prefers Korean replies", source: "From a chat · Oct 8", group: "Preference" }]}
107
+ * onAdd={() => {}}
108
+ * />
109
+ * );
110
+ * }
111
+ * ```
57
112
  */
58
113
  export function BotMemoryPanel({
59
114
  title = "Memory",
@@ -4,23 +4,42 @@ import type { ComponentProps, ReactNode } from "react";
4
4
 
5
5
  import { cn } from "../lib/cn";
6
6
 
7
+ /**
8
+ * Root of the bottom sheet: owns the open state and the drag/Escape dismissal. Wrap a
9
+ * {@link BottomSheetTrigger} and a {@link BottomSheetContent}; the sheet is only mounted while open.
10
+ */
7
11
  export const BottomSheet = Drawer.Root;
12
+ /** The element that opens the sheet, forwarded to Base UI's trigger unchanged. */
8
13
  export const BottomSheetTrigger = Drawer.Trigger;
14
+ /** A close control for the sheet body; it also dismisses on scrim, Escape and a downward drag. */
9
15
  export const BottomSheetClose = Drawer.Close;
10
16
 
17
+ /** Props of {@link BottomSheetContent}. */
11
18
  export type BottomSheetContentProps = Omit<ComponentProps<typeof Drawer.Popup>, "className" | "title"> & {
19
+ /** Sheet title, rendered in the drawer's title slot above the children. */
12
20
  title: ReactNode;
13
21
  /**
14
22
  * `panel` is the Figma panel sheet (374px max, label-lg title). `detail` is the Paper detail sheet:
15
23
  * full width, 18px rhythm, a caller-built header (tile, title, description) set flush left.
24
+ * @default "panel"
16
25
  */
17
26
  variant?: "panel" | "detail";
18
- /** `glass` is the veil sheet (`veil-overlay`); `solid` is the Paper workspace sheet: opaque default fill, 20px top corners, 8/24/16 padding. */
27
+ /**
28
+ * `glass` is the veil sheet (`veil-overlay`); `solid` is the Paper workspace sheet: opaque default fill, 20px top corners, 8/24/16 padding.
29
+ * @default "solid"
30
+ */
19
31
  surface?: "glass" | "solid";
20
- /** Draws the dimmed, blurred scrim behind the sheet. Paper's floating panel sheet has none. */
32
+ /**
33
+ * Draws the dimmed, blurred scrim behind the sheet. Paper's floating panel sheet has none.
34
+ * @default true
35
+ */
21
36
  backdrop?: boolean;
22
- /** Insets the sheet 16px from the sides and bottom edge (Paper's 358px panel sheet) instead of docking it full width. */
37
+ /**
38
+ * Insets the sheet 16px from the sides and bottom edge (Paper's 358px panel sheet) instead of docking it full width.
39
+ * @default false
40
+ */
23
41
  floating?: boolean;
42
+ /** Extra classes merged onto the sheet popup. */
24
43
  className?: string;
25
44
  };
26
45
 
@@ -28,6 +47,29 @@ export type BottomSheetContentProps = Omit<ComponentProps<typeof Drawer.Popup>,
28
47
  * Figma `Bottom sheet` (Panels 88:808): the veil with 32px top corners, a 36x5 grabber and a
29
48
  * label-lg title. There is no close button: the sheet closes on the scrim, on Escape, on a
30
49
  * downward drag of the grabber, or through the caller's own bottom action row.
50
+ *
51
+ * Portal-mounted above the page (z-50). The popup slides up on open and down on close; the body
52
+ * scrolls on its own with `overscroll-contain`, and the sheet is capped at `100dvh` minus 2rem.
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * import { BottomSheet, BottomSheetAction, BottomSheetActions, BottomSheetContent, BottomSheetTrigger } from "@horon-ai/react";
57
+ *
58
+ * function Example() {
59
+ * return (
60
+ * <BottomSheet>
61
+ * <BottomSheetTrigger>Open sheet</BottomSheetTrigger>
62
+ * <BottomSheetContent title="Reminder" floating>
63
+ * <p className="text-body-sm text-text-secondary">Every weekday at 9:00</p>
64
+ * <BottomSheetActions>
65
+ * <BottomSheetAction>Cancel</BottomSheetAction>
66
+ * <BottomSheetAction variant="primary">Save</BottomSheetAction>
67
+ * </BottomSheetActions>
68
+ * </BottomSheetContent>
69
+ * </BottomSheet>
70
+ * );
71
+ * }
72
+ * ```
31
73
  */
32
74
  export function BottomSheetContent({
33
75
  title,
@@ -82,14 +124,17 @@ export function BottomSheetContent({
82
124
  );
83
125
  }
84
126
 
127
+ /** Props of {@link BottomSheetRow}. */
85
128
  export type BottomSheetRowProps = Omit<ComponentProps<"button">, "className"> & {
129
+ /** Leading icon rendered at 20px in secondary text color. */
86
130
  icon?: ReactNode;
87
131
  /** Current value on the right, such as "On" or "Weekdays". */
88
132
  value?: ReactNode;
133
+ /** Extra classes merged onto the row button. */
89
134
  className?: string;
90
135
  };
91
136
 
92
- /** A 48px settings row: 20px icon, label, value and a 16px caret. */
137
+ /** A 48px settings row: 20px icon, label, value and a 16px caret. Part of {@link BottomSheetContent}. */
93
138
  export function BottomSheetRow({ icon, value, className, children, ...props }: BottomSheetRowProps) {
94
139
  return (
95
140
  <button
@@ -108,22 +153,31 @@ export function BottomSheetRow({ icon, value, className, children, ...props }: B
108
153
  );
109
154
  }
110
155
 
111
- /** The action row: secondary first, the primary action at the right edge, 8px apart. */
156
+ /** The action row: secondary first, the primary action at the right edge, 8px apart. Wraps onto a second line when the sheet is too narrow. Part of {@link BottomSheetContent}. */
112
157
  export function BottomSheetActions({
113
158
  className,
114
159
  ...props
115
- }: Omit<ComponentProps<"div">, "className"> & { className?: string }) {
160
+ }: Omit<ComponentProps<"div">, "className"> & {
161
+ /** Extra classes merged onto the action row. */
162
+ className?: string;
163
+ }) {
116
164
  return (
117
165
  <div className={cn("relative flex w-full flex-wrap items-center justify-end gap-2 pt-3", className)} {...props} />
118
166
  );
119
167
  }
120
168
 
169
+ /** Props of {@link BottomSheetAction}. */
121
170
  export type BottomSheetActionProps = Omit<ComponentProps<"button">, "className"> & {
171
+ /**
172
+ * `primary` is the filled brand pill, `secondary` the 6% ink pill.
173
+ * @default "secondary"
174
+ */
122
175
  variant?: "primary" | "secondary";
176
+ /** Extra classes merged onto the action button. */
123
177
  className?: string;
124
178
  };
125
179
 
126
- /** The sheet's 44px pill actions: `secondary` is 6% ink, `primary` is ink. */
180
+ /** The sheet's 44px pill actions: `secondary` is 6% ink, `primary` is ink. Part of {@link BottomSheetActions}. */
127
181
  export function BottomSheetAction({ variant = "secondary", className, ...props }: BottomSheetActionProps) {
128
182
  return (
129
183
  <button
@@ -6,26 +6,56 @@ import { BreadcrumbItem } from "./breadcrumb";
6
6
  import { IconButton } from "./icon-button";
7
7
  import { LiquidGlass } from "./liquid-glass";
8
8
 
9
+ /** One crumb of {@link BreadcrumbHeader}; the last crumb in `crumbs` is the current page. */
9
10
  export type BreadcrumbHeaderCrumb = {
11
+ /** Crumb text; also its React key, so crumbs should have distinct labels. */
10
12
  label: string;
13
+ /** Makes the crumb a link; ignored for the last (current) crumb, which stays text. */
11
14
  href?: string;
12
15
  /** Leading 20px ink tile, such as the workspace initial. */
13
16
  tile?: ReactNode;
14
17
  };
15
18
 
19
+ /** Props of {@link BreadcrumbHeader}. */
16
20
  export type BreadcrumbHeaderProps = Omit<ComponentProps<"header">, "className"> & {
17
21
  /** Crumbs in order; the last one is the current page. */
18
22
  crumbs: BreadcrumbHeaderCrumb[];
23
+ /** Adds the search icon button; without it the button is not rendered at all. */
19
24
  onSearch?: () => void;
25
+ /** Adds the "more" (three dots) icon button; without it the button is not rendered at all. */
20
26
  onMore?: () => void;
27
+ /** Accessible label of the search button. @default "Search" */
21
28
  searchLabel?: string;
29
+ /** Accessible label of the more button. @default "More" */
22
30
  moreLabel?: string;
23
- /** `solid` (default) on the page, `glass` over imagery and glass panels. */
31
+ /** `solid` (default) on the page, `glass` over imagery and glass panels. @default "solid" */
24
32
  surface?: "solid" | "glass";
33
+ /** Extra classes merged onto the header. */
25
34
  className?: string;
26
35
  };
27
36
 
28
- /** Paper `Header with breadcrumb`: a 52px regular-glass pill with a crumb row, search and more buttons. */
37
+ /**
38
+ * Paper `Header with breadcrumb`: a 52px regular-glass pill with a crumb row, search and more buttons.
39
+ *
40
+ * The crumb trail scrolls sideways inside the pill instead of truncating; only the last crumb truncates.
41
+ * The two icon buttons (36px) are only rendered when their handlers are passed. On `glass` a
42
+ * {@link LiquidGlass} layer sits under the content, so the header must be placed over imagery.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * import { BreadcrumbHeader } from "@horon-ai/react";
47
+ *
48
+ * function Example() {
49
+ * return (
50
+ * <BreadcrumbHeader
51
+ * crumbs={[{ label: "H", tile: "H", href: "/" }, { label: "Projects", href: "/projects" }, { label: "Horon" }]}
52
+ * onSearch={() => {}}
53
+ * onMore={() => {}}
54
+ * />
55
+ * );
56
+ * }
57
+ * ```
58
+ */
29
59
  export function BreadcrumbHeader({
30
60
  crumbs,
31
61
  onSearch,