@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,14 @@
1
+ export {
2
+ Toolbar,
3
+ ToolbarButton,
4
+ ToolbarSeparator,
5
+ ToolbarSlot,
6
+ ToolbarToggleGroup,
7
+ ToolbarToggleItem,
8
+ toolbarVariants,
9
+ type ToolbarButtonProps,
10
+ type ToolbarProps,
11
+ type ToolbarSeparatorProps,
12
+ type ToolbarToggleGroupProps,
13
+ type ToolbarToggleItemProps,
14
+ } from "./toolbar";
@@ -0,0 +1,126 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent, within } from "storybook/test";
3
+ import {
4
+ AlignCenterIcon,
5
+ AlignLeftIcon,
6
+ AlignRightIcon,
7
+ BoldIcon,
8
+ ItalicIcon,
9
+ Redo2Icon,
10
+ Undo2Icon,
11
+ } from "lucide-react";
12
+
13
+ import {
14
+ Toolbar,
15
+ ToolbarButton,
16
+ ToolbarSeparator,
17
+ ToolbarToggleGroup,
18
+ ToolbarToggleItem,
19
+ } from "./toolbar";
20
+
21
+ /**
22
+ * A dense row of controls that acts on the content beside it — and the only row
23
+ * in the system that claims `role="toolbar"`, because it is the only one that
24
+ * keeps the role's promise: **one tab stop, arrow keys move between controls**.
25
+ *
26
+ * For the ordinary control row above a list or table, reach for `ViewToolbar`
27
+ * instead — there every control is its own tab stop, which is what readers
28
+ * expect there.
29
+ */
30
+ const meta = {
31
+ title: "Foundation/Toolbar",
32
+ component: Toolbar,
33
+ tags: ["autodocs"],
34
+ parameters: { layout: "padded" },
35
+ args: { "aria-label": "Formatting" },
36
+ } satisfies Meta<typeof Toolbar>;
37
+
38
+ export default meta;
39
+ type Story = StoryObj<typeof meta>;
40
+
41
+ export const Default: Story = {
42
+ render: (args) => (
43
+ <Toolbar {...args}>
44
+ <ToolbarButton aria-label="Undo" size="icon-sm">
45
+ <Undo2Icon aria-hidden="true" />
46
+ </ToolbarButton>
47
+ <ToolbarButton aria-label="Redo" size="icon-sm">
48
+ <Redo2Icon aria-hidden="true" />
49
+ </ToolbarButton>
50
+ <ToolbarSeparator />
51
+ <ToolbarToggleGroup type="multiple" aria-label="Text style">
52
+ <ToolbarToggleItem value="bold" aria-label="Bold">
53
+ <BoldIcon aria-hidden="true" />
54
+ </ToolbarToggleItem>
55
+ <ToolbarToggleItem value="italic" aria-label="Italic">
56
+ <ItalicIcon aria-hidden="true" />
57
+ </ToolbarToggleItem>
58
+ </ToolbarToggleGroup>
59
+ <ToolbarSeparator />
60
+ <ToolbarToggleGroup type="single" defaultValue="left" aria-label="Alignment">
61
+ <ToolbarToggleItem value="left" aria-label="Align left">
62
+ <AlignLeftIcon aria-hidden="true" />
63
+ </ToolbarToggleItem>
64
+ <ToolbarToggleItem value="center" aria-label="Align centre">
65
+ <AlignCenterIcon aria-hidden="true" />
66
+ </ToolbarToggleItem>
67
+ <ToolbarToggleItem value="right" aria-label="Align right">
68
+ <AlignRightIcon aria-hidden="true" />
69
+ </ToolbarToggleItem>
70
+ </ToolbarToggleGroup>
71
+ <span className="flex-1" />
72
+ <ToolbarButton variant="outline">Publish</ToolbarButton>
73
+ </Toolbar>
74
+ ),
75
+ };
76
+
77
+ /**
78
+ * The behaviour the role promises, exercised: Tab reaches the toolbar ONCE, and
79
+ * the arrow keys walk it. A row that claims `role="toolbar"` without this tells
80
+ * a screen-reader user to press keys that do nothing.
81
+ */
82
+ export const KeyboardNavigation: Story = {
83
+ ...Default,
84
+ play: async ({ canvasElement }) => {
85
+ const canvas = within(canvasElement);
86
+ const toolbar = canvas.getByRole("toolbar", { name: "Formatting" });
87
+
88
+ await userEvent.tab();
89
+ const undo = canvas.getByRole("button", { name: "Undo" });
90
+ await expect(undo).toHaveFocus();
91
+
92
+ await userEvent.keyboard("{ArrowRight}");
93
+ await expect(canvas.getByRole("button", { name: "Redo" })).toHaveFocus();
94
+
95
+ // Out of the toolbar in one Tab — the whole row is a single stop.
96
+ await userEvent.tab();
97
+ await expect(toolbar.contains(document.activeElement)).toBe(false);
98
+ },
99
+ };
100
+
101
+ /** A rail beside the content it acts on. Arrow keys follow the orientation. */
102
+ export const Vertical: Story = {
103
+ args: { "aria-label": "Document", orientation: "vertical" },
104
+ render: (args) => (
105
+ <Toolbar {...args} className="w-fit">
106
+ <ToolbarButton aria-label="Undo" size="icon-sm">
107
+ <Undo2Icon aria-hidden="true" />
108
+ </ToolbarButton>
109
+ <ToolbarButton aria-label="Redo" size="icon-sm">
110
+ <Redo2Icon aria-hidden="true" />
111
+ </ToolbarButton>
112
+ <ToolbarSeparator />
113
+ <ToolbarToggleGroup type="multiple" aria-label="Text style">
114
+ <ToolbarToggleItem value="bold" aria-label="Bold">
115
+ <BoldIcon aria-hidden="true" />
116
+ </ToolbarToggleItem>
117
+ </ToolbarToggleGroup>
118
+ </Toolbar>
119
+ ),
120
+ };
121
+
122
+ /** Explicitly horizontal — the default, spelled out. */
123
+ export const Horizontal: Story = {
124
+ ...Default,
125
+ args: { "aria-label": "Formatting", orientation: "horizontal" },
126
+ };
@@ -0,0 +1,73 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ import { Toolbar, ToolbarButton, ToolbarSeparator } from "./toolbar";
6
+
7
+ function Fixture() {
8
+ return (
9
+ <>
10
+ <button type="button">before</button>
11
+ <Toolbar aria-label="Document">
12
+ <ToolbarButton>First</ToolbarButton>
13
+ <ToolbarSeparator />
14
+ <ToolbarButton>Second</ToolbarButton>
15
+ <ToolbarButton>Third</ToolbarButton>
16
+ </Toolbar>
17
+ <button type="button">after</button>
18
+ </>
19
+ );
20
+ }
21
+
22
+ describe("Toolbar", () => {
23
+ it("is a named toolbar", () => {
24
+ render(<Fixture />);
25
+ expect(screen.getByRole("toolbar", { name: "Document" })).toBeInTheDocument();
26
+ });
27
+
28
+ it("is ONE tab stop — the promise the role makes", async () => {
29
+ render(<Fixture />);
30
+
31
+ await userEvent.tab();
32
+ expect(screen.getByRole("button", { name: "before" })).toHaveFocus();
33
+
34
+ await userEvent.tab();
35
+ expect(screen.getByRole("button", { name: "First" })).toHaveFocus();
36
+
37
+ // Not "Second": the whole row is a single stop, so Tab leaves it.
38
+ await userEvent.tab();
39
+ expect(screen.getByRole("button", { name: "after" })).toHaveFocus();
40
+ });
41
+
42
+ it("moves between controls with the arrow keys", async () => {
43
+ render(<Fixture />);
44
+
45
+ await userEvent.tab();
46
+ await userEvent.tab();
47
+ await userEvent.keyboard("{ArrowRight}");
48
+ expect(screen.getByRole("button", { name: "Second" })).toHaveFocus();
49
+
50
+ await userEvent.keyboard("{ArrowLeft}");
51
+ expect(screen.getByRole("button", { name: "First" })).toHaveFocus();
52
+ });
53
+
54
+ it("returns focus to the control the user left, not to the first one", async () => {
55
+ render(<Fixture />);
56
+
57
+ await userEvent.tab();
58
+ await userEvent.tab();
59
+ await userEvent.keyboard("{ArrowRight}{ArrowRight}");
60
+ expect(screen.getByRole("button", { name: "Third" })).toHaveFocus();
61
+
62
+ await userEvent.tab();
63
+ await userEvent.tab({ shift: true });
64
+ expect(screen.getByRole("button", { name: "Third" })).toHaveFocus();
65
+ });
66
+
67
+ it("keeps the separator out of the accessibility tree's control list", () => {
68
+ render(<Fixture />);
69
+ // Radix renders it as a real separator, not a focusable child.
70
+ expect(screen.getByRole("separator")).toBeInTheDocument();
71
+ expect(screen.getAllByRole("button")).toHaveLength(5);
72
+ });
73
+ });
@@ -0,0 +1,177 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Toolbar — the ONE row that genuinely claims `role="toolbar"`.
5
+ *
6
+ * The role is a promise: a toolbar is a SINGLE tab stop, and the arrow keys move
7
+ * between its controls (WAI-ARIA APG). Four rows in this repo claim the role
8
+ * without implementing that, which is worse than not claiming it — a screen
9
+ * reader tells the user to press the arrow keys and nothing happens. This
10
+ * component delegates the roving tabindex to `@radix-ui/react-toolbar` so the
11
+ * promise is kept, and exists so no one has to hand-roll it again.
12
+ *
13
+ * When NOT to use it. A row of ordinary controls above a list or table is a
14
+ * `ViewToolbar` — that component deliberately does not claim the role, because
15
+ * every control there is an ordinary tab stop and readers expect Tab, not
16
+ * arrows. Reach for `Toolbar` when the row is DENSE and SECONDARY to the content
17
+ * it acts on (a document's page/zoom controls, a formatting bar): collapsing a
18
+ * dozen controls into one tab stop is what makes the content reachable again.
19
+ *
20
+ * Anatomy — a shell plus three composable parts. There is no `actions` prop and
21
+ * no items array: a toolbar's layout is the caller's, so groups are arranged
22
+ * with ordinary flex utilities and `ToolbarSeparator` between them.
23
+ */
24
+
25
+ import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
26
+ import { cva, type VariantProps } from "class-variance-authority";
27
+ import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "react";
28
+
29
+ import { cn } from "../../lib/cn";
30
+ import { buttonVariants } from "../button";
31
+ import { toggleVariants } from "../toggle";
32
+
33
+ /* -------------------------------------------------------------------------- */
34
+ /* Root */
35
+ /* -------------------------------------------------------------------------- */
36
+
37
+ export const toolbarVariants = cva("flex items-center gap-1", {
38
+ variants: {
39
+ orientation: {
40
+ horizontal: "flex-row",
41
+ // Radix already writes `aria-orientation`; this only lays it out.
42
+ vertical: "h-full flex-col",
43
+ },
44
+ },
45
+ defaultVariants: { orientation: "horizontal" },
46
+ });
47
+
48
+ export interface ToolbarProps
49
+ extends
50
+ Omit<ComponentPropsWithoutRef<typeof ToolbarPrimitive.Root>, "orientation">,
51
+ VariantProps<typeof toolbarVariants> {
52
+ /**
53
+ * What this toolbar acts on ("Document", "Formatting"). A toolbar is one tab
54
+ * stop with several controls inside it, so without a name a screen-reader
55
+ * user lands on an anonymous group — hence required, not optional.
56
+ */
57
+ "aria-label": string;
58
+ }
59
+
60
+ /**
61
+ * The row shell. Renders no surface of its own: a toolbar sits ON something
62
+ * (a card header, a document frame) and inherits that surface, so painting a
63
+ * background here would double the fill wherever it is placed.
64
+ */
65
+ export const Toolbar = forwardRef<ElementRef<typeof ToolbarPrimitive.Root>, ToolbarProps>(
66
+ function Toolbar({ className, orientation, ...props }, ref) {
67
+ return (
68
+ <ToolbarPrimitive.Root
69
+ ref={ref}
70
+ data-slot="toolbar"
71
+ orientation={orientation ?? "horizontal"}
72
+ className={cn(toolbarVariants({ orientation }), className)}
73
+ {...props}
74
+ />
75
+ );
76
+ },
77
+ );
78
+
79
+ /* -------------------------------------------------------------------------- */
80
+ /* Parts */
81
+ /* -------------------------------------------------------------------------- */
82
+
83
+ export interface ToolbarButtonProps
84
+ extends
85
+ ComponentPropsWithoutRef<typeof ToolbarPrimitive.Button>,
86
+ Pick<VariantProps<typeof buttonVariants>, "variant" | "size"> {}
87
+
88
+ /**
89
+ * A control inside the toolbar. Styled with `buttonVariants` — the same ink,
90
+ * heights and focus ring as every other button in the system, not a parallel
91
+ * set. Icon-only controls take `size="icon-sm"` and MUST carry an `aria-label`;
92
+ * wrap in a `Tooltip` when the glyph needs a name on screen too.
93
+ */
94
+ export const ToolbarButton = forwardRef<
95
+ ElementRef<typeof ToolbarPrimitive.Button>,
96
+ ToolbarButtonProps
97
+ >(function ToolbarButton({ className, variant = "ghost", size = "sm", ...props }, ref) {
98
+ return (
99
+ <ToolbarPrimitive.Button
100
+ ref={ref}
101
+ data-slot="toolbar-button"
102
+ className={cn(buttonVariants({ variant, size }), className)}
103
+ {...props}
104
+ />
105
+ );
106
+ });
107
+
108
+ export type ToolbarSeparatorProps = ComponentPropsWithoutRef<typeof ToolbarPrimitive.Separator>;
109
+
110
+ /**
111
+ * The divider between two groups of controls. It is the ONLY cue between two
112
+ * same-fill regions of the row, so it takes the strong rung (WCAG 1.4.11,
113
+ * ADR 0010) — `Separator`'s subtle default would be a decorative hairline here.
114
+ */
115
+ export const ToolbarSeparator = forwardRef<
116
+ ElementRef<typeof ToolbarPrimitive.Separator>,
117
+ ToolbarSeparatorProps
118
+ >(function ToolbarSeparator({ className, ...props }, ref) {
119
+ return (
120
+ <ToolbarPrimitive.Separator
121
+ ref={ref}
122
+ data-slot="toolbar-separator"
123
+ className={cn(
124
+ "bg-border-strong mx-1 shrink-0",
125
+ "data-[orientation=horizontal]:h-5 data-[orientation=horizontal]:w-px",
126
+ "data-[orientation=vertical]:h-px data-[orientation=vertical]:w-5",
127
+ className,
128
+ )}
129
+ {...props}
130
+ />
131
+ );
132
+ });
133
+
134
+ export type ToolbarToggleGroupProps = ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleGroup>;
135
+
136
+ /** A set of mutually-exclusive or multi-select toggles (a view switcher, bold/italic). */
137
+ export const ToolbarToggleGroup = forwardRef<
138
+ ElementRef<typeof ToolbarPrimitive.ToggleGroup>,
139
+ ToolbarToggleGroupProps
140
+ >(function ToolbarToggleGroup({ className, ...props }, ref) {
141
+ return (
142
+ <ToolbarPrimitive.ToggleGroup
143
+ ref={ref}
144
+ data-slot="toolbar-toggle-group"
145
+ className={cn("flex items-center gap-1", className)}
146
+ {...props}
147
+ />
148
+ );
149
+ });
150
+
151
+ export interface ToolbarToggleItemProps
152
+ extends
153
+ ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleItem>,
154
+ Pick<VariantProps<typeof toggleVariants>, "size"> {}
155
+
156
+ /** One toggle. Shares `Toggle`'s pressed styling, so state reads the same everywhere. */
157
+ export const ToolbarToggleItem = forwardRef<
158
+ ElementRef<typeof ToolbarPrimitive.ToggleItem>,
159
+ ToolbarToggleItemProps
160
+ >(function ToolbarToggleItem({ className, size = "sm", ...props }, ref) {
161
+ return (
162
+ <ToolbarPrimitive.ToggleItem
163
+ ref={ref}
164
+ data-slot="toolbar-toggle-item"
165
+ className={cn(toggleVariants({ size }), className)}
166
+ {...props}
167
+ />
168
+ );
169
+ });
170
+
171
+ /**
172
+ * An escape hatch for anything the parts above do not cover — a `Select`, an
173
+ * input, a `DropdownMenu` trigger. `asChild` hands the roving-tabindex wiring to
174
+ * whatever you render, so the control stays part of the arrow-key sequence
175
+ * instead of becoming a second tab stop inside a single-tab-stop row.
176
+ */
177
+ export const ToolbarSlot = ToolbarPrimitive.Button;
@@ -0,0 +1 @@
1
+ export { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from "./tooltip";
@@ -0,0 +1,67 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, waitFor, within } from "storybook/test";
3
+ import { Button } from "../button";
4
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
5
+
6
+ const meta = {
7
+ title: "Overlays/Tooltip",
8
+ component: Tooltip,
9
+ tags: ["autodocs"],
10
+ argTypes: {
11
+ open: {
12
+ description: "Controlled open state of the tooltip.",
13
+ control: "boolean",
14
+ table: { category: "State" },
15
+ },
16
+ defaultOpen: {
17
+ description: "Uncontrolled initial open state.",
18
+ control: "boolean",
19
+ table: { category: "State" },
20
+ },
21
+ onOpenChange: {
22
+ description: "Callback when the open state changes.",
23
+ control: false,
24
+ table: { category: "Events" },
25
+ },
26
+ delayDuration: {
27
+ description: "Milliseconds to wait before opening. Default 700.",
28
+ control: "number",
29
+ table: { category: "Behaviour" },
30
+ },
31
+ disableHoverableContent: {
32
+ description: "Prevent the tooltip content from being hoverable.",
33
+ control: "boolean",
34
+ table: { category: "Behaviour" },
35
+ },
36
+ },
37
+ } satisfies Meta<typeof Tooltip>;
38
+ export default meta;
39
+ type Story = StoryObj<typeof meta>;
40
+
41
+ export const Default: Story = {
42
+ render: () => (
43
+ <TooltipProvider delayDuration={0}>
44
+ <Tooltip>
45
+ <TooltipTrigger asChild>
46
+ <Button variant="outline">Hover me</Button>
47
+ </TooltipTrigger>
48
+ <TooltipContent>Helpful context</TooltipContent>
49
+ </Tooltip>
50
+ </TooltipProvider>
51
+ ),
52
+ // Hovers over the trigger (delay zeroed for determinism) to open the portalled
53
+ // tooltip and confirms the content is visible. NOTE: Radix renders the visible
54
+ // tooltip content as a node separate from its clipped, sr-only role="tooltip"
55
+ // announcement span — so we assert on the visible CONTENT text, not getByRole.
56
+ play: async ({ canvas, canvasElement, userEvent }) => {
57
+ const trigger = canvas.getByRole("button", { name: /hover me/i });
58
+ await userEvent.hover(trigger);
59
+ const body = within(canvasElement.ownerDocument.body);
60
+ // Radix nests an sr-only role="tooltip" copy of the text INSIDE the visible
61
+ // popper content, so the text matches twice. Target the popper content node
62
+ // (the only one carrying data-side) and assert it is visible.
63
+ const content = await body.findByText("Helpful context", { selector: "[data-side]" });
64
+ // wait out the fade-in entrance animation before asserting visibility
65
+ await waitFor(() => expect(content).toBeVisible());
66
+ },
67
+ };
@@ -0,0 +1,27 @@
1
+ import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "react";
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
+ import { cn } from "../../lib/cn";
4
+
5
+ export const TooltipProvider = TooltipPrimitive.Provider;
6
+ export const Tooltip = TooltipPrimitive.Root;
7
+ export const TooltipTrigger = TooltipPrimitive.Trigger;
8
+
9
+ export const TooltipContent = forwardRef<
10
+ ElementRef<typeof TooltipPrimitive.Content>,
11
+ ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
12
+ >(function TooltipContent({ className, sideOffset = 6, ...props }, ref) {
13
+ return (
14
+ <TooltipPrimitive.Portal>
15
+ <TooltipPrimitive.Content
16
+ ref={ref}
17
+ sideOffset={sideOffset}
18
+ className={cn(
19
+ "z-50 overflow-hidden rounded-md bg-foreground px-2.5 py-1.5 text-xs text-background shadow-md",
20
+ "data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95",
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ </TooltipPrimitive.Portal>
26
+ );
27
+ });
@@ -0,0 +1 @@
1
+ export { TopNav, type TopNavProps } from "./top-nav";
@@ -0,0 +1,18 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Button } from "../button";
3
+ import { TopNav } from "./top-nav";
4
+ const meta = {
5
+ title: "Layout/TopNav",
6
+ component: TopNav,
7
+ parameters: { layout: "fullscreen" },
8
+ } satisfies Meta<typeof TopNav>;
9
+ export default meta;
10
+ type Story = StoryObj<typeof meta>;
11
+ export const Default: Story = {
12
+ render: () => (
13
+ <TopNav
14
+ start={<span className="font-semibold">Dashboard</span>}
15
+ end={<Button size="sm">Invite</Button>}
16
+ />
17
+ ),
18
+ };
@@ -0,0 +1,34 @@
1
+ import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export interface TopNavProps extends HTMLAttributes<HTMLElement> {
5
+ /** Left slot (brand on mobile, breadcrumbs, title). */
6
+ start?: ReactNode;
7
+ /** Right slot (search, actions, account menu). */
8
+ end?: ReactNode;
9
+ }
10
+
11
+ /** Application top bar. Sticky, single row, brand-neutral. */
12
+ export const TopNav = forwardRef<HTMLElement, TopNavProps>(function TopNav(
13
+ { className, start, end, children, ...props },
14
+ ref,
15
+ ) {
16
+ return (
17
+ <header
18
+ ref={ref}
19
+ className={cn(
20
+ "sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-surface-elevated/80 px-4 backdrop-blur",
21
+ className,
22
+ )}
23
+ {...props}
24
+ >
25
+ {start ? <div className="flex items-center gap-2">{start}</div> : null}
26
+ {children ? (
27
+ <div className="flex flex-1 items-center">{children}</div>
28
+ ) : (
29
+ <div className="flex-1" />
30
+ )}
31
+ {end ? <div className="flex items-center gap-2">{end}</div> : null}
32
+ </header>
33
+ );
34
+ });
@@ -0,0 +1,2 @@
1
+ export { Transfer } from "./transfer";
2
+ export type { TransferItem, TransferProps } from "./transfer";