@cueplusplus/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 (372) hide show
  1. package/dist/charts/_token.js +39 -0
  2. package/dist/charts/container.d.ts +58 -0
  3. package/dist/charts/container.js +77 -0
  4. package/dist/charts/index.d.ts +5 -0
  5. package/dist/charts/index.js +5 -0
  6. package/dist/charts/ramps.d.ts +140 -0
  7. package/dist/charts/ramps.js +615 -0
  8. package/dist/charts/theme.d.ts +112 -0
  9. package/dist/charts/theme.js +165 -0
  10. package/dist/charts/tones.d.ts +111 -0
  11. package/dist/charts/tones.js +127 -0
  12. package/dist/chat/_glyphs.js +55 -0
  13. package/dist/chat/agent-color.d.ts +84 -0
  14. package/dist/chat/agent-color.js +101 -0
  15. package/dist/chat/agent-pile.d.ts +55 -0
  16. package/dist/chat/agent-pile.js +121 -0
  17. package/dist/chat/ask-box.d.ts +88 -0
  18. package/dist/chat/ask-box.js +341 -0
  19. package/dist/chat/composer.d.ts +66 -0
  20. package/dist/chat/composer.js +144 -0
  21. package/dist/chat/delegation-card.d.ts +60 -0
  22. package/dist/chat/delegation-card.js +129 -0
  23. package/dist/chat/index.d.ts +8 -0
  24. package/dist/chat/index.js +8 -0
  25. package/dist/chat/message-list.d.ts +39 -0
  26. package/dist/chat/message-list.js +105 -0
  27. package/dist/chat/message.d.ts +47 -0
  28. package/dist/chat/message.js +108 -0
  29. package/dist/chrome/_glyphs.js +101 -0
  30. package/dist/chrome/_tabs-scroll.d.ts +62 -0
  31. package/dist/chrome/_tabs-scroll.js +68 -0
  32. package/dist/chrome/_tree.d.ts +75 -0
  33. package/dist/chrome/_tree.js +49 -0
  34. package/dist/chrome/empty-state.d.ts +37 -0
  35. package/dist/chrome/empty-state.js +57 -0
  36. package/dist/chrome/eyebrow.d.ts +29 -0
  37. package/dist/chrome/eyebrow.js +36 -0
  38. package/dist/chrome/index.d.ts +17 -0
  39. package/dist/chrome/index.js +17 -0
  40. package/dist/chrome/navigation-menu.d.ts +80 -0
  41. package/dist/chrome/navigation-menu.js +121 -0
  42. package/dist/chrome/page-shell.d.ts +38 -0
  43. package/dist/chrome/page-shell.js +66 -0
  44. package/dist/chrome/panel.d.ts +55 -0
  45. package/dist/chrome/panel.js +67 -0
  46. package/dist/chrome/row.d.ts +82 -0
  47. package/dist/chrome/row.js +106 -0
  48. package/dist/chrome/scroll-area.d.ts +39 -0
  49. package/dist/chrome/scroll-area.js +70 -0
  50. package/dist/chrome/section-header.d.ts +42 -0
  51. package/dist/chrome/section-header.js +79 -0
  52. package/dist/chrome/segmented-control.d.ts +58 -0
  53. package/dist/chrome/segmented-control.js +76 -0
  54. package/dist/chrome/status-bar.d.ts +46 -0
  55. package/dist/chrome/status-bar.js +56 -0
  56. package/dist/chrome/tabs.d.ts +80 -0
  57. package/dist/chrome/tabs.js +176 -0
  58. package/dist/chrome/title-bar.d.ts +37 -0
  59. package/dist/chrome/title-bar.js +45 -0
  60. package/dist/chrome/toolbar.d.ts +46 -0
  61. package/dist/chrome/toolbar.js +62 -0
  62. package/dist/chrome/tree.d.ts +98 -0
  63. package/dist/chrome/tree.js +248 -0
  64. package/dist/color/_checker.js +14 -0
  65. package/dist/color/_convert.d.ts +52 -0
  66. package/dist/color/_convert.js +66 -0
  67. package/dist/color/_glyphs.js +28 -0
  68. package/dist/color/_thumb.js +15 -0
  69. package/dist/color/_value.js +28 -0
  70. package/dist/color/color-area.d.ts +27 -0
  71. package/dist/color/color-area.js +40 -0
  72. package/dist/color/color-field.d.ts +32 -0
  73. package/dist/color/color-field.js +43 -0
  74. package/dist/color/color-picker.d.ts +71 -0
  75. package/dist/color/color-picker.js +178 -0
  76. package/dist/color/color-slider.d.ts +31 -0
  77. package/dist/color/color-slider.js +51 -0
  78. package/dist/color/color-swatch.d.ts +32 -0
  79. package/dist/color/color-swatch.js +44 -0
  80. package/dist/color/index.d.ts +7 -0
  81. package/dist/color/index.js +7 -0
  82. package/dist/configurator/_export.d.ts +78 -0
  83. package/dist/configurator/_export.js +261 -0
  84. package/dist/configurator/_glyphs.js +92 -0
  85. package/dist/configurator/_overrides.d.ts +247 -0
  86. package/dist/configurator/_overrides.js +514 -0
  87. package/dist/configurator/configurator.d.ts +71 -0
  88. package/dist/configurator/configurator.js +279 -0
  89. package/dist/configurator/export-dialog.d.ts +30 -0
  90. package/dist/configurator/export-dialog.js +121 -0
  91. package/dist/configurator/index.d.ts +8 -0
  92. package/dist/configurator/index.js +8 -0
  93. package/dist/configurator/panel-sections.d.ts +86 -0
  94. package/dist/configurator/panel-sections.js +316 -0
  95. package/dist/configurator/token-editor.d.ts +74 -0
  96. package/dist/configurator/token-editor.js +149 -0
  97. package/dist/configurator/use-zoom.d.ts +87 -0
  98. package/dist/configurator/use-zoom.js +113 -0
  99. package/dist/date/_glyphs.js +34 -0
  100. package/dist/date/_segments.js +55 -0
  101. package/dist/date/_trigger.js +31 -0
  102. package/dist/date/calendar.d.ts +25 -0
  103. package/dist/date/calendar.js +125 -0
  104. package/dist/date/date-field.d.ts +34 -0
  105. package/dist/date/date-field.js +41 -0
  106. package/dist/date/date-picker.d.ts +69 -0
  107. package/dist/date/date-picker.js +89 -0
  108. package/dist/date/date-range-picker.d.ts +65 -0
  109. package/dist/date/date-range-picker.js +92 -0
  110. package/dist/date/index.d.ts +6 -0
  111. package/dist/date/index.js +6 -0
  112. package/dist/date/time-field.d.ts +29 -0
  113. package/dist/date/time-field.js +36 -0
  114. package/dist/dmx/_ramp.d.ts +77 -0
  115. package/dist/dmx/_ramp.js +169 -0
  116. package/dist/dmx/_readout.js +53 -0
  117. package/dist/dmx/_universe.d.ts +105 -0
  118. package/dist/dmx/_universe.js +112 -0
  119. package/dist/dmx/channel-matrix.d.ts +81 -0
  120. package/dist/dmx/channel-matrix.js +285 -0
  121. package/dist/dmx/dmx-bar.d.ts +50 -0
  122. package/dist/dmx/dmx-bar.js +107 -0
  123. package/dist/dmx/dmx-strip.d.ts +41 -0
  124. package/dist/dmx/dmx-strip.js +196 -0
  125. package/dist/dmx/index.d.ts +7 -0
  126. package/dist/dmx/index.js +7 -0
  127. package/dist/dmx/universe-grid.d.ts +61 -0
  128. package/dist/dmx/universe-grid.js +182 -0
  129. package/dist/flow/_frame.d.ts +52 -0
  130. package/dist/flow/_frame.js +38 -0
  131. package/dist/flow/controls.d.ts +41 -0
  132. package/dist/flow/controls.js +53 -0
  133. package/dist/flow/flow.css +121 -0
  134. package/dist/flow/handles.d.ts +40 -0
  135. package/dist/flow/handles.js +61 -0
  136. package/dist/flow/index.d.ts +7 -0
  137. package/dist/flow/index.js +7 -0
  138. package/dist/flow/node-card.d.ts +71 -0
  139. package/dist/flow/node-card.js +102 -0
  140. package/dist/flow/signal-edge.d.ts +60 -0
  141. package/dist/flow/signal-edge.js +108 -0
  142. package/dist/flow/use-flow-theme.d.ts +69 -0
  143. package/dist/flow/use-flow-theme.js +109 -0
  144. package/dist/forms/_chassis.d.ts +64 -0
  145. package/dist/forms/_chassis.js +59 -0
  146. package/dist/forms/_glyphs.js +143 -0
  147. package/dist/forms/_listbox.js +55 -0
  148. package/dist/forms/autocomplete.d.ts +61 -0
  149. package/dist/forms/autocomplete.js +88 -0
  150. package/dist/forms/button-group.d.ts +47 -0
  151. package/dist/forms/button-group.js +52 -0
  152. package/dist/forms/checkbox.d.ts +51 -0
  153. package/dist/forms/checkbox.js +98 -0
  154. package/dist/forms/combobox.d.ts +73 -0
  155. package/dist/forms/combobox.js +108 -0
  156. package/dist/forms/env-var-input.d.ts +74 -0
  157. package/dist/forms/env-var-input.js +116 -0
  158. package/dist/forms/field.d.ts +89 -0
  159. package/dist/forms/field.js +102 -0
  160. package/dist/forms/file-upload.d.ts +113 -0
  161. package/dist/forms/file-upload.js +232 -0
  162. package/dist/forms/index.d.ts +26 -0
  163. package/dist/forms/index.js +26 -0
  164. package/dist/forms/input-group.d.ts +31 -0
  165. package/dist/forms/input-group.js +54 -0
  166. package/dist/forms/input.d.ts +33 -0
  167. package/dist/forms/input.js +36 -0
  168. package/dist/forms/multi-select.d.ts +62 -0
  169. package/dist/forms/multi-select.js +107 -0
  170. package/dist/forms/number-field.d.ts +38 -0
  171. package/dist/forms/number-field.js +64 -0
  172. package/dist/forms/otp-field.d.ts +54 -0
  173. package/dist/forms/otp-field.js +59 -0
  174. package/dist/forms/password-input.d.ts +46 -0
  175. package/dist/forms/password-input.js +62 -0
  176. package/dist/forms/radio-group.d.ts +50 -0
  177. package/dist/forms/radio-group.js +64 -0
  178. package/dist/forms/rating.d.ts +69 -0
  179. package/dist/forms/rating.js +103 -0
  180. package/dist/forms/scrub-input.d.ts +81 -0
  181. package/dist/forms/scrub-input.js +249 -0
  182. package/dist/forms/search-input.d.ts +54 -0
  183. package/dist/forms/search-input.js +112 -0
  184. package/dist/forms/select.d.ts +75 -0
  185. package/dist/forms/select.js +91 -0
  186. package/dist/forms/slider.d.ts +44 -0
  187. package/dist/forms/slider.js +65 -0
  188. package/dist/forms/switch.d.ts +36 -0
  189. package/dist/forms/switch.js +71 -0
  190. package/dist/forms/tags-input.d.ts +69 -0
  191. package/dist/forms/tags-input.js +148 -0
  192. package/dist/forms/textarea.d.ts +27 -0
  193. package/dist/forms/textarea.js +37 -0
  194. package/dist/forms/toggle-group.d.ts +41 -0
  195. package/dist/forms/toggle-group.js +56 -0
  196. package/dist/forms/toggle.d.ts +41 -0
  197. package/dist/forms/toggle.js +100 -0
  198. package/dist/index.d.ts +112 -0
  199. package/dist/index.js +104 -0
  200. package/dist/instruments/_ansi.js +116 -0
  201. package/dist/instruments/_glyphs.js +90 -0
  202. package/dist/instruments/_motion.js +56 -0
  203. package/dist/instruments/_rem.js +21 -0
  204. package/dist/instruments/app-window-frame.d.ts +48 -0
  205. package/dist/instruments/app-window-frame.js +76 -0
  206. package/dist/instruments/copy-button.d.ts +41 -0
  207. package/dist/instruments/copy-button.js +112 -0
  208. package/dist/instruments/data-table.d.ts +168 -0
  209. package/dist/instruments/data-table.js +212 -0
  210. package/dist/instruments/group-bar.d.ts +63 -0
  211. package/dist/instruments/group-bar.js +122 -0
  212. package/dist/instruments/index.d.ts +13 -0
  213. package/dist/instruments/index.js +13 -0
  214. package/dist/instruments/log-viewer.d.ts +74 -0
  215. package/dist/instruments/log-viewer.js +215 -0
  216. package/dist/instruments/meter.d.ts +77 -0
  217. package/dist/instruments/meter.js +96 -0
  218. package/dist/instruments/progress.d.ts +45 -0
  219. package/dist/instruments/progress.js +61 -0
  220. package/dist/instruments/seam-grid.d.ts +66 -0
  221. package/dist/instruments/seam-grid.js +78 -0
  222. package/dist/instruments/sparkline.d.ts +39 -0
  223. package/dist/instruments/sparkline.js +85 -0
  224. package/dist/instruments/stat.d.ts +35 -0
  225. package/dist/instruments/stat.js +59 -0
  226. package/dist/instruments/table.d.ts +51 -0
  227. package/dist/instruments/table.js +92 -0
  228. package/dist/instruments/terminal-frame.d.ts +89 -0
  229. package/dist/instruments/terminal-frame.js +256 -0
  230. package/dist/instruments/two-step-button.d.ts +47 -0
  231. package/dist/instruments/two-step-button.js +147 -0
  232. package/dist/instruments/usage-chart.d.ts +55 -0
  233. package/dist/instruments/usage-chart.js +235 -0
  234. package/dist/layout/_collapse.js +46 -0
  235. package/dist/layout/_glyphs.js +45 -0
  236. package/dist/layout/accordion.d.ts +52 -0
  237. package/dist/layout/accordion.js +81 -0
  238. package/dist/layout/breadcrumb.d.ts +35 -0
  239. package/dist/layout/breadcrumb.js +67 -0
  240. package/dist/layout/card.d.ts +51 -0
  241. package/dist/layout/card.js +76 -0
  242. package/dist/layout/carousel.d.ts +96 -0
  243. package/dist/layout/carousel.js +211 -0
  244. package/dist/layout/collapsible.d.ts +74 -0
  245. package/dist/layout/collapsible.js +126 -0
  246. package/dist/layout/container.d.ts +27 -0
  247. package/dist/layout/container.js +40 -0
  248. package/dist/layout/description-list.d.ts +24 -0
  249. package/dist/layout/description-list.js +37 -0
  250. package/dist/layout/grid.d.ts +40 -0
  251. package/dist/layout/grid.js +48 -0
  252. package/dist/layout/index.d.ts +15 -0
  253. package/dist/layout/index.js +15 -0
  254. package/dist/layout/item.d.ts +65 -0
  255. package/dist/layout/item.js +100 -0
  256. package/dist/layout/link.d.ts +48 -0
  257. package/dist/layout/link.js +63 -0
  258. package/dist/layout/pagination.d.ts +54 -0
  259. package/dist/layout/pagination.js +121 -0
  260. package/dist/layout/resizable.d.ts +38 -0
  261. package/dist/layout/resizable.js +68 -0
  262. package/dist/layout/sidebar.d.ts +67 -0
  263. package/dist/layout/sidebar.js +129 -0
  264. package/dist/layout/stack.d.ts +46 -0
  265. package/dist/layout/stack.js +62 -0
  266. package/dist/layout/stepper.d.ts +49 -0
  267. package/dist/layout/stepper.js +81 -0
  268. package/dist/layout/timeline.d.ts +43 -0
  269. package/dist/layout/timeline.js +63 -0
  270. package/dist/lib/aria-class.js +24 -0
  271. package/dist/lib/cn.js +18 -0
  272. package/dist/lib/compose.js +38 -0
  273. package/dist/lib/cva.d.ts +18 -0
  274. package/dist/lib/cva.js +65 -0
  275. package/dist/midi/_notes.d.ts +101 -0
  276. package/dist/midi/_notes.js +183 -0
  277. package/dist/midi/_ruler.d.ts +100 -0
  278. package/dist/midi/_ruler.js +105 -0
  279. package/dist/midi/_time.d.ts +107 -0
  280. package/dist/midi/_time.js +130 -0
  281. package/dist/midi/index.d.ts +9 -0
  282. package/dist/midi/index.js +9 -0
  283. package/dist/midi/musical-time-input.d.ts +48 -0
  284. package/dist/midi/musical-time-input.js +154 -0
  285. package/dist/midi/piano-keyboard.d.ts +50 -0
  286. package/dist/midi/piano-keyboard.js +193 -0
  287. package/dist/midi/spectrum-visualizer.d.ts +65 -0
  288. package/dist/midi/spectrum-visualizer.js +213 -0
  289. package/dist/midi/threshold-rail.d.ts +72 -0
  290. package/dist/midi/threshold-rail.js +214 -0
  291. package/dist/midi/timeline-ruler.d.ts +83 -0
  292. package/dist/midi/timeline-ruler.js +152 -0
  293. package/dist/overlays/_debounce.js +34 -0
  294. package/dist/overlays/_glyphs.js +59 -0
  295. package/dist/overlays/_surface.d.ts +70 -0
  296. package/dist/overlays/_surface.js +70 -0
  297. package/dist/overlays/_wheel.js +42 -0
  298. package/dist/overlays/alert-dialog.d.ts +67 -0
  299. package/dist/overlays/alert-dialog.js +117 -0
  300. package/dist/overlays/command-palette.d.ts +93 -0
  301. package/dist/overlays/command-palette.js +193 -0
  302. package/dist/overlays/context-menu.d.ts +64 -0
  303. package/dist/overlays/context-menu.js +78 -0
  304. package/dist/overlays/dialog.d.ts +72 -0
  305. package/dist/overlays/dialog.js +124 -0
  306. package/dist/overlays/drawer.d.ts +75 -0
  307. package/dist/overlays/drawer.js +151 -0
  308. package/dist/overlays/dropdown-menu.d.ts +79 -0
  309. package/dist/overlays/dropdown-menu.js +156 -0
  310. package/dist/overlays/hover-card.d.ts +58 -0
  311. package/dist/overlays/hover-card.js +91 -0
  312. package/dist/overlays/index.d.ts +15 -0
  313. package/dist/overlays/index.js +15 -0
  314. package/dist/overlays/info-tip.d.ts +41 -0
  315. package/dist/overlays/info-tip.js +45 -0
  316. package/dist/overlays/menubar.d.ts +72 -0
  317. package/dist/overlays/menubar.js +100 -0
  318. package/dist/overlays/popover.d.ts +66 -0
  319. package/dist/overlays/popover.js +114 -0
  320. package/dist/overlays/sheet.d.ts +78 -0
  321. package/dist/overlays/sheet.js +131 -0
  322. package/dist/overlays/toast.d.ts +106 -0
  323. package/dist/overlays/toast.js +146 -0
  324. package/dist/overlays/tooltip.d.ts +57 -0
  325. package/dist/overlays/tooltip.js +95 -0
  326. package/dist/primitives/avatar.d.ts +51 -0
  327. package/dist/primitives/avatar.js +93 -0
  328. package/dist/primitives/button.d.ts +46 -0
  329. package/dist/primitives/button.js +94 -0
  330. package/dist/primitives/chip.d.ts +47 -0
  331. package/dist/primitives/chip.js +152 -0
  332. package/dist/primitives/icon-button.d.ts +41 -0
  333. package/dist/primitives/icon-button.js +59 -0
  334. package/dist/primitives/index.d.ts +10 -0
  335. package/dist/primitives/index.js +10 -0
  336. package/dist/primitives/kbd.d.ts +17 -0
  337. package/dist/primitives/kbd.js +24 -0
  338. package/dist/primitives/separator.d.ts +25 -0
  339. package/dist/primitives/separator.js +30 -0
  340. package/dist/primitives/skeleton.d.ts +20 -0
  341. package/dist/primitives/skeleton.js +27 -0
  342. package/dist/primitives/spinner.d.ts +26 -0
  343. package/dist/primitives/spinner.js +31 -0
  344. package/dist/primitives/status-dot.d.ts +44 -0
  345. package/dist/primitives/status-dot.js +50 -0
  346. package/dist/styles.css +19 -0
  347. package/dist/system/density.d.ts +33 -0
  348. package/dist/system/density.js +49 -0
  349. package/dist/system/index.d.ts +8 -0
  350. package/dist/system/index.js +7 -0
  351. package/dist/system/portal.d.ts +51 -0
  352. package/dist/system/portal.js +122 -0
  353. package/dist/system/prepaint.d.ts +38 -0
  354. package/dist/system/prepaint.js +37 -0
  355. package/dist/system/theme-provider.d.ts +72 -0
  356. package/dist/system/theme-provider.js +252 -0
  357. package/dist/system/use-density.d.ts +33 -0
  358. package/dist/system/use-density.js +84 -0
  359. package/dist/system/use-isomorphic-layout-effect.js +15 -0
  360. package/dist/system/use-theme.d.ts +19 -0
  361. package/dist/system/use-theme.js +25 -0
  362. package/dist/theming/_presets.d.ts +11 -0
  363. package/dist/theming/_presets.js +487 -0
  364. package/dist/theming/contrast.d.ts +110 -0
  365. package/dist/theming/contrast.js +167 -0
  366. package/dist/theming/create-theme.d.ts +165 -0
  367. package/dist/theming/create-theme.js +442 -0
  368. package/dist/theming/index.d.ts +5 -0
  369. package/dist/theming/index.js +5 -0
  370. package/dist/theming/serialize.d.ts +145 -0
  371. package/dist/theming/serialize.js +165 -0
  372. package/package.json +216 -0
@@ -0,0 +1,215 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { Chip } from "../primitives/chip.js";
4
+ import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
5
+ import { toRem } from "./_rem.js";
6
+ import { useControlHeight } from "../system/use-density.js";
7
+ import { ansiSpans } from "./_ansi.js";
8
+ import * as React from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { useVirtualizer } from "@tanstack/react-virtual";
11
+ //#region src/instruments/log-viewer.tsx
12
+ /** Canonical order for the filter strip — quietest first, loudest last. */
13
+ const LEVEL_ORDER = [
14
+ "debug",
15
+ "info",
16
+ "warn",
17
+ "error"
18
+ ];
19
+ const LEVEL_TONES = {
20
+ debug: "text-fg-subtle",
21
+ info: "text-fg-muted",
22
+ warn: "text-warn",
23
+ error: "text-danger"
24
+ };
25
+ const LEVEL_CHIP_TONES = {
26
+ debug: "neutral",
27
+ info: "info",
28
+ warn: "warn",
29
+ error: "danger"
30
+ };
31
+ /** How many lines are kept before the head is dropped. */
32
+ const DEFAULT_MAX_LINES = 5e3;
33
+ /** Rows rendered beyond the viewport, so a fast scroll never shows white. */
34
+ const OVERSCAN = 8;
35
+ /** Slack, in px, for "close enough to the bottom to count as pinned". */
36
+ const BOTTOM_TOLERANCE = 4;
37
+ /** Local wall-clock, seconds resolution — the only part of a log timestamp a reader scans. */
38
+ function defaultTimestamp(ts) {
39
+ return new Date(ts).toLocaleTimeString(void 0, {
40
+ hour: "2-digit",
41
+ minute: "2-digit",
42
+ second: "2-digit",
43
+ hour12: false
44
+ });
45
+ }
46
+ /**
47
+ * A virtualised log tail: ANSI colour, level filters, and stick-to-bottom.
48
+ *
49
+ * The four things a log pane has to get right, and the reasons each one is not
50
+ * optional:
51
+ *
52
+ * - **Virtualised.** Five thousand DOM rows is a frozen tab. Rows are fixed at
53
+ * the short control rung so the virtualiser never has to measure, which is
54
+ * also what keeps the scrollbar honest while lines stream in.
55
+ * - **ANSI as data, never as HTML.** `anser` will happily hand back a markup
56
+ * string, and the moment that reaches `dangerouslySetInnerHTML` any process
57
+ * that prints a `<script>` owns the console watching it. Colour arrives here
58
+ * as spans of *text* with resolved token colours (see `_ansi.ts`).
59
+ * - **Following is a mode, not a scroll.** While pinned to the bottom the view
60
+ * follows; scroll up and it stops and starts counting, so reading something
61
+ * is never fought by the stream. The count comes back as a pill, not as a
62
+ * jump.
63
+ * - **A cap.** An unbounded buffer is a memory leak with a progress bar. The
64
+ * head is dropped past `maxLines`.
65
+ *
66
+ * Ships from `@cueplusplus/ui/instruments/log-viewer`: it needs
67
+ * `@tanstack/react-virtual` and `anser`, both optional peers, and neither
68
+ * belongs in the bundle of an app that renders no logs.
69
+ *
70
+ * @example
71
+ * <LogViewer aria-label="Build output" height={320} lines={lines} />
72
+ * @example
73
+ * <LogViewer aria-label="Worker" height={200} follow={false} filters={["warn", "error"]} lines={lines} />
74
+ */
75
+ const LogViewer = React.forwardRef(function LogViewer({ className, lines, follow = true, filters, height, rowHeight, maxLines = DEFAULT_MAX_LINES, formatTimestamp = defaultTimestamp, emptyLabel = "No output", newLinesLabel = (count) => `${count} new line${count === 1 ? "" : "s"}`, "aria-label": ariaLabel, ...elementProps }, ref) {
76
+ const measuredRow = useControlHeight("sm");
77
+ const row = rowHeight ?? measuredRow;
78
+ const scrollerRef = React.useRef(null);
79
+ const [muted, setMuted] = React.useState([]);
80
+ const [pinned, setPinned] = React.useState(true);
81
+ const [pending, setPending] = React.useState(0);
82
+ const capped = React.useMemo(() => lines.length > maxLines ? lines.slice(lines.length - maxLines) : lines, [lines, maxLines]);
83
+ const chips = React.useMemo(() => {
84
+ if (filters === false) return [];
85
+ if (filters !== void 0) return filters;
86
+ const present = new Set(capped.map((line) => line.level).filter(Boolean));
87
+ return LEVEL_ORDER.filter((level) => present.has(level));
88
+ }, [capped, filters]);
89
+ const hidden = React.useMemo(() => muted.filter((level) => chips.includes(level)), [chips, muted]);
90
+ const visible = React.useMemo(() => hidden.length === 0 ? capped : capped.filter((line) => line.level === void 0 || !hidden.includes(line.level)), [capped, hidden]);
91
+ const virtualizer = useVirtualizer({
92
+ count: visible.length,
93
+ getScrollElement: () => scrollerRef.current,
94
+ estimateSize: () => row,
95
+ getItemKey: (index) => visible[index].id,
96
+ overscan: OVERSCAN
97
+ });
98
+ const jumpToBottom = React.useCallback(() => {
99
+ const element = scrollerRef.current;
100
+ if (element === null) return;
101
+ element.scrollTop = element.scrollHeight;
102
+ setPinned(true);
103
+ setPending(0);
104
+ }, []);
105
+ const previousCountRef = React.useRef(visible.length);
106
+ useIsomorphicLayoutEffect(() => {
107
+ const element = scrollerRef.current;
108
+ const added = visible.length - previousCountRef.current;
109
+ previousCountRef.current = visible.length;
110
+ if (element === null) return;
111
+ if (follow && pinned) {
112
+ element.scrollTop = element.scrollHeight;
113
+ setPending(0);
114
+ return;
115
+ }
116
+ if (added > 0) setPending((current) => current + added);
117
+ }, [
118
+ follow,
119
+ pinned,
120
+ visible.length
121
+ ]);
122
+ const onScroll = React.useCallback(() => {
123
+ const element = scrollerRef.current;
124
+ if (element === null) return;
125
+ const atBottom = element.scrollHeight - element.scrollTop - element.clientHeight <= BOTTOM_TOLERANCE;
126
+ setPinned(atBottom);
127
+ if (atBottom) setPending(0);
128
+ }, []);
129
+ const items = virtualizer.getVirtualItems();
130
+ return /* @__PURE__ */ jsxs("div", {
131
+ ref,
132
+ "data-slot": "log-viewer",
133
+ className: cn("relative flex min-w-0 flex-col overflow-hidden rounded-(--radius-surface) border border-border bg-sunken", className),
134
+ style: { height: toRem(height) },
135
+ ...elementProps,
136
+ children: [
137
+ chips.length === 0 ? null : /* @__PURE__ */ jsx("div", {
138
+ "data-slot": "log-viewer-filters",
139
+ className: "flex shrink-0 items-center gap-(--cue-space-2) border-b border-border bg-surface-1 px-(--cue-pad-row-x) py-(--cue-space-2)",
140
+ children: chips.map((level) => {
141
+ const active = !muted.includes(level);
142
+ return /* @__PURE__ */ jsx(Chip, {
143
+ interactive: true,
144
+ "aria-pressed": active,
145
+ "data-level": level,
146
+ tone: LEVEL_CHIP_TONES[level],
147
+ variant: active ? "tinted" : "outline",
148
+ className: active ? void 0 : "opacity-50",
149
+ onClick: () => setMuted((current) => current.includes(level) ? current.filter((entry) => entry !== level) : [...current, level]),
150
+ children: level
151
+ }, level);
152
+ })
153
+ }),
154
+ /* @__PURE__ */ jsx("div", {
155
+ ref: scrollerRef,
156
+ role: "log",
157
+ "aria-label": ariaLabel,
158
+ tabIndex: 0,
159
+ "data-slot": "log-viewer-scroller",
160
+ className: "min-h-0 flex-1 overflow-auto font-mono text-(length:--cue-text-label) leading-none outline-none focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-2 focus-visible:outline-accent",
161
+ onScroll,
162
+ children: visible.length === 0 ? /* @__PURE__ */ jsx("div", {
163
+ "data-slot": "log-viewer-empty",
164
+ className: "flex h-full items-center justify-center text-fg-subtle",
165
+ children: emptyLabel
166
+ }) : /* @__PURE__ */ jsx("div", {
167
+ "data-slot": "log-viewer-canvas",
168
+ className: "relative w-full",
169
+ style: { height: `${virtualizer.getTotalSize()}px` },
170
+ children: items.map((item) => {
171
+ const line = visible[item.index];
172
+ return /* @__PURE__ */ jsxs("div", {
173
+ "data-slot": "log-viewer-row",
174
+ "data-level": line.level,
175
+ className: cn("absolute top-0 left-0 flex w-full items-center gap-(--cue-space-3) overflow-hidden px-(--cue-pad-row-x) whitespace-pre", LEVEL_TONES[line.level ?? "info"]),
176
+ style: {
177
+ height: `${item.size}px`,
178
+ transform: `translateY(${item.start}px)`
179
+ },
180
+ children: [line.ts === void 0 ? null : /* @__PURE__ */ jsx("span", {
181
+ "data-slot": "log-viewer-ts",
182
+ className: "shrink-0 tabular-nums text-fg-subtle",
183
+ children: formatTimestamp(line.ts)
184
+ }), /* @__PURE__ */ jsx("span", {
185
+ "data-slot": "log-viewer-text",
186
+ className: "min-w-0 flex-1 overflow-hidden",
187
+ children: line.ansi === true ? ansiSpans(line.text).map((span, index) => /* @__PURE__ */ jsx("span", {
188
+ "data-slot": "log-viewer-span",
189
+ style: {
190
+ color: span.color,
191
+ backgroundColor: span.background,
192
+ fontWeight: span.bold ? 600 : void 0,
193
+ fontStyle: span.italic ? "italic" : void 0,
194
+ opacity: span.dim ? .6 : void 0,
195
+ textDecoration: span.underline ? "underline" : span.strikethrough ? "line-through" : void 0
196
+ },
197
+ children: span.text
198
+ }, index)) : line.text
199
+ })]
200
+ }, item.key);
201
+ })
202
+ })
203
+ }),
204
+ pending > 0 ? /* @__PURE__ */ jsx("button", {
205
+ type: "button",
206
+ "data-slot": "log-viewer-jump",
207
+ className: "absolute bottom-(--cue-space-4) left-1/2 -translate-x-1/2 cursor-pointer rounded-(--radius-control) border border-border-overlay bg-surface-2 px-(--cue-space-4) py-(--cue-space-2) font-mono text-(length:--cue-text-label) text-fg shadow-[0_8px_24px_rgba(0,0,0,0.4)] outline-none hover:border-accent hover:text-accent focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-1 focus-visible:outline-accent",
208
+ onClick: jumpToBottom,
209
+ children: newLinesLabel(pending)
210
+ }) : null
211
+ ]
212
+ });
213
+ });
214
+ //#endregion
215
+ export { LogViewer };
@@ -0,0 +1,77 @@
1
+ import * as React from "react";
2
+ //#region src/instruments/meter.d.ts
3
+ /**
4
+ * The shared status vocabulary, minus the two tones a fill level cannot mean:
5
+ * `info` (a meter is never advisory) and `neutral` (a level always has a
6
+ * reading). Same tokens as `Chip`, `StatusDot` and `Toast`.
7
+ */
8
+ type MeterTone = "ok" | "busy" | "warn" | "danger";
9
+ /**
10
+ * Where the ceiling came from — the meter's honesty dial.
11
+ *
12
+ * - `vendor` — the provider stated the limit. Plain fill; full authority.
13
+ * - `estimated` — somebody typed the limit. Hatched, so an estimate never
14
+ * wears the authority of a reported number.
15
+ * - `unbounded` — there is no denominator, so no percentage exists. Drawn as a
16
+ * wash with no value at all: an empty bar would read as "0% used" and a full
17
+ * one as "exhausted", and both are inventions.
18
+ * - `none` — provenance not stated. Plain, and says nothing it cannot back up.
19
+ */
20
+ type MeterProvenance = "vendor" | "estimated" | "unbounded" | "none";
21
+ interface MeterProps extends Omit<React.ComponentPropsWithoutRef<"div">, "children"> {
22
+ /** How full, 0–100. Clamped; a non-finite value reads as 0. */
23
+ percent: number;
24
+ /** Status colour for the fill. Defaults to `"ok"`. */
25
+ tone?: MeterTone;
26
+ /**
27
+ * How far through the window we are *by time*, 0–1. Drawn as a 1px rule
28
+ * across the bar so the fill can be read against its pace: fill ahead of the
29
+ * tick means burning faster than the window refills. Pass `null` when there
30
+ * is no window.
31
+ */
32
+ paceTick?: number | null;
33
+ /** Render the striped "no data" treatment instead of a level. */
34
+ empty?: boolean;
35
+ /** Where the ceiling came from. Defaults to `"vendor"`. */
36
+ provenance?: MeterProvenance;
37
+ /** Bar height in px at the 16px root, emitted as `rem`. Defaults to `4`. */
38
+ height?: number;
39
+ /**
40
+ * The accessible name. A meter is nearly always a usage readout, so that is
41
+ * the default; override it for anything else. When there is no level to
42
+ * report (`empty`, or an unbounded ceiling) the state is appended to it, as
43
+ * in `"Usage: No data"`.
44
+ */
45
+ label?: string;
46
+ }
47
+ /**
48
+ * A quota bar that shows **pace**, not just fill.
49
+ *
50
+ * A bare percentage answers "how much is gone" but not the question that
51
+ * actually matters: *will it last*. 93% used is fine in the last hour of a
52
+ * weekly window and an emergency on the first day. So the meter carries a
53
+ * second mark — how far through the window we are in time — and reads the two
54
+ * together: fill ahead of the tick is burning faster than the window refills.
55
+ *
56
+ * The tick is deliberately quiet: a 1px rule, not a second bar. It annotates
57
+ * the fill and stays ignorable when someone only wants the number.
58
+ *
59
+ * Severity is never invented here — `tone` comes from whoever owns the
60
+ * thresholds. Likewise `provenance`: the bar changes its own texture rather
61
+ * than letting an estimate or a missing denominator look like a measurement.
62
+ *
63
+ * The over-pace pulse stills under `prefers-reduced-motion`; the state stays
64
+ * readable as `data-over-pace`.
65
+ *
66
+ * Static markup — no `"use client"`.
67
+ *
68
+ * @example
69
+ * <Meter percent={72} tone="warn" paceTick={0.5} />
70
+ * @example
71
+ * <Meter percent={0} empty label="Requests this month" />
72
+ * @example
73
+ * <Meter percent={40} provenance="estimated" />
74
+ */
75
+ declare const Meter: React.ForwardRefExoticComponent<MeterProps & React.RefAttributes<HTMLDivElement>>;
76
+ //#endregion
77
+ export { Meter, MeterProps, MeterProvenance, MeterTone };
@@ -0,0 +1,96 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { toRem } from "./_rem.js";
3
+ import * as React from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/instruments/meter.tsx
6
+ const TONE_CLASSES = {
7
+ ok: "text-ok",
8
+ busy: "text-busy",
9
+ warn: "text-warn",
10
+ danger: "text-danger"
11
+ };
12
+ /** No-data stripes, drawn in the border colour so they read as "not a level". */
13
+ const EMPTY_STRIPES = "repeating-linear-gradient(135deg, var(--cue-border) 0 2px, transparent 2px 6px)";
14
+ /**
15
+ * The hatch over an estimated fill: cut in the page ground rather than a fixed
16
+ * black, so it reads the same way in light themes.
17
+ */
18
+ const ESTIMATED_HATCH = "repeating-linear-gradient(135deg, transparent 0 2px, color-mix(in oklch, var(--cue-bg) 65%, transparent) 2px 4px)";
19
+ /** An unbounded meter fades out instead of ending: there is no end. */
20
+ const UNBOUNDED_WASH = "linear-gradient(to right, color-mix(in oklch, currentColor 45%, transparent), transparent)";
21
+ /** How far past the pace tick the fill must run before the bar calls it out. */
22
+ const OVER_PACE_MARGIN = .1;
23
+ /** Default bar height in px at the 16px root — a rule, not a trough. */
24
+ const DEFAULT_HEIGHT_PX = 4;
25
+ /**
26
+ * A quota bar that shows **pace**, not just fill.
27
+ *
28
+ * A bare percentage answers "how much is gone" but not the question that
29
+ * actually matters: *will it last*. 93% used is fine in the last hour of a
30
+ * weekly window and an emergency on the first day. So the meter carries a
31
+ * second mark — how far through the window we are in time — and reads the two
32
+ * together: fill ahead of the tick is burning faster than the window refills.
33
+ *
34
+ * The tick is deliberately quiet: a 1px rule, not a second bar. It annotates
35
+ * the fill and stays ignorable when someone only wants the number.
36
+ *
37
+ * Severity is never invented here — `tone` comes from whoever owns the
38
+ * thresholds. Likewise `provenance`: the bar changes its own texture rather
39
+ * than letting an estimate or a missing denominator look like a measurement.
40
+ *
41
+ * The over-pace pulse stills under `prefers-reduced-motion`; the state stays
42
+ * readable as `data-over-pace`.
43
+ *
44
+ * Static markup — no `"use client"`.
45
+ *
46
+ * @example
47
+ * <Meter percent={72} tone="warn" paceTick={0.5} />
48
+ * @example
49
+ * <Meter percent={0} empty label="Requests this month" />
50
+ * @example
51
+ * <Meter percent={40} provenance="estimated" />
52
+ */
53
+ const Meter = React.forwardRef(function Meter({ className, percent, tone = "ok", paceTick, empty = false, provenance = "vendor", height = DEFAULT_HEIGHT_PX, label = "Usage", style, ...elementProps }, ref) {
54
+ const unbounded = provenance === "unbounded";
55
+ const pct = Math.min(100, Math.max(0, Number.isFinite(percent) ? percent : 0));
56
+ const hasLevel = !unbounded && !empty;
57
+ const stateText = unbounded ? "Unbounded" : empty ? "No data" : null;
58
+ const showTick = !unbounded && typeof paceTick === "number" && paceTick > .02 && paceTick < .98;
59
+ const overPace = hasLevel && showTick && pct / 100 > paceTick + OVER_PACE_MARGIN;
60
+ const trackImage = unbounded ? UNBOUNDED_WASH : empty ? EMPTY_STRIPES : void 0;
61
+ return /* @__PURE__ */ jsxs("div", {
62
+ ref,
63
+ role: hasLevel ? "meter" : "img",
64
+ "aria-label": stateText === null ? label : `${label}: ${stateText}`,
65
+ "aria-valuemin": hasLevel ? 0 : void 0,
66
+ "aria-valuemax": hasLevel ? 100 : void 0,
67
+ "aria-valuenow": hasLevel ? Math.round(pct) : void 0,
68
+ "data-slot": "meter",
69
+ "data-tone": tone,
70
+ "data-provenance": provenance,
71
+ "data-empty": empty ? "true" : void 0,
72
+ "data-over-pace": overPace ? "true" : void 0,
73
+ className: cn("relative w-full overflow-hidden rounded-full bg-surface-3", TONE_CLASSES[tone], className),
74
+ style: {
75
+ height: toRem(height),
76
+ backgroundImage: trackImage,
77
+ ...style
78
+ },
79
+ ...elementProps,
80
+ children: [hasLevel ? /* @__PURE__ */ jsx("div", {
81
+ "data-slot": "meter-fill",
82
+ className: cn("absolute inset-y-0 left-0 bg-current transition-[width] duration-150 ease-out motion-reduce:transition-none", overPace ? "animate-pulse motion-reduce:animate-none" : null),
83
+ style: {
84
+ width: `${pct}%`,
85
+ backgroundImage: provenance === "estimated" ? ESTIMATED_HATCH : void 0
86
+ }
87
+ }) : null, showTick ? /* @__PURE__ */ jsx("div", {
88
+ "data-slot": "meter-tick",
89
+ "aria-hidden": "true",
90
+ className: "absolute inset-y-0 w-px bg-fg/55",
91
+ style: { left: `${paceTick * 100}%` }
92
+ }) : null]
93
+ });
94
+ });
95
+ //#endregion
96
+ export { Meter };
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+ import { Progress } from "@base-ui/react/progress";
3
+ //#region src/instruments/progress.d.ts
4
+ interface ProgressProps extends Omit<Progress.Root.Props, "className"> {
5
+ /** Extra classes for the frame that holds the header and the track. */
6
+ className?: string;
7
+ /** Extra classes for the track. */
8
+ trackClassName?: string;
9
+ /** Extra classes for the fill. */
10
+ indicatorClassName?: string;
11
+ /**
12
+ * Caption above the bar. Rendered as Base UI's `Progress.Label`, so it also
13
+ * becomes the bar's accessible name — pass one, or an `aria-label`.
14
+ */
15
+ label?: React.ReactNode;
16
+ /** Show the formatted value opposite the label. Defaults to `false`. */
17
+ showValue?: boolean;
18
+ /** Track height in px at the 16px root, emitted as `rem`. Defaults to `5`. */
19
+ height?: number;
20
+ }
21
+ /**
22
+ * Determinate work, drawn as a filling rule.
23
+ *
24
+ * Wraps Base UI's `Progress`, so `role="progressbar"`, the value formatting and
25
+ * the completion status are the primitive's; this layer adds the console skin:
26
+ * a sunk `surface-3` track, an accent fill and a 120ms linear width transition
27
+ * — linear because a progress bar that eases is lying about its rate.
28
+ *
29
+ * Passing `value={null}` makes it indeterminate: the fill collapses and the
30
+ * track pulses instead, which says "working, size unknown" without inventing a
31
+ * number. The pulse stills under `prefers-reduced-motion`.
32
+ *
33
+ * Use `Meter` instead for a *level* (how full something is); a progress bar is
34
+ * for a *task* (how far along something is).
35
+ *
36
+ * Static markup — no `"use client"`.
37
+ *
38
+ * @example
39
+ * <Progress value={40} label="Rendering" showValue />
40
+ * @example
41
+ * <Progress value={null} aria-label="Connecting" />
42
+ */
43
+ declare const Progress$1: React.ForwardRefExoticComponent<Omit<ProgressProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
44
+ //#endregion
45
+ export { Progress$1 as Progress, ProgressProps };
@@ -0,0 +1,61 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { toRem } from "./_rem.js";
3
+ import * as React from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Progress } from "@base-ui/react/progress";
6
+ //#region src/instruments/progress.tsx
7
+ /** Default track height in px at the 16px root. */
8
+ const DEFAULT_HEIGHT_PX = 5;
9
+ /**
10
+ * Determinate work, drawn as a filling rule.
11
+ *
12
+ * Wraps Base UI's `Progress`, so `role="progressbar"`, the value formatting and
13
+ * the completion status are the primitive's; this layer adds the console skin:
14
+ * a sunk `surface-3` track, an accent fill and a 120ms linear width transition
15
+ * — linear because a progress bar that eases is lying about its rate.
16
+ *
17
+ * Passing `value={null}` makes it indeterminate: the fill collapses and the
18
+ * track pulses instead, which says "working, size unknown" without inventing a
19
+ * number. The pulse stills under `prefers-reduced-motion`.
20
+ *
21
+ * Use `Meter` instead for a *level* (how full something is); a progress bar is
22
+ * for a *task* (how far along something is).
23
+ *
24
+ * Static markup — no `"use client"`.
25
+ *
26
+ * @example
27
+ * <Progress value={40} label="Rendering" showValue />
28
+ * @example
29
+ * <Progress value={null} aria-label="Connecting" />
30
+ */
31
+ const Progress$1 = React.forwardRef(function Progress$2({ className, trackClassName, indicatorClassName, label, showValue = false, height = DEFAULT_HEIGHT_PX, ...rootProps }, ref) {
32
+ const hasHeader = label !== void 0 || showValue;
33
+ return /* @__PURE__ */ jsxs(Progress.Root, {
34
+ ref,
35
+ "data-slot": "progress",
36
+ className: cn("flex w-full flex-col gap-(--cue-space-2)", className),
37
+ ...rootProps,
38
+ children: [hasHeader ? /* @__PURE__ */ jsxs("div", {
39
+ "data-slot": "progress-header",
40
+ className: "flex items-baseline gap-(--cue-space-3) font-mono text-(length:--cue-text-label)",
41
+ children: [label === void 0 ? null : /* @__PURE__ */ jsx(Progress.Label, {
42
+ "data-slot": "progress-label",
43
+ className: "truncate tracking-[0.15em] text-fg-subtle uppercase",
44
+ children: label
45
+ }), showValue ? /* @__PURE__ */ jsx(Progress.Value, {
46
+ "data-slot": "progress-value",
47
+ className: "ml-auto shrink-0 tabular-nums text-fg-muted"
48
+ }) : null]
49
+ }) : null, /* @__PURE__ */ jsx(Progress.Track, {
50
+ "data-slot": "progress-track",
51
+ className: cn("relative w-full overflow-hidden rounded-full bg-surface-3 data-[indeterminate]:animate-pulse motion-reduce:animate-none", trackClassName),
52
+ style: { height: toRem(height) },
53
+ children: /* @__PURE__ */ jsx(Progress.Indicator, {
54
+ "data-slot": "progress-indicator",
55
+ className: cn("bg-accent transition-[width] duration-[120ms] ease-linear motion-reduce:transition-none", indicatorClassName)
56
+ })
57
+ })]
58
+ });
59
+ });
60
+ //#endregion
61
+ export { Progress$1 as Progress };
@@ -0,0 +1,66 @@
1
+ import * as React from "react";
2
+ //#region src/instruments/seam-grid.d.ts
3
+ /**
4
+ * The surface a seam cell can be filled with.
5
+ *
6
+ * Anything from the elevation ladder works; what matters is that the *cell* is
7
+ * filled and the *container* is not, because the container's colour is what
8
+ * shows through the gap and becomes the seam.
9
+ */
10
+ type SeamGround = "bg" | "sunken" | "surface-1" | "surface-2" | "surface-3";
11
+ interface SeamGridProps extends React.ComponentPropsWithoutRef<"div"> {
12
+ /** Fixed column count. Ignored when {@link SeamGridProps.min} is set. */
13
+ columns?: number;
14
+ /**
15
+ * Minimum cell width in px at the 16px root. Switches the track to
16
+ * `auto-fit`, so the grid reflows without a media query.
17
+ */
18
+ min?: number;
19
+ /** Draw a hairline rim around the whole grid as well as between the cells. */
20
+ bordered?: boolean;
21
+ }
22
+ /**
23
+ * The seam idiom as a component: cells separated by hairlines, no borders.
24
+ *
25
+ * A grid whose `gap` is 1px over the border colour. Every cell paints its own
26
+ * ground, so the only place the container shows through is the gap — which
27
+ * reads as a hairline between neighbours, aligns perfectly at every corner, and
28
+ * never doubles up. This is snuffle's signature primitive; it was inlined at
29
+ * eighteen call sites before it was a component.
30
+ *
31
+ * Static markup — no `"use client"`.
32
+ *
33
+ * @example
34
+ * <SeamGrid columns={3}>
35
+ * <SeamCell>001</SeamCell>
36
+ * <SeamCell>002</SeamCell>
37
+ * <SeamCell>003</SeamCell>
38
+ * </SeamGrid>
39
+ * @example
40
+ * // Reflowing, no media query:
41
+ * <SeamGrid min={180} bordered>{cells}</SeamGrid>
42
+ */
43
+ declare const SeamGrid: React.ForwardRefExoticComponent<SeamGridProps & React.RefAttributes<HTMLDivElement>>;
44
+ interface SeamCellProps extends React.ComponentPropsWithoutRef<"div"> {
45
+ /** Which rung of the elevation ladder to fill with. Defaults to `"surface-1"`. */
46
+ ground?: SeamGround;
47
+ /** Add the hover feedback of a clickable cell. Put the control inside it. */
48
+ interactive?: boolean;
49
+ /** Drop the built-in padding — for a cell that holds a chart or a canvas. */
50
+ flush?: boolean;
51
+ }
52
+ /**
53
+ * One filled cell of a {@link SeamGrid} — or of the Phase 1 `SeamList`.
54
+ *
55
+ * The fill is the load-bearing part: an unfilled cell lets the container's
56
+ * border colour through everywhere, not just at the gap, and the seam
57
+ * disappears into a solid block.
58
+ *
59
+ * Static markup — no `"use client"`.
60
+ *
61
+ * @example
62
+ * <SeamCell ground="sunken" flush><Sparkline values={series} /></SeamCell>
63
+ */
64
+ declare const SeamCell: React.ForwardRefExoticComponent<SeamCellProps & React.RefAttributes<HTMLDivElement>>;
65
+ //#endregion
66
+ export { SeamCell, SeamCellProps, SeamGrid, SeamGridProps, SeamGround };
@@ -0,0 +1,78 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { toRem } from "./_rem.js";
3
+ import * as React from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/instruments/seam-grid.tsx
6
+ const GROUNDS = {
7
+ bg: "bg-bg",
8
+ sunken: "bg-sunken",
9
+ "surface-1": "bg-surface-1",
10
+ "surface-2": "bg-surface-2",
11
+ "surface-3": "bg-surface-3"
12
+ };
13
+ /**
14
+ * The seam recipe itself: a 1px gap over the border colour.
15
+ *
16
+ * No borders anywhere. Two adjacent bordered cells stack into a 2px rule and
17
+ * the grid's outer edge gets a rim nobody asked for; a gap over a coloured
18
+ * ground gives exactly one hairline between every pair of neighbours and
19
+ * nothing at all around the outside.
20
+ */
21
+ const SEAM_CONTAINER = "gap-px bg-border";
22
+ /**
23
+ * The seam idiom as a component: cells separated by hairlines, no borders.
24
+ *
25
+ * A grid whose `gap` is 1px over the border colour. Every cell paints its own
26
+ * ground, so the only place the container shows through is the gap — which
27
+ * reads as a hairline between neighbours, aligns perfectly at every corner, and
28
+ * never doubles up. This is snuffle's signature primitive; it was inlined at
29
+ * eighteen call sites before it was a component.
30
+ *
31
+ * Static markup — no `"use client"`.
32
+ *
33
+ * @example
34
+ * <SeamGrid columns={3}>
35
+ * <SeamCell>001</SeamCell>
36
+ * <SeamCell>002</SeamCell>
37
+ * <SeamCell>003</SeamCell>
38
+ * </SeamGrid>
39
+ * @example
40
+ * // Reflowing, no media query:
41
+ * <SeamGrid min={180} bordered>{cells}</SeamGrid>
42
+ */
43
+ const SeamGrid = React.forwardRef(function SeamGrid({ className, columns, min, bordered = false, style, ...elementProps }, ref) {
44
+ const gridTemplateColumns = min !== void 0 ? `repeat(auto-fit, minmax(${toRem(min)}, 1fr))` : columns === void 0 ? void 0 : `repeat(${columns}, minmax(0, 1fr))`;
45
+ return /* @__PURE__ */ jsx("div", {
46
+ ref,
47
+ "data-slot": "seam-grid",
48
+ className: cn("grid", SEAM_CONTAINER, bordered ? "overflow-hidden rounded-(--radius-surface) border border-border" : null, className),
49
+ style: {
50
+ ...gridTemplateColumns === void 0 ? null : { gridTemplateColumns },
51
+ ...style
52
+ },
53
+ ...elementProps
54
+ });
55
+ });
56
+ /**
57
+ * One filled cell of a {@link SeamGrid} — or of the Phase 1 `SeamList`.
58
+ *
59
+ * The fill is the load-bearing part: an unfilled cell lets the container's
60
+ * border colour through everywhere, not just at the gap, and the seam
61
+ * disappears into a solid block.
62
+ *
63
+ * Static markup — no `"use client"`.
64
+ *
65
+ * @example
66
+ * <SeamCell ground="sunken" flush><Sparkline values={series} /></SeamCell>
67
+ */
68
+ const SeamCell = React.forwardRef(function SeamCell({ className, ground = "surface-1", interactive = false, flush = false, ...elementProps }, ref) {
69
+ return /* @__PURE__ */ jsx("div", {
70
+ ref,
71
+ "data-slot": "seam-cell",
72
+ "data-ground": ground,
73
+ className: cn("min-w-0", GROUNDS[ground], flush ? null : "p-(--cue-space-5)", interactive ? "cursor-pointer transition-colors duration-150 hover:bg-surface-2 motion-reduce:transition-none" : null, className),
74
+ ...elementProps
75
+ });
76
+ });
77
+ //#endregion
78
+ export { SeamCell, SeamGrid };