@corbits/react-ui 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 (255) hide show
  1. package/LICENSE +501 -0
  2. package/README.md +125 -0
  3. package/dist/blocks/login/auth-layout.d.ts +38 -0
  4. package/dist/blocks/login/auth-layout.js +52 -0
  5. package/dist/blocks/login/login-form.d.ts +60 -0
  6. package/dist/blocks/login/login-form.js +151 -0
  7. package/dist/hooks/use-command-palette-navigation.d.ts +25 -0
  8. package/dist/hooks/use-command-palette-navigation.js +52 -0
  9. package/dist/hooks/use-controllable-state.d.ts +28 -0
  10. package/dist/hooks/use-controllable-state.js +44 -0
  11. package/dist/hooks/use-delayed-autofocus.d.ts +9 -0
  12. package/dist/hooks/use-delayed-autofocus.js +20 -0
  13. package/dist/hooks/use-dismissable-popover.d.ts +37 -0
  14. package/dist/hooks/use-dismissable-popover.js +64 -0
  15. package/dist/hooks/use-list-selection.d.ts +66 -0
  16. package/dist/hooks/use-list-selection.js +147 -0
  17. package/dist/hooks/use-prefers-reduced-motion.d.ts +13 -0
  18. package/dist/hooks/use-prefers-reduced-motion.js +28 -0
  19. package/dist/hooks/use-scroll-current-into-view.d.ts +19 -0
  20. package/dist/hooks/use-scroll-current-into-view.js +33 -0
  21. package/dist/index.d.ts +122 -0
  22. package/dist/index.js +131 -0
  23. package/dist/lib/artifact.d.ts +72 -0
  24. package/dist/lib/artifact.js +67 -0
  25. package/dist/lib/chart-geometry.d.ts +62 -0
  26. package/dist/lib/chart-geometry.js +86 -0
  27. package/dist/lib/chart-palette.d.ts +76 -0
  28. package/dist/lib/chart-palette.js +92 -0
  29. package/dist/lib/chat-message.d.ts +115 -0
  30. package/dist/lib/chat-message.js +41 -0
  31. package/dist/lib/chat-parts.d.ts +64 -0
  32. package/dist/lib/chat-parts.js +16 -0
  33. package/dist/lib/csv.d.ts +60 -0
  34. package/dist/lib/csv.js +108 -0
  35. package/dist/lib/metrics.d.ts +70 -0
  36. package/dist/lib/metrics.js +101 -0
  37. package/dist/lib/now-item.d.ts +65 -0
  38. package/dist/lib/now-item.js +59 -0
  39. package/dist/lib/relative-time.d.ts +9 -0
  40. package/dist/lib/relative-time.js +47 -0
  41. package/dist/lib/theme.d.ts +30 -0
  42. package/dist/lib/theme.js +77 -0
  43. package/dist/lib/url.d.ts +26 -0
  44. package/dist/lib/url.js +37 -0
  45. package/dist/lib/utils.d.ts +2 -0
  46. package/dist/lib/utils.js +5 -0
  47. package/dist/lib/workflow-display-flow.d.ts +83 -0
  48. package/dist/lib/workflow-display-flow.js +104 -0
  49. package/dist/lib/workflow-registry.d.ts +90 -0
  50. package/dist/lib/workflow-registry.js +56 -0
  51. package/dist/lib/workflow-run-error.d.ts +31 -0
  52. package/dist/lib/workflow-run-error.js +105 -0
  53. package/dist/lib/workflow-run-progress.d.ts +97 -0
  54. package/dist/lib/workflow-run-progress.js +135 -0
  55. package/dist/lib/workflow-run.d.ts +43 -0
  56. package/dist/lib/workflow-run.js +56 -0
  57. package/dist/styles.css +3080 -0
  58. package/dist/test/render-hook.d.ts +14 -0
  59. package/dist/test/render-hook.js +38 -0
  60. package/dist/theme.css +628 -0
  61. package/dist/ui/agent-turn.d.ts +26 -0
  62. package/dist/ui/agent-turn.js +60 -0
  63. package/dist/ui/animated-number.d.ts +25 -0
  64. package/dist/ui/animated-number.js +63 -0
  65. package/dist/ui/approval-card.d.ts +46 -0
  66. package/dist/ui/approval-card.js +116 -0
  67. package/dist/ui/artifact-body.d.ts +26 -0
  68. package/dist/ui/artifact-body.js +170 -0
  69. package/dist/ui/artifact-notice.d.ts +21 -0
  70. package/dist/ui/artifact-notice.js +36 -0
  71. package/dist/ui/avatar.d.ts +37 -0
  72. package/dist/ui/avatar.js +73 -0
  73. package/dist/ui/badge.d.ts +10 -0
  74. package/dist/ui/badge.js +35 -0
  75. package/dist/ui/bar-chart.d.ts +38 -0
  76. package/dist/ui/bar-chart.js +118 -0
  77. package/dist/ui/block-card.d.ts +29 -0
  78. package/dist/ui/block-card.js +66 -0
  79. package/dist/ui/boot-screen.d.ts +23 -0
  80. package/dist/ui/boot-screen.js +49 -0
  81. package/dist/ui/bulk-action-bar.d.ts +37 -0
  82. package/dist/ui/bulk-action-bar.js +64 -0
  83. package/dist/ui/button.d.ts +12 -0
  84. package/dist/ui/button.js +73 -0
  85. package/dist/ui/card.d.ts +6 -0
  86. package/dist/ui/card.js +37 -0
  87. package/dist/ui/category-bars.d.ts +20 -0
  88. package/dist/ui/category-bars.js +69 -0
  89. package/dist/ui/chart-frame.d.ts +54 -0
  90. package/dist/ui/chart-frame.js +108 -0
  91. package/dist/ui/chat-composer.d.ts +24 -0
  92. package/dist/ui/chat-composer.js +32 -0
  93. package/dist/ui/chat-dock-timing.d.ts +15 -0
  94. package/dist/ui/chat-dock-timing.js +11 -0
  95. package/dist/ui/chat-input.d.ts +38 -0
  96. package/dist/ui/chat-input.js +138 -0
  97. package/dist/ui/chat-panel.d.ts +20 -0
  98. package/dist/ui/chat-panel.js +56 -0
  99. package/dist/ui/chat-thread.d.ts +28 -0
  100. package/dist/ui/chat-thread.js +61 -0
  101. package/dist/ui/checkbox.d.ts +25 -0
  102. package/dist/ui/checkbox.js +63 -0
  103. package/dist/ui/command-palette.d.ts +100 -0
  104. package/dist/ui/command-palette.js +341 -0
  105. package/dist/ui/compare-body.d.ts +52 -0
  106. package/dist/ui/compare-body.js +140 -0
  107. package/dist/ui/confirm-button.d.ts +29 -0
  108. package/dist/ui/confirm-button.js +41 -0
  109. package/dist/ui/corbits-mark.d.ts +29 -0
  110. package/dist/ui/corbits-mark.js +33 -0
  111. package/dist/ui/csv-table.d.ts +22 -0
  112. package/dist/ui/csv-table.js +85 -0
  113. package/dist/ui/dialog.d.ts +37 -0
  114. package/dist/ui/dialog.js +105 -0
  115. package/dist/ui/dictation-status-line.d.ts +20 -0
  116. package/dist/ui/dictation-status-line.js +38 -0
  117. package/dist/ui/dither-canvas.d.ts +31 -0
  118. package/dist/ui/dither-canvas.js +169 -0
  119. package/dist/ui/embed-body.d.ts +30 -0
  120. package/dist/ui/embed-body.js +92 -0
  121. package/dist/ui/empty-state.d.ts +26 -0
  122. package/dist/ui/empty-state.js +42 -0
  123. package/dist/ui/file-input.d.ts +25 -0
  124. package/dist/ui/file-input.js +66 -0
  125. package/dist/ui/filter-bar.d.ts +42 -0
  126. package/dist/ui/filter-bar.js +95 -0
  127. package/dist/ui/filter-chip.d.ts +14 -0
  128. package/dist/ui/filter-chip.js +23 -0
  129. package/dist/ui/gate-block.d.ts +16 -0
  130. package/dist/ui/gate-block.js +37 -0
  131. package/dist/ui/horizontal-stepper.d.ts +13 -0
  132. package/dist/ui/horizontal-stepper.js +65 -0
  133. package/dist/ui/input.d.ts +2 -0
  134. package/dist/ui/input.js +14 -0
  135. package/dist/ui/inspector-shell.d.ts +47 -0
  136. package/dist/ui/inspector-shell.js +98 -0
  137. package/dist/ui/intake-form.d.ts +39 -0
  138. package/dist/ui/intake-form.js +119 -0
  139. package/dist/ui/kind-card-grid.d.ts +23 -0
  140. package/dist/ui/kind-card-grid.js +43 -0
  141. package/dist/ui/library-page-header.d.ts +20 -0
  142. package/dist/ui/library-page-header.js +43 -0
  143. package/dist/ui/list-detail.d.ts +28 -0
  144. package/dist/ui/list-detail.js +55 -0
  145. package/dist/ui/live-run-banner.d.ts +13 -0
  146. package/dist/ui/live-run-banner.js +23 -0
  147. package/dist/ui/live-status-line.d.ts +22 -0
  148. package/dist/ui/live-status-line.js +37 -0
  149. package/dist/ui/menu.d.ts +18 -0
  150. package/dist/ui/menu.js +45 -0
  151. package/dist/ui/message-bubble.d.ts +24 -0
  152. package/dist/ui/message-bubble.js +64 -0
  153. package/dist/ui/mic-button.d.ts +35 -0
  154. package/dist/ui/mic-button.js +61 -0
  155. package/dist/ui/mic-permission-dialog.d.ts +20 -0
  156. package/dist/ui/mic-permission-dialog.js +50 -0
  157. package/dist/ui/notifications-bell.d.ts +28 -0
  158. package/dist/ui/notifications-bell.js +69 -0
  159. package/dist/ui/now-cards.d.ts +37 -0
  160. package/dist/ui/now-cards.js +116 -0
  161. package/dist/ui/page-panel.d.ts +37 -0
  162. package/dist/ui/page-panel.js +23 -0
  163. package/dist/ui/page-shell.d.ts +21 -0
  164. package/dist/ui/page-shell.js +26 -0
  165. package/dist/ui/parts-renderer.d.ts +22 -0
  166. package/dist/ui/parts-renderer.js +234 -0
  167. package/dist/ui/profile-card.d.ts +39 -0
  168. package/dist/ui/profile-card.js +124 -0
  169. package/dist/ui/progress-checklist.d.ts +25 -0
  170. package/dist/ui/progress-checklist.js +70 -0
  171. package/dist/ui/provider-mark.d.ts +32 -0
  172. package/dist/ui/provider-mark.js +31 -0
  173. package/dist/ui/quick-reply-chips.d.ts +14 -0
  174. package/dist/ui/quick-reply-chips.js +24 -0
  175. package/dist/ui/quote-card.d.ts +39 -0
  176. package/dist/ui/quote-card.js +66 -0
  177. package/dist/ui/reasoning-block.d.ts +35 -0
  178. package/dist/ui/reasoning-block.js +60 -0
  179. package/dist/ui/research-body.d.ts +70 -0
  180. package/dist/ui/research-body.js +270 -0
  181. package/dist/ui/rich-empty-state.d.ts +35 -0
  182. package/dist/ui/rich-empty-state.js +65 -0
  183. package/dist/ui/run-now-button.d.ts +23 -0
  184. package/dist/ui/run-now-button.js +42 -0
  185. package/dist/ui/section.d.ts +20 -0
  186. package/dist/ui/section.js +48 -0
  187. package/dist/ui/select.d.ts +2 -0
  188. package/dist/ui/select.js +22 -0
  189. package/dist/ui/selection-checkbox.d.ts +42 -0
  190. package/dist/ui/selection-checkbox.js +54 -0
  191. package/dist/ui/settings-panel.d.ts +35 -0
  192. package/dist/ui/settings-panel.js +68 -0
  193. package/dist/ui/shimmer-text.d.ts +19 -0
  194. package/dist/ui/shimmer-text.js +20 -0
  195. package/dist/ui/sidebar-item-row.d.ts +40 -0
  196. package/dist/ui/sidebar-item-row.js +64 -0
  197. package/dist/ui/sidebar-panel.d.ts +25 -0
  198. package/dist/ui/sidebar-panel.js +56 -0
  199. package/dist/ui/sidebar.d.ts +46 -0
  200. package/dist/ui/sidebar.js +111 -0
  201. package/dist/ui/skeleton.d.ts +13 -0
  202. package/dist/ui/skeleton.js +20 -0
  203. package/dist/ui/sortable-table.d.ts +43 -0
  204. package/dist/ui/sortable-table.js +103 -0
  205. package/dist/ui/sparkline.d.ts +27 -0
  206. package/dist/ui/sparkline.js +68 -0
  207. package/dist/ui/stat-grid.d.ts +55 -0
  208. package/dist/ui/stat-grid.js +87 -0
  209. package/dist/ui/status-dot.d.ts +13 -0
  210. package/dist/ui/status-dot.js +51 -0
  211. package/dist/ui/step-list.d.ts +21 -0
  212. package/dist/ui/step-list.js +77 -0
  213. package/dist/ui/switch.d.ts +23 -0
  214. package/dist/ui/switch.js +29 -0
  215. package/dist/ui/table.d.ts +8 -0
  216. package/dist/ui/table.js +48 -0
  217. package/dist/ui/tabs.d.ts +44 -0
  218. package/dist/ui/tabs.js +101 -0
  219. package/dist/ui/textarea.d.ts +8 -0
  220. package/dist/ui/textarea.js +32 -0
  221. package/dist/ui/theme-provider.d.ts +36 -0
  222. package/dist/ui/theme-provider.js +126 -0
  223. package/dist/ui/theme-toggle.d.ts +11 -0
  224. package/dist/ui/theme-toggle.js +32 -0
  225. package/dist/ui/thinking-indicator.d.ts +7 -0
  226. package/dist/ui/thinking-indicator.js +19 -0
  227. package/dist/ui/thinking-label.d.ts +19 -0
  228. package/dist/ui/thinking-label.js +54 -0
  229. package/dist/ui/thinking-mark.d.ts +10 -0
  230. package/dist/ui/thinking-mark.js +153 -0
  231. package/dist/ui/time-series-chart.d.ts +55 -0
  232. package/dist/ui/time-series-chart.js +249 -0
  233. package/dist/ui/toast.d.ts +16 -0
  234. package/dist/ui/toast.js +41 -0
  235. package/dist/ui/token-mosaic.d.ts +22 -0
  236. package/dist/ui/token-mosaic.js +65 -0
  237. package/dist/ui/tool-block.d.ts +54 -0
  238. package/dist/ui/tool-block.js +170 -0
  239. package/dist/ui/tool-narrative.d.ts +22 -0
  240. package/dist/ui/tool-narrative.js +77 -0
  241. package/dist/ui/tool-picker.d.ts +44 -0
  242. package/dist/ui/tool-picker.js +200 -0
  243. package/dist/ui/tooltip.d.ts +21 -0
  244. package/dist/ui/tooltip.js +48 -0
  245. package/dist/ui/top-bar.d.ts +53 -0
  246. package/dist/ui/top-bar.js +98 -0
  247. package/dist/ui/trace-waterfall.d.ts +38 -0
  248. package/dist/ui/trace-waterfall.js +121 -0
  249. package/dist/ui/typing-indicator.d.ts +9 -0
  250. package/dist/ui/typing-indicator.js +25 -0
  251. package/dist/ui/view-toggle.d.ts +17 -0
  252. package/dist/ui/view-toggle.js +46 -0
  253. package/dist/ui/voice-waveform.d.ts +16 -0
  254. package/dist/ui/voice-waveform.js +23 -0
  255. package/package.json +583 -0
@@ -0,0 +1,44 @@
1
+ import type { ReactNode } from "react";
2
+ export type TabDescriptor<Id extends string> = {
3
+ readonly id: Id;
4
+ readonly label: string;
5
+ /** Shown beside the label — a count of what is inside. */
6
+ readonly count?: number;
7
+ };
8
+ export type TabsProps<Id extends string> = {
9
+ readonly tabs: readonly TabDescriptor<Id>[];
10
+ readonly active: Id;
11
+ readonly onChange: (id: Id) => void;
12
+ /** Names the set for assistive tech: "Insights sections". */
13
+ readonly label: string;
14
+ /** Renders the active panel. Called once — only the open panel is mounted. */
15
+ readonly children: (active: Id) => ReactNode;
16
+ /** `underline` for page-level navigation, `enclosed` for tabs inside a card. */
17
+ readonly variant?: "underline" | "enclosed";
18
+ readonly className?: string;
19
+ };
20
+ /**
21
+ * Tabs that behave the way the tab pattern promises.
22
+ *
23
+ * The parts people leave out are the parts that matter. A roving `tabIndex`
24
+ * puts *one* stop in the tab list, so Tab moves past the whole set into the
25
+ * panel instead of stepping through every tab — and that only works if arrow
26
+ * keys move between them, which is why `onKeyDown` here is not optional garnish.
27
+ * Home and End jump to the ends. `aria-controls` and `aria-labelledby` tie each
28
+ * tab to its panel in both directions, which is what makes "go to the panel this
29
+ * tab controls" a thing a screen reader can offer.
30
+ *
31
+ * Selection is controlled and only the active panel is mounted. Tab state is
32
+ * almost always also URL state — someone will want to link to a tab — and
33
+ * owning it internally means fighting the component to do that. Mounting one
34
+ * panel keeps hidden panels from fetching, animating, or holding focus.
35
+ *
36
+ * Focus follows the arrow keys, and only the arrow keys. The APG requires it:
37
+ * with a roving `tabIndex` the tab you arrowed away from becomes
38
+ * `tabIndex={-1}` and `aria-selected={false}`, so leaving focus there strands
39
+ * it on a tab that is no longer either the selection or a tab stop, and the
40
+ * next Tab press escapes from the wrong place. Selection by click does *not*
41
+ * move focus — the clicked tab already has it — and focus is never pushed into
42
+ * the panel, which is the "improvement" that stops people arrowing on.
43
+ */
44
+ export declare function Tabs<Id extends string>({ tabs, active, onChange, label, children, variant, className, }: TabsProps<Id>): import("react").JSX.Element;
@@ -0,0 +1,101 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useRef } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ /**
5
+ * Tabs that behave the way the tab pattern promises.
6
+ *
7
+ * The parts people leave out are the parts that matter. A roving `tabIndex`
8
+ * puts *one* stop in the tab list, so Tab moves past the whole set into the
9
+ * panel instead of stepping through every tab — and that only works if arrow
10
+ * keys move between them, which is why `onKeyDown` here is not optional garnish.
11
+ * Home and End jump to the ends. `aria-controls` and `aria-labelledby` tie each
12
+ * tab to its panel in both directions, which is what makes "go to the panel this
13
+ * tab controls" a thing a screen reader can offer.
14
+ *
15
+ * Selection is controlled and only the active panel is mounted. Tab state is
16
+ * almost always also URL state — someone will want to link to a tab — and
17
+ * owning it internally means fighting the component to do that. Mounting one
18
+ * panel keeps hidden panels from fetching, animating, or holding focus.
19
+ *
20
+ * Focus follows the arrow keys, and only the arrow keys. The APG requires it:
21
+ * with a roving `tabIndex` the tab you arrowed away from becomes
22
+ * `tabIndex={-1}` and `aria-selected={false}`, so leaving focus there strands
23
+ * it on a tab that is no longer either the selection or a tab stop, and the
24
+ * next Tab press escapes from the wrong place. Selection by click does *not*
25
+ * move focus — the clicked tab already has it — and focus is never pushed into
26
+ * the panel, which is the "improvement" that stops people arrowing on.
27
+ */ export function Tabs({ tabs, active, onChange, label, children, variant = "underline", className }) {
28
+ const base = useId();
29
+ const tabId = (id)=>`${base}-tab-${id}`;
30
+ const panelId = (id)=>`${base}-panel-${id}`;
31
+ // Every tab button is rendered whatever the selection, so the node to focus
32
+ // already exists at the moment the key is handled — no effect, no timer.
33
+ const buttons = useRef(new Map());
34
+ /** Arrow/Home/End selection: change the tab *and* take focus with it. */ function focusTab(id) {
35
+ onChange(id);
36
+ buttons.current.get(id)?.focus();
37
+ }
38
+ function move(delta) {
39
+ const index = tabs.findIndex((tab)=>tab.id === active);
40
+ const next = tabs[(index + delta + tabs.length) % tabs.length];
41
+ if (next !== undefined) focusTab(next.id);
42
+ }
43
+ return /*#__PURE__*/ _jsxs("div", {
44
+ className: cn("flex flex-col gap-4", className),
45
+ children: [
46
+ /*#__PURE__*/ _jsx("div", {
47
+ role: "tablist",
48
+ "aria-label": label,
49
+ onKeyDown: (event)=>{
50
+ if (event.key === "ArrowRight") {
51
+ event.preventDefault();
52
+ move(1);
53
+ } else if (event.key === "ArrowLeft") {
54
+ event.preventDefault();
55
+ move(-1);
56
+ } else if (event.key === "Home") {
57
+ event.preventDefault();
58
+ if (tabs[0] !== undefined) focusTab(tabs[0].id);
59
+ } else if (event.key === "End") {
60
+ event.preventDefault();
61
+ const last = tabs[tabs.length - 1];
62
+ if (last !== undefined) focusTab(last.id);
63
+ }
64
+ },
65
+ className: cn("flex flex-wrap items-center", variant === "underline" ? "gap-1 border-b border-border" : "gap-1 rounded-md border border-border p-1"),
66
+ children: tabs.map((tab)=>{
67
+ const selected = tab.id === active;
68
+ return /*#__PURE__*/ _jsxs("button", {
69
+ ref: (node)=>{
70
+ if (node === null) buttons.current.delete(tab.id);
71
+ else buttons.current.set(tab.id, node);
72
+ },
73
+ type: "button",
74
+ role: "tab",
75
+ id: tabId(tab.id),
76
+ "aria-selected": selected,
77
+ "aria-controls": panelId(tab.id),
78
+ tabIndex: selected ? 0 : -1,
79
+ onClick: ()=>onChange(tab.id),
80
+ className: cn("inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium transition-colors", variant === "underline" ? // inactive, so selecting a tab does not shift the row.
81
+ selected ? "-mb-px border-b-2 border-primary-emphasis text-foreground" : "-mb-px border-b-2 border-transparent text-muted-foreground hover:text-foreground" : selected ? "rounded-sm bg-muted text-foreground" : "rounded-sm text-muted-foreground hover:text-foreground"),
82
+ children: [
83
+ tab.label,
84
+ tab.count === undefined ? null : /*#__PURE__*/ _jsx("span", {
85
+ className: "rounded-sm bg-muted px-1.5 font-mono text-xs text-muted-foreground",
86
+ children: tab.count
87
+ })
88
+ ]
89
+ }, tab.id);
90
+ })
91
+ }),
92
+ /*#__PURE__*/ _jsx("div", {
93
+ role: "tabpanel",
94
+ id: panelId(active),
95
+ "aria-labelledby": tabId(active),
96
+ tabIndex: 0,
97
+ children: children(active)
98
+ })
99
+ ]
100
+ });
101
+ }
@@ -0,0 +1,8 @@
1
+ import type * as React from "react";
2
+ export type TextareaProps = React.ComponentProps<"textarea"> & {
3
+ /** Grows the textarea to fit its content instead of scrolling internally. */
4
+ readonly autoResize?: boolean;
5
+ /** Pixel height autoResize grows to before it caps and scrolls. */
6
+ readonly maxHeight?: number;
7
+ };
8
+ export declare function Textarea({ className, autoResize, maxHeight, onChange, ...props }: TextareaProps): React.JSX.Element;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ // Matches ChatInput's MAX_ROWS_PX — the same cap, so a textarea grown by
5
+ // autoResize behaves identically wherever it's used.
6
+ const DEFAULT_MAX_HEIGHT_PX = 200;
7
+ export function Textarea({ className, autoResize, maxHeight = DEFAULT_MAX_HEIGHT_PX, onChange, ...props }) {
8
+ const ref = useRef(null);
9
+ const resize = (element)=>{
10
+ element.style.height = "auto";
11
+ element.style.height = `${Math.min(element.scrollHeight, maxHeight)}px`;
12
+ };
13
+ useEffect(()=>{
14
+ if (autoResize && ref.current) resize(ref.current);
15
+ }, [
16
+ autoResize,
17
+ maxHeight
18
+ ]);
19
+ return /*#__PURE__*/ _jsx("textarea", {
20
+ ref: ref,
21
+ "data-slot": "textarea",
22
+ style: autoResize ? {
23
+ maxHeight
24
+ } : undefined,
25
+ className: cn("flex min-h-16 w-full min-w-0 rounded-md border border-input bg-card px-3 py-2 text-sm shadow-xs transition-colors", autoResize && "resize-none overflow-y-auto", "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground", "aria-invalid:border-destructive disabled:cursor-not-allowed disabled:opacity-50", className),
26
+ onChange: (event)=>{
27
+ if (autoResize) resize(event.currentTarget);
28
+ onChange?.(event);
29
+ },
30
+ ...props
31
+ });
32
+ }
@@ -0,0 +1,36 @@
1
+ import { type ReactNode } from "react";
2
+ import { type ResolvedThemeMode, type ThemeMode, type ThemePreset } from "../lib/theme.js";
3
+ export type ThemeContextValue = {
4
+ readonly mode: ThemeMode;
5
+ readonly setMode: (mode: ThemeMode) => void;
6
+ readonly cycleMode: () => void;
7
+ readonly resolvedMode: ResolvedThemeMode;
8
+ readonly preset: ThemePreset;
9
+ readonly setPreset: (preset: ThemePreset) => void;
10
+ };
11
+ export type ThemeProviderProps = {
12
+ readonly children: ReactNode;
13
+ /**
14
+ * localStorage key. Hosts that persist per user should pass a key that
15
+ * includes the user id (e.g. `corbits-theme:user_1`).
16
+ */
17
+ readonly storageKey?: string;
18
+ readonly defaultMode?: ThemeMode;
19
+ readonly defaultPreset?: ThemePreset;
20
+ /**
21
+ * Element that receives `.dark`/`.light`, `data-theme`, and `color-scheme`.
22
+ * Defaults to `document.documentElement`. A scoped root still inherits the
23
+ * OS-following media query, which only inspects `:root` — explicit light on
24
+ * a dark-OS host requires `.light` on `documentElement` regardless.
25
+ */
26
+ readonly root?: Element | null;
27
+ };
28
+ /**
29
+ * Owns light/dark/system mode and named preset overlays for the Corbits theme.
30
+ *
31
+ * The host mounts this once near the root and keys `storageKey` by user when
32
+ * signed in. Mode is applied as `.dark` on the root (see theme.css); presets
33
+ * as `data-theme`. Components never import storage — they call `useTheme`.
34
+ */
35
+ export declare function ThemeProvider({ children, storageKey, defaultMode, defaultPreset, root, }: ThemeProviderProps): import("react").JSX.Element;
36
+ export declare function useTheme(): ThemeContextValue;
@@ -0,0 +1,126 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
3
+ import { DEFAULT_THEME_STORAGE_KEY, applyThemeToRoot, nextThemeMode, parseThemePreference, resolveThemeMode, serializeThemePreference } from "../lib/theme.js";
4
+ const ThemeContext = /*#__PURE__*/ createContext(null);
5
+ function readSystemPrefersDark() {
6
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
7
+ return false;
8
+ }
9
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
10
+ }
11
+ function readStoredPreference(storageKey, fallback) {
12
+ if (typeof window === "undefined") return fallback;
13
+ try {
14
+ return parseThemePreference(window.localStorage.getItem(storageKey), fallback);
15
+ } catch {
16
+ return fallback;
17
+ }
18
+ }
19
+ function writeStoredPreference(storageKey, preference) {
20
+ if (typeof window === "undefined") return;
21
+ try {
22
+ window.localStorage.setItem(storageKey, serializeThemePreference(preference));
23
+ } catch {
24
+ // Quota / private mode — preference still applies for this session.
25
+ }
26
+ }
27
+ /**
28
+ * Owns light/dark/system mode and named preset overlays for the Corbits theme.
29
+ *
30
+ * The host mounts this once near the root and keys `storageKey` by user when
31
+ * signed in. Mode is applied as `.dark` on the root (see theme.css); presets
32
+ * as `data-theme`. Components never import storage — they call `useTheme`.
33
+ */ export function ThemeProvider({ children, storageKey = DEFAULT_THEME_STORAGE_KEY, defaultMode = "system", defaultPreset = "default", root = null }) {
34
+ const fallback = useMemo(()=>({
35
+ mode: defaultMode,
36
+ preset: defaultPreset
37
+ }), [
38
+ defaultMode,
39
+ defaultPreset
40
+ ]);
41
+ const [preference, setPreference] = useState(()=>readStoredPreference(storageKey, fallback));
42
+ const [systemPrefersDark, setSystemPrefersDark] = useState(readSystemPrefersDark);
43
+ // Re-read when the host changes the storage key (e.g. user signs in).
44
+ useEffect(()=>{
45
+ setPreference(readStoredPreference(storageKey, fallback));
46
+ }, [
47
+ storageKey,
48
+ fallback
49
+ ]);
50
+ useEffect(()=>{
51
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
52
+ return;
53
+ }
54
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
55
+ const onChange = ()=>setSystemPrefersDark(media.matches);
56
+ onChange();
57
+ media.addEventListener("change", onChange);
58
+ return ()=>media.removeEventListener("change", onChange);
59
+ }, []);
60
+ const resolvedMode = resolveThemeMode(preference.mode, systemPrefersDark);
61
+ useEffect(()=>{
62
+ const target = root ?? (typeof document !== "undefined" ? document.documentElement : null);
63
+ if (target === null) return;
64
+ applyThemeToRoot(target, resolvedMode, preference.preset);
65
+ }, [
66
+ root,
67
+ resolvedMode,
68
+ preference.preset
69
+ ]);
70
+ const setMode = useCallback((mode)=>{
71
+ setPreference((current)=>{
72
+ const next = {
73
+ mode,
74
+ preset: current.preset
75
+ };
76
+ writeStoredPreference(storageKey, next);
77
+ return next;
78
+ });
79
+ }, [
80
+ storageKey
81
+ ]);
82
+ const setPreset = useCallback((preset)=>{
83
+ setPreference((current)=>{
84
+ const next = {
85
+ mode: current.mode,
86
+ preset
87
+ };
88
+ writeStoredPreference(storageKey, next);
89
+ return next;
90
+ });
91
+ }, [
92
+ storageKey
93
+ ]);
94
+ const cycleMode = useCallback(()=>{
95
+ setMode(nextThemeMode(preference.mode));
96
+ }, [
97
+ preference.mode,
98
+ setMode
99
+ ]);
100
+ const value = useMemo(()=>({
101
+ mode: preference.mode,
102
+ setMode,
103
+ cycleMode,
104
+ resolvedMode,
105
+ preset: preference.preset,
106
+ setPreset
107
+ }), [
108
+ preference.mode,
109
+ preference.preset,
110
+ setMode,
111
+ cycleMode,
112
+ resolvedMode,
113
+ setPreset
114
+ ]);
115
+ return /*#__PURE__*/ _jsx(ThemeContext.Provider, {
116
+ value: value,
117
+ children: children
118
+ });
119
+ }
120
+ export function useTheme() {
121
+ const value = useContext(ThemeContext);
122
+ if (value === null) {
123
+ throw new Error("No ThemeProvider in scope — wrap the tree in <ThemeProvider>.");
124
+ }
125
+ return value;
126
+ }
@@ -0,0 +1,11 @@
1
+ import { type ButtonProps } from "./button.js";
2
+ export type ThemeToggleProps = Omit<ButtonProps, "onClick" | "children" | "aria-label" | "title"> & {
3
+ /** Override the accessible name; defaults to the current mode label. */
4
+ readonly label?: string;
5
+ };
6
+ /**
7
+ * Cycles system → light → dark. Intended for the host identity dock (or any
8
+ * chrome that needs a one-control theme switch). Uses `useTheme`, so it must
9
+ * sit under `ThemeProvider`.
10
+ */
11
+ export declare function ThemeToggle({ label, variant, size, ...props }: ThemeToggleProps): import("react").JSX.Element;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Monitor, Moon, Sun } from "lucide-react";
3
+ import { Button } from "./button.js";
4
+ import { useTheme } from "./theme-provider.js";
5
+ function iconFor(mode) {
6
+ if (mode === "light") return /*#__PURE__*/ _jsx(Sun, {});
7
+ if (mode === "dark") return /*#__PURE__*/ _jsx(Moon, {});
8
+ return /*#__PURE__*/ _jsx(Monitor, {});
9
+ }
10
+ function labelFor(mode) {
11
+ if (mode === "light") return "Light theme";
12
+ if (mode === "dark") return "Dark theme";
13
+ return "System theme";
14
+ }
15
+ /**
16
+ * Cycles system → light → dark. Intended for the host identity dock (or any
17
+ * chrome that needs a one-control theme switch). Uses `useTheme`, so it must
18
+ * sit under `ThemeProvider`.
19
+ */ export function ThemeToggle({ label, variant = "ghost", size = "sm", ...props }) {
20
+ const { mode, cycleMode } = useTheme();
21
+ const name = label ?? labelFor(mode);
22
+ return /*#__PURE__*/ _jsx(Button, {
23
+ ...props,
24
+ type: "button",
25
+ variant: variant,
26
+ size: size,
27
+ onClick: cycleMode,
28
+ title: name,
29
+ "aria-label": name,
30
+ children: iconFor(mode)
31
+ });
32
+ }
@@ -0,0 +1,7 @@
1
+ import { type ThinkingMarkProps } from "./thinking-mark.js";
2
+ export interface ThinkingIndicatorProps extends ThinkingMarkProps {
3
+ /** Visible status text, also announced by assistive technology. */
4
+ label?: string;
5
+ }
6
+ /** A thinking status with Silk, Strata, or Echo motion. Defaults to Silk. */
7
+ export declare function ThinkingIndicator({ label, className, style, ...markProps }: ThinkingIndicatorProps): import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ import { ThinkingMark } from "./thinking-mark.js";
4
+ /** A thinking status with Silk, Strata, or Echo motion. Defaults to Silk. */ export function ThinkingIndicator({ label = "Thinking...", className, style, ...markProps }) {
5
+ return /*#__PURE__*/ _jsxs("span", {
6
+ role: "status",
7
+ className: cn("inline-flex items-center gap-1.5 text-sm text-muted-foreground", className),
8
+ style: style,
9
+ children: [
10
+ /*#__PURE__*/ _jsx(ThinkingMark, {
11
+ ...markProps,
12
+ className: "-translate-y-[8%]"
13
+ }),
14
+ /*#__PURE__*/ _jsx("span", {
15
+ children: label
16
+ })
17
+ ]
18
+ });
19
+ }
@@ -0,0 +1,19 @@
1
+ export type ThinkingLabelProps = {
2
+ /** What the agent is doing, in its own words — rotated in order. */
3
+ readonly verbs: readonly string[];
4
+ /** Rotation cadence. A heartbeat, not a ticker — keep it at ~1s or slower. */
5
+ readonly intervalMs?: number;
6
+ /** What the status role announces, once. Defaults to "Working". */
7
+ readonly statusLabel?: string;
8
+ readonly className?: string;
9
+ };
10
+ /**
11
+ * The shimmering "working" word, rotating through caller-supplied verbs on
12
+ * an interval. Every verb renders stacked in one grid cell, so the element's
13
+ * width is the widest verb and a swap crossfades instead of reflowing
14
+ * siblings. Visual rotation is decorative: a single `role="status"`
15
+ * announces "Working" once, so a screen reader is not re-interrupted every
16
+ * few seconds. Reduced motion is live via `usePrefersReducedMotion` and
17
+ * stops the rotation rather than just stilling the shimmer.
18
+ */
19
+ export declare function ThinkingLabel({ verbs, intervalMs, statusLabel, className, }: ThinkingLabelProps): import("react").JSX.Element | null;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
4
+ import { cn } from "../lib/utils.js";
5
+ import { ShimmerText } from "./shimmer-text.js";
6
+ const DEFAULT_INTERVAL_MS = 3_000;
7
+ /**
8
+ * The shimmering "working" word, rotating through caller-supplied verbs on
9
+ * an interval. Every verb renders stacked in one grid cell, so the element's
10
+ * width is the widest verb and a swap crossfades instead of reflowing
11
+ * siblings. Visual rotation is decorative: a single `role="status"`
12
+ * announces "Working" once, so a screen reader is not re-interrupted every
13
+ * few seconds. Reduced motion is live via `usePrefersReducedMotion` and
14
+ * stops the rotation rather than just stilling the shimmer.
15
+ */ export function ThinkingLabel({ verbs, intervalMs = DEFAULT_INTERVAL_MS, statusLabel = "Working", className }) {
16
+ const [at, setAt] = useState(0);
17
+ const reduceMotion = usePrefersReducedMotion();
18
+ useEffect(()=>{
19
+ if (verbs.length < 2 || !reduceMotion) return;
20
+ setAt(0);
21
+ }, [
22
+ verbs.length,
23
+ reduceMotion
24
+ ]);
25
+ useEffect(()=>{
26
+ if (verbs.length < 2 || reduceMotion) return;
27
+ const timer = setInterval(()=>setAt((current)=>(current + 1) % verbs.length), intervalMs);
28
+ return ()=>clearInterval(timer);
29
+ }, [
30
+ verbs.length,
31
+ intervalMs,
32
+ reduceMotion
33
+ ]);
34
+ if (verbs.length === 0) return null;
35
+ const active = at % verbs.length;
36
+ return /*#__PURE__*/ _jsxs("span", {
37
+ className: cn("text-sm", className),
38
+ children: [
39
+ /*#__PURE__*/ _jsx("span", {
40
+ role: "status",
41
+ className: "sr-only",
42
+ children: statusLabel
43
+ }),
44
+ /*#__PURE__*/ _jsx("span", {
45
+ "aria-hidden": true,
46
+ className: "inline-grid",
47
+ children: verbs.map((verb, index)=>/*#__PURE__*/ _jsx(ShimmerText, {
48
+ className: cn("col-start-1 row-start-1 transition-opacity duration-200", index === active ? "opacity-100" : "opacity-0"),
49
+ children: verb
50
+ }, index))
51
+ })
52
+ ]
53
+ });
54
+ }
@@ -0,0 +1,10 @@
1
+ import { type CSSProperties } from "react";
2
+ export type ThinkingVariant = "silk" | "strata" | "echo";
3
+ export interface ThinkingMarkProps {
4
+ variant?: ThinkingVariant;
5
+ paused?: boolean;
6
+ className?: string;
7
+ style?: CSSProperties;
8
+ }
9
+ /** Decorative animated Corbits mark. Pair with visible text or ThinkingIndicator. */
10
+ export declare function ThinkingMark({ variant, paused, className, style }: ThinkingMarkProps): import("react").JSX.Element;