{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gradient-sidebar",
  "title": "Gradient Sidebar",
  "description": "A sidebar with animated gradient orb background and collapsible sections.",
  "dependencies": [
    "motion",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "registry/wise-ui/components/gradient-sidebar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, AnimatePresence } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\nconst EASE_OUT_QUART: [number, number, number, number] = [0.25, 1, 0.5, 1]\n\n// ── Types ────────────────────────────────────────────────────────────\n\ninterface GradientSidebarItem {\n  id: string\n  label: string\n  icon?: React.ReactNode\n  href?: string\n  children?: GradientSidebarItem[]\n  badge?: string | number\n}\n\ninterface GradientSidebarSection {\n  title?: string\n  items: GradientSidebarItem[]\n}\n\ninterface GradientSidebarProps {\n  sections: GradientSidebarSection[]\n  activeId?: string\n  onItemClick?: (item: GradientSidebarItem) => void\n  header?: React.ReactNode\n  footer?: React.ReactNode\n  defaultCollapsed?: boolean\n  collapsed?: boolean\n  onCollapsedChange?: (collapsed: boolean) => void\n  width?: number\n  collapsedWidth?: number\n  colorA?: string\n  colorB?: string\n  colorC?: string\n  overlayOpacity?: number\n  className?: string\n}\n\n// ── Gradient Background ──────────────────────────────────────────────\n\nfunction GradientBackground({\n  colorA = \"rgba(0,130,116,0.85)\",\n  colorB = \"rgba(108,80,190,0.8)\",\n  colorC = \"rgba(0,155,140,0.65)\",\n  overlayOpacity = 0.55,\n}: {\n  colorA?: string\n  colorB?: string\n  colorC?: string\n  overlayOpacity?: number\n}) {\n  return (\n    <>\n      <div className=\"pointer-events-none absolute inset-0 overflow-hidden\">\n        <div\n          className=\"absolute rounded-full\"\n          style={{\n            width: \"130%\",\n            height: \"110%\",\n            top: \"-40%\",\n            right: \"-50%\",\n            background: `radial-gradient(circle, ${colorA} 0%, transparent 65%)`,\n            filter: \"blur(40px)\",\n            animation: \"gradient-sidebar-blob-1 23s ease-in-out infinite\",\n          }}\n        />\n        <div\n          className=\"absolute rounded-full\"\n          style={{\n            width: \"120%\",\n            height: \"100%\",\n            bottom: \"-35%\",\n            left: \"-40%\",\n            background: `radial-gradient(circle, ${colorB} 0%, transparent 65%)`,\n            filter: \"blur(45px)\",\n            animation: \"gradient-sidebar-blob-2 29s ease-in-out infinite\",\n          }}\n        />\n        <div\n          className=\"absolute rounded-full\"\n          style={{\n            width: \"80%\",\n            height: \"70%\",\n            top: \"20%\",\n            left: \"-10%\",\n            background: `radial-gradient(circle, ${colorC} 0%, transparent 60%)`,\n            filter: \"blur(35px)\",\n            animation: \"gradient-sidebar-blob-3 19s ease-in-out infinite\",\n          }}\n        />\n      </div>\n      <div\n        className=\"pointer-events-none absolute inset-0 backdrop-blur-sm\"\n        style={{\n          background: `rgba(var(--background-rgb, 30,31,34), ${overlayOpacity})`,\n        }}\n      />\n      <style>{`\n        @keyframes gradient-sidebar-blob-1 {\n          0%   { transform: translate(0, 0) scale(1); }\n          20%  { transform: translate(-15%, 25%) scale(1.05); }\n          45%  { transform: translate(-25%, 55%) scale(0.95); }\n          70%  { transform: translate(-5%, 35%) scale(1.08); }\n          100% { transform: translate(0, 0) scale(1); }\n        }\n        @keyframes gradient-sidebar-blob-2 {\n          0%   { transform: translate(0, 0) scale(1); }\n          30%  { transform: translate(25%, -40%) scale(1.12); }\n          55%  { transform: translate(40%, -20%) scale(0.9); }\n          80%  { transform: translate(15%, -55%) scale(1.05); }\n          100% { transform: translate(0, 0) scale(1); }\n        }\n        @keyframes gradient-sidebar-blob-3 {\n          0%   { transform: translate(0, 0) scale(1); }\n          25%  { transform: translate(-20%, -15%) scale(1.15); }\n          50%  { transform: translate(10%, 20%) scale(0.88); }\n          75%  { transform: translate(-10%, 5%) scale(1.1); }\n          100% { transform: translate(0, 0) scale(1); }\n        }\n      `}</style>\n    </>\n  )\n}\n\n// ── Sidebar Item ─────────────────────────────────────────────────────\n\nfunction SidebarMenuItem({\n  item,\n  activeId,\n  onItemClick,\n  depth,\n  isCollapsed,\n}: {\n  item: GradientSidebarItem\n  activeId?: string\n  onItemClick?: (item: GradientSidebarItem) => void\n  depth: number\n  isCollapsed: boolean\n}) {\n  const [expanded, setExpanded] = React.useState(false)\n  const hasChildren = item.children && item.children.length > 0\n  const isActive = activeId === item.id\n\n  React.useEffect(() => {\n    if (hasChildren && activeId) {\n      const hasActiveChild = (items: GradientSidebarItem[]): boolean =>\n        items.some(\n          (child) =>\n            child.id === activeId ||\n            (child.children && hasActiveChild(child.children))\n        )\n      if (hasActiveChild(item.children!)) {\n        setExpanded(true)\n      }\n    }\n  }, [activeId, hasChildren, item.children])\n\n  function handleClick() {\n    if (hasChildren) {\n      setExpanded((e) => !e)\n    }\n    onItemClick?.(item)\n  }\n\n  return (\n    <div>\n      <button\n        onClick={handleClick}\n        className={cn(\n          \"group relative flex w-full items-center rounded-md py-2 text-sm transition-colors\",\n          isActive\n            ? \"text-foreground\"\n            : \"text-muted-foreground hover:text-foreground\",\n          isCollapsed ? \"justify-center px-1\" : \"gap-2 px-2.5 pr-3\",\n          depth > 0 && !isCollapsed && \"pl-7\"\n        )}\n      >\n        {isActive && !isCollapsed && (\n          <motion.div\n            layoutId=\"sidebar-active\"\n            className=\"absolute inset-0 rounded-md bg-foreground/10\"\n            transition={{ duration: 0.3, ease: EASE_OUT_QUART }}\n          />\n        )}\n\n        {item.icon && (\n          <span className=\"relative z-10 shrink-0\">{item.icon}</span>\n        )}\n\n        {!isCollapsed && (\n          <span className=\"relative z-10 flex-1 truncate text-left font-medium\">\n            {item.label}\n          </span>\n        )}\n\n        {!isCollapsed && item.badge != null && (\n          <span className=\"relative z-10 shrink-0 inline-flex items-center justify-center rounded-full bg-foreground/10 px-1.5 py-0.5 text-[10px] font-semibold text-muted-foreground\">\n            {item.badge}\n          </span>\n        )}\n\n        {!isCollapsed && hasChildren && (\n          <motion.svg\n            width=\"12\"\n            height=\"12\"\n            viewBox=\"0 0 12 12\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            className=\"relative z-10 shrink-0 text-muted-foreground/60\"\n            initial={false}\n            animate={{ rotate: expanded ? 0 : -90 }}\n            transition={{ duration: 0.2, ease: EASE_OUT_QUART }}\n          >\n            <polyline points=\"2 4 6 8 10 4\" />\n          </motion.svg>\n        )}\n      </button>\n\n      {hasChildren && !isCollapsed && (\n        <AnimatePresence initial={false}>\n          {expanded && (\n            <motion.div\n              initial={{ height: 0, opacity: 0 }}\n              animate={{ height: \"auto\", opacity: 1 }}\n              exit={{ height: 0, opacity: 0 }}\n              transition={{ duration: 0.25, ease: EASE_OUT_QUART }}\n              className=\"overflow-hidden\"\n            >\n              {item.children!.map((child) => (\n                <SidebarMenuItem\n                  key={child.id}\n                  item={child}\n                  activeId={activeId}\n                  onItemClick={onItemClick}\n                  depth={depth + 1}\n                  isCollapsed={isCollapsed}\n                />\n              ))}\n            </motion.div>\n          )}\n        </AnimatePresence>\n      )}\n    </div>\n  )\n}\n\n// ── Main GradientSidebar ─────────────────────────────────────────────\n\nfunction GradientSidebar({\n  sections,\n  activeId,\n  onItemClick,\n  header,\n  footer,\n  defaultCollapsed = false,\n  collapsed: controlledCollapsed,\n  onCollapsedChange,\n  width = 256,\n  collapsedWidth = 64,\n  colorA,\n  colorB,\n  colorC,\n  overlayOpacity,\n  className,\n}: GradientSidebarProps) {\n  const [internalCollapsed, setInternalCollapsed] =\n    React.useState(defaultCollapsed)\n\n  const isControlled = controlledCollapsed !== undefined\n  const isCollapsed = isControlled ? controlledCollapsed : internalCollapsed\n\n  function toggleCollapse() {\n    const next = !isCollapsed\n    if (!isControlled) setInternalCollapsed(next)\n    onCollapsedChange?.(next)\n  }\n\n  const currentWidth = isCollapsed ? collapsedWidth : width\n\n  return (\n    <motion.div\n      className={cn(\n        \"relative flex h-full shrink-0 flex-col overflow-hidden\",\n        className\n      )}\n      initial={false}\n      animate={{ width: currentWidth }}\n      transition={{ duration: 0.3, ease: EASE_OUT_QUART }}\n    >\n      {/* Animated gradient background */}\n      <GradientBackground\n        colorA={colorA}\n        colorB={colorB}\n        colorC={colorC}\n        overlayOpacity={overlayOpacity}\n      />\n\n      {/* Right border */}\n      <div className=\"absolute right-0 top-0 h-full w-px bg-border\" />\n\n      {/* Content */}\n      <div className=\"relative z-10 flex h-full flex-col overflow-y-auto overflow-x-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">\n        {/* Header - wrapper always renders with fixed height to keep icons stable */}\n        <div className=\"shrink-0 px-3 pt-4 pb-2 min-h-[48px] overflow-hidden\">\n          {!isCollapsed && header}\n        </div>\n\n        <div className={cn(\"flex-1 py-2\", isCollapsed ? \"px-1\" : \"px-2\")}>\n          {sections.map((section, sIdx) => (\n            <div\n              key={section.title ?? sIdx}\n              className={sIdx > 0 ? \"mt-5\" : \"\"}\n            >\n              {section.title && !isCollapsed && (\n                <p className=\"mb-1.5 px-2.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/50\">\n                  {section.title}\n                </p>\n              )}\n              {/* Keep section title spacing even when collapsed */}\n              {section.title && isCollapsed && (\n                <div className=\"mb-1.5 h-3\" />\n              )}\n              <div className=\"space-y-0.5\">\n                {section.items.map((item) => (\n                  <SidebarMenuItem\n                    key={item.id}\n                    item={item}\n                    activeId={activeId}\n                    onItemClick={onItemClick}\n                    depth={0}\n                    isCollapsed={isCollapsed}\n                  />\n                ))}\n              </div>\n            </div>\n          ))}\n        </div>\n\n        {footer && (\n          <div className=\"shrink-0 border-t border-border/40 px-3 py-3\">\n            {footer}\n          </div>\n        )}\n      </div>\n\n      {/* Collapse toggle */}\n      <button\n        onClick={toggleCollapse}\n        className={cn(\n          \"absolute top-3 z-20 flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground\",\n          isCollapsed ? \"left-1/2 -translate-x-1/2\" : \"right-2\"\n        )}\n        aria-label={isCollapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n      >\n        <motion.svg\n          width=\"14\"\n          height=\"14\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          initial={false}\n          animate={{ rotate: isCollapsed ? 180 : 0 }}\n          transition={{ duration: 0.3, ease: EASE_OUT_QUART }}\n        >\n          <polyline points=\"15 18 9 12 15 6\" />\n        </motion.svg>\n      </button>\n    </motion.div>\n  )\n}\n\nexport { GradientSidebar }\nexport type { GradientSidebarProps, GradientSidebarSection, GradientSidebarItem }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}