@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,170 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronRight } from "lucide-react";
3
+ import { useControllableState } from "../hooks/use-controllable-state.js";
4
+ import { cn } from "../lib/utils.js";
5
+ import { StatusDot } from "./status-dot.js";
6
+ const STATE_TEXT = {
7
+ pending: "Queued",
8
+ running: "Working",
9
+ "output-available": "Done",
10
+ error: "Failed",
11
+ "approval-requested": "Needs approval",
12
+ "output-denied": "Denied"
13
+ };
14
+ /**
15
+ * Orange (`emphasis`) is reserved for `approval-requested` alone: it is the
16
+ * one state that needs a human to act, so it is the one state that earns the
17
+ * brand's single emphasis color. `running` already signals "in progress"
18
+ * through the dot's own pulse animation (`StatusDot`'s `live` prop) — giving
19
+ * it orange too would stack two "look here" signals on a state that isn't
20
+ * actually asking for anything.
21
+ */ const STATE_TONE = {
22
+ pending: "neutral",
23
+ running: "neutral",
24
+ "output-available": "neutral",
25
+ error: "danger",
26
+ "approval-requested": "emphasis",
27
+ "output-denied": "danger"
28
+ };
29
+ /** True when the state carries no `error`/`running` weight in a warning sense but still opens by default. */ const OPENS_BY_DEFAULT = {
30
+ pending: false,
31
+ running: false,
32
+ "output-available": false,
33
+ error: true,
34
+ "approval-requested": true,
35
+ "output-denied": true
36
+ };
37
+ function humaniseToolName(name) {
38
+ const [provider, ...rest] = name.split("__");
39
+ const humanise = (value)=>{
40
+ const words = value.replace(/[_-]+/g, " ").trim();
41
+ return words.charAt(0).toUpperCase() + words.slice(1);
42
+ };
43
+ if (rest.length > 0 && provider !== undefined) {
44
+ return `${humanise(rest.join("__"))} (${humanise(provider)})`;
45
+ }
46
+ return humanise(name);
47
+ }
48
+ function stateDetailText(state) {
49
+ switch(state.status){
50
+ case "output-available":
51
+ return state.output;
52
+ case "error":
53
+ return state.message;
54
+ case "approval-requested":
55
+ case "output-denied":
56
+ return state.reason;
57
+ case "pending":
58
+ case "running":
59
+ return undefined;
60
+ }
61
+ }
62
+ function stateDetailTitle(state) {
63
+ switch(state.status){
64
+ case "error":
65
+ return "Error";
66
+ case "approval-requested":
67
+ return "Reason";
68
+ case "output-denied":
69
+ return "Denied because";
70
+ case "output-available":
71
+ case "pending":
72
+ case "running":
73
+ return "Output";
74
+ }
75
+ }
76
+ /**
77
+ * What a tool call did, as one line the transcript can open.
78
+ *
79
+ * Collapsed by default and collapsed even while running: a transcript where
80
+ * every tool call expands itself pushes the answer off screen, and the
81
+ * answer is what the user is waiting for. A failed call, a denied approval
82
+ * and a pending approval request all open by default — those are the three
83
+ * states where the detail is the point, not an afterthought.
84
+ *
85
+ * Uncontrolled by default, seeded from `defaultOpen` (or the state's own
86
+ * default). Pass `open`/`onOpenChange` to drive it from a parent.
87
+ */ export function ToolBlock({ name, label, state, input, defaultOpen, open: openProp, onOpenChange, className }) {
88
+ const [open, setOpen] = useControllableState({
89
+ value: openProp,
90
+ defaultValue: defaultOpen ?? OPENS_BY_DEFAULT[state.status],
91
+ onChange: onOpenChange,
92
+ name: "ToolBlock"
93
+ });
94
+ const detailText = stateDetailText(state);
95
+ const hasDetail = input !== undefined || detailText !== undefined;
96
+ const displayLabel = label !== undefined && label.length > 0 ? label : humaniseToolName(name);
97
+ return /*#__PURE__*/ _jsxs("div", {
98
+ "data-slot": "tool-block",
99
+ "data-status": state.status,
100
+ className: cn("text-xs", className),
101
+ children: [
102
+ /*#__PURE__*/ _jsxs("button", {
103
+ type: "button",
104
+ onClick: ()=>setOpen((value)=>!value),
105
+ "aria-expanded": open,
106
+ disabled: !hasDetail,
107
+ // `relative` plus the `::after` pseudo-element below extends the
108
+ // button's effective (vertical) hit area to 40px without inflating
109
+ // its own padding/density — see design-engineering.md's hit-area
110
+ // pattern. The row is already full-width, so only height needs
111
+ // extending.
112
+ className: "relative flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-muted-foreground transition-colors after:absolute after:inset-x-0 after:top-1/2 after:h-10 after:-translate-y-1/2 hover:bg-muted hover:text-foreground disabled:hover:bg-transparent",
113
+ children: [
114
+ /*#__PURE__*/ _jsx("span", {
115
+ "data-slot": "tool-block-status",
116
+ // A pending approval is human-blocking: it needs to reach assistive
117
+ // tech the instant it appears, not only when a reader happens to
118
+ // tab onto this button. Scoped to this span alone (not the whole
119
+ // block) so every other state change in a busy transcript stays
120
+ // silent — announcing every "Working" -> "Done" tick here would be
121
+ // spam.
122
+ role: state.status === "approval-requested" ? "status" : undefined,
123
+ "aria-live": state.status === "approval-requested" ? "polite" : undefined,
124
+ children: /*#__PURE__*/ _jsx(StatusDot, {
125
+ label: STATE_TEXT[state.status],
126
+ live: state.status === "running",
127
+ tone: STATE_TONE[state.status]
128
+ })
129
+ }),
130
+ /*#__PURE__*/ _jsx("span", {
131
+ className: "min-w-0 flex-1 truncate",
132
+ children: displayLabel
133
+ }),
134
+ hasDetail ? /*#__PURE__*/ _jsx(ChevronRight, {
135
+ className: cn("size-3.5 shrink-0 transition-transform duration-200 ease-out", open && "rotate-90"),
136
+ "aria-hidden": true
137
+ }) : null
138
+ ]
139
+ }),
140
+ open && hasDetail ? /*#__PURE__*/ _jsxs("div", {
141
+ className: "mt-1 ml-4 flex flex-col gap-2 border-l border-border pl-3 [animation:corbits-rail-block-in_200ms_var(--ease-out)_both]",
142
+ children: [
143
+ input === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
144
+ title: "Input",
145
+ children: JSON.stringify(input, null, 2)
146
+ }),
147
+ detailText === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
148
+ title: stateDetailTitle(state),
149
+ children: detailText
150
+ })
151
+ ]
152
+ }) : null
153
+ ]
154
+ });
155
+ }
156
+ function Detail({ title, children }) {
157
+ return /*#__PURE__*/ _jsxs("div", {
158
+ className: "flex flex-col gap-1",
159
+ children: [
160
+ /*#__PURE__*/ _jsx("p", {
161
+ className: "text-[10px] font-semibold tracking-[0.1em] text-muted-foreground uppercase",
162
+ children: title
163
+ }),
164
+ /*#__PURE__*/ _jsx("pre", {
165
+ className: "max-h-48 overflow-auto rounded-md bg-muted p-2 font-mono text-[11px] whitespace-pre-wrap",
166
+ children: children
167
+ })
168
+ ]
169
+ });
170
+ }
@@ -0,0 +1,22 @@
1
+ import { type ToolPart } from "../lib/chat-message.js";
2
+ /**
3
+ * What the agent did, as one line the user can open.
4
+ *
5
+ * Collapsed by default and collapsed even while running: a transcript where
6
+ * every tool call expands itself pushes the answer off screen, and the answer
7
+ * is what the user is waiting for. The line still says which tool and how it
8
+ * ended, so nothing is hidden — only folded.
9
+ *
10
+ * A failed call opens by default. That is the one case where the detail is the
11
+ * point.
12
+ *
13
+ * Uncontrolled by default. Pass `open`/`onOpenChange` to drive it from a parent.
14
+ */
15
+ export type ToolNarrativeProps = {
16
+ readonly part: ToolPart;
17
+ /** Controlled open state. Pair with `onOpenChange` to lift it to a parent. */
18
+ readonly open?: boolean;
19
+ readonly onOpenChange?: (open: boolean) => void;
20
+ readonly className?: string;
21
+ };
22
+ export declare function ToolNarrative({ part, open: openProp, onOpenChange, className }: ToolNarrativeProps): import("react").JSX.Element;
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronRight } from "lucide-react";
3
+ import { useControllableState } from "../hooks/use-controllable-state.js";
4
+ import { toolLabel } from "../lib/chat-message.js";
5
+ import { cn } from "../lib/utils.js";
6
+ import { StatusDot } from "./status-dot.js";
7
+ const STATE_TEXT = {
8
+ running: "Working",
9
+ done: "Done",
10
+ error: "Failed"
11
+ };
12
+ export function ToolNarrative({ part, open: openProp, onOpenChange, className }) {
13
+ const [open, setOpen] = useControllableState({
14
+ value: openProp,
15
+ defaultValue: part.state === "error",
16
+ onChange: onOpenChange,
17
+ name: "ToolNarrative"
18
+ });
19
+ const label = toolLabel(part);
20
+ const hasDetail = part.output !== undefined || part.input !== undefined;
21
+ return /*#__PURE__*/ _jsxs("div", {
22
+ "data-slot": "tool-narrative",
23
+ className: cn("text-xs", className),
24
+ children: [
25
+ /*#__PURE__*/ _jsxs("button", {
26
+ type: "button",
27
+ onClick: ()=>setOpen((value)=>!value),
28
+ "aria-expanded": open,
29
+ disabled: !hasDetail,
30
+ className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:hover:bg-transparent",
31
+ children: [
32
+ /*#__PURE__*/ _jsx(StatusDot, {
33
+ label: STATE_TEXT[part.state],
34
+ live: part.state === "running",
35
+ tone: part.state === "error" ? "danger" : part.state === "running" ? "emphasis" : "neutral"
36
+ }),
37
+ /*#__PURE__*/ _jsx("span", {
38
+ className: "min-w-0 flex-1 truncate",
39
+ children: label
40
+ }),
41
+ hasDetail ? /*#__PURE__*/ _jsx(ChevronRight, {
42
+ className: cn("size-3.5 shrink-0 transition-transform", open && "rotate-90"),
43
+ "aria-hidden": true
44
+ }) : null
45
+ ]
46
+ }),
47
+ open && hasDetail ? /*#__PURE__*/ _jsxs("div", {
48
+ className: "mt-1 ml-4 flex flex-col gap-2 border-l border-border pl-3",
49
+ children: [
50
+ part.input === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
51
+ title: "Input",
52
+ children: JSON.stringify(part.input, null, 2)
53
+ }),
54
+ part.output === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
55
+ title: part.state === "error" ? "Error" : "Output",
56
+ children: part.output
57
+ })
58
+ ]
59
+ }) : null
60
+ ]
61
+ });
62
+ }
63
+ function Detail({ title, children }) {
64
+ return /*#__PURE__*/ _jsxs("div", {
65
+ className: "flex flex-col gap-1",
66
+ children: [
67
+ /*#__PURE__*/ _jsx("p", {
68
+ className: "text-[10px] font-semibold tracking-[0.1em] text-muted-foreground uppercase",
69
+ children: title
70
+ }),
71
+ /*#__PURE__*/ _jsx("pre", {
72
+ className: "max-h-48 overflow-auto rounded-md bg-muted p-2 font-mono text-[11px] whitespace-pre-wrap",
73
+ children: children
74
+ })
75
+ ]
76
+ });
77
+ }
@@ -0,0 +1,44 @@
1
+ import type { ReactNode } from "react";
2
+ export type ToolPickerStatus = "pending" | "disabled";
3
+ /**
4
+ * One entry in the caller-supplied catalog — a tool the picker can list,
5
+ * search and select. Shaped like a `DataTable` row: plain data in, no
6
+ * behaviour, so the same catalog can back a table elsewhere without a
7
+ * reshape.
8
+ */
9
+ export type ToolPickerItem = {
10
+ readonly id: string;
11
+ readonly name: string;
12
+ /** Groups the list — the picker's one piece of structure over the flat catalog. */
13
+ readonly package: string;
14
+ readonly description?: string;
15
+ /** Absent means available. `disabled` blocks selection; `pending` allows it. */
16
+ readonly status?: ToolPickerStatus;
17
+ };
18
+ export type ToolPickerProps = {
19
+ readonly items: readonly ToolPickerItem[];
20
+ /** Selected ids. A single-select picker still carries at most one entry here. */
21
+ readonly value: readonly string[];
22
+ readonly onChange: (value: readonly string[]) => void;
23
+ /** Defaults to single-select: a new pick replaces the current one. */
24
+ readonly multiple?: boolean;
25
+ /** First load — the catalog has not arrived yet, distinct from an empty catalog. */
26
+ readonly loading?: boolean;
27
+ /** Shown when the catalog itself is empty. Failed searches use a separate default. */
28
+ readonly empty?: ReactNode;
29
+ readonly searchPlaceholder?: string;
30
+ /** Accessible name for the search input — set it if this isn't picking tools. */
31
+ readonly searchLabel?: string;
32
+ readonly className?: string;
33
+ };
34
+ /**
35
+ * Browse, search and select tools from a caller-supplied catalog, grouped by
36
+ * package. Controlled like a form field (`value`/`onChange`) rather than
37
+ * owning selection itself — a caller wiring this into a bigger form needs
38
+ * the same value it would get from any other control.
39
+ *
40
+ * Disabled items stay in the list rather than disappearing — a tool a
41
+ * caller cannot pick right now is still information ("already attached
42
+ * elsewhere", "needs a credential") that vanishing would hide.
43
+ */
44
+ export declare function ToolPicker({ items, value, onChange, multiple, loading, empty, searchPlaceholder, searchLabel, className, }: ToolPickerProps): import("react").JSX.Element;
@@ -0,0 +1,200 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
3
+ import { useCommandPaletteNavigation } from "../hooks/use-command-palette-navigation.js";
4
+ import { cn } from "../lib/utils.js";
5
+ import { Badge } from "./badge.js";
6
+ import { EmptyState } from "./empty-state.js";
7
+ import { Input } from "./input.js";
8
+ const STATUS_LABEL = {
9
+ pending: "Pending",
10
+ disabled: "Disabled"
11
+ };
12
+ function matches(item, query) {
13
+ if (query.length === 0) return true;
14
+ const haystack = `${item.name} ${item.package} ${item.description ?? ""}`.toLowerCase();
15
+ return haystack.includes(query.toLowerCase());
16
+ }
17
+ function groupByPackage(items) {
18
+ const groups = new Map();
19
+ for (const item of items){
20
+ const group = groups.get(item.package);
21
+ if (group === undefined) groups.set(item.package, [
22
+ item
23
+ ]);
24
+ else group.push(item);
25
+ }
26
+ return [
27
+ ...groups.entries()
28
+ ].map(([id, groupItems])=>({
29
+ id,
30
+ items: groupItems
31
+ }));
32
+ }
33
+ /** Visible group title: a display name if the catalog already sent one, else a humanized slug. */ function groupHeading(id) {
34
+ if (/[A-Z]/.test(id) || /\s/.test(id)) return id;
35
+ return id.split(/[-_./]+/).filter(Boolean).map((part)=>part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
36
+ }
37
+ /**
38
+ * Browse, search and select tools from a caller-supplied catalog, grouped by
39
+ * package. Controlled like a form field (`value`/`onChange`) rather than
40
+ * owning selection itself — a caller wiring this into a bigger form needs
41
+ * the same value it would get from any other control.
42
+ *
43
+ * Disabled items stay in the list rather than disappearing — a tool a
44
+ * caller cannot pick right now is still information ("already attached
45
+ * elsewhere", "needs a credential") that vanishing would hide.
46
+ */ export function ToolPicker({ items, value, onChange, multiple = false, loading = false, empty, searchPlaceholder = "Search tools…", searchLabel = "Search tools", className }) {
47
+ const baseId = useId();
48
+ const [query, setQuery] = useState("");
49
+ const listRef = useRef(null);
50
+ const filtered = useMemo(()=>items.filter((item)=>matches(item, query)), [
51
+ items,
52
+ query
53
+ ]);
54
+ const groups = useMemo(()=>groupByPackage(filtered), [
55
+ filtered
56
+ ]);
57
+ const selectedIds = useMemo(()=>new Set(value), [
58
+ value
59
+ ]);
60
+ const toggle = (item)=>{
61
+ if (item.status === "disabled") return;
62
+ const isSelected = selectedIds.has(item.id);
63
+ if (!multiple) {
64
+ onChange(isSelected ? [] : [
65
+ item.id
66
+ ]);
67
+ return;
68
+ }
69
+ onChange(isSelected ? value.filter((id)=>id !== item.id) : [
70
+ ...value,
71
+ item.id
72
+ ]);
73
+ };
74
+ const navigation = useCommandPaletteNavigation({
75
+ items: filtered,
76
+ onSelect: (id)=>{
77
+ const item = filtered.find((candidate)=>candidate.id === id);
78
+ if (item !== undefined) toggle(item);
79
+ },
80
+ onClose: ()=>{
81
+ /* Escape is handled below so an empty query does not swallow the key. */ }
82
+ });
83
+ useEffect(()=>{
84
+ listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({
85
+ block: "nearest"
86
+ });
87
+ }, [
88
+ navigation.activeId
89
+ ]);
90
+ const optionId = (id)=>`${baseId}-option-${id}`;
91
+ const onKeyDown = (event)=>{
92
+ if (event.key === "Escape") {
93
+ if (query.length > 0) {
94
+ event.preventDefault();
95
+ setQuery("");
96
+ }
97
+ return;
98
+ }
99
+ navigation.onKeyDown(event);
100
+ };
101
+ const emptyCatalog = items.length === 0;
102
+ const noMatches = !emptyCatalog && filtered.length === 0;
103
+ return /*#__PURE__*/ _jsxs("div", {
104
+ "data-slot": "tool-picker",
105
+ className: cn("flex min-h-0 flex-col gap-2", className),
106
+ children: [
107
+ /*#__PURE__*/ _jsx(Input, {
108
+ value: query,
109
+ onChange: (event)=>setQuery(event.target.value),
110
+ onKeyDown: onKeyDown,
111
+ placeholder: searchPlaceholder,
112
+ "aria-label": searchLabel,
113
+ role: "combobox",
114
+ "aria-expanded": true,
115
+ "aria-autocomplete": "list",
116
+ "aria-controls": `${baseId}-list`,
117
+ "aria-activedescendant": navigation.activeId === undefined ? undefined : optionId(navigation.activeId),
118
+ "aria-busy": loading,
119
+ autoComplete: "off",
120
+ spellCheck: false
121
+ }),
122
+ /*#__PURE__*/ _jsx("div", {
123
+ ref: listRef,
124
+ id: `${baseId}-list`,
125
+ role: "listbox",
126
+ "aria-label": "Tools",
127
+ "aria-multiselectable": multiple,
128
+ className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto",
129
+ children: loading ? /*#__PURE__*/ _jsx("p", {
130
+ role: "presentation",
131
+ className: "px-2 py-8 text-center text-sm text-muted-foreground",
132
+ children: "Loading…"
133
+ }) : emptyCatalog ? /*#__PURE__*/ _jsx("div", {
134
+ role: "presentation",
135
+ children: empty ?? /*#__PURE__*/ _jsx(EmptyState, {
136
+ title: "No tools",
137
+ description: "Nothing in the catalog yet."
138
+ })
139
+ }) : noMatches ? /*#__PURE__*/ _jsx("p", {
140
+ role: "presentation",
141
+ className: "px-2 py-8 text-center text-sm text-muted-foreground",
142
+ children: "No tools match — try a different search."
143
+ }) : groups.map((group)=>/*#__PURE__*/ _jsxs("div", {
144
+ role: "presentation",
145
+ className: "flex flex-col gap-1",
146
+ children: [
147
+ /*#__PURE__*/ _jsx("p", {
148
+ className: "px-3.5 py-1.5 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground",
149
+ children: groupHeading(group.id)
150
+ }),
151
+ /*#__PURE__*/ _jsx("div", {
152
+ className: "flex flex-col gap-1",
153
+ children: group.items.map((item)=>/*#__PURE__*/ _jsx(ToolPickerOption, {
154
+ id: optionId(item.id),
155
+ item: item,
156
+ selected: selectedIds.has(item.id),
157
+ active: navigation.activeId === item.id,
158
+ onSelect: ()=>toggle(item),
159
+ onPointerMove: ()=>navigation.setActiveId(item.id)
160
+ }, item.id))
161
+ })
162
+ ]
163
+ }, group.id))
164
+ })
165
+ ]
166
+ });
167
+ }
168
+ function ToolPickerOption({ id, item, selected, active, onSelect, onPointerMove }) {
169
+ const disabled = item.status === "disabled";
170
+ return /*#__PURE__*/ _jsxs("div", {
171
+ id: id,
172
+ role: "option",
173
+ "aria-selected": selected,
174
+ "aria-disabled": disabled,
175
+ "data-active": active,
176
+ onPointerMove: disabled ? undefined : onPointerMove,
177
+ onClick: disabled ? undefined : onSelect,
178
+ className: cn("flex w-full flex-col gap-1 rounded-lg border px-3.5 py-3 text-left transition-colors ease-out", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer hover:bg-muted active:brightness-95", selected ? "border-primary-emphasis bg-primary/10" : "border-border bg-card", // The keyboard cursor must stay visible on disabled rows — aria-activedescendant can land there.
179
+ active && "bg-muted"),
180
+ children: [
181
+ /*#__PURE__*/ _jsxs("span", {
182
+ className: "flex items-center gap-2",
183
+ children: [
184
+ /*#__PURE__*/ _jsx("span", {
185
+ className: "text-sm font-semibold",
186
+ children: item.name
187
+ }),
188
+ item.status === undefined ? null : /*#__PURE__*/ _jsx(Badge, {
189
+ tone: "neutral",
190
+ children: STATUS_LABEL[item.status]
191
+ })
192
+ ]
193
+ }),
194
+ item.description === undefined ? null : /*#__PURE__*/ _jsx("span", {
195
+ className: "text-xs leading-snug text-muted-foreground",
196
+ children: item.description
197
+ })
198
+ ]
199
+ });
200
+ }
@@ -0,0 +1,21 @@
1
+ export type InfoTooltipProps = {
2
+ /** The note shown in the tooltip and bound to the trigger via `aria-describedby`. */
3
+ readonly label: string;
4
+ /** Accessible name for the trigger button. Defaults to `label`. */
5
+ readonly triggerLabel?: string;
6
+ /** Render already open — uncontrolled, for stories and screenshots. */
7
+ readonly defaultOpen?: boolean;
8
+ readonly className?: string;
9
+ };
10
+ /**
11
+ * A focusable info glyph that discloses a short note on hover or focus.
12
+ *
13
+ * Radix owns the `aria-describedby` wiring, the open-on-focus/hover
14
+ * behaviour, and Escape-to-dismiss; this layer only paints. Built for notes
15
+ * that today get bolted onto a non-interactive element via a `title`
16
+ * attribute — that reaches a mouse only, is never announced by a screen
17
+ * reader, and never fires on touch. A `<button>` trigger fixes all three:
18
+ * it is in the tab order, Radix associates the note by id, and tapping it
19
+ * opens the tooltip on touch devices.
20
+ */
21
+ export declare function InfoTooltip({ label, triggerLabel, defaultOpen, className }: InfoTooltipProps): import("react").JSX.Element;
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
+ import { InfoIcon } from "lucide-react";
4
+ import { cn } from "../lib/utils.js";
5
+ /**
6
+ * A focusable info glyph that discloses a short note on hover or focus.
7
+ *
8
+ * Radix owns the `aria-describedby` wiring, the open-on-focus/hover
9
+ * behaviour, and Escape-to-dismiss; this layer only paints. Built for notes
10
+ * that today get bolted onto a non-interactive element via a `title`
11
+ * attribute — that reaches a mouse only, is never announced by a screen
12
+ * reader, and never fires on touch. A `<button>` trigger fixes all three:
13
+ * it is in the tab order, Radix associates the note by id, and tapping it
14
+ * opens the tooltip on touch devices.
15
+ */ export function InfoTooltip({ label, triggerLabel, defaultOpen, className }) {
16
+ return /*#__PURE__*/ _jsx(TooltipPrimitive.Provider, {
17
+ delayDuration: 200,
18
+ children: /*#__PURE__*/ _jsxs(TooltipPrimitive.Root, {
19
+ defaultOpen: defaultOpen,
20
+ children: [
21
+ /*#__PURE__*/ _jsx(TooltipPrimitive.Trigger, {
22
+ asChild: true,
23
+ children: /*#__PURE__*/ _jsx("button", {
24
+ type: "button",
25
+ "aria-label": triggerLabel ?? label,
26
+ className: cn("inline-flex size-4 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors", "hover:text-foreground focus-visible:text-foreground focus-visible:outline-none", "focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary)_22%,transparent)]", className),
27
+ children: /*#__PURE__*/ _jsx(InfoIcon, {
28
+ className: "size-full"
29
+ })
30
+ })
31
+ }),
32
+ /*#__PURE__*/ _jsx(TooltipPrimitive.Portal, {
33
+ children: /*#__PURE__*/ _jsxs(TooltipPrimitive.Content, {
34
+ "data-slot": "tooltip-content",
35
+ sideOffset: 6,
36
+ className: cn("z-50 max-w-64 rounded-md border border-border bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-lg", "data-[state=delayed-open]:animate-[corbits-fade-in_150ms_ease-out] data-[state=closed]:animate-[corbits-fade-out_120ms_ease-in]"),
37
+ children: [
38
+ label,
39
+ /*#__PURE__*/ _jsx(TooltipPrimitive.Arrow, {
40
+ className: "fill-popover"
41
+ })
42
+ ]
43
+ })
44
+ })
45
+ ]
46
+ })
47
+ });
48
+ }
@@ -0,0 +1,53 @@
1
+ import type * as React from "react";
2
+ /**
3
+ * The row above the page. It owns no content of its own — a title or a
4
+ * breadcrumb trail on the left, whatever the page contributes on the right.
5
+ *
6
+ * Deliberately a `<header>` with parts rather than a `title`/`actions` prop
7
+ * pair: pages need a search box, a filter row or a context strip in that space,
8
+ * and a fixed prop list would push every one of them through a `ReactNode` prop
9
+ * that does the same job as a child.
10
+ */
11
+ export declare function TopBar({ className, ...props }: React.ComponentProps<"header">): React.JSX.Element;
12
+ export declare function TopBarTitle({ count, subtitle, className, children, ...props }: React.ComponentProps<"div"> & {
13
+ count?: number;
14
+ subtitle?: string;
15
+ }): React.JSX.Element;
16
+ export type Crumb = {
17
+ readonly label: string;
18
+ /** Omit on the last crumb — the current page is not a link. */
19
+ readonly href?: string;
20
+ };
21
+ /**
22
+ * Props a breadcrumb link receives, whether it renders as a plain `<a>` or a
23
+ * caller-supplied component. Only non-last crumbs render through this — see
24
+ * `TopBarBreadcrumbs` for why the last crumb is never a link.
25
+ */
26
+ export type BreadcrumbLinkProps = {
27
+ readonly href: string;
28
+ readonly className: string;
29
+ readonly children: React.ReactNode;
30
+ };
31
+ export type TopBarBreadcrumbsProps = Omit<React.ComponentProps<"nav">, "children"> & {
32
+ crumbs: readonly Crumb[];
33
+ /**
34
+ * Renders every non-last crumb; defaults to a plain `<a>`. Must be a
35
+ * stable, module-scope reference — an inline arrow here creates a new
36
+ * component type on every render, which remounts every crumb. A router
37
+ * `Link` (e.g. React Router) that takes `to` instead of `href` needs a
38
+ * one-line wrapper: `({ href, ...rest }) => <Link to={href} {...rest} />`.
39
+ */
40
+ linkComponent?: React.ComponentType<BreadcrumbLinkProps>;
41
+ };
42
+ /**
43
+ * Breadcrumb trail. The last crumb is the current page: it is rendered as text
44
+ * with `aria-current="page"` even if a caller passes an href, because linking
45
+ * to where you already are is a known screen-reader annoyance.
46
+ *
47
+ * Every other crumb renders through `linkComponent`, defaulting to a plain
48
+ * `<a>`. A consumer routing through a SPA router passes its own `Link` so a
49
+ * breadcrumb click does not force a full page reload.
50
+ */
51
+ export declare function TopBarBreadcrumbs({ crumbs, linkComponent: LinkComponent, className, ...props }: TopBarBreadcrumbsProps): React.JSX.Element;
52
+ /** Trailing controls. Pushes itself to the right; put it last. */
53
+ export declare function TopBarActions({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;