@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,169 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
4
+ import { cn } from "../lib/utils.js";
5
+ /**
6
+ * 4×4 Bayer matrix, normalised to 0–1.
7
+ *
8
+ * Ordered dithering, not error diffusion. Error diffusion (Floyd–Steinberg and
9
+ * friends) is serial — each pixel's error feeds the next — so it cannot be
10
+ * recomputed cheaply every frame, and the pattern crawls when anything moves.
11
+ * An ordered matrix is a pure function of position, so a frame costs one pass
12
+ * and the texture stays locked to the surface instead of swimming.
13
+ */ const BAYER = [
14
+ [
15
+ 0,
16
+ 8,
17
+ 2,
18
+ 10
19
+ ],
20
+ [
21
+ 12,
22
+ 4,
23
+ 14,
24
+ 6
25
+ ],
26
+ [
27
+ 3,
28
+ 11,
29
+ 1,
30
+ 9
31
+ ],
32
+ [
33
+ 15,
34
+ 7,
35
+ 13,
36
+ 5
37
+ ]
38
+ ].map((row)=>row.map((value)=>(value + 0.5) / 16));
39
+ /**
40
+ * A dithered field that reacts to the pointer — decoration for a full-bleed
41
+ * panel, and nothing else.
42
+ *
43
+ * Drawn on a canvas rather than as a repeating background image because the
44
+ * point is that it *moves*: a radial field drifts slowly, the pointer pushes a
45
+ * bright spot through it, and both need per-frame recomputation. A CSS pattern
46
+ * can be one of those things but not the other.
47
+ *
48
+ * `aria-hidden` and unfocusable, unconditionally. It carries no information;
49
+ * anything a reader needs is in the content laid over it.
50
+ *
51
+ * Reduced motion is honoured by drawing exactly one frame and stopping — not by
52
+ * slowing down and not by hiding it. The texture is the design; the animation is
53
+ * the part that makes some people ill. The theme's global CSS override cannot
54
+ * reach a `requestAnimationFrame` loop, so the check lives here.
55
+ *
56
+ * The canvas is sized in device pixels and scaled by `devicePixelRatio`. Without
57
+ * that the grid is resampled by the compositor on a retina display and the
58
+ * crisp dot pattern — the entire effect — turns to mush.
59
+ */ export function DitherCanvas({ cell = 4, color, warpRadius = 160, className }) {
60
+ const ref = useRef(null);
61
+ const reduce = usePrefersReducedMotion();
62
+ useEffect(()=>{
63
+ const canvas = ref.current;
64
+ if (canvas === null) return;
65
+ const context = canvas.getContext("2d");
66
+ if (context === null) return;
67
+ // Resolved once per mount: reading a custom property costs a style
68
+ // recalculation, which is not something to do sixty times a second. The
69
+ // fallback reads the canvas's own computed `color` rather than a literal
70
+ // hex — the theme's base layer already ties that to `--foreground`, so
71
+ // the fallback tracks the active theme instead of only being correct in
72
+ // whichever one it was copied from.
73
+ const computed = getComputedStyle(canvas);
74
+ const themeInk = computed.getPropertyValue("--primary").trim();
75
+ const ink = color ?? (themeInk === "" ? computed.color : themeInk);
76
+ const pointer = {
77
+ x: -1e6,
78
+ y: -1e6
79
+ };
80
+ let frame = 0;
81
+ let width = 0;
82
+ let height = 0;
83
+ const resize = ()=>{
84
+ const ratio = window.devicePixelRatio || 1;
85
+ const box = canvas.getBoundingClientRect();
86
+ width = box.width;
87
+ height = box.height;
88
+ canvas.width = Math.max(1, Math.round(width * ratio));
89
+ canvas.height = Math.max(1, Math.round(height * ratio));
90
+ context.setTransform(ratio, 0, 0, ratio, 0, 0);
91
+ };
92
+ const draw = (time)=>{
93
+ context.clearRect(0, 0, width, height);
94
+ context.fillStyle = ink;
95
+ const drift = reduce ? 0 : time / 3000;
96
+ const columns = Math.ceil(width / cell);
97
+ const rows = Math.ceil(height / cell);
98
+ for(let row = 0; row < rows; row += 1){
99
+ for(let column = 0; column < columns; column += 1){
100
+ const x = column * cell;
101
+ const y = row * cell;
102
+ // Base field: a soft diagonal gradient with a slow sine drift, so the
103
+ // panel is darkest at one corner and the grain has somewhere to go.
104
+ const across = (x / Math.max(width, 1) + y / Math.max(height, 1)) / 2;
105
+ let intensity = 0.55 - across * 0.5 + Math.sin(across * 6 + drift) * 0.08;
106
+ if (warpRadius > 0) {
107
+ const dx = x - pointer.x;
108
+ const dy = y - pointer.y;
109
+ const distance = Math.hypot(dx, dy);
110
+ if (distance < warpRadius) {
111
+ // Squared falloff: a linear one leaves a visible disc edge.
112
+ const falloff = 1 - distance / warpRadius;
113
+ intensity += falloff * falloff * 0.55;
114
+ }
115
+ }
116
+ // The threshold comparison *is* the dither: a cell is either on or
117
+ // off, and the matrix decides which, so the average over a 4×4 block
118
+ // approximates the continuous intensity.
119
+ const threshold = BAYER[row % 4]?.[column % 4] ?? 0.5;
120
+ if (intensity > threshold) {
121
+ context.fillRect(x, y, cell - 1, cell - 1);
122
+ }
123
+ }
124
+ }
125
+ };
126
+ const loop = (time)=>{
127
+ draw(time);
128
+ frame = requestAnimationFrame(loop);
129
+ };
130
+ const onPointerMove = (event)=>{
131
+ const box = canvas.getBoundingClientRect();
132
+ pointer.x = event.clientX - box.left;
133
+ pointer.y = event.clientY - box.top;
134
+ };
135
+ const onPointerLeave = ()=>{
136
+ pointer.x = -1e6;
137
+ pointer.y = -1e6;
138
+ };
139
+ resize();
140
+ const observer = new ResizeObserver(()=>{
141
+ resize();
142
+ if (reduce) draw(0);
143
+ });
144
+ observer.observe(canvas);
145
+ if (reduce) {
146
+ draw(0);
147
+ } else {
148
+ frame = requestAnimationFrame(loop);
149
+ canvas.addEventListener("pointermove", onPointerMove);
150
+ canvas.addEventListener("pointerleave", onPointerLeave);
151
+ }
152
+ return ()=>{
153
+ cancelAnimationFrame(frame);
154
+ observer.disconnect();
155
+ canvas.removeEventListener("pointermove", onPointerMove);
156
+ canvas.removeEventListener("pointerleave", onPointerLeave);
157
+ };
158
+ }, [
159
+ cell,
160
+ color,
161
+ warpRadius,
162
+ reduce
163
+ ]);
164
+ return /*#__PURE__*/ _jsx("canvas", {
165
+ ref: ref,
166
+ "aria-hidden": true,
167
+ className: cn("block size-full", className)
168
+ });
169
+ }
@@ -0,0 +1,30 @@
1
+ export type EmbedBodyProps = {
2
+ /** Must be `https:`. Anything else renders the fallback notice. */
3
+ readonly url: string;
4
+ /** Names the frame for assistive tech. Required — an unnamed frame is unnavigable. */
5
+ readonly title: string;
6
+ readonly description?: string;
7
+ /** Offers the original file alongside the embed, when the host serves one. */
8
+ readonly downloadUrl?: string;
9
+ /**
10
+ * Frame shape. `wide` is 16:9 for slides; `tall` suits paged documents, where
11
+ * a 16:9 box shows a third of a page and forces scrolling inside a scroller.
12
+ */
13
+ readonly aspect?: "wide" | "tall";
14
+ readonly className?: string;
15
+ };
16
+ /**
17
+ * A third-party URL, framed.
18
+ *
19
+ * One component covers every embedded-viewer kind — slide decks, hosted
20
+ * documents, rendered previews. They differ only in the URL and the frame's
21
+ * proportions, and three near-identical files is how three near-identical
22
+ * sandboxes drift apart until one of them is wrong.
23
+ *
24
+ * The escape hatch below the frame is not decoration. Embedding is the thing
25
+ * most likely to be blocked by a frame-ancestors policy, an extension or a
26
+ * login wall, and an iframe that renders blank fires no error we can catch — so
27
+ * the direct link is always present rather than being a fallback we would have
28
+ * to detect our way into.
29
+ */
30
+ export declare function EmbedBody({ url, title, description, downloadUrl, aspect, className }: EmbedBodyProps): import("react").JSX.Element;
@@ -0,0 +1,92 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ExternalLink } from "lucide-react";
3
+ import { isSafeUrl, toSafeHref } from "../lib/url.js";
4
+ import { cn } from "../lib/utils.js";
5
+ import { ArtifactNotice } from "./artifact-notice.js";
6
+ import { buttonVariants } from "./button.js";
7
+ /**
8
+ * Everything embedded here is third-party: a deck on someone's hosting, a
9
+ * rendered document, a preview a model produced. None of it is trusted, so the
10
+ * sandbox is opened one capability at a time.
11
+ *
12
+ * `allow-scripts` and `allow-same-origin` together are required for essentially
13
+ * every real embed (slide players and PDF viewers both need them) and *together*
14
+ * they are what lets framed content reach its own origin's storage — which is
15
+ * correct and expected for a third-party player on its own domain. What is
16
+ * withheld is the set that lets the frame act on the *user*:
17
+ * `allow-top-navigation` (redirect the tab out from under them),
18
+ * `allow-popups` (open a tab that outlives the frame), and `allow-forms` (POST
19
+ * credentials somewhere). A viewer needs none of the three.
20
+ */ const EMBED_SANDBOX = "allow-scripts allow-same-origin allow-presentation";
21
+ const EMBED_ALLOWED_PROTOCOLS = [
22
+ "https:"
23
+ ];
24
+ /**
25
+ * A third-party URL, framed.
26
+ *
27
+ * One component covers every embedded-viewer kind — slide decks, hosted
28
+ * documents, rendered previews. They differ only in the URL and the frame's
29
+ * proportions, and three near-identical files is how three near-identical
30
+ * sandboxes drift apart until one of them is wrong.
31
+ *
32
+ * The escape hatch below the frame is not decoration. Embedding is the thing
33
+ * most likely to be blocked by a frame-ancestors policy, an extension or a
34
+ * login wall, and an iframe that renders blank fires no error we can catch — so
35
+ * the direct link is always present rather than being a fallback we would have
36
+ * to detect our way into.
37
+ */ export function EmbedBody({ url, title, description, downloadUrl, aspect = "wide", className }) {
38
+ if (!isSafeUrl(url, EMBED_ALLOWED_PROTOCOLS)) {
39
+ return /*#__PURE__*/ _jsx(ArtifactNotice, {
40
+ message: "This link is missing or is not a secure (https) address, so it cannot be shown."
41
+ });
42
+ }
43
+ const safeDownloadUrl = toSafeHref(downloadUrl);
44
+ return /*#__PURE__*/ _jsxs("div", {
45
+ className: cn("flex w-full flex-col gap-3", className),
46
+ children: [
47
+ description === undefined ? null : /*#__PURE__*/ _jsx("p", {
48
+ className: "max-w-prose text-sm text-muted-foreground",
49
+ children: description
50
+ }),
51
+ /*#__PURE__*/ _jsx("div", {
52
+ className: cn("w-full overflow-hidden rounded-lg border border-border bg-card", aspect === "wide" ? "aspect-video" : "aspect-[3/4] max-h-[75vh]"),
53
+ children: /*#__PURE__*/ _jsx("iframe", {
54
+ src: url,
55
+ title: title,
56
+ sandbox: EMBED_SANDBOX,
57
+ allow: "fullscreen",
58
+ className: "size-full border-0"
59
+ })
60
+ }),
61
+ /*#__PURE__*/ _jsxs("div", {
62
+ className: "flex items-center gap-2",
63
+ children: [
64
+ /*#__PURE__*/ _jsxs("a", {
65
+ href: url,
66
+ target: "_blank",
67
+ rel: "noopener noreferrer",
68
+ className: buttonVariants({
69
+ variant: "outline",
70
+ size: "sm"
71
+ }),
72
+ children: [
73
+ /*#__PURE__*/ _jsx(ExternalLink, {
74
+ "aria-hidden": true
75
+ }),
76
+ "Open in a new tab"
77
+ ]
78
+ }),
79
+ safeDownloadUrl === undefined ? null : /*#__PURE__*/ _jsx("a", {
80
+ href: safeDownloadUrl,
81
+ download: true,
82
+ className: buttonVariants({
83
+ variant: "ghost",
84
+ size: "sm"
85
+ }),
86
+ children: "Download"
87
+ })
88
+ ]
89
+ })
90
+ ]
91
+ });
92
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from "react";
2
+ export type EmptyStateProps = {
3
+ /** What is not here, as a statement: "No workflows yet". */
4
+ readonly title: string;
5
+ /** Why, or what to do about it. One or two sentences. */
6
+ readonly description?: ReactNode;
7
+ readonly icon?: ReactNode;
8
+ /** The way out — a create button, a "clear filters" control. */
9
+ readonly action?: ReactNode;
10
+ readonly className?: string;
11
+ };
12
+ /**
13
+ * Nothing to show, said properly.
14
+ *
15
+ * A shared component because every list otherwise grows its own centred stack
16
+ * of icon-title-body, and they end up differently spaced and differently
17
+ * worded. Three surfaces had already written this inline before it existed.
18
+ *
19
+ * The icon is decorative and hidden from assistive tech: it never carries
20
+ * meaning the title does not already carry, and a screen reader announcing
21
+ * "inbox icon" before "You're all caught up" is noise.
22
+ *
23
+ * Not a `role="status"`. An empty state is the rendered result of a finished
24
+ * load, not an announcement — the surface that was loading owns that.
25
+ */
26
+ export declare function EmptyState({ title, description, icon, action, className }: EmptyStateProps): import("react").JSX.Element;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ /**
4
+ * Nothing to show, said properly.
5
+ *
6
+ * A shared component because every list otherwise grows its own centred stack
7
+ * of icon-title-body, and they end up differently spaced and differently
8
+ * worded. Three surfaces had already written this inline before it existed.
9
+ *
10
+ * The icon is decorative and hidden from assistive tech: it never carries
11
+ * meaning the title does not already carry, and a screen reader announcing
12
+ * "inbox icon" before "You're all caught up" is noise.
13
+ *
14
+ * Not a `role="status"`. An empty state is the rendered result of a finished
15
+ * load, not an announcement — the surface that was loading owns that.
16
+ */ export function EmptyState({ title, description, icon, action, className }) {
17
+ return /*#__PURE__*/ _jsxs("div", {
18
+ "data-slot": "empty-state",
19
+ className: cn("flex flex-col items-center gap-3 px-6 py-12 text-center", className),
20
+ children: [
21
+ icon === undefined ? null : /*#__PURE__*/ _jsx("span", {
22
+ "aria-hidden": true,
23
+ className: "grid size-11 place-items-center rounded-full bg-muted text-muted-foreground [&_svg]:size-5",
24
+ children: icon
25
+ }),
26
+ /*#__PURE__*/ _jsxs("div", {
27
+ className: "flex flex-col gap-1",
28
+ children: [
29
+ /*#__PURE__*/ _jsx("p", {
30
+ className: "text-sm font-medium",
31
+ children: title
32
+ }),
33
+ description === undefined ? null : /*#__PURE__*/ _jsx("p", {
34
+ className: "max-w-prose text-xs leading-relaxed text-muted-foreground",
35
+ children: description
36
+ })
37
+ ]
38
+ }),
39
+ action
40
+ ]
41
+ });
42
+ }
@@ -0,0 +1,25 @@
1
+ import type * as React from "react";
2
+ export type FileInputProps = Omit<React.ComponentProps<"input">, "type" | "onChange" | "className"> & {
3
+ readonly onFiles: (files: FileList) => void;
4
+ readonly label?: string;
5
+ readonly hint?: string;
6
+ readonly className?: string;
7
+ };
8
+ /**
9
+ * A drop zone that is also a button that is also a real file input.
10
+ *
11
+ * The `<input type="file">` is the control — visually hidden with `sr-only`,
12
+ * never `display: none`, because a hidden-by-display input is unreachable by
13
+ * keyboard and invisible to assistive tech. The `<label>` wrapping it is what
14
+ * the user sees and clicks, which is also what makes the whole zone activate
15
+ * the picker with no click handler at all.
16
+ *
17
+ * Drag-and-drop is added on top rather than replacing that. A drop-only zone
18
+ * excludes anyone not using a mouse, and every real upload control needs the
19
+ * picker anyway.
20
+ *
21
+ * The input is cleared after each selection so choosing the same file twice
22
+ * still fires — the browser's change event will not repeat for an identical
23
+ * value.
24
+ */
25
+ export declare function FileInput({ onFiles, label, hint, className, disabled, ...props }: FileInputProps): React.JSX.Element;
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Upload } from "lucide-react";
3
+ import { useRef, useState } from "react";
4
+ import { cn } from "../lib/utils.js";
5
+ /**
6
+ * A drop zone that is also a button that is also a real file input.
7
+ *
8
+ * The `<input type="file">` is the control — visually hidden with `sr-only`,
9
+ * never `display: none`, because a hidden-by-display input is unreachable by
10
+ * keyboard and invisible to assistive tech. The `<label>` wrapping it is what
11
+ * the user sees and clicks, which is also what makes the whole zone activate
12
+ * the picker with no click handler at all.
13
+ *
14
+ * Drag-and-drop is added on top rather than replacing that. A drop-only zone
15
+ * excludes anyone not using a mouse, and every real upload control needs the
16
+ * picker anyway.
17
+ *
18
+ * The input is cleared after each selection so choosing the same file twice
19
+ * still fires — the browser's change event will not repeat for an identical
20
+ * value.
21
+ */ export function FileInput({ onFiles, label = "Choose files", hint, className, disabled, ...props }) {
22
+ const [dragging, setDragging] = useState(false);
23
+ const inputRef = useRef(null);
24
+ return /*#__PURE__*/ _jsxs("label", {
25
+ onDragOver: (event)=>{
26
+ if (disabled === true) return;
27
+ event.preventDefault();
28
+ setDragging(true);
29
+ },
30
+ onDragLeave: ()=>setDragging(false),
31
+ onDrop: (event)=>{
32
+ if (disabled === true) return;
33
+ event.preventDefault();
34
+ setDragging(false);
35
+ if (event.dataTransfer.files.length > 0) onFiles(event.dataTransfer.files);
36
+ },
37
+ className: cn("flex cursor-pointer flex-col items-center gap-1.5 rounded-lg border border-dashed border-input px-6 py-8 text-center transition-colors", dragging && "border-primary-emphasis bg-primary/10", disabled === true ? "cursor-not-allowed opacity-50" : "hover:bg-muted", // The focus ring lives on the hidden input; project it onto the zone,
38
+ // which is the thing the user can actually see.
39
+ "has-[input:focus-visible]:outline has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2 has-[input:focus-visible]:outline-[var(--ring)]", className),
40
+ children: [
41
+ /*#__PURE__*/ _jsx("input", {
42
+ ref: inputRef,
43
+ type: "file",
44
+ disabled: disabled,
45
+ className: "sr-only",
46
+ onChange: (event)=>{
47
+ if (event.target.files !== null && event.target.files.length > 0) onFiles(event.target.files);
48
+ event.target.value = "";
49
+ },
50
+ ...props
51
+ }),
52
+ /*#__PURE__*/ _jsx(Upload, {
53
+ className: "size-5 text-muted-foreground",
54
+ "aria-hidden": true
55
+ }),
56
+ /*#__PURE__*/ _jsx("span", {
57
+ className: "text-sm font-medium",
58
+ children: label
59
+ }),
60
+ /*#__PURE__*/ _jsx("span", {
61
+ className: "text-xs text-muted-foreground",
62
+ children: hint ?? "or drag them here"
63
+ })
64
+ ]
65
+ });
66
+ }
@@ -0,0 +1,42 @@
1
+ import type { ReactNode } from "react";
2
+ export type FilterOption = {
3
+ readonly value: string;
4
+ readonly label: string;
5
+ };
6
+ export type FilterSpec = {
7
+ readonly id: string;
8
+ readonly label: string;
9
+ readonly options: readonly FilterOption[];
10
+ /** The selected option's value, or `null` for "no filter". */
11
+ readonly value: string | null;
12
+ /** The wording for the unfiltered choice — "All kinds", "Everyone". */
13
+ readonly anyLabel: string;
14
+ };
15
+ export type FilterBarProps = {
16
+ readonly filters: readonly FilterSpec[];
17
+ readonly onChange: (id: string, value: string | null) => void;
18
+ /** Rendered at the end — a time range, an export button. */
19
+ readonly children?: ReactNode;
20
+ readonly className?: string;
21
+ };
22
+ /**
23
+ * A row of filters, and — the part that is usually missing — a readable summary
24
+ * of which ones are on.
25
+ *
26
+ * The chips are not decoration. A `<select>` collapsed to its chosen value is
27
+ * easy to scroll past, and "why is this dashboard empty" is almost always a
28
+ * filter someone forgot they set three tabs ago. The chips say what is applied
29
+ * in a sentence-shaped row, each one removable, with a single "Clear all"
30
+ * beside them.
31
+ *
32
+ * Native `<select>`, not a custom listbox. A hand-built one has to re-implement
33
+ * type-ahead, arrow keys, page-up, the mobile wheel and the platform's own
34
+ * rendering, and the usual result is a control that looks better and works
35
+ * worse. When a filter genuinely needs search or multi-select, that is a
36
+ * different control — reach for the command palette, not a bigger select.
37
+ *
38
+ * Filter state lives with the caller because it almost always also lives in the
39
+ * URL. Owning it here would mean every consumer fights the component to make
40
+ * their filters linkable.
41
+ */
42
+ export declare function FilterBar({ filters, onChange, children, className }: FilterBarProps): import("react").JSX.Element;
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { X } from "lucide-react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Button } from "./button.js";
5
+ /**
6
+ * A row of filters, and — the part that is usually missing — a readable summary
7
+ * of which ones are on.
8
+ *
9
+ * The chips are not decoration. A `<select>` collapsed to its chosen value is
10
+ * easy to scroll past, and "why is this dashboard empty" is almost always a
11
+ * filter someone forgot they set three tabs ago. The chips say what is applied
12
+ * in a sentence-shaped row, each one removable, with a single "Clear all"
13
+ * beside them.
14
+ *
15
+ * Native `<select>`, not a custom listbox. A hand-built one has to re-implement
16
+ * type-ahead, arrow keys, page-up, the mobile wheel and the platform's own
17
+ * rendering, and the usual result is a control that looks better and works
18
+ * worse. When a filter genuinely needs search or multi-select, that is a
19
+ * different control — reach for the command palette, not a bigger select.
20
+ *
21
+ * Filter state lives with the caller because it almost always also lives in the
22
+ * URL. Owning it here would mean every consumer fights the component to make
23
+ * their filters linkable.
24
+ */ export function FilterBar({ filters, onChange, children, className }) {
25
+ const active = filters.filter((filter)=>filter.value !== null);
26
+ return /*#__PURE__*/ _jsxs("div", {
27
+ className: cn("flex flex-col gap-2", className),
28
+ children: [
29
+ /*#__PURE__*/ _jsxs("div", {
30
+ className: "flex flex-wrap items-center gap-3",
31
+ children: [
32
+ filters.map((filter)=>/*#__PURE__*/ _jsxs("label", {
33
+ className: "flex items-center gap-1.5 text-xs text-muted-foreground",
34
+ children: [
35
+ filter.label,
36
+ /*#__PURE__*/ _jsxs("select", {
37
+ value: filter.value ?? "",
38
+ onChange: (event)=>onChange(filter.id, event.target.value === "" ? null : event.target.value),
39
+ className: "h-8 rounded-md border border-input bg-card px-2 text-xs text-foreground",
40
+ children: [
41
+ /*#__PURE__*/ _jsx("option", {
42
+ value: "",
43
+ children: filter.anyLabel
44
+ }),
45
+ filter.options.map((option)=>/*#__PURE__*/ _jsx("option", {
46
+ value: option.value,
47
+ children: option.label
48
+ }, option.value))
49
+ ]
50
+ })
51
+ ]
52
+ }, filter.id)),
53
+ children
54
+ ]
55
+ }),
56
+ active.length === 0 ? null : /*#__PURE__*/ _jsxs("div", {
57
+ className: "flex flex-wrap items-center gap-2",
58
+ children: [
59
+ active.map((filter)=>{
60
+ const option = filter.options.find((candidate)=>candidate.value === filter.value);
61
+ return /*#__PURE__*/ _jsxs("button", {
62
+ type: "button",
63
+ onClick: ()=>onChange(filter.id, null),
64
+ // The whole chip is the remove control, and its accessible name
65
+ // says so — an "×" alone is announced as "times".
66
+ "aria-label": `Remove filter ${filter.label}: ${option?.label ?? filter.value}`,
67
+ className: "inline-flex items-center gap-1 rounded-sm bg-muted px-2 py-0.5 text-xs text-muted-foreground transition-colors hover:text-foreground",
68
+ children: [
69
+ /*#__PURE__*/ _jsx("span", {
70
+ className: "font-medium text-foreground",
71
+ children: filter.label
72
+ }),
73
+ option?.label ?? filter.value,
74
+ /*#__PURE__*/ _jsx(X, {
75
+ className: "size-3",
76
+ "aria-hidden": true
77
+ })
78
+ ]
79
+ }, filter.id);
80
+ }),
81
+ /*#__PURE__*/ _jsx(Button, {
82
+ type: "button",
83
+ variant: "ghost",
84
+ size: "sm",
85
+ className: "h-6 px-2 text-xs",
86
+ onClick: ()=>{
87
+ for (const filter of active)onChange(filter.id, null);
88
+ },
89
+ children: "Clear all"
90
+ })
91
+ ]
92
+ })
93
+ ]
94
+ });
95
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from "react";
2
+ export type FilterChipProps = {
3
+ readonly selected?: boolean;
4
+ readonly onClick?: () => void;
5
+ readonly children: ReactNode;
6
+ readonly count?: number;
7
+ readonly className?: string;
8
+ };
9
+ /**
10
+ * A toggleable filter pill — a category, a tag, a facet value. A real
11
+ * `<button>` with `aria-pressed`, not a styled label, so the selection state
12
+ * is announced and the control is reachable with Space/Enter alone.
13
+ */
14
+ export declare function FilterChip({ selected, onClick, children, count, className }: FilterChipProps): 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
+ * A toggleable filter pill — a category, a tag, a facet value. A real
5
+ * `<button>` with `aria-pressed`, not a styled label, so the selection state
6
+ * is announced and the control is reachable with Space/Enter alone.
7
+ */ export function FilterChip({ selected = false, onClick, children, count, className }) {
8
+ return /*#__PURE__*/ _jsxs("button", {
9
+ type: "button",
10
+ "aria-pressed": selected,
11
+ onClick: onClick,
12
+ className: cn(// active:brightness-95 mirrors Button's press state: it lands on
13
+ // pointer-down, so it sits outside transition-colors.
14
+ "inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-xs font-semibold transition-colors ease-out active:brightness-95", selected ? "border-border bg-card text-foreground shadow-sm" : "border-border bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground", className),
15
+ children: [
16
+ children,
17
+ count === undefined ? null : /*#__PURE__*/ _jsx("span", {
18
+ className: cn("text-[10.5px] font-bold", selected ? "text-primary-emphasis" : "text-muted-foreground"),
19
+ children: count
20
+ })
21
+ ]
22
+ });
23
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from "react";
2
+ import { type GateShellModel } from "../lib/workflow-registry.js";
3
+ export type GateBlockProps = {
4
+ readonly gate: GateShellModel;
5
+ /** The interactive payload — a review list, choice buttons, a form. */
6
+ readonly children?: ReactNode;
7
+ readonly footer?: ReactNode;
8
+ readonly className?: string;
9
+ };
10
+ /**
11
+ * Presentational chrome for a pending gate: a run parked on a human
12
+ * decision. The interactive payload is entirely the caller's — this
13
+ * component only frames it, so the resume-payload contract stays wherever
14
+ * the host's signal-sending logic already lives.
15
+ */
16
+ export declare function GateBlock({ gate, children, footer, className }: GateBlockProps): import("react").JSX.Element;