@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,140 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Check } from "lucide-react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { ArtifactNotice } from "./artifact-notice.js";
5
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
6
+ /**
7
+ * Reads a `Comparison` out of a JSON string, or returns `null`.
8
+ *
9
+ * Structural, not schema-driven: the registry ships no validation dependency,
10
+ * and this only has to be strict enough that a malformed payload falls back to
11
+ * showing the raw text rather than rendering half a comparison. It checks the
12
+ * fields it will actually index and nothing else — over-validating a viewer's
13
+ * input rejects payloads it could have drawn perfectly well.
14
+ */ export function parseComparison(json) {
15
+ let raw;
16
+ try {
17
+ raw = JSON.parse(json);
18
+ } catch {
19
+ return null;
20
+ }
21
+ if (typeof raw !== "object" || raw === null) return null;
22
+ const variants = raw.variants;
23
+ if (!Array.isArray(variants) || variants.length === 0) return null;
24
+ const ok = variants.every((variant)=>typeof variant === "object" && variant !== null && typeof variant.id === "string" && typeof variant.label === "string" && typeof variant.content === "string");
25
+ return ok ? raw : null;
26
+ }
27
+ /**
28
+ * Two or more variants of the same thing, side by side, with the verdict said
29
+ * out loud.
30
+ *
31
+ * The winner is announced three ways — a text badge, a `<mark>`-free bold
32
+ * label, and an `aria-label` on the column — and only incidentally by a tinted
33
+ * border. Colour cannot be the encoding here: "which one won" is the single
34
+ * fact the surface exists to communicate, and a reader who cannot see the tint
35
+ * would otherwise get a page of undifferentiated columns.
36
+ *
37
+ * Columns collapse to a stack below `md`. Side-by-side is the whole idea, but a
38
+ * 320px viewport showing two 140px columns compares nothing; stacked variants
39
+ * with their labels intact at least remain readable.
40
+ */ export function CompareBody({ comparison, className }) {
41
+ const { variants, winnerId, rationale, criteria } = comparison;
42
+ return /*#__PURE__*/ _jsxs("div", {
43
+ className: cn("flex flex-col gap-6", className),
44
+ children: [
45
+ rationale === undefined ? null : /*#__PURE__*/ _jsx("p", {
46
+ className: "max-w-prose text-sm leading-relaxed text-muted-foreground",
47
+ children: rationale
48
+ }),
49
+ /*#__PURE__*/ _jsx("div", {
50
+ className: "grid gap-4 md:grid-cols-2",
51
+ children: variants.map((variant)=>{
52
+ const won = variant.id === winnerId;
53
+ return /*#__PURE__*/ _jsxs("section", {
54
+ "aria-label": won ? `${variant.label} — selected` : variant.label,
55
+ className: cn("flex flex-col gap-3 rounded-lg border p-4", won ? "border-primary-emphasis bg-card" : "border-border"),
56
+ children: [
57
+ /*#__PURE__*/ _jsxs("div", {
58
+ className: "flex items-center gap-2",
59
+ children: [
60
+ /*#__PURE__*/ _jsx("h3", {
61
+ className: "text-sm font-semibold",
62
+ children: variant.label
63
+ }),
64
+ won ? /*#__PURE__*/ _jsxs("span", {
65
+ className: "inline-flex items-center gap-1 rounded-sm bg-muted px-1.5 py-0.5 text-xs font-medium text-primary-emphasis",
66
+ children: [
67
+ /*#__PURE__*/ _jsx(Check, {
68
+ className: "size-3",
69
+ "aria-hidden": true
70
+ }),
71
+ "Selected"
72
+ ]
73
+ }) : null
74
+ ]
75
+ }),
76
+ /*#__PURE__*/ _jsx("p", {
77
+ className: "text-sm leading-relaxed whitespace-pre-wrap",
78
+ children: variant.content
79
+ })
80
+ ]
81
+ }, variant.id);
82
+ })
83
+ }),
84
+ criteria === undefined || criteria.length === 0 ? null : /*#__PURE__*/ _jsxs(Table, {
85
+ "aria-label": "Scores by criterion",
86
+ children: [
87
+ /*#__PURE__*/ _jsx(TableHeader, {
88
+ children: /*#__PURE__*/ _jsxs(TableRow, {
89
+ children: [
90
+ /*#__PURE__*/ _jsx(TableHead, {
91
+ scope: "col",
92
+ children: "Criterion"
93
+ }),
94
+ variants.map((variant)=>/*#__PURE__*/ _jsx(TableHead, {
95
+ scope: "col",
96
+ className: "text-right",
97
+ children: variant.label
98
+ }, variant.id))
99
+ ]
100
+ })
101
+ }),
102
+ /*#__PURE__*/ _jsx(TableBody, {
103
+ children: criteria.map((criterion)=>/*#__PURE__*/ _jsxs(TableRow, {
104
+ children: [
105
+ /*#__PURE__*/ _jsx(TableCell, {
106
+ className: "font-medium",
107
+ children: criterion.label
108
+ }),
109
+ variants.map((variant)=>{
110
+ const score = criterion.scores[variant.id];
111
+ return /*#__PURE__*/ _jsx(TableCell, {
112
+ className: "text-right font-mono tabular-nums",
113
+ children: score === undefined ? "—" : score.toLocaleString()
114
+ }, variant.id);
115
+ })
116
+ ]
117
+ }, criterion.label))
118
+ })
119
+ ]
120
+ })
121
+ ]
122
+ });
123
+ }
124
+ /** The JSON-string entry point, for when the payload arrives as artifact content. */ export function CompareBodyFromJson({ content, pending }) {
125
+ if (content.trim() === "") return /*#__PURE__*/ _jsx(ArtifactNotice, {
126
+ pending: pending
127
+ });
128
+ const comparison = parseComparison(content);
129
+ if (comparison === null) {
130
+ // The payload is not a comparison. Showing it verbatim beats an error: the
131
+ // content is still the artifact, and the user can read it.
132
+ return /*#__PURE__*/ _jsx("pre", {
133
+ className: "overflow-auto rounded-md border border-border bg-muted p-3 font-mono text-xs",
134
+ children: content
135
+ });
136
+ }
137
+ return /*#__PURE__*/ _jsx(CompareBody, {
138
+ comparison: comparison
139
+ });
140
+ }
@@ -0,0 +1,29 @@
1
+ import type { ReactNode } from "react";
2
+ import { type ButtonProps } from "./button.js";
3
+ export type ConfirmButtonProps = Omit<ButtonProps, "onClick" | "children"> & {
4
+ /** Fires on the second, confirming click only. */
5
+ readonly onConfirm: () => void;
6
+ readonly children: ReactNode;
7
+ readonly confirmLabel?: ReactNode;
8
+ /** How long the armed state lasts before disarming, in ms. */
9
+ readonly resetMs?: number;
10
+ /**
11
+ * Start already armed. Uncontrolled — it only seeds the initial `useState`,
12
+ * so it has no effect after mount. For stories and tests that need to show
13
+ * or exercise the armed state without simulating a first click.
14
+ */
15
+ readonly defaultArmed?: boolean;
16
+ };
17
+ /**
18
+ * A destructive action behind a second click.
19
+ *
20
+ * For actions that are irreversible but small — remove a row, revoke a key —
21
+ * where a modal is more ceremony than the action deserves. Anything with
22
+ * consequences worth reading a sentence about should use a `Dialog` instead;
23
+ * this deliberately gives the user nothing to read.
24
+ *
25
+ * It disarms on blur as well as on a timer. Leaving a primed destructive button
26
+ * behind when the user's attention has moved elsewhere is how the second click
27
+ * lands by accident, which is the exact thing this exists to prevent.
28
+ */
29
+ export declare function ConfirmButton({ onConfirm, children, confirmLabel, resetMs, defaultArmed, variant, ...props }: ConfirmButtonProps): import("react").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { Button } from "./button.js";
4
+ /**
5
+ * A destructive action behind a second click.
6
+ *
7
+ * For actions that are irreversible but small — remove a row, revoke a key —
8
+ * where a modal is more ceremony than the action deserves. Anything with
9
+ * consequences worth reading a sentence about should use a `Dialog` instead;
10
+ * this deliberately gives the user nothing to read.
11
+ *
12
+ * It disarms on blur as well as on a timer. Leaving a primed destructive button
13
+ * behind when the user's attention has moved elsewhere is how the second click
14
+ * lands by accident, which is the exact thing this exists to prevent.
15
+ */ export function ConfirmButton({ onConfirm, children, confirmLabel = "Click again to confirm", resetMs = 3000, defaultArmed = false, variant = "outline", ...props }) {
16
+ const [armed, setArmed] = useState(defaultArmed);
17
+ const timerRef = useRef(undefined);
18
+ const disarm = ()=>{
19
+ window.clearTimeout(timerRef.current);
20
+ setArmed(false);
21
+ };
22
+ useEffect(()=>()=>window.clearTimeout(timerRef.current), []);
23
+ return /*#__PURE__*/ _jsx(Button, {
24
+ ...props,
25
+ variant: armed ? "destructive" : variant,
26
+ onBlur: disarm,
27
+ onClick: ()=>{
28
+ if (!armed) {
29
+ setArmed(true);
30
+ timerRef.current = window.setTimeout(()=>setArmed(false), resetMs);
31
+ return;
32
+ }
33
+ disarm();
34
+ onConfirm();
35
+ },
36
+ children: /*#__PURE__*/ _jsx("span", {
37
+ "aria-live": "polite",
38
+ children: armed ? confirmLabel : children
39
+ })
40
+ });
41
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The Corbits mountain mark.
3
+ *
4
+ * The path and viewBox are exported separately from the component so a non-SVG
5
+ * consumer — a canvas splash, an OG-image generator — can build the same
6
+ * geometry from the same source instead of tracing its own copy.
7
+ *
8
+ * It fills with `currentColor`, so it takes the colour of whatever it sits in
9
+ * and needs no dark-mode variant. Size it with a class; there is no `size` prop
10
+ * because `className` already does that job in a Tailwind codebase.
11
+ */
12
+ export declare const CORBITS_MARK_PATH = "M392.899 189.107L397.586 197.031C397.586 197.031 399.539 202.891 399.539 204.844L403.094 222.422C407 222.813 407.39 226.328 409.734 227.109C412.078 228.281 415.203 231.797 416.765 235.313C417.156 236.875 418.718 240.781 420.671 244.688C422.234 247.422 422.625 250.156 424.578 251.719L426.921 254.062C432.39 258.359 432.781 261.484 433.171 272.422C432.781 280.625 434.734 295.078 435.906 301.328C436.296 302.891 436.296 302.891 437.468 303.281C438.25 304.063 437.078 302.891 437.859 303.281C439.031 304.063 443.328 304.844 449.187 307.188C451.921 307.969 454.265 309.141 455.046 313.828C456.609 320.469 464.421 350.938 467.156 369.688L468.328 385.313L447.977 371.026C443.68 353.057 435.867 327.667 433.914 323.76C431.57 321.026 423.758 320.245 419.461 319.463C416.336 319.073 413.992 317.12 412.43 312.042C412.43 308.135 411.649 301.495 410.086 292.51C408.914 285.088 408.914 281.182 408.914 277.276C408.914 272.198 406.57 270.245 400.32 263.995L397.977 259.307C391.336 246.807 390.945 244.854 387.039 244.073C385.376 244.024 384.994 238.473 383.562 235.313C383.562 234.141 384.734 231.016 383.562 225.156L368.288 212.656C361.648 212.266 357.352 225.825 353.445 232.466L348.367 244.966L342.899 261.372L341.727 263.326L340.555 266.06L330.008 281.294L326.102 287.544L324.93 289.107L317.117 299.263L308.524 309.029L291.727 327.779L287.039 334.029L284.305 335.982L270.242 352.779L267.508 355.513L265.555 358.638L264.383 359.81L256.18 368.404L248.367 375.044L246.805 376.607L241.336 380.904C238.602 384.029 231.57 384.531 228.055 384.531C225.71 382.578 216.248 381.938 217.508 379.62L226.883 372.588L228.836 371.026L234.695 364.776L237.039 362.432L242.508 357.354C247.195 352.667 251.102 349.151 253.055 340.948C254.227 336.651 264.383 326.104 268.289 321.807L269.07 321.417L295.242 293.682L315.273 261.372L318.397 253.56C320.35 249.654 320.741 247.086 321.522 244.464C322.013 242.815 326.6 216.563 324.93 212.656C323.26 208.75 332.088 195.551 326.209 189.721C323.866 187.768 320.35 185.815 318.397 183.862C313.507 178.972 300.038 175.659 301.6 169.409L300.711 159.81C303.445 148.482 306.57 135.982 303.445 135.982C301.492 135.982 290.945 149.654 286.258 155.513L285.086 156.685C276.883 168.013 266.336 177.779 258.133 191.841L242.899 218.404L237.039 226.997C235.476 228.281 229.617 232.188 227.663 232.188C221.804 228.281 222.586 225.825 217.508 234.419L203.055 260.201L201.492 264.107L200.711 265.279L197.977 270.357L196.414 273.872L182.742 296.919L173.758 314.107L170.242 319.185L168.68 321.919L167.117 323.984L163.992 327.779C163.958 327.968 156.476 332.069 155.399 331.797C154.321 331.525 148.835 329.953 147.586 327.779C146.337 325.605 148.281 323.835 149.149 322.7C154.227 316.06 153.445 310.313 153.445 308.359C153.445 306.406 151.492 298.594 151.492 296.641C151.492 294.688 151.102 294.688 151.492 284.922C152.274 273.594 143.289 274.654 144.852 265.279C145.633 259.029 146.414 251.216 143.289 253.56C141.727 253.56 129.617 266.841 124.149 273.482L114.774 285.982L99.5392 305.122L96.0236 308.247L90.5548 313.716L87.4298 315.669C84.6954 319.966 77.2736 323.091 73.3673 329.732L67.1173 340.279L64.7736 344.966L61.6486 349.654L60.4767 350.826L57.7423 355.513L55.3986 358.247L42.1173 375.826L35.0861 383.247C33.7739 384.117 33.1434 384.48 32.3517 384.531C31.5599 384.582 32.3525 374.383 32.3525 374.383L33.1329 370.859L33.9142 369.185C36.6486 363.325 41.3361 357.076 46.0236 350.826C51.1017 343.404 57.3517 329.732 63.6017 320.747L68.6798 316.06L75.3204 308.247C78.0548 306.294 81.9611 301.997 86.2579 296.529L87.8204 295.357L94.8517 286.763C100.711 278.56 116.727 261.372 126.492 251.997L128.055 249.766L130.789 247.31C137.43 241.451 143.68 233.247 148.758 233.247C156.961 233.638 162.43 240.279 167.117 244.966L184.305 261.372C185.867 262.935 187.43 262.154 188.211 260.591C200.32 237.935 210.867 216.841 219.461 216.06H226.492L229.617 213.716C236.258 202.779 244.07 187.935 250.711 178.169C258.524 167.622 283.914 134.81 298.758 121.138C302.274 117.232 305.008 115.781 307.742 115C311.649 115 315.555 116.841 319.07 119.575C333.524 131.685 356.961 157.857 366.336 168.404L372.195 173.091L387.039 181.294L390.556 185.313L392.899 189.107Z";
13
+ /** The mark's viewBox, exported so non-SVG consumers can map path space to pixels. */
14
+ export declare const CORBITS_MARK_VIEWBOX: {
15
+ readonly x: 32;
16
+ readonly y: 115;
17
+ readonly width: 437;
18
+ readonly height: 270;
19
+ };
20
+ export declare function CorbitsMark({ title, decorative, className, }: {
21
+ /** Accessible name. Ignored when `decorative`. */
22
+ title?: string;
23
+ /**
24
+ * The mark is beside text that already says the name. Hides it from assistive
25
+ * tech rather than reading the brand twice in a row.
26
+ */
27
+ decorative?: boolean;
28
+ className?: string;
29
+ }): import("react").JSX.Element;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ /**
4
+ * The Corbits mountain mark.
5
+ *
6
+ * The path and viewBox are exported separately from the component so a non-SVG
7
+ * consumer — a canvas splash, an OG-image generator — can build the same
8
+ * geometry from the same source instead of tracing its own copy.
9
+ *
10
+ * It fills with `currentColor`, so it takes the colour of whatever it sits in
11
+ * and needs no dark-mode variant. Size it with a class; there is no `size` prop
12
+ * because `className` already does that job in a Tailwind codebase.
13
+ */ export const CORBITS_MARK_PATH = "M392.899 189.107L397.586 197.031C397.586 197.031 399.539 202.891 399.539 204.844L403.094 222.422C407 222.813 407.39 226.328 409.734 227.109C412.078 228.281 415.203 231.797 416.765 235.313C417.156 236.875 418.718 240.781 420.671 244.688C422.234 247.422 422.625 250.156 424.578 251.719L426.921 254.062C432.39 258.359 432.781 261.484 433.171 272.422C432.781 280.625 434.734 295.078 435.906 301.328C436.296 302.891 436.296 302.891 437.468 303.281C438.25 304.063 437.078 302.891 437.859 303.281C439.031 304.063 443.328 304.844 449.187 307.188C451.921 307.969 454.265 309.141 455.046 313.828C456.609 320.469 464.421 350.938 467.156 369.688L468.328 385.313L447.977 371.026C443.68 353.057 435.867 327.667 433.914 323.76C431.57 321.026 423.758 320.245 419.461 319.463C416.336 319.073 413.992 317.12 412.43 312.042C412.43 308.135 411.649 301.495 410.086 292.51C408.914 285.088 408.914 281.182 408.914 277.276C408.914 272.198 406.57 270.245 400.32 263.995L397.977 259.307C391.336 246.807 390.945 244.854 387.039 244.073C385.376 244.024 384.994 238.473 383.562 235.313C383.562 234.141 384.734 231.016 383.562 225.156L368.288 212.656C361.648 212.266 357.352 225.825 353.445 232.466L348.367 244.966L342.899 261.372L341.727 263.326L340.555 266.06L330.008 281.294L326.102 287.544L324.93 289.107L317.117 299.263L308.524 309.029L291.727 327.779L287.039 334.029L284.305 335.982L270.242 352.779L267.508 355.513L265.555 358.638L264.383 359.81L256.18 368.404L248.367 375.044L246.805 376.607L241.336 380.904C238.602 384.029 231.57 384.531 228.055 384.531C225.71 382.578 216.248 381.938 217.508 379.62L226.883 372.588L228.836 371.026L234.695 364.776L237.039 362.432L242.508 357.354C247.195 352.667 251.102 349.151 253.055 340.948C254.227 336.651 264.383 326.104 268.289 321.807L269.07 321.417L295.242 293.682L315.273 261.372L318.397 253.56C320.35 249.654 320.741 247.086 321.522 244.464C322.013 242.815 326.6 216.563 324.93 212.656C323.26 208.75 332.088 195.551 326.209 189.721C323.866 187.768 320.35 185.815 318.397 183.862C313.507 178.972 300.038 175.659 301.6 169.409L300.711 159.81C303.445 148.482 306.57 135.982 303.445 135.982C301.492 135.982 290.945 149.654 286.258 155.513L285.086 156.685C276.883 168.013 266.336 177.779 258.133 191.841L242.899 218.404L237.039 226.997C235.476 228.281 229.617 232.188 227.663 232.188C221.804 228.281 222.586 225.825 217.508 234.419L203.055 260.201L201.492 264.107L200.711 265.279L197.977 270.357L196.414 273.872L182.742 296.919L173.758 314.107L170.242 319.185L168.68 321.919L167.117 323.984L163.992 327.779C163.958 327.968 156.476 332.069 155.399 331.797C154.321 331.525 148.835 329.953 147.586 327.779C146.337 325.605 148.281 323.835 149.149 322.7C154.227 316.06 153.445 310.313 153.445 308.359C153.445 306.406 151.492 298.594 151.492 296.641C151.492 294.688 151.102 294.688 151.492 284.922C152.274 273.594 143.289 274.654 144.852 265.279C145.633 259.029 146.414 251.216 143.289 253.56C141.727 253.56 129.617 266.841 124.149 273.482L114.774 285.982L99.5392 305.122L96.0236 308.247L90.5548 313.716L87.4298 315.669C84.6954 319.966 77.2736 323.091 73.3673 329.732L67.1173 340.279L64.7736 344.966L61.6486 349.654L60.4767 350.826L57.7423 355.513L55.3986 358.247L42.1173 375.826L35.0861 383.247C33.7739 384.117 33.1434 384.48 32.3517 384.531C31.5599 384.582 32.3525 374.383 32.3525 374.383L33.1329 370.859L33.9142 369.185C36.6486 363.325 41.3361 357.076 46.0236 350.826C51.1017 343.404 57.3517 329.732 63.6017 320.747L68.6798 316.06L75.3204 308.247C78.0548 306.294 81.9611 301.997 86.2579 296.529L87.8204 295.357L94.8517 286.763C100.711 278.56 116.727 261.372 126.492 251.997L128.055 249.766L130.789 247.31C137.43 241.451 143.68 233.247 148.758 233.247C156.961 233.638 162.43 240.279 167.117 244.966L184.305 261.372C185.867 262.935 187.43 262.154 188.211 260.591C200.32 237.935 210.867 216.841 219.461 216.06H226.492L229.617 213.716C236.258 202.779 244.07 187.935 250.711 178.169C258.524 167.622 283.914 134.81 298.758 121.138C302.274 117.232 305.008 115.781 307.742 115C311.649 115 315.555 116.841 319.07 119.575C333.524 131.685 356.961 157.857 366.336 168.404L372.195 173.091L387.039 181.294L390.556 185.313L392.899 189.107Z";
14
+ /** The mark's viewBox, exported so non-SVG consumers can map path space to pixels. */ export const CORBITS_MARK_VIEWBOX = {
15
+ x: 32,
16
+ y: 115,
17
+ width: 437,
18
+ height: 270
19
+ };
20
+ export function CorbitsMark({ title = "Corbits", decorative = false, className }) {
21
+ return /*#__PURE__*/ _jsx("svg", {
22
+ viewBox: "32 115 437 270",
23
+ fill: "none",
24
+ role: decorative ? undefined : "img",
25
+ "aria-hidden": decorative ? true : undefined,
26
+ "aria-label": decorative ? undefined : title,
27
+ className: cn("size-6", className),
28
+ children: /*#__PURE__*/ _jsx("path", {
29
+ d: CORBITS_MARK_PATH,
30
+ fill: "currentColor"
31
+ })
32
+ });
33
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * CSV text, rendered as a table when that is honest and as raw text when it is
3
+ * not.
4
+ *
5
+ * Three outcomes, in order of preference: a grid, the source text, or a refusal.
6
+ * The order is not about effort — it is about truthfulness. A grid is the most
7
+ * useful and the most capable of lying, so it is only used once the parse is
8
+ * provably rectangular; otherwise the source text is shown verbatim, where a
9
+ * ragged row is visibly ragged instead of silently shifting cells under the
10
+ * wrong headers.
11
+ *
12
+ * The parse is memoised on the text. Without it, any parent re-render — a hover,
13
+ * a filter's own state — re-walks every row of the file.
14
+ *
15
+ * Columns and rows are keyed by index, never by content. CSV rows duplicate
16
+ * freely and headers are routinely empty or repeated ("", two "Name" columns);
17
+ * a content key would collide and drop data.
18
+ */
19
+ export declare function CsvTable({ text, caption }: {
20
+ readonly text: string;
21
+ readonly caption?: string;
22
+ }): import("react").JSX.Element;
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { capCsv, isTabular, parseCsv } from "../lib/csv.js";
4
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
5
+ /**
6
+ * CSV text, rendered as a table when that is honest and as raw text when it is
7
+ * not.
8
+ *
9
+ * Three outcomes, in order of preference: a grid, the source text, or a refusal.
10
+ * The order is not about effort — it is about truthfulness. A grid is the most
11
+ * useful and the most capable of lying, so it is only used once the parse is
12
+ * provably rectangular; otherwise the source text is shown verbatim, where a
13
+ * ragged row is visibly ragged instead of silently shifting cells under the
14
+ * wrong headers.
15
+ *
16
+ * The parse is memoised on the text. Without it, any parent re-render — a hover,
17
+ * a filter's own state — re-walks every row of the file.
18
+ *
19
+ * Columns and rows are keyed by index, never by content. CSV rows duplicate
20
+ * freely and headers are routinely empty or repeated ("", two "Name" columns);
21
+ * a content key would collide and drop data.
22
+ */ export function CsvTable({ text, caption = "CSV contents" }) {
23
+ const parsed = useMemo(()=>parseCsv(text), [
24
+ text
25
+ ]);
26
+ if (parsed === null) {
27
+ return /*#__PURE__*/ _jsx("p", {
28
+ className: "rounded-md border border-border bg-muted px-4 py-6 text-center text-sm text-muted-foreground",
29
+ children: "This file is too large to preview — download it to see the whole thing."
30
+ });
31
+ }
32
+ if (!isTabular(parsed)) {
33
+ // Height-capped with its own scroll: a multi-thousand-line file as one
34
+ // <pre> is the same DOM freeze the size guard exists to prevent.
35
+ return /*#__PURE__*/ _jsx("pre", {
36
+ className: "max-h-96 overflow-auto rounded-md border border-border bg-muted p-3 font-mono text-xs",
37
+ children: text
38
+ });
39
+ }
40
+ const capped = capCsv(parsed);
41
+ const notes = [];
42
+ if (capped.rowsTruncated) {
43
+ notes.push(`${capped.rows.length.toLocaleString()} of ${capped.totalRows.toLocaleString()} rows`);
44
+ }
45
+ if (capped.columnsTruncated) {
46
+ notes.push(`${capped.headers.length.toLocaleString()} of ${capped.totalColumns.toLocaleString()} columns`);
47
+ }
48
+ return /*#__PURE__*/ _jsxs("div", {
49
+ className: "flex flex-col gap-2",
50
+ children: [
51
+ /*#__PURE__*/ _jsx("div", {
52
+ className: "max-h-[32rem] overflow-auto rounded-md border border-border",
53
+ children: /*#__PURE__*/ _jsxs(Table, {
54
+ "aria-label": caption,
55
+ children: [
56
+ /*#__PURE__*/ _jsx(TableHeader, {
57
+ children: /*#__PURE__*/ _jsx(TableRow, {
58
+ children: capped.headers.map((header, index)=>/*#__PURE__*/ _jsx(TableHead, {
59
+ scope: "col",
60
+ children: header
61
+ }, index))
62
+ })
63
+ }),
64
+ /*#__PURE__*/ _jsx(TableBody, {
65
+ children: capped.rows.map((row, rowIndex)=>/*#__PURE__*/ _jsx(TableRow, {
66
+ children: capped.headers.map((_, cellIndex)=>/*#__PURE__*/ _jsx(TableCell, {
67
+ className: "font-mono text-xs",
68
+ children: row[cellIndex] ?? ""
69
+ }, cellIndex))
70
+ }, rowIndex))
71
+ })
72
+ ]
73
+ })
74
+ }),
75
+ notes.length === 0 ? null : /*#__PURE__*/ _jsxs("p", {
76
+ className: "text-xs text-muted-foreground",
77
+ children: [
78
+ "Showing ",
79
+ notes.join(" and "),
80
+ " — download for the full file."
81
+ ]
82
+ })
83
+ ]
84
+ });
85
+ }
@@ -0,0 +1,37 @@
1
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2
+ import type * as React from "react";
3
+ /**
4
+ * The modal surface, in the three places a modal arrives from: the centre of
5
+ * the page, the right edge, the left edge.
6
+ *
7
+ * One file rather than a dialog and a separate drawer. A side sheet *is* a
8
+ * dialog — same Radix root, same focus trap, Escape, scroll lock and focus
9
+ * restoration, same header/title/description/footer — and the only real
10
+ * differences are which edge it is pinned to and which keyframes carry it in.
11
+ * That is a `side` prop, not a second component: two files meant every fix to
12
+ * the close button, the labelling or the animation had to be made twice, and
13
+ * anyone adopting both surfaces copied the same eighty lines into their repo
14
+ * under two names.
15
+ *
16
+ * `side="center"` is the default because the unqualified word "dialog" means a
17
+ * centred one. Centre dialogs own max-height and overflow so tall forms scroll
18
+ * inside `DialogBody` rather than growing past the viewport. `left` and
19
+ * `right` are full-height sheets; they also use `DialogBody` so the header and
20
+ * footer stay put while the middle scrolls.
21
+ */
22
+ export declare const Dialog: React.FC<DialogPrimitive.DialogProps>;
23
+ export declare const DialogTrigger: React.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
24
+ export declare const DialogClose: React.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
25
+ export type DialogSide = "center" | "left" | "right";
26
+ export declare function DialogContent({ className, children, side, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
27
+ side?: DialogSide;
28
+ }): React.JSX.Element;
29
+ /** `pr-8` keeps the title clear of the close control in the corner. */
30
+ export declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
31
+ /** Scrolls; the header and footer stay put. Needed by tall centre dialogs and side sheets. */
32
+ export declare function DialogBody({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
33
+ export declare function DialogFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
34
+ export declare function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>): React.JSX.Element;
35
+ export declare function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>): React.JSX.Element;
36
+ /** Uppercase field label used inside dialog forms. */
37
+ export declare function DialogFieldLabel({ className, ...props }: React.ComponentProps<"label">): React.JSX.Element;
@@ -0,0 +1,105 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
3
+ import { XIcon } from "lucide-react";
4
+ import { cn } from "../lib/utils.js";
5
+ /**
6
+ * The modal surface, in the three places a modal arrives from: the centre of
7
+ * the page, the right edge, the left edge.
8
+ *
9
+ * One file rather than a dialog and a separate drawer. A side sheet *is* a
10
+ * dialog — same Radix root, same focus trap, Escape, scroll lock and focus
11
+ * restoration, same header/title/description/footer — and the only real
12
+ * differences are which edge it is pinned to and which keyframes carry it in.
13
+ * That is a `side` prop, not a second component: two files meant every fix to
14
+ * the close button, the labelling or the animation had to be made twice, and
15
+ * anyone adopting both surfaces copied the same eighty lines into their repo
16
+ * under two names.
17
+ *
18
+ * `side="center"` is the default because the unqualified word "dialog" means a
19
+ * centred one. Centre dialogs own max-height and overflow so tall forms scroll
20
+ * inside `DialogBody` rather than growing past the viewport. `left` and
21
+ * `right` are full-height sheets; they also use `DialogBody` so the header and
22
+ * footer stay put while the middle scrolls.
23
+ */ export const Dialog = DialogPrimitive.Root;
24
+ export const DialogTrigger = DialogPrimitive.Trigger;
25
+ export const DialogClose = DialogPrimitive.Close;
26
+ const SIDE_CLASS = {
27
+ // Guttered width + capped height so the surface never kisses the viewport
28
+ // edge or spills past ~one form-screen of content. Overflow is clipped here;
29
+ // scroll lives on DialogBody.
30
+ center: "top-1/2 left-1/2 w-[min(calc(100vw-2rem),32rem)] max-h-[min(90dvh,42rem)] " + "overflow-hidden -translate-x-1/2 -translate-y-1/2 rounded-lg border " + "max-sm:w-[min(calc(100vw-1.25rem),32rem)] max-sm:max-h-[min(92dvh,42rem)] " + "data-[state=open]:animate-[corbits-dialog-in_150ms_ease-out] data-[state=closed]:animate-[corbits-dialog-out_120ms_ease-in]",
31
+ right: "inset-y-0 right-0 w-full max-w-lg border-l " + "data-[state=open]:animate-[corbits-drawer-in-right_200ms_ease-out] data-[state=closed]:animate-[corbits-drawer-out-right_160ms_ease-in]",
32
+ left: "inset-y-0 left-0 w-full max-w-lg border-r " + "data-[state=open]:animate-[corbits-drawer-in-left_200ms_ease-out] data-[state=closed]:animate-[corbits-drawer-out-left_160ms_ease-in]"
33
+ };
34
+ export function DialogContent({ className, children, side = "center", ...props }) {
35
+ return /*#__PURE__*/ _jsxs(DialogPrimitive.Portal, {
36
+ children: [
37
+ /*#__PURE__*/ _jsx(DialogPrimitive.Overlay, {
38
+ className: "fixed inset-0 z-50 bg-black/50 backdrop-blur-[2px] data-[state=open]:animate-[corbits-fade-in_150ms_ease-out] data-[state=closed]:animate-[corbits-fade-out_120ms_ease-in]"
39
+ }),
40
+ /*#__PURE__*/ _jsxs(DialogPrimitive.Content, {
41
+ "data-slot": "dialog-content",
42
+ "data-side": side,
43
+ className: cn("fixed z-50 flex flex-col gap-4 border-border bg-popover p-6 text-popover-foreground shadow-lg", // Keyframes are installed into your globals.css by this registry item.
44
+ SIDE_CLASS[side], className),
45
+ ...props,
46
+ children: [
47
+ children,
48
+ /*#__PURE__*/ _jsx(DialogPrimitive.Close, {
49
+ "aria-label": "Close",
50
+ className: cn("absolute top-[0.85rem] right-[0.85rem] inline-flex size-[1.8rem] items-center justify-center", "rounded-[0.45rem] p-0 text-muted-foreground transition-colors", "hover:bg-foreground/5 hover:text-foreground", // Soft primary-tint focus instead of the theme outline — the close
51
+ // control sits on the popover surface and a hard ring reads heavy.
52
+ "focus-visible:text-foreground focus-visible:outline-none", "focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary)_22%,transparent)]"),
53
+ children: /*#__PURE__*/ _jsx(XIcon, {
54
+ className: "size-4"
55
+ })
56
+ })
57
+ ]
58
+ })
59
+ ]
60
+ });
61
+ }
62
+ /** `pr-8` keeps the title clear of the close control in the corner. */ export function DialogHeader({ className, ...props }) {
63
+ return /*#__PURE__*/ _jsx("div", {
64
+ "data-slot": "dialog-header",
65
+ className: cn("flex shrink-0 flex-col gap-1 pr-8", className),
66
+ ...props
67
+ });
68
+ }
69
+ /** Scrolls; the header and footer stay put. Needed by tall centre dialogs and side sheets. */ export function DialogBody({ className, ...props }) {
70
+ return /*#__PURE__*/ _jsx("div", {
71
+ "data-slot": "dialog-body",
72
+ className: cn("min-h-0 flex-auto overflow-y-auto overscroll-contain [scrollbar-width:thin]", className),
73
+ ...props
74
+ });
75
+ }
76
+ export function DialogFooter({ className, ...props }) {
77
+ return /*#__PURE__*/ _jsx("div", {
78
+ "data-slot": "dialog-footer",
79
+ className: cn(// Stable bar: pinned to the bottom of the flex column, subtle top rule
80
+ // and wash so actions read as chrome rather than more body content.
81
+ "mt-auto flex shrink-0 flex-wrap items-center justify-end gap-2", "border-t border-foreground/10 bg-foreground/[0.025] pt-[0.85rem]", // Narrow viewports: stack full-width so primary/secondary hit areas
82
+ // don't compete for a single row of thumb space.
83
+ "max-sm:flex-col max-sm:flex-nowrap max-sm:items-stretch max-sm:[&>*]:w-full", className),
84
+ ...props
85
+ });
86
+ }
87
+ export function DialogTitle({ className, ...props }) {
88
+ return /*#__PURE__*/ _jsx(DialogPrimitive.Title, {
89
+ className: cn("m-0 text-[0.95rem] font-[650] leading-[1.3] tracking-[-0.01em] text-foreground", className),
90
+ ...props
91
+ });
92
+ }
93
+ export function DialogDescription({ className, ...props }) {
94
+ return /*#__PURE__*/ _jsx(DialogPrimitive.Description, {
95
+ className: cn("m-0 text-[0.8125rem] leading-[1.4] text-muted-foreground", className),
96
+ ...props
97
+ });
98
+ }
99
+ /** Uppercase field label used inside dialog forms. */ export function DialogFieldLabel({ className, ...props }) {
100
+ return /*#__PURE__*/ _jsx("label", {
101
+ "data-slot": "dialog-field-label",
102
+ className: cn("text-[10.5px] font-bold uppercase tracking-[0.06em] text-muted-foreground", className),
103
+ ...props
104
+ });
105
+ }
@@ -0,0 +1,20 @@
1
+ import type { DictationState } from "./mic-button.js";
2
+ export type DictationStatusLineProps = {
3
+ readonly state: DictationState;
4
+ /** Recent input levels, forwarded to `VoiceWaveform` while listening. */
5
+ readonly levels?: readonly number[];
6
+ /** Shown while `state` is `"starting"`. */
7
+ readonly startingLabel?: string;
8
+ /** Shown while `state` is `"listening"`, before the waveform. */
9
+ readonly listeningLabel?: string;
10
+ /** Shown while `state` is `"denied"`, in place of the waveform row. */
11
+ readonly errorMessage?: string;
12
+ readonly className?: string;
13
+ };
14
+ /**
15
+ * The line under a composer that a microphone toggle turns on: "Listening:"
16
+ * with a waveform, a starting message, or a refusal — never more than one of
17
+ * these, and absent entirely once `state` is `"idle"` or `"unsupported"`, so
18
+ * the row does not otherwise change the composer's shape.
19
+ */
20
+ export declare function DictationStatusLine({ state, levels, startingLabel, listeningLabel, errorMessage, className, }: DictationStatusLineProps): import("react").JSX.Element | null;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { VoiceWaveform } from "./voice-waveform.js";
3
+ import { cn } from "../lib/utils.js";
4
+ /**
5
+ * The line under a composer that a microphone toggle turns on: "Listening:"
6
+ * with a waveform, a starting message, or a refusal — never more than one of
7
+ * these, and absent entirely once `state` is `"idle"` or `"unsupported"`, so
8
+ * the row does not otherwise change the composer's shape.
9
+ */ export function DictationStatusLine({ state, levels = [], startingLabel = "Starting microphone…", listeningLabel = "Listening:", errorMessage, className }) {
10
+ if (state === "idle" || state === "unsupported") return null;
11
+ if (state === "denied") {
12
+ return /*#__PURE__*/ _jsx("p", {
13
+ role: "alert",
14
+ className: cn("text-xs text-destructive", className),
15
+ children: errorMessage ?? "Microphone access was blocked."
16
+ });
17
+ }
18
+ if (state === "starting") {
19
+ return /*#__PURE__*/ _jsx("p", {
20
+ "aria-live": "polite",
21
+ className: cn("text-xs text-muted-foreground", className),
22
+ children: startingLabel
23
+ });
24
+ }
25
+ return /*#__PURE__*/ _jsxs("p", {
26
+ "aria-live": "polite",
27
+ className: cn("flex items-center gap-2 text-xs text-muted-foreground", className),
28
+ children: [
29
+ /*#__PURE__*/ _jsx("span", {
30
+ children: listeningLabel
31
+ }),
32
+ /*#__PURE__*/ _jsx(VoiceWaveform, {
33
+ levels: levels,
34
+ className: "text-ok"
35
+ })
36
+ ]
37
+ });
38
+ }
@@ -0,0 +1,31 @@
1
+ export type DitherCanvasProps = {
2
+ /** Edge of one dither cell, in CSS pixels. Larger reads as coarser grain. */
3
+ readonly cell?: number;
4
+ /** Ink colour for lit cells. Defaults to the theme's primary. */
5
+ readonly color?: string;
6
+ /** How far the pointer's influence reaches, in CSS pixels. `0` disables it. */
7
+ readonly warpRadius?: number;
8
+ readonly className?: string;
9
+ };
10
+ /**
11
+ * A dithered field that reacts to the pointer — decoration for a full-bleed
12
+ * panel, and nothing else.
13
+ *
14
+ * Drawn on a canvas rather than as a repeating background image because the
15
+ * point is that it *moves*: a radial field drifts slowly, the pointer pushes a
16
+ * bright spot through it, and both need per-frame recomputation. A CSS pattern
17
+ * can be one of those things but not the other.
18
+ *
19
+ * `aria-hidden` and unfocusable, unconditionally. It carries no information;
20
+ * anything a reader needs is in the content laid over it.
21
+ *
22
+ * Reduced motion is honoured by drawing exactly one frame and stopping — not by
23
+ * slowing down and not by hiding it. The texture is the design; the animation is
24
+ * the part that makes some people ill. The theme's global CSS override cannot
25
+ * reach a `requestAnimationFrame` loop, so the check lives here.
26
+ *
27
+ * The canvas is sized in device pixels and scaled by `devicePixelRatio`. Without
28
+ * that the grid is resampled by the compositor on a retina display and the
29
+ * crisp dot pattern — the entire effect — turns to mush.
30
+ */
31
+ export declare function DitherCanvas({ cell, color, warpRadius, className }: DitherCanvasProps): import("react").JSX.Element;