{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "nested-drawer",
  "title": "Nested Menu Drawer",
  "description": "A fully accessible, animated drawer component with nested navigation support and smooth transitions.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "src/registry/blocks/nested-drawer/nested-drawer-example.tsx",
      "content": "\"use client\";\n\nimport {\n  BookOpen,\n  Briefcase,\n  Building2,\n  HelpCircle,\n  Home as HomeIcon,\n  Mail,\n  Package,\n  Server,\n  Shield,\n  Users,\n} from \"lucide-react\";\n\nimport type { MenuItem } from \"@/registry/blocks/nested-drawer/nested-drawer\";\nimport { NestedDrawer } from \"@/registry/blocks/nested-drawer/nested-drawer\";\n\nconst MENU_DATA: MenuItem[] = [\n  {\n    id: \"home\",\n    title: \"Home\",\n    description: \"Welcome to our platform\",\n    icon: HomeIcon,\n    href: \"#\",\n  },\n  {\n    id: \"products\",\n    title: \"Products & Services\",\n    description: \"Explore our offerings\",\n    icon: Package,\n    children: [\n      {\n        id: \"products-software\",\n        title: \"Software Solutions\",\n        description: \"Enterprise and custom software\",\n        icon: Server,\n        href: \"#\",\n      },\n      {\n        id: \"products-cloud\",\n        title: \"Cloud Services\",\n        description: \"Scalable cloud infrastructure\",\n        icon: Shield,\n        href: \"#\",\n      },\n    ],\n  },\n  {\n    id: \"company\",\n    title: \"Company\",\n    description: \"Learn about our organization\",\n    icon: Users,\n    children: [\n      {\n        id: \"company-about\",\n        title: \"About Us\",\n        description: \"Our story and mission\",\n        icon: Building2,\n        href: \"#\",\n      },\n      {\n        id: \"company-careers\",\n        title: \"Careers\",\n        description: \"Join our team\",\n        icon: Briefcase,\n        href: \"#\",\n      },\n    ],\n  },\n  {\n    id: \"resources\",\n    title: \"Resources\",\n    description: \"Knowledge base and materials\",\n    icon: BookOpen,\n    href: \"#\",\n  },\n  {\n    id: \"support\",\n    title: \"Support\",\n    description: \"Get help when you need it\",\n    icon: HelpCircle,\n    href: \"#\",\n  },\n  {\n    id: \"contact\",\n    title: \"Contact\",\n    description: \"Get in touch with our team\",\n    icon: Mail,\n    href: \"#\",\n  },\n];\n\nexport function NestedDrawerExample() {\n  return (\n    <NestedDrawer initialMenu={MENU_DATA}>\n      <NestedDrawer.Trigger>\n        <span>Open Menu</span>\n      </NestedDrawer.Trigger>\n      <NestedDrawer.Content title=\"Main Menu\">\n        <NestedDrawer.Menu />\n      </NestedDrawer.Content>\n    </NestedDrawer>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/registry/blocks/nested-drawer/nested-drawer.tsx",
      "content": "\"use client\";\n\nimport { motion, AnimatePresence } from \"motion/react\";\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport { ChevronRight, ChevronLeft, type LucideIcon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nconst ANIMATION_DURATION = 0.4;\nconst EASING = [0.32, 0.72, 0, 1] as const;\n\nexport type MenuItem = {\n  id: string;\n  title: string;\n  description?: string;\n  icon?: LucideIcon;\n  children?: MenuItem[];\n  href?: string;\n};\n\ntype NestedDrawerContext = {\n  menuStack: MenuItem[][];\n  currentMenu: MenuItem[];\n  direction: \"forward\" | \"backward\";\n  navigateToMenu: (items: MenuItem[]) => void;\n  navigateBack: () => void;\n  canGoBack: boolean;\n  open: boolean;\n  setOpen: (open: boolean) => void;\n};\n\nconst NestedDrawerContext = React.createContext<\n  NestedDrawerContext | undefined\n>(undefined);\n\nconst useNestedDrawer = () => {\n  const context = React.useContext(NestedDrawerContext);\n  if (!context) {\n    throw new Error(\n      \"NestedDrawer compound components must be used within NestedDrawer\"\n    );\n  }\n  return context;\n};\n\ntype TNestedDrawerProps = {\n  children: React.ReactNode;\n  initialMenu: MenuItem[];\n};\n\nexport function NestedDrawer({ children, initialMenu }: TNestedDrawerProps) {\n  const [open, setOpen] = React.useState(false);\n  const [menuStack, setMenuStack] = React.useState<MenuItem[][]>([initialMenu]);\n  const [direction, setDirection] = React.useState<\"forward\" | \"backward\">(\n    \"forward\"\n  );\n\n  const currentMenu = menuStack[menuStack.length - 1];\n  const canGoBack = menuStack.length > 1;\n\n  const navigateToMenu = React.useCallback((items: MenuItem[]) => {\n    setDirection(\"forward\");\n    setMenuStack((prev) => [...prev, items]);\n  }, []);\n\n  const navigateBack = React.useCallback(() => {\n    if (menuStack.length > 1) {\n      setDirection(\"backward\");\n      setMenuStack((prev) => prev.slice(0, -1));\n    }\n  }, [menuStack.length]);\n\n  React.useEffect(() => {\n    if (!open) {\n      const timeout = setTimeout(() => {\n        setMenuStack([initialMenu]);\n        setDirection(\"forward\");\n      }, ANIMATION_DURATION * 1000);\n      return () => clearTimeout(timeout);\n    }\n  }, [open, initialMenu]);\n\n  return (\n    <NestedDrawerContext.Provider\n      value={{\n        menuStack,\n        currentMenu,\n        direction,\n        navigateToMenu,\n        navigateBack,\n        canGoBack,\n        open,\n        setOpen,\n      }}\n    >\n      {children}\n    </NestedDrawerContext.Provider>\n  );\n}\n\ntype TriggerProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {\n  children: React.ReactNode;\n};\n\nfunction Trigger({ children, className, ...props }: TriggerProps) {\n  const { setOpen } = useNestedDrawer();\n\n  return (\n    <button\n      type=\"button\"\n      className={cn(\n        \"relative flex h-10 shrink-0 items-center justify-center gap-2 overflow-hidden rounded-full bg-card px-4 text-sm font-medium shadow-sm transition-all hover:bg-accent border cursor-pointer\",\n        className\n      )}\n      onClick={() => setOpen(true)}\n      aria-label=\"Open menu drawer\"\n      {...props}\n    >\n      {children}\n    </button>\n  );\n}\n\ntype ContentProps = {\n  title?: string;\n  children?: React.ReactNode;\n};\n\nfunction Content({ title, children }: ContentProps) {\n  const { open, setOpen, canGoBack, navigateBack, currentMenu } =\n    useNestedDrawer();\n  const contentRef = React.useRef<HTMLDivElement>(null);\n  const innerRef = React.useRef<HTMLDivElement>(null);\n  const [height, setHeight] = React.useState<number | \"auto\">(\"auto\");\n\n  React.useEffect(() => {\n    if (open && contentRef.current) {\n      const firstFocusable = contentRef.current.querySelector<HTMLElement>(\n        'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n      );\n      firstFocusable?.focus();\n    }\n  }, [open]);\n\n  React.useEffect(() => {\n    const handleEscape = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\" && open) {\n        setOpen(false);\n      }\n    };\n\n    document.addEventListener(\"keydown\", handleEscape);\n    return () => document.removeEventListener(\"keydown\", handleEscape);\n  }, [open, setOpen]);\n\n  React.useEffect(() => {\n    if (!innerRef.current || !open) {\n      setHeight(\"auto\");\n      return;\n    }\n\n    const measureHeight = () => {\n      if (innerRef.current) {\n        const newHeight = innerRef.current.offsetHeight;\n        setHeight(newHeight);\n      }\n    };\n\n    measureHeight();\n\n    const resizeObserver = new ResizeObserver(measureHeight);\n    resizeObserver.observe(innerRef.current);\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [currentMenu, canGoBack, open]);\n\n  return (\n    <>\n      <AnimatePresence>\n        {open && (\n          <motion.div\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: ANIMATION_DURATION, ease: EASING }}\n            className=\"fixed -inset-8 bg-black/40 z-50 backdrop-blur-sm cursor-pointer\"\n            onClick={() => setOpen(false)}\n            aria-hidden=\"true\"\n          />\n        )}\n      </AnimatePresence>\n\n      <AnimatePresence>\n        {open && (\n          <motion.div\n            ref={contentRef}\n            initial={{ y: \"calc(100% + 1rem)\" }}\n            animate={{ y: 0 }}\n            exit={{ y: \"calc(100% + 1rem)\" }}\n            transition={{ duration: ANIMATION_DURATION, ease: EASING }}\n            className=\"fixed inset-x-0 bottom-4 z-50 mx-auto max-w-lg px-4\"\n            role=\"dialog\"\n            aria-modal=\"true\"\n            aria-label={title || \"Menu drawer\"}\n          >\n            <motion.div\n              animate={{ height }}\n              transition={{\n                duration: ANIMATION_DURATION,\n                ease: EASING,\n              }}\n              className=\"bg-card border rounded-2xl shadow-xl max-h-[85vh] overflow-hidden\"\n            >\n              <div ref={innerRef}>\n                {canGoBack && (\n                  <motion.div\n                    initial={{ opacity: 0 }}\n                    animate={{ opacity: 1 }}\n                    exit={{ opacity: 0 }}\n                    transition={{\n                      opacity: {\n                        duration: ANIMATION_DURATION * 0.6,\n                        ease: EASING,\n                      },\n                    }}\n                    className=\"px-4 pt-4 pb-2 shrink-0\"\n                  >\n                    <button\n                      type=\"button\"\n                      onClick={navigateBack}\n                      className=\"flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors cursor-pointer\"\n                      aria-label=\"Navigate back\"\n                    >\n                      <ChevronLeft className=\"w-4 h-4\" />\n                      <span>Back</span>\n                    </button>\n                  </motion.div>\n                )}\n\n                <div className=\"px-1 py-4 max-h-[calc(85vh-4rem)] overflow-y-auto overflow-x-hidden\">\n                  <div className=\"px-3\">{children || <Menu />}</div>\n                </div>\n              </div>\n            </motion.div>\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </>\n  );\n}\n\nfunction Menu() {\n  const { currentMenu, direction } = useNestedDrawer();\n\n  const variants = {\n    enter: (dir: \"forward\" | \"backward\") => ({\n      x: dir === \"forward\" ? \"100%\" : \"-100%\",\n      opacity: 0,\n    }),\n    center: {\n      x: 0,\n      opacity: 1,\n    },\n    exit: (dir: \"forward\" | \"backward\") => ({\n      x: dir === \"forward\" ? \"-100%\" : \"100%\",\n      opacity: 0,\n    }),\n  };\n\n  return (\n    <div className=\"relative overflow-hidden\">\n      <AnimatePresence initial={false} mode=\"popLayout\" custom={direction}>\n        <motion.div\n          key={currentMenu[0]?.id || \"root\"}\n          custom={direction}\n          variants={variants}\n          initial=\"enter\"\n          animate=\"center\"\n          exit=\"exit\"\n          transition={{\n            duration: ANIMATION_DURATION * 0.5,\n            ease: EASING,\n          }}\n          // style={{ position: \"relative\" }}\n          className=\"space-y-1 relative\"\n          role=\"menu\"\n          aria-orientation=\"vertical\"\n        >\n          {currentMenu.map((item) => (\n            <MenuItem key={item.id} item={item} />\n          ))}\n        </motion.div>\n      </AnimatePresence>\n    </div>\n  );\n}\n\ntype MenuItemProps = {\n  item: MenuItem;\n};\n\nfunction MenuItem({ item }: MenuItemProps) {\n  const { navigateToMenu, setOpen } = useNestedDrawer();\n  const hasChildren = item.children && item.children.length > 0;\n\n  const handleClick = () => {\n    if (hasChildren) {\n      navigateToMenu(item.children!);\n    } else if (item.href) {\n      // Close drawer when navigating to a link\n      setOpen(false);\n    }\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault();\n      handleClick();\n    }\n  };\n\n  const Icon = item.icon;\n\n  const content = (\n    <>\n      {Icon && (\n        <div className=\"shrink-0 mt-1 text-muted-foreground\">\n          <Icon className=\"size-5\" />\n        </div>\n      )}\n\n      <div className=\"flex-1 min-w-0\">\n        <div className=\"font-medium text-foreground\">{item.title}</div>\n        {item.description && (\n          <div className=\"text-sm text-muted-foreground mt-0.5\">\n            {item.description}\n          </div>\n        )}\n      </div>\n\n      {hasChildren && (\n        <div className=\"shrink-0 text-muted-foreground\">\n          <ChevronRight className=\"w-5 h-5\" />\n        </div>\n      )}\n    </>\n  );\n\n  const className = cn(\n    \"w-full flex items-start gap-3 px-4 py-3 rounded-lg text-left transition-colors cursor-pointer\",\n    \"hover:bg-accent\",\n    \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0\",\n    \"active:bg-accent/80\"\n  );\n\n  // Render as Link if href is provided and no children\n  if (item.href && !hasChildren) {\n    return (\n      <motion.div whileTap={{ scale: 0.98 }} transition={{ duration: 0.1 }}>\n        <Link\n          href={item.href}\n          onClick={handleClick}\n          className={className}\n          role=\"menuitem\"\n          tabIndex={0}\n        >\n          {content}\n        </Link>\n      </motion.div>\n    );\n  }\n\n  // Render as button for items with children\n  return (\n    <motion.button\n      type=\"button\"\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      className={className}\n      role=\"menuitem\"\n      aria-haspopup={hasChildren}\n      tabIndex={0}\n      whileTap={{ scale: 0.98 }}\n      transition={{ duration: 0.1 }}\n    >\n      {content}\n    </motion.button>\n  );\n}\n\nNestedDrawer.Trigger = Trigger;\nNestedDrawer.Content = Content;\nNestedDrawer.Menu = Menu;\nNestedDrawer.MenuItem = MenuItem;\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}