@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.
- package/package.json +2 -2
- package/src/components/agent-activity.tsx +75 -7
- package/src/components/agent-lane.tsx +32 -3
- package/src/components/agent-lanes-timeline.tsx +85 -7
- package/src/components/agent-panel.tsx +46 -4
- package/src/components/agent-steering-panel.tsx +53 -3
- package/src/components/approval-card.tsx +34 -2
- package/src/components/avatar.tsx +20 -4
- package/src/components/badge.tsx +24 -3
- package/src/components/banner.tsx +19 -3
- package/src/components/bot-instructions-panel.tsx +86 -7
- package/src/components/bot-list-row.tsx +13 -1
- package/src/components/bot-memory-panel.tsx +58 -3
- package/src/components/bottom-sheet.tsx +61 -7
- package/src/components/breadcrumb-header.tsx +32 -2
- package/src/components/breadcrumb.tsx +31 -1
- package/src/components/button.tsx +46 -1
- package/src/components/calendar.tsx +76 -6
- package/src/components/card.tsx +30 -1
- package/src/components/chat-bubble.tsx +32 -3
- package/src/components/chat-controls.tsx +60 -7
- package/src/components/checkbox.tsx +22 -1
- package/src/components/chip.tsx +29 -2
- package/src/components/choice-card.tsx +25 -1
- package/src/components/code-panel.tsx +28 -3
- package/src/components/combobox.tsx +52 -2
- package/src/components/command-palette.tsx +65 -1
- package/src/components/composer.tsx +28 -1
- package/src/components/data-table.tsx +198 -16
- package/src/components/date-field.tsx +31 -2
- package/src/components/dialog.tsx +45 -3
- package/src/components/directory-card.tsx +23 -2
- package/src/components/document-extract-panel.tsx +83 -5
- package/src/components/empty-state.tsx +21 -2
- package/src/components/field.tsx +49 -3
- package/src/components/file-drop.tsx +39 -4
- package/src/components/filter-bar.tsx +38 -1
- package/src/components/flue-background.tsx +88 -4
- package/src/components/horon-icon.tsx +50 -3
- package/src/components/horon-wordmark.tsx +26 -1
- package/src/components/icon-button.tsx +38 -1
- package/src/components/input-group.tsx +57 -6
- package/src/components/input.tsx +48 -2
- package/src/components/integration-row.tsx +43 -2
- package/src/components/kbd.tsx +28 -2
- package/src/components/knowledge-base-panel.tsx +40 -2
- package/src/components/liquid-glass.tsx +16 -0
- package/src/components/list-detail.tsx +89 -7
- package/src/components/marketplace-card.tsx +40 -4
- package/src/components/mcp-server-row.tsx +49 -3
- package/src/components/menu.tsx +83 -3
- package/src/components/message-extras.tsx +50 -4
- package/src/components/modal.tsx +71 -4
- package/src/components/orchestrator-panel.tsx +48 -4
- package/src/components/page-header.tsx +26 -3
- package/src/components/plan-card.tsx +28 -1
- package/src/components/plan-step.tsx +41 -1
- package/src/components/plugin-row.tsx +61 -3
- package/src/components/progress.tsx +32 -2
- package/src/components/radio-group.tsx +55 -2
- package/src/components/research-run-panel.tsx +73 -3
- package/src/components/schedule.tsx +163 -12
- package/src/components/segmented-control.tsx +57 -2
- package/src/components/select.tsx +60 -0
- package/src/components/settings-list.tsx +66 -8
- package/src/components/sidebar.tsx +214 -23
- package/src/components/skeleton.tsx +61 -4
- package/src/components/slider.tsx +30 -0
- package/src/components/source-row.tsx +43 -4
- package/src/components/spinner.tsx +29 -2
- package/src/components/split-button.tsx +37 -3
- package/src/components/switch.tsx +31 -1
- package/src/components/tab-bar.tsx +47 -2
- package/src/components/tabs.tsx +59 -5
- package/src/components/task-row.tsx +49 -3
- package/src/components/textarea-field.tsx +36 -2
- package/src/components/textarea.tsx +35 -2
- package/src/components/toast.tsx +52 -3
- package/src/components/tool-call.tsx +87 -4
- package/src/components/tool-calls-header.tsx +32 -1
- package/src/components/tool-permissions.tsx +50 -2
- package/src/components/tooltip.tsx +33 -1
- package/src/components/top-bar.tsx +35 -2
- package/src/components/usage-meter.tsx +28 -2
- package/src/components/workspace-menu.tsx +46 -1
- package/src/hooks/use-active-indicator.ts +44 -1
- package/src/hooks/use-color-scheme.ts +24 -1
- package/src/hooks/use-copy-to-clipboard.ts +23 -0
- package/src/hooks/use-device.ts +29 -1
- package/src/hooks/use-hotkey.ts +21 -0
- package/src/hooks/use-media-query.ts +18 -0
- package/src/hooks/use-platform.ts +30 -1
- package/src/hooks/use-reduced-motion.ts +18 -1
- package/src/index.ts +31 -0
- package/src/lib/cn.ts +13 -1
- package/src/lib/cursor-ghost.ts +16 -6
- 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
|
-
/**
|
|
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",
|
package/src/components/badge.tsx
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
-
/**
|
|
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"> & {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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"> & {
|
|
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
|
-
/**
|
|
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,
|