@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.
- package/README.md +2 -2
- 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
|
@@ -81,12 +81,33 @@ import {
|
|
|
81
81
|
} from "@phosphor-icons/react";
|
|
82
82
|
import type { ComponentProps } from "react";
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
/** Props for the Horon mark: any svg attribute plus a size like a Phosphor icon. */
|
|
85
|
+
export type HoronIconProps = Omit<ComponentProps<"svg">, "children"> & {
|
|
86
|
+
/** Width and height together: a px number or a CSS length. @default "1em" */
|
|
87
|
+
size?: number | string;
|
|
88
|
+
};
|
|
85
89
|
|
|
86
90
|
/**
|
|
87
91
|
* Figma `icon/horon` (256:9116): the line falcon eye that stands in for Horon in
|
|
88
92
|
* navigation. Phosphor has no equivalent, so the path is copied from the Tab bar
|
|
89
93
|
* (63:441) export. It takes `currentColor` and sizes like a Phosphor icon.
|
|
94
|
+
*
|
|
95
|
+
* The svg is `aria-hidden` by default and its 22-unit viewBox scales with `size`, so it drops into any place
|
|
96
|
+
* a Phosphor glyph fits. The default `1em` makes it follow the font size, which is what a row of mixed icons
|
|
97
|
+
* needs.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```tsx
|
|
101
|
+
* import { HoronIcon } from "@horon-ai/react";
|
|
102
|
+
*
|
|
103
|
+
* function BrandMark() {
|
|
104
|
+
* return (
|
|
105
|
+
* <span className="flex size-10 items-center justify-center rounded-full bg-bg-subtle text-text-primary">
|
|
106
|
+
* <HoronIcon size={22} />
|
|
107
|
+
* </span>
|
|
108
|
+
* );
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
90
111
|
*/
|
|
91
112
|
export function HoronIcon({ size = "1em", ...props }: HoronIconProps) {
|
|
92
113
|
return (
|
|
@@ -100,6 +121,9 @@ export function HoronIcon({ size = "1em", ...props }: HoronIconProps) {
|
|
|
100
121
|
* Paper's icon contract: every icon is a component named `icon/<name>`. `iconNames` is that list in
|
|
101
122
|
* Paper order. Paper draws three sizes (16 in small controls and captions, 20 in buttons and rows,
|
|
102
123
|
* 24 in bars and empty states); 12, 14, 18 and 22 elsewhere in the code stay as they are.
|
|
124
|
+
*
|
|
125
|
+
* The tuple is the single source of the registry: `HoronIconName` derives from it and `phosphorGlyphs`
|
|
126
|
+
* must map every entry except `horon`, so a name added here fails to type-check until it has a glyph.
|
|
103
127
|
*/
|
|
104
128
|
export const iconNames = [
|
|
105
129
|
"plus",
|
|
@@ -183,6 +207,7 @@ export const iconNames = [
|
|
|
183
207
|
"plus-circle",
|
|
184
208
|
] as const;
|
|
185
209
|
|
|
210
|
+
/** Every registered icon name: a literal union derived from `iconNames`. */
|
|
186
211
|
export type HoronIconName = (typeof iconNames)[number];
|
|
187
212
|
|
|
188
213
|
const phosphorGlyphs: Record<Exclude<HoronIconName, "horon">, Icon> = {
|
|
@@ -266,13 +291,35 @@ const phosphorGlyphs: Record<Exclude<HoronIconName, "horon">, Icon> = {
|
|
|
266
291
|
"plus-circle": PlusCircle,
|
|
267
292
|
};
|
|
268
293
|
|
|
294
|
+
/** Props for `HoronGlyph`: the registry name plus the same svg attributes a Phosphor icon takes. */
|
|
269
295
|
export type HoronGlyphProps = Omit<ComponentProps<"svg">, "children" | "ref"> & {
|
|
296
|
+
/** Which registered glyph to draw; `horon` resolves to the Horon mark instead of a Phosphor icon. */
|
|
270
297
|
name: HoronIconName;
|
|
271
|
-
/** Paper sizes are 16, 20 and 24. */
|
|
298
|
+
/** Paper sizes are 16, 20 and 24. @default 20 */
|
|
272
299
|
size?: number | string;
|
|
273
300
|
};
|
|
274
301
|
|
|
275
|
-
/**
|
|
302
|
+
/**
|
|
303
|
+
* Figma `icon/<name>`: resolves a registry name to its glyph. Takes `currentColor`, so the parent's text color drives it.
|
|
304
|
+
*
|
|
305
|
+
* The runtime lookup behind an icon name stored as data (a tool's icon, a settings row): `horon` returns
|
|
306
|
+
* `HoronIcon`, everything else the matching Phosphor component. Like Phosphor, the svg is `aria-hidden`;
|
|
307
|
+
* put the meaning on the surrounding button or label. Outside data-driven cases, prefer importing the
|
|
308
|
+
* Phosphor icon directly so the bundler can tree-shake it.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```tsx
|
|
312
|
+
* import { HoronGlyph, type HoronIconName } from "@horon-ai/react";
|
|
313
|
+
*
|
|
314
|
+
* function ToolIcon({ icon }: { icon: HoronIconName }) {
|
|
315
|
+
* return (
|
|
316
|
+
* <span className="text-text-secondary">
|
|
317
|
+
* <HoronGlyph name={icon} size={20} />
|
|
318
|
+
* </span>
|
|
319
|
+
* );
|
|
320
|
+
* }
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
276
323
|
export function HoronGlyph({ name, size = 20, ...props }: HoronGlyphProps) {
|
|
277
324
|
if (name === "horon") return <HoronIcon size={size} {...props} />;
|
|
278
325
|
const Glyph = phosphorGlyphs[name];
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { ComponentProps } from "react";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** Props of {@link HoronWordmark}: any svg attribute plus the accessible name. */
|
|
4
|
+
export type HoronWordmarkProps = Omit<ComponentProps<"svg">, "children"> & {
|
|
5
|
+
/**
|
|
6
|
+
* Accessible name of the wordmark. It becomes the `aria-label` on the `role="img"` svg, so screen readers
|
|
7
|
+
* announce it as one image rather than six paths.
|
|
8
|
+
* @default "Horon"
|
|
9
|
+
*/
|
|
10
|
+
title?: string;
|
|
11
|
+
};
|
|
4
12
|
|
|
5
13
|
const PATHS = [
|
|
6
14
|
"M0.0230414 11.2212H1.91244L1.92626 8.60366L1.93548 5.99076H8.47923L8.48845 8.60366L8.50228 11.2212H10.3917L10.4193 8.06449C10.447 5.32717 10.5207 2.68663 10.6267 0.585252L10.6543 0H8.47923V4.2857H1.93548V0H0L0.0230414 11.2212Z",
|
|
@@ -14,6 +22,23 @@ const PATHS = [
|
|
|
14
22
|
/**
|
|
15
23
|
* The UI-size Horon wordmark from the Figma Sidebar (88:808): a 48.7 x 11.5
|
|
16
24
|
* vector in `currentColor`. Scale it with `height`; the width follows.
|
|
25
|
+
*
|
|
26
|
+
* Six filled paths trace the letter outlines as one drawing, painted in the current text color, so color it
|
|
27
|
+
* with the surrounding `text-*` class rather than a `fill` prop. The paths carry no individual names; only
|
|
28
|
+
* `title` gives the svg its accessible name.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* import { HoronWordmark } from "@horon-ai/react";
|
|
33
|
+
*
|
|
34
|
+
* function SidebarBrand() {
|
|
35
|
+
* return (
|
|
36
|
+
* <div className="flex items-center gap-2 text-text-primary">
|
|
37
|
+
* <HoronWordmark height={12} />
|
|
38
|
+
* </div>
|
|
39
|
+
* );
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
17
42
|
*/
|
|
18
43
|
export function HoronWordmark({ title = "Horon", height = 11.5, ...props }: HoronWordmarkProps) {
|
|
19
44
|
return (
|
|
@@ -5,15 +5,31 @@ import { cn } from "../lib/cn";
|
|
|
5
5
|
import type { ControlSize } from "../lib/size";
|
|
6
6
|
import { LiquidGlass } from "./liquid-glass";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Fill and text colour of an icon button:
|
|
10
|
+
*
|
|
11
|
+
* - `primary`: solid brand fill with the inverse icon, for the one main action in a cluster.
|
|
12
|
+
* - `ghost` (default): no fill until hover, primary icon; the common toolbar and row action.
|
|
13
|
+
* - `glass`: the clear glass disc with a `LiquidGlass` layer, for floating chrome over imagery.
|
|
14
|
+
* - `solid`: bordered raised surface, for a button that sits on the page background.
|
|
15
|
+
* - `quiet`: no fill and no liquid layer, secondary icon that turns primary on hover; for dark shells
|
|
16
|
+
* and title bars where a glass disc would read as a grey bubble.
|
|
17
|
+
*/
|
|
8
18
|
export type IconButtonVariant = "primary" | "ghost" | "glass" | "solid" | "quiet";
|
|
19
|
+
/** Square footprint: `sm` 32px, `md` 40px, `lg` 48px. */
|
|
9
20
|
export type IconButtonSize = ControlSize;
|
|
10
21
|
|
|
22
|
+
/** Props of {@link IconButton}: the treatment, the footprint and the glyph. */
|
|
11
23
|
export type IconButtonProps = Omit<ComponentProps<typeof BaseButton>, "className" | "children"> & {
|
|
24
|
+
/** Treatment, from the list on `IconButtonVariant`. @default "ghost" */
|
|
12
25
|
variant?: IconButtonVariant;
|
|
26
|
+
/** Square footprint: `sm` 32px, `md` 40px, `lg` 48px, with the icon at 16/20/24px. @default "md" */
|
|
13
27
|
size?: IconButtonSize;
|
|
28
|
+
/** The glyph. It is wrapped in a positioning span; size it through the button rather than by hand. */
|
|
14
29
|
icon: ReactNode;
|
|
15
30
|
/** Accessible name; the button has no visible text. */
|
|
16
31
|
label: string;
|
|
32
|
+
/** Extra classes for the button. */
|
|
17
33
|
className?: string;
|
|
18
34
|
};
|
|
19
35
|
|
|
@@ -37,7 +53,28 @@ const variantClass: Record<IconButtonVariant, string> = {
|
|
|
37
53
|
"text-text-secondary hover:not-data-disabled:bg-overlay-ink-6 hover:not-data-disabled:text-text-primary active:not-data-disabled:bg-overlay-ink-10",
|
|
38
54
|
};
|
|
39
55
|
|
|
40
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Figma `Icon button` (57:167): Primary, Ghost, Glass and Solid in three sizes, plus a fill-free `quiet` variant.
|
|
58
|
+
*
|
|
59
|
+
* A round, icon-only Base UI button. `label` becomes `aria-label`, since nothing is visible; the visible
|
|
60
|
+
* disc is 32/40/48px while an invisible pseudo-element keeps a 44px touch target around it. It renders
|
|
61
|
+
* `LiquidGlass` inside the `glass` variant only, and disabled state is 40% opacity with `cursor-not-allowed`.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* import { Gear, Plus } from "@phosphor-icons/react";
|
|
66
|
+
* import { IconButton } from "@horon-ai/react";
|
|
67
|
+
*
|
|
68
|
+
* function Actions() {
|
|
69
|
+
* return (
|
|
70
|
+
* <div className="flex items-center gap-1">
|
|
71
|
+
* <IconButton icon={<Plus />} label="New agent" variant="primary" />
|
|
72
|
+
* <IconButton icon={<Gear />} label="Settings" />
|
|
73
|
+
* </div>
|
|
74
|
+
* );
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
41
78
|
export function IconButton({ variant = "ghost", size = "md", icon, label, className, ...props }: IconButtonProps) {
|
|
42
79
|
return (
|
|
43
80
|
<BaseButton
|
|
@@ -5,8 +5,10 @@ import { cn } from "../lib/cn";
|
|
|
5
5
|
import type { ControlSize } from "../lib/size";
|
|
6
6
|
import { fieldBoxClass, fieldTextClass, type InputSurface } from "./input";
|
|
7
7
|
|
|
8
|
+
/** Height rung of the group and its segments: `sm` 32px, `md` 40px, `lg` 48px. */
|
|
8
9
|
export type InputGroupSize = ControlSize;
|
|
9
10
|
|
|
11
|
+
/** Props of {@link InputGroup}: the input's own props plus the segments around it. */
|
|
10
12
|
export type InputGroupProps = Omit<ComponentProps<typeof BaseInput>, "className" | "prefix" | "size"> & {
|
|
11
13
|
/**
|
|
12
14
|
* Segment before the input: `<Select variant="segment" />` or `<Combobox variant="segment" />`
|
|
@@ -15,19 +17,35 @@ export type InputGroupProps = Omit<ComponentProps<typeof BaseInput>, "className"
|
|
|
15
17
|
prefix?: ReactNode;
|
|
16
18
|
/** Segment after the input, such as a currency select. */
|
|
17
19
|
suffix?: ReactNode;
|
|
20
|
+
/** Height and text rung: 32/40/48px, matching a standalone `Input`. @default "md" */
|
|
18
21
|
size?: InputGroupSize;
|
|
22
|
+
/** Paints the danger border on the whole group and marks the input `aria-invalid`. */
|
|
19
23
|
invalid?: boolean;
|
|
20
|
-
/** Solid (default) or glass, as on `Input`. */
|
|
24
|
+
/** Solid (default) or glass, as on `Input`. @default "solid" */
|
|
21
25
|
surface?: InputSurface;
|
|
26
|
+
/** Extra classes for the box that wraps the segments and the input. */
|
|
22
27
|
className?: string;
|
|
23
28
|
};
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
/** What a segment reads from the group it sits in. */
|
|
31
|
+
type InputGroupContextValue = {
|
|
32
|
+
/** Height rung the group was given. */
|
|
33
|
+
size: InputGroupSize;
|
|
34
|
+
/** Whether the group is disabled, so segments can dim and drop their own interactivity. */
|
|
35
|
+
disabled: boolean;
|
|
36
|
+
/** Surface the group uses, so a segment and the box stay the same material. */
|
|
37
|
+
surface: InputSurface;
|
|
38
|
+
};
|
|
26
39
|
|
|
27
40
|
/** Lets a segment (select, combobox or fixed text) take the group's size, disabled state and surface. */
|
|
28
41
|
const InputGroupContext = createContext<InputGroupContextValue | null>(null);
|
|
29
42
|
|
|
30
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The group a segment sits in, or null outside an `InputGroup`. `Select` and `Combobox` call it in their
|
|
45
|
+
* `segment` variant to pick the right height, padding and disabled look without a prop of their own.
|
|
46
|
+
*
|
|
47
|
+
* @returns The `{ size, disabled, surface }` of the enclosing group, or `null` when there is none.
|
|
48
|
+
*/
|
|
31
49
|
export function useInputGroup() {
|
|
32
50
|
return useContext(InputGroupContext);
|
|
33
51
|
}
|
|
@@ -55,7 +73,30 @@ export const inputGroupSegmentClass: Record<InputGroupSize, string> = {
|
|
|
55
73
|
const divider =
|
|
56
74
|
"relative h-full w-px shrink-0 bg-border-default group-data-invalid:bg-border-danger group-data-disabled:bg-border-default/60";
|
|
57
75
|
|
|
58
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Figma `Input group` (60:429): a glass or solid field split into a select segment and an input segment, in three sizes.
|
|
78
|
+
*
|
|
79
|
+
* One box drawn from `fieldBoxClass` holds the optional `prefix`, the input and the optional `suffix`, with a
|
|
80
|
+
* 1px hairline between them; the hairlines turn danger-red with `invalid` and dim with the group's `disabled`.
|
|
81
|
+
* The group renders its own Base UI input, so pass `placeholder`/`value`/`onChange` as props rather than
|
|
82
|
+
* children. Size, disabled state and surface go to the segments through `InputGroupContext`, which is why a
|
|
83
|
+
* `Select` or `Combobox` with `variant="segment"` matches without extra props.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* import { InputGroup, InputGroupSegment } from "@horon-ai/react";
|
|
88
|
+
*
|
|
89
|
+
* function Website() {
|
|
90
|
+
* return (
|
|
91
|
+
* <InputGroup
|
|
92
|
+
* prefix={<InputGroupSegment>https://</InputGroupSegment>}
|
|
93
|
+
* placeholder="horon.ai"
|
|
94
|
+
* size="md"
|
|
95
|
+
* />
|
|
96
|
+
* );
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
59
100
|
export function InputGroup({
|
|
60
101
|
prefix,
|
|
61
102
|
suffix,
|
|
@@ -99,8 +140,18 @@ export function InputGroup({
|
|
|
99
140
|
);
|
|
100
141
|
}
|
|
101
142
|
|
|
102
|
-
/**
|
|
103
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Part of {@link InputGroup}. A fixed-text segment, such as a unit or a `https://` prefix. Takes the group's
|
|
145
|
+
* size (falling back to `md` outside a group), sits on the 4% ink tint, and dims its text when the group is
|
|
146
|
+
* disabled.
|
|
147
|
+
*/
|
|
148
|
+
// prettier-ignore
|
|
149
|
+
export function InputGroupSegment({ className, children }: {
|
|
150
|
+
/** Extra classes for the segment. */
|
|
151
|
+
className?: string;
|
|
152
|
+
/** Segment content: short text, or text plus an icon sized by the segment's rung. */
|
|
153
|
+
children: ReactNode;
|
|
154
|
+
}) {
|
|
104
155
|
const group = useInputGroup();
|
|
105
156
|
return (
|
|
106
157
|
<div
|
package/src/components/input.tsx
CHANGED
|
@@ -4,17 +4,29 @@ import type { ComponentProps, ReactNode } from "react";
|
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import type { ControlSize } from "../lib/size";
|
|
6
6
|
|
|
7
|
+
/** Height rung shared with every sized control: `sm` 32px, `md` 40px, `lg` 48px. */
|
|
7
8
|
export type InputSize = ControlSize;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Where the input sits:
|
|
12
|
+
*
|
|
13
|
+
* - `glass`: on imagery or a glass panel; uses the veil field (`.veil-field`) with no liquid layer inside.
|
|
14
|
+
* - `solid`: on a raised surface (`bg-bg-surface`), the default.
|
|
15
|
+
* - `solid-on-page`: directly on the page background (`bg-bg-default`), so the box reads against it.
|
|
16
|
+
*/
|
|
8
17
|
export type InputSurface = "glass" | "solid" | "solid-on-page";
|
|
9
18
|
|
|
19
|
+
/** Props of {@link Input}: the native input attributes plus the box's size, surface and decorations. */
|
|
10
20
|
export type InputProps = Omit<ComponentProps<typeof BaseInput>, "className" | "size"> & {
|
|
21
|
+
/** Height and text rung: 32/40/48px with `body-sm/md/lg` text. @default "md" */
|
|
11
22
|
size?: InputSize;
|
|
12
|
-
/** `glass` over imagery and glass panels, `solid` on raised surfaces, `solid-on-page` directly on the page background. */
|
|
23
|
+
/** `glass` over imagery and glass panels, `solid` on raised surfaces, `solid-on-page` directly on the page background. @default "solid" */
|
|
13
24
|
surface?: InputSurface;
|
|
14
25
|
/** Leading icon, 16px (sm, md) or 20px (lg), in the secondary text color. */
|
|
15
26
|
icon?: ReactNode;
|
|
16
27
|
/** Shows the error border. Inside a `Field`, an invalid field does the same. */
|
|
17
28
|
invalid?: boolean;
|
|
29
|
+
/** Extra classes for the box that wraps the input. */
|
|
18
30
|
className?: string;
|
|
19
31
|
/** Classes for the native `<input>`. */
|
|
20
32
|
inputClassName?: string;
|
|
@@ -34,19 +46,53 @@ export const fieldTextClass: Record<ControlSize, string> = {
|
|
|
34
46
|
lg: "text-body-lg",
|
|
35
47
|
};
|
|
36
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The class set a field-shaped control composes: the box (border, radius, focus ring), the surface fill and
|
|
51
|
+
* the inner text/input. Shared by `Input` and `InputGroup` so every field-style control stays one material.
|
|
52
|
+
*/
|
|
37
53
|
export const fieldBoxClass = {
|
|
38
54
|
/** The box is the focus target's container, so the ring is drawn on `:focus-within`, not on the inner input. */
|
|
39
55
|
base: "motion-colors relative flex min-w-0 max-w-full items-center overflow-clip rounded-md border focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-text-brand",
|
|
40
56
|
/** The veil field (globals.css `.veil-field`): no liquid layers, so hosts render no `LiquidGlass`. */
|
|
41
57
|
glass: "veil-field has-[[data-disabled]]:opacity-60 has-[:disabled]:opacity-60",
|
|
58
|
+
/** Raised surface fill with a default border; disabled or invalid states repaint both fill and border. */
|
|
42
59
|
solid:
|
|
43
60
|
"border-border-default bg-bg-surface has-[[data-disabled]]:border-border-default has-[:disabled]:border-border-default has-[[data-disabled]]:bg-bg-subtle has-[:disabled]:bg-bg-subtle has-[[data-invalid]]:border-border-danger data-invalid:border-border-danger",
|
|
61
|
+
/** The solid fill on the page background instead of a raised surface; the same disabled and invalid states. */
|
|
44
62
|
"solid-on-page":
|
|
45
63
|
"border-border-default bg-bg-default has-[[data-disabled]]:border-border-default has-[:disabled]:border-border-default has-[[data-disabled]]:bg-bg-subtle has-[:disabled]:bg-bg-subtle has-[[data-invalid]]:border-border-danger data-invalid:border-border-danger",
|
|
64
|
+
/** The inner control: transparent, fills the remaining width, primary text, placeholder and disabled colours. */
|
|
46
65
|
text: "min-w-0 flex-1 bg-transparent text-text-primary outline-none placeholder:text-text-placeholder disabled:cursor-not-allowed disabled:text-text-disabled",
|
|
47
66
|
} as const;
|
|
48
67
|
|
|
49
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Figma `Input` (58:126): SM, MD and LG, on Glass or Solid surface.
|
|
70
|
+
*
|
|
71
|
+
* A bordered box around Base UI's input: render the box, then the optional leading icon, then the native
|
|
72
|
+
* `<input>`, which takes the remaining width. Focus is drawn on the box (`:focus-within`), so the ring never
|
|
73
|
+
* detaches from the field. `invalid` marks both the box (`data-invalid`) and the input (`aria-invalid`); a
|
|
74
|
+
* `Field` that reports invalid does the same through Base UI. Every other prop (`value`, `onChange`,
|
|
75
|
+
* `placeholder`, `disabled`, `type`, …) goes to the native input, while `className`/`inputClassName` split
|
|
76
|
+
* between the box and the input itself.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* import { MagnifyingGlass } from "@phosphor-icons/react";
|
|
81
|
+
* import { Input } from "@horon-ai/react";
|
|
82
|
+
*
|
|
83
|
+
* function Search() {
|
|
84
|
+
* return (
|
|
85
|
+
* <Input
|
|
86
|
+
* type="search"
|
|
87
|
+
* size="md"
|
|
88
|
+
* icon={<MagnifyingGlass />}
|
|
89
|
+
* placeholder="Search agents"
|
|
90
|
+
* aria-label="Search agents"
|
|
91
|
+
* />
|
|
92
|
+
* );
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
50
96
|
export function Input({
|
|
51
97
|
size = "md",
|
|
52
98
|
surface = "solid",
|
|
@@ -4,21 +4,36 @@ import type { ReactNode } from "react";
|
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { LiquidGlass } from "./liquid-glass";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Connection state of an integration:
|
|
9
|
+
*
|
|
10
|
+
* - `connected`: sky tile, a live-dot "Connected" pill and no button.
|
|
11
|
+
* - `reconnect`: dark-sky tile, a red outline on the row, danger text in `detail` and a danger action button.
|
|
12
|
+
* - `not-connected`: subtle tile, secondary text and the primary "Connect" button.
|
|
13
|
+
*/
|
|
7
14
|
export type IntegrationStatus = "connected" | "reconnect" | "not-connected";
|
|
8
15
|
|
|
16
|
+
/** Props of {@link IntegrationRow}: the service, its connection state and the one action. */
|
|
9
17
|
export type IntegrationRowProps = {
|
|
10
18
|
/** 20px icon shown in inverse on the 40px tile. */
|
|
11
19
|
icon: ReactNode;
|
|
20
|
+
/** Service name on the first line, truncated with an ellipsis. */
|
|
12
21
|
name: ReactNode;
|
|
13
22
|
/** Account and scopes, or the reason a reconnect is needed. */
|
|
14
23
|
detail?: ReactNode;
|
|
24
|
+
/** Drives the tile fill, the pill or button on the right and the row's danger border. */
|
|
15
25
|
status: IntegrationStatus;
|
|
26
|
+
/** Called when the button is pressed; connected rows have no button, so it never fires then. */
|
|
16
27
|
onAction?: () => void;
|
|
28
|
+
/** Pill text for `connected`. @default "Connected" */
|
|
17
29
|
connectedLabel?: string;
|
|
30
|
+
/** Button text for `reconnect`. @default "Reconnect" */
|
|
18
31
|
reconnectLabel?: string;
|
|
32
|
+
/** Button text for `not-connected`. @default "Connect" */
|
|
19
33
|
connectLabel?: string;
|
|
20
|
-
/** `solid` (default) is an opaque surface card with a default border; `glass` is the thick glass row. */
|
|
34
|
+
/** `solid` (default) is an opaque surface card with a default border; `glass` is the thick glass row. @default "solid" */
|
|
21
35
|
surface?: "solid" | "glass";
|
|
36
|
+
/** Extra classes for the row. */
|
|
22
37
|
className?: string;
|
|
23
38
|
};
|
|
24
39
|
|
|
@@ -28,7 +43,33 @@ const tileClass: Record<IntegrationStatus, string> = {
|
|
|
28
43
|
"not-connected": "bg-bg-subtle text-text-secondary",
|
|
29
44
|
};
|
|
30
45
|
|
|
31
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Figma `Integration row` (Integrations 110:883): 40px tile, status pill on the right, solid or thick glass.
|
|
48
|
+
*
|
|
49
|
+
* A one-line service row: 40px rounded tile with the icon, a name and an optional detail line that share a
|
|
50
|
+
* `basis-40` column so the row wraps on narrow widths, and one action slot on the right. The pill and the
|
|
51
|
+
* button are both mounted and swapped with `motion-swap`; the inactive one is `aria-hidden` and untabbable,
|
|
52
|
+
* so the row never exposes a dead control. `glass` adds a `LiquidGlass` layer and moves the reconnect outline
|
|
53
|
+
* to the glass edge.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* import { SlackLogo } from "@phosphor-icons/react";
|
|
58
|
+
* import { IntegrationRow } from "@horon-ai/react";
|
|
59
|
+
*
|
|
60
|
+
* function Slack() {
|
|
61
|
+
* return (
|
|
62
|
+
* <IntegrationRow
|
|
63
|
+
* icon={<SlackLogo />}
|
|
64
|
+
* name="Slack"
|
|
65
|
+
* detail="horon.slack.com · 4 scopes"
|
|
66
|
+
* status="reconnect"
|
|
67
|
+
* onAction={() => reconnect("slack")}
|
|
68
|
+
* />
|
|
69
|
+
* );
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
32
73
|
export function IntegrationRow({
|
|
33
74
|
icon,
|
|
34
75
|
name,
|
package/src/components/kbd.tsx
CHANGED
|
@@ -5,13 +5,16 @@ import { cn } from "../lib/cn";
|
|
|
5
5
|
import { formatShortcut } from "../lib/platform";
|
|
6
6
|
import type { ControlSize } from "../lib/size";
|
|
7
7
|
|
|
8
|
+
/** Height rung of the chip, shared with the other sized controls: `sm`, `md`, `lg`. */
|
|
8
9
|
export type KbdSize = ControlSize;
|
|
9
10
|
|
|
11
|
+
/** Props of {@link Kbd}: the native `kbd` attributes plus the chip size. */
|
|
10
12
|
export type KbdProps = Omit<ComponentProps<"kbd">, "className"> & {
|
|
11
|
-
/** Chip size: `sm` tighter caption, `md` the Command palette chip (default), `lg` label-sm with more padding. */
|
|
13
|
+
/** Chip size: `sm` tighter caption, `md` the Command palette chip (default), `lg` label-sm with more padding. @default "md" */
|
|
12
14
|
size?: KbdSize;
|
|
13
15
|
/** Shortcut such as `Mod+Enter`, drawn with the OS's key labels (`⌘↵` / `Ctrl+Enter`) instead of children. */
|
|
14
16
|
keys?: string;
|
|
17
|
+
/** Extra classes for the `kbd` element. */
|
|
15
18
|
className?: string;
|
|
16
19
|
};
|
|
17
20
|
|
|
@@ -22,7 +25,30 @@ const sizeClass: Record<KbdSize, string> = {
|
|
|
22
25
|
lg: "h-6 gap-1 px-2 text-label-sm [&_svg]:size-3.5",
|
|
23
26
|
};
|
|
24
27
|
|
|
25
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Figma `Kbd` (Command palette 110:676): a 6% ink chip with caption text.
|
|
30
|
+
*
|
|
31
|
+
* A `<kbd>` for a keyboard shortcut: inline-flex, `rounded-xs`, secondary text, centred on a trimmed
|
|
32
|
+
* line box so the glyph sits optically centred in the chip. Pass `keys` as a `+`-joined shortcut such as
|
|
33
|
+
* `Mod+Enter` and the chip draws the OS's labels — `⌘↵` on macOS and iOS, `Ctrl+Enter` on Windows, Linux
|
|
34
|
+
* and Android, and the Mac symbols whenever the OS is still unknown (server render, first hydration pass).
|
|
35
|
+
* Without `keys` it renders `children`, so several chips can be composed by hand
|
|
36
|
+
* (`<Kbd>⌘</Kbd><Kbd>K</Kbd>`). Sizes are 16/20/24px tall, and any icon inside is resized with the chip
|
|
37
|
+
* (`2.5`/`3`/`3.5`), so pass a bare Phosphor icon or glyph.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* import { Kbd } from "@horon-ai/react";
|
|
42
|
+
*
|
|
43
|
+
* function Hint() {
|
|
44
|
+
* return (
|
|
45
|
+
* <span className="flex items-center gap-1 text-caption">
|
|
46
|
+
* Search <Kbd keys="Mod+K" />
|
|
47
|
+
* </span>
|
|
48
|
+
* );
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
26
52
|
export function Kbd({ size = "md", keys, className, children, ...props }: KbdProps) {
|
|
27
53
|
const { os } = usePlatform();
|
|
28
54
|
return (
|
|
@@ -5,32 +5,51 @@ import { cn } from "../lib/cn";
|
|
|
5
5
|
import { PanelPillTabs } from "./bot-instructions-panel";
|
|
6
6
|
import { Spinner } from "./spinner";
|
|
7
7
|
|
|
8
|
+
/** One pill tab of {@link KnowledgeBasePanel}, such as "All" or "Documents". */
|
|
8
9
|
export type KnowledgeBaseTab = {
|
|
10
|
+
/** Stable identifier; it is the tab's value and React key. */
|
|
9
11
|
id: string;
|
|
12
|
+
/** Tab text. */
|
|
10
13
|
label: ReactNode;
|
|
14
|
+
/** Optional count drawn in caption colour right of the label. */
|
|
11
15
|
count?: number;
|
|
12
16
|
};
|
|
13
17
|
|
|
18
|
+
/** Props for {@link KnowledgeBasePanel}: header, tabs, indexing state and the dropzone handler. */
|
|
14
19
|
export type KnowledgeBasePanelProps = Omit<ComponentProps<"section">, "className" | "title" | "onChange"> & {
|
|
20
|
+
/** Panel heading, rendered as the `h3`. */
|
|
15
21
|
title: ReactNode;
|
|
16
22
|
/** The 12/18 line under the title: sources, chunks, bots. */
|
|
17
23
|
meta?: ReactNode;
|
|
24
|
+
/** Pills of the tab tray, in display order. */
|
|
18
25
|
tabs: KnowledgeBaseTab[];
|
|
26
|
+
/** Controlled selected tab id. Leave unset to let the component hold the selection itself. */
|
|
19
27
|
value?: string;
|
|
28
|
+
/** Initial selected tab when uncontrolled. Falls back to the first tab. */
|
|
20
29
|
defaultValue?: string;
|
|
30
|
+
/** Fires with the new tab id when a pill is clicked. */
|
|
21
31
|
onValueChange?: (id: string) => void;
|
|
22
|
-
/** Shows the spinner and "N indexing" at the right of the tabs when above zero.
|
|
32
|
+
/** Shows the spinner and "N indexing" at the right of the tabs when above zero.
|
|
33
|
+
* @default 0 */
|
|
23
34
|
indexing?: number;
|
|
35
|
+
/** Fires when the round settings button is clicked; the button always renders. */
|
|
24
36
|
onSettings?: () => void;
|
|
37
|
+
/** Fires when "Add source" is clicked; the button always renders. */
|
|
25
38
|
onAddSource?: () => void;
|
|
26
39
|
/** Called with the files dropped on the dropzone or picked with Browse. Omit to hide the dropzone. */
|
|
27
40
|
onFiles?: (files: File[]) => void;
|
|
41
|
+
/** Headline inside the dropzone.
|
|
42
|
+
* @default "Drop files here, or browse" */
|
|
28
43
|
dropTitle?: ReactNode;
|
|
44
|
+
/** Supporting line under the dropzone headline.
|
|
45
|
+
* @default "PDF, DOCX, Markdown or TXT · Notion, Google Drive and websites from Add source" */
|
|
29
46
|
dropHint?: ReactNode;
|
|
30
|
-
/** Pipeline column labels shown above the list; hidden on narrow widths.
|
|
47
|
+
/** Pipeline column labels shown above the list; hidden on narrow widths.
|
|
48
|
+
* @default ["Parse", "Chunk", "Embed", "Index"] */
|
|
31
49
|
stages?: string[];
|
|
32
50
|
/** The list rows, usually `SourceRow variant="list"`. */
|
|
33
51
|
children?: ReactNode;
|
|
52
|
+
/** Extra classes on the panel `section`, merged after the built-in ones. */
|
|
34
53
|
className?: string;
|
|
35
54
|
};
|
|
36
55
|
|
|
@@ -40,6 +59,25 @@ const defaultStages = ["Parse", "Chunk", "Embed", "Index"];
|
|
|
40
59
|
* Paper `Knowledge base` (XVM-1): a 24px-radius surface panel with a header, a pill tab tray, a table head, a
|
|
41
60
|
* flat divided source list and a dashed dropzone. The selected tab pill travels in 280ms, the dropzone border and
|
|
42
61
|
* fill change in 150ms while a file is over it, and indexing progress fills are animated by the rows.
|
|
62
|
+
*
|
|
63
|
+
* The dropzone is a `<label>` wrapping a visually hidden multi-file input, so
|
|
64
|
+
* clicking it opens the file picker as well as accepting a drag; the input is
|
|
65
|
+
* cleared after each pick so the same file can be chosen twice. Passing `onFiles`
|
|
66
|
+
* is what makes the dropzone appear.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <KnowledgeBasePanel
|
|
71
|
+
* title="Knowledge base"
|
|
72
|
+
* meta="3 sources · 128 chunks"
|
|
73
|
+
* tabs={[{ id: "all", label: "All", count: 3 }]}
|
|
74
|
+
* indexing={2}
|
|
75
|
+
* onAddSource={openSourcePicker}
|
|
76
|
+
* onFiles={upload}
|
|
77
|
+
* >
|
|
78
|
+
* <SourceRow variant="list" status="ready" icon={<FileText />} name="README.md" detail="24 chunks" />
|
|
79
|
+
* </KnowledgeBasePanel>
|
|
80
|
+
* ```
|
|
43
81
|
*/
|
|
44
82
|
export function KnowledgeBasePanel({
|
|
45
83
|
title,
|
|
@@ -18,6 +18,22 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Without backdrop-filter support, or with reduced transparency requested, the
|
|
20
20
|
* fill turns to `glass-fill-frost` and the rim stays (see `globals.css`).
|
|
21
|
+
*
|
|
22
|
+
* It takes no props and renders a single `aria-hidden` span; every visual
|
|
23
|
+
* decision comes from the host's class list, so the same element serves buttons,
|
|
24
|
+
* headers, cards and sheets.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* function GlassCard() {
|
|
29
|
+
* return (
|
|
30
|
+
* <div className="relative isolate overflow-clip rounded-xl bg-glass-regular p-4">
|
|
31
|
+
* <LiquidGlass />
|
|
32
|
+
* <span className="relative">Floating chrome over the backdrop</span>
|
|
33
|
+
* </div>
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
21
37
|
*/
|
|
22
38
|
export function LiquidGlass() {
|
|
23
39
|
return <span aria-hidden className="liquid-layers" />;
|