@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,101 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CopyableValue — shown short, copied exact.
5
+ *
6
+ * Numbers across the library are formatted COMPACT by default (`1.5M`, `$820`),
7
+ * because the surfaces they appear on — a KPI tile, an axis tick, a chart in a
8
+ * chat bubble — have no room for `50012102.632741`. That is only defensible if
9
+ * the exact value stays reachable, so this renders the compact string as a real
10
+ * `<button>` that puts the unrounded one on the clipboard.
11
+ *
12
+ * Why a button and not a tooltip: a chart tooltip is `pointer-events-none` (it
13
+ * must not swallow the `mousemove` that drives the crosshair), so it can carry
14
+ * text but never an interaction. A button is reachable by mouse, by keyboard,
15
+ * and by touch — all three, with no bespoke affordance.
16
+ */
17
+
18
+ import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react";
19
+ import { cn } from "../../lib/cn";
20
+ import { useCopyToClipboard } from "../../lib/use-copy-to-clipboard";
21
+ import { useLocale } from "../locale-provider";
22
+
23
+ export interface CopyableValueProps extends Omit<
24
+ ButtonHTMLAttributes<HTMLButtonElement>,
25
+ "value" | "children"
26
+ > {
27
+ /**
28
+ * The EXACT text written to the clipboard — the unrounded, un-abbreviated
29
+ * value. Not locale-formatted by this component: a copied number is usually
30
+ * on its way into a spreadsheet or a query.
31
+ */
32
+ value: string;
33
+ /** What the reader sees. Defaults to `value`. */
34
+ children?: ReactNode;
35
+ /**
36
+ * Overrides the screen-reader hint appended to the visible text. The
37
+ * accessible name is always `<visible text> + <hint>`, which keeps WCAG 2.5.3
38
+ * (Label in Name) satisfied — voice-control users can say what they read.
39
+ */
40
+ hint?: string;
41
+ /** ms the "Copied" confirmation stays. Default: 1500. */
42
+ resetAfterMs?: number;
43
+ }
44
+
45
+ /**
46
+ * A value that copies its exact form on click.
47
+ *
48
+ * ```tsx
49
+ * <CopyableValue value="50012102.632741">50M</CopyableValue>
50
+ * ```
51
+ */
52
+ export const CopyableValue = forwardRef<HTMLButtonElement, CopyableValueProps>(
53
+ function CopyableValue(
54
+ { value, children, hint, resetAfterMs, className, onClick, ...props },
55
+ ref,
56
+ ) {
57
+ const { t } = useLocale();
58
+ const { copied, copy } = useCopyToClipboard(
59
+ resetAfterMs === undefined ? undefined : { resetAfterMs },
60
+ );
61
+
62
+ return (
63
+ <>
64
+ <button
65
+ data-slot="copyable-value"
66
+ data-copied={copied ? "" : undefined}
67
+ ref={ref}
68
+ type="button"
69
+ {...props}
70
+ className={cn(
71
+ // Reads as text, behaves as a control: the dotted underline is the
72
+ // only resting affordance, so a value inside a heading or a KPI
73
+ // still looks like a value, not a link.
74
+ "inline-flex items-baseline gap-1 rounded-sm underline decoration-dotted decoration-muted-foreground underline-offset-4",
75
+ "hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
76
+ "transition-colors duration-fast ease-standard motion-reduce:transition-none",
77
+ className,
78
+ )}
79
+ onClick={(event) => {
80
+ onClick?.(event);
81
+ if (!event.defaultPrevented) {
82
+ void copy(value);
83
+ }
84
+ }}
85
+ >
86
+ {children ?? value}
87
+ <span className="sr-only">{hint ?? t("ui.copyableValue.hint")}</span>
88
+ </button>
89
+ {/*
90
+ One live region per control, always mounted: an `aria-live` region
91
+ inserted at the same moment its text appears is frequently missed by
92
+ screen readers (ARIA22), so the region exists from first paint and
93
+ only its contents change.
94
+ */}
95
+ <span aria-live="polite" className="sr-only" role="status">
96
+ {copied ? t("ui.copyableValue.copied") : ""}
97
+ </span>
98
+ </>
99
+ );
100
+ },
101
+ );
@@ -0,0 +1 @@
1
+ export { CopyableValue, type CopyableValueProps } from "./copyable-value";
@@ -0,0 +1,42 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, within } from "storybook/test";
3
+ import { DatePicker } from "./date-picker";
4
+ const meta = {
5
+ title: "Forms/DatePicker",
6
+ component: DatePicker,
7
+ tags: ["autodocs"],
8
+ argTypes: {
9
+ value: {
10
+ description: "Controlled selected date.",
11
+ control: false,
12
+ table: { category: "State" },
13
+ },
14
+ placeholder: {
15
+ description: "Text shown in the trigger when no date is selected.",
16
+ control: "text",
17
+ table: { category: "Content" },
18
+ },
19
+ onValueChange: {
20
+ description: "Called when the selected date changes.",
21
+ control: false,
22
+ table: { category: "Behavior" },
23
+ },
24
+ className: {
25
+ description: "Additional CSS classes applied to the trigger button.",
26
+ control: "text",
27
+ table: { category: "Appearance" },
28
+ },
29
+ },
30
+ } satisfies Meta<typeof DatePicker>;
31
+ export default meta;
32
+ type Story = StoryObj<typeof meta>;
33
+ export const Default: Story = {
34
+ // Opens the calendar popover and confirms the grid is visible.
35
+ play: async ({ canvas, canvasElement, userEvent }) => {
36
+ const trigger = canvas.getByRole("button");
37
+ await userEvent.click(trigger);
38
+ const body = within(canvasElement.ownerDocument.body);
39
+ const grid = await body.findByRole("grid");
40
+ await expect(grid).toBeInTheDocument();
41
+ },
42
+ };
@@ -0,0 +1,49 @@
1
+ import { useState } from "react";
2
+ import { Calendar as CalendarIcon } from "lucide-react";
3
+ import { cn } from "../../lib/cn";
4
+ import { Button } from "../button";
5
+ import { Calendar } from "../calendar";
6
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
7
+
8
+ export interface DatePickerProps {
9
+ value?: Date;
10
+ onValueChange?: (date?: Date) => void;
11
+ placeholder?: string;
12
+ className?: string;
13
+ }
14
+
15
+ /** Single-date picker (Popover + Calendar). Controlled or uncontrolled. */
16
+ export function DatePicker({
17
+ value,
18
+ onValueChange,
19
+ placeholder = "Pick a date",
20
+ className,
21
+ }: DatePickerProps) {
22
+ const [internal, setInternal] = useState<Date | undefined>(undefined);
23
+ const date = value ?? internal;
24
+ const setDate = (d?: Date) => (onValueChange ? onValueChange(d) : setInternal(d));
25
+ const label = date
26
+ ? new Intl.DateTimeFormat(undefined, { dateStyle: "medium" }).format(date)
27
+ : placeholder;
28
+
29
+ return (
30
+ <Popover>
31
+ <PopoverTrigger asChild>
32
+ <Button
33
+ variant="outline"
34
+ className={cn(
35
+ "w-56 justify-start gap-2 text-start font-normal",
36
+ !date && "text-muted-foreground",
37
+ className,
38
+ )}
39
+ >
40
+ <CalendarIcon className="size-4" />
41
+ {label}
42
+ </Button>
43
+ </PopoverTrigger>
44
+ <PopoverContent className="w-auto p-0" align="start">
45
+ <Calendar mode="single" selected={date} onSelect={setDate} autoFocus />
46
+ </PopoverContent>
47
+ </Popover>
48
+ );
49
+ }
@@ -0,0 +1 @@
1
+ export { DatePicker, type DatePickerProps } from "./date-picker";
@@ -0,0 +1,151 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import { expect, within } from "storybook/test";
4
+ import type { DateRange } from "react-day-picker";
5
+ import { DateRangePicker } from "./date-range-picker";
6
+ import type { DateRangePreset } from "./date-range-picker";
7
+
8
+ const meta = {
9
+ title: "Forms/DateRangePicker",
10
+ component: DateRangePicker,
11
+ tags: ["autodocs"],
12
+ parameters: {
13
+ layout: "centered",
14
+ },
15
+ argTypes: {
16
+ value: {
17
+ description: "Controlled date range `{ from, to }`.",
18
+ control: false,
19
+ table: { category: "State" },
20
+ },
21
+ defaultValue: {
22
+ description: "Uncontrolled initial date range.",
23
+ control: false,
24
+ table: { category: "State" },
25
+ },
26
+ presets: {
27
+ description: "Array of quick-pick preset definitions `{ label, getRange }`.",
28
+ control: false,
29
+ table: { category: "Content" },
30
+ },
31
+ numberOfMonths: {
32
+ description: "Number of calendar months to display.",
33
+ control: "number",
34
+ table: { category: "Appearance" },
35
+ },
36
+ placeholder: {
37
+ description: "Trigger text when no range is selected.",
38
+ control: "text",
39
+ table: { category: "Content" },
40
+ },
41
+ onValueChange: {
42
+ description: "Called when the selected range changes.",
43
+ control: false,
44
+ table: { category: "Behavior" },
45
+ },
46
+ className: {
47
+ description: "Additional CSS classes applied to the trigger button.",
48
+ control: "text",
49
+ table: { category: "Appearance" },
50
+ },
51
+ },
52
+ } satisfies Meta<typeof DateRangePicker>;
53
+
54
+ export default meta;
55
+ type Story = StoryObj<typeof meta>;
56
+
57
+ /** Default two-month picker with no presets. */
58
+ export const Default: Story = {
59
+ // Opens the popover and confirms the range calendar grid is visible.
60
+ play: async ({ canvas, canvasElement, userEvent }) => {
61
+ const trigger = canvas.getByRole("button");
62
+ await userEvent.click(trigger);
63
+ const body = within(canvasElement.ownerDocument.body);
64
+ // Two-month picker → one grid per month; findByRole (singular) throws.
65
+ const grids = await body.findAllByRole("grid");
66
+ await expect(grids[0]).toBeInTheDocument();
67
+ },
68
+ };
69
+
70
+ const commonPresets: DateRangePreset[] = [
71
+ {
72
+ label: "Today",
73
+ getRange: () => {
74
+ const today = new Date();
75
+ return { from: today, to: today };
76
+ },
77
+ },
78
+ {
79
+ label: "Last 7 days",
80
+ getRange: () => {
81
+ const to = new Date();
82
+ const from = new Date();
83
+ from.setDate(from.getDate() - 6);
84
+ return { from, to };
85
+ },
86
+ },
87
+ {
88
+ label: "Last 30 days",
89
+ getRange: () => {
90
+ const to = new Date();
91
+ const from = new Date();
92
+ from.setDate(from.getDate() - 29);
93
+ return { from, to };
94
+ },
95
+ },
96
+ {
97
+ label: "This month",
98
+ getRange: () => {
99
+ const now = new Date();
100
+ const from = new Date(now.getFullYear(), now.getMonth(), 1);
101
+ const to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
102
+ return { from, to };
103
+ },
104
+ },
105
+ ];
106
+
107
+ /** Two-month picker with common quick-pick presets. */
108
+ export const WithPresets: Story = {
109
+ args: {
110
+ presets: commonPresets,
111
+ },
112
+ };
113
+
114
+ /** Single-month layout. */
115
+ export const SingleMonth: Story = {
116
+ args: {
117
+ numberOfMonths: 1,
118
+ },
119
+ };
120
+
121
+ /** Pre-selected range via defaultValue. */
122
+ export const Preselected: Story = {
123
+ args: {
124
+ defaultValue: {
125
+ from: new Date(2025, 0, 6),
126
+ to: new Date(2025, 0, 20),
127
+ },
128
+ },
129
+ };
130
+
131
+ /** Controlled example using useState (defined as a real component to satisfy rules-of-hooks). */
132
+ function ControlledExample() {
133
+ const [range, setRange] = useState<DateRange | undefined>({
134
+ from: new Date(2025, 2, 1),
135
+ to: new Date(2025, 2, 14),
136
+ });
137
+ return (
138
+ <div className="flex flex-col gap-4">
139
+ <DateRangePicker value={range} onValueChange={setRange} />
140
+ <p className="text-body text-muted-foreground">
141
+ {range?.from && range?.to
142
+ ? `Selected: ${range.from.toLocaleDateString()} – ${range.to.toLocaleDateString()}`
143
+ : "No range selected"}
144
+ </p>
145
+ </div>
146
+ );
147
+ }
148
+
149
+ export const Controlled: Story = {
150
+ render: () => <ControlledExample />,
151
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Smoke tests for DateRangePicker trigger rendering.
3
+ *
4
+ * NOTE: Opening the Radix Popover and interacting with the Calendar is NOT
5
+ * tested here — Radix Portals do not render in jsdom reliably. Calendar open/
6
+ * select interaction and accessibility are covered by the Storybook stories
7
+ * (run via `pnpm --filter @elabs-ai/components-docs test-storybook` or the MCP story tests).
8
+ */
9
+ import { describe, expect, it } from "vitest";
10
+ import { render, screen } from "@testing-library/react";
11
+ import { DateRangePicker } from "./date-range-picker";
12
+
13
+ describe("DateRangePicker", () => {
14
+ it("renders the placeholder on the trigger when no range is set", () => {
15
+ render(<DateRangePicker placeholder="Pick a date range" />);
16
+ expect(screen.getByRole("button", { name: /pick a date range/i })).toBeInTheDocument();
17
+ });
18
+
19
+ it("renders a formatted 'from – to' label for a controlled value", () => {
20
+ const from = new Date(2025, 0, 6); // Jan 6 2025
21
+ const to = new Date(2025, 0, 20); // Jan 20 2025
22
+ render(<DateRangePicker value={{ from, to }} />);
23
+
24
+ const button = screen.getByRole("button");
25
+ // The label should contain an EN DASH between the two dates
26
+ expect(button.textContent).toMatch(/–/);
27
+ // Both formatted dates should appear somewhere in the label
28
+ const formatted = new Intl.DateTimeFormat(undefined, { dateStyle: "medium" });
29
+ expect(button.textContent).toContain(formatted.format(from));
30
+ expect(button.textContent).toContain(formatted.format(to));
31
+ });
32
+
33
+ it("renders a partial label with '…' when only 'from' is set", () => {
34
+ const from = new Date(2025, 3, 1); // Apr 1 2025
35
+ render(<DateRangePicker value={{ from, to: undefined }} />);
36
+
37
+ const button = screen.getByRole("button");
38
+ expect(button.textContent).toMatch(/–\s*…/);
39
+ const formatted = new Intl.DateTimeFormat(undefined, { dateStyle: "medium" });
40
+ expect(button.textContent).toContain(formatted.format(from));
41
+ });
42
+
43
+ it("applies the placeholder text-muted-foreground class when no range", () => {
44
+ render(<DateRangePicker />);
45
+ const button = screen.getByRole("button");
46
+ expect(button.className).toContain("text-muted-foreground");
47
+ });
48
+
49
+ it("does not apply text-muted-foreground when a range is set", () => {
50
+ const from = new Date(2025, 0, 1);
51
+ const to = new Date(2025, 0, 15);
52
+ render(<DateRangePicker value={{ from, to }} />);
53
+ const button = screen.getByRole("button");
54
+ expect(button.className).not.toContain("text-muted-foreground");
55
+ });
56
+
57
+ it("merges a custom className onto the trigger button", () => {
58
+ render(<DateRangePicker className="my-custom-class" />);
59
+ const button = screen.getByRole("button");
60
+ expect(button.className).toContain("my-custom-class");
61
+ });
62
+ });
@@ -0,0 +1,126 @@
1
+ // a2ui.exposed: yes
2
+ "use client";
3
+
4
+ import { forwardRef, useState } from "react";
5
+ import { Calendar as CalendarIcon } from "lucide-react";
6
+ import type { DateRange } from "react-day-picker";
7
+ import { cn } from "../../lib/cn";
8
+ import { Button } from "../button";
9
+ import { Calendar } from "../calendar";
10
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
11
+
12
+ export type { DateRange };
13
+
14
+ export interface DateRangePreset {
15
+ label: string;
16
+ getRange: () => DateRange;
17
+ }
18
+
19
+ export interface DateRangePickerProps {
20
+ value?: DateRange;
21
+ defaultValue?: DateRange;
22
+ onValueChange?: (range?: DateRange) => void;
23
+ presets?: DateRangePreset[];
24
+ numberOfMonths?: number;
25
+ placeholder?: string;
26
+ className?: string;
27
+ }
28
+
29
+ const fmt = (date: Date) =>
30
+ new Intl.DateTimeFormat(undefined, { dateStyle: "medium" }).format(date);
31
+
32
+ function formatRange(range: DateRange | undefined, placeholder: string): string {
33
+ if (!range) return placeholder;
34
+ if (range.from && range.to) return `${fmt(range.from)} – ${fmt(range.to)}`;
35
+ if (range.from) return `${fmt(range.from)} – …`;
36
+ return placeholder;
37
+ }
38
+
39
+ /**
40
+ * Date range picker (Popover + Calendar in range mode).
41
+ * Supports optional preset quick-picks, controlled or uncontrolled.
42
+ */
43
+ export const DateRangePicker = forwardRef<HTMLButtonElement, DateRangePickerProps>(
44
+ function DateRangePicker(
45
+ {
46
+ value,
47
+ defaultValue,
48
+ onValueChange,
49
+ presets,
50
+ numberOfMonths = 2,
51
+ placeholder = "Pick a date range",
52
+ className,
53
+ },
54
+ ref,
55
+ ) {
56
+ const [internal, setInternal] = useState<DateRange | undefined>(defaultValue);
57
+
58
+ const isControlled = value !== undefined;
59
+ const range = isControlled ? value : internal;
60
+
61
+ const setRange = (r?: DateRange) => {
62
+ if (!isControlled) {
63
+ setInternal(r);
64
+ }
65
+ // Always fire the callback so uncontrolled consumers can observe changes
66
+ onValueChange?.(r);
67
+ };
68
+
69
+ const hasRange = Boolean(range?.from);
70
+ const label = formatRange(range, placeholder);
71
+
72
+ return (
73
+ <Popover>
74
+ <PopoverTrigger asChild>
75
+ <Button
76
+ ref={ref}
77
+ variant="outline"
78
+ aria-haspopup="dialog"
79
+ className={cn(
80
+ "w-72 justify-start gap-2 text-start font-normal",
81
+ !hasRange && "text-muted-foreground",
82
+ className,
83
+ )}
84
+ >
85
+ <CalendarIcon className="size-4" aria-hidden="true" />
86
+ {label}
87
+ </Button>
88
+ </PopoverTrigger>
89
+ <PopoverContent className="w-auto p-0" align="start">
90
+ {presets && presets.length > 0 ? (
91
+ <div className="flex">
92
+ <div className="flex flex-col gap-1 border-r border-border p-2">
93
+ {presets.map((preset) => (
94
+ <Button
95
+ key={preset.label}
96
+ variant="ghost"
97
+ size="sm"
98
+ className="justify-start"
99
+ onClick={() => setRange(preset.getRange())}
100
+ >
101
+ {preset.label}
102
+ </Button>
103
+ ))}
104
+ </div>
105
+ <Calendar
106
+ mode="range"
107
+ numberOfMonths={numberOfMonths}
108
+ selected={range}
109
+ onSelect={setRange}
110
+ autoFocus
111
+ />
112
+ </div>
113
+ ) : (
114
+ <Calendar
115
+ mode="range"
116
+ numberOfMonths={numberOfMonths}
117
+ selected={range}
118
+ onSelect={setRange}
119
+ autoFocus
120
+ />
121
+ )}
122
+ </PopoverContent>
123
+ </Popover>
124
+ );
125
+ },
126
+ );
@@ -0,0 +1,6 @@
1
+ export {
2
+ DateRangePicker,
3
+ type DateRangePickerProps,
4
+ type DateRangePreset,
5
+ } from "./date-range-picker";
6
+ export type { DateRange } from "react-day-picker";
@@ -0,0 +1,43 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Descriptions, DescriptionsItem } from "./descriptions";
3
+
4
+ const meta = {
5
+ title: "Layout/Descriptions",
6
+ component: Descriptions,
7
+ tags: ["autodocs"],
8
+ argTypes: {
9
+ columns: { control: "inline-radio", options: [1, 2, 3] },
10
+ layout: { control: "inline-radio", options: ["horizontal", "vertical"] },
11
+ },
12
+ } satisfies Meta<typeof Descriptions>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ const items = (
18
+ <>
19
+ <DescriptionsItem label="Name">Ada Lovelace</DescriptionsItem>
20
+ <DescriptionsItem label="Email">ada@example.com</DescriptionsItem>
21
+ <DescriptionsItem label="Role">Maintainer</DescriptionsItem>
22
+ <DescriptionsItem label="Records" numeric>
23
+ 1,284
24
+ </DescriptionsItem>
25
+ <DescriptionsItem label="Status">Active</DescriptionsItem>
26
+ <DescriptionsItem label="Last seen">2 hours ago</DescriptionsItem>
27
+ </>
28
+ );
29
+
30
+ export const Default: Story = {
31
+ args: { columns: 1, layout: "horizontal" },
32
+ render: (args) => <Descriptions {...args}>{items}</Descriptions>,
33
+ };
34
+
35
+ export const TwoColumns: Story = {
36
+ args: { columns: 2, layout: "horizontal" },
37
+ render: (args) => <Descriptions {...args}>{items}</Descriptions>,
38
+ };
39
+
40
+ export const VerticalThreeColumns: Story = {
41
+ args: { columns: 3, layout: "vertical" },
42
+ render: (args) => <Descriptions {...args}>{items}</Descriptions>,
43
+ };
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { Descriptions, DescriptionsItem } from "./descriptions";
4
+
5
+ describe("Descriptions", () => {
6
+ it("renders a definition list with dt/dd pairs", () => {
7
+ render(
8
+ <Descriptions>
9
+ <DescriptionsItem label="Name">Ada</DescriptionsItem>
10
+ <DescriptionsItem label="Role">Maintainer</DescriptionsItem>
11
+ </Descriptions>,
12
+ );
13
+ const term = screen.getByText("Name");
14
+ expect(term.tagName).toBe("DT");
15
+ expect(screen.getAllByRole("term")).toHaveLength(2);
16
+ expect(screen.getByText("Ada").tagName).toBe("DD");
17
+ expect(screen.getByText("Maintainer")).toBeInTheDocument();
18
+ });
19
+
20
+ it("applies tabular-nums to numeric values", () => {
21
+ render(
22
+ <Descriptions>
23
+ <DescriptionsItem label="Records" numeric>
24
+ 1284
25
+ </DescriptionsItem>
26
+ </Descriptions>,
27
+ );
28
+ expect(screen.getByText("1284")).toHaveClass("tabular-nums");
29
+ });
30
+
31
+ it("renders a real <dl> element", () => {
32
+ const { container } = render(
33
+ <Descriptions>
34
+ <DescriptionsItem label="Name">Ada</DescriptionsItem>
35
+ </Descriptions>,
36
+ );
37
+ expect(container.querySelector("dl")).not.toBeNull();
38
+ expect(container.querySelectorAll("dt")).toHaveLength(1);
39
+ expect(container.querySelectorAll("dd")).toHaveLength(1);
40
+ });
41
+ });