@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,156 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { AlertTriangle, CheckCircle2, Info } from "lucide-react";
3
+ import { expect } from "storybook/test";
4
+ import { Alert, AlertDescription, AlertTitle } from "./alert";
5
+ const meta = {
6
+ title: "States/Alert",
7
+ component: Alert,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ variant: {
11
+ description: "Visual tone of the alert banner.",
12
+ control: { type: "select" },
13
+ options: ["default", "info", "success", "warning", "destructive"],
14
+ table: { category: "Appearance" },
15
+ },
16
+ role: {
17
+ description:
18
+ 'ARIA role. Use "alert" for assertive announcements or "group" for panels with focusable controls.',
19
+ control: { type: "select" },
20
+ options: ["alert", "group", "status"],
21
+ table: { category: "Accessibility" },
22
+ },
23
+ className: {
24
+ description: "Additional CSS classes applied to the alert container.",
25
+ control: "text",
26
+ table: { category: "Styling" },
27
+ },
28
+ children: {
29
+ description: "Compose AlertTitle and AlertDescription inside.",
30
+ control: false,
31
+ table: { category: "Content" },
32
+ },
33
+ },
34
+ } satisfies Meta<typeof Alert>;
35
+ export default meta;
36
+ type Story = StoryObj<typeof meta>;
37
+ export const Info_: Story = {
38
+ name: "Info",
39
+ render: () => (
40
+ <Alert variant="info" className="max-w-md">
41
+ <Info />
42
+ <AlertTitle>Heads up</AlertTitle>
43
+ <AlertDescription>This deploy includes a schema migration.</AlertDescription>
44
+ </Alert>
45
+ ),
46
+ };
47
+ export const Destructive: Story = {
48
+ render: () => (
49
+ <Alert variant="destructive" className="max-w-md">
50
+ <AlertTriangle />
51
+ <AlertTitle>Error</AlertTitle>
52
+ <AlertDescription>Your session has expired.</AlertDescription>
53
+ </Alert>
54
+ ),
55
+ };
56
+
57
+ /**
58
+ * `default` and `success` were selectable in the controls panel
59
+ * (`argTypes.variant.options`) but never actually rendered by any story
60
+ * (#388) — so neither one ever reached the blocking interaction + axe job.
61
+ * This renders them alongside `info`/`warning` to close that gap.
62
+ *
63
+ * `destructive` is intentionally NOT repeated here: it already has its own
64
+ * dedicated `Destructive` story above, with a known, separately-tracked
65
+ * contrast exemption (`states-alert--destructive` in
66
+ * scripts/a11y-baseline.json, #381) — folding it into this new story id would
67
+ * launder that pre-existing violation onto a brand-new, unbaselined surface.
68
+ */
69
+ export const AllVariants: Story = {
70
+ render: () => (
71
+ <div className="flex max-w-md flex-col gap-3">
72
+ <Alert variant="default">
73
+ <AlertTitle>Default</AlertTitle>
74
+ <AlertDescription>Neutral tone — no semantic status implied.</AlertDescription>
75
+ </Alert>
76
+ <Alert variant="info">
77
+ <Info />
78
+ <AlertTitle>Info</AlertTitle>
79
+ <AlertDescription>This deploy includes a schema migration.</AlertDescription>
80
+ </Alert>
81
+ <Alert variant="success">
82
+ <CheckCircle2 />
83
+ <AlertTitle>Success</AlertTitle>
84
+ <AlertDescription>Your changes were saved.</AlertDescription>
85
+ </Alert>
86
+ <Alert variant="warning">
87
+ <AlertTriangle />
88
+ <AlertTitle>Warning</AlertTitle>
89
+ <AlertDescription>Double-check before continuing.</AlertDescription>
90
+ </Alert>
91
+ </div>
92
+ ),
93
+ };
94
+
95
+ /**
96
+ * `AlertTitle` defaults to `<h5>` (unchanged) but accepts `as` to render at a
97
+ * different heading level, avoiding a skipped-level jump when the alert sits
98
+ * inline in a form under an `<h2>`/`<h3>` (#329).
99
+ *
100
+ * Uses `variant="warning"`, not `"destructive"` — the destructive variant's
101
+ * text-on-tint contrast is a pre-existing, separately tracked issue
102
+ * (`states-alert--destructive` in scripts/a11y-baseline.json) unrelated to
103
+ * this fix; reusing it here would launder a second, avoidable a11y violation
104
+ * into a brand-new story.
105
+ */
106
+ export const InlineFormError: Story = {
107
+ render: () => (
108
+ <div className="flex max-w-md flex-col gap-3">
109
+ <h2 className="text-title font-semibold text-foreground">Payment details</h2>
110
+ <Alert variant="warning">
111
+ <AlertTriangle />
112
+ <AlertTitle as="h3">Card declined</AlertTitle>
113
+ <AlertDescription>Your card was declined — try another payment method.</AlertDescription>
114
+ </Alert>
115
+ </div>
116
+ ),
117
+ play: async ({ canvas }) => {
118
+ const heading = canvas.getByRole("heading", { level: 3, name: "Card declined" });
119
+ await expect(heading.tagName).toBe("H3");
120
+ },
121
+ };
122
+
123
+ /**
124
+ * `AlertDescription` gains an opt-in `measure` prop that caps genuine prose to
125
+ * a readable line length (`max-w-prose`, ~65ch) instead of running edge to
126
+ * edge in a wide alert (#339). Off by default.
127
+ */
128
+ export const DescriptionMeasure: Story = {
129
+ render: () => (
130
+ <div className="flex w-[900px] flex-col gap-4">
131
+ <Alert variant="info" className="w-full">
132
+ <Info />
133
+ <AlertTitle>Uncapped (default)</AlertTitle>
134
+ <AlertDescription>
135
+ This migration note runs the full width of a very wide alert banner, which makes it
136
+ materially harder to scan once the line gets long enough for the eye to lose its place
137
+ between lines.
138
+ </AlertDescription>
139
+ </Alert>
140
+ <Alert variant="info" className="w-full">
141
+ <Info />
142
+ <AlertTitle>Capped via `measure`</AlertTitle>
143
+ <AlertDescription measure>
144
+ This migration note is capped to a readable prose measure even inside a very wide banner,
145
+ so each line stays short enough to read comfortably.
146
+ </AlertDescription>
147
+ </Alert>
148
+ </div>
149
+ ),
150
+ play: async ({ canvas }) => {
151
+ const capped = canvas.getByText(/capped to a readable prose measure/);
152
+ await expect(capped).toHaveClass("max-w-prose");
153
+ const uncapped = canvas.getByText(/runs the full width/);
154
+ await expect(uncapped).not.toHaveClass("max-w-prose");
155
+ },
156
+ };
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { Alert, AlertDescription, AlertTitle } from "./alert";
4
+ describe("Alert", () => {
5
+ it("renders with role=alert and a title", () => {
6
+ render(
7
+ <Alert>
8
+ <AlertTitle>Heads up</AlertTitle>
9
+ </Alert>,
10
+ );
11
+ expect(screen.getByRole("alert")).toBeInTheDocument();
12
+ expect(screen.getByText("Heads up")).toBeInTheDocument();
13
+ });
14
+
15
+ // --- AlertTitle `as` seam + ref-type fix (#329) ---
16
+ it("AlertTitle defaults to <h5> (byte-identical to pre-#329 output)", () => {
17
+ render(<AlertTitle>Default level</AlertTitle>);
18
+ const heading = screen.getByRole("heading", { level: 5, name: "Default level" });
19
+ expect(heading.tagName).toBe("H5");
20
+ });
21
+
22
+ it("AlertTitle renders the requested heading level via `as` (#329)", () => {
23
+ render(<AlertTitle as="h3">Inline error</AlertTitle>);
24
+ const heading = screen.getByRole("heading", { level: 3, name: "Inline error" });
25
+ expect(heading.tagName).toBe("H3");
26
+ expect(heading).toHaveClass("mb-1", "font-medium", "leading-none", "tracking-tight");
27
+ });
28
+
29
+ // --- AlertDescription `measure` cap (#339) ---
30
+ it("AlertDescription has no max-w cap by default (#339)", () => {
31
+ render(<AlertDescription>Short note</AlertDescription>);
32
+ expect(screen.getByText("Short note")).not.toHaveClass("max-w-prose");
33
+ });
34
+
35
+ it("AlertDescription caps to a readable measure when `measure` is set (#339)", () => {
36
+ render(
37
+ <AlertDescription measure>
38
+ Genuine prose that should not run edge to edge in a wide alert.
39
+ </AlertDescription>,
40
+ );
41
+ const el = screen.getByText(/Genuine prose/);
42
+ expect(el).toHaveClass("max-w-prose");
43
+ expect(el).toHaveClass("text-muted-foreground");
44
+ });
45
+
46
+ // role override seam (#191, research 11 §B.3): a pending decision with
47
+ // focusable controls must be a labelled region, not an assertive live region.
48
+ it("accepts a role override (default stays alert)", () => {
49
+ render(
50
+ <Alert aria-label="Pending decision" role="group">
51
+ <AlertTitle>Approve this?</AlertTitle>
52
+ </Alert>,
53
+ );
54
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
55
+ expect(screen.getByRole("group", { name: "Pending decision" })).toBeInTheDocument();
56
+ });
57
+ });
@@ -0,0 +1,91 @@
1
+ import { forwardRef, type HTMLAttributes } from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "../../lib/cn";
4
+
5
+ export const alertVariants = cva(
6
+ "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg]:absolute [&>svg]:start-4 [&>svg]:top-4 [&>svg]:size-4 [&>svg]:text-current [&>svg~*]:ps-7",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-card text-card-foreground",
11
+ info: "border-info/40 bg-info/10 text-foreground",
12
+ success: "border-success/40 bg-success/10 text-foreground",
13
+ warning: "border-warning/50 bg-warning/10 text-foreground",
14
+ destructive:
15
+ "border-destructive/40 bg-destructive/10 text-destructive [&>svg]:text-destructive",
16
+ },
17
+ },
18
+ defaultVariants: { variant: "default" },
19
+ },
20
+ );
21
+
22
+ export const Alert = forwardRef<
23
+ HTMLDivElement,
24
+ HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(function Alert(
26
+ // `role` is an explicit override seam (default "alert"): a PENDING decision
27
+ // containing focusable controls (e.g. @elabs-ai/components-ai ApprovalCard) must be a
28
+ // labelled region (`role="group"` + aria-labelledby), not an assertive live
29
+ // region (research/structural-design/11 §B.3).
30
+ { className, variant, role = "alert", ...props },
31
+ ref,
32
+ ) {
33
+ return (
34
+ <div ref={ref} role={role} className={cn(alertVariants({ variant }), className)} {...props} />
35
+ );
36
+ });
37
+
38
+ export interface AlertTitleProps extends HTMLAttributes<HTMLHeadingElement> {
39
+ /**
40
+ * Render at a different heading level to fit the surrounding document
41
+ * outline (#329). The visual (`mb-1 font-medium leading-none
42
+ * tracking-tight`) is identical at every level. @default "h5"
43
+ */
44
+ as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
45
+ }
46
+
47
+ // Ref type matches the rendered element (`HTMLHeadingElement` for every `as`
48
+ // value) — previously mismatched to `HTMLParagraphElement` (#329).
49
+ export const AlertTitle = forwardRef<HTMLHeadingElement, AlertTitleProps>(function AlertTitle(
50
+ { className, as: Tag = "h5", ...props },
51
+ ref,
52
+ ) {
53
+ return (
54
+ <Tag
55
+ ref={ref}
56
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
57
+ {...props}
58
+ />
59
+ );
60
+ });
61
+
62
+ export interface AlertDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {
63
+ /**
64
+ * Cap the line length to a readable prose measure (`max-w-prose`, ~65ch)
65
+ * for genuine paragraph content (#339). Off by default — short,
66
+ * label-style descriptions should stay full width.
67
+ */
68
+ measure?: boolean;
69
+ }
70
+
71
+ // NOTE (#329 AC): checked for the same ref-type mismatch as `AlertTitle` —
72
+ // `AlertDescription` renders a `<div>`, not a heading, so it needs no `as`
73
+ // heading seam. Its own generic (`HTMLParagraphElement` on a rendered `<div>`)
74
+ // is a pre-existing, unrelated mismatch; left as-is (out of scope for #329,
75
+ // which is scoped to heading semantics, and not requested by #339 either) to
76
+ // avoid a ref-type change unrelated to either issue's acceptance criteria.
77
+ export const AlertDescription = forwardRef<HTMLParagraphElement, AlertDescriptionProps>(
78
+ function AlertDescription({ className, measure, ...props }, ref) {
79
+ return (
80
+ <div
81
+ ref={ref}
82
+ className={cn(
83
+ "text-sm [&_p]:leading-relaxed text-muted-foreground",
84
+ measure && "max-w-prose",
85
+ className,
86
+ )}
87
+ {...props}
88
+ />
89
+ );
90
+ },
91
+ );
@@ -0,0 +1,8 @@
1
+ export {
2
+ Alert,
3
+ AlertTitle,
4
+ type AlertTitleProps,
5
+ AlertDescription,
6
+ type AlertDescriptionProps,
7
+ alertVariants,
8
+ } from "./alert";
@@ -0,0 +1,73 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, waitFor, within } from "storybook/test";
3
+ import { Button } from "../button";
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogAction,
7
+ AlertDialogCancel,
8
+ AlertDialogContent,
9
+ AlertDialogDescription,
10
+ AlertDialogFooter,
11
+ AlertDialogHeader,
12
+ AlertDialogTitle,
13
+ AlertDialogTrigger,
14
+ } from "./alert-dialog";
15
+ const meta = {
16
+ title: "Overlays/AlertDialog",
17
+ component: AlertDialog,
18
+ tags: ["autodocs"],
19
+ argTypes: {
20
+ open: {
21
+ description: "Controlled open state of the dialog.",
22
+ control: "boolean",
23
+ table: { category: "State" },
24
+ },
25
+ defaultOpen: {
26
+ description: "Uncontrolled initial open state.",
27
+ control: "boolean",
28
+ table: { category: "State" },
29
+ },
30
+ onOpenChange: {
31
+ description: "Callback when the open state changes.",
32
+ control: false,
33
+ table: { category: "Events" },
34
+ },
35
+ },
36
+ } satisfies Meta<typeof AlertDialog>;
37
+ export default meta;
38
+ type Story = StoryObj<typeof meta>;
39
+ export const Default: Story = {
40
+ render: () => (
41
+ <AlertDialog>
42
+ <AlertDialogTrigger asChild>
43
+ <Button variant="destructive">Delete</Button>
44
+ </AlertDialogTrigger>
45
+ <AlertDialogContent>
46
+ <AlertDialogHeader>
47
+ <AlertDialogTitle>Are you sure?</AlertDialogTitle>
48
+ <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
49
+ </AlertDialogHeader>
50
+ <AlertDialogFooter>
51
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
52
+ <AlertDialogAction>Delete</AlertDialogAction>
53
+ </AlertDialogFooter>
54
+ </AlertDialogContent>
55
+ </AlertDialog>
56
+ ),
57
+ // Opens the alert dialog (portalled to body), asserts the title and both action
58
+ // buttons are visible, then cancels to close it.
59
+ play: async ({ canvas, canvasElement, userEvent }) => {
60
+ await userEvent.click(canvas.getByRole("button", { name: /delete/i }));
61
+ const body = within(canvasElement.ownerDocument.body);
62
+ const dialog = await body.findByRole("alertdialog");
63
+ await waitFor(() => expect(within(dialog).getByText("Are you sure?")).toBeVisible());
64
+ await waitFor(() =>
65
+ expect(within(dialog).getByRole("button", { name: /cancel/i })).toBeVisible(),
66
+ );
67
+ await waitFor(() =>
68
+ expect(within(dialog).getByRole("button", { name: /delete/i })).toBeVisible(),
69
+ );
70
+ // Cancel dismisses the dialog
71
+ await userEvent.click(within(dialog).getByRole("button", { name: /cancel/i }));
72
+ },
73
+ };
@@ -0,0 +1,104 @@
1
+ import {
2
+ forwardRef,
3
+ type ComponentPropsWithoutRef,
4
+ type ElementRef,
5
+ type HTMLAttributes,
6
+ } from "react";
7
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
8
+ import { cn } from "../../lib/cn";
9
+ import { buttonVariants } from "../button";
10
+
11
+ export const AlertDialog = AlertDialogPrimitive.Root;
12
+ export const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
13
+ export const AlertDialogPortal = AlertDialogPrimitive.Portal;
14
+
15
+ const AlertDialogOverlay = forwardRef<
16
+ ElementRef<typeof AlertDialogPrimitive.Overlay>,
17
+ ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
18
+ >(function AlertDialogOverlay({ className, ...props }, ref) {
19
+ return (
20
+ <AlertDialogPrimitive.Overlay
21
+ ref={ref}
22
+ className={cn(
23
+ "fixed inset-0 z-50 bg-foreground/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
24
+ className,
25
+ )}
26
+ {...props}
27
+ />
28
+ );
29
+ });
30
+
31
+ export const AlertDialogContent = forwardRef<
32
+ ElementRef<typeof AlertDialogPrimitive.Content>,
33
+ ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
34
+ >(function AlertDialogContent({ className, ...props }, ref) {
35
+ return (
36
+ <AlertDialogPortal>
37
+ <AlertDialogOverlay />
38
+ <AlertDialogPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ "fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-card p-6 text-card-foreground shadow-ring-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ </AlertDialogPortal>
47
+ );
48
+ });
49
+
50
+ export function AlertDialogHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
51
+ return <div className={cn("flex flex-col gap-1.5 text-start", className)} {...props} />;
52
+ }
53
+ export function AlertDialogFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
54
+ return (
55
+ <div
56
+ className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
57
+ {...props}
58
+ />
59
+ );
60
+ }
61
+ export const AlertDialogTitle = forwardRef<
62
+ ElementRef<typeof AlertDialogPrimitive.Title>,
63
+ ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
64
+ >(function AlertDialogTitle({ className, ...props }, ref) {
65
+ return (
66
+ <AlertDialogPrimitive.Title
67
+ ref={ref}
68
+ className={cn("text-lg font-semibold", className)}
69
+ {...props}
70
+ />
71
+ );
72
+ });
73
+ export const AlertDialogDescription = forwardRef<
74
+ ElementRef<typeof AlertDialogPrimitive.Description>,
75
+ ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
76
+ >(function AlertDialogDescription({ className, ...props }, ref) {
77
+ return (
78
+ <AlertDialogPrimitive.Description
79
+ ref={ref}
80
+ className={cn("text-sm text-muted-foreground", className)}
81
+ {...props}
82
+ />
83
+ );
84
+ });
85
+ export const AlertDialogAction = forwardRef<
86
+ ElementRef<typeof AlertDialogPrimitive.Action>,
87
+ ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
88
+ >(function AlertDialogAction({ className, ...props }, ref) {
89
+ return (
90
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
91
+ );
92
+ });
93
+ export const AlertDialogCancel = forwardRef<
94
+ ElementRef<typeof AlertDialogPrimitive.Cancel>,
95
+ ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
96
+ >(function AlertDialogCancel({ className, ...props }, ref) {
97
+ return (
98
+ <AlertDialogPrimitive.Cancel
99
+ ref={ref}
100
+ className={cn(buttonVariants({ variant: "outline" }), className)}
101
+ {...props}
102
+ />
103
+ );
104
+ });
@@ -0,0 +1,12 @@
1
+ export {
2
+ AlertDialog,
3
+ AlertDialogTrigger,
4
+ AlertDialogPortal,
5
+ AlertDialogContent,
6
+ AlertDialogHeader,
7
+ AlertDialogFooter,
8
+ AlertDialogTitle,
9
+ AlertDialogDescription,
10
+ AlertDialogAction,
11
+ AlertDialogCancel,
12
+ } from "./alert-dialog";
@@ -0,0 +1,61 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { AppShell } from "./app-shell";
3
+ import { TopNav } from "../top-nav";
4
+ import { PageShell } from "../page-shell";
5
+ import { SectionHeader } from "../section-header";
6
+ import { Button } from "../button";
7
+
8
+ const meta = {
9
+ title: "Layout/App Shell/Basic",
10
+ component: AppShell,
11
+ parameters: { layout: "fullscreen" },
12
+ } satisfies Meta<typeof AppShell>;
13
+ export default meta;
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ const nav = ["Dashboard", "Projects", "Reports", "Settings"];
17
+
18
+ export const Default: Story = {
19
+ render: () => (
20
+ <div className="h-[600px]">
21
+ <AppShell
22
+ sidebar={
23
+ <nav className="flex h-full w-60 flex-col gap-1 border-e border-sidebar-border bg-sidebar p-3 text-sidebar-foreground">
24
+ <div className="px-2 pb-3 font-semibold">Brand UI</div>
25
+ {nav.map((n, i) => (
26
+ <button
27
+ key={n}
28
+ className={
29
+ "rounded-md px-3 py-2 text-start text-body font-medium " +
30
+ (i === 0
31
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
32
+ : "text-sidebar-foreground/80 hover:bg-sidebar-accent/60")
33
+ }
34
+ >
35
+ {n}
36
+ </button>
37
+ ))}
38
+ </nav>
39
+ }
40
+ topNav={
41
+ <TopNav end={<Button size="sm">Invite</Button>}>
42
+ <span className="text-body font-medium">Dashboard</span>
43
+ </TopNav>
44
+ }
45
+ >
46
+ <PageShell
47
+ header={
48
+ <SectionHeader
49
+ title="Overview"
50
+ description="A lightweight generic shell. For full sidebar behavior use the Sidebar primitive."
51
+ />
52
+ }
53
+ >
54
+ <div className="rounded-xl border bg-card p-6 text-body text-muted-foreground">
55
+ Main content area.
56
+ </div>
57
+ </PageShell>
58
+ </AppShell>
59
+ </div>
60
+ ),
61
+ };
@@ -0,0 +1,31 @@
1
+ import { type ReactNode } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export interface AppShellProps {
5
+ /** Persistent left navigation (often a <Sidebar />). */
6
+ sidebar?: ReactNode;
7
+ /** Top bar (often a <TopNav />). */
8
+ topNav?: ReactNode;
9
+ children: ReactNode;
10
+ className?: string;
11
+ /** Class for the scrolling main content region. */
12
+ mainClassName?: string;
13
+ }
14
+
15
+ /**
16
+ * App-first layout: fixed-height viewport with an optional sidebar, a top bar,
17
+ * and a scrollable main region. Pure layout — bring your own nav components.
18
+ */
19
+ export function AppShell({ sidebar, topNav, children, className, mainClassName }: AppShellProps) {
20
+ return (
21
+ <div
22
+ className={cn("flex h-dvh w-full overflow-hidden bg-background text-foreground", className)}
23
+ >
24
+ {sidebar ? <div className="hidden md:flex">{sidebar}</div> : null}
25
+ <div className="flex min-w-0 flex-1 flex-col">
26
+ {topNav}
27
+ <main className={cn("min-h-0 flex-1 overflow-y-auto", mainClassName)}>{children}</main>
28
+ </div>
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1 @@
1
+ export { AppShell, type AppShellProps } from "./app-shell";