{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"sidebar","type":"registry:ui","title":"Sidebar","description":"Sidebar from react-fancy","package":"react-fancy","dependencies":["lucide-react"],"registryDependencies":[],"files":[{"path":"components\/fancy\/sidebar\/Sidebar.context.ts","content":"import { createContext, useContext } from \"react\";\r\nimport type { SidebarContextValue } from \".\/Sidebar.types\";\r\n\r\nexport const SidebarContext = createContext<SidebarContextValue | null>(null);\r\n\r\nexport function useSidebar(): SidebarContextValue {\r\n  const ctx = useContext(SidebarContext);\r\n  if (!ctx) {\r\n    throw new Error(\"useSidebar must be used within a <Sidebar> component\");\r\n  }\r\n  return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/Sidebar.context.ts"},{"path":"components\/fancy\/sidebar\/Sidebar.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useControllableState } from \"..\/..\/hooks\";\r\nimport { SidebarContext } from \".\/Sidebar.context\";\r\nimport { SidebarItem } from \".\/SidebarItem\";\r\nimport { SidebarGroup } from \".\/SidebarGroup\";\r\nimport { SidebarSubmenu } from \".\/SidebarSubmenu\";\r\nimport { SidebarToggle } from \".\/SidebarToggle\";\r\nimport type { SidebarProps } from \".\/Sidebar.types\";\r\n\r\nfunction SidebarRoot({\r\n  children,\r\n  collapsed: controlledCollapsed,\r\n  defaultCollapsed = false,\r\n  onCollapsedChange,\r\n  collapseMode = \"icons\",\r\n  className,\r\n}: SidebarProps) {\r\n  const [collapsed, setCollapsed] = useControllableState(\r\n    controlledCollapsed,\r\n    defaultCollapsed,\r\n    onCollapsedChange,\r\n  );\r\n\r\n  return (\r\n    <SidebarContext.Provider value={{ collapsed, collapseMode, setCollapsed }}>\r\n      <aside\r\n        data-react-fancy-sidebar=\"\"\r\n        data-collapsed={collapsed || undefined}\r\n        className={cn(\r\n          \"flex flex-col gap-0.5 border-r border-zinc-200 bg-white transition-[width] duration-200 dark:border-zinc-700 dark:bg-zinc-900\",\r\n          collapsed ? \"w-16\" : \"w-60\",\r\n          className,\r\n        )}\r\n      >\r\n        {children}\r\n      <\/aside>\r\n    <\/SidebarContext.Provider>\r\n  );\r\n}\r\n\r\nexport const Sidebar = Object.assign(SidebarRoot, {\r\n  Item: SidebarItem,\r\n  Group: SidebarGroup,\r\n  Submenu: SidebarSubmenu,\r\n  Toggle: SidebarToggle,\r\n});\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/Sidebar.tsx"},{"path":"components\/fancy\/sidebar\/Sidebar.types.ts","content":"import type { ReactNode } from \"react\";\r\n\r\nexport type SidebarCollapseMode = \"icons\" | \"letters\";\r\n\r\nexport interface SidebarProps {\r\n  children: ReactNode;\r\n  collapsed?: boolean;\r\n  defaultCollapsed?: boolean;\r\n  onCollapsedChange?: (collapsed: boolean) => void;\r\n  \/**\r\n   * How items display when collapsed:\r\n   * - `\"icons\"` \u2014 show only the icon (falls back to first 3 letters if no icon)\r\n   * - `\"letters\"` \u2014 show the first 3 letters of the label\r\n   * @default \"icons\"\r\n   *\/\r\n  collapseMode?: SidebarCollapseMode;\r\n  className?: string;\r\n}\r\n\r\nexport interface SidebarItemProps {\r\n  children: ReactNode;\r\n  href?: string;\r\n  icon?: ReactNode;\r\n  active?: boolean;\r\n  disabled?: boolean;\r\n  badge?: ReactNode;\r\n  onClick?: () => void;\r\n  className?: string;\r\n}\r\n\r\nexport interface SidebarGroupProps {\r\n  children: ReactNode;\r\n  label?: string;\r\n  className?: string;\r\n}\r\n\r\nexport interface SidebarToggleProps {\r\n  className?: string;\r\n}\r\n\r\nexport interface SidebarSubmenuProps {\r\n  children: ReactNode;\r\n  label: string;\r\n  icon?: ReactNode;\r\n  defaultOpen?: boolean;\r\n  className?: string;\r\n}\r\n\r\nexport interface SidebarContextValue {\r\n  collapsed: boolean;\r\n  collapseMode: SidebarCollapseMode;\r\n  setCollapsed: (collapsed: boolean) => void;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/Sidebar.types.ts"},{"path":"components\/fancy\/sidebar\/SidebarGroup.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useSidebar } from \".\/Sidebar.context\";\r\nimport type { SidebarGroupProps } from \".\/Sidebar.types\";\r\n\r\nexport function SidebarGroup({ children, label, className }: SidebarGroupProps) {\r\n  const { collapsed } = useSidebar();\r\n\r\n  return (\r\n    <div data-react-fancy-sidebar-group=\"\" className={cn(\"py-1\", className)}>\r\n      {label && !collapsed && (\r\n        <p className=\"mb-1 px-3 text-xs font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-500\">\r\n          {label}\r\n        <\/p>\r\n      )}\r\n      {label && collapsed && (\r\n        <div className=\"mx-auto mb-1 h-px w-6 bg-zinc-200 dark:bg-zinc-700\" \/>\r\n      )}\r\n      {children}\r\n    <\/div>\r\n  );\r\n}\r\n\r\nSidebarGroup.displayName = \"SidebarGroup\";\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/SidebarGroup.tsx"},{"path":"components\/fancy\/sidebar\/SidebarItem.tsx","content":"import { cn } from \"..\/..\/utils\/cn\";\r\nimport { useSidebar } from \".\/Sidebar.context\";\r\nimport type { SidebarItemProps } from \".\/Sidebar.types\";\r\n\r\n\/** Extract first 3 characters from a ReactNode if it's a string. *\/\r\nfunction getLetters(children: React.ReactNode): string {\r\n  if (typeof children === \"string\") return children.slice(0, 3);\r\n  return \"...\";\r\n}\r\n\r\nexport function SidebarItem({\r\n  children,\r\n  href,\r\n  icon,\r\n  active = false,\r\n  disabled = false,\r\n  badge,\r\n  onClick,\r\n  className,\r\n}: SidebarItemProps) {\r\n  const { collapsed, collapseMode } = useSidebar();\r\n\r\n  const Tag = href ? \"a\" : \"button\";\r\n  const tagProps = href ? { href } : { type: \"button\" as const, onClick };\r\n\r\n  const showIconOnly = collapsed && collapseMode === \"icons\" && icon;\r\n  const showLetters = collapsed && (collapseMode === \"letters\" || (collapseMode === \"icons\" && !icon));\r\n\r\n  const title = typeof children === \"string\" ? children : undefined;\r\n\r\n  return (\r\n    <Tag\r\n      {...tagProps}\r\n      data-react-fancy-sidebar-item=\"\"\r\n      data-active={active || undefined}\r\n      aria-current={active ? \"page\" : undefined}\r\n      aria-disabled={disabled || undefined}\r\n      title={collapsed ? title : undefined}\r\n      className={cn(\r\n        \"flex items-center rounded-md text-sm font-medium transition-colors\",\r\n        \"outline-none focus-visible:ring-2 focus-visible:ring-blue-500\",\r\n        collapsed ? \"justify-center px-2 py-2\" : \"gap-2 px-3 py-2\",\r\n        active\r\n          ? \"bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-zinc-100\"\r\n          : \"text-zinc-600 hover:bg-zinc-50 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800\/50 dark:hover:text-zinc-100\",\r\n        disabled && \"pointer-events-none opacity-50\",\r\n        className,\r\n      )}\r\n    >\r\n      {showIconOnly && (\r\n        <span className=\"flex h-5 w-5 shrink-0 items-center justify-center\">{icon}<\/span>\r\n      )}\r\n      {showLetters && (\r\n        <span className=\"text-xs font-semibold uppercase\">{getLetters(children)}<\/span>\r\n      )}\r\n      {!collapsed && (\r\n        <>\r\n          {icon && <span className=\"flex h-5 w-5 shrink-0 items-center justify-center\">{icon}<\/span>}\r\n          <span className=\"flex-1 truncate text-left\">{children}<\/span>\r\n          {badge && <span className=\"shrink-0\">{badge}<\/span>}\r\n        <\/>\r\n      )}\r\n    <\/Tag>\r\n  );\r\n}\r\n\r\nSidebarItem.displayName = \"SidebarItem\";\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/SidebarItem.tsx"},{"path":"components\/fancy\/sidebar\/SidebarSubmenu.tsx","content":"import { useState } from \"react\";\r\nimport { ChevronDown } from \"lucide-react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useSidebar } from \".\/Sidebar.context\";\r\nimport type { SidebarSubmenuProps } from \".\/Sidebar.types\";\r\n\r\n\/** Extract first 3 characters from a string. *\/\r\nfunction getLetters(label: string): string {\r\n  return label.slice(0, 3);\r\n}\r\n\r\nexport function SidebarSubmenu({\r\n  children,\r\n  label,\r\n  icon,\r\n  defaultOpen = false,\r\n  className,\r\n}: SidebarSubmenuProps) {\r\n  const [open, setOpen] = useState(defaultOpen);\r\n  const { collapsed, collapseMode } = useSidebar();\r\n\r\n  const showIconOnly = collapsed && collapseMode === \"icons\" && icon;\r\n  const showLetters = collapsed && (collapseMode === \"letters\" || (collapseMode === \"icons\" && !icon));\r\n\r\n  \/\/ When collapsed, just show the icon\/letters as a simple item (no expand)\r\n  if (collapsed) {\r\n    return (\r\n      <div\r\n        data-react-fancy-sidebar-submenu=\"\"\r\n        title={label}\r\n        className={cn(\r\n          \"flex items-center justify-center rounded-md px-2 py-2 text-sm font-medium transition-colors\",\r\n          \"text-zinc-600 hover:bg-zinc-50 hover:text-zinc-900\",\r\n          \"dark:text-zinc-400 dark:hover:bg-zinc-800\/50 dark:hover:text-zinc-100\",\r\n          className,\r\n        )}\r\n      >\r\n        {showIconOnly && (\r\n          <span className=\"flex h-5 w-5 shrink-0 items-center justify-center\">{icon}<\/span>\r\n        )}\r\n        {showLetters && (\r\n          <span className=\"text-xs font-semibold uppercase\">{getLetters(label)}<\/span>\r\n        )}\r\n      <\/div>\r\n    );\r\n  }\r\n\r\n  return (\r\n    <div data-react-fancy-sidebar-submenu=\"\" className={className}>\r\n      <button\r\n        type=\"button\"\r\n        onClick={() => setOpen((v) => !v)}\r\n        className={cn(\r\n          \"flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors\",\r\n          \"text-zinc-600 hover:bg-zinc-50 hover:text-zinc-900\",\r\n          \"dark:text-zinc-400 dark:hover:bg-zinc-800\/50 dark:hover:text-zinc-100\",\r\n        )}\r\n      >\r\n        {icon && <span className=\"flex h-5 w-5 shrink-0 items-center justify-center\">{icon}<\/span>}\r\n        <span className=\"flex-1 text-left\">{label}<\/span>\r\n        <ChevronDown\r\n          className={cn(\r\n            \"h-3.5 w-3.5 opacity-60 transition-transform duration-200\",\r\n            open && \"rotate-180\",\r\n          )}\r\n        \/>\r\n      <\/button>\r\n      <div\r\n        className={cn(\r\n          \"grid transition-[grid-template-rows] duration-200\",\r\n          open ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\",\r\n        )}\r\n      >\r\n        <div className=\"overflow-hidden\">\r\n          <div className=\"ml-4 border-l border-zinc-200 pl-2 pt-1 dark:border-zinc-700\">\r\n            {children}\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n\r\nSidebarSubmenu.displayName = \"SidebarSubmenu\";\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/SidebarSubmenu.tsx"},{"path":"components\/fancy\/sidebar\/SidebarToggle.tsx","content":"import { PanelLeftClose, PanelLeftOpen } from \"lucide-react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useSidebar } from \".\/Sidebar.context\";\r\nimport type { SidebarToggleProps } from \".\/Sidebar.types\";\r\n\r\nexport function SidebarToggle({ className }: SidebarToggleProps) {\r\n  const { collapsed, setCollapsed } = useSidebar();\r\n\r\n  return (\r\n    <button\r\n      type=\"button\"\r\n      data-react-fancy-sidebar-toggle=\"\"\r\n      onClick={() => setCollapsed(!collapsed)}\r\n      title={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\r\n      className={cn(\r\n        \"flex items-center justify-center rounded-md p-1.5 text-zinc-400 transition-colors\",\r\n        \"hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300\",\r\n        className,\r\n      )}\r\n    >\r\n      {collapsed ? (\r\n        <PanelLeftOpen className=\"h-4 w-4\" \/>\r\n      ) : (\r\n        <PanelLeftClose className=\"h-4 w-4\" \/>\r\n      )}\r\n    <\/button>\r\n  );\r\n}\r\n\r\nSidebarToggle.displayName = \"SidebarToggle\";\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/SidebarToggle.tsx"},{"path":"components\/fancy\/sidebar\/index.ts","content":"export { Sidebar } from \".\/Sidebar\";\r\nexport { useSidebar } from \".\/Sidebar.context\";\r\nexport type {\r\n  SidebarProps,\r\n  SidebarItemProps,\r\n  SidebarGroupProps,\r\n  SidebarSubmenuProps,\r\n  SidebarToggleProps,\r\n  SidebarCollapseMode,\r\n  SidebarContextValue,\r\n} from \".\/Sidebar.types\";\r\n","type":"registry:ui","target":"components\/fancy\/sidebar\/index.ts"}]}