@srcroot/ui 0.0.54 → 0.0.56

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 (107) hide show
  1. package/README.md +151 -151
  2. package/dist/index.d.ts +0 -0
  3. package/dist/index.js +55 -1
  4. package/package.json +7 -2
  5. package/src/registry/analytics/google-analytics.tsx +36 -39
  6. package/src/registry/analytics/google-tag-manager.tsx +62 -65
  7. package/src/registry/analytics/meta-pixel.tsx +44 -47
  8. package/src/registry/analytics/microsoft-clarity.tsx +31 -34
  9. package/src/registry/analytics/tiktok-pixel.tsx +34 -37
  10. package/src/registry/lib/utils.ts +0 -0
  11. package/src/registry/themes/v3/blue.css +157 -157
  12. package/src/registry/themes/v3/glass.css +153 -153
  13. package/src/registry/themes/v3/gray.css +157 -157
  14. package/src/registry/themes/v3/green.css +157 -157
  15. package/src/registry/themes/v3/neutral.css +157 -157
  16. package/src/registry/themes/v3/orange.css +157 -157
  17. package/src/registry/themes/v3/rose.css +157 -157
  18. package/src/registry/themes/v3/slate.css +157 -157
  19. package/src/registry/themes/v3/stone.css +157 -157
  20. package/src/registry/themes/v3/violet.css +186 -186
  21. package/src/registry/themes/v3/zinc.css +157 -157
  22. package/src/registry/themes/v4/blue.css +184 -184
  23. package/src/registry/themes/v4/glass.css +180 -180
  24. package/src/registry/themes/v4/gray.css +184 -184
  25. package/src/registry/themes/v4/green.css +184 -184
  26. package/src/registry/themes/v4/neutral.css +184 -184
  27. package/src/registry/themes/v4/orange.css +184 -184
  28. package/src/registry/themes/v4/rose.css +184 -184
  29. package/src/registry/themes/v4/slate.css +184 -184
  30. package/src/registry/themes/v4/stone.css +184 -184
  31. package/src/registry/themes/v4/violet.css +184 -184
  32. package/src/registry/themes/v4/zinc.css +184 -184
  33. package/src/registry/ui/accordion.tsx +164 -165
  34. package/src/registry/ui/alert-dialog.tsx +213 -214
  35. package/src/registry/ui/alert.tsx +73 -76
  36. package/src/registry/ui/aspect-ratio.tsx +44 -47
  37. package/src/registry/ui/avatar.tsx +96 -97
  38. package/src/registry/ui/badge.tsx +52 -55
  39. package/src/registry/ui/breadcrumb.tsx +147 -150
  40. package/src/registry/ui/button-group.tsx +64 -67
  41. package/src/registry/ui/button.tsx +71 -72
  42. package/src/registry/ui/calendar.tsx +514 -515
  43. package/src/registry/ui/card.tsx +88 -91
  44. package/src/registry/ui/carousel.tsx +214 -214
  45. package/src/registry/ui/chart.tsx +373 -373
  46. package/src/registry/ui/chatbot.tsx +86 -13
  47. package/src/registry/ui/checkbox.tsx +93 -94
  48. package/src/registry/ui/collapsible.tsx +107 -108
  49. package/src/registry/ui/combobox.tsx +171 -171
  50. package/src/registry/ui/command.tsx +300 -300
  51. package/src/registry/ui/container.tsx +44 -47
  52. package/src/registry/ui/context-menu.tsx +221 -221
  53. package/src/registry/ui/date-picker.tsx +228 -228
  54. package/src/registry/ui/dialog.tsx +269 -270
  55. package/src/registry/ui/drawer.tsx +10 -4
  56. package/src/registry/ui/dropdown-menu.tsx +529 -530
  57. package/src/registry/ui/empty-state.tsx +0 -2
  58. package/src/registry/ui/file-upload.tsx +0 -0
  59. package/src/registry/ui/floating-dock.tsx +0 -0
  60. package/src/registry/ui/form-field.tsx +91 -94
  61. package/src/registry/ui/google-analytics.tsx +38 -0
  62. package/src/registry/ui/google-tag-manager.tsx +64 -0
  63. package/src/registry/ui/hover-card.tsx +223 -223
  64. package/src/registry/ui/image.tsx +144 -147
  65. package/src/registry/ui/input-group.tsx +82 -85
  66. package/src/registry/ui/input.tsx +125 -125
  67. package/src/registry/ui/kbd.tsx +60 -63
  68. package/src/registry/ui/label.tsx +36 -37
  69. package/src/registry/ui/loading-spinner.tsx +108 -111
  70. package/src/registry/ui/map.tsx +0 -0
  71. package/src/registry/ui/marquee.tsx +2 -0
  72. package/src/registry/ui/menubar.tsx +246 -246
  73. package/src/registry/ui/meta-pixel.tsx +46 -0
  74. package/src/registry/ui/microsoft-clarity.tsx +33 -0
  75. package/src/registry/ui/native-select.tsx +49 -52
  76. package/src/registry/ui/otp-input.tsx +152 -155
  77. package/src/registry/ui/pagination.tsx +149 -152
  78. package/src/registry/ui/patterns.tsx +28 -0
  79. package/src/registry/ui/popover.tsx +226 -227
  80. package/src/registry/ui/progress.tsx +51 -52
  81. package/src/registry/ui/radio.tsx +99 -102
  82. package/src/registry/ui/resizable.tsx +314 -314
  83. package/src/registry/ui/scroll-animation.tsx +45 -0
  84. package/src/registry/ui/scroll-area.tsx +121 -122
  85. package/src/registry/ui/scroll-to-top.tsx +0 -0
  86. package/src/registry/ui/search.tsx +147 -150
  87. package/src/registry/ui/select.tsx +292 -293
  88. package/src/registry/ui/separator.tsx +46 -47
  89. package/src/registry/ui/sheet.tsx +6 -3
  90. package/src/registry/ui/sidebar.tsx +628 -628
  91. package/src/registry/ui/skeleton.tsx +26 -29
  92. package/src/registry/ui/slider.tsx +196 -197
  93. package/src/registry/ui/slot.tsx +69 -72
  94. package/src/registry/ui/star-rating.tsx +131 -134
  95. package/src/registry/ui/switch.tsx +72 -73
  96. package/src/registry/ui/table-of-contents.tsx +96 -96
  97. package/src/registry/ui/table.tsx +138 -139
  98. package/src/registry/ui/tabs.tsx +124 -125
  99. package/src/registry/ui/text.tsx +61 -64
  100. package/src/registry/ui/textarea.tsx +41 -42
  101. package/src/registry/ui/theme-switcher.tsx +66 -66
  102. package/src/registry/ui/tiktok-pixel.tsx +36 -0
  103. package/src/registry/ui/toast.tsx +97 -98
  104. package/src/registry/ui/toggle-group.tsx +129 -129
  105. package/src/registry/ui/toggle.tsx +72 -72
  106. package/src/registry/ui/tooltip.tsx +143 -144
  107. package/src/registry/ui/whatsapp.tsx +0 -0
@@ -1,628 +1,628 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { LuPanelLeft } from "react-icons/lu";
5
- import { cva, type VariantProps } from "class-variance-authority"
6
-
7
- import { cn } from "@/lib/utils"
8
- import { Button } from "@/components/ui/button"
9
- import { Slot } from "@/components/ui/slot"
10
- import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
11
-
12
-
13
-
14
- const SidebarContext = React.createContext<{
15
- state: "expanded" | "collapsed"
16
- open: boolean
17
- setOpen: (open: boolean) => void
18
- openMobile: boolean
19
- setOpenMobile: (open: boolean) => void
20
- isMobile: boolean
21
- toggleSidebar: () => void
22
- } | null>(null)
23
-
24
- function useSidebar() {
25
- const context = React.useContext(SidebarContext)
26
- if (!context) {
27
- throw new Error("useSidebar must be used within a SidebarProvider")
28
- }
29
- return context
30
- }
31
-
32
- const SidebarProvider = React.forwardRef<
33
- HTMLDivElement,
34
- React.HTMLAttributes<HTMLDivElement> & {
35
- defaultOpen?: boolean
36
- open?: boolean
37
- onOpenChange?: (open: boolean) => void
38
- }
39
- >(({ className, style, children, defaultOpen = true, open: openProp, onOpenChange: setOpenProp, ...props }, ref) => {
40
- const [isMobile, setIsMobile] = React.useState(false)
41
- const [openMobile, setOpenMobile] = React.useState(false)
42
-
43
- // Using internal state for "expanded/collapsed" on desktop
44
- const [_open, _setOpen] = React.useState(defaultOpen)
45
- const open = openProp ?? _open
46
- const setOpen = React.useCallback(
47
- (value: boolean | ((value: boolean) => boolean)) => {
48
- if (setOpenProp) {
49
- return setOpenProp(typeof value === "function" ? value(open) : value)
50
- }
51
- _setOpen(value)
52
- },
53
- [setOpenProp, open]
54
- )
55
-
56
-
57
- // Collapsible state helper
58
- const state = open ? "expanded" : "collapsed"
59
-
60
- const toggleSidebar = React.useCallback(() => {
61
- return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
62
- }, [isMobile, setOpen, setOpenMobile])
63
-
64
-
65
- React.useEffect(() => {
66
- const checkMobile = () => {
67
- setIsMobile(window.innerWidth < 768) // md breakpoint
68
- }
69
- checkMobile()
70
- window.addEventListener("resize", checkMobile)
71
- return () => window.removeEventListener("resize", checkMobile)
72
- }, [])
73
-
74
- return (
75
- <SidebarContext.Provider
76
- value={{
77
- state,
78
- open,
79
- setOpen,
80
- isMobile,
81
- openMobile,
82
- setOpenMobile,
83
- toggleSidebar,
84
- }}
85
- >
86
- <div
87
- style={
88
- {
89
- ...style,
90
- } as React.CSSProperties
91
- }
92
- ref={ref}
93
- className={cn(
94
- "group/sidebar-wrapper flex min-h-screen w-full has-[[data-variant=inset]]:bg-sidebar",
95
- className
96
- )}
97
- {...props}
98
- >
99
- {children}
100
- </div>
101
- </SidebarContext.Provider>
102
- )
103
- })
104
- SidebarProvider.displayName = "SidebarProvider"
105
-
106
-
107
- /**
108
- * Sidebar component for application layout
109
- *
110
- * @example
111
- * <SidebarProvider>
112
- * <Sidebar>
113
- * <SidebarHeader />
114
- * <SidebarContent>
115
- * <SidebarGroup />
116
- * <SidebarMenu />
117
- * </SidebarContent>
118
- * <SidebarFooter />
119
- * </Sidebar>
120
- * <SidebarInset>
121
- * <main>Content</main>
122
- * </SidebarInset>
123
- * </SidebarProvider>
124
- */
125
- const Sidebar = React.forwardRef<
126
- HTMLDivElement,
127
- React.HTMLAttributes<HTMLDivElement> & {
128
- side?: "left" | "right"
129
- variant?: "sidebar" | "floating" | "inset"
130
- collapsible?: "offcanvas" | "icon" | "none"
131
- }
132
- >(
133
- (
134
- {
135
- side = "left",
136
- variant = "sidebar",
137
- collapsible = "offcanvas",
138
- className,
139
- children,
140
- ...props
141
- },
142
- ref
143
- ) => {
144
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
145
-
146
- if (collapsible === "none") {
147
- return (
148
- <div
149
- className={cn(
150
- "flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar text-sidebar-foreground",
151
- className
152
- )}
153
- ref={ref}
154
- {...props}
155
- >
156
- {children}
157
- </div>
158
- )
159
- }
160
-
161
- if (isMobile) {
162
- return (
163
- <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
164
- <SheetContent
165
- data-sidebar="sidebar"
166
- data-mobile="true"
167
- className="w-[var(--sidebar-width)] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
168
- style={
169
- {
170
- "--sidebar-width": "var(--sidebar-width-mobile)",
171
- } as React.CSSProperties
172
- }
173
- side={side}
174
- >
175
- {/* Hidden Headers for Accessibility */}
176
- <div className="sr-only">
177
- <SheetHeader>
178
- <SheetTitle>Menu</SheetTitle>
179
- <SheetDescription>Navigation Menu</SheetDescription>
180
- </SheetHeader>
181
- </div>
182
- <div className="flex h-full w-full flex-col">{children}</div>
183
- </SheetContent>
184
- </Sheet>
185
- )
186
- }
187
-
188
- return (
189
- <div
190
- ref={ref}
191
- className={cn(
192
- "group peer md:block text-sidebar-foreground",
193
- className
194
- )}
195
- data-state={state}
196
- data-collapsible={state === "collapsed" ? collapsible : ""}
197
- data-variant={variant}
198
- data-side={side}
199
- >
200
- {/* Visual Gap for Sidebar (Fixed placeholder) */}
201
- <div
202
- className={cn(
203
- "duration-200 relative h-svh w-[var(--sidebar-width)] bg-transparent transition-[width] ease-linear",
204
- "group-data-[collapsible=offcanvas]:w-0",
205
- "group-data-[side=right]:rotate-180",
206
- variant === "floating" || variant === "inset"
207
- ? "group-data-[collapsible=icon]:!w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
208
- : "group-data-[collapsible=icon]:!w-[var(--sidebar-width-icon)]"
209
- )}
210
- />
211
-
212
- {/* Actual Fixed Sidebar */}
213
- <div
214
- className={cn(
215
- "duration-200 fixed inset-y-0 z-10 h-svh w-[var(--sidebar-width)] transition-[left,right,width] ease-linear md:flex",
216
- side === "left"
217
- ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
218
- : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
219
- // Adjustments for floating/inset
220
- variant === "floating" || variant === "inset"
221
- ? "p-2 group-data-[collapsible=icon]:!w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+_2px)]"
222
- : "group-data-[collapsible=icon]:!w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l",
223
- className
224
- )}
225
- {...props}
226
- >
227
- <div
228
- data-sidebar="sidebar"
229
- 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]:shadow"
230
- >
231
- {children}
232
- </div>
233
- </div>
234
- </div>
235
- )
236
- }
237
- )
238
- Sidebar.displayName = "Sidebar"
239
-
240
- const SidebarTrigger = React.forwardRef<
241
- HTMLButtonElement,
242
- React.ButtonHTMLAttributes<HTMLButtonElement>
243
- >(({ className, onClick, ...props }, ref) => {
244
- const { toggleSidebar } = useSidebar()
245
-
246
- return (
247
- <Button
248
- ref={ref}
249
- data-sidebar="trigger"
250
- variant="ghost"
251
- size="icon"
252
- className={cn("h-7 w-7", className)}
253
- onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
254
- onClick?.(event)
255
- toggleSidebar()
256
- }}
257
- {...props}
258
- >
259
- <LuPanelLeft />
260
- <span className="sr-only">Toggle Sidebar</span>
261
- </Button>
262
- )
263
- })
264
- SidebarTrigger.displayName = "SidebarTrigger"
265
-
266
- const SidebarRail = React.forwardRef<
267
- HTMLButtonElement,
268
- React.ButtonHTMLAttributes<HTMLButtonElement>
269
- >(({ className, ...props }, ref) => {
270
- const { toggleSidebar } = useSidebar()
271
-
272
- return (
273
- <button
274
- ref={ref}
275
- data-sidebar="rail"
276
- aria-label="Toggle Sidebar"
277
- tabIndex={-1}
278
- onClick={toggleSidebar}
279
- title="Toggle Sidebar"
280
- className={cn(
281
- "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",
282
- "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
283
- "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
284
- "group-data-[collapsible=offcanvas]:hover:bg-sidebar",
285
- "group-data-[collapsible=icon]:w-2 group-data-[collapsible=icon]:group-data-[side=left]:-right-3 group-data-[collapsible=icon]:group-data-[side=right]:left-0",
286
- className
287
- )}
288
- {...props}
289
- />
290
- )
291
- })
292
- SidebarRail.displayName = "SidebarRail"
293
-
294
- const SidebarInset = React.forwardRef<
295
- HTMLDivElement,
296
- React.HTMLAttributes<HTMLDivElement>
297
- >(({ className, ...props }, ref) => {
298
- return (
299
- <main
300
- ref={ref}
301
- className={cn(
302
- "relative flex min-h-svh flex-1 flex-col bg-background",
303
- // Width calculation based on sidebar state
304
- "w-full md:w-[calc(100%-var(--sidebar-width))]",
305
- "md:peer-data-[state=collapsed]:w-[calc(100%-var(--sidebar-width-icon))]",
306
- "md:peer-data-[collapsible=offcanvas]:peer-data-[state=collapsed]:w-full",
307
- // Transition for smooth resize
308
- "transition-[width] duration-200 ease-linear",
309
- // Inset variant styles
310
- "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",
311
- className
312
- )}
313
- {...props}
314
- />
315
- )
316
- })
317
- SidebarInset.displayName = "SidebarInset"
318
-
319
- const SidebarHeader = React.forwardRef<
320
- HTMLDivElement,
321
- React.HTMLAttributes<HTMLDivElement>
322
- >(({ className, ...props }, ref) => (
323
- <div
324
- ref={ref}
325
- data-sidebar="header"
326
- className={cn("flex flex-col gap-2 p-2", className)}
327
- {...props}
328
- />
329
- ))
330
- SidebarHeader.displayName = "SidebarHeader"
331
-
332
- const SidebarFooter = React.forwardRef<
333
- HTMLDivElement,
334
- React.HTMLAttributes<HTMLDivElement>
335
- >(({ className, ...props }, ref) => (
336
- <div
337
- ref={ref}
338
- data-sidebar="footer"
339
- className={cn("flex flex-col gap-2 p-2", className)}
340
- {...props}
341
- />
342
- ))
343
- SidebarFooter.displayName = "SidebarFooter"
344
-
345
- const SidebarContent = React.forwardRef<
346
- HTMLDivElement,
347
- React.HTMLAttributes<HTMLDivElement>
348
- >(({ className, ...props }, ref) => (
349
- <div
350
- ref={ref}
351
- data-sidebar="content"
352
- className={cn(
353
- "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
354
- className
355
- )}
356
- {...props}
357
- />
358
- ))
359
- SidebarContent.displayName = "SidebarContent"
360
-
361
- const SidebarGroup = React.forwardRef<
362
- HTMLDivElement,
363
- React.HTMLAttributes<HTMLDivElement>
364
- >(({ className, ...props }, ref) => (
365
- <div
366
- ref={ref}
367
- data-sidebar="group"
368
- className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
369
- {...props}
370
- />
371
- ))
372
- SidebarGroup.displayName = "SidebarGroup"
373
-
374
- const SidebarGroupLabel = React.forwardRef<
375
- HTMLDivElement,
376
- React.HTMLAttributes<HTMLDivElement> & { asChild?: boolean }
377
- >(({ className, asChild = false, ...props }, ref) => {
378
- const Comp = asChild ? Slot : "div"
379
-
380
- return (
381
- <Comp
382
- ref={ref}
383
- data-sidebar="group-label"
384
- className={cn(
385
- "duration-200 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,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
386
- "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
387
- className
388
- )}
389
- {...props}
390
- />
391
- )
392
- })
393
- SidebarGroupLabel.displayName = "SidebarGroupLabel"
394
-
395
- const SidebarGroupContent = React.forwardRef<
396
- HTMLDivElement,
397
- React.HTMLAttributes<HTMLDivElement>
398
- >(({ className, ...props }, ref) => (
399
- <div
400
- ref={ref}
401
- data-sidebar="group-content"
402
- className={cn("w-full text-sm", className)}
403
- {...props}
404
- />
405
- ))
406
- SidebarGroupContent.displayName = "SidebarGroupContent"
407
-
408
- const SidebarMenu = React.forwardRef<
409
- HTMLUListElement,
410
- React.HTMLAttributes<HTMLUListElement>
411
- >(({ className, ...props }, ref) => (
412
- <ul
413
- ref={ref}
414
- data-sidebar="menu"
415
- className={cn("flex w-full min-w-0 flex-col gap-1", className)}
416
- {...props}
417
- />
418
- ))
419
- SidebarMenu.displayName = "SidebarMenu"
420
-
421
- const SidebarMenuItem = React.forwardRef<
422
- HTMLLIElement,
423
- React.LiHTMLAttributes<HTMLLIElement>
424
- >(({ className, ...props }, ref) => (
425
- <li
426
- ref={ref}
427
- data-sidebar="menu-item"
428
- className={cn("group/menu-item relative", className)}
429
- {...props}
430
- />
431
- ))
432
- SidebarMenuItem.displayName = "SidebarMenuItem"
433
-
434
- const SidebarMenuButton = React.forwardRef<
435
- HTMLButtonElement,
436
- React.ButtonHTMLAttributes<HTMLButtonElement> & {
437
- asChild?: boolean
438
- isActive?: boolean
439
- tooltip?: string | React.ComponentProps<any>
440
- variant?: "default" | "ghost" | "outline" | "secondary" | "destructive" | "link" | null | undefined
441
- size?: "default" | "sm" | "lg" | "icon" | null | undefined
442
- }
443
- >(
444
- (
445
- {
446
- asChild = false,
447
- isActive = false,
448
- variant = "default",
449
- size = "default",
450
- tooltip,
451
- className,
452
- ...props
453
- },
454
- ref
455
- ) => {
456
- const { isMobile, state, setOpen } = useSidebar()
457
-
458
- const buttonClass = cn(
459
- "cursor-pointer 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 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
460
- "data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground",
461
- "data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground",
462
- "group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:group-data-[collapsible=icon]:hidden",
463
- className
464
- )
465
-
466
- // If tooltip is needed, we should implement it. For now, ignoring complexity of tooltip.
467
-
468
- const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
469
- props.onClick?.(e)
470
- if (!isMobile && state === "collapsed") {
471
- setOpen(true)
472
- }
473
- }
474
-
475
- const Comp = asChild ? Slot : "button"
476
-
477
- return (
478
- <Comp
479
- ref={ref}
480
- data-sidebar="menu-button"
481
- data-size={size}
482
- data-active={isActive}
483
- className={buttonClass}
484
- onClick={handleClick}
485
- {...props}
486
- />
487
- )
488
- }
489
- )
490
- SidebarMenuButton.displayName = "SidebarMenuButton"
491
-
492
- const SidebarMenuAction = React.forwardRef<
493
- HTMLButtonElement,
494
- React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; showOnHover?: boolean }
495
- >(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
496
- const Comp = asChild ? Slot : "button"
497
-
498
- return (
499
- <Comp
500
- ref={ref}
501
- data-sidebar="menu-action"
502
- className={cn(
503
- "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",
504
- // Increases the hit area of the button on mobile.
505
- "after:absolute after:-inset-2 after:md:hidden",
506
- "peer-data-[size=sm]/menu-button:top-1",
507
- "peer-data-[size=default]/menu-button:top-1.5",
508
- "peer-data-[size=lg]/menu-button:top-2.5",
509
- "group-data-[collapsible=icon]:hidden",
510
- showOnHover &&
511
- "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",
512
- className
513
- )}
514
- {...props}
515
- />
516
- )
517
- })
518
- SidebarMenuAction.displayName = "SidebarMenuAction"
519
-
520
- const SidebarMenuSub = React.forwardRef<
521
- HTMLUListElement,
522
- React.HTMLAttributes<HTMLUListElement>
523
- >(({ className, ...props }, ref) => (
524
- <ul
525
- ref={ref}
526
- data-sidebar="menu-sub"
527
- className={cn(
528
- "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",
529
- "group-data-[collapsible=icon]:hidden",
530
- className
531
- )}
532
- {...props}
533
- />
534
- ))
535
- SidebarMenuSub.displayName = "SidebarMenuSub"
536
-
537
- const SidebarMenuSubItem = React.forwardRef<
538
- HTMLLIElement,
539
- React.LiHTMLAttributes<HTMLLIElement>
540
- >(({ ...props }, ref) => <li ref={ref} {...props} />)
541
- SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
542
-
543
- const SidebarMenuSubButton = React.forwardRef<
544
- HTMLAnchorElement,
545
- React.AnchorHTMLAttributes<HTMLAnchorElement> & {
546
- asChild?: boolean
547
- size?: "sm" | "md"
548
- isActive?: boolean
549
- }
550
- >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
551
- const Comp = asChild ? Slot : "a"
552
-
553
- return (
554
- <Comp
555
- ref={ref}
556
- data-sidebar="menu-sub-button"
557
- data-size={size}
558
- data-active={isActive}
559
- className={cn(
560
- "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 group-data-[collapsible=icon]:hidden",
561
- size === "sm" && "text-xs",
562
- size === "md" && "text-sm",
563
- "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
564
- className
565
- )}
566
- {...props}
567
- />
568
- )
569
- })
570
- SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
571
-
572
- const SidebarInput = React.forwardRef<
573
- HTMLInputElement,
574
- React.InputHTMLAttributes<HTMLInputElement>
575
- >(({ className, ...props }, ref) => {
576
- return (
577
- <input
578
- ref={ref}
579
- data-sidebar="input"
580
- className={cn(
581
- "flex h-8 w-full bg-background rounded-md px-3 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 group-data-[collapsible=icon]:w-0 group-data-[collapsible=icon]:p-0 group-data-[collapsible=icon]:border-0 group-data-[collapsible=icon]:hidden",
582
- className
583
- )}
584
- {...props}
585
- />
586
- )
587
- })
588
- SidebarInput.displayName = "SidebarInput"
589
-
590
-
591
- const SidebarSeparator = React.forwardRef<
592
- HTMLDivElement,
593
- React.HTMLAttributes<HTMLDivElement>
594
- >(({ className, ...props }, ref) => {
595
- return (
596
- <div
597
- ref={ref}
598
- data-sidebar="separator"
599
- className={cn("mx-2 w-auto bg-sidebar-border h-px", className)}
600
- {...props}
601
- />
602
- )
603
- })
604
- SidebarSeparator.displayName = "SidebarSeparator"
605
-
606
- export {
607
- Sidebar,
608
- SidebarContent,
609
- SidebarFooter,
610
- SidebarGroup,
611
- SidebarGroupContent,
612
- SidebarGroupLabel,
613
- SidebarHeader,
614
- SidebarInput,
615
- SidebarInset,
616
- SidebarMenu,
617
- SidebarMenuAction,
618
- SidebarMenuButton,
619
- SidebarMenuItem,
620
- SidebarMenuSub,
621
- SidebarMenuSubButton,
622
- SidebarMenuSubItem,
623
- SidebarProvider,
624
- SidebarRail,
625
- SidebarSeparator,
626
- SidebarTrigger,
627
- useSidebar,
628
- }
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { LuPanelLeft } from "react-icons/lu";
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { Button } from "@/components/ui/button"
9
+ import { Slot } from "@/components/ui/slot"
10
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
11
+
12
+
13
+
14
+ const SidebarContext = React.createContext<{
15
+ state: "expanded" | "collapsed"
16
+ open: boolean
17
+ setOpen: (open: boolean) => void
18
+ openMobile: boolean
19
+ setOpenMobile: (open: boolean) => void
20
+ isMobile: boolean
21
+ toggleSidebar: () => void
22
+ } | null>(null)
23
+
24
+ function useSidebar() {
25
+ const context = React.useContext(SidebarContext)
26
+ if (!context) {
27
+ throw new Error("useSidebar must be used within a SidebarProvider")
28
+ }
29
+ return context
30
+ }
31
+
32
+ const SidebarProvider = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement> & {
35
+ defaultOpen?: boolean
36
+ open?: boolean
37
+ onOpenChange?: (open: boolean) => void
38
+ }
39
+ >(({ className, style, children, defaultOpen = true, open: openProp, onOpenChange: setOpenProp, ...props }, ref) => {
40
+ const [isMobile, setIsMobile] = React.useState(false)
41
+ const [openMobile, setOpenMobile] = React.useState(false)
42
+
43
+ // Using internal state for "expanded/collapsed" on desktop
44
+ const [_open, _setOpen] = React.useState(defaultOpen)
45
+ const open = openProp ?? _open
46
+ const setOpen = React.useCallback(
47
+ (value: boolean | ((value: boolean) => boolean)) => {
48
+ if (setOpenProp) {
49
+ return setOpenProp(typeof value === "function" ? value(open) : value)
50
+ }
51
+ _setOpen(value)
52
+ },
53
+ [setOpenProp, open]
54
+ )
55
+
56
+
57
+ // Collapsible state helper
58
+ const state = open ? "expanded" : "collapsed"
59
+
60
+ const toggleSidebar = React.useCallback(() => {
61
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
62
+ }, [isMobile, setOpen, setOpenMobile])
63
+
64
+
65
+ React.useEffect(() => {
66
+ const checkMobile = () => {
67
+ setIsMobile(window.innerWidth < 768) // md breakpoint
68
+ }
69
+ checkMobile()
70
+ window.addEventListener("resize", checkMobile)
71
+ return () => window.removeEventListener("resize", checkMobile)
72
+ }, [])
73
+
74
+ return (
75
+ <SidebarContext.Provider
76
+ value={{
77
+ state,
78
+ open,
79
+ setOpen,
80
+ isMobile,
81
+ openMobile,
82
+ setOpenMobile,
83
+ toggleSidebar,
84
+ }}
85
+ >
86
+ <div
87
+ style={
88
+ {
89
+ ...style,
90
+ } as React.CSSProperties
91
+ }
92
+ ref={ref}
93
+ className={cn(
94
+ "group/sidebar-wrapper flex min-h-screen w-full has-[[data-variant=inset]]:bg-sidebar",
95
+ className
96
+ )}
97
+ {...props}
98
+ >
99
+ {children}
100
+ </div>
101
+ </SidebarContext.Provider>
102
+ )
103
+ })
104
+ SidebarProvider.displayName = "SidebarProvider"
105
+
106
+
107
+ /**
108
+ * Sidebar component for application layout
109
+ *
110
+ * @example
111
+ * <SidebarProvider>
112
+ * <Sidebar>
113
+ * <SidebarHeader />
114
+ * <SidebarContent>
115
+ * <SidebarGroup />
116
+ * <SidebarMenu />
117
+ * </SidebarContent>
118
+ * <SidebarFooter />
119
+ * </Sidebar>
120
+ * <SidebarInset>
121
+ * <main>Content</main>
122
+ * </SidebarInset>
123
+ * </SidebarProvider>
124
+ */
125
+ const Sidebar = React.forwardRef<
126
+ HTMLDivElement,
127
+ React.HTMLAttributes<HTMLDivElement> & {
128
+ side?: "left" | "right"
129
+ variant?: "sidebar" | "floating" | "inset"
130
+ collapsible?: "offcanvas" | "icon" | "none"
131
+ }
132
+ >(
133
+ (
134
+ {
135
+ side = "left",
136
+ variant = "sidebar",
137
+ collapsible = "offcanvas",
138
+ className,
139
+ children,
140
+ ...props
141
+ },
142
+ ref
143
+ ) => {
144
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
145
+
146
+ if (collapsible === "none") {
147
+ return (
148
+ <div
149
+ className={cn(
150
+ "flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar text-sidebar-foreground",
151
+ className
152
+ )}
153
+ ref={ref}
154
+ {...props}
155
+ >
156
+ {children}
157
+ </div>
158
+ )
159
+ }
160
+
161
+ if (isMobile) {
162
+ return (
163
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
164
+ <SheetContent
165
+ data-sidebar="sidebar"
166
+ data-mobile="true"
167
+ className="w-[var(--sidebar-width)] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
168
+ style={
169
+ {
170
+ "--sidebar-width": "var(--sidebar-width-mobile)",
171
+ } as React.CSSProperties
172
+ }
173
+ side={side}
174
+ >
175
+ {/* Hidden Headers for Accessibility */}
176
+ <div className="sr-only">
177
+ <SheetHeader>
178
+ <SheetTitle>Menu</SheetTitle>
179
+ <SheetDescription>Navigation Menu</SheetDescription>
180
+ </SheetHeader>
181
+ </div>
182
+ <div className="flex h-full w-full flex-col">{children}</div>
183
+ </SheetContent>
184
+ </Sheet>
185
+ )
186
+ }
187
+
188
+ return (
189
+ <div
190
+ ref={ref}
191
+ className={cn(
192
+ "group peer md:block text-sidebar-foreground",
193
+ className
194
+ )}
195
+ data-state={state}
196
+ data-collapsible={state === "collapsed" ? collapsible : ""}
197
+ data-variant={variant}
198
+ data-side={side}
199
+ >
200
+ {/* Visual Gap for Sidebar (Fixed placeholder) */}
201
+ <div
202
+ className={cn(
203
+ "duration-200 relative h-svh w-[var(--sidebar-width)] bg-transparent transition-[width] ease-linear",
204
+ "group-data-[collapsible=offcanvas]:w-0",
205
+ "group-data-[side=right]:rotate-180",
206
+ variant === "floating" || variant === "inset"
207
+ ? "group-data-[collapsible=icon]:!w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
208
+ : "group-data-[collapsible=icon]:!w-[var(--sidebar-width-icon)]"
209
+ )}
210
+ />
211
+
212
+ {/* Actual Fixed Sidebar */}
213
+ <div
214
+ className={cn(
215
+ "duration-200 fixed inset-y-0 z-10 h-svh w-[var(--sidebar-width)] transition-[left,right,width] ease-linear md:flex",
216
+ side === "left"
217
+ ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
218
+ : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
219
+ // Adjustments for floating/inset
220
+ variant === "floating" || variant === "inset"
221
+ ? "p-2 group-data-[collapsible=icon]:!w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+_2px)]"
222
+ : "group-data-[collapsible=icon]:!w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l",
223
+ className
224
+ )}
225
+ {...props}
226
+ >
227
+ <div
228
+ data-sidebar="sidebar"
229
+ 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]:shadow"
230
+ >
231
+ {children}
232
+ </div>
233
+ </div>
234
+ </div>
235
+ )
236
+ }
237
+ )
238
+ Sidebar.displayName = "Sidebar"
239
+
240
+ const SidebarTrigger = React.forwardRef<
241
+ HTMLButtonElement,
242
+ React.ButtonHTMLAttributes<HTMLButtonElement>
243
+ >(({ className, onClick, ...props }, ref) => {
244
+ const { toggleSidebar } = useSidebar()
245
+
246
+ return (
247
+ <Button
248
+ ref={ref}
249
+ data-sidebar="trigger"
250
+ variant="ghost"
251
+ size="icon"
252
+ className={cn("h-7 w-7", className)}
253
+ onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
254
+ onClick?.(event)
255
+ toggleSidebar()
256
+ }}
257
+ {...props}
258
+ >
259
+ <LuPanelLeft />
260
+ <span className="sr-only">Toggle Sidebar</span>
261
+ </Button>
262
+ )
263
+ })
264
+ SidebarTrigger.displayName = "SidebarTrigger"
265
+
266
+ const SidebarRail = React.forwardRef<
267
+ HTMLButtonElement,
268
+ React.ButtonHTMLAttributes<HTMLButtonElement>
269
+ >(({ className, ...props }, ref) => {
270
+ const { toggleSidebar } = useSidebar()
271
+
272
+ return (
273
+ <button
274
+ ref={ref}
275
+ data-sidebar="rail"
276
+ aria-label="Toggle Sidebar"
277
+ tabIndex={-1}
278
+ onClick={toggleSidebar}
279
+ title="Toggle Sidebar"
280
+ className={cn(
281
+ "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",
282
+ "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
283
+ "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
284
+ "group-data-[collapsible=offcanvas]:hover:bg-sidebar",
285
+ "group-data-[collapsible=icon]:w-2 group-data-[collapsible=icon]:group-data-[side=left]:-right-3 group-data-[collapsible=icon]:group-data-[side=right]:left-0",
286
+ className
287
+ )}
288
+ {...props}
289
+ />
290
+ )
291
+ })
292
+ SidebarRail.displayName = "SidebarRail"
293
+
294
+ const SidebarInset = React.forwardRef<
295
+ HTMLDivElement,
296
+ React.HTMLAttributes<HTMLDivElement>
297
+ >(({ className, ...props }, ref) => {
298
+ return (
299
+ <main
300
+ ref={ref}
301
+ className={cn(
302
+ "relative flex min-h-svh flex-1 flex-col bg-background",
303
+ // Width calculation based on sidebar state
304
+ "w-full md:w-[calc(100%-var(--sidebar-width))]",
305
+ "md:peer-data-[state=collapsed]:w-[calc(100%-var(--sidebar-width-icon))]",
306
+ "md:peer-data-[collapsible=offcanvas]:peer-data-[state=collapsed]:w-full",
307
+ // Transition for smooth resize
308
+ "transition-[width] duration-200 ease-linear",
309
+ // Inset variant styles
310
+ "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",
311
+ className
312
+ )}
313
+ {...props}
314
+ />
315
+ )
316
+ })
317
+ SidebarInset.displayName = "SidebarInset"
318
+
319
+ const SidebarHeader = React.forwardRef<
320
+ HTMLDivElement,
321
+ React.HTMLAttributes<HTMLDivElement>
322
+ >(({ className, ...props }, ref) => (
323
+ <div
324
+ ref={ref}
325
+ data-sidebar="header"
326
+ className={cn("flex flex-col gap-2 p-2", className)}
327
+ {...props}
328
+ />
329
+ ))
330
+ SidebarHeader.displayName = "SidebarHeader"
331
+
332
+ const SidebarFooter = React.forwardRef<
333
+ HTMLDivElement,
334
+ React.HTMLAttributes<HTMLDivElement>
335
+ >(({ className, ...props }, ref) => (
336
+ <div
337
+ ref={ref}
338
+ data-sidebar="footer"
339
+ className={cn("flex flex-col gap-2 p-2", className)}
340
+ {...props}
341
+ />
342
+ ))
343
+ SidebarFooter.displayName = "SidebarFooter"
344
+
345
+ const SidebarContent = React.forwardRef<
346
+ HTMLDivElement,
347
+ React.HTMLAttributes<HTMLDivElement>
348
+ >(({ className, ...props }, ref) => (
349
+ <div
350
+ ref={ref}
351
+ data-sidebar="content"
352
+ className={cn(
353
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
354
+ className
355
+ )}
356
+ {...props}
357
+ />
358
+ ))
359
+ SidebarContent.displayName = "SidebarContent"
360
+
361
+ const SidebarGroup = React.forwardRef<
362
+ HTMLDivElement,
363
+ React.HTMLAttributes<HTMLDivElement>
364
+ >(({ className, ...props }, ref) => (
365
+ <div
366
+ ref={ref}
367
+ data-sidebar="group"
368
+ className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
369
+ {...props}
370
+ />
371
+ ))
372
+ SidebarGroup.displayName = "SidebarGroup"
373
+
374
+ const SidebarGroupLabel = React.forwardRef<
375
+ HTMLDivElement,
376
+ React.HTMLAttributes<HTMLDivElement> & { asChild?: boolean }
377
+ >(({ className, asChild = false, ...props }, ref) => {
378
+ const Comp = asChild ? Slot : "div"
379
+
380
+ return (
381
+ <Comp
382
+ ref={ref}
383
+ data-sidebar="group-label"
384
+ className={cn(
385
+ "duration-200 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,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
386
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
387
+ className
388
+ )}
389
+ {...props}
390
+ />
391
+ )
392
+ })
393
+ SidebarGroupLabel.displayName = "SidebarGroupLabel"
394
+
395
+ const SidebarGroupContent = React.forwardRef<
396
+ HTMLDivElement,
397
+ React.HTMLAttributes<HTMLDivElement>
398
+ >(({ className, ...props }, ref) => (
399
+ <div
400
+ ref={ref}
401
+ data-sidebar="group-content"
402
+ className={cn("w-full text-sm", className)}
403
+ {...props}
404
+ />
405
+ ))
406
+ SidebarGroupContent.displayName = "SidebarGroupContent"
407
+
408
+ const SidebarMenu = React.forwardRef<
409
+ HTMLUListElement,
410
+ React.HTMLAttributes<HTMLUListElement>
411
+ >(({ className, ...props }, ref) => (
412
+ <ul
413
+ ref={ref}
414
+ data-sidebar="menu"
415
+ className={cn("flex w-full min-w-0 flex-col gap-1", className)}
416
+ {...props}
417
+ />
418
+ ))
419
+ SidebarMenu.displayName = "SidebarMenu"
420
+
421
+ const SidebarMenuItem = React.forwardRef<
422
+ HTMLLIElement,
423
+ React.LiHTMLAttributes<HTMLLIElement>
424
+ >(({ className, ...props }, ref) => (
425
+ <li
426
+ ref={ref}
427
+ data-sidebar="menu-item"
428
+ className={cn("group/menu-item relative", className)}
429
+ {...props}
430
+ />
431
+ ))
432
+ SidebarMenuItem.displayName = "SidebarMenuItem"
433
+
434
+ const SidebarMenuButton = React.forwardRef<
435
+ HTMLButtonElement,
436
+ React.ButtonHTMLAttributes<HTMLButtonElement> & {
437
+ asChild?: boolean
438
+ isActive?: boolean
439
+ tooltip?: string | React.ComponentProps<any>
440
+ variant?: "default" | "ghost" | "outline" | "secondary" | "destructive" | "link" | null | undefined
441
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined
442
+ }
443
+ >(
444
+ (
445
+ {
446
+ asChild = false,
447
+ isActive = false,
448
+ variant = "default",
449
+ size = "default",
450
+ tooltip,
451
+ className,
452
+ ...props
453
+ },
454
+ ref
455
+ ) => {
456
+ const { isMobile, state, setOpen } = useSidebar()
457
+
458
+ const buttonClass = cn(
459
+ "cursor-pointer 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 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
460
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground",
461
+ "data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground",
462
+ "group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:group-data-[collapsible=icon]:hidden",
463
+ className
464
+ )
465
+
466
+ // If tooltip is needed, we should implement it. For now, ignoring complexity of tooltip.
467
+
468
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
469
+ props.onClick?.(e)
470
+ if (!isMobile && state === "collapsed") {
471
+ setOpen(true)
472
+ }
473
+ }
474
+
475
+ const Comp = asChild ? Slot : "button"
476
+
477
+ return (
478
+ <Comp
479
+ ref={ref}
480
+ data-sidebar="menu-button"
481
+ data-size={size}
482
+ data-active={isActive}
483
+ className={buttonClass}
484
+ onClick={handleClick}
485
+ {...props}
486
+ />
487
+ )
488
+ }
489
+ )
490
+ SidebarMenuButton.displayName = "SidebarMenuButton"
491
+
492
+ const SidebarMenuAction = React.forwardRef<
493
+ HTMLButtonElement,
494
+ React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; showOnHover?: boolean }
495
+ >(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
496
+ const Comp = asChild ? Slot : "button"
497
+
498
+ return (
499
+ <Comp
500
+ ref={ref}
501
+ data-sidebar="menu-action"
502
+ className={cn(
503
+ "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",
504
+ // Increases the hit area of the button on mobile.
505
+ "after:absolute after:-inset-2 after:md:hidden",
506
+ "peer-data-[size=sm]/menu-button:top-1",
507
+ "peer-data-[size=default]/menu-button:top-1.5",
508
+ "peer-data-[size=lg]/menu-button:top-2.5",
509
+ "group-data-[collapsible=icon]:hidden",
510
+ showOnHover &&
511
+ "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",
512
+ className
513
+ )}
514
+ {...props}
515
+ />
516
+ )
517
+ })
518
+ SidebarMenuAction.displayName = "SidebarMenuAction"
519
+
520
+ const SidebarMenuSub = React.forwardRef<
521
+ HTMLUListElement,
522
+ React.HTMLAttributes<HTMLUListElement>
523
+ >(({ className, ...props }, ref) => (
524
+ <ul
525
+ ref={ref}
526
+ data-sidebar="menu-sub"
527
+ className={cn(
528
+ "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",
529
+ "group-data-[collapsible=icon]:hidden",
530
+ className
531
+ )}
532
+ {...props}
533
+ />
534
+ ))
535
+ SidebarMenuSub.displayName = "SidebarMenuSub"
536
+
537
+ const SidebarMenuSubItem = React.forwardRef<
538
+ HTMLLIElement,
539
+ React.LiHTMLAttributes<HTMLLIElement>
540
+ >(({ ...props }, ref) => <li ref={ref} {...props} />)
541
+ SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
542
+
543
+ const SidebarMenuSubButton = React.forwardRef<
544
+ HTMLAnchorElement,
545
+ React.AnchorHTMLAttributes<HTMLAnchorElement> & {
546
+ asChild?: boolean
547
+ size?: "sm" | "md"
548
+ isActive?: boolean
549
+ }
550
+ >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
551
+ const Comp = asChild ? Slot : "a"
552
+
553
+ return (
554
+ <Comp
555
+ ref={ref}
556
+ data-sidebar="menu-sub-button"
557
+ data-size={size}
558
+ data-active={isActive}
559
+ className={cn(
560
+ "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 group-data-[collapsible=icon]:hidden",
561
+ size === "sm" && "text-xs",
562
+ size === "md" && "text-sm",
563
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
564
+ className
565
+ )}
566
+ {...props}
567
+ />
568
+ )
569
+ })
570
+ SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
571
+
572
+ const SidebarInput = React.forwardRef<
573
+ HTMLInputElement,
574
+ React.InputHTMLAttributes<HTMLInputElement>
575
+ >(({ className, ...props }, ref) => {
576
+ return (
577
+ <input
578
+ ref={ref}
579
+ data-sidebar="input"
580
+ className={cn(
581
+ "flex h-8 w-full bg-background rounded-md px-3 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 group-data-[collapsible=icon]:w-0 group-data-[collapsible=icon]:p-0 group-data-[collapsible=icon]:border-0 group-data-[collapsible=icon]:hidden",
582
+ className
583
+ )}
584
+ {...props}
585
+ />
586
+ )
587
+ })
588
+ SidebarInput.displayName = "SidebarInput"
589
+
590
+
591
+ const SidebarSeparator = React.forwardRef<
592
+ HTMLDivElement,
593
+ React.HTMLAttributes<HTMLDivElement>
594
+ >(({ className, ...props }, ref) => {
595
+ return (
596
+ <div
597
+ ref={ref}
598
+ data-sidebar="separator"
599
+ className={cn("mx-2 w-auto bg-sidebar-border h-px", className)}
600
+ {...props}
601
+ />
602
+ )
603
+ })
604
+ SidebarSeparator.displayName = "SidebarSeparator"
605
+
606
+ export {
607
+ Sidebar,
608
+ SidebarContent,
609
+ SidebarFooter,
610
+ SidebarGroup,
611
+ SidebarGroupContent,
612
+ SidebarGroupLabel,
613
+ SidebarHeader,
614
+ SidebarInput,
615
+ SidebarInset,
616
+ SidebarMenu,
617
+ SidebarMenuAction,
618
+ SidebarMenuButton,
619
+ SidebarMenuItem,
620
+ SidebarMenuSub,
621
+ SidebarMenuSubButton,
622
+ SidebarMenuSubItem,
623
+ SidebarProvider,
624
+ SidebarRail,
625
+ SidebarSeparator,
626
+ SidebarTrigger,
627
+ useSidebar,
628
+ }