@elabs-ai/components-ui 4.0.0 → 4.2.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 (339) hide show
  1. package/README.md +10 -8
  2. package/dist/chunk-FCH4ZN6G.js +57 -0
  3. package/dist/chunk-FCH4ZN6G.js.map +1 -0
  4. package/dist/form.d.ts +28 -0
  5. package/dist/form.js +123 -0
  6. package/dist/form.js.map +1 -0
  7. package/dist/index.d.ts +2597 -188
  8. package/dist/index.js +12855 -3578
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/cn.js +10 -1
  11. package/dist/lib/cn.js.map +1 -1
  12. package/package.json +21 -7
  13. package/src/__contract__/attribution-panel.contract.test.tsx +49 -0
  14. package/src/__contract__/badge.contract.test.tsx +49 -0
  15. package/src/__contract__/bounded-number.contract.test.tsx +49 -0
  16. package/src/__contract__/button.contract.test.tsx +49 -0
  17. package/src/__contract__/color-picker.contract.test.tsx +49 -0
  18. package/src/__contract__/combobox.contract.test.tsx +49 -0
  19. package/src/__contract__/command-trigger.contract.test.tsx +49 -0
  20. package/src/__contract__/copyable-value.contract.test.tsx +49 -0
  21. package/src/__contract__/date-picker.contract.test.tsx +49 -0
  22. package/src/__contract__/date-range-picker.contract.test.tsx +49 -0
  23. package/src/__contract__/empty-state.contract.test.tsx +49 -0
  24. package/src/__contract__/error-state.contract.test.tsx +49 -0
  25. package/src/__contract__/icon-button.contract.test.tsx +49 -0
  26. package/src/__contract__/input.contract.test.tsx +49 -0
  27. package/src/__contract__/key-value-editor.contract.test.tsx +49 -0
  28. package/src/__contract__/list-editor.contract.test.tsx +49 -0
  29. package/src/__contract__/loading-state.contract.test.tsx +49 -0
  30. package/src/__contract__/metric-card.contract.test.tsx +49 -0
  31. package/src/__contract__/model-picker.contract.test.tsx +49 -0
  32. package/src/__contract__/nav-notifications.contract.test.tsx +49 -0
  33. package/src/__contract__/number-input.contract.test.tsx +49 -0
  34. package/src/__contract__/rating.contract.test.tsx +49 -0
  35. package/src/__contract__/schema-form.contract.test.tsx +49 -0
  36. package/src/__contract__/section-header.contract.test.tsx +49 -0
  37. package/src/__contract__/segmented-field.contract.test.tsx +49 -0
  38. package/src/__contract__/slider-number.contract.test.tsx +49 -0
  39. package/src/__contract__/status-badge.contract.test.tsx +49 -0
  40. package/src/__contract__/tag-input.contract.test.tsx +49 -0
  41. package/src/__contract__/text.contract.test.tsx +49 -0
  42. package/src/__contract__/textarea.contract.test.tsx +49 -0
  43. package/src/__contract__/timeline.contract.test.tsx +49 -0
  44. package/src/__contract__/toggle.contract.test.tsx +49 -0
  45. package/src/__contract__/transfer.contract.test.tsx +49 -0
  46. package/src/__contract__/tree-select.contract.test.tsx +49 -0
  47. package/src/__contract__/tree.contract.test.tsx +49 -0
  48. package/src/__contract__/virtual-select.contract.test.tsx +49 -0
  49. package/src/__contract__/workspace-picker.contract.test.tsx +49 -0
  50. package/src/_peer-deps.test.ts +59 -0
  51. package/src/blocks-comparison-table.stories.tsx +2 -2
  52. package/src/components/accordion/accordion.tsx +13 -4
  53. package/src/components/advanced-group/advanced-group.tsx +1 -1
  54. package/src/components/alert/alert.tsx +15 -4
  55. package/src/components/alert-dialog/alert-dialog.test.tsx +32 -0
  56. package/src/components/alert-dialog/alert-dialog.tsx +28 -6
  57. package/src/components/app-shell/app-shell.stories.tsx +44 -2
  58. package/src/components/app-shell/app-shell.test.tsx +99 -0
  59. package/src/components/app-shell/app-shell.tsx +56 -4
  60. package/src/components/app-sidebar/app-sidebar.stories.tsx +9 -1
  61. package/src/components/attribution-panel/attribution-panel.tsx +3 -1
  62. package/src/components/attribution-panel/attributions.generated.ts +83 -79
  63. package/src/components/avatar/avatar.tsx +4 -1
  64. package/src/components/badge/badge.stories.tsx +8 -0
  65. package/src/components/badge/badge.test.tsx +8 -0
  66. package/src/components/badge/badge.tsx +15 -5
  67. package/src/components/bento-grid/bento-grid.test.tsx +24 -3
  68. package/src/components/bento-grid/bento-grid.tsx +11 -1
  69. package/src/components/bounded-number/bounded-number.test.tsx +1 -1
  70. package/src/components/bounded-number/bounded-number.tsx +78 -75
  71. package/src/components/breadcrumb/breadcrumb.stories.tsx +38 -0
  72. package/src/components/breadcrumb/breadcrumb.test.tsx +24 -1
  73. package/src/components/breadcrumb/breadcrumb.tsx +10 -4
  74. package/src/components/button/button.stories.tsx +116 -8
  75. package/src/components/button/button.tsx +18 -12
  76. package/src/components/button-group/button-group.tsx +1 -1
  77. package/src/components/calendar/calendar.tsx +3 -2
  78. package/src/components/card/card.stories.tsx +11 -6
  79. package/src/components/card/card.test.tsx +24 -2
  80. package/src/components/card/card.tsx +48 -7
  81. package/src/components/carousel/carousel.test.tsx +169 -0
  82. package/src/components/carousel/carousel.tsx +93 -32
  83. package/src/components/change-review/change-review.stories.tsx +145 -1
  84. package/src/components/change-review/change-review.test.tsx +143 -0
  85. package/src/components/change-review/change-review.tsx +214 -24
  86. package/src/components/checkbox/checkbox.tsx +2 -1
  87. package/src/components/collapsible-panel/use-collapsible-panel.ts +0 -3
  88. package/src/components/color-picker/color-picker.tsx +22 -15
  89. package/src/components/combobox/combobox.stories.tsx +10 -0
  90. package/src/components/combobox/combobox.test.tsx +54 -0
  91. package/src/components/combobox/combobox.tsx +25 -11
  92. package/src/components/command/command.stories.tsx +16 -6
  93. package/src/components/command/command.test.tsx +88 -0
  94. package/src/components/command/command.tsx +38 -13
  95. package/src/components/command/index.ts +1 -0
  96. package/src/components/command-trigger/command-trigger.stories.tsx +39 -0
  97. package/src/components/command-trigger/command-trigger.test.tsx +18 -0
  98. package/src/components/command-trigger/command-trigger.tsx +70 -0
  99. package/src/components/command-trigger/index.ts +1 -0
  100. package/src/components/context-menu/context-menu.tsx +16 -7
  101. package/src/components/context-rail/context-rail.stories.tsx +285 -0
  102. package/src/components/context-rail/context-rail.test.tsx +263 -0
  103. package/src/components/context-rail/context-rail.tsx +678 -0
  104. package/src/components/context-rail/index.ts +1 -0
  105. package/src/components/copyable-value/copyable-value.stories.tsx +10 -0
  106. package/src/components/copyable-value/copyable-value.test.tsx +28 -1
  107. package/src/components/copyable-value/copyable-value.tsx +13 -2
  108. package/src/components/date-picker/date-picker.stories.tsx +9 -0
  109. package/src/components/date-picker/date-picker.test.tsx +52 -0
  110. package/src/components/date-picker/date-picker.tsx +13 -15
  111. package/src/components/date-range-picker/date-range-picker.stories.tsx +9 -0
  112. package/src/components/date-range-picker/date-range-picker.test.tsx +76 -1
  113. package/src/components/date-range-picker/date-range-picker.tsx +39 -28
  114. package/src/components/descriptions/descriptions.tsx +1 -1
  115. package/src/components/dialog/dialog.test.tsx +8 -3
  116. package/src/components/dialog/dialog.tsx +14 -6
  117. package/src/components/drawer/drawer.tsx +23 -6
  118. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +1 -1
  119. package/src/components/dropdown-menu/dropdown-menu.tsx +4 -4
  120. package/src/components/empty-state/empty-state.test.tsx +26 -0
  121. package/src/components/empty-state/empty-state.tsx +6 -1
  122. package/src/components/expand-dialog/expand-dialog.tsx +1 -2
  123. package/src/components/field/field-context.ts +59 -0
  124. package/src/components/field/field.stories.tsx +142 -0
  125. package/src/components/field/field.test.tsx +415 -0
  126. package/src/components/field/field.tsx +342 -0
  127. package/src/components/field/index.ts +9 -0
  128. package/src/components/field-row/field-row.stories.tsx +85 -1
  129. package/src/components/field-row/field-row.test.tsx +145 -0
  130. package/src/components/field-row/field-row.tsx +50 -6
  131. package/src/components/file-upload/file-upload.stories.tsx +16 -0
  132. package/src/components/file-upload/file-upload.test.tsx +90 -12
  133. package/src/components/file-upload/file-upload.tsx +164 -31
  134. package/src/components/form/form.tsx +8 -3
  135. package/src/components/hover-card/hover-card.tsx +1 -0
  136. package/src/components/icon-button/icon-button.test.tsx +1 -1
  137. package/src/components/icon-button/icon-button.tsx +3 -3
  138. package/src/components/input/input.stories.tsx +121 -0
  139. package/src/components/input/input.test.tsx +13 -0
  140. package/src/components/input/input.tsx +8 -4
  141. package/src/components/input-group/input-group.stories.tsx +74 -0
  142. package/src/components/input-group/input-group.tsx +18 -9
  143. package/src/components/input-otp/input-otp.tsx +8 -2
  144. package/src/components/keyboard-shortcuts/index.ts +6 -0
  145. package/src/components/keyboard-shortcuts/keyboard-shortcuts.stories.tsx +177 -0
  146. package/src/components/keyboard-shortcuts/keyboard-shortcuts.test.tsx +73 -0
  147. package/src/components/keyboard-shortcuts/keyboard-shortcuts.tsx +203 -0
  148. package/src/components/label/label.tsx +1 -1
  149. package/src/components/link-preview/link-preview.tsx +1 -1
  150. package/src/components/list-editor/list-editor.test.tsx +24 -0
  151. package/src/components/list-editor/list-editor.tsx +126 -52
  152. package/src/components/locale-provider/index.ts +6 -1
  153. package/src/components/locale-provider/locale-keys.test.ts +134 -0
  154. package/src/components/locale-provider/locale-provider.stories.tsx +89 -1
  155. package/src/components/locale-provider/locale-provider.test.tsx +257 -1
  156. package/src/components/locale-provider/locale-provider.tsx +63 -8
  157. package/src/components/locale-provider/messages.ts +822 -2
  158. package/src/components/match-highlight/match-highlight.stories.tsx +1 -1
  159. package/src/components/mention-input/mention-input-mirror.stories.tsx +9 -1
  160. package/src/components/mention-input/mention-input.stories.tsx +3 -3
  161. package/src/components/mention-input/mention-input.test.tsx +51 -1
  162. package/src/components/mention-input/mention-input.tsx +66 -46
  163. package/src/components/mention-input/mention-value.ts +8 -2
  164. package/src/components/menubar/menubar.tsx +16 -6
  165. package/src/components/metric-card/metric-card.stories.tsx +5 -3
  166. package/src/components/metric-card/metric-card.tsx +21 -2
  167. package/src/components/model-picker/model-picker.stories.tsx +63 -3
  168. package/src/components/model-picker/model-picker.test.tsx +63 -0
  169. package/src/components/model-picker/model-picker.tsx +62 -36
  170. package/src/components/nav-main/nav-main.tsx +8 -4
  171. package/src/components/nav-notifications/nav-notifications.stories.tsx +110 -0
  172. package/src/components/nav-notifications/nav-notifications.test.tsx +11 -0
  173. package/src/components/nav-notifications/nav-notifications.tsx +67 -9
  174. package/src/components/nav-user/nav-user.tsx +5 -5
  175. package/src/components/navigation-menu/navigation-menu.stories.tsx +22 -0
  176. package/src/components/navigation-menu/navigation-menu.test.tsx +174 -0
  177. package/src/components/navigation-menu/navigation-menu.tsx +204 -15
  178. package/src/components/number-input/number-input.stories.tsx +7 -3
  179. package/src/components/number-input/number-input.test.tsx +47 -0
  180. package/src/components/number-input/number-input.tsx +65 -24
  181. package/src/components/page-shell/page-shell.stories.tsx +163 -0
  182. package/src/components/page-shell/page-shell.test.tsx +229 -0
  183. package/src/components/page-shell/page-shell.tsx +108 -25
  184. package/src/components/pagination/pagination.test.tsx +109 -0
  185. package/src/components/pagination/pagination.tsx +27 -19
  186. package/src/components/popover/popover.tsx +1 -0
  187. package/src/components/progress/progress.tsx +2 -0
  188. package/src/components/radio-group/radio-group.tsx +1 -1
  189. package/src/components/rating/rating.tsx +3 -1
  190. package/src/components/resizable/resizable.stories.tsx +114 -2
  191. package/src/components/resizable/resizable.test.tsx +76 -0
  192. package/src/components/resizable/resizable.tsx +32 -6
  193. package/src/components/revision-timeline/revision-timeline.stories.tsx +8 -1
  194. package/src/components/revision-timeline/revision-timeline.test.tsx +26 -0
  195. package/src/components/revision-timeline/revision-timeline.tsx +53 -5
  196. package/src/components/schema-form/from-json-schema.test.ts +390 -0
  197. package/src/components/schema-form/from-json-schema.ts +375 -0
  198. package/src/components/schema-form/index.ts +81 -0
  199. package/src/components/schema-form/schema-form-spec.ts +698 -0
  200. package/src/components/schema-form/schema-form-store.ts +506 -0
  201. package/src/components/schema-form/schema-form.stories.tsx +566 -0
  202. package/src/components/schema-form/schema-form.test.tsx +1021 -0
  203. package/src/components/schema-form/schema-form.tsx +1381 -0
  204. package/src/components/section-header/section-header.stories.tsx +37 -0
  205. package/src/components/section-header/section-header.tsx +14 -3
  206. package/src/components/select/select.tsx +13 -7
  207. package/src/components/separator/separator.tsx +1 -0
  208. package/src/components/sheet/sheet.test.tsx +39 -0
  209. package/src/components/sheet/sheet.tsx +40 -9
  210. package/src/components/side-dock/index.ts +1 -0
  211. package/src/components/side-dock/side-dock.stories.tsx +137 -0
  212. package/src/components/side-dock/side-dock.test.tsx +299 -0
  213. package/src/components/side-dock/side-dock.tsx +515 -0
  214. package/src/components/sidebar/index.ts +1 -0
  215. package/src/components/sidebar/sidebar-frame.test.tsx +162 -0
  216. package/src/components/sidebar/sidebar.stories.tsx +421 -1
  217. package/src/components/sidebar/sidebar.test.tsx +71 -2
  218. package/src/components/sidebar/sidebar.tsx +264 -38
  219. package/src/components/skeleton/skeleton.tsx +1 -0
  220. package/src/components/skip-link/index.ts +1 -0
  221. package/src/components/skip-link/skip-link.stories.tsx +106 -0
  222. package/src/components/skip-link/skip-link.test.tsx +20 -0
  223. package/src/components/skip-link/skip-link.tsx +34 -0
  224. package/src/components/slider/slider.tsx +1 -1
  225. package/src/components/slider-number/slider-number.stories.tsx +4 -1
  226. package/src/components/slider-number/slider-number.test.tsx +1 -1
  227. package/src/components/sonner/sonner.tsx +2 -0
  228. package/src/components/spinner/spinner.test.tsx +18 -0
  229. package/src/components/spinner/spinner.tsx +14 -4
  230. package/src/components/split-panel/index.ts +6 -1
  231. package/src/components/split-panel/split-panel.stories.tsx +18 -2
  232. package/src/components/split-panel/split-panel.test.tsx +20 -0
  233. package/src/components/split-panel/split-panel.tsx +28 -8
  234. package/src/components/state-panel/state-panel.stories.tsx +52 -0
  235. package/src/components/state-panel/state-panel.test.tsx +125 -0
  236. package/src/components/state-panel/state-panel.tsx +110 -24
  237. package/src/components/status-badge/status-badge.stories.tsx +8 -0
  238. package/src/components/switch/switch.tsx +3 -1
  239. package/src/components/table/table.stories.tsx +64 -0
  240. package/src/components/table/table.test.tsx +149 -1
  241. package/src/components/table/table.tsx +71 -2
  242. package/src/components/tabs/index.ts +10 -1
  243. package/src/components/tabs/tabs.stories.tsx +48 -0
  244. package/src/components/tabs/tabs.test.tsx +67 -0
  245. package/src/components/tabs/tabs.tsx +176 -72
  246. package/src/components/tag-input/tag-input.tsx +6 -3
  247. package/src/components/team-switcher/team-switcher.tsx +35 -6
  248. package/src/components/textarea/textarea.tsx +2 -2
  249. package/src/components/theme-switcher/theme-switcher.stories.tsx +76 -0
  250. package/src/components/theme-switcher/theme-switcher.test.tsx +182 -0
  251. package/src/components/theme-switcher/theme-switcher.tsx +140 -12
  252. package/src/components/theme-switcher/use-theme-transition.ts +2 -2
  253. package/src/components/timeline/timeline.stories.tsx +27 -0
  254. package/src/components/timeline/timeline.tsx +10 -3
  255. package/src/components/toggle/toggle.tsx +7 -2
  256. package/src/components/toggle-group/toggle-group.tsx +2 -0
  257. package/src/components/toolbar/toolbar.stories.tsx +10 -2
  258. package/src/components/tooltip/tooltip.tsx +2 -1
  259. package/src/components/top-nav/top-nav.tsx +1 -1
  260. package/src/components/transfer/transfer.tsx +35 -30
  261. package/src/components/tree/tree.stories.tsx +2 -2
  262. package/src/components/tree/tree.test.tsx +41 -0
  263. package/src/components/tree/tree.tsx +91 -31
  264. package/src/components/tree/use-tree-keyboard.ts +37 -14
  265. package/src/components/tree-select/tree-select.stories.tsx +9 -0
  266. package/src/components/tree-select/tree-select.test.tsx +5 -0
  267. package/src/components/tree-select/tree-select.tsx +11 -14
  268. package/src/components/typography/prose.test.tsx +3 -2
  269. package/src/components/typography/prose.tsx +3 -3
  270. package/src/components/typography/typography.stories.tsx +228 -0
  271. package/src/components/view-toolbar/view-toolbar.stories.tsx +41 -1
  272. package/src/components/view-toolbar/view-toolbar.test.tsx +33 -0
  273. package/src/components/view-toolbar/view-toolbar.tsx +37 -4
  274. package/src/components/virtual-select/virtual-select.stories.tsx +9 -0
  275. package/src/components/virtual-select/virtual-select.test.tsx +29 -3
  276. package/src/components/virtual-select/virtual-select.tsx +56 -18
  277. package/src/components/wizard/wizard.test.tsx +9 -0
  278. package/src/components/wizard/wizard.tsx +13 -5
  279. package/src/components/workspace-picker/index.ts +3 -0
  280. package/src/components/workspace-picker/workspace-picker-state.ts +50 -0
  281. package/src/components/workspace-picker/workspace-picker.stories.tsx +126 -0
  282. package/src/components/workspace-picker/workspace-picker.test.tsx +207 -0
  283. package/src/components/workspace-picker/workspace-picker.tsx +172 -0
  284. package/src/illustrations/empty-list-illustration.tsx +26 -0
  285. package/src/illustrations/error-illustration.tsx +60 -0
  286. package/src/illustrations/first-run-illustration.tsx +27 -0
  287. package/src/illustrations/illustration-base.tsx +108 -0
  288. package/src/illustrations/illustrations.stories.tsx +94 -0
  289. package/src/illustrations/illustrations.test.tsx +98 -0
  290. package/src/illustrations/index.ts +16 -0
  291. package/src/illustrations/no-access-illustration.tsx +45 -0
  292. package/src/illustrations/no-results-illustration.tsx +46 -0
  293. package/src/illustrations/offline-illustration.tsx +37 -0
  294. package/src/illustrations/success-illustration.tsx +40 -0
  295. package/src/index.ts +80 -2
  296. package/src/lib/agent-event-model.test.ts +12 -0
  297. package/src/lib/agent-event-model.ts +42 -0
  298. package/src/lib/approval-option.test.ts +17 -0
  299. package/src/lib/approval-option.ts +53 -0
  300. package/src/lib/check-result.ts +33 -0
  301. package/src/lib/cn.ts +9 -0
  302. package/src/lib/csv.ts +48 -0
  303. package/src/lib/diff-line.test.ts +36 -0
  304. package/src/lib/diff-line.ts +63 -0
  305. package/src/lib/diff-rows.test.ts +76 -0
  306. package/src/lib/diff-rows.ts +94 -0
  307. package/src/lib/format-duration.test.ts +22 -0
  308. package/src/lib/format-duration.ts +30 -0
  309. package/src/lib/has-renderable-content.test.ts +49 -0
  310. package/src/lib/has-renderable-content.ts +32 -0
  311. package/src/lib/merge-refs.ts +2 -0
  312. package/src/lib/operating-mode.test.ts +36 -0
  313. package/src/lib/operating-mode.ts +81 -0
  314. package/src/lib/optional-peer.ts +59 -0
  315. package/src/lib/session-launch.ts +37 -0
  316. package/src/lib/slash-command.test.ts +44 -0
  317. package/src/lib/slash-command.ts +45 -0
  318. package/src/lib/trigger-query.test.ts +94 -0
  319. package/src/lib/trigger-query.ts +88 -0
  320. package/src/lib/use-controllable-state.ts +47 -0
  321. package/src/lib/use-mobile.test.tsx +47 -0
  322. package/src/lib/use-mobile.ts +34 -12
  323. package/src/motion.stories.tsx +6 -0
  324. package/src/templates-object-detail-hub.stories.tsx +2 -2
  325. package/src/templates-screen-states.stories.tsx +5 -5
  326. package/src/templates-settings.stories.tsx +1 -1
  327. package/src/blocks/sidebar-02/app-sidebar.tsx +0 -160
  328. package/src/blocks/sidebar-02/logo.tsx +0 -18
  329. package/src/blocks/sidebar-02/nav-main.tsx +0 -13
  330. package/src/blocks/sidebar-02/nav-notifications.tsx +0 -7
  331. package/src/blocks/sidebar-02/sidebar-02.stories.tsx +0 -23
  332. package/src/blocks/sidebar-02/team-switcher.tsx +0 -7
  333. package/src/blocks/sidebar-04/app-sidebar.tsx +0 -236
  334. package/src/blocks/sidebar-04/mail-context.tsx +0 -29
  335. package/src/blocks/sidebar-04/nav-user.tsx +0 -6
  336. package/src/blocks/sidebar-04/sidebar-04.stories.tsx +0 -150
  337. package/src/blocks/sidebar-05/app-sidebar.tsx +0 -375
  338. package/src/blocks/sidebar-05/sidebar-05.stories.tsx +0 -18
  339. package/src/blocks/sidebar-05/team-switcher.tsx +0 -6
@@ -1,8 +1,8 @@
1
1
  // GENERATED by scripts/gen-attributions.mjs — DO NOT EDIT BY HAND.
2
- // Run `pnpm gen:attributions` after changing a dependency, a vendored font, or
3
- // scripts/attributions.sources.json. `pnpm attributions:check` fails on a stale copy.
2
+ // Run `pnpm gen` after changing a dependency, a vendored font, or
3
+ // scripts/attributions.sources.json. `pnpm gen:check` fails on a stale copy.
4
4
  //
5
- // 116 entries (data: 2, source: 16, font: 2, dependency: 96).
5
+ // 116 entries (data: 2, source: 18, font: 2, dependency: 94).
6
6
 
7
7
  import type { Attribution } from "./attribution-types";
8
8
 
@@ -93,14 +93,14 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
93
93
  },
94
94
  {
95
95
  version: null,
96
- note: "The sidebar-02 / sidebar-04 / sidebar-05 copy-owned blocks, re-tokenized with Tabler icons mapped to Lucide.",
97
- id: "blocks-so",
96
+ note: "Agent-session UI logic re-expressed on our tokens: the line-level diff model and its screen-reader polarity prefixes, the scoped N-option permission anatomy, the slash-palette prefix-filter/clamp behaviour, the turn-status elapsed formatter, the hook/lifecycle event line, and the grouped keyboard-shortcuts sheet. No upstream code, styling or terminal palette is shipped.",
97
+ id: "brainless",
98
98
  category: "source",
99
- name: "blocks.so",
99
+ name: "brainless",
100
100
  license: "MIT",
101
- copyright: "Copyright (c) 2025 Ephraim Duncan",
102
- url: "https://github.com/ephraimduncan/blocks",
103
- usedBy: ["@elabs-ai/components-ui"],
101
+ copyright: "Copyright (c) 2026 Ben Swerdlow",
102
+ url: "https://github.com/theswerd/brainless",
103
+ usedBy: ["@elabs-ai/components-ai", "@elabs-ai/components-ui", "@elabs-ai/components-terminal"],
104
104
  required: false,
105
105
  },
106
106
  {
@@ -115,6 +115,18 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
115
115
  usedBy: ["@elabs-ai/components-viewer"],
116
116
  required: false,
117
117
  },
118
+ {
119
+ version: null,
120
+ note: "The editorial chart gallery behind the 2026-09-04 charts gap analysis: 71 files across the charts and tokens packages cite it as the source of a value, a mark shape or a composition (unit decomposition, hairline furniture, the sequential/diverging ramps, the reveal timing), and three copy-own registry blocks (chart-editorial-hourglass, -patchwork, -almanac) reproduce three of its gallery cards' DATA-SHAPE DESCRIPTIONS from that same analysis' one-line prose. No code was copied verbatim — each was re-expressed onto this repo's tokens and primitives. Its LICENSE is PolyForm Noncommercial 1.0.0, read verbatim from the upstream file; GitHub's own detection reports NOASSERTION, which is why nothing flagged it earlier. The licence's Required Notice slot is left at the template's example value, so upstream states no copyright holder and none is claimed here. Noncommercial-only is unresolved for a repo headed for public release — a licensing decision for the maintainer, not something attribution settles.",
121
+ id: "lieflat-charts",
122
+ category: "source",
123
+ name: "lieflat-charts",
124
+ license: "PolyForm-Noncommercial-1.0.0",
125
+ copyright: null,
126
+ url: "https://github.com/larashero3-dotcom/lieflat-charts",
127
+ usedBy: ["@elabs-ai/components-charts", "@elabs-ai/components-tokens"],
128
+ required: false,
129
+ },
118
130
  {
119
131
  version: null,
120
132
  note: "The live-axis tick-interval picker behind live-y-axis.tsx. Upstream now names that function niceTimeInterval.",
@@ -151,6 +163,18 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
151
163
  usedBy: ["@elabs-ai/components-editor"],
152
164
  required: false,
153
165
  },
166
+ {
167
+ version: null,
168
+ note: "Algorithm shape for the framework-free process-mining core: which aggregates a directly-follows graph carries (discoverGraph) and how traces are grouped into variants (extractVariants), with durationStats as their shared summary. Re-typed in TypeScript from the published algorithms, not copied.",
169
+ id: "pm4js",
170
+ category: "source",
171
+ name: "pm4js",
172
+ license: "BSD-3-Clause",
173
+ copyright: "Copyright 2021 Alessandro Berti",
174
+ url: "https://github.com/pm4js/pm4js-core",
175
+ usedBy: ["@elabs-ai/components-process"],
176
+ required: false,
177
+ },
154
178
  {
155
179
  version: null,
156
180
  note: "Beyond the npm dependency below, the shipped dist of react-scroll-area and react-select is modified in patches/ — disclosed here so the change is not invisible.",
@@ -261,14 +285,38 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
261
285
  note: null,
262
286
  },
263
287
  {
264
- id: "@hookform/resolvers",
288
+ id: "@dnd-kit/core",
265
289
  category: "dependency",
266
- name: "@hookform/resolvers",
267
- version: "3.10.0",
290
+ name: "@dnd-kit/core",
291
+ version: "6.3.1",
268
292
  license: "MIT",
269
- copyright: "bluebill1049",
270
- url: "https://www.npmjs.com/package/@hookform/resolvers",
271
- usedBy: ["@elabs-ai/components-ui"],
293
+ copyright: "Claudéric Demers",
294
+ url: "https://www.npmjs.com/package/@dnd-kit/core",
295
+ usedBy: ["@elabs-ai/components-data"],
296
+ required: false,
297
+ note: null,
298
+ },
299
+ {
300
+ id: "@dnd-kit/sortable",
301
+ category: "dependency",
302
+ name: "@dnd-kit/sortable",
303
+ version: "10.0.0",
304
+ license: "MIT",
305
+ copyright: "Claudéric Demers",
306
+ url: "https://www.npmjs.com/package/@dnd-kit/sortable",
307
+ usedBy: ["@elabs-ai/components-data"],
308
+ required: false,
309
+ note: null,
310
+ },
311
+ {
312
+ id: "@dnd-kit/utilities",
313
+ category: "dependency",
314
+ name: "@dnd-kit/utilities",
315
+ version: "3.2.2",
316
+ license: "MIT",
317
+ copyright: "Claudéric Demers",
318
+ url: "https://www.npmjs.com/package/@dnd-kit/utilities",
319
+ usedBy: ["@elabs-ai/components-data"],
272
320
  required: false,
273
321
  note: null,
274
322
  },
@@ -656,18 +704,6 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
656
704
  required: false,
657
705
  note: null,
658
706
  },
659
- {
660
- id: "@rive-app/react-webgl2",
661
- category: "dependency",
662
- name: "@rive-app/react-webgl2",
663
- version: "4.28.6",
664
- license: "MIT",
665
- copyright: null,
666
- url: "https://www.npmjs.com/package/@rive-app/react-webgl2",
667
- usedBy: ["@elabs-ai/components-ai"],
668
- required: false,
669
- note: null,
670
- },
671
707
  {
672
708
  id: "@streamdown/cjk",
673
709
  category: "dependency",
@@ -901,30 +937,6 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
901
937
  required: false,
902
938
  note: null,
903
939
  },
904
- {
905
- id: "@xterm/addon-fit",
906
- category: "dependency",
907
- name: "@xterm/addon-fit",
908
- version: "0.11.0",
909
- license: "MIT",
910
- copyright: "The xterm.js authors",
911
- url: "https://www.npmjs.com/package/@xterm/addon-fit",
912
- usedBy: ["@elabs-ai/components-ai"],
913
- required: false,
914
- note: null,
915
- },
916
- {
917
- id: "@xterm/xterm",
918
- category: "dependency",
919
- name: "@xterm/xterm",
920
- version: "6.0.0",
921
- license: "MIT",
922
- copyright: null,
923
- url: "https://www.npmjs.com/package/@xterm/xterm",
924
- usedBy: ["@elabs-ai/components-ai"],
925
- required: false,
926
- note: null,
927
- },
928
940
  {
929
941
  id: "ansi-to-react",
930
942
  category: "dependency",
@@ -933,7 +945,7 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
933
945
  license: "BSD-3-Clause",
934
946
  copyright: "Kyle Kelley",
935
947
  url: "https://www.npmjs.com/package/ansi-to-react",
936
- usedBy: ["@elabs-ai/components-ai"],
948
+ usedBy: ["@elabs-ai/components-terminal"],
937
949
  required: false,
938
950
  note: null,
939
951
  },
@@ -949,6 +961,8 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
949
961
  "@elabs-ai/components-ai",
950
962
  "@elabs-ai/components-charts",
951
963
  "@elabs-ai/components-editor",
964
+ "@elabs-ai/components-process",
965
+ "@elabs-ai/components-terminal",
952
966
  "@elabs-ai/components-ui",
953
967
  ],
954
968
  required: false,
@@ -998,7 +1012,7 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
998
1012
  license: "ISC",
999
1013
  copyright: "Mike Bostock",
1000
1014
  url: "https://www.npmjs.com/package/d3-force",
1001
- usedBy: ["@elabs-ai/components-flow"],
1015
+ usedBy: ["@elabs-ai/components-charts", "@elabs-ai/components-flow"],
1002
1016
  required: false,
1003
1017
  note: null,
1004
1018
  },
@@ -1014,6 +1028,18 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
1014
1028
  required: false,
1015
1029
  note: null,
1016
1030
  },
1031
+ {
1032
+ id: "d3-hierarchy",
1033
+ category: "dependency",
1034
+ name: "d3-hierarchy",
1035
+ version: "3.1.2",
1036
+ license: "ISC",
1037
+ copyright: "Mike Bostock",
1038
+ url: "https://www.npmjs.com/package/d3-hierarchy",
1039
+ usedBy: ["@elabs-ai/components-charts"],
1040
+ required: false,
1041
+ note: null,
1042
+ },
1017
1043
  {
1018
1044
  id: "d3-sankey",
1019
1045
  category: "dependency",
@@ -1113,24 +1139,14 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
1113
1139
  "@elabs-ai/components-editor",
1114
1140
  "@elabs-ai/components-flow",
1115
1141
  "@elabs-ai/components-maps",
1142
+ "@elabs-ai/components-process",
1143
+ "@elabs-ai/components-terminal",
1116
1144
  "@elabs-ai/components-ui",
1117
1145
  "@elabs-ai/components-viewer",
1118
1146
  ],
1119
1147
  required: false,
1120
1148
  note: null,
1121
1149
  },
1122
- {
1123
- id: "media-chrome",
1124
- category: "dependency",
1125
- name: "media-chrome",
1126
- version: "4.19.1",
1127
- license: "MIT",
1128
- copyright: "@muxinc",
1129
- url: "https://www.npmjs.com/package/media-chrome",
1130
- usedBy: ["@elabs-ai/components-ai"],
1131
- required: false,
1132
- note: null,
1133
- },
1134
1150
  {
1135
1151
  id: "mermaid",
1136
1152
  category: "dependency",
@@ -1139,7 +1155,7 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
1139
1155
  license: "MIT",
1140
1156
  copyright: "Knut Sveidqvist",
1141
1157
  url: "https://www.npmjs.com/package/mermaid",
1142
- usedBy: ["@elabs-ai/components-ai", "@elabs-ai/components-editor"],
1158
+ usedBy: ["@elabs-ai/components-editor"],
1143
1159
  required: false,
1144
1160
  note: null,
1145
1161
  },
@@ -1179,18 +1195,6 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
1179
1195
  required: false,
1180
1196
  note: null,
1181
1197
  },
1182
- {
1183
- id: "react-hook-form",
1184
- category: "dependency",
1185
- name: "react-hook-form",
1186
- version: "7.77.0",
1187
- license: "MIT",
1188
- copyright: "Beier(Bill) Luo",
1189
- url: "https://www.npmjs.com/package/react-hook-form",
1190
- usedBy: ["@elabs-ai/components-ui"],
1191
- required: false,
1192
- note: null,
1193
- },
1194
1198
  {
1195
1199
  id: "react-jsx-parser",
1196
1200
  category: "dependency",
@@ -1415,7 +1419,7 @@ export const ATTRIBUTIONS: readonly Attribution[] = [
1415
1419
  license: "MIT",
1416
1420
  copyright: "Colin McDonnell",
1417
1421
  url: "https://www.npmjs.com/package/zod",
1418
- usedBy: ["@elabs-ai/components-ai", "@elabs-ai/components-ui"],
1422
+ usedBy: ["@elabs-ai/components-ai"],
1419
1423
  required: false,
1420
1424
  note: null,
1421
1425
  },
@@ -9,6 +9,7 @@ export const Avatar = forwardRef<
9
9
  return (
10
10
  <AvatarPrimitive.Root
11
11
  ref={ref}
12
+ data-slot="avatar"
12
13
  className={cn("relative flex size-9 shrink-0 overflow-hidden rounded-full", className)}
13
14
  {...props}
14
15
  />
@@ -21,6 +22,7 @@ export const AvatarImage = forwardRef<
21
22
  return (
22
23
  <AvatarPrimitive.Image
23
24
  ref={ref}
25
+ data-slot="avatar-image"
24
26
  className={cn(
25
27
  "aspect-square size-full animate-in fade-in duration-base ease-entrance",
26
28
  className,
@@ -36,8 +38,9 @@ export const AvatarFallback = forwardRef<
36
38
  return (
37
39
  <AvatarPrimitive.Fallback
38
40
  ref={ref}
41
+ data-slot="avatar-fallback"
39
42
  className={cn(
40
- "flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground",
43
+ "flex size-full items-center justify-center rounded-full bg-muted text-meta font-medium text-muted-foreground",
41
44
  className,
42
45
  )}
43
46
  {...props}
@@ -5,6 +5,14 @@ const meta = {
5
5
  title: "Core/Badge",
6
6
  component: Badge,
7
7
  tags: ["autodocs"],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component:
12
+ "A neutral label or count. The closed seven-state execution-status vocabulary — each state carrying its own glyph, so it survives greyscale — is `Core/StatusBadge`; see [Choosing between similar components](?path=/docs/docs-choosing-between-similar-components--docs). Reach for `Badge` when the text IS the meaning and the tone is decoration.",
13
+ },
14
+ },
15
+ },
8
16
  args: { children: "Badge" },
9
17
  argTypes: {
10
18
  variant: {
@@ -1,3 +1,4 @@
1
+ import { createRef } from "react";
1
2
  import { describe, expect, it } from "vitest";
2
3
  import { render, screen } from "@testing-library/react";
3
4
  import { Badge } from "./badge";
@@ -7,4 +8,11 @@ describe("Badge", () => {
7
8
  render(<Badge>New</Badge>);
8
9
  expect(screen.getByText("New")).toBeInTheDocument();
9
10
  });
11
+
12
+ it("forwards its ref to the rendered <span>", () => {
13
+ const ref = createRef<HTMLSpanElement>();
14
+ render(<Badge ref={ref}>New</Badge>);
15
+ expect(ref.current).toBeInstanceOf(HTMLSpanElement);
16
+ expect(ref.current).toBe(screen.getByText("New"));
17
+ });
10
18
  });
@@ -1,9 +1,9 @@
1
- import { type HTMLAttributes } from "react";
1
+ import { forwardRef, type HTMLAttributes } from "react";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
3
  import { cn } from "../../lib/cn";
4
4
 
5
5
  export const badgeVariants = cva(
6
- "inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-meta font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
6
+ "inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-meta font-medium transition-colors focus-ring focus-visible:ring-offset-2",
7
7
  {
8
8
  variants: {
9
9
  variant: {
@@ -23,6 +23,16 @@ export const badgeVariants = cva(
23
23
  export interface BadgeProps
24
24
  extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {}
25
25
 
26
- export function Badge({ className, variant, ...props }: BadgeProps) {
27
- return <span className={cn(badgeVariants({ variant }), className)} {...props} />;
28
- }
26
+ export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(
27
+ { className, variant, ...props },
28
+ ref,
29
+ ) {
30
+ return (
31
+ <span
32
+ ref={ref}
33
+ data-slot="badge"
34
+ className={cn(badgeVariants({ variant }), className)}
35
+ {...props}
36
+ />
37
+ );
38
+ });
@@ -1,5 +1,5 @@
1
- import { render, screen } from "@testing-library/react";
2
- import { describe, expect, it } from "vitest";
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
3
 
4
4
  import { BentoGrid, BentoGridItem } from "./bento-grid";
5
5
 
@@ -104,7 +104,7 @@ describe("BentoGridItem", () => {
104
104
  // The tile stays a div; the inner anchor is the focusable element.
105
105
  const item = screen.getByTestId("item");
106
106
  expect(item.tagName).toBe("DIV");
107
- expect(item.className).toMatch(/focus-within:ring/);
107
+ expect(item.className).toMatch(/focus-ring-within/);
108
108
  expect(screen.getByRole("link", { name: "go" })).toBeInTheDocument();
109
109
  });
110
110
 
@@ -116,4 +116,25 @@ describe("BentoGridItem", () => {
116
116
  );
117
117
  expect(screen.getByTestId("item").className).toMatch(/from-primary/);
118
118
  });
119
+
120
+ it("cancels a pending spotlight rAF on unmount", () => {
121
+ const rafSpy = vi.spyOn(window, "requestAnimationFrame").mockReturnValue(7);
122
+ const cafSpy = vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => {});
123
+ const { unmount } = render(
124
+ <BentoGridItem spotlight data-testid="item">
125
+ content
126
+ </BentoGridItem>,
127
+ );
128
+ const item = screen.getByTestId("item");
129
+ fireEvent.mouseMove(item, { clientX: 10, clientY: 10 });
130
+ expect(rafSpy).toHaveBeenCalled();
131
+
132
+ unmount();
133
+ // A mouse move right before the tile unmounts (removed from the grid
134
+ // while hovered) must not leave a scheduled frame pending after teardown.
135
+ expect(cafSpy).toHaveBeenCalledWith(7);
136
+
137
+ rafSpy.mockRestore();
138
+ cafSpy.mockRestore();
139
+ });
119
140
  });
@@ -34,6 +34,7 @@ import {
34
34
  createContext,
35
35
  forwardRef,
36
36
  use,
37
+ useEffect,
37
38
  useRef,
38
39
  useCallback,
39
40
  type CSSProperties,
@@ -178,7 +179,7 @@ export const bentoGridItemVariants = cva(
178
179
  * (elevation + edge) is on the base — every tile lifts, clickable or not.
179
180
  */
180
181
  interactive: {
181
- true: "cursor-pointer focus-within:outline-none focus-within:ring-2 focus-within:ring-ring",
182
+ true: "cursor-pointer focus-ring-within",
182
183
  false: "",
183
184
  },
184
185
  },
@@ -244,6 +245,15 @@ export const BentoGridItem = forwardRef<HTMLDivElement, BentoGridItemProps>(func
244
245
  // rAF ref so we only schedule one frame at a time.
245
246
  const rafRef = useRef<number>(0);
246
247
 
248
+ // Cancel a pending frame on unmount — otherwise a mouse move right before
249
+ // the tile unmounts (removed from the grid while hovered) still fires the
250
+ // scheduled callback after teardown.
251
+ useEffect(() => {
252
+ return () => {
253
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
254
+ };
255
+ }, []);
256
+
247
257
  const handleMouseMove = useCallback(
248
258
  (e: React.MouseEvent<HTMLDivElement>) => {
249
259
  if (!spotlightOn || !tileRef.current || !overlayRef.current) return;
@@ -18,7 +18,7 @@ describe("BoundedNumber", () => {
18
18
  it("hides the empty label once a value is set", () => {
19
19
  render(<BoundedNumber aria-label="Limit" defaultValue={250} />);
20
20
  expect(screen.queryByText("No limit")).not.toBeInTheDocument();
21
- expect(screen.getByRole("spinbutton", { name: "Limit" })).toHaveValue(250);
21
+ expect(screen.getByRole("spinbutton", { name: "Limit" })).toHaveValue("250");
22
22
  });
23
23
 
24
24
  it("hides the empty label while focused so the field reads as truly empty", async () => {
@@ -28,83 +28,86 @@ export interface BoundedNumberProps extends NumberInputProps {
28
28
  * `null` AND the field is not focused, so the underlying input stays fully
29
29
  * clickable/typable and still round-trips to `null` on an empty blur.
30
30
  */
31
- export const BoundedNumber = forwardRef<HTMLDivElement, BoundedNumberProps>(function BoundedNumber(
32
- {
33
- value: valueProp,
34
- defaultValue,
35
- onValueChange,
36
- emptyLabel,
37
- onFocus,
38
- onBlur,
39
- className,
40
- ...props
41
- },
42
- ref,
43
- ) {
44
- const { t } = useLocale();
45
- const resolvedEmptyLabel = emptyLabel ?? t("ui.boundedNumber.emptyLabel");
46
- const isControlled = valueProp !== undefined;
47
- const [internalValue, setInternalValue] = useState<number | null>(
48
- defaultValue !== undefined ? defaultValue : null,
49
- );
50
- const [focused, setFocused] = useState(false);
51
- const currentValue = isControlled ? valueProp : internalValue;
52
-
53
- const handleValueChange = useCallback(
54
- (next: number | null) => {
55
- if (!isControlled) setInternalValue(next);
56
- onValueChange?.(next);
31
+ export const BoundedNumber = forwardRef<HTMLInputElement, BoundedNumberProps>(
32
+ function BoundedNumber(
33
+ {
34
+ value: valueProp,
35
+ defaultValue,
36
+ onValueChange,
37
+ emptyLabel,
38
+ onFocus,
39
+ onBlur,
40
+ className,
41
+ ...props
57
42
  },
58
- [isControlled, onValueChange],
59
- );
43
+ ref,
44
+ ) {
45
+ const { t } = useLocale();
46
+ const resolvedEmptyLabel = emptyLabel ?? t("ui.boundedNumber.emptyLabel");
47
+ const isControlled = valueProp !== undefined;
48
+ const [internalValue, setInternalValue] = useState<number | null>(
49
+ defaultValue !== undefined ? defaultValue : null,
50
+ );
51
+ const [focused, setFocused] = useState(false);
52
+ const currentValue = isControlled ? valueProp : internalValue;
60
53
 
61
- const handleFocus = useCallback(
62
- (event: FocusEvent<HTMLDivElement>) => {
63
- setFocused(true);
64
- onFocus?.(event);
65
- },
66
- [onFocus],
67
- );
54
+ const handleValueChange = useCallback(
55
+ (next: number | null) => {
56
+ if (!isControlled) setInternalValue(next);
57
+ onValueChange?.(next);
58
+ },
59
+ [isControlled, onValueChange],
60
+ );
68
61
 
69
- const handleBlur = useCallback(
70
- (event: FocusEvent<HTMLDivElement>) => {
71
- setFocused(false);
72
- onBlur?.(event);
73
- },
74
- [onBlur],
75
- );
62
+ const handleFocus = useCallback(
63
+ (event: FocusEvent<HTMLDivElement>) => {
64
+ setFocused(true);
65
+ onFocus?.(event);
66
+ },
67
+ [onFocus],
68
+ );
69
+
70
+ const handleBlur = useCallback(
71
+ (event: FocusEvent<HTMLDivElement>) => {
72
+ setFocused(false);
73
+ onBlur?.(event);
74
+ },
75
+ [onBlur],
76
+ );
76
77
 
77
- const showEmptyLabel = currentValue == null && !focused;
78
+ const showEmptyLabel = currentValue == null && !focused;
78
79
 
79
- return (
80
- // `className` lands on the ROOT (the positioning context), not on the
81
- // inner `NumberInput`: a width utility on the inner control would resolve
82
- // against a shrink-to-fit wrapper and silently do nothing, so
83
- // `className="w-full"` could never widen the field. `w-36` here is
84
- // `NumberInput`'s own default width, re-declared so `cn()` lets the caller
85
- // override it; the input then simply fills the root.
86
- <div className={cn("relative inline-block w-36", className)} data-slot="bounded-number">
87
- <NumberInput
88
- ref={ref}
89
- value={currentValue}
90
- onValueChange={handleValueChange}
91
- onFocus={handleFocus}
92
- onBlur={handleBlur}
93
- className="w-full"
94
- {...props}
95
- />
96
- {showEmptyLabel && (
97
- <span
98
- aria-hidden="true"
99
- data-slot="bounded-number-empty-label"
100
- className={cn(
101
- "pointer-events-none absolute inset-0 flex items-center justify-center",
102
- "text-body text-muted-foreground",
103
- )}
104
- >
105
- {resolvedEmptyLabel}
106
- </span>
107
- )}
108
- </div>
109
- );
110
- });
80
+ return (
81
+ // `className` lands on the ROOT (the positioning context), not on the
82
+ // inner `NumberInput`: a width utility on the inner control would resolve
83
+ // against a shrink-to-fit wrapper and silently do nothing, so
84
+ // `className="w-full"` could never widen the field. `w-36` is this
85
+ // preset's own default width (`NumberInput` itself is unconstrained and
86
+ // fills its container) — `cn()` lets the caller override it, and the
87
+ // inner `NumberInput` simply fills the root via `className="w-full"`.
88
+ <div className={cn("relative inline-block w-36", className)} data-slot="bounded-number">
89
+ <NumberInput
90
+ ref={ref}
91
+ value={currentValue}
92
+ onValueChange={handleValueChange}
93
+ onFocus={handleFocus}
94
+ onBlur={handleBlur}
95
+ className="w-full"
96
+ {...props}
97
+ />
98
+ {showEmptyLabel && (
99
+ <span
100
+ aria-hidden="true"
101
+ data-slot="bounded-number-empty-label"
102
+ className={cn(
103
+ "pointer-events-none absolute inset-0 flex items-center justify-center",
104
+ "text-body text-muted-foreground",
105
+ )}
106
+ >
107
+ {resolvedEmptyLabel}
108
+ </span>
109
+ )}
110
+ </div>
111
+ );
112
+ },
113
+ );
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect } from "storybook/test";
2
3
  import {
3
4
  Breadcrumb,
4
5
  BreadcrumbItem,
@@ -45,3 +46,40 @@ export const Default: Story = {
45
46
  </Breadcrumb>
46
47
  ),
47
48
  };
49
+
50
+ /**
51
+ * #310 — `BreadcrumbLink` had no focus-visible styling of its own, so keyboard
52
+ * focus fell straight through to the browser's own default ring instead of
53
+ * the library's token-driven indicator (`focus-ring`, ADR 0027) every other
54
+ * interactive control carries. A class-string assertion would be worthless
55
+ * (it wouldn't prove anything actually paints) so this locks the RENDERED
56
+ * indicator: Tab to the first link and assert a real box-shadow exists,
57
+ * mirroring `Button`'s `CompoundFocusIndicator` lock.
58
+ */
59
+ export const FocusIndicator: Story = {
60
+ name: "Focus indicator (#310)",
61
+ render: () => (
62
+ <Breadcrumb>
63
+ <BreadcrumbList>
64
+ <BreadcrumbItem>
65
+ <BreadcrumbLink href="#">Home</BreadcrumbLink>
66
+ </BreadcrumbItem>
67
+ <BreadcrumbSeparator />
68
+ <BreadcrumbItem>
69
+ <BreadcrumbPage>brand-ui</BreadcrumbPage>
70
+ </BreadcrumbItem>
71
+ </BreadcrumbList>
72
+ </Breadcrumb>
73
+ ),
74
+ play: async ({ canvas, userEvent }) => {
75
+ const link = canvas.getByRole("link", { name: "Home" });
76
+
77
+ // Tab rather than .focus(): `:focus-visible` is what `focus-ring` keys
78
+ // on, and a programmatic focus does not reliably match it.
79
+ await userEvent.tab();
80
+ await expect(link).toHaveFocus();
81
+
82
+ const focused = getComputedStyle(link);
83
+ await expect(focused.boxShadow).not.toBe("none");
84
+ },
85
+ };