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