@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.
Files changed (93) hide show
  1. package/README.md +168 -2
  2. package/package.json +50 -5
  3. package/src/assets/sidebar-photo.jpg +0 -0
  4. package/src/components/agent-activity.tsx +202 -0
  5. package/src/components/agent-lane.tsx +80 -0
  6. package/src/components/agent-lanes-timeline.tsx +312 -0
  7. package/src/components/agent-panel.tsx +121 -0
  8. package/src/components/agent-steering-panel.tsx +274 -0
  9. package/src/components/approval-card.tsx +89 -0
  10. package/src/components/avatar.tsx +104 -0
  11. package/src/components/badge.tsx +63 -0
  12. package/src/components/banner.tsx +103 -0
  13. package/src/components/bot-instructions-panel.tsx +365 -0
  14. package/src/components/bot-list-row.tsx +42 -0
  15. package/src/components/bot-memory-panel.tsx +229 -0
  16. package/src/components/bottom-sheet.tsx +140 -0
  17. package/src/components/breadcrumb-header.tsx +87 -0
  18. package/src/components/breadcrumb.tsx +86 -0
  19. package/src/components/button.tsx +146 -0
  20. package/src/components/calendar.tsx +410 -0
  21. package/src/components/card.tsx +79 -0
  22. package/src/components/chat-bubble.tsx +36 -0
  23. package/src/components/chat-controls.tsx +118 -0
  24. package/src/components/checkbox.tsx +73 -0
  25. package/src/components/chip.tsx +81 -0
  26. package/src/components/choice-card.tsx +55 -0
  27. package/src/components/code-panel.tsx +27 -0
  28. package/src/components/combobox.tsx +312 -0
  29. package/src/components/command-palette.tsx +191 -0
  30. package/src/components/composer.tsx +124 -0
  31. package/src/components/data-table.tsx +429 -0
  32. package/src/components/date-field.tsx +100 -0
  33. package/src/components/dialog.tsx +93 -0
  34. package/src/components/directory-card.tsx +59 -0
  35. package/src/components/document-extract-panel.tsx +398 -0
  36. package/src/components/empty-state.tsx +29 -0
  37. package/src/components/field.tsx +78 -0
  38. package/src/components/file-drop.tsx +89 -0
  39. package/src/components/filter-bar.tsx +83 -0
  40. package/src/components/flue-background.tsx +402 -0
  41. package/src/components/horon-icon.tsx +280 -0
  42. package/src/components/horon-wordmark.tsx +26 -0
  43. package/src/components/icon-button.tsx +61 -0
  44. package/src/components/input-group.tsx +112 -0
  45. package/src/components/input.tsx +72 -0
  46. package/src/components/integration-row.tsx +103 -0
  47. package/src/components/kbd.tsx +35 -0
  48. package/src/components/knowledge-base-panel.tsx +185 -0
  49. package/src/components/liquid-glass.tsx +24 -0
  50. package/src/components/list-detail.tsx +275 -0
  51. package/src/components/marketplace-card.tsx +97 -0
  52. package/src/components/mcp-server-row.tsx +122 -0
  53. package/src/components/menu.tsx +165 -0
  54. package/src/components/message-extras.tsx +156 -0
  55. package/src/components/modal.tsx +108 -0
  56. package/src/components/orchestrator-panel.tsx +175 -0
  57. package/src/components/page-header.tsx +48 -0
  58. package/src/components/plan-card.tsx +70 -0
  59. package/src/components/plan-step.tsx +111 -0
  60. package/src/components/plugin-row.tsx +128 -0
  61. package/src/components/progress.tsx +21 -0
  62. package/src/components/radio-group.tsx +84 -0
  63. package/src/components/research-run-panel.tsx +232 -0
  64. package/src/components/schedule.tsx +332 -0
  65. package/src/components/segmented-control.tsx +136 -0
  66. package/src/components/select.tsx +154 -0
  67. package/src/components/settings-list.tsx +99 -0
  68. package/src/components/sidebar.tsx +542 -0
  69. package/src/components/skeleton.tsx +75 -0
  70. package/src/components/slider.tsx +29 -0
  71. package/src/components/source-row.tsx +112 -0
  72. package/src/components/spinner.tsx +29 -0
  73. package/src/components/split-button.tsx +72 -0
  74. package/src/components/switch.tsx +79 -0
  75. package/src/components/tab-bar.tsx +90 -0
  76. package/src/components/tabs.tsx +162 -0
  77. package/src/components/task-row.tsx +159 -0
  78. package/src/components/textarea-field.tsx +127 -0
  79. package/src/components/textarea.tsx +47 -0
  80. package/src/components/toast.tsx +128 -0
  81. package/src/components/tool-call.tsx +224 -0
  82. package/src/components/tool-calls-header.tsx +65 -0
  83. package/src/components/tool-permissions.tsx +105 -0
  84. package/src/components/tooltip.tsx +33 -0
  85. package/src/components/top-bar.tsx +65 -0
  86. package/src/components/usage-meter.tsx +47 -0
  87. package/src/components/workspace-menu.tsx +77 -0
  88. package/src/index.ts +86 -0
  89. package/src/lib/cn.ts +25 -0
  90. package/src/lib/cursor-ghost.ts +244 -0
  91. package/src/lib/size.ts +10 -0
  92. package/src/lib/use-active-indicator.ts +60 -0
  93. 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
+ }