@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
@@ -0,0 +1,94 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { findTriggerQuery, replaceTriggerRun } from "./trigger-query";
3
+
4
+ describe("findTriggerQuery — word boundary (default)", () => {
5
+ it("finds a trigger + query run under the caret", () => {
6
+ const text = "Hi @ada";
7
+ expect(findTriggerQuery(text, text.length, "@")).toEqual({ start: 3, query: "ada" });
8
+ });
9
+
10
+ it("returns null when there is no trigger before the caret", () => {
11
+ expect(findTriggerQuery("hello", 5, "@")).toBeNull();
12
+ });
13
+
14
+ it("returns null when the run contains whitespace (the query already closed)", () => {
15
+ const text = "Hi @ada lovelace";
16
+ expect(findTriggerQuery(text, text.length, "@")).toBeNull();
17
+ });
18
+
19
+ it("returns null when the trigger is not at a word boundary (inside a token)", () => {
20
+ const text = "ping ada@example.com";
21
+ const at = text.indexOf("@");
22
+ expect(findTriggerQuery(text, at + 1, "@")).toBeNull();
23
+ });
24
+
25
+ it("allows the trigger at index 0", () => {
26
+ expect(findTriggerQuery("@ada", 4, "@")).toEqual({ start: 0, query: "ada" });
27
+ });
28
+
29
+ it("allows the trigger right after '(' or '['", () => {
30
+ expect(findTriggerQuery("see (@ada", 9, "@")).toEqual({ start: 5, query: "ada" });
31
+ expect(findTriggerQuery("see [@ada", 9, "@")).toEqual({ start: 5, query: "ada" });
32
+ });
33
+
34
+ it("supports a multi-character trigger", () => {
35
+ const text = "run ::deploy";
36
+ expect(findTriggerQuery(text, text.length, "::")).toEqual({ start: 4, query: "deploy" });
37
+ });
38
+ });
39
+
40
+ describe("findTriggerQuery — line-start boundary", () => {
41
+ it("opens a query at the start of a line", () => {
42
+ const text = "/help";
43
+ expect(findTriggerQuery(text, text.length, "/", { boundary: "line-start" })).toEqual({
44
+ start: 0,
45
+ query: "help",
46
+ });
47
+ });
48
+
49
+ it("opens a query right after a newline", () => {
50
+ const text = "first line\n/help";
51
+ expect(findTriggerQuery(text, text.length, "/", { boundary: "line-start" })).toEqual({
52
+ start: 11,
53
+ query: "help",
54
+ });
55
+ });
56
+
57
+ it("does not open mid-line, even at a word boundary the 'word' boundary would accept", () => {
58
+ const text = "cd /usr";
59
+ expect(findTriggerQuery(text, text.length, "/", { boundary: "line-start" })).toBeNull();
60
+ });
61
+ });
62
+
63
+ describe("findTriggerQuery — isTriggerConsumed veto", () => {
64
+ it("returns null when the candidate trigger has already been consumed", () => {
65
+ const text = "Hi @ada";
66
+ const result = findTriggerQuery(text, text.length, "@", {
67
+ isTriggerConsumed: (start) => start === 3,
68
+ });
69
+ expect(result).toBeNull();
70
+ });
71
+
72
+ it("returns the candidate when isTriggerConsumed reports false", () => {
73
+ const text = "Hi @ada";
74
+ const result = findTriggerQuery(text, text.length, "@", {
75
+ isTriggerConsumed: () => false,
76
+ });
77
+ expect(result).toEqual({ start: 3, query: "ada" });
78
+ });
79
+ });
80
+
81
+ describe("replaceTriggerRun", () => {
82
+ it("splices the insert text over the trigger + query run", () => {
83
+ const text = "Hi @ada how are you";
84
+ const result = replaceTriggerRun(text, 3, 7, "@Ada Lovelace ");
85
+ expect(result.text).toBe("Hi @Ada Lovelace how are you");
86
+ expect(result.caret).toBe(3 + "@Ada Lovelace ".length);
87
+ });
88
+
89
+ it("places the caret immediately after the inserted text", () => {
90
+ const result = replaceTriggerRun("/help", 0, 5, "/deploy ");
91
+ expect(result.text).toBe("/deploy ");
92
+ expect(result.caret).toBe("/deploy ".length);
93
+ });
94
+ });
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The generic, component-agnostic half of a trigger-driven inline palette —
3
+ * "is the caret sitting inside a `trigger + query` run, and how do I splice a
4
+ * chosen result back in". Zero React, zero DOM, zero knowledge of mentions.
5
+ *
6
+ * Lifted out of `MentionInput`'s private `findQuery`/`isWordBoundary`
7
+ * (docs/decisions/2026-09-01-brainless-adoption-architecture.md § 5) so a second
8
+ * trigger-driven surface (a slash-command palette) reuses the algorithm instead
9
+ * of re-implementing it. `MentionInput` consumes this module unchanged — see
10
+ * `mention-input.tsx`.
11
+ */
12
+
13
+ /**
14
+ * Where a trigger character is allowed to START a query.
15
+ *
16
+ * `"word"` — index 0, or preceded by whitespace / "(" / "[". (`MentionInput`'s
17
+ * behaviour, unchanged — an "@" inside an email address must not open a popup.)
18
+ * `"line-start"` — index 0, or preceded by "\n". (A slash palette: "/help" opens, "cd /usr" does not.)
19
+ */
20
+ export type TriggerBoundary = "word" | "line-start";
21
+
22
+ /** An in-progress `trigger + query` run under the caret. */
23
+ export interface TriggerQuery {
24
+ /** Offset of the trigger character in `text`. */
25
+ start: number;
26
+ /** The query WITHOUT the trigger character. */
27
+ query: string;
28
+ }
29
+
30
+ export interface FindTriggerQueryOptions {
31
+ /** @default "word" */
32
+ boundary?: TriggerBoundary;
33
+ /**
34
+ * Veto a candidate whose trigger character has already been consumed by a
35
+ * committed token — `MentionInput` passes `mentionAt(...)` here so an
36
+ * inserted "@Ada Lovelace" cannot re-open the popup. Omit when there are no
37
+ * committed tokens (a slash palette has none).
38
+ */
39
+ isTriggerConsumed?: (start: number) => boolean;
40
+ }
41
+
42
+ function isBoundary(text: string, index: number, boundary: TriggerBoundary): boolean {
43
+ if (index === 0) return true;
44
+ const previous = text[index - 1] ?? "";
45
+ if (boundary === "line-start") return previous === "\n";
46
+ return /\s/.test(previous) || previous === "(" || previous === "[";
47
+ }
48
+
49
+ /**
50
+ * Is the caret sitting inside a `trigger + query` run?
51
+ *
52
+ * DERIVED from committed text + caret, never intercepted at keydown — a
53
+ * keydown-driven trigger must `preventDefault()` every printable character and
54
+ * re-insert it by hand, which destroys IME composition, native undo, spellcheck
55
+ * and paste. Deriving means the popup opens identically whether the trigger
56
+ * arrived by typing, pasting, an IME commit, or a click that moved the caret
57
+ * back into a half-typed query.
58
+ */
59
+ export function findTriggerQuery(
60
+ text: string,
61
+ caret: number,
62
+ trigger: string,
63
+ options?: FindTriggerQueryOptions,
64
+ ): TriggerQuery | null {
65
+ const boundary = options?.boundary ?? "word";
66
+
67
+ if (caret < trigger.length) return null;
68
+ const start = text.lastIndexOf(trigger, caret - trigger.length);
69
+ if (start < 0) return null;
70
+
71
+ const query = text.slice(start + trigger.length, caret);
72
+ if (/\s/.test(query)) return null;
73
+ if (!isBoundary(text, start, boundary)) return null;
74
+ if (options?.isTriggerConsumed?.(start)) return null;
75
+
76
+ return { start, query };
77
+ }
78
+
79
+ /** The plain-text half of an insert: splice `insertText` over the run, and say where the caret lands. */
80
+ export function replaceTriggerRun(
81
+ text: string,
82
+ queryStart: number,
83
+ caret: number,
84
+ insertText: string,
85
+ ): { text: string; caret: number } {
86
+ const nextText = text.slice(0, queryStart) + insertText + text.slice(caret);
87
+ return { text: nextText, caret: queryStart + insertText.length };
88
+ }
@@ -0,0 +1,47 @@
1
+ "use client";
2
+
3
+ import { useCallback, useRef, useState } from "react";
4
+
5
+ /**
6
+ * Shared controlled/uncontrolled state primitive.
7
+ *
8
+ * `isControlled` is fixed by whether `controlledValue` is `undefined` on the
9
+ * FIRST render (the usual React contract — don't flip modes across renders).
10
+ * The setter always does BOTH of its jobs, unconditionally:
11
+ * - uncontrolled: updates the internal state (so the next render reflects it)
12
+ * - always: calls `onChange`, so a listener attached to an otherwise
13
+ * uncontrolled component still observes every change.
14
+ *
15
+ * A hand-rolled `value ?? internal` + `onChange ? onChange(v) : setInternal(v)`
16
+ * ternary gets this wrong in two ways: an uncontrolled component with an
17
+ * `onChange` handler stops updating its own displayed value (the ternary
18
+ * only ever takes one branch), and clearing a controlled value to `undefined`
19
+ * falls through to a stale `internal` instead of showing "no value" — see
20
+ * Combobox/DatePicker (#reviewed 1.7).
21
+ */
22
+ export function useControllableState<T>(
23
+ controlledValue: T | undefined,
24
+ defaultValue: T,
25
+ onChange?: (value: T) => void,
26
+ ): [T, (next: T) => void] {
27
+ // Locked on mount, not recomputed every render: once a caller passes a
28
+ // defined `value`, the component stays controlled even if a later render
29
+ // clears it to `undefined` (e.g. "no date selected"). Recomputing
30
+ // `value !== undefined` on every render would flip a cleared controlled
31
+ // value to the (stale) uncontrolled `internal` state instead of showing
32
+ // "empty" — see DateRangePicker (#reviewed 1.7).
33
+ const isControlledRef = useRef(controlledValue !== undefined);
34
+ const isControlled = isControlledRef.current;
35
+ const [internal, setInternal] = useState<T>(defaultValue);
36
+ const value = isControlled ? (controlledValue as T) : internal;
37
+
38
+ const setValue = useCallback(
39
+ (next: T) => {
40
+ if (!isControlled) setInternal(next);
41
+ onChange?.(next);
42
+ },
43
+ [isControlled, onChange],
44
+ );
45
+
46
+ return [value, setValue];
47
+ }
@@ -0,0 +1,47 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { useIsMobile } from "./use-mobile";
4
+
5
+ function setViewportWidth(width: number) {
6
+ Object.defineProperty(window, "innerWidth", {
7
+ writable: true,
8
+ configurable: true,
9
+ value: width,
10
+ });
11
+ }
12
+
13
+ describe("useIsMobile", () => {
14
+ const originalInnerWidth = window.innerWidth;
15
+
16
+ afterEach(() => {
17
+ setViewportWidth(originalInnerWidth);
18
+ });
19
+
20
+ it("reports the correct value on the very first render — no desktop flash", () => {
21
+ setViewportWidth(500);
22
+ const renderedValues: boolean[] = [];
23
+ function Probe() {
24
+ const isMobile = useIsMobile();
25
+ renderedValues.push(isMobile);
26
+ return <span>{String(isMobile)}</span>;
27
+ }
28
+ render(<Probe />);
29
+
30
+ // A stale `false` in the recorded renders means the mobile viewport
31
+ // still rendered the desktop layout for at least one paint before the
32
+ // effect corrected it — i.e. a flash.
33
+ expect(renderedValues.length).toBeGreaterThan(0);
34
+ expect(renderedValues.every((value) => value === true)).toBe(true);
35
+ expect(screen.getByText("true")).toBeInTheDocument();
36
+ });
37
+
38
+ it("reports false on a desktop-width viewport", () => {
39
+ setViewportWidth(1280);
40
+ function Probe() {
41
+ const isMobile = useIsMobile();
42
+ return <span>{String(isMobile)}</span>;
43
+ }
44
+ render(<Probe />);
45
+ expect(screen.getByText("false")).toBeInTheDocument();
46
+ });
47
+ });
@@ -1,16 +1,38 @@
1
- import { useEffect, useState } from "react";
1
+ "use client";
2
+
3
+ import { useSyncExternalStore } from "react";
2
4
 
3
5
  const MOBILE_BREAKPOINT = 768;
4
6
 
5
- /** Returns true when the viewport is below the mobile breakpoint (768px). */
6
- export function useIsMobile(): boolean {
7
- const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);
8
- useEffect(() => {
9
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
10
- const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
11
- mql.addEventListener("change", onChange);
12
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
13
- return () => mql.removeEventListener("change", onChange);
14
- }, []);
15
- return !!isMobile;
7
+ function subscribe(breakpoint: number, onStoreChange: () => void) {
8
+ const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
9
+ mql.addEventListener("change", onStoreChange);
10
+ return () => mql.removeEventListener("change", onStoreChange);
11
+ }
12
+
13
+ function getSnapshot(breakpoint: number) {
14
+ return window.innerWidth < breakpoint;
15
+ }
16
+
17
+ // SSR / first-paint snapshot: no viewport to measure yet, so report "not
18
+ // mobile" rather than guessing — this must match what the server rendered to
19
+ // avoid a hydration mismatch, and useSyncExternalStore re-syncs to the real
20
+ // value on the client before paint (no desktop-layout flash).
21
+ function getServerSnapshot() {
22
+ return false;
23
+ }
24
+
25
+ /**
26
+ * Returns true when the viewport is below `breakpoint` (default 768px, the
27
+ * mobile breakpoint). Additive optional argument (ADR 0035 §5) — every
28
+ * existing zero-arg call site is unchanged. A caller with its own threshold
29
+ * (e.g. `ContextRail`'s `overlayBreakpoint`) passes it directly instead of a
30
+ * second hook.
31
+ */
32
+ export function useIsMobile(breakpoint: number = MOBILE_BREAKPOINT): boolean {
33
+ return useSyncExternalStore(
34
+ (onStoreChange) => subscribe(breakpoint, onStoreChange),
35
+ () => getSnapshot(breakpoint),
36
+ getServerSnapshot,
37
+ );
16
38
  }
@@ -235,6 +235,12 @@ export const GateFloorNeverZero: Story = {
235
235
  * additions, these resolve to defaults/none and this fails.
236
236
  */
237
237
  export const UtilitiesGenerated: Story = {
238
+ // The suite runs under `prefers-reduced-motion: reduce`
239
+ // (`apps/docs/vitest.config.ts`, #125), which collapses the CSS gate to its
240
+ // floor. This assertion is ABOUT the full-motion scale, so the story opts
241
+ // back in explicitly — `data-motion-pref="full"` is the documented
242
+ // informed-consent override that beats the OS rule.
243
+ globals: { motionPref: "full" },
238
244
  render: () => (
239
245
  <div>
240
246
  <span data-testid="util-fast" className="transition-opacity duration-fast ease-standard" />
@@ -209,7 +209,7 @@ function PipelineList({
209
209
  onClick={() => onSelect(p.id)}
210
210
  className={[
211
211
  "flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-start transition-colors",
212
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
212
+ "focus-ring",
213
213
  selected ? "bg-primary/10 text-foreground" : "hover:bg-surface-muted",
214
214
  ].join(" ")}
215
215
  >
@@ -449,7 +449,7 @@ const meta = {
449
449
  docs: {
450
450
  description: {
451
451
  component:
452
- "The canonical master-detail enterprise screen: a searchable object list (SplitPanel `start`) drives a detail HUB (`end`) with a sticky SectionHeader identity + status, a ButtonGroup toolbar, Tabs for the object's sections (Overview / Activity / Settings), Descriptions for attributes, a related-runs list, and a high-value Run task promoted to its own Dialog. Selecting nothing shows a real StatePanel empty state. Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in all three themes.",
452
+ "The canonical master-detail enterprise screen: a searchable object list (SplitPanel `start`) drives a detail HUB (`end`) with a sticky SectionHeader identity + status, a ButtonGroup toolbar, Tabs for the object's sections (Overview / Activity / Settings), Descriptions for attributes, a related-runs list, and a high-value Run task promoted to its own Dialog. Selecting nothing shows a real StatePanel empty state. Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in every theme.",
453
453
  },
454
454
  },
455
455
  },
@@ -14,8 +14,8 @@
14
14
  * • Error — StatePanel kind="error" with a retry action (role="alert").
15
15
  * • FirstRun — onboarding empty: illustration slot + one sentence + one CTA.
16
16
  *
17
- * Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in all
18
- * three themes. Verify with globals=theme:<slug>.
17
+ * Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in
18
+ * every theme. Verify with globals=theme:<slug>.
19
19
  */
20
20
  import type { Meta, StoryObj } from "@storybook/react-vite";
21
21
  import { Inbox, Plus, RotateCcw, Search, UploadCloud, UserPlus } from "lucide-react";
@@ -210,8 +210,8 @@ function CustomersScreen({ state = "ready" }: { state?: ScreenState }) {
210
210
  {state === "error" && (
211
211
  <StatePanel
212
212
  kind="error"
213
- title="Couldn't load customers"
214
- description="The billing service didn't respond. Your data is safe — this is a display error."
213
+ title="Couldnt load customers"
214
+ description="The billing service didnt respond. Your data is safe — this is a display error."
215
215
  actions={
216
216
  <Button variant="outline" size="sm" onClick={() => toast.success("Retrying…")}>
217
217
  <RotateCcw className="size-4" aria-hidden="true" />
@@ -273,7 +273,7 @@ const meta = {
273
273
  docs: {
274
274
  description: {
275
275
  component:
276
- 'One representative data screen (Customers) rendered across the FULL state grid — Ready, Loading, Empty, Error, FirstRun — with the same chrome and the real state primitives (Table, Skeleton, StatePanel kind="empty"|"error"). This is the design-first state grid that the Patterns/Templates examples omit. Semantic tokens only; reads in all three themes.',
276
+ 'One representative data screen (Customers) rendered across the FULL state grid — Ready, Loading, Empty, Error, FirstRun — with the same chrome and the real state primitives (Table, Skeleton, StatePanel kind="empty"|"error"). This is the design-first state grid that the Patterns/Templates examples omit. Semantic tokens only; reads in every theme.',
277
277
  },
278
278
  },
279
279
  },
@@ -3,7 +3,7 @@
3
3
  * (app-shell + Descriptions + Wizard). This story is the single source of
4
4
  * truth: `pnpm gen:templates` derives the consumer template source
5
5
  * (`docs/playbooks/templates/settings.tsx`) from it.
6
- * Verify across all three themes with globals=theme:<slug>.
6
+ * Verify across every theme with globals=theme:<slug>.
7
7
  */
8
8
  import type { Meta, StoryObj } from "@storybook/react-vite";
9
9
  import { useState } from "react";
@@ -1,160 +0,0 @@
1
- /** sidebar-02 — dashboard sidebar (copy-owned block). Adapted from blocks.so
2
- * (ephraim duncan). framer-motion removed (plain fade); next/link -> <a>. */
3
- "use client";
4
-
5
- import {
6
- Activity,
7
- DollarSign,
8
- Home,
9
- Infinity as InfinityIcon,
10
- Link as LinkIcon,
11
- Package2,
12
- Percent,
13
- PieChart,
14
- Settings,
15
- ShoppingBag,
16
- Sparkles,
17
- Store,
18
- TrendingUp,
19
- Users,
20
- } from "lucide-react";
21
- import { AppSidebar, SidebarTrigger, cn, useSidebar } from "@elabs-ai/components-ui";
22
- import { Logo } from "./logo";
23
- import DashboardNavigation, { type Route } from "./nav-main";
24
- import { NotificationsPopover } from "./nav-notifications";
25
- import { TeamSwitcher } from "./team-switcher";
26
-
27
- const sampleNotifications = [
28
- {
29
- id: "1",
30
- fallback: "OM",
31
- text: "New order received.",
32
- time: "10m ago",
33
- },
34
- {
35
- id: "2",
36
- fallback: "JL",
37
- text: "Server upgrade completed.",
38
- time: "1h ago",
39
- },
40
- {
41
- id: "3",
42
- fallback: "HH",
43
- text: "New user signed up.",
44
- time: "2h ago",
45
- },
46
- ];
47
-
48
- const dashboardRoutes: Route[] = [
49
- { id: "home", title: "Home", icon: <Home className="size-4" />, link: "#" },
50
- {
51
- id: "products",
52
- title: "Products",
53
- icon: <Package2 className="size-4" />,
54
- link: "#",
55
- subs: [
56
- { title: "Catalogue", link: "#" },
57
- { title: "Checkout Links", link: "#" },
58
- { title: "Discounts", link: "#" },
59
- ],
60
- },
61
- {
62
- id: "usage-billing",
63
- title: "Usage Billing",
64
- icon: <PieChart className="size-4" />,
65
- link: "#",
66
- subs: [
67
- { title: "Meters", link: "#" },
68
- { title: "Events", link: "#" },
69
- ],
70
- },
71
- { id: "benefits", title: "Benefits", icon: <Sparkles className="size-4" />, link: "#" },
72
- { id: "customers", title: "Customers", icon: <Users className="size-4" />, link: "#" },
73
- {
74
- id: "sales",
75
- title: "Sales",
76
- icon: <ShoppingBag className="size-4" />,
77
- link: "#",
78
- subs: [
79
- { title: "Orders", link: "#" },
80
- { title: "Subscriptions", link: "#" },
81
- ],
82
- },
83
- { id: "storefront", title: "Storefront", icon: <Store className="size-4" />, link: "#" },
84
- { id: "analytics", title: "Analytics", icon: <TrendingUp className="size-4" />, link: "#" },
85
- {
86
- id: "finance",
87
- title: "Finance",
88
- icon: <DollarSign className="size-4" />,
89
- link: "#",
90
- subs: [
91
- { title: "Incoming", link: "#" },
92
- { title: "Outgoing", link: "#" },
93
- { title: "Payout Account", link: "#" },
94
- ],
95
- },
96
- {
97
- id: "settings",
98
- title: "Settings",
99
- icon: <Settings className="size-4" />,
100
- link: "#",
101
- subs: [
102
- { title: "General", link: "#" },
103
- { title: "Webhooks", link: "#" },
104
- { title: "Custom Fields", link: "#" },
105
- ],
106
- },
107
- ];
108
-
109
- // Reference icons retained for parity with the source data shape.
110
- void Activity;
111
- void InfinityIcon;
112
- void LinkIcon;
113
- void Percent;
114
-
115
- const teams = [
116
- { name: "Alpha Inc.", logo: Logo, plan: "Free" },
117
- { name: "Beta Corp.", logo: Logo, plan: "Free" },
118
- { name: "Gamma Tech", logo: Logo, plan: "Free" },
119
- ];
120
-
121
- export function DashboardSidebar() {
122
- const { state } = useSidebar();
123
- const isCollapsed = state === "collapsed";
124
-
125
- const header = (
126
- <div
127
- className={cn(
128
- "flex md:pt-3.5",
129
- isCollapsed
130
- ? "flex-row items-center justify-between gap-y-4 md:flex-col md:items-start md:justify-start"
131
- : "flex-row items-center justify-between",
132
- )}
133
- >
134
- <a href="#" className="flex items-center gap-2">
135
- <Logo className="h-8 w-8" />
136
- {!isCollapsed && <span className="font-semibold text-foreground">Acme</span>}
137
- </a>
138
- <div
139
- className={cn(
140
- "flex items-center gap-2 animate-in fade-in",
141
- isCollapsed ? "flex-row md:flex-col-reverse" : "flex-row",
142
- )}
143
- >
144
- <NotificationsPopover notifications={sampleNotifications} />
145
- <SidebarTrigger />
146
- </div>
147
- </div>
148
- );
149
-
150
- return (
151
- <AppSidebar
152
- variant="inset"
153
- collapsible="icon"
154
- header={header}
155
- footer={<TeamSwitcher teams={teams} />}
156
- >
157
- <DashboardNavigation routes={dashboardRoutes} className="gap-4 px-2 py-4" />
158
- </AppSidebar>
159
- );
160
- }
@@ -1,18 +0,0 @@
1
- /** sidebar-02 — Acme logo mark (copy-owned block). */
2
- import type { SVGProps } from "react";
3
-
4
- export const Logo = (props: SVGProps<SVGSVGElement>) => (
5
- <svg fill="currentColor" height="48" viewBox="0 0 40 48" width="40" {...props}>
6
- <clipPath id="acme-a">
7
- <path d="m0 0h40v48h-40z" />
8
- </clipPath>
9
- <g clipPath="url(#acme-a)">
10
- <path d="m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z" />
11
- <path d="m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z" />
12
- <path d="m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z" />
13
- <path d="m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z" />
14
- <path d="m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z" />
15
- <path d="m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z" />
16
- </g>
17
- </svg>
18
- );
@@ -1,13 +0,0 @@
1
- /**
2
- * sidebar-02 — primary navigation.
3
- * Re-exports the shared `NavMain` primitive from `@elabs-ai/components-ui` so this block
4
- * no longer maintains its own copy (issue #99).
5
- *
6
- * The block-local alias `DashboardNavigation` is preserved for back-compat
7
- * so `app-sidebar.tsx` does not need to change its import name.
8
- */
9
- export { NavMain as default, NavMain as DashboardNavigation } from "@elabs-ai/components-ui";
10
- export type {
11
- NavMainProps as DashboardNavigationProps,
12
- NavMainRoute as Route,
13
- } from "@elabs-ai/components-ui";
@@ -1,7 +0,0 @@
1
- /**
2
- * sidebar-02 — notifications popover.
3
- * Re-exports the shared `NavNotifications` primitive from `@elabs-ai/components-ui` so this
4
- * block no longer maintains its own copy (issue #99).
5
- */
6
- export { NavNotifications as NotificationsPopover } from "@elabs-ai/components-ui";
7
- export type { NavNotificationsProps, NavNotification } from "@elabs-ai/components-ui";
@@ -1,23 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { SidebarInset, SidebarProvider } from "@elabs-ai/components-ui";
3
- import { DashboardSidebar } from "./app-sidebar";
4
-
5
- const meta = {
6
- title: "Layout/App Shell/Dashboard",
7
- parameters: { layout: "fullscreen" },
8
- } satisfies Meta;
9
- export default meta;
10
- type Story = StoryObj<typeof meta>;
11
-
12
- export const Default: Story = {
13
- render: () => (
14
- <SidebarProvider>
15
- <div className="relative flex h-dvh w-full">
16
- <DashboardSidebar />
17
- <SidebarInset className="flex flex-col p-6 text-body text-muted-foreground">
18
- Dashboard content area (collapsible icon sidebar — press ⌘/Ctrl+B).
19
- </SidebarInset>
20
- </div>
21
- </SidebarProvider>
22
- ),
23
- };
@@ -1,7 +0,0 @@
1
- /**
2
- * sidebar-02 — team switcher.
3
- * Re-exports the shared `TeamSwitcher` primitive from `@elabs-ai/components-ui` so this block
4
- * no longer maintains its own copy (issue #99).
5
- */
6
- export { TeamSwitcher } from "@elabs-ai/components-ui";
7
- export type { TeamSwitcherProps, TeamSwitcherTeam } from "@elabs-ai/components-ui";