@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,47 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import {
3
+ Breadcrumb,
4
+ BreadcrumbItem,
5
+ BreadcrumbLink,
6
+ BreadcrumbList,
7
+ BreadcrumbPage,
8
+ BreadcrumbSeparator,
9
+ } from "./breadcrumb";
10
+ const meta = {
11
+ title: "Navigation/Breadcrumb",
12
+ component: Breadcrumb,
13
+ tags: ["autodocs"],
14
+ argTypes: {
15
+ children: {
16
+ description: "Breadcrumb content — compose with BreadcrumbList, BreadcrumbItem, etc.",
17
+ control: false,
18
+ table: { category: "Content" },
19
+ },
20
+ className: {
21
+ description: "Additional CSS classes applied to the nav element.",
22
+ control: "text",
23
+ table: { category: "Styling" },
24
+ },
25
+ },
26
+ } satisfies Meta<typeof Breadcrumb>;
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+ export const Default: Story = {
30
+ render: () => (
31
+ <Breadcrumb>
32
+ <BreadcrumbList>
33
+ <BreadcrumbItem>
34
+ <BreadcrumbLink href="#">Home</BreadcrumbLink>
35
+ </BreadcrumbItem>
36
+ <BreadcrumbSeparator />
37
+ <BreadcrumbItem>
38
+ <BreadcrumbLink href="#">Projects</BreadcrumbLink>
39
+ </BreadcrumbItem>
40
+ <BreadcrumbSeparator />
41
+ <BreadcrumbItem>
42
+ <BreadcrumbPage>brand-ui</BreadcrumbPage>
43
+ </BreadcrumbItem>
44
+ </BreadcrumbList>
45
+ </Breadcrumb>
46
+ ),
47
+ };
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "./breadcrumb";
4
+ describe("Breadcrumb", () => {
5
+ it("renders the current page", () => {
6
+ render(
7
+ <Breadcrumb>
8
+ <BreadcrumbList>
9
+ <BreadcrumbItem>
10
+ <BreadcrumbPage>Here</BreadcrumbPage>
11
+ </BreadcrumbItem>
12
+ </BreadcrumbList>
13
+ </Breadcrumb>,
14
+ );
15
+ expect(screen.getByText("Here")).toHaveAttribute("aria-current", "page");
16
+ });
17
+ });
@@ -0,0 +1,86 @@
1
+ import { forwardRef, type ComponentProps, type ReactNode } from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react";
4
+ import { cn } from "../../lib/cn";
5
+
6
+ export const Breadcrumb = forwardRef<HTMLElement, ComponentProps<"nav">>(
7
+ function Breadcrumb(props, ref) {
8
+ return <nav ref={ref} aria-label="breadcrumb" {...props} />;
9
+ },
10
+ );
11
+ export const BreadcrumbList = forwardRef<HTMLOListElement, ComponentProps<"ol">>(
12
+ function BreadcrumbList({ className, ...props }, ref) {
13
+ return (
14
+ <ol
15
+ ref={ref}
16
+ className={cn(
17
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ },
24
+ );
25
+ export const BreadcrumbItem = forwardRef<HTMLLIElement, ComponentProps<"li">>(
26
+ function BreadcrumbItem({ className, ...props }, ref) {
27
+ return (
28
+ <li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
29
+ );
30
+ },
31
+ );
32
+ export const BreadcrumbLink = forwardRef<
33
+ HTMLAnchorElement,
34
+ ComponentProps<"a"> & { asChild?: boolean }
35
+ >(function BreadcrumbLink({ className, asChild, ...props }, ref) {
36
+ const Comp = asChild ? Slot : "a";
37
+ return (
38
+ <Comp
39
+ ref={ref}
40
+ className={cn("transition-colors duration-fast hover:text-foreground", className)}
41
+ {...props}
42
+ />
43
+ );
44
+ });
45
+ export const BreadcrumbPage = forwardRef<HTMLSpanElement, ComponentProps<"span">>(
46
+ function BreadcrumbPage({ className, ...props }, ref) {
47
+ return (
48
+ <span
49
+ ref={ref}
50
+ role="link"
51
+ aria-disabled="true"
52
+ aria-current="page"
53
+ className={cn("font-medium text-foreground", className)}
54
+ {...props}
55
+ />
56
+ );
57
+ },
58
+ );
59
+ export function BreadcrumbSeparator({ children, className, ...props }: ComponentProps<"li">) {
60
+ return (
61
+ <li
62
+ role="presentation"
63
+ aria-hidden="true"
64
+ className={cn("[&>svg]:size-3.5", className)}
65
+ {...props}
66
+ >
67
+ {children ?? <ChevronRight />}
68
+ </li>
69
+ );
70
+ }
71
+ export function BreadcrumbEllipsis({ className, ...props }: ComponentProps<"span">) {
72
+ return (
73
+ <span
74
+ role="presentation"
75
+ aria-hidden="true"
76
+ className={cn("flex size-9 items-center justify-center", className)}
77
+ {...props}
78
+ >
79
+ <MoreHorizontal className="size-4" />
80
+ <span className="sr-only">More</span>
81
+ </span>
82
+ );
83
+ }
84
+ export function BreadcrumbItems({ children }: { children: ReactNode }) {
85
+ return <>{children}</>;
86
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ Breadcrumb,
3
+ BreadcrumbList,
4
+ BreadcrumbItem,
5
+ BreadcrumbLink,
6
+ BreadcrumbPage,
7
+ BreadcrumbSeparator,
8
+ BreadcrumbEllipsis,
9
+ } from "./breadcrumb";
@@ -0,0 +1,70 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect } from "storybook/test";
3
+ import { Button } from "./button";
4
+
5
+ const meta = {
6
+ title: "Core/Button",
7
+ component: Button,
8
+ tags: ["autodocs"],
9
+ args: { children: "Button" },
10
+ argTypes: {
11
+ variant: {
12
+ control: "select",
13
+ options: [
14
+ "default",
15
+ "secondary",
16
+ "destructive",
17
+ "outline",
18
+ "outline-subtle",
19
+ "ghost",
20
+ "link",
21
+ ],
22
+ },
23
+ size: { control: "select", options: ["sm", "default", "lg", "icon"] },
24
+ },
25
+ } satisfies Meta<typeof Button>;
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Default: Story = {};
30
+ export const Secondary: Story = { args: { variant: "secondary" } };
31
+ export const Destructive: Story = { args: { variant: "destructive" } };
32
+ export const Outline: Story = { args: { variant: "outline" } };
33
+ // The quiet hairline rung (#194): `border-border` instead of the strong
34
+ // form-field `border-input` — for "outlined but calm" non-field controls.
35
+ export const OutlineSubtle: Story = { args: { variant: "outline-subtle" } };
36
+ export const Ghost: Story = { args: { variant: "ghost" } };
37
+
38
+ // The single project-wide CssCheck. Proves the shared preview actually loaded
39
+ // the brand token stylesheet + Tailwind: the default Button variant is
40
+ // `bg-primary`, which resolves to the applied theme's `--primary` token. If
41
+ // themes.css/Tailwind failed to load, this would be the transparent default
42
+ // `rgba(0, 0, 0, 0)` and fail — where a plain `toBeVisible()` would still pass
43
+ // on the unstyled element.
44
+ //
45
+ // The harness applies the shipped DEFAULT theme (`light`) to
46
+ // `document.documentElement` (see `apps/docs/.storybook/preview.tsx`'s
47
+ // `withTheme` decorator, #402), so `--primary` here is light's own
48
+ // value (`oklch(0.553 0.143 153)` in themes.css) — not the unbranded `:root`
49
+ // fallback this test asserted against before #402 fixed the harness to
50
+ // actually apply a theme.
51
+ export const CssCheck: Story = {
52
+ play: async ({ canvas }) => {
53
+ const btn = canvas.getByRole("button", { name: "Button" });
54
+ await expect(getComputedStyle(btn).backgroundColor).toBe("oklch(0.553 0.143 153)");
55
+ },
56
+ };
57
+
58
+ export const AllVariants: Story = {
59
+ render: () => (
60
+ <div className="flex flex-wrap gap-2">
61
+ <Button>Default</Button>
62
+ <Button variant="secondary">Secondary</Button>
63
+ <Button variant="destructive">Destructive</Button>
64
+ <Button variant="outline">Outline</Button>
65
+ <Button variant="outline-subtle">Outline subtle</Button>
66
+ <Button variant="ghost">Ghost</Button>
67
+ <Button variant="link">Link</Button>
68
+ </div>
69
+ ),
70
+ };
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { Button } from "./button";
5
+
6
+ describe("Button", () => {
7
+ it("renders its children", () => {
8
+ render(<Button>Click me</Button>);
9
+ expect(screen.getByRole("button", { name: "Click me" })).toBeInTheDocument();
10
+ });
11
+
12
+ it("calls onClick when pressed", async () => {
13
+ const onClick = vi.fn();
14
+ render(<Button onClick={onClick}>Go</Button>);
15
+ await userEvent.click(screen.getByRole("button", { name: "Go" }));
16
+ expect(onClick).toHaveBeenCalledTimes(1);
17
+ });
18
+
19
+ it("does not fire when disabled", async () => {
20
+ const onClick = vi.fn();
21
+ render(
22
+ <Button disabled onClick={onClick}>
23
+ Nope
24
+ </Button>,
25
+ );
26
+ await userEvent.click(screen.getByRole("button", { name: "Nope" }));
27
+ expect(onClick).not.toHaveBeenCalled();
28
+ });
29
+
30
+ // #194: `outline-subtle` targets `border-border`, `outline` targets the form-field
31
+ // `border-input`. Since the ADR 0010 Amendment (2026-06-20) returned `--input` to the
32
+ // subtle rung these two render identically by default, but the class STRINGS stay
33
+ // distinct (a semantic seam a brand could re-separate). This locks the class target,
34
+ // not the resting contrast.
35
+ it("outline-subtle uses border-border, not the form-field border-input", () => {
36
+ render(<Button variant="outline-subtle">Quiet</Button>);
37
+ const btn = screen.getByRole("button", { name: "Quiet" });
38
+ expect(btn.className.split(" ")).toContain("border-border");
39
+ expect(btn.className.split(" ")).not.toContain("border-input");
40
+ });
41
+
42
+ it("renders as a child element with asChild", () => {
43
+ render(
44
+ <Button asChild>
45
+ <a href="/home">Home</a>
46
+ </Button>,
47
+ );
48
+ expect(screen.getByRole("link", { name: "Home" })).toHaveAttribute("href", "/home");
49
+ });
50
+ });
@@ -0,0 +1,63 @@
1
+ import { forwardRef, type ButtonHTMLAttributes } from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "../../lib/cn";
5
+
6
+ export const buttonVariants = cva(
7
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap touch-manipulation rounded-md text-body font-medium transition-[color,background-color,border-color,box-shadow,scale] duration-fast ease-standard active:scale-[0.98] motion-reduce:active:scale-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
13
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
16
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ // The calm "outlined but quiet" rung (#194, research 02 §3a). `outline` uses
18
+ // the form-field `border-input` token, `outline-subtle` uses `border-border`.
19
+ // Since the ADR 0010 Amendment (2026-06-20) returned `--input` to the subtle
20
+ // rung, these two are now VISUALLY IDENTICAL by default — both variant names
21
+ // are kept as a SEMANTIC SEAM (a future brand could re-separate `--input` from
22
+ // `--border`) and to avoid churning `outline-subtle` callers (e.g.
23
+ // change-review.tsx). `outline` stays for genuinely form-adjacent controls.
24
+ "outline-subtle":
25
+ "border border-border bg-background hover:bg-accent hover:text-accent-foreground",
26
+ ghost: "hover:bg-accent hover:text-accent-foreground",
27
+ // #399 — a link button is TEXT on the page, so it takes the on-surface
28
+ // `-text` rung; `bg-primary` above keeps the fill rung.
29
+ link: "text-primary-text underline-offset-4 hover:underline",
30
+ },
31
+ size: {
32
+ sm: "h-8 rounded-md px-3 text-meta",
33
+ default: "h-9 px-4 py-2",
34
+ lg: "h-10 rounded-md px-6",
35
+ icon: "size-9",
36
+ "icon-sm": "size-8",
37
+ "icon-lg": "size-10",
38
+ },
39
+ },
40
+ defaultVariants: { variant: "default", size: "default" },
41
+ },
42
+ );
43
+
44
+ export interface ButtonProps
45
+ extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
46
+ /** Render the child element as the button (Radix Slot) instead of a <button>. */
47
+ asChild?: boolean;
48
+ }
49
+
50
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
51
+ { className, variant, size, asChild = false, type, ...props },
52
+ ref,
53
+ ) {
54
+ const Comp = asChild ? Slot : "button";
55
+ return (
56
+ <Comp
57
+ ref={ref}
58
+ className={cn(buttonVariants({ variant, size }), className)}
59
+ type={asChild ? undefined : (type ?? "button")}
60
+ {...props}
61
+ />
62
+ );
63
+ });
@@ -0,0 +1 @@
1
+ export { Button, buttonVariants, type ButtonProps } from "./button";
@@ -0,0 +1,45 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { ButtonGroup, ButtonGroupText } from "./button-group";
3
+ import { Button } from "../button";
4
+ const meta = {
5
+ title: "Forms/ButtonGroup",
6
+ component: ButtonGroup,
7
+ tags: ["autodocs"],
8
+ argTypes: {
9
+ orientation: {
10
+ description: "Layout direction of the group.",
11
+ control: { type: "radio" },
12
+ options: ["horizontal", "vertical"],
13
+ table: { category: "Appearance" },
14
+ },
15
+ className: {
16
+ description: "Additional CSS classes applied to the root element.",
17
+ control: "text",
18
+ table: { category: "Appearance" },
19
+ },
20
+ children: {
21
+ description: "Button or ButtonGroupText elements to group together.",
22
+ control: false,
23
+ table: { category: "Content" },
24
+ },
25
+ },
26
+ } satisfies Meta<typeof ButtonGroup>;
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+ export const Default: Story = {
30
+ render: () => (
31
+ <ButtonGroup>
32
+ <Button variant="outline">Day</Button>
33
+ <Button variant="outline">Week</Button>
34
+ <Button variant="outline">Month</Button>
35
+ </ButtonGroup>
36
+ ),
37
+ };
38
+ export const WithText: Story = {
39
+ render: () => (
40
+ <ButtonGroup>
41
+ <ButtonGroupText>https://</ButtonGroupText>
42
+ <Button variant="outline">brand-ui.dev</Button>
43
+ </ButtonGroup>
44
+ ),
45
+ };
@@ -0,0 +1,70 @@
1
+ import { forwardRef, type ComponentProps } from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { cn } from "../../lib/cn";
5
+ import { Separator } from "../separator";
6
+
7
+ export const buttonGroupVariants = cva(
8
+ "flex w-fit items-stretch [&>*]:relative [&>*]:focus-within:z-10 has-[>[data-slot=button-group]]:gap-2",
9
+ {
10
+ variants: {
11
+ orientation: {
12
+ horizontal:
13
+ "[&>*:not(:first-child)]:rounded-s-none [&>*:not(:first-child)]:border-s-0 [&>*:not(:last-child)]:rounded-e-none",
14
+ vertical:
15
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
16
+ },
17
+ },
18
+ defaultVariants: { orientation: "horizontal" },
19
+ },
20
+ );
21
+
22
+ export const ButtonGroup = forwardRef<
23
+ HTMLDivElement,
24
+ ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>
25
+ >(function ButtonGroup({ className, orientation = "horizontal", ...props }, ref) {
26
+ return (
27
+ <div
28
+ ref={ref}
29
+ role="group"
30
+ data-slot="button-group"
31
+ data-orientation={orientation}
32
+ className={cn(buttonGroupVariants({ orientation }), className)}
33
+ {...props}
34
+ />
35
+ );
36
+ });
37
+
38
+ export const ButtonGroupText = forwardRef<
39
+ HTMLDivElement,
40
+ ComponentProps<"div"> & { asChild?: boolean }
41
+ >(function ButtonGroupText({ className, asChild = false, ...props }, ref) {
42
+ const Comp = asChild ? Slot : "div";
43
+ return (
44
+ <Comp
45
+ ref={ref}
46
+ className={cn(
47
+ "flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium shadow-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ );
53
+ });
54
+
55
+ export const ButtonGroupSeparator = forwardRef<HTMLDivElement, ComponentProps<typeof Separator>>(
56
+ function ButtonGroupSeparator({ className, orientation = "vertical", ...props }, ref) {
57
+ return (
58
+ <Separator
59
+ ref={ref}
60
+ data-slot="button-group-separator"
61
+ orientation={orientation}
62
+ className={cn(
63
+ "relative !m-0 self-stretch bg-input data-[orientation=vertical]:h-auto",
64
+ className,
65
+ )}
66
+ {...props}
67
+ />
68
+ );
69
+ },
70
+ );
@@ -0,0 +1,6 @@
1
+ export {
2
+ ButtonGroup,
3
+ ButtonGroupText,
4
+ ButtonGroupSeparator,
5
+ buttonGroupVariants,
6
+ } from "./button-group";
@@ -0,0 +1,95 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import { expect } from "storybook/test";
4
+ import { Calendar } from "./calendar";
5
+ const meta = {
6
+ title: "Forms/Calendar",
7
+ component: Calendar,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ mode: {
11
+ description: "Selection mode: single date, multiple dates, or a range.",
12
+ control: { type: "select" },
13
+ options: ["single", "multiple", "range"],
14
+ table: { category: "Behavior" },
15
+ },
16
+ showOutsideDays: {
17
+ description: "Show days from adjacent months in the current month grid.",
18
+ control: "boolean",
19
+ table: { category: "Appearance" },
20
+ },
21
+ numberOfMonths: {
22
+ description: "Number of months to display side by side.",
23
+ control: "number",
24
+ table: { category: "Appearance" },
25
+ },
26
+ disabled: {
27
+ description: "Disables specific dates (Matcher or boolean).",
28
+ control: false,
29
+ table: { category: "State" },
30
+ },
31
+ selected: {
32
+ description: "Controlled selected date(s).",
33
+ control: false,
34
+ table: { category: "State" },
35
+ },
36
+ onSelect: {
37
+ description: "Called when the user picks a date.",
38
+ control: false,
39
+ table: { category: "Behavior" },
40
+ },
41
+ className: {
42
+ description: "Additional CSS classes applied to the root element.",
43
+ control: "text",
44
+ table: { category: "Appearance" },
45
+ },
46
+ },
47
+ } satisfies Meta<typeof Calendar>;
48
+ export default meta;
49
+ type Story = StoryObj<typeof meta>;
50
+ // Pinned rather than `new Date()` (#430): the selected day's cell paints
51
+ // `--primary-foreground` on `--primary` (light), a real, accepted AA
52
+ // miss (4.31:1 vs a 4.5:1 floor — #180, `INK_EXEMPT` in
53
+ // `packages/tokens/src/themes-contrast.test.ts`). axe-core's `color-contrast`
54
+ // rule reports single-CHARACTER text as `incomplete`/`shortTextContent`
55
+ // instead of scoring it, so with `new Date()` this story was red on days
56
+ // 10-31 of the month and silently green on days 1-9 — a verification result
57
+ // keyed to the wall clock, not to the code. The pinned day MUST stay in the
58
+ // 10-31 range: a single-digit pin would make the suite green forever, but
59
+ // only by routing the cell into that same unscoreable-text path, which hides
60
+ // the miss rather than fixing or honestly exempting it (see `Test to add` in
61
+ // #430 and `scripts/a11y-baseline.json`, which now carries this story's
62
+ // entry). `defaultMonth` is pinned alongside it so the displayed month is
63
+ // never a function of "now" either.
64
+ //
65
+ // The `today` cell (react-day-picker's `today` modifier) still tracks the
66
+ // real wall clock and is NOT pinned — only the `selected` cell is. That is
67
+ // safe for this lock: the `today` plate (`bg-accent`/`text-accent-foreground`)
68
+ // is a different, contrast-clean token pair (confirmed by probing it directly
69
+ // with axe, `runOnly: ["color-contrast"]`, on a two-digit "today" — zero
70
+ // violations), so it cannot flip this story's axe outcome on any date.
71
+ const FIXED_MONTH = new Date(2026, 7, 1); // 2026-08-01
72
+ const FIXED_SELECTED = new Date(2026, 7, 17); // 2026-08-17 — two digits, on purpose
73
+
74
+ export const Default: Story = {
75
+ render: () => {
76
+ // eslint-disable-next-line react-hooks/rules-of-hooks
77
+ const [date, setDate] = useState<Date | undefined>(FIXED_SELECTED);
78
+ return (
79
+ <Calendar
80
+ mode="single"
81
+ defaultMonth={FIXED_MONTH}
82
+ selected={date}
83
+ onSelect={setDate}
84
+ className="rounded-md border"
85
+ />
86
+ );
87
+ },
88
+ // Confirms the calendar grid renders and a day cell is present.
89
+ play: async ({ canvas }) => {
90
+ const grid = canvas.getByRole("grid");
91
+ await expect(grid).toBeInTheDocument();
92
+ const dayCells = canvas.getAllByRole("gridcell");
93
+ await expect(dayCells.length).toBeGreaterThan(0);
94
+ },
95
+ };