@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,263 @@
1
+ import type { ReactNode } from "react";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { fireEvent, render, screen, within } from "@testing-library/react";
4
+ import { FileText, Quote } from "lucide-react";
5
+
6
+ import { ContextRail, type ContextRailSection } from "./context-rail";
7
+
8
+ const sections: ContextRailSection[] = [
9
+ {
10
+ id: "sources",
11
+ label: "Sources",
12
+ icon: <FileText />,
13
+ count: 3,
14
+ content: <p>Sources body</p>,
15
+ },
16
+ {
17
+ id: "quotes",
18
+ label: "Quotes",
19
+ icon: <Quote />,
20
+ content: <p>Quotes body</p>,
21
+ },
22
+ ];
23
+
24
+ const ORIGINAL_INNER_WIDTH = window.innerWidth;
25
+
26
+ function setViewportWidth(width: number) {
27
+ Object.defineProperty(window, "innerWidth", {
28
+ writable: true,
29
+ configurable: true,
30
+ value: width,
31
+ });
32
+ }
33
+
34
+ describe("ContextRail", () => {
35
+ afterEach(() => {
36
+ setViewportWidth(ORIGINAL_INNER_WIDTH);
37
+ });
38
+
39
+ it("1. is reachable by exact accessible name when collapsed", () => {
40
+ render(<ContextRail sections={sections} open={false} />);
41
+ expect(screen.getByRole("button", { name: "Sources 3 items" })).toBeInTheDocument();
42
+ expect(screen.getByRole("button", { name: "Quotes" })).toBeInTheDocument();
43
+ });
44
+
45
+ it("2. mounts only the active section's content", () => {
46
+ render(<ContextRail sections={sections} activeSectionId="sources" />);
47
+ expect(screen.getByText("Sources body")).toBeInTheDocument();
48
+ expect(screen.queryByText("Quotes body")).not.toBeInTheDocument();
49
+ });
50
+
51
+ it("3. calls onActiveSectionChange with the clicked section's id", () => {
52
+ const onActiveSectionChange = vi.fn();
53
+ render(
54
+ <ContextRail
55
+ sections={sections}
56
+ activeSectionId="sources"
57
+ onActiveSectionChange={onActiveSectionChange}
58
+ />,
59
+ );
60
+ fireEvent.click(screen.getByRole("button", { name: "Quotes" }));
61
+ expect(onActiveSectionChange).toHaveBeenCalledWith("quotes");
62
+ });
63
+
64
+ it("4. defaults to sections[0] uncontrolled, honours defaultActiveSectionId, and updates on click with no controlled prop", () => {
65
+ const { unmount } = render(<ContextRail sections={sections} />);
66
+ expect(screen.getByText("Sources body")).toBeInTheDocument();
67
+ unmount();
68
+
69
+ render(<ContextRail sections={sections} defaultActiveSectionId="quotes" />);
70
+ expect(screen.getByText("Quotes body")).toBeInTheDocument();
71
+ fireEvent.click(screen.getByRole("button", { name: "Sources 3 items" }));
72
+ expect(screen.getByText("Sources body")).toBeInTheDocument();
73
+ expect(screen.queryByText("Quotes body")).not.toBeInTheDocument();
74
+ });
75
+
76
+ it("5. gives the active entry aria-current=true", () => {
77
+ render(<ContextRail sections={sections} activeSectionId="quotes" />);
78
+ expect(screen.getByRole("button", { name: "Quotes" })).toHaveAttribute("aria-current", "true");
79
+ expect(screen.getByRole("button", { name: "Sources 3 items" })).not.toHaveAttribute(
80
+ "aria-current",
81
+ );
82
+ });
83
+
84
+ // Finding 1 (task-11f-brief.md, BLOCKER): `isActive` was computed but never
85
+ // passed to `SidebarMenuButton`, so `data-active` defaulted to `false` on
86
+ // every entry and the ENTIRE active-state stylesheet (fill, label weight,
87
+ // icon colour, accent bar — all gated on `data-[active=true]:…` in
88
+ // `sidebarMenuButtonVariants`) never applied. Assert BOTH halves: the
89
+ // active entry carries `data-active="true"` AND an inactive one does not —
90
+ // asserting only the first half would pass against code that marks every
91
+ // entry active.
92
+ it("5b. gives the active entry data-active=true and an inactive one data-active=false", () => {
93
+ render(<ContextRail sections={sections} activeSectionId="quotes" />);
94
+ expect(screen.getByRole("button", { name: "Quotes" })).toHaveAttribute("data-active", "true");
95
+ expect(screen.getByRole("button", { name: "Sources 3 items" })).toHaveAttribute(
96
+ "data-active",
97
+ "false",
98
+ );
99
+ });
100
+
101
+ it("6. clicking the active entry toggles open; clicking an inactive entry opens and reports the new section", () => {
102
+ const onOpenChange = vi.fn();
103
+ const { rerender } = render(
104
+ <ContextRail
105
+ sections={sections}
106
+ activeSectionId="sources"
107
+ open={true}
108
+ onOpenChange={onOpenChange}
109
+ />,
110
+ );
111
+ fireEvent.click(screen.getByRole("button", { name: "Sources 3 items" }));
112
+ expect(onOpenChange).toHaveBeenCalledWith(false);
113
+
114
+ onOpenChange.mockClear();
115
+ const onActiveSectionChange = vi.fn();
116
+ rerender(
117
+ <ContextRail
118
+ sections={sections}
119
+ activeSectionId="sources"
120
+ open={false}
121
+ onOpenChange={onOpenChange}
122
+ onActiveSectionChange={onActiveSectionChange}
123
+ />,
124
+ );
125
+ fireEvent.click(screen.getByRole("button", { name: "Quotes" }));
126
+ expect(onOpenChange).toHaveBeenCalledWith(true);
127
+ expect(onActiveSectionChange).toHaveBeenCalledWith("quotes");
128
+ });
129
+
130
+ it("7. disabled sections render a disabled button and do not fire onActiveSectionChange", () => {
131
+ const onActiveSectionChange = vi.fn();
132
+ const disabledSections: ContextRailSection[] = [
133
+ sections[0]!,
134
+ { ...sections[1]!, disabled: true },
135
+ ];
136
+ render(
137
+ <ContextRail
138
+ sections={disabledSections}
139
+ activeSectionId="sources"
140
+ onActiveSectionChange={onActiveSectionChange}
141
+ />,
142
+ );
143
+ const quotesButton = screen.getByRole("button", { name: "Quotes" });
144
+ expect(quotesButton).toBeDisabled();
145
+ fireEvent.click(quotesButton);
146
+ expect(onActiveSectionChange).not.toHaveBeenCalled();
147
+ });
148
+
149
+ it("8. renders the empty slot for an empty sections array, localized by default, overridable via `empty`", () => {
150
+ const { container, rerender } = render(<ContextRail sections={[]} />);
151
+ const emptySlot = container.querySelector('[data-slot="context-rail-empty"]');
152
+ expect(emptySlot).not.toBeNull();
153
+ expect(within(emptySlot as HTMLElement).getByText("No sections")).toBeInTheDocument();
154
+
155
+ const custom: ReactNode = <p>Nothing to show here</p>;
156
+ rerender(<ContextRail sections={[]} empty={custom} />);
157
+ const customEmptySlot = container.querySelector('[data-slot="context-rail-empty"]');
158
+ expect(customEmptySlot).not.toBeNull();
159
+ expect(screen.getByText("Nothing to show here")).toBeInTheDocument();
160
+ expect(screen.queryByText("No sections")).not.toBeInTheDocument();
161
+ });
162
+
163
+ it("9. publishes `width` as --sidebar-width on the provider element, defaulting to 20rem", () => {
164
+ const { container, rerender } = render(<ContextRail sections={sections} />);
165
+ const provider = container.firstElementChild as HTMLElement;
166
+ expect(provider.getAttribute("style") ?? "").toContain("--sidebar-width: 20rem");
167
+
168
+ rerender(<ContextRail sections={sections} width="24rem" />);
169
+ const providerAfter = container.firstElementChild as HTMLElement;
170
+ expect(providerAfter.getAttribute("style") ?? "").toContain("--sidebar-width: 24rem");
171
+ });
172
+
173
+ it("10. warns exactly once when overlayBreakpoint < 768, and not when >= 768", () => {
174
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
175
+ const { unmount } = render(<ContextRail sections={sections} overlayBreakpoint={500} />);
176
+ expect(warnSpy).toHaveBeenCalledTimes(1);
177
+ unmount();
178
+ warnSpy.mockClear();
179
+
180
+ render(<ContextRail sections={sections} overlayBreakpoint={768} />);
181
+ expect(warnSpy).not.toHaveBeenCalled();
182
+ warnSpy.mockRestore();
183
+ });
184
+
185
+ it("11. below overlayBreakpoint renders the narrow strip (no [data-slot=sidebar]) with the same switcher names", () => {
186
+ setViewportWidth(500);
187
+ const { container } = render(<ContextRail sections={sections} />);
188
+ expect(container.querySelector('[data-slot="sidebar"]')).toBeNull();
189
+ expect(container.querySelector('[data-slot="context-rail"]')).not.toBeNull();
190
+ expect(screen.getByRole("button", { name: "Sources 3 items" })).toBeInTheDocument();
191
+ expect(screen.getByRole("button", { name: "Quotes" })).toBeInTheDocument();
192
+ });
193
+
194
+ it("12. renders all seven data-slot values on an open, non-empty rail", () => {
195
+ const { container } = render(
196
+ <ContextRail sections={sections} open={true} activeSectionId="sources" />,
197
+ );
198
+ for (const slot of [
199
+ "context-rail",
200
+ "context-rail-switcher",
201
+ "context-rail-switcher-item",
202
+ "context-rail-count",
203
+ "context-rail-heading",
204
+ "context-rail-body",
205
+ ]) {
206
+ expect(
207
+ container.querySelector(`[data-slot="${slot}"]`),
208
+ `expected [data-slot="${slot}"] to be present`,
209
+ ).not.toBeNull();
210
+ }
211
+ });
212
+
213
+ // #382 — `variant` used to be a derived (`Omit<ComponentProps<typeof
214
+ // Sidebar>, …>`) member of `ContextRailProps`, honoured only in the wide
215
+ // (`Sidebar`) branch and silently inert below `overlayBreakpoint` (the
216
+ // narrow branch has no `Sidebar` to forward it to). Fixed by declaring the
217
+ // props surface instead of deriving it — `variant` is no longer a public
218
+ // prop at all. This is a compile-time lock: it fails the moment someone
219
+ // re-derives `ContextRailProps` from `Sidebar`, which is the only way the
220
+ // prop returns.
221
+ it("13. (type-level) does not accept a `variant` prop", () => {
222
+ function typeOnly() {
223
+ // @ts-expect-error — `variant` was removed from `ContextRailProps`; a
224
+ // consumer can no longer pass it, and it can no longer be silently
225
+ // inert in the narrow branch.
226
+ return <ContextRail sections={[]} variant="floating" />;
227
+ }
228
+ expect(typeof typeOnly).toBe("function");
229
+ });
230
+
231
+ // Companion to #13: the type was re-derived from `ComponentProps<"div">`
232
+ // and stopped omitting `children` alongside `onSelect`, unintentionally
233
+ // making `children` a public prop again even though both `ContextRailWide`
234
+ // and `ContextRailNarrow` supply their own JSX children after spreading
235
+ // `props` — a caller's `children` type-checks and is then silently
236
+ // discarded at render, the same advertised-but-inert failure #382 fixed
237
+ // for `variant`. Compile-time lock: fails the moment `children` is
238
+ // dropped from the `Omit` again.
239
+ it("15. (type-level) does not accept a `children` prop", () => {
240
+ function typeOnly() {
241
+ // @ts-expect-error — `children` is omitted from `ContextRailProps`;
242
+ // both render branches discard it after spreading `props`, so it
243
+ // must not be publicly accepted.
244
+ return <ContextRail sections={[]}>discarded</ContextRail>;
245
+ }
246
+ expect(typeof typeOnly).toBe("function");
247
+ });
248
+
249
+ it("14. the wide branch renders data-variant=sidebar on the sidebar root; the narrow branch renders no data-variant at all", () => {
250
+ const wide = render(<ContextRail sections={sections} open={true} activeSectionId="sources" />);
251
+ const sidebarRoot = wide.container.querySelector('[data-slot="sidebar"]');
252
+ expect(sidebarRoot).not.toBeNull();
253
+ expect(sidebarRoot).toHaveAttribute("data-variant", "sidebar");
254
+ wide.unmount();
255
+
256
+ setViewportWidth(500);
257
+ const narrow = render(
258
+ <ContextRail sections={sections} open={true} activeSectionId="sources" />,
259
+ );
260
+ expect(narrow.container.querySelector("[data-variant]")).toBeNull();
261
+ narrow.unmount();
262
+ });
263
+ });