{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "responsive-dialog",
  "title": "Responsive Dialog",
  "description": "Renders a Dialog on desktop and a bottom Drawer on mobile from one shared set of components.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "@persianlabsui/dialog",
    "@persianlabsui/drawer"
  ],
  "files": [
    {
      "path": "registry/base/ui/responsive-dialog.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport {\n  Dialog,\n  DialogClose,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogPanel,\n  DialogPopup,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\"\nimport {\n  Drawer,\n  DrawerClose,\n  DrawerDescription,\n  DrawerFooter,\n  DrawerHeader,\n  DrawerPanel,\n  DrawerPopup,\n  DrawerTitle,\n  DrawerTrigger,\n} from \"@/components/ui/drawer\"\nimport { useIsMobile } from \"@/hooks/use-media-query\"\n\ntype ResponsiveDialogContextValue = {\n  isDesktop: boolean\n}\n\nconst ResponsiveDialogContext =\n  React.createContext<ResponsiveDialogContextValue | null>(null)\n\nfunction useResponsiveDialogContext() {\n  const context = React.useContext(ResponsiveDialogContext)\n\n  if (!context) {\n    throw new Error(\n      \"ResponsiveDialog components must be used within <ResponsiveDialog>\"\n    )\n  }\n\n  return context\n}\n\ntype ResponsiveDialogProps = {\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (open: boolean) => void\n  children: React.ReactNode\n  /** Which edge the drawer slides in from on mobile. @defaultValue \"bottom\" */\n  drawerPosition?: \"right\" | \"left\" | \"top\" | \"bottom\" | \"start\" | \"end\"\n}\n\nfunction ResponsiveDialog({\n  open,\n  defaultOpen,\n  onOpenChange,\n  children,\n  drawerPosition = \"bottom\",\n}: ResponsiveDialogProps) {\n  const isMobile = useIsMobile()\n  const isDesktop = !isMobile\n\n  const contextValue = React.useMemo(() => ({ isDesktop }), [isDesktop])\n\n  return (\n    <ResponsiveDialogContext.Provider value={contextValue}>\n      {isDesktop ? (\n        <Dialog\n          open={open}\n          defaultOpen={defaultOpen}\n          onOpenChange={onOpenChange}\n        >\n          {children}\n        </Dialog>\n      ) : (\n        <Drawer\n          open={open}\n          defaultOpen={defaultOpen}\n          onOpenChange={onOpenChange}\n          position={drawerPosition}\n        >\n          {children}\n        </Drawer>\n      )}\n    </ResponsiveDialogContext.Provider>\n  )\n}\n\nfunction ResponsiveDialogTrigger(\n  props: React.ComponentProps<typeof DialogTrigger>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogTrigger {...props} />\n  ) : (\n    <DrawerTrigger {...(props as React.ComponentProps<typeof DrawerTrigger>)} />\n  )\n}\n\nfunction ResponsiveDialogClose(\n  props: React.ComponentProps<typeof DialogClose>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogClose {...props} />\n  ) : (\n    <DrawerClose {...(props as React.ComponentProps<typeof DrawerClose>)} />\n  )\n}\n\nfunction ResponsiveDialogPopup(\n  props: React.ComponentProps<typeof DialogPopup>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogPopup {...props} />\n  ) : (\n    <DrawerPopup {...(props as React.ComponentProps<typeof DrawerPopup>)} />\n  )\n}\n\nfunction ResponsiveDialogPanel(\n  props: React.ComponentProps<typeof DialogPanel>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogPanel {...props} />\n  ) : (\n    <DrawerPanel {...(props as React.ComponentProps<typeof DrawerPanel>)} />\n  )\n}\n\nfunction ResponsiveDialogHeader(\n  props: React.ComponentProps<typeof DialogHeader>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogHeader {...props} />\n  ) : (\n    <DrawerHeader {...(props as React.ComponentProps<typeof DrawerHeader>)} />\n  )\n}\n\nfunction ResponsiveDialogFooter(\n  props: React.ComponentProps<typeof DialogFooter>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogFooter {...props} />\n  ) : (\n    <DrawerFooter {...(props as React.ComponentProps<typeof DrawerFooter>)} />\n  )\n}\n\nfunction ResponsiveDialogTitle(\n  props: React.ComponentProps<typeof DialogTitle>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogTitle {...props} />\n  ) : (\n    <DrawerTitle {...(props as React.ComponentProps<typeof DrawerTitle>)} />\n  )\n}\n\nfunction ResponsiveDialogDescription(\n  props: React.ComponentProps<typeof DialogDescription>\n) {\n  const { isDesktop } = useResponsiveDialogContext()\n  return isDesktop ? (\n    <DialogDescription {...props} />\n  ) : (\n    <DrawerDescription\n      {...(props as React.ComponentProps<typeof DrawerDescription>)}\n    />\n  )\n}\n\n/**\n * Renders children on desktop only. On mobile the dialog is a bottom drawer\n * users dismiss by swiping down, so a Cancel button is redundant there.\n *\n * Convention: wrap every Cancel/انصراف action in this so it shows on desktop\n * only; keep the confirm/primary action outside so it stays on both. The\n * footer is flex, so the lone primary button fills the width on mobile.\n */\nfunction ResponsiveDialogDesktopOnly({\n  children,\n}: {\n  children: React.ReactNode\n}) {\n  const { isDesktop } = useResponsiveDialogContext()\n  if (!isDesktop) return null\n  return <>{children}</>\n}\n\nexport {\n  ResponsiveDialog,\n  ResponsiveDialogClose,\n  ResponsiveDialogDescription,\n  ResponsiveDialogDesktopOnly,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogPanel,\n  ResponsiveDialogPopup,\n  ResponsiveDialogPopup as ResponsiveDialogContent,\n  ResponsiveDialogTitle,\n  ResponsiveDialogTrigger,\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"
}