'use client' import { cva, type VariantProps } from 'class-variance-authority' import { Slot } from 'radix-ui' import * as React from 'react' import { Button } from '@/components/ui/button' import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from '@/components/ui/Sheet' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useIsMobile } from '@/hooks/use-mobile' import { cn } from '@/lib/utils' import { PanelLeftIcon } from 'lucide-react' export 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(null) function useSidebar() { const context = React.useContext(SidebarContext) if (!context) { throw new Error('useSidebar must be used within a SidebarProvider.') } return context } const 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( () => (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( () => ({ state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar, }), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], ) return (
{children}
) } const 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() // Mobile sheet must run before collapsible="none" so always-expanded // desktop rails still open as a sheet on small screens. if (isMobile) { return ( Sidebar Displays the mobile sidebar.
{children}
) } if (collapsible === 'none') { return (
{children}
) } return (
{/* This is what handles the sidebar gap on desktop */}
) } const SidebarTrigger = ({ className, label = 'Abrir ou fechar menu da campanha', onClick, ...props }: React.ComponentProps & { label?: string }) => { const { toggleSidebar } = useSidebar() return ( ) } const SidebarInset = ({ className, ...props }: React.ComponentProps<'main'>) => (
) const SidebarHeader = ({ className, ...props }: React.ComponentProps<'div'>) => (
) const SidebarFooter = ({ className, ...props }: React.ComponentProps<'div'>) => (
) const SidebarContent = ({ className, ...props }: React.ComponentProps<'div'>) => (
) const SidebarGroup = ({ className, ...props }: React.ComponentProps<'div'>) => (
) const SidebarGroupContent = ({ className, ...props }: React.ComponentProps<'div'>) => (
) const SidebarMenu = ({ className, ...props }: React.ComponentProps<'ul'>) => (
    li]:mt-0', className)} {...props} /> ) const SidebarMenuItem = ({ className, ...props }: React.ComponentProps<'li'>) => (
  • ) const sidebarMenuButtonVariants = cva( 'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden 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! 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 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground [&_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 shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground 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', }, }, ) const SidebarMenuButton = ({ asChild = false, isActive = false, variant = 'default', size = 'default', tooltip, className, ...props }: React.ComponentProps<'button'> & { asChild?: boolean isActive?: boolean tooltip?: string | React.ComponentProps } & VariantProps) => { const Comp = asChild ? Slot.Root : 'button' const { isMobile, state } = useSidebar() const button = ( ) if (!tooltip) { return button } if (typeof tooltip === 'string') { tooltip = { children: tooltip, } } return ( {button} ) } /** * Re-added from upstream shadcn for B18 (the kit had been pruned of all four * submenu pieces). Two deliberate deviations, both load-bearing: * * - upstream's `showOnHover` prop is not here — the only caller reveals its * action against the SUB-item group, and `group-hover/menu-item` would leak * from the parent row, revealing every child's action at once; * - the mobile hit-area expansion is not `md:`-gated. A 20 px visible target on * a pointer device is still a 20 px target (SC 2.5.8 has no mouse exemption), * which is the call B34 already made for the chip "×". */ const SidebarMenuAction = ({ className, asChild = false, ...props }: React.ComponentProps<'button'> & { asChild?: boolean }) => { const Comp = asChild ? Slot.Root : 'button' return ( svg]:size-4 [&>svg]:shrink-0', className, )} {...props} /> ) } const SidebarMenuSub = ({ className, ...props }: React.ComponentProps<'ul'>) => (
      ) const SidebarMenuSubItem = ({ className, ...props }: React.ComponentProps<'li'>) => (
    • ) const SidebarMenuSubButton = ({ asChild = false, size = 'md', isActive = false, className, ...props }: React.ComponentProps<'a'> & { asChild?: boolean size?: 'sm' | 'md' isActive?: boolean }) => { const Comp = asChild ? Slot.Root : 'a' return ( span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', size === 'sm' && 'text-xs', size === 'md' && 'text-sm', className, )} {...props} /> ) } export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarTrigger, useSidebar, }