| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723 |
- 'use client';
- import * as React from 'react';
- import { mergeProps } from '@base-ui/react/merge-props';
- import { useRender } from '@base-ui/react/use-render';
- import { cva, type VariantProps } from 'class-variance-authority';
- import { useIsMobile } from '@/hooks/use-mobile';
- import { cn } from '@/lib/utils';
- import { Button } from '@/components/ui/button';
- import { Input } from '@/components/ui/input';
- import { Separator } from '@/components/ui/separator';
- import {
- Sheet,
- SheetContent,
- SheetDescription,
- SheetHeader,
- SheetTitle,
- } from '@/components/ui/sheet';
- import { Skeleton } from '@/components/ui/skeleton';
- import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
- } from '@/components/ui/tooltip';
- import { PanelLeftIcon } from 'lucide-react';
- const SIDEBAR_COOKIE_NAME = 'sidebar_state';
- const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
- const SIDEBAR_WIDTH = '16rem';
- const SIDEBAR_WIDTH_MOBILE = '18rem';
- const SIDEBAR_WIDTH_ICON = '3rem';
- const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
- type SidebarContextProps = {
- state: 'expanded' | 'collapsed';
- open: boolean;
- setOpen: (open: boolean) => void;
- openMobile: boolean;
- setOpenMobile: (open: boolean) => void;
- isMobile: boolean;
- toggleSidebar: () => void;
- };
- const SidebarContext = React.createContext<SidebarContextProps | null>(null);
- function useSidebar() {
- const context = React.useContext(SidebarContext);
- if (!context) {
- throw new Error('useSidebar must be used within a SidebarProvider.');
- }
- return context;
- }
- function SidebarProvider({
- defaultOpen = true,
- open: openProp,
- onOpenChange: setOpenProp,
- className,
- style,
- children,
- ...props
- }: React.ComponentProps<'div'> & {
- defaultOpen?: boolean;
- open?: boolean;
- onOpenChange?: (open: boolean) => void;
- }) {
- const isMobile = useIsMobile();
- const [openMobile, setOpenMobile] = React.useState(false);
- // This is the internal state of the sidebar.
- // We use openProp and setOpenProp for control from outside the component.
- const [_open, _setOpen] = React.useState(defaultOpen);
- const open = openProp ?? _open;
- const setOpen = React.useCallback(
- (value: boolean | ((value: boolean) => boolean)) => {
- const openState = typeof value === 'function' ? value(open) : value;
- if (setOpenProp) {
- setOpenProp(openState);
- } else {
- _setOpen(openState);
- }
- // This sets the cookie to keep the sidebar state.
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
- },
- [setOpenProp, open],
- );
- // Helper to toggle the sidebar.
- const toggleSidebar = React.useCallback(() => {
- return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
- }, [isMobile, setOpen, setOpenMobile]);
- // Adds a keyboard shortcut to toggle the sidebar.
- React.useEffect(() => {
- const handleKeyDown = (event: KeyboardEvent) => {
- if (
- event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
- (event.metaKey || event.ctrlKey)
- ) {
- event.preventDefault();
- toggleSidebar();
- }
- };
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, [toggleSidebar]);
- // We add a state so that we can do data-state="expanded" or "collapsed".
- // This makes it easier to style the sidebar with Tailwind classes.
- const state = open ? 'expanded' : 'collapsed';
- const contextValue = React.useMemo<SidebarContextProps>(
- () => ({
- state,
- open,
- setOpen,
- isMobile,
- openMobile,
- setOpenMobile,
- toggleSidebar,
- }),
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
- );
- return (
- <SidebarContext.Provider value={contextValue}>
- <div
- data-slot="sidebar-wrapper"
- style={
- {
- '--sidebar-width': SIDEBAR_WIDTH,
- '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
- ...style,
- } as React.CSSProperties
- }
- className={cn(
- 'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',
- className,
- )}
- {...props}
- >
- {children}
- </div>
- </SidebarContext.Provider>
- );
- }
- function Sidebar({
- side = 'left',
- variant = 'sidebar',
- collapsible = 'offcanvas',
- className,
- children,
- dir,
- ...props
- }: React.ComponentProps<'div'> & {
- side?: 'left' | 'right';
- variant?: 'sidebar' | 'floating' | 'inset';
- collapsible?: 'offcanvas' | 'icon' | 'none';
- }) {
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
- if (collapsible === 'none') {
- return (
- <div
- data-slot="sidebar"
- className={cn(
- 'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',
- className,
- )}
- {...props}
- >
- {children}
- </div>
- );
- }
- if (isMobile) {
- return (
- <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
- <SheetContent
- dir={dir}
- data-sidebar="sidebar"
- data-slot="sidebar"
- data-mobile="true"
- className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
- style={
- {
- '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
- } as React.CSSProperties
- }
- side={side}
- >
- <SheetHeader className="sr-only">
- <SheetTitle>Sidebar</SheetTitle>
- <SheetDescription>Displays the mobile sidebar.</SheetDescription>
- </SheetHeader>
- <div className="flex h-full w-full flex-col">{children}</div>
- </SheetContent>
- </Sheet>
- );
- }
- return (
- <div
- className="group peer hidden text-sidebar-foreground md:block"
- data-state={state}
- data-collapsible={state === 'collapsed' ? collapsible : ''}
- data-variant={variant}
- data-side={side}
- data-slot="sidebar"
- >
- {/* This is what handles the sidebar gap on desktop */}
- <div
- data-slot="sidebar-gap"
- className={cn(
- 'transition-[width] duration-200 ease-linear relative w-(--sidebar-width) bg-transparent',
- 'group-data-[collapsible=offcanvas]:w-0',
- 'group-data-[side=right]:rotate-180',
- variant === 'floating' || variant === 'inset'
- ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
- : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
- )}
- />
- <div
- data-slot="sidebar-container"
- data-side={side}
- className={cn(
- 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex',
- // Adjust the padding for floating and inset variants.
- variant === 'floating' || variant === 'inset'
- ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
- : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
- className,
- )}
- {...props}
- >
- <div
- data-sidebar="sidebar"
- data-slot="sidebar-inner"
- className="bg-sidebar group-data-[variant=floating]:ring-sidebar-border group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 flex size-full flex-col"
- >
- {children}
- </div>
- </div>
- </div>
- );
- }
- function SidebarTrigger({
- className,
- onClick,
- ...props
- }: React.ComponentProps<typeof Button>) {
- const { toggleSidebar } = useSidebar();
- return (
- <Button
- data-sidebar="trigger"
- data-slot="sidebar-trigger"
- variant="ghost"
- size="icon-sm"
- className={cn(className)}
- onClick={(event) => {
- onClick?.(event);
- toggleSidebar();
- }}
- {...props}
- >
- <PanelLeftIcon className="cn-rtl-flip" />
- <span className="sr-only">Toggle Sidebar</span>
- </Button>
- );
- }
- function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
- const { toggleSidebar } = useSidebar();
- return (
- <button
- data-sidebar="rail"
- data-slot="sidebar-rail"
- aria-label="Toggle Sidebar"
- tabIndex={-1}
- onClick={toggleSidebar}
- title="Toggle Sidebar"
- className={cn(
- 'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2',
- 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
- '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
- 'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar',
- '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
- '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
- className,
- )}
- {...props}
- />
- );
- }
- function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
- return (
- <main
- data-slot="sidebar-inset"
- className={cn(
- 'bg-background 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 md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 relative flex w-full flex-1 flex-col',
- className,
- )}
- {...props}
- />
- );
- }
- function SidebarInput({
- className,
- ...props
- }: React.ComponentProps<typeof Input>) {
- return (
- <Input
- data-slot="sidebar-input"
- data-sidebar="input"
- className={cn('bg-background h-8 w-full shadow-none', className)}
- {...props}
- />
- );
- }
- function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
- return (
- <div
- data-slot="sidebar-header"
- data-sidebar="header"
- className={cn('gap-2 p-2 flex flex-col', className)}
- {...props}
- />
- );
- }
- function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
- return (
- <div
- data-slot="sidebar-footer"
- data-sidebar="footer"
- className={cn('gap-2 p-2 flex flex-col', className)}
- {...props}
- />
- );
- }
- function SidebarSeparator({
- className,
- ...props
- }: React.ComponentProps<typeof Separator>) {
- return (
- <Separator
- data-slot="sidebar-separator"
- data-sidebar="separator"
- className={cn('bg-sidebar-border mx-2 w-auto', className)}
- {...props}
- />
- );
- }
- function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
- return (
- <div
- data-slot="sidebar-content"
- data-sidebar="content"
- className={cn(
- 'no-scrollbar gap-0 flex min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden',
- className,
- )}
- {...props}
- />
- );
- }
- function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
- return (
- <div
- data-slot="sidebar-group"
- data-sidebar="group"
- className={cn('p-2 relative flex w-full min-w-0 flex-col', className)}
- {...props}
- />
- );
- }
- function SidebarGroupLabel({
- className,
- render,
- ...props
- }: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {
- return useRender({
- defaultTagName: 'div',
- props: mergeProps<'div'>(
- {
- className: cn(
- 'text-sidebar-foreground/70 ring-sidebar-ring h-8 rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 focus-visible:ring-2 [&>svg]:size-4 flex shrink-0 items-center outline-hidden [&>svg]:shrink-0',
- className,
- ),
- },
- props,
- ),
- render,
- state: {
- slot: 'sidebar-group-label',
- sidebar: 'group-label',
- },
- });
- }
- function SidebarGroupAction({
- className,
- render,
- ...props
- }: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {
- return useRender({
- defaultTagName: 'button',
- props: mergeProps<'button'>(
- {
- className: cn(
- 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 w-5 rounded-md p-0 focus-visible:ring-2 [&>svg]:size-4 flex aspect-square items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden [&>svg]:shrink-0',
- className,
- ),
- },
- props,
- ),
- render,
- state: {
- slot: 'sidebar-group-action',
- sidebar: 'group-action',
- },
- });
- }
- function SidebarGroupContent({
- className,
- ...props
- }: React.ComponentProps<'div'>) {
- return (
- <div
- data-slot="sidebar-group-content"
- data-sidebar="group-content"
- className={cn('text-sm w-full', className)}
- {...props}
- />
- );
- }
- function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
- return (
- <ul
- data-slot="sidebar-menu"
- data-sidebar="menu"
- className={cn('gap-0 flex w-full min-w-0 flex-col', className)}
- {...props}
- />
- );
- }
- function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
- return (
- <li
- data-slot="sidebar-menu-item"
- data-sidebar="menu-item"
- className={cn('group/menu-item relative', className)}
- {...props}
- />
- );
- }
- const sidebarMenuButtonVariants = cva(
- 'ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button group/menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate',
- {
- variants: {
- variant: {
- default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
- outline:
- 'bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',
- },
- size: {
- default: 'h-8 text-sm',
- sm: 'h-7 text-xs',
- lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
- },
- );
- function SidebarMenuButton({
- render,
- isActive = false,
- variant = 'default',
- size = 'default',
- tooltip,
- className,
- ...props
- }: useRender.ComponentProps<'button'> &
- React.ComponentProps<'button'> & {
- isActive?: boolean;
- tooltip?: string | React.ComponentProps<typeof TooltipContent>;
- } & VariantProps<typeof sidebarMenuButtonVariants>) {
- const { isMobile, state } = useSidebar();
- const comp = useRender({
- defaultTagName: 'button',
- props: mergeProps<'button'>(
- {
- className: cn(sidebarMenuButtonVariants({ variant, size }), className),
- },
- props,
- ),
- render: !tooltip ? render : <TooltipTrigger render={render} />,
- state: {
- slot: 'sidebar-menu-button',
- sidebar: 'menu-button',
- size,
- active: isActive,
- },
- });
- if (!tooltip) {
- return comp;
- }
- if (typeof tooltip === 'string') {
- tooltip = {
- children: tooltip,
- };
- }
- return (
- <Tooltip>
- {comp}
- <TooltipContent
- side="right"
- align="center"
- hidden={state !== 'collapsed' || isMobile}
- {...tooltip}
- />
- </Tooltip>
- );
- }
- function SidebarMenuAction({
- className,
- render,
- showOnHover = false,
- ...props
- }: useRender.ComponentProps<'button'> &
- React.ComponentProps<'button'> & {
- showOnHover?: boolean;
- }) {
- return useRender({
- defaultTagName: 'button',
- props: mergeProps<'button'>(
- {
- className: cn(
- 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 aspect-square w-5 rounded-md p-0 peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 focus-visible:ring-2 [&>svg]:size-4 flex items-center justify-center outline-hidden transition-transform group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden [&>svg]:shrink-0',
- showOnHover &&
- 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0',
- className,
- ),
- },
- props,
- ),
- render,
- state: {
- slot: 'sidebar-menu-action',
- sidebar: 'menu-action',
- },
- });
- }
- function SidebarMenuBadge({
- className,
- ...props
- }: React.ComponentProps<'div'>) {
- return (
- <div
- data-slot="sidebar-menu-badge"
- data-sidebar="menu-badge"
- className={cn(
- 'text-sidebar-foreground peer-hover/menu-button:text-sidebar-accent-foreground peer-data-active/menu-button:text-sidebar-accent-foreground pointer-events-none absolute right-1 h-5 min-w-5 rounded-md px-1 text-xs font-medium peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 flex items-center justify-center tabular-nums select-none group-data-[collapsible=icon]:hidden',
- className,
- )}
- {...props}
- />
- );
- }
- function SidebarMenuSkeleton({
- className,
- showIcon = false,
- ...props
- }: React.ComponentProps<'div'> & {
- showIcon?: boolean;
- }) {
- // Random width between 50 to 90%.
- const [width] = React.useState(() => {
- return `${Math.floor(Math.random() * 40) + 50}%`;
- });
- return (
- <div
- data-slot="sidebar-menu-skeleton"
- data-sidebar="menu-skeleton"
- className={cn('h-8 gap-2 rounded-md px-2 flex items-center', className)}
- {...props}
- >
- {showIcon && (
- <Skeleton
- className="size-4 rounded-md"
- data-sidebar="menu-skeleton-icon"
- />
- )}
- <Skeleton
- className="h-4 max-w-(--skeleton-width) flex-1"
- data-sidebar="menu-skeleton-text"
- style={
- {
- '--skeleton-width': width,
- } as React.CSSProperties
- }
- />
- </div>
- );
- }
- function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
- return (
- <ul
- data-slot="sidebar-menu-sub"
- data-sidebar="menu-sub"
- className={cn(
- 'border-sidebar-border mx-3.5 translate-x-px gap-1 border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden flex min-w-0 flex-col',
- className,
- )}
- {...props}
- />
- );
- }
- function SidebarMenuSubItem({
- className,
- ...props
- }: React.ComponentProps<'li'>) {
- return (
- <li
- data-slot="sidebar-menu-sub-item"
- data-sidebar="menu-sub-item"
- className={cn('group/menu-sub-item relative', className)}
- {...props}
- />
- );
- }
- function SidebarMenuSubButton({
- render,
- size = 'md',
- isActive = false,
- className,
- ...props
- }: useRender.ComponentProps<'a'> &
- React.ComponentProps<'a'> & {
- size?: 'sm' | 'md';
- isActive?: boolean;
- }) {
- return useRender({
- defaultTagName: 'a',
- props: mergeProps<'a'>(
- {
- className: cn(
- 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 focus-visible:ring-2 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0',
- className,
- ),
- },
- props,
- ),
- render,
- state: {
- slot: 'sidebar-menu-sub-button',
- sidebar: 'menu-sub-button',
- size,
- active: isActive,
- },
- });
- }
- export {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarGroup,
- SidebarGroupAction,
- SidebarGroupContent,
- SidebarGroupLabel,
- SidebarHeader,
- SidebarInput,
- SidebarInset,
- SidebarMenu,
- SidebarMenuAction,
- SidebarMenuBadge,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSkeleton,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarProvider,
- SidebarRail,
- SidebarSeparator,
- SidebarTrigger,
- useSidebar,
- };
|