@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,299 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { useState } from "react";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { SideDock } from "./side-dock";
5
+
6
+ /**
7
+ * Every test that wants the COLUMN branch passes `overlayBreakpoint={800}` —
8
+ * jsdom's `window.innerWidth` is 1024 and its stubbed `matchMedia` always
9
+ * reports `matches: false`, so the default `overlayBreakpoint` (1100) would
10
+ * render the OVERLAY branch in every test (see task-10-brief.md, Mechanism §2).
11
+ * The one test that wants the overlay branch passes `overlayBreakpoint={2000}`.
12
+ */
13
+ const COLUMN_BREAKPOINT = 800;
14
+
15
+ describe("SideDock", () => {
16
+ // 1. Names the region.
17
+ it("names the complementary region from `title`", () => {
18
+ render(
19
+ <SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
20
+ Body
21
+ </SideDock>,
22
+ );
23
+ expect(screen.getByRole("complementary")).toHaveAccessibleName("Assistant");
24
+ });
25
+
26
+ // 2. Names the handle and reports its value.
27
+ it("names the resize handle and reports its current value", () => {
28
+ render(
29
+ <SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
30
+ Body
31
+ </SideDock>,
32
+ );
33
+ const handle = screen.getByRole("separator");
34
+ expect(handle).toHaveAccessibleName("Resize Assistant");
35
+ expect(handle).toHaveAttribute("aria-orientation", "vertical");
36
+ expect(handle).toHaveAttribute("aria-valuenow", "400");
37
+ expect(handle).toHaveAttribute("aria-valuemin", "320");
38
+ expect(handle.getAttribute("aria-valuetext")).toContain("400 pixels");
39
+ });
40
+
41
+ // 3. Keyboard widens (right-hand dock: ArrowLeft widens).
42
+ it("widens on ArrowLeft and commits once on keyup", () => {
43
+ const onWidthChange = vi.fn();
44
+ const onWidthCommit = vi.fn();
45
+ render(
46
+ <SideDock
47
+ title="Assistant"
48
+ open
49
+ overlayBreakpoint={COLUMN_BREAKPOINT}
50
+ onWidthChange={onWidthChange}
51
+ onWidthCommit={onWidthCommit}
52
+ >
53
+ Body
54
+ </SideDock>,
55
+ );
56
+ const handle = screen.getByRole("separator");
57
+ handle.focus();
58
+ fireEvent.keyDown(handle, { key: "ArrowLeft" });
59
+ expect(onWidthChange).toHaveBeenCalledTimes(1);
60
+ const widened = onWidthChange.mock.calls[0]![0] as number;
61
+ expect(widened).toBeGreaterThan(400);
62
+ expect(onWidthCommit).not.toHaveBeenCalled();
63
+ fireEvent.keyUp(handle, { key: "ArrowLeft" });
64
+ expect(onWidthCommit).toHaveBeenCalledTimes(1);
65
+ expect(onWidthCommit).toHaveBeenCalledWith(widened);
66
+ });
67
+
68
+ // 4. Keyboard narrows; Home/End jump to the effective bounds.
69
+ // CORRECTED 2026-09-05 (Ruling 29): under these defaults (minContentWidth
70
+ // 480 against jsdom's 1024px viewport) the effective maximum is 544, not
71
+ // maxWidth (640) — see clampWidth's viewport term.
72
+ it("narrows on ArrowRight and End jumps to the effective maximum (544, not maxWidth)", () => {
73
+ render(
74
+ <SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
75
+ Body
76
+ </SideDock>,
77
+ );
78
+ const handle = screen.getByRole("separator");
79
+ handle.focus();
80
+ fireEvent.keyDown(handle, { key: "ArrowRight" });
81
+ fireEvent.keyUp(handle, { key: "ArrowRight" });
82
+ expect(handle).toHaveAttribute("aria-valuenow", "384");
83
+ fireEvent.keyDown(handle, { key: "End" });
84
+ fireEvent.keyUp(handle, { key: "End" });
85
+ expect(handle).toHaveAttribute("aria-valuenow", "544");
86
+ });
87
+
88
+ // 5. A no-op is silent.
89
+ it("fires no callback and leaves the value unchanged when the narrowing key is a no-op at minWidth", () => {
90
+ const onWidthChange = vi.fn();
91
+ const onWidthCommit = vi.fn();
92
+ render(
93
+ <SideDock
94
+ title="Assistant"
95
+ open
96
+ overlayBreakpoint={COLUMN_BREAKPOINT}
97
+ width={320}
98
+ minWidth={320}
99
+ onWidthChange={onWidthChange}
100
+ onWidthCommit={onWidthCommit}
101
+ >
102
+ Body
103
+ </SideDock>,
104
+ );
105
+ const handle = screen.getByRole("separator");
106
+ handle.focus();
107
+ fireEvent.keyDown(handle, { key: "ArrowRight" });
108
+ fireEvent.keyUp(handle, { key: "ArrowRight" });
109
+ expect(onWidthChange).not.toHaveBeenCalled();
110
+ expect(onWidthCommit).not.toHaveBeenCalled();
111
+ expect(handle).toHaveAttribute("aria-valuenow", "320");
112
+ });
113
+
114
+ // 6. A controlled out-of-range width converges (maxWidth as the sole
115
+ // upper bound — minContentWidth={0} zeroes the viewport bound, per
116
+ // Ruling 29; assertion 7 below is what covers the viewport bound).
117
+ it("converges a controlled width above maxWidth, emitting each callback once", () => {
118
+ const onWidthChange = vi.fn();
119
+ const onWidthCommit = vi.fn();
120
+ render(
121
+ <SideDock
122
+ title="Assistant"
123
+ open
124
+ overlayBreakpoint={COLUMN_BREAKPOINT}
125
+ width={9999}
126
+ maxWidth={640}
127
+ minContentWidth={0}
128
+ onWidthChange={onWidthChange}
129
+ onWidthCommit={onWidthCommit}
130
+ >
131
+ Body
132
+ </SideDock>,
133
+ );
134
+ expect(screen.getByRole("separator")).toHaveAttribute("aria-valuenow", "640");
135
+ expect(onWidthChange).toHaveBeenCalledTimes(1);
136
+ expect(onWidthChange).toHaveBeenCalledWith(640);
137
+ expect(onWidthCommit).toHaveBeenCalledTimes(1);
138
+ expect(onWidthCommit).toHaveBeenCalledWith(640);
139
+ });
140
+
141
+ // 7. minContentWidth binds against the live viewport.
142
+ it("binds minContentWidth against jsdom's 1024px viewport", () => {
143
+ render(
144
+ <SideDock
145
+ title="Assistant"
146
+ open
147
+ overlayBreakpoint={COLUMN_BREAKPOINT}
148
+ width={640}
149
+ minContentWidth={480}
150
+ >
151
+ Body
152
+ </SideDock>,
153
+ );
154
+ expect(screen.getByRole("separator")).toHaveAttribute("aria-valuenow", "544");
155
+ });
156
+
157
+ // 8. Uncontrolled default is closed.
158
+ // `data-state` lives on the GROUP element per task-10-brief.md Mechanism §1
159
+ // ("attrs ... goes on the group element that wraps both fragments" —
160
+ // mirrors Sidebar, which puts `data-state` on `data-slot="sidebar"`, never
161
+ // on `sidebar-container`); `inert` is the CONTAINER's own attribute (the
162
+ // fixed contract's "the collapsed container is inert").
163
+ it("is closed by default, inert, with its body content still mounted", () => {
164
+ render(
165
+ <SideDock title="Assistant" overlayBreakpoint={COLUMN_BREAKPOINT}>
166
+ Body content
167
+ </SideDock>,
168
+ );
169
+ const root = document.querySelector('[data-slot="side-dock"]');
170
+ const container = document.querySelector('[data-slot="side-dock-container"]');
171
+ expect(root).toHaveAttribute("data-state", "collapsed");
172
+ // jsdom does not implement the `.inert` IDL property (it reads back
173
+ // `undefined` even when the attribute is present) — assert the
174
+ // ATTRIBUTE, which is what React 19 actually renders for `inert={true}`.
175
+ expect(container).toHaveAttribute("inert");
176
+ expect(screen.getByText("Body content")).toBeInTheDocument();
177
+ });
178
+
179
+ // 9. The close control closes it.
180
+ it("closes on clicking the close button", () => {
181
+ const onOpenChange = vi.fn();
182
+ render(
183
+ <SideDock
184
+ title="Assistant"
185
+ defaultOpen
186
+ overlayBreakpoint={COLUMN_BREAKPOINT}
187
+ onOpenChange={onOpenChange}
188
+ >
189
+ Body
190
+ </SideDock>,
191
+ );
192
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
193
+ expect(onOpenChange).toHaveBeenCalledWith(false);
194
+ const root = document.querySelector('[data-slot="side-dock"]');
195
+ expect(root).toHaveAttribute("data-state", "collapsed");
196
+ });
197
+
198
+ // 10. The overlay branch.
199
+ it("renders as an overlay Sheet below overlayBreakpoint, with no resize handle", () => {
200
+ render(
201
+ <SideDock title="Assistant" open overlayBreakpoint={2000} description="A helper panel.">
202
+ Body
203
+ </SideDock>,
204
+ );
205
+ const dialog = screen.getByRole("dialog");
206
+ expect(dialog).toHaveAccessibleName("Assistant");
207
+ expect(screen.queryByRole("separator")).not.toBeInTheDocument();
208
+ expect(screen.getByText("A helper panel.")).toBeInTheDocument();
209
+ });
210
+
211
+ // 11. side="left" inverts the keyboard direction.
212
+ it("inverts ArrowRight to widen on a left-hand dock", () => {
213
+ const onWidthChange = vi.fn();
214
+ render(
215
+ <SideDock
216
+ title="Assistant"
217
+ open
218
+ side="left"
219
+ overlayBreakpoint={COLUMN_BREAKPOINT}
220
+ onWidthChange={onWidthChange}
221
+ >
222
+ Body
223
+ </SideDock>,
224
+ );
225
+ const handle = screen.getByRole("separator");
226
+ handle.focus();
227
+ fireEvent.keyDown(handle, { key: "ArrowRight" });
228
+ expect(onWidthChange).toHaveBeenCalledTimes(1);
229
+ expect(onWidthChange.mock.calls[0]![0] as number).toBeGreaterThan(400);
230
+ });
231
+
232
+ // 12. Finding 2 (task-11f-brief.md): `inert={!panel.open}` on the column
233
+ // `<aside>` means, per the HTML focus-fixup rule, that a focused
234
+ // descendant (the close button) loses focus the instant the close click
235
+ // makes the container inert — the browser's default is to drop focus to
236
+ // `<body>`. This asserts the repair: closing via keyboard/click restores
237
+ // focus to whatever opened the dock, and explicitly never to `<body>`.
238
+ it("restores focus to the opener when the close button closes the dock", () => {
239
+ function Harness() {
240
+ const [open, setOpen] = useState(false);
241
+ return (
242
+ <>
243
+ <button type="button" onClick={() => setOpen(true)}>
244
+ Open dock
245
+ </button>
246
+ <SideDock
247
+ title="Assistant"
248
+ open={open}
249
+ onOpenChange={setOpen}
250
+ overlayBreakpoint={COLUMN_BREAKPOINT}
251
+ >
252
+ Body
253
+ </SideDock>
254
+ </>
255
+ );
256
+ }
257
+ render(<Harness />);
258
+
259
+ const trigger = screen.getByRole("button", { name: "Open dock" });
260
+ trigger.focus();
261
+ expect(document.activeElement).toBe(trigger);
262
+
263
+ fireEvent.click(trigger);
264
+ const closeButton = screen.getByRole("button", { name: "Close" });
265
+ closeButton.focus();
266
+ expect(document.activeElement).toBe(closeButton);
267
+
268
+ fireEvent.click(closeButton);
269
+
270
+ expect(document.activeElement).not.toBe(document.body);
271
+ expect(document.activeElement).toBe(trigger);
272
+ });
273
+
274
+ it("coalesces a burst of resize events into one rAF-scheduled update", () => {
275
+ const rafCallbacks: FrameRequestCallback[] = [];
276
+ const rafSpy = vi
277
+ .spyOn(window, "requestAnimationFrame")
278
+ .mockImplementation((cb: FrameRequestCallback) => {
279
+ rafCallbacks.push(cb);
280
+ return rafCallbacks.length;
281
+ });
282
+
283
+ render(
284
+ <SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
285
+ Body
286
+ </SideDock>,
287
+ );
288
+ rafCallbacks.length = 0; // clear anything scheduled at mount
289
+
290
+ // A window drag fires `resize` many times per frame — every one of these
291
+ // must coalesce into a SINGLE scheduled frame, not one per event.
292
+ fireEvent(window, new Event("resize"));
293
+ fireEvent(window, new Event("resize"));
294
+ fireEvent(window, new Event("resize"));
295
+ expect(rafCallbacks).toHaveLength(1);
296
+
297
+ rafSpy.mockRestore();
298
+ });
299
+ });