@modern-admin/ui 0.1.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 (268) hide show
  1. package/dist/components/accordion.d.ts +7 -0
  2. package/dist/components/accordion.d.ts.map +1 -0
  3. package/dist/components/accordion.jsx +19 -0
  4. package/dist/components/accordion.jsx.map +1 -0
  5. package/dist/components/alert-dialog.d.ts +22 -0
  6. package/dist/components/alert-dialog.d.ts.map +1 -0
  7. package/dist/components/alert-dialog.jsx +27 -0
  8. package/dist/components/alert-dialog.jsx.map +1 -0
  9. package/dist/components/audit-timeline.d.ts +24 -0
  10. package/dist/components/audit-timeline.d.ts.map +1 -0
  11. package/dist/components/audit-timeline.jsx +60 -0
  12. package/dist/components/audit-timeline.jsx.map +1 -0
  13. package/dist/components/avatar.d.ts +6 -0
  14. package/dist/components/avatar.d.ts.map +1 -0
  15. package/dist/components/avatar.jsx +10 -0
  16. package/dist/components/avatar.jsx.map +1 -0
  17. package/dist/components/badge.d.ts +10 -0
  18. package/dist/components/badge.d.ts.map +1 -0
  19. package/dist/components/badge.jsx +19 -0
  20. package/dist/components/badge.jsx.map +1 -0
  21. package/dist/components/breadcrumb.d.ts +17 -0
  22. package/dist/components/breadcrumb.d.ts.map +1 -0
  23. package/dist/components/breadcrumb.jsx +27 -0
  24. package/dist/components/breadcrumb.jsx.map +1 -0
  25. package/dist/components/button.d.ts +12 -0
  26. package/dist/components/button.d.ts.map +1 -0
  27. package/dist/components/button.jsx +37 -0
  28. package/dist/components/button.jsx.map +1 -0
  29. package/dist/components/calendar.d.ts +9 -0
  30. package/dist/components/calendar.d.ts.map +1 -0
  31. package/dist/components/calendar.jsx +102 -0
  32. package/dist/components/calendar.jsx.map +1 -0
  33. package/dist/components/card.d.ts +8 -0
  34. package/dist/components/card.d.ts.map +1 -0
  35. package/dist/components/card.jsx +18 -0
  36. package/dist/components/card.jsx.map +1 -0
  37. package/dist/components/chart.d.ts +97 -0
  38. package/dist/components/chart.d.ts.map +1 -0
  39. package/dist/components/chart.jsx +233 -0
  40. package/dist/components/chart.jsx.map +1 -0
  41. package/dist/components/checkbox.d.ts +4 -0
  42. package/dist/components/checkbox.d.ts.map +1 -0
  43. package/dist/components/checkbox.jsx +11 -0
  44. package/dist/components/checkbox.jsx.map +1 -0
  45. package/dist/components/combobox.d.ts +46 -0
  46. package/dist/components/combobox.d.ts.map +1 -0
  47. package/dist/components/combobox.jsx +145 -0
  48. package/dist/components/combobox.jsx.map +1 -0
  49. package/dist/components/command.d.ts +80 -0
  50. package/dist/components/command.d.ts.map +1 -0
  51. package/dist/components/command.jsx +32 -0
  52. package/dist/components/command.jsx.map +1 -0
  53. package/dist/components/date-picker.d.ts +24 -0
  54. package/dist/components/date-picker.d.ts.map +1 -0
  55. package/dist/components/date-picker.jsx +149 -0
  56. package/dist/components/date-picker.jsx.map +1 -0
  57. package/dist/components/date-range-input.d.ts +22 -0
  58. package/dist/components/date-range-input.d.ts.map +1 -0
  59. package/dist/components/date-range-input.jsx +202 -0
  60. package/dist/components/date-range-input.jsx.map +1 -0
  61. package/dist/components/dialog.d.ts +19 -0
  62. package/dist/components/dialog.d.ts.map +1 -0
  63. package/dist/components/dialog.jsx +30 -0
  64. package/dist/components/dialog.jsx.map +1 -0
  65. package/dist/components/diff-view.d.ts +24 -0
  66. package/dist/components/diff-view.d.ts.map +1 -0
  67. package/dist/components/diff-view.jsx +69 -0
  68. package/dist/components/diff-view.jsx.map +1 -0
  69. package/dist/components/dropdown-menu.d.ts +27 -0
  70. package/dist/components/dropdown-menu.d.ts.map +1 -0
  71. package/dist/components/dropdown-menu.jsx +48 -0
  72. package/dist/components/dropdown-menu.jsx.map +1 -0
  73. package/dist/components/empty.d.ts +15 -0
  74. package/dist/components/empty.d.ts.map +1 -0
  75. package/dist/components/empty.jsx +27 -0
  76. package/dist/components/empty.jsx.map +1 -0
  77. package/dist/components/field.d.ts +23 -0
  78. package/dist/components/field.d.ts.map +1 -0
  79. package/dist/components/field.jsx +60 -0
  80. package/dist/components/field.jsx.map +1 -0
  81. package/dist/components/file-input.d.ts +50 -0
  82. package/dist/components/file-input.d.ts.map +1 -0
  83. package/dist/components/file-input.jsx +104 -0
  84. package/dist/components/file-input.jsx.map +1 -0
  85. package/dist/components/form.d.ts +20 -0
  86. package/dist/components/form.d.ts.map +1 -0
  87. package/dist/components/form.jsx +66 -0
  88. package/dist/components/form.jsx.map +1 -0
  89. package/dist/components/info-tooltip.d.ts +11 -0
  90. package/dist/components/info-tooltip.d.ts.map +1 -0
  91. package/dist/components/info-tooltip.jsx +17 -0
  92. package/dist/components/info-tooltip.jsx.map +1 -0
  93. package/dist/components/input.d.ts +13 -0
  94. package/dist/components/input.d.ts.map +1 -0
  95. package/dist/components/input.jsx +19 -0
  96. package/dist/components/input.jsx.map +1 -0
  97. package/dist/components/json-editor.d.ts +23 -0
  98. package/dist/components/json-editor.d.ts.map +1 -0
  99. package/dist/components/json-editor.jsx +143 -0
  100. package/dist/components/json-editor.jsx.map +1 -0
  101. package/dist/components/kbd.d.ts +15 -0
  102. package/dist/components/kbd.d.ts.map +1 -0
  103. package/dist/components/kbd.jsx +23 -0
  104. package/dist/components/kbd.jsx.map +1 -0
  105. package/dist/components/key-value-editor.d.ts +92 -0
  106. package/dist/components/key-value-editor.d.ts.map +1 -0
  107. package/dist/components/key-value-editor.jsx +187 -0
  108. package/dist/components/key-value-editor.jsx.map +1 -0
  109. package/dist/components/keyboard-shortcuts-help.d.ts +17 -0
  110. package/dist/components/keyboard-shortcuts-help.d.ts.map +1 -0
  111. package/dist/components/keyboard-shortcuts-help.jsx +97 -0
  112. package/dist/components/keyboard-shortcuts-help.jsx.map +1 -0
  113. package/dist/components/label.d.ts +5 -0
  114. package/dist/components/label.d.ts.map +1 -0
  115. package/dist/components/label.jsx +8 -0
  116. package/dist/components/label.jsx.map +1 -0
  117. package/dist/components/media-preview.d.ts +30 -0
  118. package/dist/components/media-preview.d.ts.map +1 -0
  119. package/dist/components/media-preview.jsx +189 -0
  120. package/dist/components/media-preview.jsx.map +1 -0
  121. package/dist/components/multi-file-input.d.ts +76 -0
  122. package/dist/components/multi-file-input.d.ts.map +1 -0
  123. package/dist/components/multi-file-input.jsx +131 -0
  124. package/dist/components/multi-file-input.jsx.map +1 -0
  125. package/dist/components/password-input.d.ts +10 -0
  126. package/dist/components/password-input.d.ts.map +1 -0
  127. package/dist/components/password-input.jsx +18 -0
  128. package/dist/components/password-input.jsx.map +1 -0
  129. package/dist/components/popover.d.ts +7 -0
  130. package/dist/components/popover.d.ts.map +1 -0
  131. package/dist/components/popover.jsx +11 -0
  132. package/dist/components/popover.jsx.map +1 -0
  133. package/dist/components/revision-timeline.d.ts +30 -0
  134. package/dist/components/revision-timeline.d.ts.map +1 -0
  135. package/dist/components/revision-timeline.jsx +42 -0
  136. package/dist/components/revision-timeline.jsx.map +1 -0
  137. package/dist/components/richtext-editor.d.ts +43 -0
  138. package/dist/components/richtext-editor.d.ts.map +1 -0
  139. package/dist/components/richtext-editor.jsx +319 -0
  140. package/dist/components/richtext-editor.jsx.map +1 -0
  141. package/dist/components/richtext-mode.d.ts +23 -0
  142. package/dist/components/richtext-mode.d.ts.map +1 -0
  143. package/dist/components/richtext-mode.js +36 -0
  144. package/dist/components/richtext-mode.js.map +1 -0
  145. package/dist/components/richtext-render.d.ts +8 -0
  146. package/dist/components/richtext-render.d.ts.map +1 -0
  147. package/dist/components/richtext-render.jsx +33 -0
  148. package/dist/components/richtext-render.jsx.map +1 -0
  149. package/dist/components/richtext-sync.d.ts +37 -0
  150. package/dist/components/richtext-sync.d.ts.map +1 -0
  151. package/dist/components/richtext-sync.js +46 -0
  152. package/dist/components/richtext-sync.js.map +1 -0
  153. package/dist/components/scroll-area.d.ts +5 -0
  154. package/dist/components/scroll-area.d.ts.map +1 -0
  155. package/dist/components/scroll-area.jsx +16 -0
  156. package/dist/components/scroll-area.jsx.map +1 -0
  157. package/dist/components/select.d.ts +36 -0
  158. package/dist/components/select.d.ts.map +1 -0
  159. package/dist/components/select.jsx +87 -0
  160. package/dist/components/select.jsx.map +1 -0
  161. package/dist/components/separator.d.ts +4 -0
  162. package/dist/components/separator.d.ts.map +1 -0
  163. package/dist/components/separator.jsx +6 -0
  164. package/dist/components/separator.jsx.map +1 -0
  165. package/dist/components/sheet.d.ts +29 -0
  166. package/dist/components/sheet.d.ts.map +1 -0
  167. package/dist/components/sheet.jsx +44 -0
  168. package/dist/components/sheet.jsx.map +1 -0
  169. package/dist/components/sidebar.d.ts +70 -0
  170. package/dist/components/sidebar.d.ts.map +1 -0
  171. package/dist/components/sidebar.jsx +245 -0
  172. package/dist/components/sidebar.jsx.map +1 -0
  173. package/dist/components/skeleton.d.ts +3 -0
  174. package/dist/components/skeleton.d.ts.map +1 -0
  175. package/dist/components/skeleton.jsx +6 -0
  176. package/dist/components/skeleton.jsx.map +1 -0
  177. package/dist/components/sonner.d.ts +6 -0
  178. package/dist/components/sonner.d.ts.map +1 -0
  179. package/dist/components/sonner.jsx +29 -0
  180. package/dist/components/sonner.jsx.map +1 -0
  181. package/dist/components/switch.d.ts +4 -0
  182. package/dist/components/switch.d.ts.map +1 -0
  183. package/dist/components/switch.jsx +8 -0
  184. package/dist/components/switch.jsx.map +1 -0
  185. package/dist/components/table.d.ts +10 -0
  186. package/dist/components/table.d.ts.map +1 -0
  187. package/dist/components/table.jsx +21 -0
  188. package/dist/components/table.jsx.map +1 -0
  189. package/dist/components/tabs.d.ts +7 -0
  190. package/dist/components/tabs.d.ts.map +1 -0
  191. package/dist/components/tabs.jsx +14 -0
  192. package/dist/components/tabs.jsx.map +1 -0
  193. package/dist/components/textarea.d.ts +4 -0
  194. package/dist/components/textarea.d.ts.map +1 -0
  195. package/dist/components/textarea.jsx +5 -0
  196. package/dist/components/textarea.jsx.map +1 -0
  197. package/dist/components/tooltip.d.ts +7 -0
  198. package/dist/components/tooltip.d.ts.map +1 -0
  199. package/dist/components/tooltip.jsx +11 -0
  200. package/dist/components/tooltip.jsx.map +1 -0
  201. package/dist/index.d.ts +52 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +72 -0
  204. package/dist/index.js.map +1 -0
  205. package/dist/lib/theme.d.ts +11 -0
  206. package/dist/lib/theme.d.ts.map +1 -0
  207. package/dist/lib/theme.js +44 -0
  208. package/dist/lib/theme.js.map +1 -0
  209. package/dist/lib/utils.d.ts +3 -0
  210. package/dist/lib/utils.d.ts.map +1 -0
  211. package/dist/lib/utils.js +6 -0
  212. package/dist/lib/utils.js.map +1 -0
  213. package/dist/styles.css +242 -0
  214. package/package.json +85 -0
  215. package/src/components/accordion.tsx +48 -0
  216. package/src/components/alert-dialog.tsx +113 -0
  217. package/src/components/audit-timeline.tsx +102 -0
  218. package/src/components/avatar.tsx +42 -0
  219. package/src/components/badge.tsx +34 -0
  220. package/src/components/breadcrumb.tsx +99 -0
  221. package/src/components/button.tsx +58 -0
  222. package/src/components/calendar.tsx +176 -0
  223. package/src/components/card.tsx +60 -0
  224. package/src/components/chart.tsx +558 -0
  225. package/src/components/checkbox.tsx +23 -0
  226. package/src/components/combobox.tsx +264 -0
  227. package/src/components/command.tsx +120 -0
  228. package/src/components/date-picker.tsx +221 -0
  229. package/src/components/date-range-input.tsx +295 -0
  230. package/src/components/dialog.tsx +94 -0
  231. package/src/components/diff-view.tsx +182 -0
  232. package/src/components/dropdown-menu.tsx +165 -0
  233. package/src/components/empty.tsx +100 -0
  234. package/src/components/field.tsx +168 -0
  235. package/src/components/file-input.tsx +233 -0
  236. package/src/components/form.tsx +152 -0
  237. package/src/components/info-tooltip.tsx +40 -0
  238. package/src/components/input.tsx +55 -0
  239. package/src/components/json-editor.tsx +210 -0
  240. package/src/components/kbd.tsx +35 -0
  241. package/src/components/key-value-editor.tsx +423 -0
  242. package/src/components/keyboard-shortcuts-help.tsx +136 -0
  243. package/src/components/label.tsx +16 -0
  244. package/src/components/media-preview.tsx +278 -0
  245. package/src/components/multi-file-input.tsx +315 -0
  246. package/src/components/password-input.tsx +50 -0
  247. package/src/components/popover.tsx +26 -0
  248. package/src/components/revision-timeline.tsx +93 -0
  249. package/src/components/richtext-editor.tsx +624 -0
  250. package/src/components/richtext-mode.ts +39 -0
  251. package/src/components/richtext-render.tsx +51 -0
  252. package/src/components/richtext-sync.ts +57 -0
  253. package/src/components/scroll-area.tsx +41 -0
  254. package/src/components/select.tsx +200 -0
  255. package/src/components/separator.tsx +21 -0
  256. package/src/components/sheet.tsx +109 -0
  257. package/src/components/sidebar.tsx +660 -0
  258. package/src/components/skeleton.tsx +9 -0
  259. package/src/components/sonner.tsx +45 -0
  260. package/src/components/switch.tsx +24 -0
  261. package/src/components/table.tsx +93 -0
  262. package/src/components/tabs.tsx +57 -0
  263. package/src/components/textarea.tsx +18 -0
  264. package/src/components/tooltip.tsx +25 -0
  265. package/src/index.ts +342 -0
  266. package/src/lib/theme.ts +45 -0
  267. package/src/lib/utils.ts +6 -0
  268. package/src/styles.css +242 -0
@@ -0,0 +1,660 @@
1
+ // shadcn-style Sidebar — collapsible side navigation built on Sheet (mobile)
2
+ // + a CSS-variable-driven layout (desktop). Composed of Provider/Sidebar/
3
+ // Inset + Group/Menu primitives. Faithful port of the canonical recipe at
4
+ // https://ui.shadcn.com/docs/components/sidebar with two project-specific
5
+ // tweaks: state is persisted in localStorage instead of cookies (so the
6
+ // component works in non-SSR Vite apps without server plumbing), and the
7
+ // `width-mobile`/`width-icon` CSS variables are exposed via the @theme
8
+ // block in styles.css. All other class names match the upstream component.
9
+
10
+ import * as React from 'react'
11
+ import { Slot } from '@radix-ui/react-slot'
12
+ import { cva, type VariantProps } from 'class-variance-authority'
13
+ import { PanelLeft } from 'lucide-react'
14
+ import { cn } from '../lib/utils.js'
15
+ import { Button } from './button.js'
16
+ import { Input } from './input.js'
17
+ import { Separator } from './separator.js'
18
+ import { Sheet, SheetContent, SheetDescription, SheetTitle } from './sheet.js'
19
+ import { Skeleton } from './skeleton.js'
20
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip.js'
21
+
22
+ const SIDEBAR_STATE_KEY = 'sidebar:state'
23
+ const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
24
+ const SIDEBAR_WIDTH = '16rem'
25
+ const SIDEBAR_WIDTH_MOBILE = '18rem'
26
+ const SIDEBAR_WIDTH_ICON = '3rem'
27
+
28
+ interface SidebarContextValue {
29
+ state: 'expanded' | 'collapsed'
30
+ open: boolean
31
+ setOpen(open: boolean): void
32
+ openMobile: boolean
33
+ setOpenMobile(open: boolean): void
34
+ isMobile: boolean
35
+ toggleSidebar(): void
36
+ }
37
+
38
+ const SidebarContext = React.createContext<SidebarContextValue | null>(null)
39
+
40
+ export function useSidebar(): SidebarContextValue {
41
+ const ctx = React.useContext(SidebarContext)
42
+ if (!ctx) throw new Error('useSidebar must be used within <SidebarProvider>')
43
+ return ctx
44
+ }
45
+
46
+ function useIsMobile(breakpoint = 768): boolean {
47
+ const [isMobile, setIsMobile] = React.useState(false)
48
+ React.useEffect(() => {
49
+ const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`)
50
+ const update = (): void => setIsMobile(mql.matches)
51
+ update()
52
+ mql.addEventListener('change', update)
53
+ return () => mql.removeEventListener('change', update)
54
+ }, [breakpoint])
55
+ return isMobile
56
+ }
57
+
58
+ export interface SidebarProviderProps extends React.HTMLAttributes<HTMLDivElement> {
59
+ defaultOpen?: boolean
60
+ open?: boolean
61
+ onOpenChange?(open: boolean): void
62
+ }
63
+
64
+ export const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
65
+ (
66
+ {
67
+ defaultOpen = true,
68
+ open: openProp,
69
+ onOpenChange: setOpenProp,
70
+ className,
71
+ style,
72
+ children,
73
+ ...props
74
+ },
75
+ ref,
76
+ ) => {
77
+ const isMobile = useIsMobile()
78
+ const [openMobile, setOpenMobile] = React.useState(false)
79
+
80
+ const [_open, _setOpen] = React.useState(() => {
81
+ if (typeof localStorage === 'undefined') return defaultOpen
82
+ const v = localStorage.getItem(SIDEBAR_STATE_KEY)
83
+ return v == null ? defaultOpen : v === 'true'
84
+ })
85
+ const open = openProp ?? _open
86
+ const setOpen = React.useCallback(
87
+ (value: boolean | ((v: boolean) => boolean)) => {
88
+ const next = typeof value === 'function' ? value(open) : value
89
+ if (setOpenProp) setOpenProp(next)
90
+ else _setOpen(next)
91
+ if (typeof localStorage !== 'undefined') {
92
+ localStorage.setItem(SIDEBAR_STATE_KEY, String(next))
93
+ }
94
+ },
95
+ [setOpenProp, open],
96
+ )
97
+
98
+ const toggleSidebar = React.useCallback(() => {
99
+ if (isMobile) setOpenMobile((v) => !v)
100
+ else setOpen((v) => !v)
101
+ }, [isMobile, setOpen])
102
+
103
+ React.useEffect(() => {
104
+ const onKey = (e: KeyboardEvent): void => {
105
+ if (
106
+ e.key === SIDEBAR_KEYBOARD_SHORTCUT &&
107
+ (e.metaKey || e.ctrlKey) &&
108
+ !e.shiftKey &&
109
+ !e.altKey
110
+ ) {
111
+ e.preventDefault()
112
+ toggleSidebar()
113
+ }
114
+ }
115
+ window.addEventListener('keydown', onKey)
116
+ return () => window.removeEventListener('keydown', onKey)
117
+ }, [toggleSidebar])
118
+
119
+ const state: 'expanded' | 'collapsed' = open ? 'expanded' : 'collapsed'
120
+
121
+ const value = React.useMemo<SidebarContextValue>(
122
+ () => ({
123
+ state,
124
+ open,
125
+ setOpen,
126
+ openMobile,
127
+ setOpenMobile,
128
+ isMobile,
129
+ toggleSidebar,
130
+ }),
131
+ [state, open, setOpen, openMobile, isMobile, toggleSidebar],
132
+ )
133
+
134
+ return (
135
+ <SidebarContext.Provider value={value}>
136
+ <TooltipProvider delayDuration={0}>
137
+ <div
138
+ ref={ref}
139
+ data-slot="sidebar-wrapper"
140
+ style={
141
+ {
142
+ '--sidebar-width': SIDEBAR_WIDTH,
143
+ '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
144
+ ...style,
145
+ } as React.CSSProperties
146
+ }
147
+ className={cn(
148
+ 'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-muted',
149
+ className,
150
+ )}
151
+ {...props}
152
+ >
153
+ {children}
154
+ </div>
155
+ </TooltipProvider>
156
+ </SidebarContext.Provider>
157
+ )
158
+ },
159
+ )
160
+ SidebarProvider.displayName = 'SidebarProvider'
161
+
162
+ export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
163
+ side?: 'left' | 'right'
164
+ variant?: 'sidebar' | 'floating' | 'inset'
165
+ collapsible?: 'offcanvas' | 'icon' | 'none'
166
+ }
167
+
168
+ export const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
169
+ (
170
+ { side = 'left', variant = 'sidebar', collapsible = 'offcanvas', className, children, ...props },
171
+ ref,
172
+ ) => {
173
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
174
+
175
+ if (collapsible === 'none') {
176
+ return (
177
+ <div
178
+ ref={ref}
179
+ className={cn(
180
+ 'flex h-full w-[var(--sidebar-width)] flex-col bg-card text-foreground',
181
+ className,
182
+ )}
183
+ {...props}
184
+ >
185
+ {children}
186
+ </div>
187
+ )
188
+ }
189
+
190
+ if (isMobile) {
191
+ return (
192
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
193
+ <SheetContent
194
+ data-sidebar="sidebar"
195
+ data-mobile="true"
196
+ side={side}
197
+ className="w-[var(--sidebar-width)] bg-card p-0 text-foreground [&>button]:hidden"
198
+ style={{ '--sidebar-width': SIDEBAR_WIDTH_MOBILE } as React.CSSProperties}
199
+ >
200
+ <SheetTitle className="sr-only">Sidebar</SheetTitle>
201
+ <SheetDescription className="sr-only">Navigation menu</SheetDescription>
202
+ <div className="flex h-full w-full flex-col">{children}</div>
203
+ </SheetContent>
204
+ </Sheet>
205
+ )
206
+ }
207
+
208
+ return (
209
+ <div
210
+ ref={ref}
211
+ className="group peer hidden text-foreground md:block"
212
+ data-state={state}
213
+ data-collapsible={state === 'collapsed' ? collapsible : ''}
214
+ data-variant={variant}
215
+ data-side={side}
216
+ >
217
+ {/* Spacer that takes up width in flow */}
218
+ <div
219
+ className={cn(
220
+ 'relative h-svh w-[var(--sidebar-width)] bg-transparent transition-[width] duration-200 ease-linear',
221
+ 'group-data-[collapsible=offcanvas]:w-0',
222
+ 'group-data-[side=right]:rotate-180',
223
+ variant === 'floating' || variant === 'inset'
224
+ ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
225
+ : 'group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)]',
226
+ )}
227
+ />
228
+ {/* Actual sidebar — fixed-positioned, width animated */}
229
+ <div
230
+ className={cn(
231
+ 'fixed inset-y-0 z-40 hidden h-svh w-[var(--sidebar-width)] transition-[left,right,width] duration-200 ease-linear md:flex',
232
+ side === 'left'
233
+ ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
234
+ : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
235
+ variant === 'floating' || variant === 'inset'
236
+ ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
237
+ : 'group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l border-border',
238
+ className,
239
+ )}
240
+ {...props}
241
+ >
242
+ <div
243
+ data-sidebar="sidebar"
244
+ className={cn(
245
+ 'flex h-full w-full flex-col bg-card group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-border group-data-[variant=floating]:shadow',
246
+ )}
247
+ >
248
+ {children}
249
+ </div>
250
+ </div>
251
+ </div>
252
+ )
253
+ },
254
+ )
255
+ Sidebar.displayName = 'Sidebar'
256
+
257
+ export const SidebarTrigger = React.forwardRef<
258
+ React.ElementRef<typeof Button>,
259
+ React.ComponentPropsWithoutRef<typeof Button>
260
+ >(({ className, onClick, ...props }, ref) => {
261
+ const { toggleSidebar } = useSidebar()
262
+ return (
263
+ <Button
264
+ ref={ref}
265
+ data-sidebar="trigger"
266
+ variant="ghost"
267
+ size="icon"
268
+ className={cn('size-7', className)}
269
+ onClick={(e) => {
270
+ onClick?.(e)
271
+ toggleSidebar()
272
+ }}
273
+ {...props}
274
+ >
275
+ <PanelLeft className="size-4" />
276
+ <span className="sr-only">Toggle sidebar</span>
277
+ </Button>
278
+ )
279
+ })
280
+ SidebarTrigger.displayName = 'SidebarTrigger'
281
+
282
+ export const SidebarRail = React.forwardRef<
283
+ HTMLButtonElement,
284
+ React.ButtonHTMLAttributes<HTMLButtonElement>
285
+ >(({ className, ...props }, ref) => {
286
+ const { toggleSidebar } = useSidebar()
287
+ return (
288
+ <button
289
+ ref={ref}
290
+ type="button"
291
+ data-sidebar="rail"
292
+ aria-label="Toggle sidebar"
293
+ tabIndex={-1}
294
+ onClick={toggleSidebar}
295
+ title="Toggle sidebar"
296
+ className={cn(
297
+ '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-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
298
+ '[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
299
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
300
+ 'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-card',
301
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
302
+ '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
303
+ className,
304
+ )}
305
+ {...props}
306
+ />
307
+ )
308
+ })
309
+ SidebarRail.displayName = 'SidebarRail'
310
+
311
+ export const SidebarInset = React.forwardRef<
312
+ HTMLDivElement,
313
+ React.HTMLAttributes<HTMLDivElement>
314
+ >(({ className, ...props }, ref) => (
315
+ <main
316
+ ref={ref}
317
+ className={cn(
318
+ 'relative flex min-h-svh flex-1 flex-col bg-background',
319
+ 'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] 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',
320
+ className,
321
+ )}
322
+ {...props}
323
+ />
324
+ ))
325
+ SidebarInset.displayName = 'SidebarInset'
326
+
327
+ export const SidebarInput = React.forwardRef<
328
+ React.ElementRef<typeof Input>,
329
+ React.ComponentPropsWithoutRef<typeof Input>
330
+ >(({ className, ...props }, ref) => (
331
+ <Input
332
+ ref={ref}
333
+ data-sidebar="input"
334
+ className={cn('h-8 w-full bg-background shadow-none focus-visible:ring-2', className)}
335
+ {...props}
336
+ />
337
+ ))
338
+ SidebarInput.displayName = 'SidebarInput'
339
+
340
+ export const SidebarHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
341
+ ({ className, ...props }, ref) => (
342
+ <div
343
+ ref={ref}
344
+ data-sidebar="header"
345
+ className={cn('flex flex-col gap-2 p-2', className)}
346
+ {...props}
347
+ />
348
+ ),
349
+ )
350
+ SidebarHeader.displayName = 'SidebarHeader'
351
+
352
+ export const SidebarFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
353
+ ({ className, ...props }, ref) => (
354
+ <div
355
+ ref={ref}
356
+ data-sidebar="footer"
357
+ className={cn('flex flex-col gap-2 p-2', className)}
358
+ {...props}
359
+ />
360
+ ),
361
+ )
362
+ SidebarFooter.displayName = 'SidebarFooter'
363
+
364
+ export const SidebarSeparator = React.forwardRef<
365
+ React.ElementRef<typeof Separator>,
366
+ React.ComponentPropsWithoutRef<typeof Separator>
367
+ >(({ className, ...props }, ref) => (
368
+ <Separator
369
+ ref={ref}
370
+ data-sidebar="separator"
371
+ className={cn('mx-2 w-auto bg-border', className)}
372
+ {...props}
373
+ />
374
+ ))
375
+ SidebarSeparator.displayName = 'SidebarSeparator'
376
+
377
+ export const SidebarContent = React.forwardRef<
378
+ HTMLDivElement,
379
+ React.HTMLAttributes<HTMLDivElement>
380
+ >(({ className, ...props }, ref) => (
381
+ <div
382
+ ref={ref}
383
+ data-sidebar="content"
384
+ className={cn(
385
+ 'flex py-2 min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden',
386
+ className,
387
+ )}
388
+ {...props}
389
+ />
390
+ ))
391
+ SidebarContent.displayName = 'SidebarContent'
392
+
393
+ export const SidebarGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
394
+ ({ className, ...props }, ref) => (
395
+ <div
396
+ ref={ref}
397
+ data-sidebar="group"
398
+ className={cn('relative flex w-full min-w-0 flex-col px-2 py-1', className)}
399
+ {...props}
400
+ />
401
+ ),
402
+ )
403
+ SidebarGroup.displayName = 'SidebarGroup'
404
+
405
+ export const SidebarGroupLabel = React.forwardRef<
406
+ HTMLDivElement,
407
+ React.HTMLAttributes<HTMLDivElement> & { asChild?: boolean }
408
+ >(({ className, asChild, ...props }, ref) => {
409
+ const Comp = asChild ? Slot : 'div'
410
+ return (
411
+ <Comp
412
+ ref={ref}
413
+ data-sidebar="group-label"
414
+ className={cn(
415
+ 'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-muted-foreground outline-none ring-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
416
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
417
+ className,
418
+ )}
419
+ {...props}
420
+ />
421
+ )
422
+ })
423
+ SidebarGroupLabel.displayName = 'SidebarGroupLabel'
424
+
425
+ export const SidebarGroupAction = React.forwardRef<
426
+ HTMLButtonElement,
427
+ React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean }
428
+ >(({ className, asChild, ...props }, ref) => {
429
+ const Comp = asChild ? Slot : 'button'
430
+ return (
431
+ <Comp
432
+ ref={ref}
433
+ data-sidebar="group-action"
434
+ className={cn(
435
+ 'absolute right-3 top-3.5 flex aspect-square w-5 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
436
+ 'after:absolute after:-inset-2 md:after:hidden',
437
+ 'group-data-[collapsible=icon]:hidden',
438
+ className,
439
+ )}
440
+ {...props}
441
+ />
442
+ )
443
+ })
444
+ SidebarGroupAction.displayName = 'SidebarGroupAction'
445
+
446
+ export const SidebarGroupContent = React.forwardRef<
447
+ HTMLDivElement,
448
+ React.HTMLAttributes<HTMLDivElement>
449
+ >(({ className, ...props }, ref) => (
450
+ <div
451
+ ref={ref}
452
+ data-sidebar="group-content"
453
+ className={cn('w-full text-sm', className)}
454
+ {...props}
455
+ />
456
+ ))
457
+ SidebarGroupContent.displayName = 'SidebarGroupContent'
458
+
459
+ export const SidebarMenu = React.forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
460
+ ({ className, ...props }, ref) => (
461
+ <ul
462
+ ref={ref}
463
+ data-sidebar="menu"
464
+ className={cn('flex w-full min-w-0 flex-col gap-1', className)}
465
+ {...props}
466
+ />
467
+ ),
468
+ )
469
+ SidebarMenu.displayName = 'SidebarMenu'
470
+
471
+ export const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.HTMLAttributes<HTMLLIElement>>(
472
+ ({ className, ...props }, ref) => (
473
+ <li
474
+ ref={ref}
475
+ data-sidebar="menu-item"
476
+ className={cn('group/menu-item relative', className)}
477
+ {...props}
478
+ />
479
+ ),
480
+ )
481
+ SidebarMenuItem.displayName = 'SidebarMenuItem'
482
+
483
+ const sidebarMenuButtonVariants = cva(
484
+ 'peer/menu-button flex w-full cursor-pointer items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-ring transition-[width,height,padding] hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-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-accent data-[active=true]:font-medium data-[active=true]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:hover:text-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
485
+ {
486
+ variants: {
487
+ variant: {
488
+ default: 'hover:bg-accent hover:text-accent-foreground',
489
+ outline:
490
+ 'bg-background shadow-[0_0_0_1px_hsl(var(--border))] hover:bg-accent hover:text-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--accent))]',
491
+ },
492
+ size: {
493
+ default: 'h-8 text-sm',
494
+ sm: 'h-7 text-xs',
495
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
496
+ },
497
+ },
498
+ defaultVariants: { variant: 'default', size: 'default' },
499
+ },
500
+ )
501
+
502
+ export interface SidebarMenuButtonProps
503
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
504
+ VariantProps<typeof sidebarMenuButtonVariants> {
505
+ asChild?: boolean
506
+ isActive?: boolean
507
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>
508
+ }
509
+
510
+ export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
511
+ ({ asChild, isActive, variant, size, tooltip, className, ...props }, ref) => {
512
+ const Comp = asChild ? Slot : 'button'
513
+ const { isMobile, state } = useSidebar()
514
+ const button = (
515
+ <Comp
516
+ ref={ref}
517
+ data-sidebar="menu-button"
518
+ data-size={size ?? 'default'}
519
+ data-active={isActive}
520
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
521
+ {...props}
522
+ />
523
+ )
524
+ if (!tooltip) return button
525
+ const tooltipProps = typeof tooltip === 'string' ? { children: tooltip } : tooltip
526
+ return (
527
+ <Tooltip>
528
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
529
+ <TooltipContent side="right" align="center" hidden={state !== 'collapsed' || isMobile} {...tooltipProps} />
530
+ </Tooltip>
531
+ )
532
+ },
533
+ )
534
+ SidebarMenuButton.displayName = 'SidebarMenuButton'
535
+
536
+ export const SidebarMenuAction = React.forwardRef<
537
+ HTMLButtonElement,
538
+ React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; showOnHover?: boolean }
539
+ >(({ className, asChild, showOnHover, ...props }, ref) => {
540
+ const Comp = asChild ? Slot : 'button'
541
+ return (
542
+ <Comp
543
+ ref={ref}
544
+ data-sidebar="menu-action"
545
+ className={cn(
546
+ 'absolute right-1 top-1.5 flex aspect-square w-5 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
547
+ 'after:absolute after:-inset-2 md:after:hidden',
548
+ 'peer-data-[size=sm]/menu-button:top-1',
549
+ 'peer-data-[size=default]/menu-button:top-1.5',
550
+ 'peer-data-[size=lg]/menu-button:top-2.5',
551
+ 'group-data-[collapsible=icon]:hidden',
552
+ showOnHover &&
553
+ '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-accent-foreground md:opacity-0',
554
+ className,
555
+ )}
556
+ {...props}
557
+ />
558
+ )
559
+ })
560
+ SidebarMenuAction.displayName = 'SidebarMenuAction'
561
+
562
+ export const SidebarMenuBadge = React.forwardRef<
563
+ HTMLDivElement,
564
+ React.HTMLAttributes<HTMLDivElement>
565
+ >(({ className, ...props }, ref) => (
566
+ <div
567
+ ref={ref}
568
+ data-sidebar="menu-badge"
569
+ className={cn(
570
+ '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-muted-foreground',
571
+ 'peer-hover/menu-button:text-accent-foreground peer-data-[active=true]/menu-button:text-accent-foreground',
572
+ 'peer-data-[size=sm]/menu-button:top-1',
573
+ 'peer-data-[size=default]/menu-button:top-1.5',
574
+ 'peer-data-[size=lg]/menu-button:top-2.5',
575
+ 'group-data-[collapsible=icon]:hidden',
576
+ className,
577
+ )}
578
+ {...props}
579
+ />
580
+ ))
581
+ SidebarMenuBadge.displayName = 'SidebarMenuBadge'
582
+
583
+ export const SidebarMenuSkeleton = React.forwardRef<
584
+ HTMLDivElement,
585
+ React.HTMLAttributes<HTMLDivElement> & { showIcon?: boolean }
586
+ >(({ className, showIcon = false, ...props }, ref) => {
587
+ const id = React.useId()
588
+ // Derive a stable per-instance pseudo-random width (50%..90%) from useId so
589
+ // skeleton rows visually vary without violating component purity.
590
+ const width = React.useMemo(() => {
591
+ let h = 0
592
+ for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) | 0
593
+ return `${50 + (Math.abs(h) % 40)}%`
594
+ }, [id])
595
+ return (
596
+ <div
597
+ ref={ref}
598
+ data-sidebar="menu-skeleton"
599
+ className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
600
+ {...props}
601
+ >
602
+ {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
603
+ <Skeleton
604
+ className="h-4 max-w-[var(--skeleton-width)] flex-1"
605
+ data-sidebar="menu-skeleton-text"
606
+ style={{ '--skeleton-width': width } as React.CSSProperties}
607
+ />
608
+ </div>
609
+ )
610
+ })
611
+ SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton'
612
+
613
+ export const SidebarMenuSub = React.forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
614
+ ({ className, ...props }, ref) => (
615
+ <ul
616
+ ref={ref}
617
+ data-sidebar="menu-sub"
618
+ className={cn(
619
+ 'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-border px-2.5 py-0.5',
620
+ 'group-data-[collapsible=icon]:hidden',
621
+ className,
622
+ )}
623
+ {...props}
624
+ />
625
+ ),
626
+ )
627
+ SidebarMenuSub.displayName = 'SidebarMenuSub'
628
+
629
+ export const SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.HTMLAttributes<HTMLLIElement>>(
630
+ ({ ...props }, ref) => <li ref={ref} {...props} />,
631
+ )
632
+ SidebarMenuSubItem.displayName = 'SidebarMenuSubItem'
633
+
634
+ export const SidebarMenuSubButton = React.forwardRef<
635
+ HTMLAnchorElement,
636
+ React.AnchorHTMLAttributes<HTMLAnchorElement> & {
637
+ asChild?: boolean
638
+ size?: 'sm' | 'md'
639
+ isActive?: boolean
640
+ }
641
+ >(({ asChild, size = 'md', isActive, className, ...props }, ref) => {
642
+ const Comp = asChild ? Slot : 'a'
643
+ return (
644
+ <Comp
645
+ ref={ref}
646
+ data-sidebar="menu-sub-button"
647
+ data-size={size}
648
+ data-active={isActive}
649
+ className={cn(
650
+ 'flex h-7 min-w-0 -translate-x-px cursor-pointer items-center gap-2 overflow-hidden rounded-md px-2 text-sm text-foreground outline-none ring-ring hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-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-muted-foreground',
651
+ 'data-[active=true]:bg-accent data-[active=true]:text-accent-foreground',
652
+ size === 'sm' && 'text-xs',
653
+ 'group-data-[collapsible=icon]:hidden',
654
+ className,
655
+ )}
656
+ {...props}
657
+ />
658
+ )
659
+ })
660
+ SidebarMenuSubButton.displayName = 'SidebarMenuSubButton'
@@ -0,0 +1,9 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../lib/utils.js'
3
+
4
+ export function Skeleton({
5
+ className,
6
+ ...props
7
+ }: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
8
+ return <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />
9
+ }