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