@object-ui/components 0.3.1 → 0.5.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 (299) hide show
  1. package/.turbo/turbo-build.log +47 -0
  2. package/README.md +13 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.js +34610 -24854
  5. package/dist/index.umd.cjs +53 -32
  6. package/dist/src/SchemaRenderer.d.ts +3 -0
  7. package/dist/src/{ui → custom}/button-group.d.ts +1 -1
  8. package/dist/src/custom/field.d.ts +19 -0
  9. package/dist/src/custom/index.d.ts +12 -0
  10. package/dist/src/custom/input-group.d.ts +14 -0
  11. package/dist/src/{ui → custom}/item.d.ts +1 -1
  12. package/dist/src/custom/native-select.d.ts +12 -0
  13. package/dist/src/custom/sort-builder.d.ts +22 -0
  14. package/dist/src/index.d.ts +1 -0
  15. package/dist/src/renderers/data-display/table.d.ts +1 -1
  16. package/dist/src/renderers/layout/page.d.ts +1 -1
  17. package/dist/src/renderers/placeholders.d.ts +1 -1
  18. package/dist/src/ui/accordion.d.ts +4 -4
  19. package/dist/src/ui/alert-dialog.d.ts +17 -11
  20. package/dist/src/ui/alert.d.ts +4 -5
  21. package/dist/src/ui/aspect-ratio.d.ts +1 -1
  22. package/dist/src/ui/avatar.d.ts +3 -3
  23. package/dist/src/ui/badge.d.ts +3 -3
  24. package/dist/src/ui/breadcrumb.d.ts +16 -8
  25. package/dist/src/ui/calendar.d.ts +7 -7
  26. package/dist/src/ui/card.d.ts +7 -8
  27. package/dist/src/ui/carousel.d.ts +5 -6
  28. package/dist/src/ui/chart.d.ts +62 -0
  29. package/dist/src/ui/checkbox.d.ts +1 -1
  30. package/dist/src/ui/collapsible.d.ts +3 -3
  31. package/dist/src/ui/command.d.ts +78 -16
  32. package/dist/src/ui/context-menu.d.ts +14 -12
  33. package/dist/src/ui/dialog.d.ts +17 -13
  34. package/dist/src/ui/drawer.d.ts +19 -10
  35. package/dist/src/ui/dropdown-menu.d.ts +20 -18
  36. package/dist/src/ui/form.d.ts +6 -7
  37. package/dist/src/ui/hover-card.d.ts +3 -3
  38. package/dist/src/ui/index.d.ts +2 -8
  39. package/dist/src/ui/input-otp.d.ts +30 -7
  40. package/dist/src/ui/label.d.ts +2 -1
  41. package/dist/src/ui/menubar.d.ts +19 -17
  42. package/dist/src/ui/navigation-menu.d.ts +9 -11
  43. package/dist/src/ui/pagination.d.ts +25 -10
  44. package/dist/src/ui/popover.d.ts +4 -5
  45. package/dist/src/ui/progress.d.ts +1 -1
  46. package/dist/src/ui/radio-group.d.ts +2 -2
  47. package/dist/src/ui/resizable.d.ts +5 -8
  48. package/dist/src/ui/scroll-area.d.ts +2 -2
  49. package/dist/src/ui/select.d.ts +11 -13
  50. package/dist/src/ui/sheet.d.ts +23 -11
  51. package/dist/src/ui/sidebar.d.ts +27 -29
  52. package/dist/src/ui/skeleton.d.ts +1 -1
  53. package/dist/src/ui/slider.d.ts +1 -1
  54. package/dist/src/ui/sonner.d.ts +2 -1
  55. package/dist/src/ui/switch.d.ts +2 -2
  56. package/dist/src/ui/tabs.d.ts +1 -1
  57. package/dist/src/ui/textarea.d.ts +1 -1
  58. package/dist/src/ui/toast.d.ts +22 -0
  59. package/dist/src/ui/toggle-group.d.ts +8 -3
  60. package/dist/src/ui/toggle.d.ts +4 -1
  61. package/dist/src/ui/tooltip.d.ts +4 -4
  62. package/dist/src/ui/typography.d.ts +21 -0
  63. package/package.json +17 -7
  64. package/shadcn-components.json +52 -47
  65. package/src/SchemaRenderer.tsx +28 -0
  66. package/src/__tests__/PageRendererRegions.test.tsx +59 -0
  67. package/src/__tests__/Registry.test.ts +21 -0
  68. package/src/__tests__/basic-renderers.test.tsx +1 -1
  69. package/src/__tests__/complex-disclosure-renderers.test.tsx +3 -2
  70. package/src/__tests__/feedback-overlay-renderers.test.tsx +1 -1
  71. package/src/__tests__/form-renderers.test.tsx +1 -1
  72. package/src/__tests__/layout-data-renderers.test.tsx +1 -1
  73. package/src/{ui → custom}/button-group.tsx +1 -1
  74. package/src/{ui → custom}/combobox.tsx +3 -3
  75. package/src/{ui → custom}/date-picker.tsx +3 -3
  76. package/src/custom/field.tsx +81 -0
  77. package/src/{ui → custom}/filter-builder.tsx +3 -3
  78. package/src/custom/index.ts +12 -0
  79. package/src/custom/input-group.tsx +53 -0
  80. package/src/{ui → custom}/item.tsx +1 -1
  81. package/src/custom/native-select.tsx +33 -0
  82. package/src/custom/sort-builder.tsx +129 -0
  83. package/src/index.css +20 -1
  84. package/src/index.ts +1 -0
  85. package/src/renderers/basic/button-group.tsx +1 -0
  86. package/src/renderers/basic/div.tsx +12 -1
  87. package/src/renderers/basic/html.tsx +1 -0
  88. package/src/renderers/basic/icon.tsx +1 -0
  89. package/src/renderers/basic/image.tsx +1 -0
  90. package/src/renderers/basic/navigation-menu.tsx +1 -0
  91. package/src/renderers/basic/pagination.tsx +31 -4
  92. package/src/renderers/basic/separator.tsx +1 -0
  93. package/src/renderers/basic/span.tsx +12 -1
  94. package/src/renderers/basic/text.tsx +4 -2
  95. package/src/renderers/complex/carousel.tsx +1 -0
  96. package/src/renderers/complex/data-table.tsx +134 -95
  97. package/src/renderers/complex/filter-builder.tsx +2 -1
  98. package/src/renderers/complex/resizable.tsx +2 -1
  99. package/src/renderers/complex/scroll-area.tsx +25 -7
  100. package/src/renderers/complex/table.tsx +1 -0
  101. package/src/renderers/data-display/alert.tsx +1 -0
  102. package/src/renderers/data-display/avatar.tsx +1 -0
  103. package/src/renderers/data-display/badge.tsx +1 -0
  104. package/src/renderers/data-display/breadcrumb.tsx +1 -0
  105. package/src/renderers/data-display/kbd.tsx +1 -0
  106. package/src/renderers/data-display/list.tsx +21 -49
  107. package/src/renderers/data-display/statistic.tsx +21 -5
  108. package/src/renderers/data-display/table.tsx +21 -11
  109. package/src/renderers/data-display/tree-view.tsx +7 -1
  110. package/src/renderers/disclosure/accordion.tsx +1 -0
  111. package/src/renderers/disclosure/collapsible.tsx +1 -0
  112. package/src/renderers/disclosure/toggle-group.tsx +2 -0
  113. package/src/renderers/feedback/empty.tsx +1 -0
  114. package/src/renderers/feedback/loading.tsx +2 -1
  115. package/src/renderers/feedback/progress.tsx +1 -0
  116. package/src/renderers/feedback/skeleton.tsx +1 -0
  117. package/src/renderers/feedback/sonner.tsx +1 -0
  118. package/src/renderers/feedback/spinner.tsx +1 -0
  119. package/src/renderers/feedback/toast.tsx +1 -0
  120. package/src/renderers/feedback/toaster.tsx +1 -0
  121. package/src/renderers/form/button.tsx +35 -1
  122. package/src/renderers/form/calendar.tsx +1 -0
  123. package/src/renderers/form/checkbox.tsx +38 -16
  124. package/src/renderers/form/combobox.tsx +2 -1
  125. package/src/renderers/form/command.tsx +1 -0
  126. package/src/renderers/form/date-picker.tsx +1 -0
  127. package/src/renderers/form/file-upload.tsx +1 -0
  128. package/src/renderers/form/form.tsx +92 -15
  129. package/src/renderers/form/input-otp.tsx +1 -0
  130. package/src/renderers/form/input.tsx +3 -0
  131. package/src/renderers/form/label.tsx +1 -0
  132. package/src/renderers/form/radio-group.tsx +1 -0
  133. package/src/renderers/form/select.tsx +35 -15
  134. package/src/renderers/form/slider.tsx +1 -0
  135. package/src/renderers/form/switch.tsx +1 -0
  136. package/src/renderers/form/textarea.tsx +50 -27
  137. package/src/renderers/form/toggle.tsx +3 -45
  138. package/src/renderers/layout/aspect-ratio.tsx +2 -1
  139. package/src/renderers/layout/card.tsx +10 -2
  140. package/src/renderers/layout/container.tsx +1 -0
  141. package/src/renderers/layout/flex.tsx +1 -0
  142. package/src/renderers/layout/grid.tsx +23 -8
  143. package/src/renderers/layout/page.tsx +35 -23
  144. package/src/renderers/layout/semantic.tsx +1 -0
  145. package/src/renderers/layout/stack.tsx +2 -1
  146. package/src/renderers/layout/tabs.tsx +43 -17
  147. package/src/renderers/navigation/header-bar.tsx +1 -0
  148. package/src/renderers/navigation/sidebar.tsx +5 -0
  149. package/src/renderers/overlay/alert-dialog.tsx +1 -0
  150. package/src/renderers/overlay/context-menu.tsx +1 -0
  151. package/src/renderers/overlay/dialog.tsx +1 -0
  152. package/src/renderers/overlay/drawer.tsx +1 -0
  153. package/src/renderers/overlay/dropdown-menu.tsx +1 -0
  154. package/src/renderers/overlay/hover-card.tsx +1 -0
  155. package/src/renderers/overlay/menubar.tsx +1 -0
  156. package/src/renderers/overlay/popover.tsx +1 -0
  157. package/src/renderers/overlay/sheet.tsx +1 -0
  158. package/src/renderers/overlay/tooltip.tsx +1 -0
  159. package/src/renderers/placeholders.tsx +2 -2
  160. package/src/stories/CRMApp.stories.tsx +706 -0
  161. package/src/stories/Guide.mdx +55 -0
  162. package/src/stories/Introduction.mdx +34 -0
  163. package/src/stories/MockedData.stories.tsx +71 -0
  164. package/src/stories/assets/accessibility.png +0 -0
  165. package/src/stories/assets/accessibility.svg +1 -0
  166. package/src/stories/assets/addon-library.png +0 -0
  167. package/src/stories/assets/assets.png +0 -0
  168. package/src/stories/assets/avif-test-image.avif +0 -0
  169. package/src/stories/assets/context.png +0 -0
  170. package/src/stories/assets/discord.svg +1 -0
  171. package/src/stories/assets/docs.png +0 -0
  172. package/src/stories/assets/figma-plugin.png +0 -0
  173. package/src/stories/assets/github.svg +1 -0
  174. package/src/stories/assets/share.png +0 -0
  175. package/src/stories/assets/styling.png +0 -0
  176. package/src/stories/assets/testing.png +0 -0
  177. package/src/stories/assets/theming.png +0 -0
  178. package/src/stories/assets/tutorials.svg +1 -0
  179. package/src/stories/assets/youtube.svg +1 -0
  180. package/src/stories/button.css +30 -0
  181. package/src/stories/header.css +32 -0
  182. package/src/stories/page.css +68 -0
  183. package/src/stories-json/accordion.stories.tsx +43 -0
  184. package/src/stories-json/aggrid.stories.tsx +103 -0
  185. package/src/stories-json/alert.stories.tsx +39 -0
  186. package/src/stories-json/aspect-ratio.stories.tsx +34 -0
  187. package/src/stories-json/avatar.stories.tsx +38 -0
  188. package/src/stories-json/badge.stories.tsx +53 -0
  189. package/src/stories-json/breadcrumb.stories.tsx +30 -0
  190. package/src/stories-json/button-group.stories.tsx +43 -0
  191. package/src/stories-json/button.stories.tsx +73 -0
  192. package/src/stories-json/calendar.stories.tsx +85 -0
  193. package/src/stories-json/card.stories.tsx +48 -0
  194. package/src/stories-json/carousel.stories.tsx +33 -0
  195. package/src/stories-json/charts.stories.tsx +195 -0
  196. package/src/stories-json/chatbot.stories.tsx +248 -0
  197. package/src/stories-json/code-editor.stories.tsx +92 -0
  198. package/src/stories-json/collapsible.stories.tsx +40 -0
  199. package/src/stories-json/controls.stories.tsx +36 -0
  200. package/src/stories-json/dashboard.stories.tsx +318 -0
  201. package/src/stories-json/data-table.stories.tsx +60 -0
  202. package/src/stories-json/data_display_extras.stories.tsx +102 -0
  203. package/src/stories-json/date-picker.stories.tsx +28 -0
  204. package/src/stories-json/detail-view.stories.tsx +258 -0
  205. package/src/stories-json/dialog.stories.tsx +43 -0
  206. package/src/stories-json/feedback_extras.stories.tsx +40 -0
  207. package/src/stories-json/feedback_others.stories.tsx +46 -0
  208. package/src/stories-json/form_advanced.stories.tsx +117 -0
  209. package/src/stories-json/form_extras.stories.tsx +123 -0
  210. package/src/stories-json/grid.stories.tsx +56 -0
  211. package/src/stories-json/icon.stories.tsx +36 -0
  212. package/src/stories-json/input.stories.tsx +52 -0
  213. package/src/stories-json/kanban.stories.tsx +295 -0
  214. package/src/stories-json/layout_extended.stories.tsx +76 -0
  215. package/src/stories-json/layout_flex.stories.tsx +107 -0
  216. package/src/stories-json/list-view.stories.tsx +97 -0
  217. package/src/stories-json/markdown.stories.tsx +129 -0
  218. package/src/stories-json/menus.stories.tsx +63 -0
  219. package/src/stories-json/metric-card.stories.tsx +143 -0
  220. package/src/stories-json/navigation-menu.stories.tsx +37 -0
  221. package/src/stories-json/object-aggrid.stories.tsx +252 -0
  222. package/src/stories-json/object-form.stories.tsx +130 -0
  223. package/src/stories-json/object-gantt.stories.tsx +114 -0
  224. package/src/stories-json/object-grid.stories.tsx +157 -0
  225. package/src/stories-json/object-map.stories.tsx +116 -0
  226. package/src/stories-json/object-view.stories.tsx +118 -0
  227. package/src/stories-json/overlay_extras.stories.tsx +113 -0
  228. package/src/stories-json/overlay_others.stories.tsx +76 -0
  229. package/src/stories-json/page.stories.tsx +55 -0
  230. package/src/stories-json/reports.stories.tsx +163 -0
  231. package/src/stories-json/resizable.stories.tsx +44 -0
  232. package/src/stories-json/select.stories.tsx +34 -0
  233. package/src/stories-json/separator.stories.tsx +41 -0
  234. package/src/stories-json/sidebar.stories.tsx +147 -0
  235. package/src/stories-json/statistic.stories.tsx +44 -0
  236. package/src/stories-json/tabs.stories.tsx +51 -0
  237. package/src/stories-json/timeline.stories.tsx +188 -0
  238. package/src/stories-json/typography.stories.tsx +45 -0
  239. package/src/ui/accordion.tsx +47 -53
  240. package/src/ui/alert-dialog.tsx +103 -117
  241. package/src/ui/alert.tsx +35 -36
  242. package/src/ui/aspect-ratio.tsx +1 -5
  243. package/src/ui/avatar.tsx +41 -42
  244. package/src/ui/badge.tsx +6 -15
  245. package/src/ui/breadcrumb.tsx +81 -75
  246. package/src/ui/button.tsx +10 -11
  247. package/src/ui/calendar.tsx +178 -51
  248. package/src/ui/card.tsx +51 -110
  249. package/src/ui/carousel.tsx +136 -113
  250. package/src/ui/chart.tsx +367 -0
  251. package/src/ui/checkbox.tsx +20 -22
  252. package/src/ui/collapsible.tsx +5 -25
  253. package/src/ui/command.tsx +106 -135
  254. package/src/ui/context-menu.tsx +69 -116
  255. package/src/ui/dialog.tsx +94 -113
  256. package/src/ui/drawer.tsx +82 -99
  257. package/src/ui/dropdown-menu.tsx +134 -188
  258. package/src/ui/form.tsx +51 -40
  259. package/src/ui/hover-card.tsx +18 -33
  260. package/src/ui/index.ts +2 -8
  261. package/src/ui/input-otp.tsx +42 -52
  262. package/src/ui/input.tsx +13 -15
  263. package/src/ui/label.tsx +17 -15
  264. package/src/ui/menubar.tsx +188 -206
  265. package/src/ui/navigation-menu.tsx +96 -136
  266. package/src/ui/pagination.tsx +86 -96
  267. package/src/ui/popover.tsx +24 -41
  268. package/src/ui/progress.tsx +21 -22
  269. package/src/ui/radio-group.tsx +19 -20
  270. package/src/ui/resizable.tsx +32 -42
  271. package/src/ui/scroll-area.tsx +38 -48
  272. package/src/ui/select.tsx +129 -157
  273. package/src/ui/separator.tsx +2 -2
  274. package/src/ui/sheet.tsx +110 -107
  275. package/src/ui/sidebar.tsx +442 -408
  276. package/src/ui/skeleton.tsx +6 -11
  277. package/src/ui/slider.tsx +19 -54
  278. package/src/ui/sonner.tsx +19 -1
  279. package/src/ui/switch.tsx +19 -21
  280. package/src/ui/tabs.tsx +6 -37
  281. package/src/ui/textarea.tsx +8 -4
  282. package/src/ui/toast.tsx +137 -0
  283. package/src/ui/toggle-group.tsx +28 -37
  284. package/src/ui/toggle.tsx +19 -19
  285. package/src/ui/tooltip.tsx +21 -52
  286. package/src/ui/typography.tsx +85 -0
  287. package/tsconfig.json +1 -1
  288. package/vite.config.ts +9 -1
  289. package/vitest.config.ts +5 -0
  290. package/ISSUES_FOUND.md +0 -128
  291. /package/dist/src/{ui → custom}/combobox.d.ts +0 -0
  292. /package/dist/src/{ui → custom}/date-picker.d.ts +0 -0
  293. /package/dist/src/{ui → custom}/empty.d.ts +0 -0
  294. /package/dist/src/{ui → custom}/filter-builder.d.ts +0 -0
  295. /package/dist/src/{ui → custom}/kbd.d.ts +0 -0
  296. /package/dist/src/{ui → custom}/spinner.d.ts +0 -0
  297. /package/src/{ui → custom}/empty.tsx +0 -0
  298. /package/src/{ui → custom}/kbd.tsx +0 -0
  299. /package/src/{ui → custom}/spinner.tsx +0 -0
@@ -11,7 +11,7 @@
11
11
  import * as React from "react"
12
12
  import { Slot } from "@radix-ui/react-slot"
13
13
  import { cva, type VariantProps } from "class-variance-authority"
14
- import { PanelLeftIcon } from "lucide-react"
14
+ import { PanelLeft } from "lucide-react"
15
15
 
16
16
  import { useIsMobile } from "../hooks/use-mobile"
17
17
  import { cn } from "../lib/utils"
@@ -61,208 +61,224 @@ function useSidebar() {
61
61
  return context
62
62
  }
63
63
 
64
- function SidebarProvider({
65
- defaultOpen = true,
66
- open: openProp,
67
- onOpenChange: setOpenProp,
68
- className,
69
- style,
70
- children,
71
- ...props
72
- }: React.ComponentProps<"div"> & {
73
- defaultOpen?: boolean
74
- open?: boolean
75
- onOpenChange?: (open: boolean) => void
76
- }) {
77
- const isMobile = useIsMobile()
78
- const [openMobile, setOpenMobile] = React.useState(false)
79
-
80
- // This is the internal state of the sidebar.
81
- // We use openProp and setOpenProp for control from outside the component.
82
- const [_open, _setOpen] = React.useState(defaultOpen)
83
- const open = openProp ?? _open
84
- const setOpen = React.useCallback(
85
- (value: boolean | ((value: boolean) => boolean)) => {
86
- const openState = typeof value === "function" ? value(open) : value
87
- if (setOpenProp) {
88
- setOpenProp(openState)
89
- } else {
90
- _setOpen(openState)
91
- }
92
-
93
- // This sets the cookie to keep the sidebar state.
94
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
64
+ const SidebarProvider = React.forwardRef<
65
+ HTMLDivElement,
66
+ React.ComponentProps<"div"> & {
67
+ defaultOpen?: boolean
68
+ open?: boolean
69
+ onOpenChange?: (open: boolean) => void
70
+ }
71
+ >(
72
+ (
73
+ {
74
+ defaultOpen = true,
75
+ open: openProp,
76
+ onOpenChange: setOpenProp,
77
+ className,
78
+ style,
79
+ children,
80
+ ...props
95
81
  },
96
- [setOpenProp, open]
97
- )
82
+ ref
83
+ ) => {
84
+ const isMobile = useIsMobile()
85
+ const [openMobile, setOpenMobile] = React.useState(false)
86
+
87
+ // This is the internal state of the sidebar.
88
+ // We use openProp and setOpenProp for control from outside the component.
89
+ const [_open, _setOpen] = React.useState(defaultOpen)
90
+ const open = openProp ?? _open
91
+ const setOpen = React.useCallback(
92
+ (value: boolean | ((value: boolean) => boolean)) => {
93
+ const openState = typeof value === "function" ? value(open) : value
94
+ if (setOpenProp) {
95
+ setOpenProp(openState)
96
+ } else {
97
+ _setOpen(openState)
98
+ }
98
99
 
99
- // Helper to toggle the sidebar.
100
- const toggleSidebar = React.useCallback(() => {
101
- return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
102
- }, [isMobile, setOpen, setOpenMobile])
103
-
104
- // Adds a keyboard shortcut to toggle the sidebar.
105
- React.useEffect(() => {
106
- const handleKeyDown = (event: KeyboardEvent) => {
107
- if (
108
- event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
109
- (event.metaKey || event.ctrlKey)
110
- ) {
111
- event.preventDefault()
112
- toggleSidebar()
100
+ // This sets the cookie to keep the sidebar state.
101
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
102
+ },
103
+ [setOpenProp, open]
104
+ )
105
+
106
+ // Helper to toggle the sidebar.
107
+ const toggleSidebar = React.useCallback(() => {
108
+ return isMobile
109
+ ? setOpenMobile((open) => !open)
110
+ : setOpen((open) => !open)
111
+ }, [isMobile, setOpen, setOpenMobile])
112
+
113
+ // Adds a keyboard shortcut to toggle the sidebar.
114
+ React.useEffect(() => {
115
+ const handleKeyDown = (event: KeyboardEvent) => {
116
+ if (
117
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
118
+ (event.metaKey || event.ctrlKey)
119
+ ) {
120
+ event.preventDefault()
121
+ toggleSidebar()
122
+ }
113
123
  }
114
- }
115
124
 
116
- window.addEventListener("keydown", handleKeyDown)
117
- return () => window.removeEventListener("keydown", handleKeyDown)
118
- }, [toggleSidebar])
119
-
120
- // We add a state so that we can do data-state="expanded" or "collapsed".
121
- // This makes it easier to style the sidebar with Tailwind classes.
122
- const state = open ? "expanded" : "collapsed"
123
-
124
- const contextValue = React.useMemo<SidebarContextProps>(
125
- () => ({
126
- state,
127
- open,
128
- setOpen,
129
- isMobile,
130
- openMobile,
131
- setOpenMobile,
132
- toggleSidebar,
133
- }),
134
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
135
- )
125
+ window.addEventListener("keydown", handleKeyDown)
126
+ return () => window.removeEventListener("keydown", handleKeyDown)
127
+ }, [toggleSidebar])
128
+
129
+ // We add a state so that we can do data-state="expanded" or "collapsed".
130
+ // This makes it easier to style the sidebar with Tailwind classes.
131
+ const state = open ? "expanded" : "collapsed"
132
+
133
+ const contextValue = React.useMemo<SidebarContextProps>(
134
+ () => ({
135
+ state,
136
+ open,
137
+ setOpen,
138
+ isMobile,
139
+ openMobile,
140
+ setOpenMobile,
141
+ toggleSidebar,
142
+ }),
143
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
144
+ )
136
145
 
137
- return (
138
- <SidebarContext.Provider value={contextValue}>
139
- <TooltipProvider delayDuration={0}>
146
+ return (
147
+ <SidebarContext.Provider value={contextValue}>
148
+ <TooltipProvider delayDuration={0}>
149
+ <div
150
+ style={
151
+ {
152
+ "--sidebar-width": SIDEBAR_WIDTH,
153
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
154
+ ...style,
155
+ } as React.CSSProperties
156
+ }
157
+ className={cn(
158
+ "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
159
+ className
160
+ )}
161
+ ref={ref}
162
+ {...props}
163
+ >
164
+ {children}
165
+ </div>
166
+ </TooltipProvider>
167
+ </SidebarContext.Provider>
168
+ )
169
+ }
170
+ )
171
+ SidebarProvider.displayName = "SidebarProvider"
172
+
173
+ const Sidebar = React.forwardRef<
174
+ HTMLDivElement,
175
+ React.ComponentProps<"div"> & {
176
+ side?: "left" | "right"
177
+ variant?: "sidebar" | "floating" | "inset"
178
+ collapsible?: "offcanvas" | "icon" | "none"
179
+ }
180
+ >(
181
+ (
182
+ {
183
+ side = "left",
184
+ variant = "sidebar",
185
+ collapsible = "offcanvas",
186
+ className,
187
+ children,
188
+ ...props
189
+ },
190
+ ref
191
+ ) => {
192
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
193
+
194
+ if (collapsible === "none") {
195
+ return (
140
196
  <div
141
- data-slot="sidebar-wrapper"
142
- style={
143
- {
144
- "--sidebar-width": SIDEBAR_WIDTH,
145
- "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
146
- ...style,
147
- } as React.CSSProperties
148
- }
149
197
  className={cn(
150
- "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
198
+ "flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar text-sidebar-foreground",
151
199
  className
152
200
  )}
201
+ ref={ref}
153
202
  {...props}
154
203
  >
155
204
  {children}
156
205
  </div>
157
- </TooltipProvider>
158
- </SidebarContext.Provider>
159
- )
160
- }
206
+ )
207
+ }
161
208
 
162
- function Sidebar({
163
- side = "left",
164
- variant = "sidebar",
165
- collapsible = "offcanvas",
166
- className,
167
- children,
168
- ...props
169
- }: React.ComponentProps<"div"> & {
170
- side?: "left" | "right"
171
- variant?: "sidebar" | "floating" | "inset"
172
- collapsible?: "offcanvas" | "icon" | "none"
173
- }) {
174
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
175
-
176
- if (collapsible === "none") {
177
- return (
178
- <div
179
- data-slot="sidebar"
180
- className={cn(
181
- "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
182
- className
183
- )}
184
- {...props}
185
- >
186
- {children}
187
- </div>
188
- )
189
- }
209
+ if (isMobile) {
210
+ return (
211
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
212
+ <SheetContent
213
+ data-sidebar="sidebar"
214
+ data-mobile="true"
215
+ className="w-[var(--sidebar-width)] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
216
+ style={
217
+ {
218
+ "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
219
+ } as React.CSSProperties
220
+ }
221
+ side={side}
222
+ >
223
+ <SheetHeader className="sr-only">
224
+ <SheetTitle>Sidebar</SheetTitle>
225
+ <SheetDescription>Displays the mobile sidebar.</SheetDescription>
226
+ </SheetHeader>
227
+ <div className="flex h-full w-full flex-col">{children}</div>
228
+ </SheetContent>
229
+ </Sheet>
230
+ )
231
+ }
190
232
 
191
- if (isMobile) {
192
233
  return (
193
- <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
194
- <SheetContent
195
- data-sidebar="sidebar"
196
- data-slot="sidebar"
197
- data-mobile="true"
198
- className="bg-sidebar text-sidebar-foreground w-[--sidebar-width] p-0 [&>button]:hidden"
199
- style={
200
- {
201
- "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
202
- } as React.CSSProperties
203
- }
204
- side={side}
205
- >
206
- <SheetHeader className="sr-only">
207
- <SheetTitle>Sidebar</SheetTitle>
208
- <SheetDescription>Displays the mobile sidebar.</SheetDescription>
209
- </SheetHeader>
210
- <div className="flex h-full w-full flex-col">{children}</div>
211
- </SheetContent>
212
- </Sheet>
213
- )
214
- }
215
-
216
- return (
217
- <div
218
- className="group peer text-sidebar-foreground hidden md:block"
219
- data-state={state}
220
- data-collapsible={state === "collapsed" ? collapsible : ""}
221
- data-variant={variant}
222
- data-side={side}
223
- data-slot="sidebar"
224
- >
225
- {/* This is what handles the sidebar gap on desktop */}
226
234
  <div
227
- data-slot="sidebar-gap"
228
- className={cn(
229
- "relative w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear",
230
- "group-data-[collapsible=offcanvas]:w-0",
231
- "group-data-[side=right]:rotate-180",
232
- variant === "floating" || variant === "inset"
233
- ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+1rem)]"
234
- : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
235
- )}
236
- />
237
- <div
238
- data-slot="sidebar-container"
239
- className={cn(
240
- "fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
241
- side === "left"
242
- ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
243
- : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
244
- // Adjust the padding for floating and inset variants.
245
- variant === "floating" || variant === "inset"
246
- ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+1rem+2px)]"
247
- : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
248
- className
249
- )}
250
- {...props}
235
+ ref={ref}
236
+ className="group peer hidden text-sidebar-foreground md:block"
237
+ data-state={state}
238
+ data-collapsible={state === "collapsed" ? collapsible : ""}
239
+ data-variant={variant}
240
+ data-side={side}
251
241
  >
242
+ {/* This is what handles the sidebar gap on desktop */}
252
243
  <div
253
- data-sidebar="sidebar"
254
- data-slot="sidebar-inner"
255
- className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
244
+ className={cn(
245
+ "relative w-[var(--sidebar-width)] bg-transparent transition-[width] duration-200 ease-linear",
246
+ "group-data-[collapsible=offcanvas]:w-0",
247
+ "group-data-[side=right]:rotate-180",
248
+ variant === "floating" || variant === "inset"
249
+ ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
250
+ : "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)]"
251
+ )}
252
+ />
253
+ <div
254
+ className={cn(
255
+ "fixed inset-y-0 z-10 hidden h-svh w-[var(--sidebar-width)] transition-[left,right,width] duration-200 ease-linear md:flex",
256
+ side === "left"
257
+ ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
258
+ : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
259
+ // Adjust the padding for floating and inset variants.
260
+ variant === "floating" || variant === "inset"
261
+ ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
262
+ : "group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l",
263
+ className
264
+ )}
265
+ {...props}
256
266
  >
257
- {children}
267
+ <div
268
+ data-sidebar="sidebar"
269
+ 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"
270
+ >
271
+ {children}
272
+ </div>
258
273
  </div>
259
274
  </div>
260
- </div>
261
- )
262
- }
275
+ )
276
+ }
277
+ )
278
+ Sidebar.displayName = "Sidebar"
263
279
 
264
280
  const SidebarTrigger = React.forwardRef<
265
- HTMLButtonElement,
281
+ React.ElementRef<typeof Button>,
266
282
  React.ComponentProps<typeof Button>
267
283
  >(({ className, onClick, ...props }, ref) => {
268
284
  const { toggleSidebar } = useSidebar()
@@ -271,17 +287,16 @@ const SidebarTrigger = React.forwardRef<
271
287
  <Button
272
288
  ref={ref}
273
289
  data-sidebar="trigger"
274
- data-slot="sidebar-trigger"
275
290
  variant="ghost"
276
291
  size="icon"
277
- className={cn("size-7", className)}
292
+ className={cn("h-7 w-7", className)}
278
293
  onClick={(event) => {
279
294
  onClick?.(event)
280
295
  toggleSidebar()
281
296
  }}
282
297
  {...props}
283
298
  >
284
- <PanelLeftIcon />
299
+ <PanelLeft />
285
300
  <span className="sr-only">Toggle Sidebar</span>
286
301
  </Button>
287
302
  )
@@ -298,16 +313,15 @@ const SidebarRail = React.forwardRef<
298
313
  <button
299
314
  ref={ref}
300
315
  data-sidebar="rail"
301
- data-slot="sidebar-rail"
302
316
  aria-label="Toggle Sidebar"
303
317
  tabIndex={-1}
304
318
  onClick={toggleSidebar}
305
319
  title="Toggle Sidebar"
306
320
  className={cn(
307
- "hover:after:bg-sidebar-border 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] sm:flex",
308
- "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
321
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
322
+ "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
309
323
  "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
310
- "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
324
+ "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
311
325
  "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
312
326
  "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
313
327
  className
@@ -318,74 +332,94 @@ const SidebarRail = React.forwardRef<
318
332
  })
319
333
  SidebarRail.displayName = "SidebarRail"
320
334
 
321
- function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
335
+ const SidebarInset = React.forwardRef<
336
+ HTMLDivElement,
337
+ React.ComponentProps<"main">
338
+ >(({ className, ...props }, ref) => {
322
339
  return (
323
340
  <main
324
- data-slot="sidebar-inset"
341
+ ref={ref}
325
342
  className={cn(
326
- "bg-background relative flex w-full flex-1 flex-col",
327
- "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
343
+ "relative flex w-full flex-1 flex-col bg-background",
344
+ "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
328
345
  className
329
346
  )}
330
347
  {...props}
331
348
  />
332
349
  )
333
- }
350
+ })
351
+ SidebarInset.displayName = "SidebarInset"
334
352
 
335
- function SidebarInput({
336
- className,
337
- ...props
338
- }: React.ComponentProps<typeof Input>) {
353
+ const SidebarInput = React.forwardRef<
354
+ React.ElementRef<typeof Input>,
355
+ React.ComponentProps<typeof Input>
356
+ >(({ className, ...props }, ref) => {
339
357
  return (
340
358
  <Input
341
- data-slot="sidebar-input"
359
+ ref={ref}
342
360
  data-sidebar="input"
343
- className={cn("bg-background h-8 w-full shadow-none", className)}
361
+ className={cn(
362
+ "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
363
+ className
364
+ )}
344
365
  {...props}
345
366
  />
346
367
  )
347
- }
368
+ })
369
+ SidebarInput.displayName = "SidebarInput"
348
370
 
349
- function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
371
+ const SidebarHeader = React.forwardRef<
372
+ HTMLDivElement,
373
+ React.ComponentProps<"div">
374
+ >(({ className, ...props }, ref) => {
350
375
  return (
351
376
  <div
352
- data-slot="sidebar-header"
377
+ ref={ref}
353
378
  data-sidebar="header"
354
379
  className={cn("flex flex-col gap-2 p-2", className)}
355
380
  {...props}
356
381
  />
357
382
  )
358
- }
383
+ })
384
+ SidebarHeader.displayName = "SidebarHeader"
359
385
 
360
- function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
386
+ const SidebarFooter = React.forwardRef<
387
+ HTMLDivElement,
388
+ React.ComponentProps<"div">
389
+ >(({ className, ...props }, ref) => {
361
390
  return (
362
391
  <div
363
- data-slot="sidebar-footer"
392
+ ref={ref}
364
393
  data-sidebar="footer"
365
394
  className={cn("flex flex-col gap-2 p-2", className)}
366
395
  {...props}
367
396
  />
368
397
  )
369
- }
398
+ })
399
+ SidebarFooter.displayName = "SidebarFooter"
370
400
 
371
- function SidebarSeparator({
372
- className,
373
- ...props
374
- }: React.ComponentProps<typeof Separator>) {
401
+ const SidebarSeparator = React.forwardRef<
402
+ React.ElementRef<typeof Separator>,
403
+ React.ComponentProps<typeof Separator>
404
+ >(({ className, ...props }, ref) => {
375
405
  return (
376
406
  <Separator
377
- data-slot="sidebar-separator"
407
+ ref={ref}
378
408
  data-sidebar="separator"
379
- className={cn("bg-sidebar-border mx-2 w-auto", className)}
409
+ className={cn("mx-2 w-auto bg-sidebar-border", className)}
380
410
  {...props}
381
411
  />
382
412
  )
383
- }
413
+ })
414
+ SidebarSeparator.displayName = "SidebarSeparator"
384
415
 
385
- function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
416
+ const SidebarContent = React.forwardRef<
417
+ HTMLDivElement,
418
+ React.ComponentProps<"div">
419
+ >(({ className, ...props }, ref) => {
386
420
  return (
387
421
  <div
388
- data-slot="sidebar-content"
422
+ ref={ref}
389
423
  data-sidebar="content"
390
424
  className={cn(
391
425
  "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
@@ -394,101 +428,109 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
394
428
  {...props}
395
429
  />
396
430
  )
397
- }
431
+ })
432
+ SidebarContent.displayName = "SidebarContent"
398
433
 
399
- function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
434
+ const SidebarGroup = React.forwardRef<
435
+ HTMLDivElement,
436
+ React.ComponentProps<"div">
437
+ >(({ className, ...props }, ref) => {
400
438
  return (
401
439
  <div
402
- data-slot="sidebar-group"
440
+ ref={ref}
403
441
  data-sidebar="group"
404
442
  className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
405
443
  {...props}
406
444
  />
407
445
  )
408
- }
446
+ })
447
+ SidebarGroup.displayName = "SidebarGroup"
409
448
 
410
- function SidebarGroupLabel({
411
- className,
412
- asChild = false,
413
- ...props
414
- }: React.ComponentProps<"div"> & { asChild?: boolean }) {
449
+ const SidebarGroupLabel = React.forwardRef<
450
+ HTMLDivElement,
451
+ React.ComponentProps<"div"> & { asChild?: boolean }
452
+ >(({ className, asChild = false, ...props }, ref) => {
415
453
  const Comp = asChild ? Slot : "div"
416
454
 
417
455
  return (
418
456
  <Comp
419
- data-slot="sidebar-group-label"
457
+ ref={ref}
420
458
  data-sidebar="group-label"
421
459
  className={cn(
422
- "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
460
+ "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 [&>svg]:size-4 [&>svg]:shrink-0",
423
461
  "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
424
462
  className
425
463
  )}
426
464
  {...props}
427
465
  />
428
466
  )
429
- }
467
+ })
468
+ SidebarGroupLabel.displayName = "SidebarGroupLabel"
430
469
 
431
- function SidebarGroupAction({
432
- className,
433
- asChild = false,
434
- ...props
435
- }: React.ComponentProps<"button"> & { asChild?: boolean }) {
470
+ const SidebarGroupAction = React.forwardRef<
471
+ HTMLButtonElement,
472
+ React.ComponentProps<"button"> & { asChild?: boolean }
473
+ >(({ className, asChild = false, ...props }, ref) => {
436
474
  const Comp = asChild ? Slot : "button"
437
475
 
438
476
  return (
439
477
  <Comp
440
- data-slot="sidebar-group-action"
478
+ ref={ref}
441
479
  data-sidebar="group-action"
442
480
  className={cn(
443
- "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
481
+ "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",
444
482
  // Increases the hit area of the button on mobile.
445
- "after:absolute after:-inset-2 md:after:hidden",
483
+ "after:absolute after:-inset-2 after:md:hidden",
446
484
  "group-data-[collapsible=icon]:hidden",
447
485
  className
448
486
  )}
449
487
  {...props}
450
488
  />
451
489
  )
452
- }
453
-
454
- function SidebarGroupContent({
455
- className,
456
- ...props
457
- }: React.ComponentProps<"div">) {
458
- return (
459
- <div
460
- data-slot="sidebar-group-content"
461
- data-sidebar="group-content"
462
- className={cn("w-full text-sm", className)}
463
- {...props}
464
- />
465
- )
466
- }
467
-
468
- function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
469
- return (
470
- <ul
471
- data-slot="sidebar-menu"
472
- data-sidebar="menu"
473
- className={cn("flex w-full min-w-0 flex-col gap-1", className)}
474
- {...props}
475
- />
476
- )
477
- }
478
-
479
- function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
480
- return (
481
- <li
482
- data-slot="sidebar-menu-item"
483
- data-sidebar="menu-item"
484
- className={cn("group/menu-item relative", className)}
485
- {...props}
486
- />
487
- )
488
- }
490
+ })
491
+ SidebarGroupAction.displayName = "SidebarGroupAction"
492
+
493
+ const SidebarGroupContent = React.forwardRef<
494
+ HTMLDivElement,
495
+ React.ComponentProps<"div">
496
+ >(({ className, ...props }, ref) => (
497
+ <div
498
+ ref={ref}
499
+ data-sidebar="group-content"
500
+ className={cn("w-full text-sm", className)}
501
+ {...props}
502
+ />
503
+ ))
504
+ SidebarGroupContent.displayName = "SidebarGroupContent"
505
+
506
+ const SidebarMenu = React.forwardRef<
507
+ HTMLUListElement,
508
+ React.ComponentProps<"ul">
509
+ >(({ className, ...props }, ref) => (
510
+ <ul
511
+ ref={ref}
512
+ data-sidebar="menu"
513
+ className={cn("flex w-full min-w-0 flex-col gap-1", className)}
514
+ {...props}
515
+ />
516
+ ))
517
+ SidebarMenu.displayName = "SidebarMenu"
518
+
519
+ const SidebarMenuItem = React.forwardRef<
520
+ HTMLLIElement,
521
+ React.ComponentProps<"li">
522
+ >(({ className, ...props }, ref) => (
523
+ <li
524
+ ref={ref}
525
+ data-sidebar="menu-item"
526
+ className={cn("group/menu-item relative", className)}
527
+ {...props}
528
+ />
529
+ ))
530
+ SidebarMenuItem.displayName = "SidebarMenuItem"
489
531
 
490
532
  const sidebarMenuButtonVariants = cva(
491
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden 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",
533
+ "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",
492
534
  {
493
535
  variants: {
494
536
  variant: {
@@ -499,7 +541,7 @@ const sidebarMenuButtonVariants = cva(
499
541
  size: {
500
542
  default: "h-8 text-sm",
501
543
  sm: "h-7 text-xs",
502
- lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
544
+ lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
503
545
  },
504
546
  },
505
547
  defaultVariants: {
@@ -516,123 +558,122 @@ const SidebarMenuButton = React.forwardRef<
516
558
  isActive?: boolean
517
559
  tooltip?: string | React.ComponentProps<typeof TooltipContent>
518
560
  } & VariantProps<typeof sidebarMenuButtonVariants>
519
- >(({
520
- asChild = false,
521
- isActive = false,
522
- variant = "default",
523
- size = "default",
524
- tooltip,
525
- className,
526
- ...props
527
- }, ref) => {
528
- const Comp = asChild ? Slot : "button"
529
- const { isMobile, state } = useSidebar()
530
-
531
- const button = (
532
- <Comp
533
- ref={ref}
534
- data-slot="sidebar-menu-button"
535
- data-sidebar="menu-button"
536
- data-size={size}
537
- data-active={isActive}
538
- className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
539
- {...props}
540
- />
541
- )
561
+ >(
562
+ (
563
+ {
564
+ asChild = false,
565
+ isActive = false,
566
+ variant = "default",
567
+ size = "default",
568
+ tooltip,
569
+ className,
570
+ ...props
571
+ },
572
+ ref
573
+ ) => {
574
+ const Comp = asChild ? Slot : "button"
575
+ const { isMobile, state } = useSidebar()
576
+
577
+ const button = (
578
+ <Comp
579
+ ref={ref}
580
+ data-sidebar="menu-button"
581
+ data-size={size}
582
+ data-active={isActive}
583
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
584
+ {...props}
585
+ />
586
+ )
542
587
 
543
- if (!tooltip) {
544
- return button
545
- }
588
+ if (!tooltip) {
589
+ return button
590
+ }
546
591
 
547
- if (typeof tooltip === "string") {
548
- tooltip = {
549
- children: tooltip,
592
+ if (typeof tooltip === "string") {
593
+ tooltip = {
594
+ children: tooltip,
595
+ }
550
596
  }
551
- }
552
597
 
553
- return (
554
- <Tooltip>
555
- <TooltipTrigger asChild>{button}</TooltipTrigger>
556
- <TooltipContent
557
- side="right"
558
- align="center"
559
- hidden={state !== "collapsed" || isMobile}
560
- {...tooltip}
561
- />
562
- </Tooltip>
563
- )
564
- })
598
+ return (
599
+ <Tooltip>
600
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
601
+ <TooltipContent
602
+ side="right"
603
+ align="center"
604
+ hidden={state !== "collapsed" || isMobile}
605
+ {...tooltip}
606
+ />
607
+ </Tooltip>
608
+ )
609
+ }
610
+ )
565
611
  SidebarMenuButton.displayName = "SidebarMenuButton"
566
612
 
567
- function SidebarMenuAction({
568
- className,
569
- asChild = false,
570
- showOnHover = false,
571
- ...props
572
- }: React.ComponentProps<"button"> & {
573
- asChild?: boolean
574
- showOnHover?: boolean
575
- }) {
613
+ const SidebarMenuAction = React.forwardRef<
614
+ HTMLButtonElement,
615
+ React.ComponentProps<"button"> & {
616
+ asChild?: boolean
617
+ showOnHover?: boolean
618
+ }
619
+ >(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
576
620
  const Comp = asChild ? Slot : "button"
577
621
 
578
622
  return (
579
623
  <Comp
580
- data-slot="sidebar-menu-action"
624
+ ref={ref}
581
625
  data-sidebar="menu-action"
582
626
  className={cn(
583
- "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
627
+ "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",
584
628
  // Increases the hit area of the button on mobile.
585
- "after:absolute after:-inset-2 md:after:hidden",
629
+ "after:absolute after:-inset-2 after:md:hidden",
586
630
  "peer-data-[size=sm]/menu-button:top-1",
587
631
  "peer-data-[size=default]/menu-button:top-1.5",
588
632
  "peer-data-[size=lg]/menu-button:top-2.5",
589
633
  "group-data-[collapsible=icon]:hidden",
590
634
  showOnHover &&
591
- "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",
592
- className
593
- )}
594
- {...props}
595
- />
596
- )
597
- }
598
-
599
- function SidebarMenuBadge({
600
- className,
601
- ...props
602
- }: React.ComponentProps<"div">) {
603
- return (
604
- <div
605
- data-slot="sidebar-menu-badge"
606
- data-sidebar="menu-badge"
607
- className={cn(
608
- "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
609
- "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
610
- "peer-data-[size=sm]/menu-button:top-1",
611
- "peer-data-[size=default]/menu-button:top-1.5",
612
- "peer-data-[size=lg]/menu-button:top-2.5",
613
- "group-data-[collapsible=icon]:hidden",
635
+ "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",
614
636
  className
615
637
  )}
616
638
  {...props}
617
639
  />
618
640
  )
619
- }
620
-
621
- function SidebarMenuSkeleton({
622
- className,
623
- showIcon = false,
624
- ...props
625
- }: React.ComponentProps<"div"> & {
626
- showIcon?: boolean
627
- }) {
641
+ })
642
+ SidebarMenuAction.displayName = "SidebarMenuAction"
643
+
644
+ const SidebarMenuBadge = React.forwardRef<
645
+ HTMLDivElement,
646
+ React.ComponentProps<"div">
647
+ >(({ className, ...props }, ref) => (
648
+ <div
649
+ ref={ref}
650
+ data-sidebar="menu-badge"
651
+ className={cn(
652
+ "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",
653
+ "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
654
+ "peer-data-[size=sm]/menu-button:top-1",
655
+ "peer-data-[size=default]/menu-button:top-1.5",
656
+ "peer-data-[size=lg]/menu-button:top-2.5",
657
+ "group-data-[collapsible=icon]:hidden",
658
+ className
659
+ )}
660
+ {...props}
661
+ />
662
+ ))
663
+ SidebarMenuBadge.displayName = "SidebarMenuBadge"
664
+
665
+ const SidebarMenuSkeleton = React.forwardRef<
666
+ HTMLDivElement,
667
+ React.ComponentProps<"div"> & {
668
+ showIcon?: boolean
669
+ }
670
+ >(({ className, showIcon = false, ...props }, ref) => {
628
671
  // Random width between 50 to 90%.
629
- const [width] = React.useState(() => {
630
- return `${Math.floor(Math.random() * 40) + 50}%`
631
- })
672
+ const [width] = React.useState(() => `${Math.floor(Math.random() * 40) + 50}%`)
632
673
 
633
674
  return (
634
675
  <div
635
- data-slot="sidebar-menu-skeleton"
676
+ ref={ref}
636
677
  data-sidebar="menu-skeleton"
637
678
  className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
638
679
  {...props}
@@ -644,7 +685,7 @@ function SidebarMenuSkeleton({
644
685
  />
645
686
  )}
646
687
  <Skeleton
647
- className="h-4 max-w-(--skeleton-width) flex-1"
688
+ className="h-4 max-w-[--skeleton-width] flex-1"
648
689
  data-sidebar="menu-skeleton-text"
649
690
  style={
650
691
  {
@@ -654,58 +695,50 @@ function SidebarMenuSkeleton({
654
695
  />
655
696
  </div>
656
697
  )
657
- }
658
-
659
- function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
660
- return (
661
- <ul
662
- data-slot="sidebar-menu-sub"
663
- data-sidebar="menu-sub"
664
- className={cn(
665
- "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
666
- "group-data-[collapsible=icon]:hidden",
667
- className
668
- )}
669
- {...props}
670
- />
671
- )
672
- }
673
-
674
- function SidebarMenuSubItem({
675
- className,
676
- ...props
677
- }: React.ComponentProps<"li">) {
678
- return (
679
- <li
680
- data-slot="sidebar-menu-sub-item"
681
- data-sidebar="menu-sub-item"
682
- className={cn("group/menu-sub-item relative", className)}
683
- {...props}
684
- />
685
- )
686
- }
687
-
688
- function SidebarMenuSubButton({
689
- asChild = false,
690
- size = "md",
691
- isActive = false,
692
- className,
693
- ...props
694
- }: React.ComponentProps<"a"> & {
695
- asChild?: boolean
696
- size?: "sm" | "md"
697
- isActive?: boolean
698
- }) {
698
+ })
699
+ SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
700
+
701
+ const SidebarMenuSub = React.forwardRef<
702
+ HTMLUListElement,
703
+ React.ComponentProps<"ul">
704
+ >(({ className, ...props }, ref) => (
705
+ <ul
706
+ ref={ref}
707
+ data-sidebar="menu-sub"
708
+ className={cn(
709
+ "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",
710
+ "group-data-[collapsible=icon]:hidden",
711
+ className
712
+ )}
713
+ {...props}
714
+ />
715
+ ))
716
+ SidebarMenuSub.displayName = "SidebarMenuSub"
717
+
718
+ const SidebarMenuSubItem = React.forwardRef<
719
+ HTMLLIElement,
720
+ React.ComponentProps<"li">
721
+ >(({ ...props }, ref) => <li ref={ref} {...props} />)
722
+ SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
723
+
724
+ const SidebarMenuSubButton = React.forwardRef<
725
+ HTMLAnchorElement,
726
+ React.ComponentProps<"a"> & {
727
+ asChild?: boolean
728
+ size?: "sm" | "md"
729
+ isActive?: boolean
730
+ }
731
+ >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
699
732
  const Comp = asChild ? Slot : "a"
700
733
 
701
734
  return (
702
735
  <Comp
703
- data-slot="sidebar-menu-sub-button"
736
+ ref={ref}
704
737
  data-sidebar="menu-sub-button"
705
738
  data-size={size}
706
739
  data-active={isActive}
707
740
  className={cn(
708
- "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 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",
741
+ "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",
709
742
  "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
710
743
  size === "sm" && "text-xs",
711
744
  size === "md" && "text-sm",
@@ -715,7 +748,8 @@ function SidebarMenuSubButton({
715
748
  {...props}
716
749
  />
717
750
  )
718
- }
751
+ })
752
+ SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
719
753
 
720
754
  export {
721
755
  Sidebar,