@elabs-ai/components-ui 4.0.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 (449) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/index.d.ts +4497 -0
  4. package/dist/index.js +15192 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/cn.d.ts +10 -0
  7. package/dist/lib/cn.js +20 -0
  8. package/dist/lib/cn.js.map +1 -0
  9. package/package.json +106 -0
  10. package/src/blocks/sidebar-02/app-sidebar.tsx +160 -0
  11. package/src/blocks/sidebar-02/logo.tsx +18 -0
  12. package/src/blocks/sidebar-02/nav-main.tsx +13 -0
  13. package/src/blocks/sidebar-02/nav-notifications.tsx +7 -0
  14. package/src/blocks/sidebar-02/sidebar-02.stories.tsx +23 -0
  15. package/src/blocks/sidebar-02/team-switcher.tsx +7 -0
  16. package/src/blocks/sidebar-04/app-sidebar.tsx +236 -0
  17. package/src/blocks/sidebar-04/mail-context.tsx +29 -0
  18. package/src/blocks/sidebar-04/nav-user.tsx +6 -0
  19. package/src/blocks/sidebar-04/sidebar-04.stories.tsx +150 -0
  20. package/src/blocks/sidebar-05/app-sidebar.tsx +375 -0
  21. package/src/blocks/sidebar-05/sidebar-05.stories.tsx +18 -0
  22. package/src/blocks/sidebar-05/team-switcher.tsx +6 -0
  23. package/src/blocks-comparison-table.stories.tsx +165 -0
  24. package/src/components/accordion/accordion.stories.tsx +71 -0
  25. package/src/components/accordion/accordion.test.tsx +18 -0
  26. package/src/components/accordion/accordion.tsx +49 -0
  27. package/src/components/accordion/index.ts +1 -0
  28. package/src/components/advanced-group/advanced-group.stories.tsx +143 -0
  29. package/src/components/advanced-group/advanced-group.test.tsx +81 -0
  30. package/src/components/advanced-group/advanced-group.tsx +111 -0
  31. package/src/components/advanced-group/index.ts +2 -0
  32. package/src/components/alert/alert.stories.tsx +156 -0
  33. package/src/components/alert/alert.test.tsx +57 -0
  34. package/src/components/alert/alert.tsx +91 -0
  35. package/src/components/alert/index.ts +8 -0
  36. package/src/components/alert-dialog/alert-dialog.stories.tsx +73 -0
  37. package/src/components/alert-dialog/alert-dialog.tsx +104 -0
  38. package/src/components/alert-dialog/index.ts +12 -0
  39. package/src/components/app-shell/app-shell.stories.tsx +61 -0
  40. package/src/components/app-shell/app-shell.tsx +31 -0
  41. package/src/components/app-shell/index.ts +1 -0
  42. package/src/components/app-sidebar/app-sidebar.stories.tsx +121 -0
  43. package/src/components/app-sidebar/app-sidebar.test.tsx +55 -0
  44. package/src/components/app-sidebar/app-sidebar.tsx +53 -0
  45. package/src/components/app-sidebar/index.ts +1 -0
  46. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +46 -0
  47. package/src/components/aspect-ratio/aspect-ratio.tsx +3 -0
  48. package/src/components/aspect-ratio/index.ts +1 -0
  49. package/src/components/attribution-panel/attribution-panel.stories.tsx +83 -0
  50. package/src/components/attribution-panel/attribution-panel.test.tsx +123 -0
  51. package/src/components/attribution-panel/attribution-panel.tsx +191 -0
  52. package/src/components/attribution-panel/attribution-types.ts +44 -0
  53. package/src/components/attribution-panel/attributions.generated.ts +1422 -0
  54. package/src/components/attribution-panel/index.ts +3 -0
  55. package/src/components/avatar/avatar.stories.tsx +45 -0
  56. package/src/components/avatar/avatar.test.tsx +13 -0
  57. package/src/components/avatar/avatar.tsx +46 -0
  58. package/src/components/avatar/index.ts +1 -0
  59. package/src/components/badge/badge.stories.tsx +44 -0
  60. package/src/components/badge/badge.test.tsx +10 -0
  61. package/src/components/badge/badge.tsx +28 -0
  62. package/src/components/badge/index.ts +1 -0
  63. package/src/components/bento-grid/bento-grid.stories.tsx +165 -0
  64. package/src/components/bento-grid/bento-grid.test.tsx +119 -0
  65. package/src/components/bento-grid/bento-grid.tsx +339 -0
  66. package/src/components/bento-grid/index.ts +9 -0
  67. package/src/components/bounded-number/bounded-number.stories.tsx +118 -0
  68. package/src/components/bounded-number/bounded-number.test.tsx +73 -0
  69. package/src/components/bounded-number/bounded-number.tsx +110 -0
  70. package/src/components/bounded-number/index.ts +1 -0
  71. package/src/components/breadcrumb/breadcrumb.stories.tsx +47 -0
  72. package/src/components/breadcrumb/breadcrumb.test.tsx +17 -0
  73. package/src/components/breadcrumb/breadcrumb.tsx +86 -0
  74. package/src/components/breadcrumb/index.ts +9 -0
  75. package/src/components/button/button.stories.tsx +70 -0
  76. package/src/components/button/button.test.tsx +50 -0
  77. package/src/components/button/button.tsx +63 -0
  78. package/src/components/button/index.ts +1 -0
  79. package/src/components/button-group/button-group.stories.tsx +45 -0
  80. package/src/components/button-group/button-group.tsx +70 -0
  81. package/src/components/button-group/index.ts +6 -0
  82. package/src/components/calendar/calendar.stories.tsx +95 -0
  83. package/src/components/calendar/calendar.test.tsx +140 -0
  84. package/src/components/calendar/calendar.tsx +70 -0
  85. package/src/components/calendar/index.ts +1 -0
  86. package/src/components/card/card.stories.tsx +438 -0
  87. package/src/components/card/card.test.tsx +153 -0
  88. package/src/components/card/card.tsx +332 -0
  89. package/src/components/card/index.ts +13 -0
  90. package/src/components/carousel/carousel.stories.tsx +102 -0
  91. package/src/components/carousel/carousel.tsx +225 -0
  92. package/src/components/carousel/index.ts +8 -0
  93. package/src/components/change-review/change-review.stories.tsx +288 -0
  94. package/src/components/change-review/change-review.test.tsx +261 -0
  95. package/src/components/change-review/change-review.tsx +637 -0
  96. package/src/components/change-review/index.ts +23 -0
  97. package/src/components/checkbox/checkbox.stories.tsx +119 -0
  98. package/src/components/checkbox/checkbox.test.tsx +40 -0
  99. package/src/components/checkbox/checkbox.tsx +34 -0
  100. package/src/components/checkbox/index.ts +1 -0
  101. package/src/components/collapsible/collapsible.stories.tsx +62 -0
  102. package/src/components/collapsible/collapsible.tsx +22 -0
  103. package/src/components/collapsible/index.ts +1 -0
  104. package/src/components/collapsible-panel/index.ts +8 -0
  105. package/src/components/collapsible-panel/use-collapsible-panel.test.tsx +93 -0
  106. package/src/components/collapsible-panel/use-collapsible-panel.ts +159 -0
  107. package/src/components/color-picker/color-picker.stories.tsx +93 -0
  108. package/src/components/color-picker/color-picker.test.tsx +79 -0
  109. package/src/components/color-picker/color-picker.tsx +341 -0
  110. package/src/components/color-picker/index.ts +1 -0
  111. package/src/components/combobox/combobox.stories.tsx +154 -0
  112. package/src/components/combobox/combobox.test.tsx +246 -0
  113. package/src/components/combobox/combobox.tsx +210 -0
  114. package/src/components/combobox/index.ts +1 -0
  115. package/src/components/command/command.stories.tsx +371 -0
  116. package/src/components/command/command.test.tsx +280 -0
  117. package/src/components/command/command.tsx +301 -0
  118. package/src/components/command/index.ts +13 -0
  119. package/src/components/confirm-dialog/confirm-dialog.stories.tsx +183 -0
  120. package/src/components/confirm-dialog/confirm-dialog.test.tsx +147 -0
  121. package/src/components/confirm-dialog/confirm-dialog.tsx +146 -0
  122. package/src/components/confirm-dialog/index.ts +2 -0
  123. package/src/components/context-menu/context-menu.stories.tsx +70 -0
  124. package/src/components/context-menu/context-menu.tsx +172 -0
  125. package/src/components/context-menu/index.ts +16 -0
  126. package/src/components/copyable-value/copyable-value.stories.tsx +80 -0
  127. package/src/components/copyable-value/copyable-value.test.tsx +128 -0
  128. package/src/components/copyable-value/copyable-value.tsx +101 -0
  129. package/src/components/copyable-value/index.ts +1 -0
  130. package/src/components/date-picker/date-picker.stories.tsx +42 -0
  131. package/src/components/date-picker/date-picker.tsx +49 -0
  132. package/src/components/date-picker/index.ts +1 -0
  133. package/src/components/date-range-picker/date-range-picker.stories.tsx +151 -0
  134. package/src/components/date-range-picker/date-range-picker.test.tsx +62 -0
  135. package/src/components/date-range-picker/date-range-picker.tsx +126 -0
  136. package/src/components/date-range-picker/index.ts +6 -0
  137. package/src/components/descriptions/descriptions.stories.tsx +43 -0
  138. package/src/components/descriptions/descriptions.test.tsx +41 -0
  139. package/src/components/descriptions/descriptions.tsx +95 -0
  140. package/src/components/descriptions/index.ts +6 -0
  141. package/src/components/dialog/dialog.stories.tsx +728 -0
  142. package/src/components/dialog/dialog.test.tsx +285 -0
  143. package/src/components/dialog/dialog.tsx +340 -0
  144. package/src/components/dialog/index.ts +18 -0
  145. package/src/components/dialog/use-dialog-dismiss-guard.test.tsx +80 -0
  146. package/src/components/dialog/use-dialog-dismiss-guard.ts +109 -0
  147. package/src/components/drawer/drawer.stories.tsx +76 -0
  148. package/src/components/drawer/drawer.tsx +85 -0
  149. package/src/components/drawer/index.ts +11 -0
  150. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +176 -0
  151. package/src/components/dropdown-menu/dropdown-menu.test.tsx +109 -0
  152. package/src/components/dropdown-menu/dropdown-menu.tsx +179 -0
  153. package/src/components/dropdown-menu/index.ts +17 -0
  154. package/src/components/empty-state/empty-state.stories.tsx +54 -0
  155. package/src/components/empty-state/empty-state.test.tsx +11 -0
  156. package/src/components/empty-state/empty-state.tsx +30 -0
  157. package/src/components/empty-state/index.ts +1 -0
  158. package/src/components/error-state/error-state.stories.tsx +51 -0
  159. package/src/components/error-state/error-state.tsx +27 -0
  160. package/src/components/error-state/index.ts +1 -0
  161. package/src/components/expand-dialog/expand-dialog.stories.tsx +246 -0
  162. package/src/components/expand-dialog/expand-dialog.test.tsx +185 -0
  163. package/src/components/expand-dialog/expand-dialog.tsx +387 -0
  164. package/src/components/expand-dialog/index.ts +11 -0
  165. package/src/components/field-row/field-row.stories.tsx +114 -0
  166. package/src/components/field-row/field-row.test.tsx +188 -0
  167. package/src/components/field-row/field-row.tsx +115 -0
  168. package/src/components/field-row/index.ts +1 -0
  169. package/src/components/file-upload/file-upload.stories.tsx +183 -0
  170. package/src/components/file-upload/file-upload.test.tsx +169 -0
  171. package/src/components/file-upload/file-upload.tsx +459 -0
  172. package/src/components/file-upload/index.ts +13 -0
  173. package/src/components/form/form.stories.tsx +71 -0
  174. package/src/components/form/form.tsx +141 -0
  175. package/src/components/form/index.ts +10 -0
  176. package/src/components/hover-card/hover-card.stories.tsx +57 -0
  177. package/src/components/hover-card/hover-card.tsx +25 -0
  178. package/src/components/hover-card/index.ts +1 -0
  179. package/src/components/icon-button/icon-button.stories.tsx +108 -0
  180. package/src/components/icon-button/icon-button.test.tsx +93 -0
  181. package/src/components/icon-button/icon-button.tsx +139 -0
  182. package/src/components/icon-button/index.ts +1 -0
  183. package/src/components/input/index.ts +1 -0
  184. package/src/components/input/input.stories.tsx +44 -0
  185. package/src/components/input/input.test.tsx +19 -0
  186. package/src/components/input/input.tsx +26 -0
  187. package/src/components/input-group/index.ts +11 -0
  188. package/src/components/input-group/input-group.stories.tsx +87 -0
  189. package/src/components/input-group/input-group.test.tsx +48 -0
  190. package/src/components/input-group/input-group.tsx +170 -0
  191. package/src/components/input-otp/index.ts +1 -0
  192. package/src/components/input-otp/input-otp.stories.tsx +66 -0
  193. package/src/components/input-otp/input-otp.tsx +68 -0
  194. package/src/components/kbd/index.ts +1 -0
  195. package/src/components/kbd/kbd.stories.tsx +45 -0
  196. package/src/components/kbd/kbd.tsx +20 -0
  197. package/src/components/key-value-editor/index.ts +1 -0
  198. package/src/components/key-value-editor/key-value-editor.stories.tsx +130 -0
  199. package/src/components/key-value-editor/key-value-editor.test.tsx +231 -0
  200. package/src/components/key-value-editor/key-value-editor.tsx +263 -0
  201. package/src/components/label/index.ts +1 -0
  202. package/src/components/label/label.stories.tsx +35 -0
  203. package/src/components/label/label.tsx +19 -0
  204. package/src/components/link-preview/index.ts +2 -0
  205. package/src/components/link-preview/link-preview.stories.tsx +178 -0
  206. package/src/components/link-preview/link-preview.test.tsx +123 -0
  207. package/src/components/link-preview/link-preview.tsx +194 -0
  208. package/src/components/list-editor/index.ts +1 -0
  209. package/src/components/list-editor/list-editor.stories.tsx +119 -0
  210. package/src/components/list-editor/list-editor.test.tsx +96 -0
  211. package/src/components/list-editor/list-editor.tsx +192 -0
  212. package/src/components/loading-state/index.ts +1 -0
  213. package/src/components/loading-state/loading-state.stories.tsx +38 -0
  214. package/src/components/loading-state/loading-state.tsx +26 -0
  215. package/src/components/locale-provider/index.ts +8 -0
  216. package/src/components/locale-provider/locale-provider.stories.tsx +83 -0
  217. package/src/components/locale-provider/locale-provider.test.tsx +238 -0
  218. package/src/components/locale-provider/locale-provider.tsx +187 -0
  219. package/src/components/locale-provider/messages.ts +300 -0
  220. package/src/components/match-highlight/index.ts +7 -0
  221. package/src/components/match-highlight/match-highlight.stories.tsx +146 -0
  222. package/src/components/match-highlight/match-highlight.test.tsx +202 -0
  223. package/src/components/match-highlight/match-highlight.tsx +145 -0
  224. package/src/components/mention-input/index.ts +28 -0
  225. package/src/components/mention-input/mention-input-mirror.stories.tsx +171 -0
  226. package/src/components/mention-input/mention-input.stories.tsx +463 -0
  227. package/src/components/mention-input/mention-input.test.tsx +617 -0
  228. package/src/components/mention-input/mention-input.tsx +1102 -0
  229. package/src/components/mention-input/mention-value.test.ts +253 -0
  230. package/src/components/mention-input/mention-value.ts +193 -0
  231. package/src/components/menubar/index.ts +17 -0
  232. package/src/components/menubar/menubar.stories.tsx +67 -0
  233. package/src/components/menubar/menubar.tsx +198 -0
  234. package/src/components/metric-card/index.ts +1 -0
  235. package/src/components/metric-card/metric-card.stories.tsx +224 -0
  236. package/src/components/metric-card/metric-card.test.tsx +198 -0
  237. package/src/components/metric-card/metric-card.tsx +229 -0
  238. package/src/components/model-picker/index.ts +11 -0
  239. package/src/components/model-picker/model-picker-state.ts +85 -0
  240. package/src/components/model-picker/model-picker.stories.tsx +128 -0
  241. package/src/components/model-picker/model-picker.test.tsx +241 -0
  242. package/src/components/model-picker/model-picker.tsx +270 -0
  243. package/src/components/nav-main/index.ts +1 -0
  244. package/src/components/nav-main/nav-main.tsx +147 -0
  245. package/src/components/nav-notifications/index.ts +5 -0
  246. package/src/components/nav-notifications/nav-notifications.test.tsx +15 -0
  247. package/src/components/nav-notifications/nav-notifications.tsx +70 -0
  248. package/src/components/nav-user/index.ts +1 -0
  249. package/src/components/nav-user/nav-user.test.tsx +29 -0
  250. package/src/components/nav-user/nav-user.tsx +114 -0
  251. package/src/components/navigation-menu/index.ts +10 -0
  252. package/src/components/navigation-menu/navigation-menu.stories.tsx +92 -0
  253. package/src/components/navigation-menu/navigation-menu.tsx +99 -0
  254. package/src/components/number-input/index.ts +1 -0
  255. package/src/components/number-input/number-input.stories.tsx +120 -0
  256. package/src/components/number-input/number-input.test.tsx +96 -0
  257. package/src/components/number-input/number-input.tsx +219 -0
  258. package/src/components/page-shell/index.ts +1 -0
  259. package/src/components/page-shell/page-shell.stories.tsx +105 -0
  260. package/src/components/page-shell/page-shell.test.tsx +57 -0
  261. package/src/components/page-shell/page-shell.tsx +62 -0
  262. package/src/components/pagination/index.ts +9 -0
  263. package/src/components/pagination/pagination.stories.tsx +57 -0
  264. package/src/components/pagination/pagination.tsx +77 -0
  265. package/src/components/popover/index.ts +1 -0
  266. package/src/components/popover/popover.stories.tsx +28 -0
  267. package/src/components/popover/popover.test.tsx +16 -0
  268. package/src/components/popover/popover.tsx +29 -0
  269. package/src/components/progress/index.ts +1 -0
  270. package/src/components/progress/progress.stories.tsx +80 -0
  271. package/src/components/progress/progress.test.tsx +35 -0
  272. package/src/components/progress/progress.tsx +50 -0
  273. package/src/components/radio-group/index.ts +1 -0
  274. package/src/components/radio-group/radio-group.stories.tsx +75 -0
  275. package/src/components/radio-group/radio-group.test.tsx +16 -0
  276. package/src/components/radio-group/radio-group.tsx +33 -0
  277. package/src/components/rating/index.ts +1 -0
  278. package/src/components/rating/rating.stories.tsx +111 -0
  279. package/src/components/rating/rating.test.tsx +41 -0
  280. package/src/components/rating/rating.tsx +226 -0
  281. package/src/components/resizable/index.ts +1 -0
  282. package/src/components/resizable/resizable.stories.tsx +25 -0
  283. package/src/components/resizable/resizable.tsx +40 -0
  284. package/src/components/reveal/index.ts +7 -0
  285. package/src/components/reveal/reveal.stories.tsx +90 -0
  286. package/src/components/reveal/reveal.test.tsx +43 -0
  287. package/src/components/reveal/reveal.tsx +95 -0
  288. package/src/components/revision-timeline/index.ts +10 -0
  289. package/src/components/revision-timeline/revision-timeline.stories.tsx +785 -0
  290. package/src/components/revision-timeline/revision-timeline.test.tsx +509 -0
  291. package/src/components/revision-timeline/revision-timeline.tsx +1107 -0
  292. package/src/components/scroll-area/index.ts +1 -0
  293. package/src/components/scroll-area/scroll-area.stories.tsx +45 -0
  294. package/src/components/scroll-area/scroll-area.tsx +43 -0
  295. package/src/components/section-header/index.ts +1 -0
  296. package/src/components/section-header/section-header.stories.tsx +20 -0
  297. package/src/components/section-header/section-header.tsx +36 -0
  298. package/src/components/segmented-field/index.ts +5 -0
  299. package/src/components/segmented-field/segmented-field.stories.tsx +121 -0
  300. package/src/components/segmented-field/segmented-field.test.tsx +163 -0
  301. package/src/components/segmented-field/segmented-field.tsx +235 -0
  302. package/src/components/select/index.ts +10 -0
  303. package/src/components/select/select.stories.tsx +106 -0
  304. package/src/components/select/select.test.tsx +98 -0
  305. package/src/components/select/select.tsx +230 -0
  306. package/src/components/separator/index.ts +2 -0
  307. package/src/components/separator/separator.stories.tsx +87 -0
  308. package/src/components/separator/separator.tsx +51 -0
  309. package/src/components/sheet/index.ts +12 -0
  310. package/src/components/sheet/sheet.stories.tsx +65 -0
  311. package/src/components/sheet/sheet.tsx +109 -0
  312. package/src/components/sidebar/index.ts +27 -0
  313. package/src/components/sidebar/sidebar.stories.tsx +69 -0
  314. package/src/components/sidebar/sidebar.test.tsx +43 -0
  315. package/src/components/sidebar/sidebar.tsx +688 -0
  316. package/src/components/skeleton/index.ts +1 -0
  317. package/src/components/skeleton/skeleton.stories.tsx +28 -0
  318. package/src/components/skeleton/skeleton.tsx +15 -0
  319. package/src/components/slider/index.ts +1 -0
  320. package/src/components/slider/slider.stories.tsx +211 -0
  321. package/src/components/slider/slider.test.tsx +168 -0
  322. package/src/components/slider/slider.tsx +136 -0
  323. package/src/components/slider-number/index.ts +1 -0
  324. package/src/components/slider-number/slider-number.stories.tsx +123 -0
  325. package/src/components/slider-number/slider-number.test.tsx +165 -0
  326. package/src/components/slider-number/slider-number.tsx +191 -0
  327. package/src/components/sonner/index.ts +1 -0
  328. package/src/components/sonner/sonner.stories.tsx +124 -0
  329. package/src/components/sonner/sonner.test.tsx +76 -0
  330. package/src/components/sonner/sonner.tsx +101 -0
  331. package/src/components/spinner/index.ts +1 -0
  332. package/src/components/spinner/spinner.stories.tsx +27 -0
  333. package/src/components/spinner/spinner.tsx +19 -0
  334. package/src/components/split-panel/index.ts +1 -0
  335. package/src/components/split-panel/split-panel.stories.tsx +60 -0
  336. package/src/components/split-panel/split-panel.test.tsx +38 -0
  337. package/src/components/split-panel/split-panel.tsx +95 -0
  338. package/src/components/state-panel/index.ts +1 -0
  339. package/src/components/state-panel/state-panel.stories.tsx +80 -0
  340. package/src/components/state-panel/state-panel.test.tsx +64 -0
  341. package/src/components/state-panel/state-panel.tsx +167 -0
  342. package/src/components/status-badge/index.ts +18 -0
  343. package/src/components/status-badge/status-badge.stories.tsx +132 -0
  344. package/src/components/status-badge/status-badge.test.tsx +189 -0
  345. package/src/components/status-badge/status-badge.tsx +330 -0
  346. package/src/components/switch/index.ts +1 -0
  347. package/src/components/switch/switch.stories.tsx +58 -0
  348. package/src/components/switch/switch.test.tsx +12 -0
  349. package/src/components/switch/switch.tsx +29 -0
  350. package/src/components/table/index.ts +10 -0
  351. package/src/components/table/table.stories.tsx +53 -0
  352. package/src/components/table/table.test.tsx +17 -0
  353. package/src/components/table/table.tsx +106 -0
  354. package/src/components/tabs/index.ts +1 -0
  355. package/src/components/tabs/tabs.stories.tsx +267 -0
  356. package/src/components/tabs/tabs.test.tsx +94 -0
  357. package/src/components/tabs/tabs.tsx +170 -0
  358. package/src/components/tag-input/index.ts +1 -0
  359. package/src/components/tag-input/tag-input.stories.tsx +120 -0
  360. package/src/components/tag-input/tag-input.test.tsx +97 -0
  361. package/src/components/tag-input/tag-input.tsx +253 -0
  362. package/src/components/team-switcher/index.ts +1 -0
  363. package/src/components/team-switcher/team-switcher.test.tsx +39 -0
  364. package/src/components/team-switcher/team-switcher.tsx +100 -0
  365. package/src/components/textarea/index.ts +1 -0
  366. package/src/components/textarea/textarea.stories.tsx +42 -0
  367. package/src/components/textarea/textarea.tsx +24 -0
  368. package/src/components/theme-switcher/index.ts +2 -0
  369. package/src/components/theme-switcher/theme-switcher.stories.tsx +146 -0
  370. package/src/components/theme-switcher/theme-switcher.test.tsx +253 -0
  371. package/src/components/theme-switcher/theme-switcher.tsx +280 -0
  372. package/src/components/theme-switcher/use-theme-transition.ts +62 -0
  373. package/src/components/timeline/index.ts +13 -0
  374. package/src/components/timeline/timeline.stories.tsx +99 -0
  375. package/src/components/timeline/timeline.test.tsx +196 -0
  376. package/src/components/timeline/timeline.tsx +188 -0
  377. package/src/components/toggle/index.ts +1 -0
  378. package/src/components/toggle/toggle.stories.tsx +116 -0
  379. package/src/components/toggle/toggle.test.tsx +119 -0
  380. package/src/components/toggle/toggle.tsx +64 -0
  381. package/src/components/toggle-group/index.ts +1 -0
  382. package/src/components/toggle-group/toggle-group.stories.tsx +110 -0
  383. package/src/components/toggle-group/toggle-group.tsx +59 -0
  384. package/src/components/toolbar/index.ts +14 -0
  385. package/src/components/toolbar/toolbar.stories.tsx +126 -0
  386. package/src/components/toolbar/toolbar.test.tsx +73 -0
  387. package/src/components/toolbar/toolbar.tsx +177 -0
  388. package/src/components/tooltip/index.ts +1 -0
  389. package/src/components/tooltip/tooltip.stories.tsx +67 -0
  390. package/src/components/tooltip/tooltip.tsx +27 -0
  391. package/src/components/top-nav/index.ts +1 -0
  392. package/src/components/top-nav/top-nav.stories.tsx +18 -0
  393. package/src/components/top-nav/top-nav.tsx +34 -0
  394. package/src/components/transfer/index.ts +2 -0
  395. package/src/components/transfer/transfer.stories.tsx +162 -0
  396. package/src/components/transfer/transfer.test.tsx +177 -0
  397. package/src/components/transfer/transfer.tsx +481 -0
  398. package/src/components/tree/index.ts +6 -0
  399. package/src/components/tree/tree.stories.tsx +618 -0
  400. package/src/components/tree/tree.test.tsx +699 -0
  401. package/src/components/tree/tree.tsx +1149 -0
  402. package/src/components/tree/use-tree-keyboard.ts +314 -0
  403. package/src/components/tree-select/index.ts +1 -0
  404. package/src/components/tree-select/tree-select.stories.tsx +127 -0
  405. package/src/components/tree-select/tree-select.test.tsx +40 -0
  406. package/src/components/tree-select/tree-select.tsx +137 -0
  407. package/src/components/typography/index.ts +31 -0
  408. package/src/components/typography/prose.test.tsx +48 -0
  409. package/src/components/typography/prose.tsx +194 -0
  410. package/src/components/typography/typography.stories.tsx +244 -0
  411. package/src/components/typography/typography.test.tsx +140 -0
  412. package/src/components/typography/typography.tsx +126 -0
  413. package/src/components/view-toolbar/index.ts +10 -0
  414. package/src/components/view-toolbar/view-toolbar.stories.tsx +342 -0
  415. package/src/components/view-toolbar/view-toolbar.test.tsx +262 -0
  416. package/src/components/view-toolbar/view-toolbar.tsx +320 -0
  417. package/src/components/virtual-select/index.ts +2 -0
  418. package/src/components/virtual-select/use-virtual-listbox.ts +132 -0
  419. package/src/components/virtual-select/virtual-select.stories.tsx +203 -0
  420. package/src/components/virtual-select/virtual-select.test.tsx +106 -0
  421. package/src/components/virtual-select/virtual-select.tsx +361 -0
  422. package/src/components/wizard/index.ts +12 -0
  423. package/src/components/wizard/wizard.stories.tsx +148 -0
  424. package/src/components/wizard/wizard.test.tsx +67 -0
  425. package/src/components/wizard/wizard.tsx +354 -0
  426. package/src/decoration.stories.tsx +236 -0
  427. package/src/index.ts +174 -0
  428. package/src/lib/cn.test.ts +39 -0
  429. package/src/lib/cn.ts +31 -0
  430. package/src/lib/compact-number.ts +61 -0
  431. package/src/lib/document-address.test.ts +75 -0
  432. package/src/lib/document-address.ts +265 -0
  433. package/src/lib/download.ts +93 -0
  434. package/src/lib/file-icon.test.ts +45 -0
  435. package/src/lib/file-icon.ts +58 -0
  436. package/src/lib/file-kind.test.ts +98 -0
  437. package/src/lib/file-kind.ts +303 -0
  438. package/src/lib/file-source.test.ts +217 -0
  439. package/src/lib/file-source.ts +269 -0
  440. package/src/lib/merge-refs.test.ts +37 -0
  441. package/src/lib/merge-refs.ts +17 -0
  442. package/src/lib/streamdown-translations.ts +99 -0
  443. package/src/lib/use-copy-to-clipboard.ts +75 -0
  444. package/src/lib/use-mobile.ts +16 -0
  445. package/src/motion.stories.tsx +266 -0
  446. package/src/taste-profile.test.tsx +252 -0
  447. package/src/templates-object-detail-hub.stories.tsx +466 -0
  448. package/src/templates-screen-states.stories.tsx +306 -0
  449. package/src/templates-settings.stories.tsx +245 -0
@@ -0,0 +1,785 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, fn, userEvent, waitFor, within } from "storybook/test";
3
+ import { useState } from "react";
4
+ import {
5
+ RevisionTimeline,
6
+ type Revision,
7
+ type RevisionBranch,
8
+ type RevisionEdge,
9
+ } from "./revision-timeline";
10
+
11
+ const meta = {
12
+ title: "Data/RevisionTimeline",
13
+ component: RevisionTimeline,
14
+ tags: ["autodocs"],
15
+ parameters: {
16
+ layout: "padded",
17
+ docs: {
18
+ description: {
19
+ component:
20
+ "A presentational git-history timeline. Renders revisions (newest-first) with " +
21
+ "day grouping, an SVG branch-graph lane gutter, churn indicators (+/−), " +
22
+ "ref chips, and an accessible selection rail. The app computes lane indices " +
23
+ "and edges; the component only renders what it receives.",
24
+ },
25
+ },
26
+ },
27
+ argTypes: {
28
+ revisions: {
29
+ description: "Ordered revision entries (newest → oldest). The app assigns lane indices.",
30
+ control: false,
31
+ table: { category: "Data" },
32
+ },
33
+ edges: {
34
+ description: "Parent-link edges for drawing the branch-graph gutter (cross-lane beziers).",
35
+ control: false,
36
+ table: { category: "Data" },
37
+ },
38
+ density: {
39
+ description: "Row height — comfortable (48 px) or compact (36 px).",
40
+ control: { type: "select" },
41
+ options: ["comfortable", "compact"],
42
+ table: { category: "Appearance" },
43
+ },
44
+ groupBy: {
45
+ description:
46
+ "Insert day-group headers between revisions from different days, or render flat.",
47
+ control: { type: "select" },
48
+ options: ["day", "none"],
49
+ table: { category: "Behaviour" },
50
+ },
51
+ formatDay: {
52
+ description: "Custom day-group header formatter. Receives a Date, returns a string.",
53
+ control: false,
54
+ table: { category: "Behaviour" },
55
+ },
56
+ selectedId: {
57
+ description: "Controlled selected revision id.",
58
+ control: "text",
59
+ table: { category: "State" },
60
+ },
61
+ defaultSelectedId: {
62
+ description: "Uncontrolled initial selected revision id.",
63
+ control: "text",
64
+ table: { category: "State" },
65
+ },
66
+ onSelect: {
67
+ description: "Callback fired with the revision id when the user selects a row.",
68
+ control: false,
69
+ table: { category: "Events" },
70
+ },
71
+ branches: {
72
+ description:
73
+ "Collapsible branch registry — supplying this opts in to collapse/expand. " +
74
+ "`parentBranchId` nests branches for multi-level collapse.",
75
+ control: false,
76
+ table: { category: "Collapse" },
77
+ },
78
+ collapsedBranchIds: {
79
+ description: "Controlled set of collapsed branch ids.",
80
+ control: false,
81
+ table: { category: "Collapse" },
82
+ },
83
+ defaultCollapsedBranchIds: {
84
+ description: "Uncontrolled initial set of collapsed branch ids.",
85
+ control: false,
86
+ table: { category: "Collapse" },
87
+ },
88
+ onBranchToggle: {
89
+ description: "Callback fired with (branchId, collapsed) when a branch is folded/unfolded.",
90
+ control: false,
91
+ table: { category: "Collapse" },
92
+ },
93
+ formatBranchSummary: {
94
+ description:
95
+ "Trailing summary text on a collapsed branch row. Defaults to '<n> commits'; " +
96
+ "override to re-vocabulary (e.g. '<n> steps' for a process flow).",
97
+ control: false,
98
+ table: { category: "Collapse" },
99
+ },
100
+ className: {
101
+ description: "Additional CSS classes applied to the timeline root.",
102
+ control: "text",
103
+ table: { category: "Styling" },
104
+ },
105
+ },
106
+ } satisfies Meta<typeof RevisionTimeline>;
107
+
108
+ export default meta;
109
+ type Story = StoryObj<typeof meta>;
110
+
111
+ // ─── Shared fixture data ────────────────────────────────────────────────────
112
+
113
+ const DAY1 = new Date("2024-06-14T09:15:00");
114
+ const DAY1_B = new Date("2024-06-14T11:42:00");
115
+ const DAY2 = new Date("2024-06-13T14:30:00");
116
+ const DAY2_B = new Date("2024-06-13T16:05:00");
117
+ const DAY3 = new Date("2024-06-12T10:00:00");
118
+
119
+ const LINEAR_REVISIONS: Revision[] = [
120
+ {
121
+ id: "a1",
122
+ message: "feat: add RevisionTimeline component with SVG lane gutter",
123
+ author: "M. Reimitz",
124
+ shortSha: "a1b2c3d",
125
+ timestamp: DAY1,
126
+ additions: 312,
127
+ deletions: 14,
128
+ lane: 0,
129
+ refs: ["HEAD", "main"],
130
+ },
131
+ {
132
+ id: "a2",
133
+ message: "fix: correct token usage in churn indicators",
134
+ author: "M. Reimitz",
135
+ shortSha: "e4f5a6b",
136
+ timestamp: DAY1_B,
137
+ additions: 8,
138
+ deletions: 3,
139
+ lane: 0,
140
+ },
141
+ {
142
+ id: "a3",
143
+ message: "chore: update text-scale baseline after typography cleanup",
144
+ author: "A. Chen",
145
+ shortSha: "c7d8e9f",
146
+ timestamp: DAY2,
147
+ additions: 2,
148
+ deletions: 0,
149
+ lane: 0,
150
+ },
151
+ {
152
+ id: "a4",
153
+ message: "refactor: extract LaneGutter into its own function",
154
+ author: "A. Chen",
155
+ shortSha: "f1a2b3c",
156
+ timestamp: DAY2_B,
157
+ additions: 45,
158
+ deletions: 38,
159
+ lane: 0,
160
+ },
161
+ {
162
+ id: "a5",
163
+ message: "docs: add ADR-0014 for revision timeline design decisions",
164
+ author: "M. Reimitz",
165
+ shortSha: "d4e5f6a",
166
+ timestamp: DAY3,
167
+ additions: 90,
168
+ deletions: 0,
169
+ lane: 0,
170
+ },
171
+ ];
172
+
173
+ // ─── Stories ────────────────────────────────────────────────────────────────
174
+
175
+ /** Linear history — single lane, grouped by day (the common case). */
176
+ export const LinearHistory: Story = {
177
+ args: {
178
+ revisions: LINEAR_REVISIONS,
179
+ groupBy: "day",
180
+ },
181
+ play: async ({ canvasElement }) => {
182
+ const canvas = within(canvasElement);
183
+
184
+ // Day group headers render
185
+ const separators = canvas.getAllByRole("separator");
186
+ expect(separators.length).toBeGreaterThanOrEqual(2);
187
+
188
+ // Each revision is a listitem with a button
189
+ const items = canvas.getAllByRole("listitem");
190
+ expect(items).toHaveLength(LINEAR_REVISIONS.length);
191
+
192
+ // First revision message is visible
193
+ await waitFor(() =>
194
+ expect(
195
+ canvas.getByText("feat: add RevisionTimeline component with SVG lane gutter"),
196
+ ).toBeVisible(),
197
+ );
198
+
199
+ // Churn is rendered with tabular-nums
200
+ const churnEl = canvasElement.querySelector('[aria-label*="additions"]');
201
+ expect(churnEl).not.toBeNull();
202
+ },
203
+ };
204
+
205
+ /** Branch + merge history — two lanes with a cross-lane merge join in the SVG gutter. */
206
+ const BRANCH_REVISIONS: Revision[] = [
207
+ {
208
+ id: "m1",
209
+ message: "Merge branch 'feat/dark-mode' into main",
210
+ author: "M. Reimitz",
211
+ shortSha: "merge01",
212
+ timestamp: new Date("2024-06-14T15:00:00"),
213
+ additions: 0,
214
+ deletions: 0,
215
+ lane: 0,
216
+ refs: ["HEAD", "main"],
217
+ },
218
+ {
219
+ id: "f1",
220
+ message: "feat: dark-mode support for RevisionTimeline",
221
+ author: "A. Chen",
222
+ shortSha: "dark001",
223
+ timestamp: new Date("2024-06-14T13:00:00"),
224
+ additions: 55,
225
+ deletions: 12,
226
+ lane: 1,
227
+ refs: ["feat/dark-mode"],
228
+ },
229
+ {
230
+ id: "b1",
231
+ message: "chore: bump tokens package version",
232
+ author: "M. Reimitz",
233
+ shortSha: "bump001",
234
+ timestamp: new Date("2024-06-14T11:00:00"),
235
+ additions: 2,
236
+ deletions: 2,
237
+ lane: 0,
238
+ },
239
+ {
240
+ id: "f0",
241
+ message: "feat: scaffold dark-mode branch",
242
+ author: "A. Chen",
243
+ shortSha: "dark000",
244
+ timestamp: new Date("2024-06-13T17:00:00"),
245
+ additions: 10,
246
+ deletions: 0,
247
+ lane: 1,
248
+ },
249
+ {
250
+ id: "b0",
251
+ message: "chore: initial commit",
252
+ author: "M. Reimitz",
253
+ shortSha: "init000",
254
+ timestamp: new Date("2024-06-13T09:00:00"),
255
+ additions: 120,
256
+ deletions: 0,
257
+ lane: 0,
258
+ refs: ["origin/main"],
259
+ },
260
+ ];
261
+
262
+ const BRANCH_EDGES: RevisionEdge[] = [
263
+ // Merge commit m1 has two parents: f1 (feat branch) and b1 (main)
264
+ { from: "m1", to: "f1" },
265
+ { from: "m1", to: "b1" },
266
+ // feat branch lineage
267
+ { from: "f1", to: "f0" },
268
+ { from: "f0", to: "b0" },
269
+ // main lineage
270
+ { from: "b1", to: "b0" },
271
+ ];
272
+
273
+ /** Branch and merge — two lanes with a merge commit, showing SVG cross-lane bezier curves. */
274
+ export const BranchAndMerge: Story = {
275
+ args: {
276
+ revisions: BRANCH_REVISIONS,
277
+ edges: BRANCH_EDGES,
278
+ groupBy: "day",
279
+ },
280
+ play: async ({ canvasElement }) => {
281
+ const canvas = within(canvasElement);
282
+
283
+ // Merge commit renders with GitMerge icon (identified via aria-label on button)
284
+ const mergeBtn = canvas.getByRole("button", {
285
+ name: /Merge branch 'feat\/dark-mode' into main/,
286
+ });
287
+ expect(mergeBtn).toBeInTheDocument();
288
+
289
+ // All 5 revisions render
290
+ expect(canvas.getAllByRole("listitem")).toHaveLength(BRANCH_REVISIONS.length);
291
+
292
+ // The SVG gutter is present (aria-hidden)
293
+ const svg = canvasElement.querySelector("svg[aria-hidden='true']");
294
+ expect(svg).not.toBeNull();
295
+
296
+ // Day group headers present for two different days
297
+ expect(canvas.getAllByRole("separator").length).toBeGreaterThanOrEqual(2);
298
+ },
299
+ };
300
+
301
+ /** Selection — demonstrates controlled and uncontrolled selection with onSelect callback. */
302
+ export const Selection: Story = {
303
+ args: {
304
+ revisions: LINEAR_REVISIONS.slice(0, 4),
305
+ groupBy: "none",
306
+ onSelect: fn(),
307
+ },
308
+ play: async ({ canvasElement, args }) => {
309
+ const canvas = within(canvasElement);
310
+ const user = userEvent.setup();
311
+
312
+ const buttons = canvas.getAllByRole("button");
313
+ expect(buttons.length).toBeGreaterThan(0);
314
+
315
+ // Click the first revision button
316
+ await user.click(buttons[0]!);
317
+
318
+ // onSelect should have been called with the first revision id
319
+ expect(args.onSelect).toHaveBeenCalledWith(LINEAR_REVISIONS[0]!.id);
320
+
321
+ // The button should now be aria-pressed
322
+ await expect(buttons[0]).toHaveAttribute("aria-pressed", "true");
323
+
324
+ // Click a second revision
325
+ await user.click(buttons[1]!);
326
+ expect(args.onSelect).toHaveBeenCalledWith(LINEAR_REVISIONS[1]!.id);
327
+
328
+ // First button is no longer selected
329
+ await expect(buttons[0]).toHaveAttribute("aria-pressed", "false");
330
+ },
331
+ };
332
+
333
+ /** Compact density — tighter row height for dense information displays. */
334
+ export const Compact: Story = {
335
+ args: {
336
+ revisions: LINEAR_REVISIONS,
337
+ density: "compact",
338
+ groupBy: "day",
339
+ },
340
+ play: async ({ canvasElement }) => {
341
+ const canvas = within(canvasElement);
342
+ expect(canvas.getAllByRole("listitem")).toHaveLength(LINEAR_REVISIONS.length);
343
+ },
344
+ };
345
+
346
+ /** Controlled selection — parent manages the selected id externally. */
347
+ export const ControlledSelection: Story = {
348
+ args: {
349
+ revisions: LINEAR_REVISIONS.slice(0, 3),
350
+ groupBy: "none",
351
+ },
352
+ render: (args) => {
353
+ // eslint-disable-next-line react-hooks/rules-of-hooks
354
+ const [selected, setSelected] = useState<string>(LINEAR_REVISIONS[1]!.id);
355
+ return (
356
+ <div className="space-y-3">
357
+ <p className="text-caption text-muted-foreground">
358
+ Selected: <code className="font-mono">{selected}</code>
359
+ </p>
360
+ <RevisionTimeline {...args} selectedId={selected} onSelect={(id) => setSelected(id)} />
361
+ </div>
362
+ );
363
+ },
364
+ play: async ({ canvasElement }) => {
365
+ const canvas = within(canvasElement);
366
+ const buttons = canvas.getAllByRole("button");
367
+
368
+ // Second button (index 1) is pre-selected
369
+ await expect(buttons[1]).toHaveAttribute("aria-pressed", "true");
370
+ await expect(buttons[0]).toHaveAttribute("aria-pressed", "false");
371
+ },
372
+ };
373
+
374
+ // ─── Collapsible branches (multi-level) ──────────────────────────────────────
375
+
376
+ // main (lane 0) ← feat/dark-mode (lane 1) ← feat/dark-mode/tokens (lane 2, nested).
377
+ // `main` is NOT in `branches`, so the trunk stays permanently visible. Day B
378
+ // (2024-06-13) holds ONLY feat commits, so collapsing the branch makes that whole
379
+ // day group disappear.
380
+ const NESTED_REVISIONS: Revision[] = [
381
+ {
382
+ id: "m1",
383
+ message: "Merge branch 'feat/dark-mode' into main",
384
+ author: "M. Reimitz",
385
+ shortSha: "merge01",
386
+ timestamp: new Date("2024-06-14T15:00:00"),
387
+ lane: 0,
388
+ branchId: "main",
389
+ refs: ["HEAD", "main"],
390
+ },
391
+ {
392
+ id: "s1",
393
+ message: "feat: token contrast pass for dark surfaces",
394
+ author: "A. Chen",
395
+ shortSha: "tok0002",
396
+ timestamp: new Date("2024-06-14T14:00:00"),
397
+ additions: 24,
398
+ deletions: 6,
399
+ lane: 2,
400
+ branchId: "feat/dark-mode/tokens",
401
+ },
402
+ {
403
+ id: "f2",
404
+ message: "feat: dark-mode toggle component",
405
+ author: "A. Chen",
406
+ shortSha: "dark003",
407
+ timestamp: new Date("2024-06-14T13:00:00"),
408
+ additions: 88,
409
+ deletions: 4,
410
+ lane: 1,
411
+ branchId: "feat/dark-mode",
412
+ },
413
+ {
414
+ id: "b2",
415
+ message: "chore: bump tokens package version",
416
+ author: "M. Reimitz",
417
+ shortSha: "bump002",
418
+ timestamp: new Date("2024-06-14T11:00:00"),
419
+ additions: 2,
420
+ deletions: 2,
421
+ lane: 0,
422
+ branchId: "main",
423
+ },
424
+ {
425
+ id: "s0",
426
+ message: "feat: scaffold token sub-branch",
427
+ author: "A. Chen",
428
+ shortSha: "tok0001",
429
+ timestamp: new Date("2024-06-13T17:00:00"),
430
+ additions: 14,
431
+ deletions: 0,
432
+ lane: 2,
433
+ branchId: "feat/dark-mode/tokens",
434
+ },
435
+ {
436
+ id: "f1",
437
+ message: "feat: dark-mode surface + chart palettes",
438
+ author: "A. Chen",
439
+ shortSha: "dark002",
440
+ timestamp: new Date("2024-06-13T16:00:00"),
441
+ additions: 55,
442
+ deletions: 12,
443
+ lane: 1,
444
+ branchId: "feat/dark-mode",
445
+ },
446
+ {
447
+ id: "f0",
448
+ message: "feat: scaffold dark-mode branch",
449
+ author: "A. Chen",
450
+ shortSha: "dark001",
451
+ timestamp: new Date("2024-06-13T15:00:00"),
452
+ additions: 10,
453
+ deletions: 0,
454
+ lane: 1,
455
+ branchId: "feat/dark-mode",
456
+ },
457
+ {
458
+ id: "b1",
459
+ message: "docs: expand README with theming guide",
460
+ author: "M. Reimitz",
461
+ shortSha: "docs001",
462
+ timestamp: new Date("2024-06-12T10:00:00"),
463
+ additions: 60,
464
+ deletions: 3,
465
+ lane: 0,
466
+ branchId: "main",
467
+ },
468
+ {
469
+ id: "b0",
470
+ message: "chore: initial commit",
471
+ author: "M. Reimitz",
472
+ shortSha: "init000",
473
+ timestamp: new Date("2024-06-12T09:00:00"),
474
+ additions: 120,
475
+ deletions: 0,
476
+ lane: 0,
477
+ branchId: "main",
478
+ refs: ["origin/main"],
479
+ },
480
+ ];
481
+
482
+ const NESTED_BRANCHES: RevisionBranch[] = [
483
+ { id: "feat/dark-mode", name: "feat/dark-mode" },
484
+ { id: "feat/dark-mode/tokens", name: "feat/dark-mode/tokens", parentBranchId: "feat/dark-mode" },
485
+ ];
486
+
487
+ const NESTED_EDGES: RevisionEdge[] = [
488
+ // merge commit m1 has two parents: f2 (feat tip) and b2 (main)
489
+ { from: "m1", to: "f2" },
490
+ { from: "m1", to: "b2" },
491
+ // feat/dark-mode lineage
492
+ { from: "f2", to: "f1" },
493
+ { from: "f1", to: "f0" },
494
+ { from: "f0", to: "b1" }, // feat forks off main at b1
495
+ // nested token sub-branch lineage
496
+ { from: "s1", to: "s0" },
497
+ { from: "s0", to: "f1" }, // tokens forks off feat at f1
498
+ // main lineage
499
+ { from: "b2", to: "b1" },
500
+ { from: "b1", to: "b0" },
501
+ ];
502
+
503
+ /**
504
+ * Collapsible branches — supply `branches` to fold a branch (and its nested
505
+ * descendants) into a single summary row at its tip. Click the chevron on the
506
+ * `feat/dark-mode` tip to collapse it; the nested `feat/dark-mode/tokens` commits
507
+ * fold in too, and the merge connector re-routes to the summary node.
508
+ */
509
+ export const NestedBranches: Story = {
510
+ args: {
511
+ revisions: NESTED_REVISIONS,
512
+ edges: NESTED_EDGES,
513
+ branches: NESTED_BRANCHES,
514
+ groupBy: "day",
515
+ },
516
+ play: async ({ canvasElement }) => {
517
+ const canvas = within(canvasElement);
518
+ const user = userEvent.setup();
519
+
520
+ // All nine rows visible initially
521
+ expect(canvas.getAllByRole("listitem")).toHaveLength(NESTED_REVISIONS.length);
522
+
523
+ // Collapse feat/dark-mode via the toggle on its tip row
524
+ const toggle = canvas.getByRole("button", { name: "Collapse branch feat/dark-mode" });
525
+ await user.click(toggle);
526
+
527
+ // A summary row stands in, counting the whole folded subtree (3 feat + 2 tokens)
528
+ const summary = await canvas.findByRole("button", {
529
+ name: /Collapsed branch feat\/dark-mode,/,
530
+ });
531
+ expect(summary.getAttribute("aria-label")).toContain("5 commits");
532
+
533
+ // The feat commit rows are gone…
534
+ expect(canvas.queryByText("feat: dark-mode toggle component")).toBeNull();
535
+ // …but the branch-graph connector to the summary still draws
536
+ expect(canvasElement.querySelectorAll("svg path").length).toBeGreaterThanOrEqual(1);
537
+ },
538
+ };
539
+
540
+ /** Collapsed by default — the branch starts folded; expanding restores its commits. */
541
+ export const CollapsedByDefault: Story = {
542
+ args: {
543
+ revisions: NESTED_REVISIONS,
544
+ edges: NESTED_EDGES,
545
+ branches: NESTED_BRANCHES,
546
+ defaultCollapsedBranchIds: ["feat/dark-mode"],
547
+ groupBy: "day",
548
+ },
549
+ play: async ({ canvasElement }) => {
550
+ const canvas = within(canvasElement);
551
+
552
+ // The summary renders collapsed with the full subtree count
553
+ const summary = canvas.getByRole("button", { name: /Collapsed branch feat\/dark-mode,/ });
554
+ expect(summary).toHaveAttribute("aria-expanded", "false");
555
+ expect(summary.getAttribute("aria-label")).toContain("5 commits");
556
+
557
+ // The day that held only feat commits (2024-06-13) drops out → 2 day headers
558
+ expect(canvas.getAllByRole("separator")).toHaveLength(2);
559
+ },
560
+ };
561
+
562
+ /** Controlled collapse — the parent owns the collapsed-branch set. */
563
+ export const ControlledCollapse: Story = {
564
+ args: {
565
+ revisions: NESTED_REVISIONS,
566
+ edges: NESTED_EDGES,
567
+ branches: NESTED_BRANCHES,
568
+ groupBy: "day",
569
+ },
570
+ render: (args) => {
571
+ // eslint-disable-next-line react-hooks/rules-of-hooks
572
+ const [collapsed, setCollapsed] = useState<string[]>(["feat/dark-mode"]);
573
+ return (
574
+ <div className="space-y-3">
575
+ <p className="text-caption text-muted-foreground">
576
+ Collapsed:{" "}
577
+ <code className="font-mono">{collapsed.length ? collapsed.join(", ") : "—"}</code>
578
+ </p>
579
+ <RevisionTimeline
580
+ {...args}
581
+ collapsedBranchIds={collapsed}
582
+ onBranchToggle={(id, isCollapsed) =>
583
+ setCollapsed((prev) => (isCollapsed ? [...prev, id] : prev.filter((b) => b !== id)))
584
+ }
585
+ />
586
+ </div>
587
+ );
588
+ },
589
+ play: async ({ canvasElement }) => {
590
+ const canvas = within(canvasElement);
591
+ const user = userEvent.setup();
592
+
593
+ // Starts collapsed
594
+ const summary = canvas.getByRole("button", { name: /Collapsed branch feat\/dark-mode,/ });
595
+ expect(summary).toHaveAttribute("aria-expanded", "false");
596
+
597
+ // Expanding through the controlled round-trip restores the feat commits
598
+ await user.click(summary);
599
+ await waitFor(() => expect(canvas.getByText("feat: dark-mode toggle component")).toBeVisible());
600
+ },
601
+ };
602
+
603
+ // ─── Process flow (branches-as-categories, deeply nested) ────────────────────
604
+
605
+ // A deliberate re-use of the branch model as a PROCESS FLOW. The main lane is the
606
+ // agent's top-level run; each "branch" is a category of sub-steps that nests
607
+ // inside the one above it — Research phase ▸ Web search ▸ Fetch documents ▸ Rank
608
+ // results — so collapsing folds a whole sub-tree into one category row, exactly
609
+ // like a collapsed group of (nested) tool calls. `main` is not listed, so the
610
+ // top-level steps stay pinned. Steps are ordered so the run dives down into the
611
+ // deepest sub-task and climbs back out (newest-first, result at the top).
612
+ const FLOW_STEPS: Revision[] = [
613
+ {
614
+ id: "p_done",
615
+ message: "Composed the final answer",
616
+ author: "Agent",
617
+ timestamp: new Date("2024-06-14T10:00:42"),
618
+ lane: 0,
619
+ branchId: "main",
620
+ refs: ["result"],
621
+ },
622
+ {
623
+ id: "r2",
624
+ message: "Synthesized the findings",
625
+ author: "research",
626
+ timestamp: new Date("2024-06-14T10:00:38"),
627
+ lane: 1,
628
+ branchId: "research",
629
+ },
630
+ {
631
+ id: "se2",
632
+ message: "Selected the top queries",
633
+ author: "web.search",
634
+ timestamp: new Date("2024-06-14T10:00:34"),
635
+ lane: 2,
636
+ branchId: "search",
637
+ },
638
+ {
639
+ id: "f2",
640
+ message: "Parsed the downloaded documents",
641
+ author: "doc.fetch",
642
+ timestamp: new Date("2024-06-14T10:00:30"),
643
+ lane: 3,
644
+ branchId: "fetch",
645
+ },
646
+ {
647
+ id: "ra2",
648
+ message: "Scored candidates by relevance",
649
+ author: "ranker",
650
+ timestamp: new Date("2024-06-14T10:00:26"),
651
+ lane: 4,
652
+ branchId: "rank",
653
+ },
654
+ {
655
+ id: "ra1",
656
+ message: "Built the ranking features",
657
+ author: "ranker",
658
+ timestamp: new Date("2024-06-14T10:00:22"),
659
+ lane: 4,
660
+ branchId: "rank",
661
+ },
662
+ {
663
+ id: "f1",
664
+ message: "Downloaded 12 documents",
665
+ author: "doc.fetch",
666
+ timestamp: new Date("2024-06-14T10:00:18"),
667
+ lane: 3,
668
+ branchId: "fetch",
669
+ },
670
+ {
671
+ id: "se1",
672
+ message: "Brainstormed search queries",
673
+ author: "web.search",
674
+ timestamp: new Date("2024-06-14T10:00:14"),
675
+ lane: 2,
676
+ branchId: "search",
677
+ },
678
+ {
679
+ id: "r1",
680
+ message: "Scoped the research questions",
681
+ author: "research",
682
+ timestamp: new Date("2024-06-14T10:00:10"),
683
+ lane: 1,
684
+ branchId: "research",
685
+ },
686
+ {
687
+ id: "p_plan",
688
+ message: "Planned the work",
689
+ author: "Agent",
690
+ timestamp: new Date("2024-06-14T10:00:05"),
691
+ lane: 0,
692
+ branchId: "main",
693
+ },
694
+ {
695
+ id: "p_start",
696
+ message: "Received the user's question",
697
+ author: "User",
698
+ timestamp: new Date("2024-06-14T10:00:00"),
699
+ lane: 0,
700
+ branchId: "main",
701
+ refs: ["prompt"],
702
+ },
703
+ ];
704
+
705
+ // Four nested levels below the top-level run: research ▸ search ▸ fetch ▸ rank.
706
+ const FLOW_BRANCHES: RevisionBranch[] = [
707
+ { id: "research", name: "Research phase" },
708
+ { id: "search", name: "Web search", parentBranchId: "research" },
709
+ { id: "fetch", name: "Fetch documents", parentBranchId: "search" },
710
+ { id: "rank", name: "Rank results", parentBranchId: "fetch" },
711
+ ];
712
+
713
+ const FLOW_EDGES: RevisionEdge[] = [
714
+ // top-level run
715
+ { from: "p_done", to: "p_plan" },
716
+ { from: "p_plan", to: "p_start" },
717
+ // research forks off the plan and rejoins at the answer
718
+ { from: "p_done", to: "r2" },
719
+ { from: "r2", to: "r1" },
720
+ { from: "r1", to: "p_plan" },
721
+ // search nests inside research
722
+ { from: "r2", to: "se2" },
723
+ { from: "se2", to: "se1" },
724
+ { from: "se1", to: "r1" },
725
+ // fetch nests inside search
726
+ { from: "se2", to: "f2" },
727
+ { from: "f2", to: "f1" },
728
+ { from: "f1", to: "se1" },
729
+ // rank nests inside fetch
730
+ { from: "f2", to: "ra2" },
731
+ { from: "ra2", to: "ra1" },
732
+ { from: "ra1", to: "f1" },
733
+ ];
734
+
735
+ const flowSummary = (count: number) => `${count} ${count === 1 ? "step" : "steps"}`;
736
+
737
+ /**
738
+ * Process flow — branches as collapsible, **deeply nested** categories. Opens
739
+ * fully folded into the top category ("Research phase · 8 steps"); each expand
740
+ * reveals that level's steps plus the next collapsed category, so you can drill
741
+ * four levels deep: Research phase ▸ Web search ▸ Fetch documents ▸ Rank results.
742
+ * A deliberate (useful) misuse of the branch model.
743
+ */
744
+ export const ProcessFlow: Story = {
745
+ args: {
746
+ revisions: FLOW_STEPS,
747
+ edges: FLOW_EDGES,
748
+ branches: FLOW_BRANCHES,
749
+ groupBy: "none",
750
+ // every level collapsed → expanding drills one level at a time
751
+ defaultCollapsedBranchIds: ["research", "search", "fetch", "rank"],
752
+ formatBranchSummary: flowSummary,
753
+ },
754
+ play: async ({ canvasElement }) => {
755
+ const canvas = within(canvasElement);
756
+ const user = userEvent.setup();
757
+
758
+ // L1 — everything folds into the top category (all 8 nested steps)
759
+ const research = canvas.getByRole("button", { name: /Collapsed branch Research phase,/ });
760
+ expect(research.getAttribute("aria-label")).toContain("8 steps");
761
+ // top-level run steps stay pinned
762
+ expect(canvas.getByText("Planned the work")).toBeVisible();
763
+
764
+ // L2 — expanding reveals research steps + the nested Web search category
765
+ await user.click(research);
766
+ const search = await canvas.findByRole("button", { name: /Collapsed branch Web search,/ });
767
+ expect(search.getAttribute("aria-label")).toContain("6 steps");
768
+
769
+ // L3 — Fetch documents
770
+ await user.click(search);
771
+ const fetchCat = await canvas.findByRole("button", {
772
+ name: /Collapsed branch Fetch documents,/,
773
+ });
774
+ expect(fetchCat.getAttribute("aria-label")).toContain("4 steps");
775
+
776
+ // L4 — Rank results
777
+ await user.click(fetchCat);
778
+ const rank = await canvas.findByRole("button", { name: /Collapsed branch Rank results,/ });
779
+ expect(rank.getAttribute("aria-label")).toContain("2 steps");
780
+
781
+ // Fully expanded — the deepest sub-step is now visible
782
+ await user.click(rank);
783
+ await waitFor(() => expect(canvas.getByText("Scored candidates by relevance")).toBeVisible());
784
+ },
785
+ };