@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,688 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ forwardRef,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useMemo,
10
+ useState,
11
+ type ComponentProps,
12
+ type CSSProperties,
13
+ } from "react";
14
+ import { Slot } from "@radix-ui/react-slot";
15
+ import { cva, type VariantProps } from "class-variance-authority";
16
+ import { PanelLeftIcon } from "lucide-react";
17
+ import { cn } from "../../lib/cn";
18
+ import { useIsMobile } from "../../lib/use-mobile";
19
+ import { Button } from "../button";
20
+ import { useCollapsiblePanel } from "../collapsible-panel";
21
+ import { Input } from "../input";
22
+ import { Separator } from "../separator";
23
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "../sheet";
24
+ import { Skeleton } from "../skeleton";
25
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../tooltip";
26
+
27
+ const SIDEBAR_COOKIE_NAME = "sidebar_state";
28
+ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
29
+ const SIDEBAR_WIDTH = "16rem";
30
+ const SIDEBAR_WIDTH_MOBILE = "18rem";
31
+ const SIDEBAR_WIDTH_ICON = "3rem";
32
+ const SIDEBAR_KEYBOARD_SHORTCUT = "b";
33
+
34
+ type SidebarContextProps = {
35
+ state: "expanded" | "collapsed";
36
+ open: boolean;
37
+ setOpen: (open: boolean) => void;
38
+ openMobile: boolean;
39
+ setOpenMobile: (open: boolean) => void;
40
+ isMobile: boolean;
41
+ toggleSidebar: () => void;
42
+ };
43
+
44
+ const SidebarContext = createContext<SidebarContextProps | null>(null);
45
+
46
+ export function useSidebar() {
47
+ const context = useContext(SidebarContext);
48
+ if (!context) throw new Error("useSidebar must be used within a SidebarProvider.");
49
+ return context;
50
+ }
51
+
52
+ export const SidebarProvider = forwardRef<
53
+ HTMLDivElement,
54
+ ComponentProps<"div"> & {
55
+ defaultOpen?: boolean;
56
+ open?: boolean;
57
+ onOpenChange?: (open: boolean) => void;
58
+ }
59
+ >(function SidebarProvider(
60
+ {
61
+ defaultOpen = true,
62
+ open: openProp,
63
+ onOpenChange: setOpenProp,
64
+ className,
65
+ style,
66
+ children,
67
+ ...props
68
+ },
69
+ ref,
70
+ ) {
71
+ const isMobile = useIsMobile();
72
+ const [openMobile, setOpenMobile] = useState(false);
73
+
74
+ const [_open, _setOpen] = useState(defaultOpen);
75
+ const open = openProp ?? _open;
76
+ const setOpen = useCallback(
77
+ (value: boolean | ((value: boolean) => boolean)) => {
78
+ const openState = typeof value === "function" ? value(open) : value;
79
+ if (setOpenProp) setOpenProp(openState);
80
+ else _setOpen(openState);
81
+ if (typeof document !== "undefined") {
82
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
83
+ }
84
+ },
85
+ [setOpenProp, open],
86
+ );
87
+
88
+ const toggleSidebar = useCallback(() => {
89
+ return isMobile ? setOpenMobile((o) => !o) : setOpen((o) => !o);
90
+ }, [isMobile, setOpen]);
91
+
92
+ useEffect(() => {
93
+ const handleKeyDown = (event: KeyboardEvent) => {
94
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
95
+ event.preventDefault();
96
+ toggleSidebar();
97
+ }
98
+ };
99
+ window.addEventListener("keydown", handleKeyDown);
100
+ return () => window.removeEventListener("keydown", handleKeyDown);
101
+ }, [toggleSidebar]);
102
+
103
+ const state = open ? "expanded" : "collapsed";
104
+
105
+ const contextValue = useMemo<SidebarContextProps>(
106
+ () => ({ state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar }),
107
+ [state, open, setOpen, isMobile, openMobile, toggleSidebar],
108
+ );
109
+
110
+ return (
111
+ <SidebarContext.Provider value={contextValue}>
112
+ <TooltipProvider delayDuration={0}>
113
+ <div
114
+ ref={ref}
115
+ data-slot="sidebar-wrapper"
116
+ style={
117
+ {
118
+ "--sidebar-width": SIDEBAR_WIDTH,
119
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
120
+ ...style,
121
+ } as CSSProperties
122
+ }
123
+ className={cn(
124
+ // The wrapper spans the WHOLE app frame — chrome AND content — so its
125
+ // ink is the ordinary page ink, not the chrome's. `Sidebar` (desktop
126
+ // and mobile) sets `text-sidebar-foreground` on itself, so the chrome
127
+ // is covered without painting chrome ink onto the canvas. Putting the
128
+ // chrome ink here was invisible only while a theme happened to give
129
+ // `--sidebar` and `--background` the same polarity: a theme with dark
130
+ // chrome under a light canvas rendered near-white text on the canvas
131
+ // for every element that inherits its colour (outline Buttons, list
132
+ // rows). See the chrome<canvas elevation invariant in
133
+ // .claude/rules/styling-and-tokens.md.
134
+ "group/sidebar-wrapper flex min-h-svh w-full text-foreground has-data-[variant=inset]:bg-sidebar",
135
+ className,
136
+ )}
137
+ {...props}
138
+ >
139
+ {children}
140
+ </div>
141
+ </TooltipProvider>
142
+ </SidebarContext.Provider>
143
+ );
144
+ });
145
+
146
+ export const Sidebar = forwardRef<
147
+ HTMLDivElement,
148
+ ComponentProps<"div"> & {
149
+ side?: "left" | "right";
150
+ variant?: "sidebar" | "floating" | "inset";
151
+ collapsible?: "offcanvas" | "icon" | "none";
152
+ }
153
+ >(function Sidebar(
154
+ { side = "left", variant = "sidebar", collapsible = "offcanvas", className, children, ...props },
155
+ ref,
156
+ ) {
157
+ const { isMobile, openMobile, setOpenMobile, open, setOpen } = useSidebar();
158
+
159
+ // The collapse mechanism (gap spacer + fixed slide) is the canonical
160
+ // useCollapsiblePanel hook (#190, research 09 §B.2) — Sidebar passes its
161
+ // literal width/collapse/slide utilities (Tailwind scans THIS file for
162
+ // them) and gets back its original class fragments byte-identically.
163
+ const panel = useCollapsiblePanel({
164
+ side,
165
+ open,
166
+ onOpenChange: setOpen,
167
+ widthClassName: "w-(--sidebar-width)",
168
+ spacerCollapsedClassName: "group-data-[collapsible=offcanvas]:w-0",
169
+ containerSlideClassNames: {
170
+ left: "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]",
171
+ right: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
172
+ },
173
+ });
174
+
175
+ if (collapsible === "none") {
176
+ return (
177
+ <div
178
+ ref={ref}
179
+ data-slot="sidebar"
180
+ className={cn(
181
+ "flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
182
+ className,
183
+ )}
184
+ {...props}
185
+ >
186
+ {children}
187
+ </div>
188
+ );
189
+ }
190
+
191
+ if (isMobile) {
192
+ return (
193
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
194
+ <SheetContent
195
+ data-sidebar="sidebar"
196
+ data-slot="sidebar"
197
+ data-mobile="true"
198
+ side={side}
199
+ className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
200
+ style={{ "--sidebar-width": SIDEBAR_WIDTH_MOBILE } as CSSProperties}
201
+ >
202
+ <SheetHeader className="sr-only">
203
+ <SheetTitle>Sidebar</SheetTitle>
204
+ <SheetDescription>Displays the mobile sidebar.</SheetDescription>
205
+ </SheetHeader>
206
+ <div className="flex h-full w-full flex-col">{children}</div>
207
+ </SheetContent>
208
+ </Sheet>
209
+ );
210
+ }
211
+
212
+ return (
213
+ <div
214
+ ref={ref}
215
+ className="group peer hidden text-sidebar-foreground md:block"
216
+ data-state={panel.attrs["data-state"]}
217
+ data-collapsible={panel.state === "collapsed" ? collapsible : ""}
218
+ data-variant={variant}
219
+ data-side={panel.attrs["data-side"]}
220
+ data-slot="sidebar"
221
+ >
222
+ <div
223
+ data-slot="sidebar-gap"
224
+ className={cn(
225
+ panel.spacerClassName,
226
+ "group-data-[side=right]:rotate-180",
227
+ variant === "floating" || variant === "inset"
228
+ ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
229
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
230
+ )}
231
+ />
232
+ <div
233
+ data-slot="sidebar-container"
234
+ className={cn(
235
+ panel.containerClassName,
236
+ variant === "floating" || variant === "inset"
237
+ ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
238
+ : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
239
+ className,
240
+ )}
241
+ {...props}
242
+ >
243
+ <div
244
+ data-sidebar="sidebar"
245
+ data-slot="sidebar-inner"
246
+ className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
247
+ >
248
+ {children}
249
+ </div>
250
+ </div>
251
+ </div>
252
+ );
253
+ });
254
+
255
+ export const SidebarTrigger = forwardRef<HTMLButtonElement, ComponentProps<typeof Button>>(
256
+ function SidebarTrigger({ className, onClick, ...props }, ref) {
257
+ const { toggleSidebar } = useSidebar();
258
+ return (
259
+ <Button
260
+ ref={ref}
261
+ data-sidebar="trigger"
262
+ data-slot="sidebar-trigger"
263
+ variant="ghost"
264
+ size="icon"
265
+ className={cn("size-7", className)}
266
+ onClick={(event) => {
267
+ onClick?.(event);
268
+ toggleSidebar();
269
+ }}
270
+ {...props}
271
+ >
272
+ <PanelLeftIcon />
273
+ <span className="sr-only">Toggle Sidebar</span>
274
+ </Button>
275
+ );
276
+ },
277
+ );
278
+
279
+ export const SidebarRail = forwardRef<HTMLButtonElement, ComponentProps<"button">>(
280
+ function SidebarRail({ className, ...props }, ref) {
281
+ const { toggleSidebar } = useSidebar();
282
+ return (
283
+ <button
284
+ ref={ref}
285
+ data-sidebar="rail"
286
+ data-slot="sidebar-rail"
287
+ aria-label="Toggle Sidebar"
288
+ tabIndex={-1}
289
+ onClick={toggleSidebar}
290
+ title="Toggle Sidebar"
291
+ className={cn(
292
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex",
293
+ "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
294
+ "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2 [[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
295
+ className,
296
+ )}
297
+ {...props}
298
+ />
299
+ );
300
+ },
301
+ );
302
+
303
+ export const SidebarInset = forwardRef<HTMLDivElement, ComponentProps<"main">>(
304
+ function SidebarInset({ className, ...props }, ref) {
305
+ return (
306
+ <main
307
+ ref={ref}
308
+ data-slot="sidebar-inset"
309
+ className={cn(
310
+ "relative flex w-full flex-1 flex-col bg-background",
311
+ "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ms-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ms-2",
312
+ className,
313
+ )}
314
+ {...props}
315
+ />
316
+ );
317
+ },
318
+ );
319
+
320
+ export const SidebarInput = forwardRef<HTMLInputElement, ComponentProps<typeof Input>>(
321
+ function SidebarInput({ className, ...props }, ref) {
322
+ return (
323
+ <Input
324
+ ref={ref}
325
+ data-slot="sidebar-input"
326
+ data-sidebar="input"
327
+ className={cn("h-8 w-full bg-background shadow-none", className)}
328
+ {...props}
329
+ />
330
+ );
331
+ },
332
+ );
333
+
334
+ export const SidebarHeader = forwardRef<HTMLDivElement, ComponentProps<"div">>(
335
+ function SidebarHeader({ className, ...props }, ref) {
336
+ return (
337
+ <div
338
+ ref={ref}
339
+ data-slot="sidebar-header"
340
+ data-sidebar="header"
341
+ className={cn("flex flex-col gap-2 p-2", className)}
342
+ {...props}
343
+ />
344
+ );
345
+ },
346
+ );
347
+
348
+ export const SidebarFooter = forwardRef<HTMLDivElement, ComponentProps<"div">>(
349
+ function SidebarFooter({ className, ...props }, ref) {
350
+ return (
351
+ <div
352
+ ref={ref}
353
+ data-slot="sidebar-footer"
354
+ data-sidebar="footer"
355
+ className={cn("flex flex-col gap-2 p-2", className)}
356
+ {...props}
357
+ />
358
+ );
359
+ },
360
+ );
361
+
362
+ export const SidebarSeparator = forwardRef<HTMLDivElement, ComponentProps<typeof Separator>>(
363
+ function SidebarSeparator({ className, ...props }, ref) {
364
+ return (
365
+ <Separator
366
+ ref={ref}
367
+ data-slot="sidebar-separator"
368
+ data-sidebar="separator"
369
+ className={cn("mx-2 w-auto bg-sidebar-border", className)}
370
+ {...props}
371
+ />
372
+ );
373
+ },
374
+ );
375
+
376
+ export const SidebarContent = forwardRef<HTMLDivElement, ComponentProps<"div">>(
377
+ function SidebarContent({ className, ...props }, ref) {
378
+ return (
379
+ <div
380
+ ref={ref}
381
+ data-slot="sidebar-content"
382
+ data-sidebar="content"
383
+ className={cn(
384
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
385
+ className,
386
+ )}
387
+ {...props}
388
+ />
389
+ );
390
+ },
391
+ );
392
+
393
+ export const SidebarGroup = forwardRef<HTMLDivElement, ComponentProps<"div">>(function SidebarGroup(
394
+ { className, ...props },
395
+ ref,
396
+ ) {
397
+ return (
398
+ <div
399
+ ref={ref}
400
+ data-slot="sidebar-group"
401
+ data-sidebar="group"
402
+ className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
403
+ {...props}
404
+ />
405
+ );
406
+ });
407
+
408
+ export const SidebarGroupLabel = forwardRef<
409
+ HTMLDivElement,
410
+ ComponentProps<"div"> & { asChild?: boolean }
411
+ >(function SidebarGroupLabel({ className, asChild = false, ...props }, ref) {
412
+ const Comp = asChild ? Slot : "div";
413
+ return (
414
+ <Comp
415
+ ref={ref}
416
+ data-slot="sidebar-group-label"
417
+ data-sidebar="group-label"
418
+ className={cn(
419
+ "flex h-8 shrink-0 items-center rounded-md px-2 text-meta font-medium text-sidebar-muted-foreground outline-none ring-sidebar-ring transition-[margin,opacity] duration-base ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
420
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
421
+ className,
422
+ )}
423
+ {...props}
424
+ />
425
+ );
426
+ });
427
+
428
+ export const SidebarGroupAction = forwardRef<
429
+ HTMLButtonElement,
430
+ ComponentProps<"button"> & { asChild?: boolean }
431
+ >(function SidebarGroupAction({ className, asChild = false, ...props }, ref) {
432
+ const Comp = asChild ? Slot : "button";
433
+ return (
434
+ <Comp
435
+ ref={ref}
436
+ data-slot="sidebar-group-action"
437
+ data-sidebar="group-action"
438
+ className={cn(
439
+ "absolute end-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden",
440
+ "group-data-[collapsible=icon]:hidden",
441
+ className,
442
+ )}
443
+ {...props}
444
+ />
445
+ );
446
+ });
447
+
448
+ export const SidebarGroupContent = forwardRef<HTMLDivElement, ComponentProps<"div">>(
449
+ function SidebarGroupContent({ className, ...props }, ref) {
450
+ return (
451
+ <div
452
+ ref={ref}
453
+ data-slot="sidebar-group-content"
454
+ data-sidebar="group-content"
455
+ className={cn("w-full text-body", className)}
456
+ {...props}
457
+ />
458
+ );
459
+ },
460
+ );
461
+
462
+ export const SidebarMenu = forwardRef<HTMLUListElement, ComponentProps<"ul">>(function SidebarMenu(
463
+ { className, ...props },
464
+ ref,
465
+ ) {
466
+ return (
467
+ <ul
468
+ ref={ref}
469
+ data-slot="sidebar-menu"
470
+ data-sidebar="menu"
471
+ className={cn("flex w-full min-w-0 flex-col gap-1", className)}
472
+ {...props}
473
+ />
474
+ );
475
+ });
476
+
477
+ export const SidebarMenuItem = forwardRef<HTMLLIElement, ComponentProps<"li">>(
478
+ function SidebarMenuItem({ className, ...props }, ref) {
479
+ return (
480
+ <li
481
+ ref={ref}
482
+ data-slot="sidebar-menu-item"
483
+ data-sidebar="menu-item"
484
+ className={cn("group/menu-item relative", className)}
485
+ {...props}
486
+ />
487
+ );
488
+ },
489
+ );
490
+
491
+ export const sidebarMenuButtonVariants = cva(
492
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-start text-body text-sidebar-foreground outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pe-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[active=true]:[&>svg]:text-sidebar-primary data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
493
+ {
494
+ variants: {
495
+ variant: {
496
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
497
+ // A 1px outline drawn as the ring layer, not a `border` — the button sits
498
+ // in a fixed-height menu row, so an edge that took layout space would
499
+ // change its metrics. `shadow-hairline` reads --shadow-ring-color, so the
500
+ // sidebar retints it (chrome has its own edge tokens) instead of
501
+ // hand-rolling `shadow-[0_0_0_1px_…]`.
502
+ outline:
503
+ "bg-background shadow-hairline [--shadow-ring-color:var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:[--shadow-ring-color:var(--sidebar-accent)]",
504
+ },
505
+ size: {
506
+ default: "h-8 text-body",
507
+ sm: "h-7 text-meta",
508
+ lg: "h-12 text-body group-data-[collapsible=icon]:p-0!",
509
+ },
510
+ },
511
+ defaultVariants: { variant: "default", size: "default" },
512
+ },
513
+ );
514
+
515
+ export const SidebarMenuButton = forwardRef<
516
+ HTMLButtonElement,
517
+ ComponentProps<"button"> & {
518
+ asChild?: boolean;
519
+ isActive?: boolean;
520
+ tooltip?: string | ComponentProps<typeof TooltipContent>;
521
+ } & VariantProps<typeof sidebarMenuButtonVariants>
522
+ >(function SidebarMenuButton(
523
+ {
524
+ asChild = false,
525
+ isActive = false,
526
+ variant = "default",
527
+ size = "default",
528
+ tooltip,
529
+ className,
530
+ ...props
531
+ },
532
+ ref,
533
+ ) {
534
+ const Comp = asChild ? Slot : "button";
535
+ const { isMobile, state } = useSidebar();
536
+
537
+ const button = (
538
+ <Comp
539
+ ref={ref}
540
+ data-slot="sidebar-menu-button"
541
+ data-sidebar="menu-button"
542
+ data-size={size}
543
+ data-active={isActive}
544
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
545
+ {...props}
546
+ />
547
+ );
548
+
549
+ if (!tooltip) return button;
550
+ const tooltipProps = typeof tooltip === "string" ? { children: tooltip } : tooltip;
551
+
552
+ return (
553
+ <Tooltip>
554
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
555
+ <TooltipContent
556
+ side="right"
557
+ align="center"
558
+ hidden={state !== "collapsed" || isMobile}
559
+ {...tooltipProps}
560
+ />
561
+ </Tooltip>
562
+ );
563
+ });
564
+
565
+ export const SidebarMenuAction = forwardRef<
566
+ HTMLButtonElement,
567
+ ComponentProps<"button"> & { asChild?: boolean; showOnHover?: boolean }
568
+ >(function SidebarMenuAction({ className, asChild = false, showOnHover = false, ...props }, ref) {
569
+ const Comp = asChild ? Slot : "button";
570
+ return (
571
+ <Comp
572
+ ref={ref}
573
+ data-slot="sidebar-menu-action"
574
+ data-sidebar="menu-action"
575
+ className={cn(
576
+ "absolute end-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0 after:absolute after:-inset-2 md:after:hidden",
577
+ "group-data-[collapsible=icon]:hidden",
578
+ showOnHover &&
579
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
580
+ className,
581
+ )}
582
+ {...props}
583
+ />
584
+ );
585
+ });
586
+
587
+ export const SidebarMenuBadge = forwardRef<HTMLDivElement, ComponentProps<"div">>(
588
+ function SidebarMenuBadge({ className, ...props }, ref) {
589
+ return (
590
+ <div
591
+ ref={ref}
592
+ data-slot="sidebar-menu-badge"
593
+ data-sidebar="menu-badge"
594
+ className={cn(
595
+ "pointer-events-none absolute end-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-meta font-medium tabular-nums text-sidebar-foreground",
596
+ "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
597
+ "peer-data-[size=sm]/menu-button:top-1 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5",
598
+ "group-data-[collapsible=icon]:hidden",
599
+ className,
600
+ )}
601
+ {...props}
602
+ />
603
+ );
604
+ },
605
+ );
606
+
607
+ export function SidebarMenuSkeleton({
608
+ className,
609
+ showIcon = false,
610
+ ...props
611
+ }: ComponentProps<"div"> & { showIcon?: boolean }) {
612
+ const width = useMemo(() => `${Math.floor(Math.random() * 40) + 50}%`, []);
613
+ return (
614
+ <div
615
+ data-slot="sidebar-menu-skeleton"
616
+ data-sidebar="menu-skeleton"
617
+ className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
618
+ {...props}
619
+ >
620
+ {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
621
+ <Skeleton
622
+ className="h-4 max-w-(--skeleton-width) flex-1"
623
+ data-sidebar="menu-skeleton-text"
624
+ style={{ "--skeleton-width": width } as CSSProperties}
625
+ />
626
+ </div>
627
+ );
628
+ }
629
+
630
+ export const SidebarMenuSub = forwardRef<HTMLUListElement, ComponentProps<"ul">>(
631
+ function SidebarMenuSub({ className, ...props }, ref) {
632
+ return (
633
+ <ul
634
+ ref={ref}
635
+ data-slot="sidebar-menu-sub"
636
+ data-sidebar="menu-sub"
637
+ className={cn(
638
+ "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-s border-sidebar-border px-2.5 py-0.5",
639
+ "group-data-[collapsible=icon]:hidden",
640
+ className,
641
+ )}
642
+ {...props}
643
+ />
644
+ );
645
+ },
646
+ );
647
+
648
+ export const SidebarMenuSubItem = forwardRef<HTMLLIElement, ComponentProps<"li">>(
649
+ function SidebarMenuSubItem({ className, ...props }, ref) {
650
+ return (
651
+ <li
652
+ ref={ref}
653
+ data-slot="sidebar-menu-sub-item"
654
+ data-sidebar="menu-sub-item"
655
+ className={cn("group/menu-sub-item relative", className)}
656
+ {...props}
657
+ />
658
+ );
659
+ },
660
+ );
661
+
662
+ export const SidebarMenuSubButton = forwardRef<
663
+ HTMLAnchorElement,
664
+ ComponentProps<"a"> & { asChild?: boolean; size?: "sm" | "md"; isActive?: boolean }
665
+ >(function SidebarMenuSubButton(
666
+ { asChild = false, size = "md", isActive = false, className, ...props },
667
+ ref,
668
+ ) {
669
+ const Comp = asChild ? Slot : "a";
670
+ return (
671
+ <Comp
672
+ ref={ref}
673
+ data-slot="sidebar-menu-sub-button"
674
+ data-sidebar="menu-sub-button"
675
+ data-size={size}
676
+ data-active={isActive}
677
+ className={cn(
678
+ "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
679
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
680
+ size === "sm" && "text-meta",
681
+ size === "md" && "text-body",
682
+ "group-data-[collapsible=icon]:hidden",
683
+ className,
684
+ )}
685
+ {...props}
686
+ />
687
+ );
688
+ });
@@ -0,0 +1 @@
1
+ export { Skeleton, type SkeletonProps } from "./skeleton";