@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,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { ArrowLeft } from "lucide-react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { EmptyState } from "./empty-state.js";
5
+ /**
6
+ * Index on the left, the open item on the right.
7
+ *
8
+ * Two panes above `lg`, one below it. On a narrow screen the detail *replaces*
9
+ * the list rather than sitting under it: a phone-width column showing both
10
+ * means the user opens something and sees no change without scrolling.
11
+ *
12
+ * That swap is why `onCloseDetail` is required rather than optional. Once the
13
+ * list is hidden, the back control is the only way out — a caller who omitted
14
+ * it would ship a dead end on mobile, so the type does not let them.
15
+ *
16
+ * Both panes scroll independently, so reading a long detail does not scroll the
17
+ * list out from under the selection.
18
+ */ export function ListDetail({ list, detail, onCloseDetail, placeholder, detailLabel = "Details", className }) {
19
+ const open = detail !== null;
20
+ return /*#__PURE__*/ _jsxs("div", {
21
+ className: cn("flex min-h-0 flex-1 gap-6", className),
22
+ children: [
23
+ /*#__PURE__*/ _jsx("div", {
24
+ className: cn("min-h-0 flex-1 overflow-y-auto lg:max-w-md lg:shrink-0 lg:border-r lg:border-border lg:pr-6", // Hidden, not merely visually stacked: leaving it in the tree on
25
+ // mobile would keep every row in the tab order behind the detail.
26
+ open && "hidden lg:block"),
27
+ children: list
28
+ }),
29
+ /*#__PURE__*/ _jsx("section", {
30
+ "aria-label": detailLabel,
31
+ className: cn("min-h-0 min-w-0 flex-1 overflow-y-auto", !open && "hidden lg:block"),
32
+ children: open ? /*#__PURE__*/ _jsxs(_Fragment, {
33
+ children: [
34
+ /*#__PURE__*/ _jsxs("button", {
35
+ type: "button",
36
+ onClick: onCloseDetail,
37
+ className: "mb-3 inline-flex items-center gap-1.5 rounded-md text-sm text-muted-foreground transition-colors hover:text-foreground lg:hidden",
38
+ children: [
39
+ /*#__PURE__*/ _jsx(ArrowLeft, {
40
+ className: "size-4",
41
+ "aria-hidden": true
42
+ }),
43
+ "Back to list"
44
+ ]
45
+ }),
46
+ detail
47
+ ]
48
+ }) : placeholder ?? /*#__PURE__*/ _jsx(EmptyState, {
49
+ title: "Nothing selected",
50
+ description: "Pick something from the list."
51
+ })
52
+ })
53
+ ]
54
+ });
55
+ }
@@ -0,0 +1,13 @@
1
+ export type LiveRunBannerProps = {
2
+ readonly awaiting?: boolean;
3
+ readonly message?: string;
4
+ readonly elapsed?: string;
5
+ readonly className?: string;
6
+ };
7
+ /**
8
+ * The one-line status strip above a live run's steps: awaiting gets the
9
+ * accent tone that marks "this needs you" everywhere else in the registry,
10
+ * running gets the info tone. Standalone so a consumer building its own run
11
+ * surface can reuse just this strip.
12
+ */
13
+ export declare function LiveRunBanner({ awaiting, message, elapsed, className }: LiveRunBannerProps): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ /**
4
+ * The one-line status strip above a live run's steps: awaiting gets the
5
+ * accent tone that marks "this needs you" everywhere else in the registry,
6
+ * running gets the info tone. Standalone so a consumer building its own run
7
+ * surface can reuse just this strip.
8
+ */ export function LiveRunBanner({ awaiting = false, message, elapsed, className }) {
9
+ return /*#__PURE__*/ _jsxs("div", {
10
+ role: "status",
11
+ className: cn("mb-3 flex flex-wrap items-center justify-between gap-2 rounded-lg border px-3 py-2 text-xs", awaiting ? "border-primary-emphasis/35 bg-primary/10 text-primary-emphasis" : "border-accent bg-accent text-accent-foreground", className),
12
+ children: [
13
+ /*#__PURE__*/ _jsx("span", {
14
+ className: "font-semibold",
15
+ children: message ?? (awaiting ? "Paused for your input" : "Running")
16
+ }),
17
+ elapsed === undefined ? null : /*#__PURE__*/ _jsx("span", {
18
+ className: "font-mono text-[11.5px] tabular-nums opacity-90",
19
+ children: elapsed
20
+ })
21
+ ]
22
+ });
23
+ }
@@ -0,0 +1,22 @@
1
+ export type LiveStatusLineProps = {
2
+ readonly label: string;
3
+ readonly className?: string;
4
+ };
5
+ /** The single "what's happening now" line under a stepper. A spinning ring
6
+ * plus text, not colour alone — `motion-reduce:animate-none` stills the
7
+ * spinner without hiding the line, since the words already carry the
8
+ * meaning. */
9
+ export declare function LiveStatusLine({ label, className }: LiveStatusLineProps): import("react").JSX.Element;
10
+ export type LiveStatusSlotProps = {
11
+ /** `null` animates the line out and renders nothing. */
12
+ readonly label: string | null;
13
+ readonly className?: string;
14
+ };
15
+ /**
16
+ * Layout-stable wrapper for `LiveStatusLine`: a `null` label collapses the
17
+ * slot's height instead of the line disappearing instantly, so the body
18
+ * below it doesn't jump at every gate-to-processing boundary. The transition
19
+ * is a plain CSS grid-rows trick — no animation library — and
20
+ * `prefers-reduced-motion` disables it globally via the theme's base layer.
21
+ */
22
+ export declare function LiveStatusSlot({ label, className }: LiveStatusSlotProps): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ /** The single "what's happening now" line under a stepper. A spinning ring
4
+ * plus text, not colour alone — `motion-reduce:animate-none` stills the
5
+ * spinner without hiding the line, since the words already carry the
6
+ * meaning. */ export function LiveStatusLine({ label, className }) {
7
+ return /*#__PURE__*/ _jsxs("div", {
8
+ role: "status",
9
+ "aria-live": "polite",
10
+ className: cn("flex items-center gap-2 border-b border-border bg-card px-6 py-2.5 text-sm text-muted-foreground", className),
11
+ children: [
12
+ /*#__PURE__*/ _jsx("span", {
13
+ "aria-hidden": true,
14
+ className: "size-3 animate-spin rounded-full border-2 border-border border-t-primary-emphasis motion-reduce:animate-none"
15
+ }),
16
+ label,
17
+ "…"
18
+ ]
19
+ });
20
+ }
21
+ /**
22
+ * Layout-stable wrapper for `LiveStatusLine`: a `null` label collapses the
23
+ * slot's height instead of the line disappearing instantly, so the body
24
+ * below it doesn't jump at every gate-to-processing boundary. The transition
25
+ * is a plain CSS grid-rows trick — no animation library — and
26
+ * `prefers-reduced-motion` disables it globally via the theme's base layer.
27
+ */ export function LiveStatusSlot({ label, className }) {
28
+ return /*#__PURE__*/ _jsx("div", {
29
+ className: cn("grid transition-[grid-template-rows] duration-200 ease-out", label === null ? "grid-rows-[0fr]" : "grid-rows-[1fr]", className),
30
+ children: /*#__PURE__*/ _jsx("div", {
31
+ className: "overflow-hidden",
32
+ children: label === null ? null : /*#__PURE__*/ _jsx(LiveStatusLine, {
33
+ label: label
34
+ })
35
+ })
36
+ });
37
+ }
@@ -0,0 +1,18 @@
1
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
+ import type * as React from "react";
3
+ /**
4
+ * The popover menu used by toolbars and selectors.
5
+ *
6
+ * Radix owns anchored positioning, focus management, keyboard navigation and
7
+ * dismissal; this layer only paints. Every menu gets the same fully opaque
8
+ * popover surface — a see-through menu over a busy page is the bug this
9
+ * primitive exists to prevent — and the same item hover, so a menu opened from
10
+ * a toolbar and one opened from a table row read as the same control.
11
+ */
12
+ export declare const Menu: React.FC<DropdownMenuPrimitive.DropdownMenuProps>;
13
+ export declare const MenuTrigger: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuTriggerProps & React.RefAttributes<HTMLButtonElement>>;
14
+ export declare const MenuGroup: React.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuGroupProps & React.RefAttributes<HTMLDivElement>>;
15
+ export declare function MenuContent({ className, sideOffset, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>): React.JSX.Element;
16
+ export declare function MenuItem({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Item>): React.JSX.Element;
17
+ export declare function MenuLabel({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Label>): React.JSX.Element;
18
+ export declare function MenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>): React.JSX.Element;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
3
+ import { cn } from "../lib/utils.js";
4
+ /**
5
+ * The popover menu used by toolbars and selectors.
6
+ *
7
+ * Radix owns anchored positioning, focus management, keyboard navigation and
8
+ * dismissal; this layer only paints. Every menu gets the same fully opaque
9
+ * popover surface — a see-through menu over a busy page is the bug this
10
+ * primitive exists to prevent — and the same item hover, so a menu opened from
11
+ * a toolbar and one opened from a table row read as the same control.
12
+ */ export const Menu = DropdownMenuPrimitive.Root;
13
+ export const MenuTrigger = DropdownMenuPrimitive.Trigger;
14
+ export const MenuGroup = DropdownMenuPrimitive.Group;
15
+ export function MenuContent({ className, sideOffset = 4, ...props }) {
16
+ return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Portal, {
17
+ children: /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Content, {
18
+ "data-slot": "menu-content",
19
+ sideOffset: sideOffset,
20
+ className: cn("z-50 min-w-32 overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg", className),
21
+ ...props
22
+ })
23
+ });
24
+ }
25
+ export function MenuItem({ className, ...props }) {
26
+ return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Item, {
27
+ "data-slot": "menu-item",
28
+ className: cn("flex w-full cursor-pointer items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm outline-none select-none transition-colors data-[highlighted]:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
29
+ ...props
30
+ });
31
+ }
32
+ export function MenuLabel({ className, ...props }) {
33
+ return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Label, {
34
+ "data-slot": "menu-label",
35
+ className: cn("px-2.5 py-1.5 text-xs font-semibold text-muted-foreground", className),
36
+ ...props
37
+ });
38
+ }
39
+ export function MenuSeparator({ className, ...props }) {
40
+ return /*#__PURE__*/ _jsx(DropdownMenuPrimitive.Separator, {
41
+ "data-slot": "menu-separator",
42
+ className: cn("my-1 h-px bg-border", className),
43
+ ...props
44
+ });
45
+ }
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ChatMessage } from "../lib/chat-message.js";
3
+ export type MessageBubbleProps = {
4
+ readonly message: ChatMessage;
5
+ /** Retry affordance for a failed send. Omit and a failure is only reported. */
6
+ readonly onRetry?: () => void;
7
+ readonly now?: number;
8
+ /** Rich body — a markdown renderer. Defaults to the message's plain text. */
9
+ readonly children?: ReactNode;
10
+ readonly className?: string;
11
+ };
12
+ /**
13
+ * One message, as said by whoever said it.
14
+ *
15
+ * Only the user's messages get a filled bubble. An agent reply is set on the
16
+ * page ground with no container: agent turns are long, and a 600-word tinted
17
+ * block is harder to read than plain text, while the alternating alignment that
18
+ * makes chat apps legible comes from the user side alone.
19
+ *
20
+ * Text is rendered as text. Passing agent output through a markdown or HTML
21
+ * renderer is the host's decision to make, with the host's sanitiser — pass one
22
+ * as `children`.
23
+ */
24
+ export declare function MessageBubble({ message, onRetry, now, children, className }: MessageBubbleProps): import("react").JSX.Element;
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { messageText } from "../lib/chat-message.js";
3
+ import { formatRelativeTime } from "../lib/relative-time.js";
4
+ import { cn } from "../lib/utils.js";
5
+ /**
6
+ * One message, as said by whoever said it.
7
+ *
8
+ * Only the user's messages get a filled bubble. An agent reply is set on the
9
+ * page ground with no container: agent turns are long, and a 600-word tinted
10
+ * block is harder to read than plain text, while the alternating alignment that
11
+ * makes chat apps legible comes from the user side alone.
12
+ *
13
+ * Text is rendered as text. Passing agent output through a markdown or HTML
14
+ * renderer is the host's decision to make, with the host's sanitiser — pass one
15
+ * as `children`.
16
+ */ export function MessageBubble({ message, onRetry, now, children, className }) {
17
+ const isUser = message.role === "user";
18
+ const isSystem = message.role === "system";
19
+ const failed = message.status === "failed";
20
+ if (isSystem) {
21
+ return /*#__PURE__*/ _jsx("p", {
22
+ "data-slot": "message-bubble",
23
+ className: cn("px-2 py-1 text-center text-xs text-muted-foreground", className),
24
+ children: children ?? messageText(message)
25
+ });
26
+ }
27
+ return /*#__PURE__*/ _jsxs("div", {
28
+ "data-slot": "message-bubble",
29
+ className: cn("flex flex-col gap-1", isUser ? "items-end" : "items-start", className),
30
+ children: [
31
+ /*#__PURE__*/ _jsx("div", {
32
+ className: cn("max-w-[46rem] text-sm leading-relaxed break-words whitespace-pre-wrap", isUser && "rounded-lg bg-muted px-3 py-2", failed && "opacity-60"),
33
+ children: children ?? messageText(message)
34
+ }),
35
+ /*#__PURE__*/ _jsxs("div", {
36
+ className: "flex items-center gap-2 text-[11px] text-muted-foreground",
37
+ children: [
38
+ /*#__PURE__*/ _jsx("time", {
39
+ dateTime: message.createdAt,
40
+ children: formatRelativeTime(message.createdAt, now)
41
+ }),
42
+ message.status === "sending" ? /*#__PURE__*/ _jsx("span", {
43
+ children: "Sending…"
44
+ }) : null,
45
+ failed ? /*#__PURE__*/ _jsxs(_Fragment, {
46
+ children: [
47
+ /*#__PURE__*/ _jsx("span", {
48
+ role: "alert",
49
+ className: "text-destructive",
50
+ children: "Not sent"
51
+ }),
52
+ onRetry === undefined ? null : /*#__PURE__*/ _jsx("button", {
53
+ type: "button",
54
+ onClick: onRetry,
55
+ className: "text-primary-emphasis hover:underline",
56
+ children: "Retry"
57
+ })
58
+ ]
59
+ }) : null
60
+ ]
61
+ })
62
+ ]
63
+ });
64
+ }
@@ -0,0 +1,35 @@
1
+ export type DictationState = "idle" | "starting" | "listening" | "denied" | "unsupported";
2
+ export type MicButtonProps = {
3
+ readonly state: DictationState;
4
+ readonly onToggle: () => void;
5
+ readonly disabled?: boolean;
6
+ readonly className?: string;
7
+ };
8
+ /**
9
+ * A microphone toggle for dictation, presentational only.
10
+ *
11
+ * This component and its siblings (`VoiceWaveform`, `DictationStatusLine`,
12
+ * `MicPermissionDialog`) carry no `SpeechRecognition`, Tauri, or media-device
13
+ * code — react-ui never imports a browser or native capture API. A consumer
14
+ * wires them to a backend through a hook shaped roughly like:
15
+ *
16
+ * ```ts
17
+ * type UseDictation = (value: string, onValueChange: (value: string) => void) => {
18
+ * supported: boolean;
19
+ * state: DictationState;
20
+ * levels: number[]; // recent RMS samples, 0..1, oldest first
21
+ * errorMessage: string | null; // set when state is "denied"
22
+ * permissionHelp: { title: string; detail: string; action: string } | null;
23
+ * start: () => void;
24
+ * stop: () => void;
25
+ * dismiss: () => void;
26
+ * };
27
+ * ```
28
+ *
29
+ * `state` drives the pressed look, the glyph, and the label. Listening is
30
+ * green with a stop square so colour is never the only channel; starting
31
+ * stays the idle mic so it cannot be mistaken for a live capture.
32
+ * `unsupported` is disabled. `denied` stays clickable so the host can open
33
+ * `MicPermissionDialog` from `onToggle`.
34
+ */
35
+ export declare function MicButton({ state, onToggle, disabled, className }: MicButtonProps): import("react").JSX.Element;
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Mic, MicOff, Square } from "lucide-react";
3
+ import { cn } from "../lib/utils.js";
4
+ function labelFor(state) {
5
+ if (state === "denied") return "Microphone blocked";
6
+ if (state === "unsupported") return "Dictation unavailable";
7
+ if (state === "listening") return "Stop dictating";
8
+ if (state === "starting") return "Starting microphone";
9
+ return "Dictate instead of typing";
10
+ }
11
+ /**
12
+ * A microphone toggle for dictation, presentational only.
13
+ *
14
+ * This component and its siblings (`VoiceWaveform`, `DictationStatusLine`,
15
+ * `MicPermissionDialog`) carry no `SpeechRecognition`, Tauri, or media-device
16
+ * code — react-ui never imports a browser or native capture API. A consumer
17
+ * wires them to a backend through a hook shaped roughly like:
18
+ *
19
+ * ```ts
20
+ * type UseDictation = (value: string, onValueChange: (value: string) => void) => {
21
+ * supported: boolean;
22
+ * state: DictationState;
23
+ * levels: number[]; // recent RMS samples, 0..1, oldest first
24
+ * errorMessage: string | null; // set when state is "denied"
25
+ * permissionHelp: { title: string; detail: string; action: string } | null;
26
+ * start: () => void;
27
+ * stop: () => void;
28
+ * dismiss: () => void;
29
+ * };
30
+ * ```
31
+ *
32
+ * `state` drives the pressed look, the glyph, and the label. Listening is
33
+ * green with a stop square so colour is never the only channel; starting
34
+ * stays the idle mic so it cannot be mistaken for a live capture.
35
+ * `unsupported` is disabled. `denied` stays clickable so the host can open
36
+ * `MicPermissionDialog` from `onToggle`.
37
+ */ export function MicButton({ state, onToggle, disabled = false, className }) {
38
+ const listening = state === "listening";
39
+ const denied = state === "denied";
40
+ const unusable = disabled || state === "unsupported";
41
+ const label = labelFor(state);
42
+ const Glyph = listening ? Square : denied ? MicOff : Mic;
43
+ return /*#__PURE__*/ _jsx("button", {
44
+ type: "button",
45
+ // Denied opens the permission dialog rather than toggling — declare the
46
+ // popup and drop the toggle semantic.
47
+ "aria-pressed": denied ? undefined : listening,
48
+ "aria-haspopup": denied ? "dialog" : undefined,
49
+ "aria-label": label,
50
+ disabled: unusable,
51
+ // Tapping the mic must not blur the field being dictated into — a field
52
+ // that commits on blur would lose the edit to its own microphone tap.
53
+ onMouseDown: (event)=>event.preventDefault(),
54
+ onClick: onToggle,
55
+ className: cn("grid size-9 shrink-0 place-items-center rounded-md transition-colors disabled:opacity-50", !unusable && "active:brightness-95 motion-safe:active:scale-[0.97]", listening ? "bg-success text-success-foreground hover:bg-success/90" : "text-muted-foreground hover:bg-muted hover:text-foreground", className),
56
+ children: /*#__PURE__*/ _jsx(Glyph, {
57
+ className: listening ? "size-3.5 fill-current" : "size-4",
58
+ "aria-hidden": true
59
+ })
60
+ });
61
+ }
@@ -0,0 +1,20 @@
1
+ export type MicPermissionDialogProps = {
2
+ readonly open: boolean;
3
+ readonly onOpenChange: (open: boolean) => void;
4
+ readonly title: string;
5
+ readonly detail: string;
6
+ /** Label for the action button, e.g. "Open Sound settings". */
7
+ readonly actionLabel: string;
8
+ readonly onAction: () => void;
9
+ /** Shown when opening the settings pane itself failed. */
10
+ readonly failure?: string | null;
11
+ readonly dismissLabel?: string;
12
+ };
13
+ /**
14
+ * A refusal that is a setting, put in front of the person with the button
15
+ * that opens the pane. Presentational: it does not know what "open settings"
16
+ * means for a given host (a native shell invoking an OS pane, a browser
17
+ * opening a help link) — the consumer's dictation hook supplies `onAction`
18
+ * and, if that action can itself fail, `failure`.
19
+ */
20
+ export declare function MicPermissionDialog({ open, onOpenChange, title, detail, actionLabel, onAction, failure, dismissLabel, }: MicPermissionDialogProps): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "./button.js";
3
+ import { Dialog, DialogBody, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./dialog.js";
4
+ /**
5
+ * A refusal that is a setting, put in front of the person with the button
6
+ * that opens the pane. Presentational: it does not know what "open settings"
7
+ * means for a given host (a native shell invoking an OS pane, a browser
8
+ * opening a help link) — the consumer's dictation hook supplies `onAction`
9
+ * and, if that action can itself fail, `failure`.
10
+ */ export function MicPermissionDialog({ open, onOpenChange, title, detail, actionLabel, onAction, failure = null, dismissLabel = "Not now" }) {
11
+ return /*#__PURE__*/ _jsx(Dialog, {
12
+ open: open,
13
+ onOpenChange: onOpenChange,
14
+ children: /*#__PURE__*/ _jsxs(DialogContent, {
15
+ children: [
16
+ /*#__PURE__*/ _jsxs(DialogHeader, {
17
+ children: [
18
+ /*#__PURE__*/ _jsx(DialogTitle, {
19
+ children: title
20
+ }),
21
+ /*#__PURE__*/ _jsx(DialogDescription, {
22
+ children: detail
23
+ })
24
+ ]
25
+ }),
26
+ failure ? /*#__PURE__*/ _jsx(DialogBody, {
27
+ children: /*#__PURE__*/ _jsx("p", {
28
+ role: "alert",
29
+ className: "text-sm text-destructive",
30
+ children: failure
31
+ })
32
+ }) : null,
33
+ /*#__PURE__*/ _jsxs(DialogFooter, {
34
+ children: [
35
+ /*#__PURE__*/ _jsx(Button, {
36
+ variant: "ghost",
37
+ onClick: ()=>onOpenChange(false),
38
+ children: dismissLabel
39
+ }),
40
+ /*#__PURE__*/ _jsx(Button, {
41
+ variant: "primary",
42
+ onClick: onAction,
43
+ children: actionLabel
44
+ })
45
+ ]
46
+ })
47
+ ]
48
+ })
49
+ });
50
+ }
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from "react";
2
+ export type NotificationsBellProps = {
3
+ /** Unread count. `0` renders the bell with no marker. */
4
+ readonly count: number;
5
+ /** Panel contents — a notification list, an empty state, whatever the host has. */
6
+ readonly children: ReactNode;
7
+ /** Counts above this render as "N+" so the marker cannot grow unbounded. */
8
+ readonly maxCount?: number;
9
+ /** Controlled open state. Pair with `onOpenChange` to lift it to a parent. */
10
+ readonly open?: boolean;
11
+ readonly onOpenChange?: (open: boolean) => void;
12
+ readonly className?: string;
13
+ };
14
+ /**
15
+ * Bell + count, with the panel body as a slot. It renders a count and opens a
16
+ * panel; it does not know what a notification is, does not fetch, and does not
17
+ * mark anything read — the host puts a list in `children` and handles the rest.
18
+ *
19
+ * The panel is a `dialog`-role popup rather than a menu: its contents are
20
+ * arbitrary (links, buttons, an empty state), and a menu role would promise
21
+ * arrow-key semantics that a free-form list does not have. Escape closes and
22
+ * returns focus to the bell; a pointer-down outside closes without stealing
23
+ * focus back, so clicking straight into another control still works.
24
+ *
25
+ * Uncontrolled by default. Pass `open`/`onOpenChange` to drive it from a
26
+ * parent — closing every other panel when this one opens, say.
27
+ */
28
+ export declare function NotificationsBell({ count, children, maxCount, open: openProp, onOpenChange, className, }: NotificationsBellProps): import("react").JSX.Element;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Bell } from "lucide-react";
3
+ import { useEffect, useId, useRef } from "react";
4
+ import { useDismissablePopover } from "../hooks/use-dismissable-popover.js";
5
+ import { cn } from "../lib/utils.js";
6
+ /**
7
+ * Bell + count, with the panel body as a slot. It renders a count and opens a
8
+ * panel; it does not know what a notification is, does not fetch, and does not
9
+ * mark anything read — the host puts a list in `children` and handles the rest.
10
+ *
11
+ * The panel is a `dialog`-role popup rather than a menu: its contents are
12
+ * arbitrary (links, buttons, an empty state), and a menu role would promise
13
+ * arrow-key semantics that a free-form list does not have. Escape closes and
14
+ * returns focus to the bell; a pointer-down outside closes without stealing
15
+ * focus back, so clicking straight into another control still works.
16
+ *
17
+ * Uncontrolled by default. Pass `open`/`onOpenChange` to drive it from a
18
+ * parent — closing every other panel when this one opens, say.
19
+ */ export function NotificationsBell({ count, children, maxCount = 99, open: openProp, onOpenChange, className }) {
20
+ const { open, setOpen, rootRef, triggerRef } = useDismissablePopover({
21
+ open: openProp,
22
+ onOpenChange
23
+ });
24
+ const panelRef = useRef(null);
25
+ const panelId = useId();
26
+ useEffect(()=>{
27
+ if (!open) return;
28
+ panelRef.current?.focus();
29
+ }, [
30
+ open
31
+ ]);
32
+ const label = count === 0 ? "Notifications" : `Notifications, ${count} unread`;
33
+ return /*#__PURE__*/ _jsxs("div", {
34
+ ref: rootRef,
35
+ className: cn("relative", className),
36
+ children: [
37
+ /*#__PURE__*/ _jsxs("button", {
38
+ ref: triggerRef,
39
+ type: "button",
40
+ "aria-haspopup": "dialog",
41
+ "aria-expanded": open,
42
+ "aria-controls": open ? panelId : undefined,
43
+ "aria-label": label,
44
+ onClick: ()=>setOpen((value)=>!value),
45
+ className: "relative grid size-9 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
46
+ children: [
47
+ /*#__PURE__*/ _jsx(Bell, {
48
+ className: "size-4",
49
+ "aria-hidden": true
50
+ }),
51
+ count > 0 ? /*#__PURE__*/ _jsx("span", {
52
+ "aria-hidden": true,
53
+ className: "absolute top-1 right-1 min-w-4 rounded-full bg-primary px-1 text-center font-mono text-[10px] leading-4 text-primary-foreground",
54
+ children: count > maxCount ? `${maxCount}+` : count
55
+ }) : null
56
+ ]
57
+ }),
58
+ open ? /*#__PURE__*/ _jsx("div", {
59
+ ref: panelRef,
60
+ id: panelId,
61
+ role: "dialog",
62
+ "aria-label": label,
63
+ tabIndex: -1,
64
+ className: "absolute top-full right-0 z-50 mt-1 max-h-96 w-80 overflow-y-auto rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-lg",
65
+ children: children
66
+ }) : null
67
+ ]
68
+ });
69
+ }
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from "react";
2
+ import { type NowItem } from "../lib/now-item.js";
3
+ export type NowSectionProps = {
4
+ /** How many things are asking for attention — drives the count sentence. */
5
+ readonly count: number;
6
+ /**
7
+ * Tucks the body away and leaves a one-line summary. For when a detail pane
8
+ * is open and the vertical space is worth more to it than to the strip.
9
+ */
10
+ readonly collapsed?: boolean;
11
+ readonly children: ReactNode;
12
+ readonly className?: string;
13
+ };
14
+ /**
15
+ * The attention band at the top of the inbox. Its own container rather than the
16
+ * generic `Section` because it says something `Section` cannot: an eyebrow that
17
+ * marks urgency, a count in words, and a collapsed mode.
18
+ *
19
+ * Collapsing keeps the body mounted (so it can animate, and so anchors into it
20
+ * still resolve) but marks it `inert` — `aria-hidden` alone would hide it from
21
+ * a screen reader while leaving every card in the tab order.
22
+ */
23
+ export declare function NowSection({ count, collapsed, children, className }: NowSectionProps): import("react").JSX.Element;
24
+ export type NowCardsProps = {
25
+ readonly items: readonly NowItem[];
26
+ /** Cap on cards shown. The rest belong in the queue below, not here. */
27
+ readonly limit?: number;
28
+ readonly selectedId?: string | null;
29
+ readonly now?: number;
30
+ readonly className?: string;
31
+ };
32
+ /**
33
+ * The highest-attention items as cards. Ordering is not the caller's problem —
34
+ * the list is sorted here by priority then recency, so two surfaces fed the
35
+ * same items always agree on what is most urgent.
36
+ */
37
+ export declare function NowCards({ items, limit, selectedId, now, className }: NowCardsProps): import("react").JSX.Element | null;