{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "sidebar-menu", "type": "registry:ui", "title": "Sidebar Menu", "description": "SidebarGlass Menu Components", "dependencies": [ "@radix-ui/react-slot" ], "registryDependencies": [ "cn" ], "files": [ { "path": "components/glass/ui/sidebar-menu.tsx", "type": "registry:component", "content": "/* eslint-disable react-refresh/only-export-components */\n/**\n * SidebarGlass Menu Components\n *\n * Menu components for building navigation within the sidebar.\n * 100% API compatible with shadcn/ui Sidebar menu components.\n *\n * @module sidebar-menu\n */\n\nimport {\n forwardRef,\n type ReactNode,\n type CSSProperties,\n type ComponentPropsWithoutRef,\n createContext,\n useContext,\n} from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cn } from '@/lib/utils';\nimport { useSidebar } from './sidebar-context';\nimport { TooltipGlassSimple } from '@/components/glass/ui/tooltip-glass';\nimport { SkeletonGlass } from '@/components/glass/ui/skeleton-glass';\n\n// ========================================\n// SIDEBAR GROUP\n// ========================================\n\nexport interface SidebarGroupProps extends ComponentPropsWithoutRef<'div'> {\n children: ReactNode;\n}\n\n/**\n * SidebarGlass.Group - Container for a group of menu items\n */\nexport const SidebarGroup = forwardRef(\n ({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n\nSidebarGroup.displayName = 'SidebarGlass.Group';\n\n// ========================================\n// SIDEBAR GROUP LABEL\n// ========================================\n\nexport interface SidebarGroupLabelProps extends ComponentPropsWithoutRef<'div'> {\n children: ReactNode;\n /** Render as child element */\n asChild?: boolean;\n}\n\n/**\n * SidebarGlass.GroupLabel - Label for a group of menu items\n */\nexport const SidebarGroupLabel = forwardRef(\n ({ children, asChild = false, className, ...props }, ref) => {\n const { state } = useSidebar();\n const Comp = asChild ? Slot : 'div';\n\n return (\n \n {children}\n \n );\n }\n);\n\nSidebarGroupLabel.displayName = 'SidebarGlass.GroupLabel';\n\n// ========================================\n// SIDEBAR GROUP ACTION\n// ========================================\n\nexport interface SidebarGroupActionProps extends ComponentPropsWithoutRef<'button'> {\n /** Render as child element */\n asChild?: boolean;\n}\n\n/**\n * SidebarGlass.GroupAction - Action button in group header\n */\nexport const SidebarGroupAction = forwardRef(\n ({ asChild = false, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n\n return (\n svg]:size-4 [&>svg]:shrink-0',\n // Hide when collapsed\n 'group-data-[state=collapsed]/sidebar:hidden',\n // Show on hover\n 'after:absolute after:-inset-2 after:md:hidden',\n className\n )}\n {...props}\n />\n );\n }\n);\n\nSidebarGroupAction.displayName = 'SidebarGlass.GroupAction';\n\n// ========================================\n// SIDEBAR GROUP CONTENT\n// ========================================\n\nexport interface SidebarGroupContentProps extends ComponentPropsWithoutRef<'div'> {\n children: ReactNode;\n}\n\n/**\n * SidebarGlass.GroupContent - Content wrapper for group items\n */\nexport const SidebarGroupContent = forwardRef(\n ({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n\nSidebarGroupContent.displayName = 'SidebarGlass.GroupContent';\n\n// ========================================\n// SIDEBAR MENU\n// ========================================\n\nexport interface SidebarMenuProps extends ComponentPropsWithoutRef<'ul'> {\n children: ReactNode;\n}\n\n/**\n * SidebarGlass.Menu - Container for menu items\n */\nexport const SidebarMenu = forwardRef(\n ({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n\nSidebarMenu.displayName = 'SidebarGlass.Menu';\n\n// ========================================\n// SIDEBAR MENU ITEM\n// ========================================\n\nexport interface SidebarMenuItemProps extends ComponentPropsWithoutRef<'li'> {\n children: ReactNode;\n}\n\n/**\n * SidebarGlass.MenuItem - Container for a single menu item\n */\nexport const SidebarMenuItem = forwardRef(\n ({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n\nSidebarMenuItem.displayName = 'SidebarGlass.MenuItem';\n\n// ========================================\n// SIDEBAR MENU BUTTON\n// ========================================\n\nexport type SidebarMenuButtonSize = 'default' | 'sm' | 'lg';\nexport type SidebarMenuButtonVariant = 'default' | 'outline';\n\nexport interface SidebarMenuButtonProps extends ComponentPropsWithoutRef<'button'> {\n /** Render as child element */\n asChild?: boolean;\n /** Whether this item is active */\n isActive?: boolean;\n /** Tooltip text when collapsed */\n tooltip?: string | ReactNode;\n /** Button size */\n size?: SidebarMenuButtonSize;\n /** Button variant */\n variant?: SidebarMenuButtonVariant;\n}\n\nconst menuButtonSizeClasses: Record = {\n default: 'h-8 text-sm',\n sm: 'h-7 text-xs',\n lg: 'h-10 text-sm group-data-[state=collapsed]/sidebar:!p-0',\n};\n\n/**\n * SidebarGlass.MenuButton - Interactive menu button\n */\nexport const SidebarMenuButton = forwardRef(\n (\n {\n asChild = false,\n isActive = false,\n tooltip,\n size = 'default',\n variant = 'default',\n className,\n children,\n ...props\n },\n ref\n ) => {\n const { state, isMobile } = useSidebar();\n const Comp = asChild ? Slot : 'button';\n\n const button = (\n span:last-child]:truncate',\n '[&>svg]:size-4 [&>svg]:shrink-0',\n // Size variants\n menuButtonSizeClasses[size],\n // Variant styles\n variant === 'default' && [\n 'hover:bg-[var(--sidebar-accent)] hover:text-[var(--sidebar-accent-foreground)]',\n ],\n variant === 'outline' && [\n 'bg-transparent shadow-none',\n 'hover:bg-[var(--sidebar-accent)] hover:text-[var(--sidebar-accent-foreground)]',\n 'hover:shadow-[0_0_0_1px_var(--sidebar-border)]',\n ],\n // Active state\n isActive && [\n 'bg-[var(--sidebar-primary)] text-[var(--sidebar-primary-foreground)]',\n 'hover:bg-[var(--sidebar-primary)] hover:text-[var(--sidebar-primary-foreground)]',\n ],\n // Collapsed state - icon only\n 'group-data-[state=collapsed]/sidebar:w-8 group-data-[state=collapsed]/sidebar:!px-0',\n 'group-data-[state=collapsed]/sidebar:justify-center',\n className\n )}\n {...props}\n >\n {children}\n \n );\n\n // No tooltip on mobile or expanded state\n if (!tooltip || isMobile || state === 'expanded') {\n return button;\n }\n\n return (\n \n {button}\n \n );\n }\n);\n\nSidebarMenuButton.displayName = 'SidebarGlass.MenuButton';\n\n// ========================================\n// SIDEBAR MENU ACTION\n// ========================================\n\nexport interface SidebarMenuActionProps extends ComponentPropsWithoutRef<'button'> {\n /** Render as child element */\n asChild?: boolean;\n /** Only show on hover */\n showOnHover?: boolean;\n}\n\n/**\n * SidebarGlass.MenuAction - Action button within menu item\n */\nexport const SidebarMenuAction = forwardRef(\n ({ asChild = false, showOnHover = false, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button';\n\n return (\n svg]:size-4 [&>svg]:shrink-0',\n // Hide when collapsed\n 'group-data-[state=collapsed]/sidebar:hidden',\n // Show on hover\n showOnHover &&\n 'peer-hover/menu-button:opacity-100 group-focus-within/menu-item:opacity-100',\n showOnHover && 'data-[state=open]:opacity-100 md:opacity-0',\n // Tap area for touch\n 'after:absolute after:-inset-2 after:md:hidden',\n className\n )}\n {...props}\n />\n );\n }\n);\n\nSidebarMenuAction.displayName = 'SidebarGlass.MenuAction';\n\n// ========================================\n// SIDEBAR MENU BADGE\n// ========================================\n\nexport interface SidebarMenuBadgeProps extends ComponentPropsWithoutRef<'div'> {\n children: ReactNode;\n}\n\n/**\n * SidebarGlass.MenuBadge - Badge within menu item\n */\nexport const SidebarMenuBadge = forwardRef(\n ({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n }\n);\n\nSidebarMenuBadge.displayName = 'SidebarGlass.MenuBadge';\n\n// ========================================\n// SIDEBAR MENU SKELETON\n// ========================================\n\nexport interface SidebarMenuSkeletonProps extends ComponentPropsWithoutRef<'div'> {\n /** Show icon placeholder */\n showIcon?: boolean;\n}\n\n/**\n * SidebarGlass.MenuSkeleton - Loading skeleton for menu items\n */\nexport const SidebarMenuSkeleton = forwardRef(\n ({ showIcon = false, className, ...props }, ref) => {\n // Fixed width for consistent appearance (avoids Math.random during render)\n const width = '70%';\n\n return (\n \n {showIcon && }\n \n \n );\n }\n);\n\nSidebarMenuSkeleton.displayName = 'SidebarGlass.MenuSkeleton';\n\n// ========================================\n// SIDEBAR MENU SUB (SUBMENU CONTAINER)\n// ========================================\n\n// Context for submenu state\ninterface SidebarMenuSubContextValue {\n open: boolean;\n}\n\nconst SidebarMenuSubContext = createContext(null);\n\nexport interface SidebarMenuSubProps extends ComponentPropsWithoutRef<'ul'> {\n children: ReactNode;\n}\n\n/**\n * SidebarGlass.MenuSub - Container for submenu items\n */\nexport const SidebarMenuSub = forwardRef(\n ({ children, className, ...props }, ref) => {\n return (\n \n \n {children}\n \n \n );\n }\n);\n\nSidebarMenuSub.displayName = 'SidebarGlass.MenuSub';\n\n// ========================================\n// SIDEBAR MENU SUB ITEM\n// ========================================\n\nexport interface SidebarMenuSubItemProps extends ComponentPropsWithoutRef<'li'> {\n children: ReactNode;\n}\n\n/**\n * SidebarGlass.MenuSubItem - Container for a submenu item\n */\nexport const SidebarMenuSubItem = forwardRef(\n ({ children, className, ...props }, ref) => {\n return (\n
  • \n {children}\n
  • \n );\n }\n);\n\nSidebarMenuSubItem.displayName = 'SidebarGlass.MenuSubItem';\n\n// ========================================\n// SIDEBAR MENU SUB BUTTON\n// ========================================\n\nexport interface SidebarMenuSubButtonProps extends ComponentPropsWithoutRef<'a'> {\n /** Render as child element */\n asChild?: boolean;\n /** Whether this item is active */\n isActive?: boolean;\n /** Button size */\n size?: 'sm' | 'md';\n}\n\n/**\n * SidebarGlass.MenuSubButton - Interactive submenu button\n */\nexport const SidebarMenuSubButton = forwardRef(\n ({ asChild = false, isActive = false, size = 'md', className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'a';\n\n return (\n span:last-child]:truncate',\n '[&>svg]:size-4 [&>svg]:shrink-0',\n // Size variants\n size === 'sm' && 'h-7 px-2 text-xs',\n size === 'md' && 'h-8 px-2 text-sm',\n // Active state\n isActive && ['border-[var(--sidebar-primary)]', 'text-[var(--sidebar-foreground)]'],\n className\n )}\n {...props}\n />\n );\n }\n);\n\nSidebarMenuSubButton.displayName = 'SidebarGlass.MenuSubButton';\n\n// Export hook for submenu context (optional use)\nexport function useSidebarMenuSub() {\n const context = useContext(SidebarMenuSubContext);\n return context;\n}\n" } ], "categories": [ "ui" ], "cssVars": { "light": { "--glass-bg": "rgba(255, 255, 255, 0.1)", "--glass-border": "rgba(255, 255, 255, 0.2)", "--blur-sm": "8px", "--blur-md": "16px", "--blur-lg": "24px" }, "dark": { "--glass-bg": "rgba(255, 255, 255, 0.05)", "--glass-border": "rgba(255, 255, 255, 0.1)", "--blur-sm": "8px", "--blur-md": "16px", "--blur-lg": "24px" } } }