@mihcm/ui 0.14.1 → 0.15.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 (300) hide show
  1. package/dist/CheckboxGrid.native.d.ts.map +1 -1
  2. package/dist/CheckboxGrid.native.js +2 -1
  3. package/dist/CheckboxGrid.native.js.map +1 -1
  4. package/dist/Combobox.native.d.ts.map +1 -1
  5. package/dist/Combobox.native.js +2 -1
  6. package/dist/Combobox.native.js.map +1 -1
  7. package/dist/DataTable/column-filter.d.ts +8 -0
  8. package/dist/DataTable/column-filter.d.ts.map +1 -0
  9. package/dist/DataTable/column-filter.js +67 -0
  10. package/dist/DataTable/column-filter.js.map +1 -0
  11. package/dist/DataTable/column-header.d.ts +16 -0
  12. package/dist/DataTable/column-header.d.ts.map +1 -0
  13. package/dist/DataTable/column-header.js +11 -0
  14. package/dist/DataTable/column-header.js.map +1 -0
  15. package/dist/DataTable/column-visibility.d.ts +7 -0
  16. package/dist/DataTable/column-visibility.d.ts.map +1 -0
  17. package/dist/DataTable/column-visibility.js +35 -0
  18. package/dist/DataTable/column-visibility.js.map +1 -0
  19. package/dist/DataTable/index.d.ts +5 -0
  20. package/dist/DataTable/index.d.ts.map +1 -0
  21. package/dist/DataTable/index.js +5 -0
  22. package/dist/DataTable/index.js.map +1 -0
  23. package/dist/DataTable/pinning.d.ts +13 -0
  24. package/dist/DataTable/pinning.d.ts.map +1 -0
  25. package/dist/DataTable/pinning.js +29 -0
  26. package/dist/DataTable/pinning.js.map +1 -0
  27. package/dist/DataTable.d.ts +3 -7
  28. package/dist/DataTable.d.ts.map +1 -1
  29. package/dist/DataTable.js +7 -126
  30. package/dist/DataTable.js.map +1 -1
  31. package/dist/Dialog.native.d.ts +3 -1
  32. package/dist/Dialog.native.d.ts.map +1 -1
  33. package/dist/Dialog.native.js +2 -2
  34. package/dist/Dialog.native.js.map +1 -1
  35. package/dist/Form/building-blocks.d.ts +26 -0
  36. package/dist/Form/building-blocks.d.ts.map +1 -0
  37. package/dist/Form/building-blocks.js +29 -0
  38. package/dist/Form/building-blocks.js.map +1 -0
  39. package/dist/Form/fields-choice.d.ts +72 -0
  40. package/dist/Form/fields-choice.d.ts.map +1 -0
  41. package/dist/Form/fields-choice.js +69 -0
  42. package/dist/Form/fields-choice.js.map +1 -0
  43. package/dist/Form/fields-complex.d.ts +28 -0
  44. package/dist/Form/fields-complex.d.ts.map +1 -0
  45. package/dist/Form/fields-complex.js +38 -0
  46. package/dist/Form/fields-complex.js.map +1 -0
  47. package/dist/Form/fields-date.d.ts +46 -0
  48. package/dist/Form/fields-date.d.ts.map +1 -0
  49. package/dist/Form/fields-date.js +41 -0
  50. package/dist/Form/fields-date.js.map +1 -0
  51. package/dist/Form/fields-text.d.ts +47 -0
  52. package/dist/Form/fields-text.d.ts.map +1 -0
  53. package/dist/Form/fields-text.js +46 -0
  54. package/dist/Form/fields-text.js.map +1 -0
  55. package/dist/Form/fields-toggle.d.ts +24 -0
  56. package/dist/Form/fields-toggle.d.ts.map +1 -0
  57. package/dist/Form/fields-toggle.js +32 -0
  58. package/dist/Form/fields-toggle.js.map +1 -0
  59. package/dist/Form/helpers.d.ts +66 -0
  60. package/dist/Form/helpers.d.ts.map +1 -0
  61. package/dist/Form/helpers.js +44 -0
  62. package/dist/Form/helpers.js.map +1 -0
  63. package/dist/Form/types.d.ts +25 -0
  64. package/dist/Form/types.d.ts.map +1 -0
  65. package/dist/Form/types.js +8 -0
  66. package/dist/Form/types.js.map +1 -0
  67. package/dist/Form.d.ts +24 -298
  68. package/dist/Form.d.ts.map +1 -1
  69. package/dist/Form.js +30 -246
  70. package/dist/Form.js.map +1 -1
  71. package/dist/IconSidebar.d.ts +6 -46
  72. package/dist/IconSidebar.d.ts.map +1 -1
  73. package/dist/IconSidebar.js +6 -116
  74. package/dist/IconSidebar.js.map +1 -1
  75. package/dist/MainSidebar/back-button.d.ts +14 -0
  76. package/dist/MainSidebar/back-button.d.ts.map +1 -0
  77. package/dist/MainSidebar/back-button.js +14 -0
  78. package/dist/MainSidebar/back-button.js.map +1 -0
  79. package/dist/MainSidebar/breadcrumb.d.ts +10 -0
  80. package/dist/MainSidebar/breadcrumb.d.ts.map +1 -0
  81. package/dist/MainSidebar/breadcrumb.js +24 -0
  82. package/dist/MainSidebar/breadcrumb.js.map +1 -0
  83. package/dist/MainSidebar/columns.d.ts +3 -0
  84. package/dist/MainSidebar/columns.d.ts.map +1 -0
  85. package/dist/MainSidebar/columns.js +198 -0
  86. package/dist/MainSidebar/columns.js.map +1 -0
  87. package/dist/MainSidebar/command.d.ts +3 -0
  88. package/dist/MainSidebar/command.d.ts.map +1 -0
  89. package/dist/MainSidebar/command.js +193 -0
  90. package/dist/MainSidebar/command.js.map +1 -0
  91. package/dist/MainSidebar/drilldown.d.ts +3 -0
  92. package/dist/MainSidebar/drilldown.d.ts.map +1 -0
  93. package/dist/MainSidebar/drilldown.js +154 -0
  94. package/dist/MainSidebar/drilldown.js.map +1 -0
  95. package/dist/MainSidebar/expanded.d.ts +7 -0
  96. package/dist/MainSidebar/expanded.d.ts.map +1 -0
  97. package/dist/MainSidebar/expanded.js +102 -0
  98. package/dist/MainSidebar/expanded.js.map +1 -0
  99. package/dist/MainSidebar/floating.d.ts +3 -0
  100. package/dist/MainSidebar/floating.d.ts.map +1 -0
  101. package/dist/MainSidebar/floating.js +116 -0
  102. package/dist/MainSidebar/floating.js.map +1 -0
  103. package/dist/MainSidebar/helpers.d.ts +50 -0
  104. package/dist/MainSidebar/helpers.d.ts.map +1 -0
  105. package/dist/MainSidebar/helpers.js +148 -0
  106. package/dist/MainSidebar/helpers.js.map +1 -0
  107. package/dist/MainSidebar/hover.d.ts +3 -0
  108. package/dist/MainSidebar/hover.d.ts.map +1 -0
  109. package/dist/MainSidebar/hover.js +177 -0
  110. package/dist/MainSidebar/hover.js.map +1 -0
  111. package/dist/MainSidebar/index.d.ts +6 -0
  112. package/dist/MainSidebar/index.d.ts.map +1 -0
  113. package/dist/MainSidebar/index.js +108 -0
  114. package/dist/MainSidebar/index.js.map +1 -0
  115. package/dist/MainSidebar/mobile.d.ts +29 -0
  116. package/dist/MainSidebar/mobile.d.ts.map +1 -0
  117. package/dist/MainSidebar/mobile.js +38 -0
  118. package/dist/MainSidebar/mobile.js.map +1 -0
  119. package/dist/MainSidebar/motion.d.ts +23 -0
  120. package/dist/MainSidebar/motion.d.ts.map +1 -0
  121. package/dist/MainSidebar/motion.js +40 -0
  122. package/dist/MainSidebar/motion.js.map +1 -0
  123. package/dist/MainSidebar/rail.d.ts +24 -0
  124. package/dist/MainSidebar/rail.d.ts.map +1 -0
  125. package/dist/MainSidebar/rail.js +29 -0
  126. package/dist/MainSidebar/rail.js.map +1 -0
  127. package/dist/MainSidebar/search.d.ts +19 -0
  128. package/dist/MainSidebar/search.d.ts.map +1 -0
  129. package/dist/MainSidebar/search.js +33 -0
  130. package/dist/MainSidebar/search.js.map +1 -0
  131. package/dist/MainSidebar/types.d.ts +161 -0
  132. package/dist/MainSidebar/types.d.ts.map +1 -0
  133. package/dist/MainSidebar/types.js +2 -0
  134. package/dist/MainSidebar/types.js.map +1 -0
  135. package/dist/MainSidebar.d.ts +6 -1
  136. package/dist/MainSidebar.d.ts.map +1 -1
  137. package/dist/MainSidebar.js +6 -1
  138. package/dist/MainSidebar.js.map +1 -1
  139. package/dist/NavigationMenu.js +1 -1
  140. package/dist/NavigationMenu.js.map +1 -1
  141. package/dist/RichTextEditor/theme.d.ts +44 -0
  142. package/dist/RichTextEditor/theme.d.ts.map +1 -0
  143. package/dist/RichTextEditor/theme.js +41 -0
  144. package/dist/RichTextEditor/theme.js.map +1 -0
  145. package/dist/RichTextEditor/toolbar-icons.d.ts +21 -0
  146. package/dist/RichTextEditor/toolbar-icons.d.ts.map +1 -0
  147. package/dist/RichTextEditor/toolbar-icons.js +21 -0
  148. package/dist/RichTextEditor/toolbar-icons.js.map +1 -0
  149. package/dist/RichTextEditor/toolbar.d.ts +5 -0
  150. package/dist/RichTextEditor/toolbar.d.ts.map +1 -0
  151. package/dist/RichTextEditor/toolbar.js +116 -0
  152. package/dist/RichTextEditor/toolbar.js.map +1 -0
  153. package/dist/RichTextEditor.d.ts +16 -9
  154. package/dist/RichTextEditor.d.ts.map +1 -1
  155. package/dist/RichTextEditor.js +18 -164
  156. package/dist/RichTextEditor.js.map +1 -1
  157. package/dist/Select/content.d.ts +9 -0
  158. package/dist/Select/content.d.ts.map +1 -0
  159. package/dist/Select/content.js +80 -0
  160. package/dist/Select/content.js.map +1 -0
  161. package/dist/Select/context.d.ts +27 -0
  162. package/dist/Select/context.d.ts.map +1 -0
  163. package/dist/Select/context.js +35 -0
  164. package/dist/Select/context.js.map +1 -0
  165. package/dist/Select/item.d.ts +13 -0
  166. package/dist/Select/item.d.ts.map +1 -0
  167. package/dist/Select/item.js +39 -0
  168. package/dist/Select/item.js.map +1 -0
  169. package/dist/Select/parts.d.ts +14 -0
  170. package/dist/Select/parts.d.ts.map +1 -0
  171. package/dist/Select/parts.js +17 -0
  172. package/dist/Select/parts.js.map +1 -0
  173. package/dist/Select/react-select.d.ts +25 -0
  174. package/dist/Select/react-select.d.ts.map +1 -0
  175. package/dist/Select/react-select.js +66 -0
  176. package/dist/Select/react-select.js.map +1 -0
  177. package/dist/Select/root.d.ts +15 -0
  178. package/dist/Select/root.d.ts.map +1 -0
  179. package/dist/Select/root.js +41 -0
  180. package/dist/Select/root.js.map +1 -0
  181. package/dist/Select/trigger.d.ts +15 -0
  182. package/dist/Select/trigger.d.ts.map +1 -0
  183. package/dist/Select/trigger.js +61 -0
  184. package/dist/Select/trigger.js.map +1 -0
  185. package/dist/Select.d.ts +14 -62
  186. package/dist/Select.d.ts.map +1 -1
  187. package/dist/Select.js +14 -293
  188. package/dist/Select.js.map +1 -1
  189. package/dist/Sidebar/context.d.ts +28 -0
  190. package/dist/Sidebar/context.d.ts.map +1 -0
  191. package/dist/Sidebar/context.js +37 -0
  192. package/dist/Sidebar/context.js.map +1 -0
  193. package/dist/Sidebar/group.d.ts +13 -0
  194. package/dist/Sidebar/group.d.ts.map +1 -0
  195. package/dist/Sidebar/group.js +20 -0
  196. package/dist/Sidebar/group.js.map +1 -0
  197. package/dist/Sidebar/icons.d.ts +7 -0
  198. package/dist/Sidebar/icons.d.ts.map +1 -0
  199. package/dist/Sidebar/icons.js +12 -0
  200. package/dist/Sidebar/icons.js.map +1 -0
  201. package/dist/Sidebar/layout.d.ts +9 -0
  202. package/dist/Sidebar/layout.d.ts.map +1 -0
  203. package/dist/Sidebar/layout.js +21 -0
  204. package/dist/Sidebar/layout.js.map +1 -0
  205. package/dist/Sidebar/menu.d.ts +29 -0
  206. package/dist/Sidebar/menu.d.ts.map +1 -0
  207. package/dist/Sidebar/menu.js +55 -0
  208. package/dist/Sidebar/menu.js.map +1 -0
  209. package/dist/Sidebar/provider.d.ts +33 -0
  210. package/dist/Sidebar/provider.d.ts.map +1 -0
  211. package/dist/Sidebar/provider.js +110 -0
  212. package/dist/Sidebar/provider.js.map +1 -0
  213. package/dist/Sidebar/sidebar.d.ts +17 -0
  214. package/dist/Sidebar/sidebar.d.ts.map +1 -0
  215. package/dist/Sidebar/sidebar.js +51 -0
  216. package/dist/Sidebar/sidebar.js.map +1 -0
  217. package/dist/Sidebar/submenu.d.ts +13 -0
  218. package/dist/Sidebar/submenu.d.ts.map +1 -0
  219. package/dist/Sidebar/submenu.js +17 -0
  220. package/dist/Sidebar/submenu.js.map +1 -0
  221. package/dist/Sidebar/trigger.d.ts +9 -0
  222. package/dist/Sidebar/trigger.d.ts.map +1 -0
  223. package/dist/Sidebar/trigger.js +33 -0
  224. package/dist/Sidebar/trigger.js.map +1 -0
  225. package/dist/Sidebar.d.ts +14 -104
  226. package/dist/Sidebar.d.ts.map +1 -1
  227. package/dist/Sidebar.js +14 -300
  228. package/dist/Sidebar.js.map +1 -1
  229. package/dist/StatCard.d.ts +67 -9
  230. package/dist/StatCard.d.ts.map +1 -1
  231. package/dist/StatCard.js +111 -9
  232. package/dist/StatCard.js.map +1 -1
  233. package/dist/TransferList.native.d.ts.map +1 -1
  234. package/dist/TransferList.native.js +2 -1
  235. package/dist/TransferList.native.js.map +1 -1
  236. package/package.json +2 -2
  237. package/src/CheckboxGrid.native.tsx +2 -1
  238. package/src/Combobox.native.tsx +2 -1
  239. package/src/DataTable/column-filter.tsx +134 -0
  240. package/src/DataTable/column-header.tsx +67 -0
  241. package/src/DataTable/column-visibility.tsx +87 -0
  242. package/src/DataTable/index.ts +4 -0
  243. package/src/DataTable/pinning.ts +40 -0
  244. package/src/DataTable.tsx +14 -297
  245. package/src/Dialog.native.tsx +4 -2
  246. package/src/Form/building-blocks.tsx +97 -0
  247. package/src/Form/fields-choice.tsx +312 -0
  248. package/src/Form/fields-complex.tsx +195 -0
  249. package/src/Form/fields-date.tsx +195 -0
  250. package/src/Form/fields-text.tsx +218 -0
  251. package/src/Form/fields-toggle.tsx +123 -0
  252. package/src/Form/helpers.tsx +189 -0
  253. package/src/Form/types.ts +26 -0
  254. package/src/Form.tsx +91 -1308
  255. package/src/IconSidebar.tsx +20 -442
  256. package/src/MainSidebar/back-button.tsx +58 -0
  257. package/src/MainSidebar/breadcrumb.tsx +53 -0
  258. package/src/MainSidebar/columns.tsx +350 -0
  259. package/src/MainSidebar/command.tsx +404 -0
  260. package/src/MainSidebar/drilldown.tsx +373 -0
  261. package/src/MainSidebar/expanded.tsx +414 -0
  262. package/src/MainSidebar/floating.tsx +268 -0
  263. package/src/MainSidebar/helpers.ts +164 -0
  264. package/src/MainSidebar/hover.tsx +334 -0
  265. package/src/MainSidebar/index.tsx +191 -0
  266. package/src/MainSidebar/mobile.tsx +117 -0
  267. package/src/MainSidebar/motion.ts +64 -0
  268. package/src/MainSidebar/rail.tsx +137 -0
  269. package/src/MainSidebar/search.tsx +99 -0
  270. package/src/MainSidebar/types.ts +208 -0
  271. package/src/MainSidebar.tsx +15 -4
  272. package/src/NavigationMenu.tsx +1 -1
  273. package/src/RichTextEditor/theme.ts +43 -0
  274. package/src/RichTextEditor/toolbar-icons.tsx +40 -0
  275. package/src/RichTextEditor/toolbar.tsx +271 -0
  276. package/src/RichTextEditor.tsx +23 -371
  277. package/src/Select/content.tsx +111 -0
  278. package/src/Select/context.tsx +66 -0
  279. package/src/Select/item.tsx +97 -0
  280. package/src/Select/parts.tsx +43 -0
  281. package/src/Select/react-select.tsx +216 -0
  282. package/src/Select/root.tsx +75 -0
  283. package/src/Select/trigger.tsx +122 -0
  284. package/src/Select.tsx +34 -692
  285. package/src/Sidebar/context.tsx +72 -0
  286. package/src/Sidebar/group.tsx +69 -0
  287. package/src/Sidebar/icons.tsx +42 -0
  288. package/src/Sidebar/layout.tsx +64 -0
  289. package/src/Sidebar/menu.tsx +171 -0
  290. package/src/Sidebar/provider.tsx +224 -0
  291. package/src/Sidebar/sidebar.tsx +178 -0
  292. package/src/Sidebar/submenu.tsx +58 -0
  293. package/src/Sidebar/trigger.tsx +104 -0
  294. package/src/Sidebar.tsx +44 -927
  295. package/src/StatCard.tsx +365 -20
  296. package/src/TransferList.native.tsx +2 -1
  297. package/dist/TiptapEditor.d.ts +0 -24
  298. package/dist/TiptapEditor.d.ts.map +0 -1
  299. package/dist/TiptapEditor.js +0 -84
  300. package/dist/TiptapEditor.js.map +0 -1
package/src/Sidebar.tsx CHANGED
@@ -3,10 +3,9 @@
3
3
  /**
4
4
  * Sidebar (web-only — React DOM).
5
5
  *
6
- * A composable, collapsible sidebar with mobile sheet support,
7
- * keyboard shortcuts (Ctrl/Cmd+B), and cookie persistence.
8
- *
9
- * Composes: Button, Sheet, Separator, Skeleton, Tooltip, Input.
6
+ * A composable, collapsible sidebar with mobile sheet support, keyboard
7
+ * shortcut (Ctrl/Cmd+B), and cookie persistence. Composes Button, Sheet,
8
+ * Separator, Skeleton, Tooltip, Input.
10
9
  *
11
10
  * <SidebarProvider>
12
11
  * <Sidebar>
@@ -28,929 +27,47 @@
28
27
  * <SidebarInset>…main content…</SidebarInset>
29
28
  * </SidebarProvider>
30
29
  *
30
+ * Public barrel — implementation is split across `./Sidebar/*` so each
31
+ * file stays under the 400-line rule (CLAUDE.md §6).
32
+ *
31
33
  * Wiki: docs/components/Sidebar.md
32
34
  */
33
- import {
34
- createContext,
35
- forwardRef,
36
- useCallback,
37
- useContext,
38
- useEffect,
39
- useMemo,
40
- useState,
41
- type AnchorHTMLAttributes,
42
- type ButtonHTMLAttributes,
43
- type ComponentProps,
44
- type HTMLAttributes,
45
- } from 'react';
46
- import { cva, type VariantProps } from 'class-variance-authority';
47
- import { cn } from './internal/cn.js';
48
- import { Button } from './Button.js';
49
- import { Sheet, SheetContent } from './Sheet.js';
50
- import { Separator } from './Separator.js';
51
- import { Skeleton } from './Skeleton.js';
52
- import { Tooltip } from './Tooltip.js';
53
- import { Input } from './Input.js';
54
-
55
- type SidebarSide = 'left' | 'right';
56
- type SidebarVariant = 'sidebar' | 'floating' | 'inset';
57
- type SidebarCollapsible = 'offcanvas' | 'icon' | 'none';
58
- type SidebarPosition = 'contained' | 'fixed';
59
-
60
- /* ── Inline icon (avoids a lucide-react dep on the ui package) ───── */
61
-
62
- function PanelLeftIcon() {
63
- return (
64
- <svg
65
- aria-hidden="true"
66
- xmlns="http://www.w3.org/2000/svg"
67
- viewBox="0 0 24 24"
68
- fill="none"
69
- stroke="currentColor"
70
- strokeWidth={2}
71
- strokeLinecap="round"
72
- strokeLinejoin="round"
73
- className="h-4 w-4"
74
- >
75
- <rect width="18" height="18" x="3" y="3" rx="2" />
76
- <path d="M9 3v18" />
77
- </svg>
78
- );
79
- }
80
-
81
- function PanelRightIcon() {
82
- return (
83
- <svg
84
- aria-hidden="true"
85
- xmlns="http://www.w3.org/2000/svg"
86
- viewBox="0 0 24 24"
87
- fill="none"
88
- stroke="currentColor"
89
- strokeWidth={2}
90
- strokeLinecap="round"
91
- strokeLinejoin="round"
92
- className="h-4 w-4"
93
- >
94
- <rect width="18" height="18" x="3" y="3" rx="2" />
95
- <path d="M15 3v18" />
96
- </svg>
97
- );
98
- }
99
-
100
- /* ── Constants ─────────────────────────────────────────────────────── */
101
-
102
- const SIDEBAR_COOKIE_NAME = 'sidebar_state';
103
- const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; // 7 days
104
- const SIDEBAR_WIDTH = '16rem';
105
- const SIDEBAR_WIDTH_ICON = '3rem';
106
- const SIDEBAR_WIDTH_MOBILE = '18rem';
107
- const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
108
- const SIDEBAR_MOBILE_BREAKPOINT = 768;
109
-
110
- /* ── useIsMobile (inline) ──────────────────────────────────────────── */
111
-
112
- function useIsMobile(breakpoint: number) {
113
- const [isMobile, setIsMobile] = useState(false);
114
-
115
- useEffect(() => {
116
- if (typeof window.matchMedia !== 'function') {
117
- return;
118
- }
119
-
120
- const mql = window.matchMedia(`(max-width: ${breakpoint}px)`);
121
- const onChange = () => setIsMobile(mql.matches);
122
- onChange();
123
- mql.addEventListener('change', onChange);
124
- return () => mql.removeEventListener('change', onChange);
125
- }, [breakpoint]);
126
-
127
- return isMobile;
128
- }
129
-
130
- /* ── Context ───────────────────────────────────────────────────────── */
131
-
132
- interface SidebarContextValue {
133
- state: 'expanded' | 'collapsed';
134
- open: boolean;
135
- setOpen: (open: boolean | ((prev: boolean) => boolean)) => void;
136
- openMobile: boolean;
137
- setOpenMobile: (open: boolean | ((prev: boolean) => boolean)) => void;
138
- isMobile: boolean;
139
- side: SidebarSide;
140
- variant: SidebarVariant;
141
- collapsible: SidebarCollapsible;
142
- position: SidebarPosition;
143
- toggleSidebar: () => void;
144
- }
145
-
146
- const SidebarContext = createContext<SidebarContextValue | null>(null);
147
-
148
- export function useSidebar() {
149
- const ctx = useContext(SidebarContext);
150
- if (!ctx) {
151
- throw new Error('useSidebar must be used within a <SidebarProvider>.');
152
- }
153
- return ctx;
154
- }
155
-
156
- /* ── SidebarProvider ───────────────────────────────────────────────── */
157
-
158
- export interface SidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
159
- defaultOpen?: boolean;
160
- open?: boolean;
161
- onOpenChange?: (open: boolean) => void;
162
- /** Initial mobile Sheet open state. Useful for examples and forced mobile previews. */
163
- defaultOpenMobile?: boolean;
164
- /** Controlled mobile Sheet open state. */
165
- openMobile?: boolean;
166
- /** Called when mobile Sheet open state changes. */
167
- onOpenMobileChange?: (open: boolean) => void;
168
- side?: SidebarSide;
169
- variant?: SidebarVariant;
170
- collapsible?: SidebarCollapsible;
171
- position?: SidebarPosition;
172
- /** Force mobile behavior for previews/tests. When omitted, matchMedia decides. */
173
- mobile?: boolean;
174
- /** Breakpoint in px where the sidebar switches to mobile Sheet mode. */
175
- mobileBreakpoint?: number;
176
- /** Expanded desktop width. */
177
- width?: string;
178
- /** Collapsed icon-rail width. */
179
- iconWidth?: string;
180
- /** Mobile Sheet width. */
181
- mobileWidth?: string;
182
- }
183
-
184
- export const SidebarProvider = forwardRef<HTMLDivElement, SidebarProviderProps>(
185
- function SidebarProvider(
186
- {
187
- defaultOpen = true,
188
- open: openProp,
189
- onOpenChange: setOpenProp,
190
- defaultOpenMobile = false,
191
- openMobile: openMobileProp,
192
- onOpenMobileChange: setOpenMobileProp,
193
- side = 'left',
194
- variant = 'sidebar',
195
- collapsible = 'offcanvas',
196
- position = 'contained',
197
- mobile,
198
- mobileBreakpoint = SIDEBAR_MOBILE_BREAKPOINT,
199
- width = SIDEBAR_WIDTH,
200
- iconWidth = SIDEBAR_WIDTH_ICON,
201
- mobileWidth = SIDEBAR_WIDTH_MOBILE,
202
- className,
203
- style,
204
- children,
205
- ...props
206
- },
207
- ref,
208
- ) {
209
- const detectedMobile = useIsMobile(mobileBreakpoint);
210
- const isMobile = mobile ?? detectedMobile;
211
- const [_openMobile, _setOpenMobile] = useState(defaultOpenMobile);
212
-
213
- // Start from props so server and initial client render match.
214
- const [_open, _setOpen] = useState(defaultOpen);
215
-
216
- const open = openProp ?? _open;
217
- const openMobile = openMobileProp ?? _openMobile;
218
-
219
- useEffect(() => {
220
- if (openProp !== undefined) return;
221
-
222
- const cookie = document.cookie
223
- .split('; ')
224
- .find((row) => row.startsWith(`${SIDEBAR_COOKIE_NAME}=`));
225
- if (!cookie) return;
226
-
227
- const nextOpen = cookie.split('=')[1] === 'true';
228
- let cancelled = false;
229
- queueMicrotask(() => {
230
- if (!cancelled) {
231
- _setOpen(nextOpen);
232
- }
233
- });
234
-
235
- return () => {
236
- cancelled = true;
237
- };
238
- }, [openProp]);
239
-
240
- const setOpen = useCallback(
241
- (value: boolean | ((prev: boolean) => boolean)) => {
242
- const next = typeof value === 'function' ? value(open) : value;
243
- if (setOpenProp) {
244
- setOpenProp(next);
245
- } else {
246
- _setOpen(next);
247
- }
248
- if (typeof document !== 'undefined') {
249
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${next}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
250
- }
251
- },
252
- [setOpenProp, open],
253
- );
254
-
255
- const setOpenMobile = useCallback(
256
- (value: boolean | ((prev: boolean) => boolean)) => {
257
- const next = typeof value === 'function' ? value(openMobile) : value;
258
- if (setOpenMobileProp) {
259
- setOpenMobileProp(next);
260
- } else {
261
- _setOpenMobile(next);
262
- }
263
- },
264
- [openMobile, setOpenMobileProp],
265
- );
266
-
267
- const toggleSidebar = useCallback(() => {
268
- if (collapsible === 'none') return;
269
-
270
- if (isMobile) {
271
- setOpenMobile((prev) => !prev);
272
- } else {
273
- setOpen((prev) => !prev);
274
- }
275
- }, [collapsible, isMobile, setOpen]);
276
-
277
- // Keyboard shortcut: Ctrl+B / Cmd+B
278
- useEffect(() => {
279
- const handleKeyDown = (e: KeyboardEvent) => {
280
- if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
281
- e.preventDefault();
282
- toggleSidebar();
283
- }
284
- };
285
- window.addEventListener('keydown', handleKeyDown);
286
- return () => window.removeEventListener('keydown', handleKeyDown);
287
- }, [toggleSidebar]);
288
-
289
- const state = open ? 'expanded' : 'collapsed';
290
-
291
- const value = useMemo<SidebarContextValue>(
292
- () => ({
293
- state,
294
- open,
295
- setOpen,
296
- isMobile,
297
- openMobile,
298
- setOpenMobile,
299
- side,
300
- variant,
301
- collapsible,
302
- position,
303
- toggleSidebar,
304
- }),
305
- [
306
- state,
307
- open,
308
- setOpen,
309
- isMobile,
310
- openMobile,
311
- setOpenMobile,
312
- side,
313
- variant,
314
- collapsible,
315
- position,
316
- toggleSidebar,
317
- ],
318
- );
319
-
320
- return (
321
- <SidebarContext.Provider value={value}>
322
- <div
323
- ref={ref}
324
- style={
325
- {
326
- '--sidebar-width': width,
327
- '--sidebar-width-icon': iconWidth,
328
- '--sidebar-width-mobile': mobileWidth,
329
- ...style,
330
- } as React.CSSProperties
331
- }
332
- className={cn(
333
- 'group/sidebar-wrapper flex min-h-svh w-full',
334
- side === 'right' && 'flex-row-reverse',
335
- variant === 'inset' && 'bg-sidebar',
336
- className,
337
- )}
338
- data-sidebar="provider"
339
- data-state={state}
340
- data-collapsible={state === 'collapsed' ? collapsible : ''}
341
- data-variant={variant}
342
- data-side={side}
343
- data-position={position}
344
- {...props}
345
- >
346
- {children}
347
- </div>
348
- </SidebarContext.Provider>
349
- );
350
- },
351
- );
352
-
353
- /* ── Sidebar ───────────────────────────────────────────────────────── */
354
-
355
- export interface SidebarProps extends HTMLAttributes<HTMLElement> {
356
- side?: SidebarSide;
357
- variant?: SidebarVariant;
358
- collapsible?: SidebarCollapsible;
359
- position?: SidebarPosition;
360
- }
361
-
362
- export const Sidebar = forwardRef<HTMLElement, SidebarProps>(function Sidebar(
363
- { side, variant, collapsible, position, className, children, ...props },
364
- ref,
365
- ) {
366
- const context = useSidebar();
367
- const { isMobile, state, openMobile, setOpenMobile } = context;
368
- const currentSide = side ?? context.side;
369
- const currentVariant = variant ?? context.variant;
370
- const currentCollapsible = collapsible ?? context.collapsible;
371
- const currentPosition = position ?? context.position;
372
- const isCollapsed = state === 'collapsed';
373
- const isFloating = currentVariant === 'floating' || currentVariant === 'inset';
374
- const isOffcanvasCollapsed = isCollapsed && currentCollapsible === 'offcanvas';
375
- const isIconCollapsed = isCollapsed && currentCollapsible === 'icon';
376
- const sidebarEdgeBorder = currentSide === 'right'
377
- ? 'border-l border-sidebar-border'
378
- : 'border-r border-sidebar-border';
379
-
380
- if (currentCollapsible === 'none') {
381
- return (
382
- <nav
383
- ref={ref}
384
- aria-label={props['aria-label'] ?? 'Sidebar'}
385
- data-sidebar="sidebar"
386
- data-state={state}
387
- data-collapsible=""
388
- data-variant={currentVariant}
389
- data-side={currentSide}
390
- data-position={currentPosition}
391
- className={cn(
392
- 'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',
393
- isFloating ? 'rounded-lg border border-sidebar-border shadow' : sidebarEdgeBorder,
394
- className,
395
- )}
396
- {...props}
397
- >
398
- {children}
399
- </nav>
400
- );
401
- }
402
-
403
- if (isMobile) {
404
- return (
405
- <Sheet open={openMobile} onOpenChange={setOpenMobile}>
406
- <SheetContent
407
- data-sidebar="sidebar"
408
- data-mobile="true"
409
- className={cn(
410
- 'w-(--sidebar-width-mobile) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden',
411
- className,
412
- )}
413
- side={currentSide}
414
- {...props}
415
- >
416
- <nav aria-label={props['aria-label'] ?? 'Sidebar'} className="flex h-full w-full flex-col">
417
- {children}
418
- </nav>
419
- </SheetContent>
420
- </Sheet>
421
- );
422
- }
423
-
424
- if (currentPosition === 'contained') {
425
- return (
426
- <div
427
- data-sidebar="sidebar-container"
428
- className={cn(
429
- 'group peer relative hidden h-full shrink-0 overflow-hidden text-sidebar-foreground transition-[width] duration-200 ease-linear md:flex',
430
- isOffcanvasCollapsed
431
- ? 'w-0'
432
- : isIconCollapsed
433
- ? isFloating
434
- ? 'w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]'
435
- : 'w-(--sidebar-width-icon)'
436
- : 'w-(--sidebar-width)',
437
- isFloating && !isOffcanvasCollapsed && 'p-2',
438
- className,
439
- )}
440
- data-state={state}
441
- data-collapsible={isCollapsed ? currentCollapsible : ''}
442
- data-variant={currentVariant}
443
- data-side={currentSide}
444
- data-position={currentPosition}
445
- >
446
- <nav
447
- ref={ref}
448
- aria-label={props['aria-label'] ?? 'Sidebar'}
449
- data-sidebar="sidebar"
450
- className={cn(
451
- 'flex h-full w-full min-w-0 flex-col bg-sidebar',
452
- isFloating ? 'rounded-lg border border-sidebar-border shadow' : sidebarEdgeBorder,
453
- )}
454
- {...props}
455
- >
456
- {children}
457
- </nav>
458
- </div>
459
- );
460
- }
461
-
462
- return (
463
- <div
464
- data-sidebar="sidebar-container"
465
- className="group peer hidden md:block text-sidebar-foreground"
466
- data-state={state}
467
- data-collapsible={isCollapsed ? currentCollapsible : ''}
468
- data-variant={currentVariant}
469
- data-side={currentSide}
470
- data-position={currentPosition}
471
- >
472
- {/* Gap element — occupies space so the main content shifts */}
473
- <div
474
- className={cn(
475
- 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
476
- 'group-data-[collapsible=offcanvas]:w-0',
477
- 'group-data-[side=right]:rotate-180',
478
- isFloating
479
- ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4))]'
480
- : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
481
- )}
482
- />
483
- <div
484
- className={cn(
485
- 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
486
- currentSide === 'left'
487
- ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
488
- : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
489
- isFloating
490
- ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+theme(spacing.4)+2px)]'
491
- : cn(
492
- 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
493
- 'border-sidebar-border',
494
- currentSide === 'right' ? 'group-data-[variant=sidebar]:border-l' : 'group-data-[variant=sidebar]:border-r',
495
- ),
496
- className,
497
- )}
498
- {...props}
499
- >
500
- <nav
501
- ref={ref}
502
- aria-label={props['aria-label'] ?? 'Sidebar'}
503
- data-sidebar="sidebar"
504
- className={cn(
505
- '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',
506
- )}
507
- >
508
- {children}
509
- </nav>
510
- </div>
511
- </div>
512
- );
513
- });
514
-
515
- /* ── SidebarTrigger ────────────────────────────────────────────────── */
516
-
517
- export const SidebarTrigger = forwardRef<
518
- HTMLButtonElement,
519
- ComponentProps<typeof Button>
520
- >(function SidebarTrigger({ className, disabled, onClick, ...props }, ref) {
521
- const { isMobile, open, openMobile, collapsible, side, toggleSidebar } = useSidebar();
522
- const expanded = collapsible === 'none' ? true : isMobile ? openMobile : open;
523
- const isDisabled = disabled || collapsible === 'none';
524
-
525
- return (
526
- <Button
527
- ref={ref}
528
- data-sidebar="trigger"
529
- aria-expanded={expanded}
530
- aria-disabled={isDisabled || undefined}
531
- disabled={isDisabled}
532
- variant="ghost"
533
- size="icon"
534
- className={cn('size-7', className)}
535
- onClick={(e) => {
536
- onClick?.(e);
537
- if (collapsible === 'none') return;
538
- toggleSidebar();
539
- }}
540
- {...props}
541
- >
542
- {side === 'right' ? <PanelRightIcon /> : <PanelLeftIcon />}
543
- <span className="sr-only">Toggle Sidebar</span>
544
- </Button>
545
- );
546
- });
547
-
548
- /* ── SidebarRail ───────────────────────────────────────────────────── */
549
-
550
- export const SidebarRail = forwardRef<HTMLButtonElement, HTMLAttributes<HTMLButtonElement>>(
551
- function SidebarRail({ className, ...props }, ref) {
552
- const { toggleSidebar } = useSidebar();
553
-
554
- return (
555
- <button
556
- ref={ref}
557
- type="button"
558
- data-sidebar="rail"
559
- aria-label="Toggle Sidebar"
560
- tabIndex={-1}
561
- onClick={toggleSidebar}
562
- title="Toggle Sidebar"
563
- className={cn(
564
- 'absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=left]:-translate-x-1/2 group-data-[side=right]:-left-4 group-data-[side=right]:translate-x-1/2 sm:flex',
565
- 'hover:after:bg-sidebar-border',
566
- '[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
567
- '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
568
- 'group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
569
- '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2 [[data-side=left][data-collapsible=offcanvas]_&]:translate-x-0',
570
- '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2 [[data-side=right][data-collapsible=offcanvas]_&]:translate-x-0',
571
- className,
572
- )}
573
- {...props}
574
- />
575
- );
576
- },
577
- );
578
-
579
- /* ── SidebarInset ──────────────────────────────────────────────────── */
580
-
581
- export const SidebarInset = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
582
- function SidebarInset({ className, ...props }, ref) {
583
- return (
584
- <main
585
- ref={ref}
586
- className={cn(
587
- 'relative flex min-h-svh flex-1 flex-col bg-background',
588
- 'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[side=left]:peer-data-[variant=inset]:ml-0 md:peer-data-[side=right]:peer-data-[variant=inset]:mr-0 md:peer-data-[side=left]:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[side=right]:peer-data-[state=collapsed]:peer-data-[variant=inset]:mr-2 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
589
- className,
590
- )}
591
- {...props}
592
- />
593
- );
594
- },
595
- );
596
-
597
- /* ── SidebarInput ──────────────────────────────────────────────────── */
598
-
599
- export const SidebarInput = forwardRef<
600
- HTMLInputElement,
601
- ComponentProps<typeof Input>
602
- >(function SidebarInput({ className, ...props }, ref) {
603
- return (
604
- <Input
605
- ref={ref}
606
- data-sidebar="input"
607
- className={cn(
608
- 'h-8 w-full bg-background shadow-none focus-within:ring-2 focus-within:ring-sidebar-ring',
609
- className,
610
- )}
611
- {...props}
612
- />
613
- );
614
- });
615
-
616
- /* ── Layout sections ───────────────────────────────────────────────── */
617
-
618
- export const SidebarHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
619
- function SidebarHeader({ className, ...props }, ref) {
620
- return (
621
- <div
622
- ref={ref}
623
- data-sidebar="header"
624
- className={cn('flex flex-col gap-2 p-2', className)}
625
- {...props}
626
- />
627
- );
628
- },
629
- );
630
-
631
- export const SidebarFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
632
- function SidebarFooter({ className, ...props }, ref) {
633
- return (
634
- <div
635
- ref={ref}
636
- data-sidebar="footer"
637
- className={cn('flex flex-col gap-2 p-2', className)}
638
- {...props}
639
- />
640
- );
641
- },
642
- );
643
-
644
- export const SidebarContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
645
- function SidebarContent({ className, ...props }, ref) {
646
- return (
647
- <div
648
- ref={ref}
649
- data-sidebar="content"
650
- className={cn(
651
- 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
652
- className,
653
- )}
654
- {...props}
655
- />
656
- );
657
- },
658
- );
659
-
660
- /* ── SidebarSeparator ──────────────────────────────────────────────── */
661
-
662
- export const SidebarSeparator = forwardRef<
663
- HTMLDivElement,
664
- ComponentProps<typeof Separator>
665
- >(function SidebarSeparator({ className, ...props }, ref) {
666
- return (
667
- <Separator
668
- ref={ref}
669
- data-sidebar="separator"
670
- className={cn('mx-2 w-auto bg-sidebar-border', className)}
671
- {...props}
672
- />
673
- );
674
- });
675
-
676
- /* ── Group ─────────────────────────────────────────────────────────── */
677
-
678
- export const SidebarGroup = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
679
- function SidebarGroup({ className, ...props }, ref) {
680
- return (
681
- <div
682
- ref={ref}
683
- data-sidebar="group"
684
- className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
685
- {...props}
686
- />
687
- );
688
- },
689
- );
690
-
691
- export const SidebarGroupLabel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement> & { asChild?: boolean }>(
692
- function SidebarGroupLabel({ className, asChild: _asChild, ...props }, ref) {
693
- return (
694
- <div
695
- ref={ref}
696
- data-sidebar="group-label"
697
- className={cn(
698
- 'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2',
699
- 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
700
- className,
701
- )}
702
- {...props}
703
- />
704
- );
705
- },
706
- );
707
-
708
- export const SidebarGroupAction = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean }>(
709
- function SidebarGroupAction({ className, asChild: _asChild, ...props }, ref) {
710
- return (
711
- <button
712
- ref={ref}
713
- type="button"
714
- data-sidebar="group-action"
715
- className={cn(
716
- 'absolute right-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',
717
- 'after:absolute after:-inset-2 after:md:hidden',
718
- 'group-data-[collapsible=icon]:hidden',
719
- className,
720
- )}
721
- {...props}
722
- />
723
- );
724
- },
725
- );
726
-
727
- export const SidebarGroupContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
728
- function SidebarGroupContent({ className, ...props }, ref) {
729
- return (
730
- <div
731
- ref={ref}
732
- data-sidebar="group-content"
733
- className={cn('w-full text-sm', className)}
734
- {...props}
735
- />
736
- );
737
- },
738
- );
739
-
740
- /* ── Menu ──────────────────────────────────────────────────────────── */
741
-
742
- export const SidebarMenu = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(
743
- function SidebarMenu({ className, ...props }, ref) {
744
- return (
745
- <ul
746
- ref={ref}
747
- data-sidebar="menu"
748
- className={cn('flex w-full min-w-0 flex-col gap-1', className)}
749
- {...props}
750
- />
751
- );
752
- },
753
- );
754
-
755
- export const SidebarMenuItem = forwardRef<HTMLLIElement, HTMLAttributes<HTMLLIElement>>(
756
- function SidebarMenuItem({ className, ...props }, ref) {
757
- return (
758
- <li
759
- ref={ref}
760
- data-sidebar="menu-item"
761
- className={cn('group/menu-item relative', className)}
762
- {...props}
763
- />
764
- );
765
- },
766
- );
767
-
768
- /* ── SidebarMenuButton ─────────────────────────────────────────────── */
769
-
770
- export const sidebarMenuButtonVariants = cva(
771
- 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm 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:pr-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-[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',
772
- {
773
- variants: {
774
- variant: {
775
- default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
776
- outline:
777
- 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
778
- },
779
- size: {
780
- default: 'h-8 text-sm',
781
- sm: 'h-7 text-xs',
782
- lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
783
- },
784
- },
785
- defaultVariants: {
786
- variant: 'default',
787
- size: 'default',
788
- },
789
- },
790
- );
791
-
792
- export interface SidebarMenuButtonProps
793
- extends ButtonHTMLAttributes<HTMLButtonElement>,
794
- VariantProps<typeof sidebarMenuButtonVariants> {
795
- asChild?: boolean;
796
- isActive?: boolean;
797
- tooltip?: string | ComponentProps<typeof Tooltip>;
798
- }
799
-
800
- export const SidebarMenuButton = forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
801
- function SidebarMenuButton(
802
- { asChild: _asChild, isActive = false, variant, size, tooltip, className, ...props },
803
- ref,
804
- ) {
805
- useSidebar();
806
-
807
- const button = (
808
- <button
809
- ref={ref}
810
- type="button"
811
- data-sidebar="menu-button"
812
- data-size={size}
813
- data-active={isActive}
814
- className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
815
- {...props}
816
- />
817
- );
818
-
819
- if (!tooltip) return button;
820
-
821
- const tooltipProps: ComponentProps<typeof Tooltip> =
822
- typeof tooltip === 'string' ? { content: tooltip } : tooltip;
823
-
824
- return (
825
- <Tooltip {...tooltipProps} side="right">
826
- {button}
827
- </Tooltip>
828
- );
829
- },
830
- );
831
-
832
- /* ── SidebarMenuAction ─────────────────────────────────────────────── */
833
-
834
- export const SidebarMenuAction = forwardRef<
835
- HTMLButtonElement,
836
- ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; showOnHover?: boolean }
837
- >(function SidebarMenuAction({ className, asChild: _asChild, showOnHover = false, ...props }, ref) {
838
- return (
839
- <button
840
- ref={ref}
841
- type="button"
842
- data-sidebar="menu-action"
843
- className={cn(
844
- 'absolute right-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',
845
- 'after:absolute after:-inset-2 after:md:hidden',
846
- 'group-data-[collapsible=icon]:hidden',
847
- showOnHover &&
848
- 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
849
- className,
850
- )}
851
- {...props}
852
- />
853
- );
854
- });
855
-
856
- /* ── SidebarMenuBadge ──────────────────────────────────────────────── */
857
-
858
- export const SidebarMenuBadge = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
859
- function SidebarMenuBadge({ className, ...props }, ref) {
860
- return (
861
- <div
862
- ref={ref}
863
- data-sidebar="menu-badge"
864
- className={cn(
865
- 'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground',
866
- 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
867
- 'group-data-[collapsible=icon]:hidden',
868
- className,
869
- )}
870
- {...props}
871
- />
872
- );
873
- },
874
- );
875
-
876
- /* ── SidebarMenuSkeleton ───────────────────────────────────────────── */
877
-
878
- export interface SidebarMenuSkeletonProps extends HTMLAttributes<HTMLDivElement> {
879
- showIcon?: boolean;
880
- }
881
-
882
- export const SidebarMenuSkeleton = forwardRef<HTMLDivElement, SidebarMenuSkeletonProps>(
883
- function SidebarMenuSkeleton({ className, showIcon = false, ...props }, ref) {
884
- // Fixed skeleton width — visual variety comes from multiple instances
885
- const width = '70%';
886
-
887
- return (
888
- <div
889
- ref={ref}
890
- data-sidebar="menu-skeleton"
891
- className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
892
- {...props}
893
- >
894
- {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
895
- <Skeleton
896
- className="h-4 max-w-(--skeleton-width) flex-1"
897
- data-sidebar="menu-skeleton-text"
898
- style={{ '--skeleton-width': width } as React.CSSProperties}
899
- />
900
- </div>
901
- );
902
- },
903
- );
904
-
905
- /* ── Sub-menu ──────────────────────────────────────────────────────── */
906
-
907
- export const SidebarMenuSub = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(
908
- function SidebarMenuSub({ className, ...props }, ref) {
909
- return (
910
- <ul
911
- ref={ref}
912
- data-sidebar="menu-sub"
913
- className={cn(
914
- 'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
915
- 'group-data-[collapsible=icon]:hidden',
916
- className,
917
- )}
918
- {...props}
919
- />
920
- );
921
- },
922
- );
923
-
924
- export const SidebarMenuSubItem = forwardRef<HTMLLIElement, HTMLAttributes<HTMLLIElement>>(
925
- function SidebarMenuSubItem({ ...props }, ref) {
926
- return <li ref={ref} {...props} />;
927
- },
928
- );
929
-
930
- export interface SidebarMenuSubButtonProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
931
- asChild?: boolean;
932
- size?: 'sm' | 'md';
933
- isActive?: boolean;
934
- }
935
35
 
936
- export const SidebarMenuSubButton = forwardRef<HTMLAnchorElement, SidebarMenuSubButtonProps>(
937
- function SidebarMenuSubButton({ asChild: _asChild, size = 'md', isActive, className, ...props }, ref) {
938
- return (
939
- <a
940
- ref={ref}
941
- data-sidebar="menu-sub-button"
942
- data-size={size}
943
- data-active={isActive}
944
- className={cn(
945
- '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',
946
- 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
947
- size === 'sm' && 'text-xs',
948
- size === 'md' && 'text-sm',
949
- 'group-data-[collapsible=icon]:hidden',
950
- className,
951
- )}
952
- {...props}
953
- />
954
- );
955
- },
956
- );
36
+ export { useSidebar } from './Sidebar/context.js';
37
+ export { SidebarProvider, type SidebarProviderProps } from './Sidebar/provider.js';
38
+ export { Sidebar, type SidebarProps } from './Sidebar/sidebar.js';
39
+ export {
40
+ SidebarInput,
41
+ SidebarInset,
42
+ SidebarRail,
43
+ SidebarTrigger,
44
+ } from './Sidebar/trigger.js';
45
+ export {
46
+ SidebarContent,
47
+ SidebarFooter,
48
+ SidebarHeader,
49
+ SidebarSeparator,
50
+ } from './Sidebar/layout.js';
51
+ export {
52
+ SidebarGroup,
53
+ SidebarGroupAction,
54
+ SidebarGroupContent,
55
+ SidebarGroupLabel,
56
+ } from './Sidebar/group.js';
57
+ export {
58
+ SidebarMenu,
59
+ SidebarMenuAction,
60
+ SidebarMenuBadge,
61
+ SidebarMenuButton,
62
+ SidebarMenuItem,
63
+ SidebarMenuSkeleton,
64
+ sidebarMenuButtonVariants,
65
+ type SidebarMenuButtonProps,
66
+ type SidebarMenuSkeletonProps,
67
+ } from './Sidebar/menu.js';
68
+ export {
69
+ SidebarMenuSub,
70
+ SidebarMenuSubButton,
71
+ SidebarMenuSubItem,
72
+ type SidebarMenuSubButtonProps,
73
+ } from './Sidebar/submenu.js';