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