{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "responsive-menu",
  "title": "Responsive Menu",
  "description": "Renders a Dropdown Menu on desktop and a bottom Drawer menu on mobile from one shared set of components.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "@persianlabsui/dropdown-menu",
    "@persianlabsui/drawer"
  ],
  "files": [
    {
      "path": "registry/base/ui/responsive-menu.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport {\n  Drawer,\n  DrawerClose,\n  DrawerMenu,\n  DrawerMenuGroup,\n  DrawerMenuGroupLabel,\n  DrawerMenuItem,\n  DrawerMenuSeparator,\n  DrawerPanel,\n  DrawerPopup,\n  DrawerTrigger,\n} from \"@/components/ui/drawer\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuGroupLabel,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { useIsMobile } from \"@/hooks/use-media-query\"\n\ntype ResponsiveMenuContextValue = { isDesktop: boolean }\n\nconst ResponsiveMenuContext =\n  React.createContext<ResponsiveMenuContextValue | null>(null)\n\nfunction useResponsiveMenuContext() {\n  const context = React.useContext(ResponsiveMenuContext)\n  if (!context) {\n    throw new Error(\n      \"ResponsiveMenu components must be used within <ResponsiveMenu>\"\n    )\n  }\n  return context\n}\n\ntype ResponsiveMenuProps = {\n  children: React.ReactNode\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (open: boolean) => void\n}\n\nfunction ResponsiveMenu({\n  children,\n  open,\n  defaultOpen,\n  onOpenChange,\n}: ResponsiveMenuProps) {\n  const isDesktop = !useIsMobile()\n\n  return (\n    <ResponsiveMenuContext.Provider value={{ isDesktop }}>\n      {isDesktop ? (\n        <DropdownMenu\n          open={open}\n          defaultOpen={defaultOpen}\n          onOpenChange={onOpenChange}\n        >\n          {children}\n        </DropdownMenu>\n      ) : (\n        <Drawer\n          open={open}\n          defaultOpen={defaultOpen}\n          onOpenChange={onOpenChange}\n        >\n          {children}\n        </Drawer>\n      )}\n    </ResponsiveMenuContext.Provider>\n  )\n}\n\nfunction ResponsiveMenuTrigger(\n  props: React.ComponentProps<typeof DropdownMenuTrigger>\n) {\n  const { isDesktop } = useResponsiveMenuContext()\n  return isDesktop ? (\n    <DropdownMenuTrigger {...props} />\n  ) : (\n    <DrawerTrigger {...(props as React.ComponentProps<typeof DrawerTrigger>)} />\n  )\n}\n\ntype ResponsiveMenuContentProps = {\n  children: React.ReactNode\n  groupLabel?: string\n  align?: React.ComponentProps<typeof DropdownMenuContent>[\"align\"]\n  side?: React.ComponentProps<typeof DropdownMenuContent>[\"side\"]\n  sideOffset?: number\n  className?: string\n}\n\nfunction ResponsiveMenuContent({\n  children,\n  groupLabel,\n  align = \"end\",\n  side = \"bottom\",\n  sideOffset = 6,\n  className,\n}: ResponsiveMenuContentProps) {\n  const { isDesktop } = useResponsiveMenuContext()\n\n  if (isDesktop) {\n    return (\n      <DropdownMenuContent\n        align={align}\n        side={side}\n        sideOffset={sideOffset}\n        className={className}\n      >\n        <DropdownMenuGroup>\n          {groupLabel && (\n            <DropdownMenuGroupLabel>{groupLabel}</DropdownMenuGroupLabel>\n          )}\n          {children}\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    )\n  }\n\n  return (\n    <DrawerPopup showBar>\n      <DrawerPanel>\n        <DrawerMenu>\n          <DrawerMenuGroup>\n            {groupLabel && (\n              <DrawerMenuGroupLabel>{groupLabel}</DrawerMenuGroupLabel>\n            )}\n            {children}\n          </DrawerMenuGroup>\n        </DrawerMenu>\n      </DrawerPanel>\n    </DrawerPopup>\n  )\n}\n\ntype ResponsiveMenuItemProps = {\n  children: React.ReactNode\n  onClick?: () => void\n  variant?: \"default\" | \"destructive\"\n  disabled?: boolean\n}\n\nfunction ResponsiveMenuItem({\n  children,\n  onClick,\n  variant = \"default\",\n  disabled,\n}: ResponsiveMenuItemProps) {\n  const { isDesktop } = useResponsiveMenuContext()\n\n  if (isDesktop) {\n    return (\n      <DropdownMenuItem variant={variant} disabled={disabled} onClick={onClick}>\n        {children}\n      </DropdownMenuItem>\n    )\n  }\n\n  return (\n    <DrawerClose\n      render={<DrawerMenuItem variant={variant} disabled={disabled} />}\n      onClick={onClick}\n    >\n      {children}\n    </DrawerClose>\n  )\n}\n\nfunction ResponsiveMenuSeparator() {\n  const { isDesktop } = useResponsiveMenuContext()\n  return isDesktop ? <DropdownMenuSeparator /> : <DrawerMenuSeparator />\n}\n\nexport {\n  ResponsiveMenu,\n  ResponsiveMenuContent,\n  ResponsiveMenuItem,\n  ResponsiveMenuSeparator,\n  ResponsiveMenuTrigger,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/base/hooks/use-media-query.ts",
      "content": "\"use client\"\n\nimport { useCallback, useSyncExternalStore } from \"react\"\n\nconst BREAKPOINTS = {\n  \"2xl\": 1536,\n  \"3xl\": 1600,\n  \"4xl\": 2000,\n  lg: 1024,\n  md: 800,\n  sm: 640,\n  xl: 1280,\n} as const\n\ntype Breakpoint = keyof typeof BREAKPOINTS\ntype BreakpointQuery =\n  Breakpoint | `max-${Breakpoint}` | `${Breakpoint}:max-${Breakpoint}`\n\nfunction resolveMin(value: Breakpoint | number): string {\n  const px = typeof value === \"number\" ? value : BREAKPOINTS[value]\n  return `(min-width: ${px}px)`\n}\n\nfunction resolveMax(value: Breakpoint | number): string {\n  const px = typeof value === \"number\" ? value : BREAKPOINTS[value]\n  return `(max-width: ${px - 1}px)`\n}\n\n/**\n * @internal exported for unit tests; not part of the public API.\n */\nexport function parseQuery(\n  query: BreakpointQuery | MediaQueryInput | (string & {})\n): string {\n  if (typeof query !== \"string\") {\n    const parts: string[] = []\n    if (query.min != null) parts.push(resolveMin(query.min))\n    if (query.max != null) parts.push(resolveMax(query.max))\n    if (query.pointer === \"coarse\") parts.push(\"(pointer: coarse)\")\n    if (query.pointer === \"fine\") parts.push(\"(pointer: fine)\")\n    if (parts.length === 0) return \"(min-width: 0px)\"\n    return parts.join(\" and \")\n  }\n\n  if (query.startsWith(\"(\")) return query\n\n  const parts: string[] = []\n  for (const segment of query.split(\":\")) {\n    if (segment.startsWith(\"max-\")) {\n      const bp = segment.slice(4)\n      if (bp in BREAKPOINTS) parts.push(resolveMax(bp as Breakpoint))\n    } else if (segment in BREAKPOINTS) {\n      parts.push(resolveMin(segment as Breakpoint))\n    }\n  }\n\n  return parts.length > 0 ? parts.join(\" and \") : query\n}\n\nfunction getServerSnapshot(): boolean {\n  return false\n}\n\nexport type MediaQueryInput = {\n  min?: Breakpoint | number\n  max?: Breakpoint | number\n  /** Touch-like input (finger). Use \"fine\" for mouse/trackpad. */\n  pointer?: \"coarse\" | \"fine\"\n}\n\n/** Tracks a CSS media query with Tailwind-like breakpoint syntax. Returns `false` during SSR. */\nexport function useMediaQuery(\n  query: BreakpointQuery | MediaQueryInput | (string & {})\n): boolean {\n  const mediaQuery = parseQuery(query)\n\n  const subscribe = useCallback(\n    (callback: () => void) => {\n      if (typeof window === \"undefined\") return () => {}\n      const mql = window.matchMedia(mediaQuery)\n      mql.addEventListener(\"change\", callback)\n      return () => mql.removeEventListener(\"change\", callback)\n    },\n    [mediaQuery]\n  )\n\n  const getSnapshot = useCallback(() => {\n    if (typeof window === \"undefined\") return false\n    return window.matchMedia(mediaQuery).matches\n  }, [mediaQuery])\n\n  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n\nexport function useIsMobile(): boolean {\n  return useMediaQuery(\"max-md\")\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:ui"
}