@horon-ai/react 0.0.0-stage → 0.1.0
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 +168 -2
- package/package.json +50 -5
- package/src/assets/sidebar-photo.jpg +0 -0
- package/src/components/agent-activity.tsx +202 -0
- package/src/components/agent-lane.tsx +80 -0
- package/src/components/agent-lanes-timeline.tsx +312 -0
- package/src/components/agent-panel.tsx +121 -0
- package/src/components/agent-steering-panel.tsx +274 -0
- package/src/components/approval-card.tsx +89 -0
- package/src/components/avatar.tsx +104 -0
- package/src/components/badge.tsx +63 -0
- package/src/components/banner.tsx +103 -0
- package/src/components/bot-instructions-panel.tsx +365 -0
- package/src/components/bot-list-row.tsx +42 -0
- package/src/components/bot-memory-panel.tsx +229 -0
- package/src/components/bottom-sheet.tsx +140 -0
- package/src/components/breadcrumb-header.tsx +87 -0
- package/src/components/breadcrumb.tsx +86 -0
- package/src/components/button.tsx +146 -0
- package/src/components/calendar.tsx +410 -0
- package/src/components/card.tsx +79 -0
- package/src/components/chat-bubble.tsx +36 -0
- package/src/components/chat-controls.tsx +118 -0
- package/src/components/checkbox.tsx +73 -0
- package/src/components/chip.tsx +81 -0
- package/src/components/choice-card.tsx +55 -0
- package/src/components/code-panel.tsx +27 -0
- package/src/components/combobox.tsx +312 -0
- package/src/components/command-palette.tsx +191 -0
- package/src/components/composer.tsx +124 -0
- package/src/components/data-table.tsx +429 -0
- package/src/components/date-field.tsx +100 -0
- package/src/components/dialog.tsx +93 -0
- package/src/components/directory-card.tsx +59 -0
- package/src/components/document-extract-panel.tsx +398 -0
- package/src/components/empty-state.tsx +29 -0
- package/src/components/field.tsx +78 -0
- package/src/components/file-drop.tsx +89 -0
- package/src/components/filter-bar.tsx +83 -0
- package/src/components/flue-background.tsx +402 -0
- package/src/components/horon-icon.tsx +280 -0
- package/src/components/horon-wordmark.tsx +26 -0
- package/src/components/icon-button.tsx +61 -0
- package/src/components/input-group.tsx +112 -0
- package/src/components/input.tsx +72 -0
- package/src/components/integration-row.tsx +103 -0
- package/src/components/kbd.tsx +35 -0
- package/src/components/knowledge-base-panel.tsx +185 -0
- package/src/components/liquid-glass.tsx +24 -0
- package/src/components/list-detail.tsx +275 -0
- package/src/components/marketplace-card.tsx +97 -0
- package/src/components/mcp-server-row.tsx +122 -0
- package/src/components/menu.tsx +165 -0
- package/src/components/message-extras.tsx +156 -0
- package/src/components/modal.tsx +108 -0
- package/src/components/orchestrator-panel.tsx +175 -0
- package/src/components/page-header.tsx +48 -0
- package/src/components/plan-card.tsx +70 -0
- package/src/components/plan-step.tsx +111 -0
- package/src/components/plugin-row.tsx +128 -0
- package/src/components/progress.tsx +21 -0
- package/src/components/radio-group.tsx +84 -0
- package/src/components/research-run-panel.tsx +232 -0
- package/src/components/schedule.tsx +332 -0
- package/src/components/segmented-control.tsx +136 -0
- package/src/components/select.tsx +154 -0
- package/src/components/settings-list.tsx +99 -0
- package/src/components/sidebar.tsx +542 -0
- package/src/components/skeleton.tsx +75 -0
- package/src/components/slider.tsx +29 -0
- package/src/components/source-row.tsx +112 -0
- package/src/components/spinner.tsx +29 -0
- package/src/components/split-button.tsx +72 -0
- package/src/components/switch.tsx +79 -0
- package/src/components/tab-bar.tsx +90 -0
- package/src/components/tabs.tsx +162 -0
- package/src/components/task-row.tsx +159 -0
- package/src/components/textarea-field.tsx +127 -0
- package/src/components/textarea.tsx +47 -0
- package/src/components/toast.tsx +128 -0
- package/src/components/tool-call.tsx +224 -0
- package/src/components/tool-calls-header.tsx +65 -0
- package/src/components/tool-permissions.tsx +105 -0
- package/src/components/tooltip.tsx +33 -0
- package/src/components/top-bar.tsx +65 -0
- package/src/components/usage-meter.tsx +47 -0
- package/src/components/workspace-menu.tsx +77 -0
- package/src/index.ts +86 -0
- package/src/lib/cn.ts +25 -0
- package/src/lib/cursor-ghost.ts +244 -0
- package/src/lib/size.ts +10 -0
- package/src/lib/use-active-indicator.ts +60 -0
- package/src/styles/globals.css +564 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Autocomplete } from "@base-ui/react/autocomplete";
|
|
2
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
3
|
+
import { MagnifyingGlass } from "@phosphor-icons/react";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/cn";
|
|
7
|
+
import { Kbd } from "./kbd";
|
|
8
|
+
|
|
9
|
+
export type CommandItem = {
|
|
10
|
+
value: string;
|
|
11
|
+
label: string;
|
|
12
|
+
keywords?: string;
|
|
13
|
+
/** 28px leading tile: an initial or a 16px icon. */
|
|
14
|
+
tile?: ReactNode;
|
|
15
|
+
/** Tile fill class, such as "bg-sky-500"; defaults to the primary tile. */
|
|
16
|
+
tileClassName?: string;
|
|
17
|
+
/** Caption after the label, such as the workspace. */
|
|
18
|
+
meta?: ReactNode;
|
|
19
|
+
/** Right-side hint, usually `<Kbd>`. */
|
|
20
|
+
hint?: ReactNode;
|
|
21
|
+
onSelect?: () => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type CommandGroup = { label: string; items: CommandItem[] };
|
|
25
|
+
|
|
26
|
+
export type CommandPaletteProps = {
|
|
27
|
+
open?: boolean;
|
|
28
|
+
onOpenChange?: (open: boolean) => void;
|
|
29
|
+
groups: CommandGroup[];
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
/** Shown when the query has no matches; defaults to "No results". */
|
|
32
|
+
empty?: ReactNode;
|
|
33
|
+
/** Footer key hints, such as `[["↑↓", "Move"], ["↵", "Open"]]`. */
|
|
34
|
+
hints?: [ReactNode, string][];
|
|
35
|
+
trigger?: ReactNode;
|
|
36
|
+
/**
|
|
37
|
+
* `solid` (default) is an opaque palette with a default border. `glass` puts it on the veil (`veil-overlay`).
|
|
38
|
+
*/
|
|
39
|
+
surface?: "solid" | "glass";
|
|
40
|
+
className?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Figma `Command palette` (110:676): a 560px palette, 24px radius, with a 48px search row,
|
|
45
|
+
* grouped 44px results and a key-hint footer.
|
|
46
|
+
*/
|
|
47
|
+
export function CommandPalette({
|
|
48
|
+
open,
|
|
49
|
+
onOpenChange,
|
|
50
|
+
groups,
|
|
51
|
+
placeholder = "Search",
|
|
52
|
+
empty,
|
|
53
|
+
hints,
|
|
54
|
+
trigger,
|
|
55
|
+
surface = "solid",
|
|
56
|
+
className,
|
|
57
|
+
}: CommandPaletteProps) {
|
|
58
|
+
return (
|
|
59
|
+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
60
|
+
{trigger}
|
|
61
|
+
<Dialog.Portal>
|
|
62
|
+
<Dialog.Backdrop className="fixed inset-0 z-50 bg-bg-scrim/32 motion-fade backdrop-blur-[6px]" />
|
|
63
|
+
<Dialog.Viewport className="fixed inset-0 z-50 flex items-start justify-center px-2 pt-24">
|
|
64
|
+
<Dialog.Popup
|
|
65
|
+
aria-label={placeholder}
|
|
66
|
+
className={cn(
|
|
67
|
+
// 560px where it fits; the viewport's 8px gutters cap `w-full` below that.
|
|
68
|
+
"relative flex w-full max-w-140 motion-popup flex-col overflow-clip rounded-[24px] p-1.5",
|
|
69
|
+
surface === "glass" ? "veil-overlay" : "border border-border-default bg-bg-surface shadow-palette",
|
|
70
|
+
"duration-240 data-starting-style:translate-y-2",
|
|
71
|
+
className,
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
<CommandPaletteBody
|
|
75
|
+
groups={groups}
|
|
76
|
+
placeholder={placeholder}
|
|
77
|
+
empty={empty}
|
|
78
|
+
closeControl={
|
|
79
|
+
<Dialog.Close aria-label="Close" className="motion-press cursor-pointer">
|
|
80
|
+
<Kbd>esc</Kbd>
|
|
81
|
+
</Dialog.Close>
|
|
82
|
+
}
|
|
83
|
+
onSelect={() => onOpenChange?.(false)}
|
|
84
|
+
/>
|
|
85
|
+
{hints && hints.length > 0 && (
|
|
86
|
+
<div className="relative flex flex-wrap items-center gap-x-4 gap-y-1 px-3 pt-2 pb-1">
|
|
87
|
+
{hints.map(([key, label]) => (
|
|
88
|
+
<span key={label} className="flex items-center gap-1.5 text-caption text-text-secondary">
|
|
89
|
+
<Kbd>{key}</Kbd>
|
|
90
|
+
{label}
|
|
91
|
+
</span>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
</Dialog.Popup>
|
|
96
|
+
</Dialog.Viewport>
|
|
97
|
+
</Dialog.Portal>
|
|
98
|
+
</Dialog.Root>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type CommandPaletteBodyProps = {
|
|
103
|
+
groups: CommandGroup[];
|
|
104
|
+
placeholder?: string;
|
|
105
|
+
empty?: ReactNode;
|
|
106
|
+
closeLabel?: string;
|
|
107
|
+
/** A ready close control; the dialog passes its own `Dialog.Close` so uncontrolled palettes still close. */
|
|
108
|
+
closeControl?: ReactNode;
|
|
109
|
+
onClose?: () => void;
|
|
110
|
+
onSelect?: () => void;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export function CommandPaletteBody({
|
|
114
|
+
groups,
|
|
115
|
+
placeholder = "Search",
|
|
116
|
+
empty = "No results",
|
|
117
|
+
closeLabel = "Close",
|
|
118
|
+
closeControl,
|
|
119
|
+
onClose,
|
|
120
|
+
onSelect,
|
|
121
|
+
}: CommandPaletteBodyProps) {
|
|
122
|
+
return (
|
|
123
|
+
<Autocomplete.Root
|
|
124
|
+
open
|
|
125
|
+
inline
|
|
126
|
+
items={groups}
|
|
127
|
+
itemToStringValue={(item: CommandItem) => `${item.label} ${item.keywords ?? ""}`}
|
|
128
|
+
autoHighlight="always"
|
|
129
|
+
keepHighlight
|
|
130
|
+
>
|
|
131
|
+
<div className="relative flex h-12 items-center gap-2.5 px-3">
|
|
132
|
+
<span className="flex size-7 shrink-0 items-center justify-center">
|
|
133
|
+
<MagnifyingGlass aria-hidden className="size-5 text-text-secondary" />
|
|
134
|
+
</span>
|
|
135
|
+
<Autocomplete.Input
|
|
136
|
+
placeholder={placeholder}
|
|
137
|
+
className="min-w-0 flex-1 bg-transparent text-body-sm text-text-primary outline-none placeholder:text-text-secondary"
|
|
138
|
+
/>
|
|
139
|
+
{closeControl ??
|
|
140
|
+
(onClose === undefined ? null : (
|
|
141
|
+
<button type="button" aria-label={closeLabel} className="motion-press cursor-pointer" onClick={onClose}>
|
|
142
|
+
<Kbd>esc</Kbd>
|
|
143
|
+
</button>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
<div className="relative pt-1">
|
|
147
|
+
<div className="h-px bg-overlay-ink-8" />
|
|
148
|
+
</div>
|
|
149
|
+
<Autocomplete.Empty className="relative px-3 py-4 text-body-sm text-text-secondary empty:hidden">
|
|
150
|
+
{empty}
|
|
151
|
+
</Autocomplete.Empty>
|
|
152
|
+
<Autocomplete.List className="relative flex max-h-[min(60dvh,24rem)] flex-col overflow-y-auto">
|
|
153
|
+
{(group: CommandGroup) => (
|
|
154
|
+
<Autocomplete.Group key={group.label} items={group.items} className="flex flex-col pt-1">
|
|
155
|
+
<Autocomplete.GroupLabel className="px-3 pt-1 pb-1 text-caption text-text-secondary">
|
|
156
|
+
{group.label}
|
|
157
|
+
</Autocomplete.GroupLabel>
|
|
158
|
+
<Autocomplete.Collection>
|
|
159
|
+
{(item: CommandItem) => (
|
|
160
|
+
<Autocomplete.Item
|
|
161
|
+
key={item.value}
|
|
162
|
+
value={item}
|
|
163
|
+
onClick={() => {
|
|
164
|
+
item.onSelect?.();
|
|
165
|
+
onSelect?.();
|
|
166
|
+
}}
|
|
167
|
+
className="flex h-11 cursor-pointer items-center gap-2.5 rounded-md px-3 motion-colors outline-none select-none data-highlighted:bg-overlay-ink-6"
|
|
168
|
+
>
|
|
169
|
+
{item.tile && (
|
|
170
|
+
<span
|
|
171
|
+
className={cn(
|
|
172
|
+
"flex size-7 shrink-0 items-center justify-center rounded-sm text-label-sm [&>svg]:size-4",
|
|
173
|
+
item.tileClassName ?? "bg-interactive-primary text-interactive-on-primary",
|
|
174
|
+
)}
|
|
175
|
+
>
|
|
176
|
+
{item.tile}
|
|
177
|
+
</span>
|
|
178
|
+
)}
|
|
179
|
+
<span className="truncate text-label-md text-text-primary">{item.label}</span>
|
|
180
|
+
{item.meta && <span className="shrink-0 text-caption text-text-secondary">{item.meta}</span>}
|
|
181
|
+
<span className="flex-1" />
|
|
182
|
+
{item.hint}
|
|
183
|
+
</Autocomplete.Item>
|
|
184
|
+
)}
|
|
185
|
+
</Autocomplete.Collection>
|
|
186
|
+
</Autocomplete.Group>
|
|
187
|
+
)}
|
|
188
|
+
</Autocomplete.List>
|
|
189
|
+
</Autocomplete.Root>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ArrowUp, Microphone, Plus } from "@phosphor-icons/react";
|
|
2
|
+
import { useState, type FormEvent } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/cn";
|
|
5
|
+
import type { ControlSize } from "../lib/size";
|
|
6
|
+
import { IconButton } from "./icon-button";
|
|
7
|
+
|
|
8
|
+
export type ComposerSize = ControlSize;
|
|
9
|
+
export type ComposerSurface = "solid" | "glass";
|
|
10
|
+
|
|
11
|
+
export type ComposerProps = {
|
|
12
|
+
size?: ComposerSize;
|
|
13
|
+
/** `solid` (default) on raised surfaces, `glass` over imagery and glass panels. */
|
|
14
|
+
surface?: ComposerSurface;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
value?: string;
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
onValueChange?: (value: string) => void;
|
|
19
|
+
onSubmit?: (value: string) => void;
|
|
20
|
+
onAttach?: () => void;
|
|
21
|
+
onVoice?: () => void;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
className?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const sizeClass: Record<ComposerSize, string> = {
|
|
27
|
+
sm: "gap-1.5 p-1.5",
|
|
28
|
+
md: "gap-2 p-2",
|
|
29
|
+
lg: "gap-2.5 p-2.5",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const textClass: Record<ComposerSize, string> = {
|
|
33
|
+
sm: "text-body-sm",
|
|
34
|
+
md: "text-body-md",
|
|
35
|
+
lg: "text-body-lg",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Figma `Composer` (63:462): a regular-glass pill with an attach button, the
|
|
40
|
+
* message field, and a voice button that turns into the ink send button once
|
|
41
|
+
* there is text.
|
|
42
|
+
*/
|
|
43
|
+
export function Composer({
|
|
44
|
+
size = "md",
|
|
45
|
+
surface = "solid",
|
|
46
|
+
placeholder = "Message",
|
|
47
|
+
value: valueProp,
|
|
48
|
+
defaultValue = "",
|
|
49
|
+
onValueChange,
|
|
50
|
+
onSubmit,
|
|
51
|
+
onAttach,
|
|
52
|
+
onVoice,
|
|
53
|
+
disabled,
|
|
54
|
+
className,
|
|
55
|
+
}: ComposerProps) {
|
|
56
|
+
const [valueState, setValueState] = useState(defaultValue);
|
|
57
|
+
const value = valueProp ?? valueState;
|
|
58
|
+
const setValue = (next: string) => {
|
|
59
|
+
if (valueProp === undefined) setValueState(next);
|
|
60
|
+
onValueChange?.(next);
|
|
61
|
+
};
|
|
62
|
+
const submit = (event: FormEvent) => {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
if (!value.trim()) return;
|
|
65
|
+
onSubmit?.(value);
|
|
66
|
+
setValue("");
|
|
67
|
+
};
|
|
68
|
+
const canSubmit = value.trim() !== "";
|
|
69
|
+
return (
|
|
70
|
+
<form
|
|
71
|
+
onSubmit={submit}
|
|
72
|
+
className={cn(
|
|
73
|
+
"relative flex max-w-full min-w-0 items-center overflow-clip rounded-full motion-colors has-[input:focus-visible]:border-border-strong",
|
|
74
|
+
surface === "glass" ? "veil-field" : "border border-border-default bg-bg-surface",
|
|
75
|
+
sizeClass[size],
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<IconButton
|
|
80
|
+
type="button"
|
|
81
|
+
size={size}
|
|
82
|
+
icon={<Plus className="text-text-secondary" />}
|
|
83
|
+
label="Attach"
|
|
84
|
+
onClick={onAttach}
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
/>
|
|
87
|
+
<input
|
|
88
|
+
value={value}
|
|
89
|
+
onChange={(event) => setValue(event.target.value)}
|
|
90
|
+
placeholder={placeholder}
|
|
91
|
+
aria-label={placeholder}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
className={cn(
|
|
94
|
+
"relative min-w-0 flex-1 bg-transparent text-text-primary outline-none placeholder:text-text-placeholder disabled:text-text-disabled",
|
|
95
|
+
textClass[size],
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
<span className="motion-swap">
|
|
99
|
+
<span data-hidden={!canSubmit || undefined} inert={!canSubmit}>
|
|
100
|
+
<IconButton
|
|
101
|
+
type="submit"
|
|
102
|
+
size={size}
|
|
103
|
+
variant="primary"
|
|
104
|
+
icon={<ArrowUp />}
|
|
105
|
+
label="Send"
|
|
106
|
+
disabled={disabled}
|
|
107
|
+
tabIndex={canSubmit ? undefined : -1}
|
|
108
|
+
/>
|
|
109
|
+
</span>
|
|
110
|
+
<span data-hidden={canSubmit || undefined} inert={canSubmit}>
|
|
111
|
+
<IconButton
|
|
112
|
+
type="button"
|
|
113
|
+
size={size}
|
|
114
|
+
icon={<Microphone className="text-text-secondary" />}
|
|
115
|
+
label="Voice input"
|
|
116
|
+
onClick={onVoice}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
tabIndex={canSubmit ? -1 : undefined}
|
|
119
|
+
/>
|
|
120
|
+
</span>
|
|
121
|
+
</span>
|
|
122
|
+
</form>
|
|
123
|
+
);
|
|
124
|
+
}
|