{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "responsive-alert-dialog",
  "title": "Responsive Alert Dialog",
  "description": "Renders an Alert Dialog on desktop and a bottom Drawer on mobile from one shared set of components.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "@persianlabsui/alert-dialog",
    "@persianlabsui/drawer"
  ],
  "files": [
    {
      "path": "registry/base/ui/responsive-alert-dialog.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Drawer,\n  DrawerClose,\n  DrawerDescription,\n  DrawerFooter,\n  DrawerHeader,\n  DrawerPopup,\n  DrawerTitle,\n  DrawerTrigger,\n} from \"@/components/ui/drawer\"\nimport { useIsMobile } from \"@/hooks/use-media-query\"\nimport { cn } from \"@/lib/utils\"\n\ntype ResponsiveAlertDialogContextValue = {\n  isDesktop: boolean\n}\n\nconst ResponsiveAlertDialogContext =\n  React.createContext<ResponsiveAlertDialogContextValue | null>(null)\n\nfunction useResponsiveAlertDialogContext() {\n  const context = React.useContext(ResponsiveAlertDialogContext)\n\n  if (!context) {\n    throw new Error(\n      \"ResponsiveAlertDialog components must be used within <ResponsiveAlertDialog>\"\n    )\n  }\n\n  return context\n}\n\ntype ResponsiveAlertDialogProps = {\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (open: boolean) => void\n  children: React.ReactNode\n}\n\nfunction ResponsiveAlertDialog({\n  open,\n  defaultOpen,\n  onOpenChange,\n  children,\n}: ResponsiveAlertDialogProps) {\n  const isMobile = useIsMobile()\n  const isDesktop = !isMobile\n\n  const contextValue = React.useMemo(() => ({ isDesktop }), [isDesktop])\n\n  return (\n    <ResponsiveAlertDialogContext.Provider value={contextValue}>\n      {isDesktop ? (\n        <AlertDialog\n          open={open}\n          defaultOpen={defaultOpen}\n          onOpenChange={onOpenChange}\n        >\n          {children}\n        </AlertDialog>\n      ) : (\n        // Alert Dialog never closes on an outside press — disablePointerDismissal\n        // carries that same \"must pick an action\" behavior over to the mobile\n        // drawer. Swiping down still dismisses it, same as a native action sheet.\n        <Drawer\n          open={open}\n          defaultOpen={defaultOpen}\n          onOpenChange={onOpenChange}\n          disablePointerDismissal\n        >\n          {children}\n        </Drawer>\n      )}\n    </ResponsiveAlertDialogContext.Provider>\n  )\n}\n\nfunction ResponsiveAlertDialogTrigger(\n  props: React.ComponentProps<typeof AlertDialogTrigger>\n) {\n  const { isDesktop } = useResponsiveAlertDialogContext()\n  return isDesktop ? (\n    <AlertDialogTrigger {...props} />\n  ) : (\n    <DrawerTrigger {...(props as React.ComponentProps<typeof DrawerTrigger>)} />\n  )\n}\n\n/**\n * Self-contained popup — like AlertDialogContent, it renders its own\n * portal/backdrop, so it's the only piece consumers place directly under\n * ResponsiveAlertDialog alongside the trigger.\n */\nfunction ResponsiveAlertDialogContent(\n  props: React.ComponentProps<typeof AlertDialogContent>\n) {\n  const { isDesktop } = useResponsiveAlertDialogContext()\n  return isDesktop ? (\n    <AlertDialogContent {...props} />\n  ) : (\n    <DrawerPopup\n      showBar\n      {...(props as React.ComponentProps<typeof DrawerPopup>)}\n    />\n  )\n}\n\nfunction ResponsiveAlertDialogHeader(\n  props: React.ComponentProps<typeof AlertDialogHeader>\n) {\n  const { isDesktop } = useResponsiveAlertDialogContext()\n  return isDesktop ? (\n    <AlertDialogHeader {...props} />\n  ) : (\n    <DrawerHeader {...(props as React.ComponentProps<typeof DrawerHeader>)} />\n  )\n}\n\nfunction ResponsiveAlertDialogFooter(\n  props: React.ComponentProps<typeof AlertDialogFooter>\n) {\n  const { isDesktop } = useResponsiveAlertDialogContext()\n  return isDesktop ? (\n    <AlertDialogFooter {...props} />\n  ) : (\n    <DrawerFooter\n      {...(props as React.ComponentProps<typeof DrawerFooter>)}\n      className={cn(\n        // Alert Dialog's action pair reads as a single choice, not a list —\n        // keep Cancel/Continue side by side even on the drawer's narrow\n        // mobile width instead of DrawerFooter's default stacked layout.\n        \"grid grid-cols-2\",\n        props.className\n      )}\n    />\n  )\n}\n\nfunction ResponsiveAlertDialogTitle(\n  props: React.ComponentProps<typeof AlertDialogTitle>\n) {\n  const { isDesktop } = useResponsiveAlertDialogContext()\n  return isDesktop ? (\n    <AlertDialogTitle {...props} />\n  ) : (\n    <DrawerTitle {...(props as React.ComponentProps<typeof DrawerTitle>)} />\n  )\n}\n\nfunction ResponsiveAlertDialogDescription(\n  props: React.ComponentProps<typeof AlertDialogDescription>\n) {\n  const { isDesktop } = useResponsiveAlertDialogContext()\n  return isDesktop ? (\n    <AlertDialogDescription {...props} />\n  ) : (\n    <DrawerDescription\n      {...(props as React.ComponentProps<typeof DrawerDescription>)}\n    />\n  )\n}\n\n/**\n * A small icon badge above the title. AlertDialogMedia's own styles lean on\n * selectors scoped to AlertDialogContent's DOM, which the drawer doesn't\n * have — this renders the same look directly instead of reusing that\n * component, so it works identically on both platforms.\n */\nfunction ResponsiveAlertDialogMedia({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"responsive-alert-dialog-media\"\n      className={cn(\n        \"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted *:[svg:not([class*='size-'])]:size-6\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Just a styled Button on both platforms — like AlertDialogAction, it\n * carries no built-in close behavior, so there's nothing platform-specific\n * to branch on.\n */\nfunction ResponsiveAlertDialogAction(\n  props: React.ComponentProps<typeof AlertDialogAction>\n) {\n  return <AlertDialogAction {...props} />\n}\n\nfunction ResponsiveAlertDialogCancel({\n  className,\n  variant = \"outline\",\n  size = \"default\",\n  ...props\n}: React.ComponentProps<typeof AlertDialogCancel>) {\n  const { isDesktop } = useResponsiveAlertDialogContext()\n  return isDesktop ? (\n    <AlertDialogCancel\n      className={className}\n      variant={variant}\n      size={size}\n      {...props}\n    />\n  ) : (\n    <DrawerClose\n      data-slot=\"alert-dialog-cancel\"\n      className={className}\n      render={<Button variant={variant} size={size} />}\n      {...(props as React.ComponentProps<typeof DrawerClose>)}\n    />\n  )\n}\n\nexport {\n  ResponsiveAlertDialog,\n  ResponsiveAlertDialogAction,\n  ResponsiveAlertDialogCancel,\n  ResponsiveAlertDialogContent,\n  ResponsiveAlertDialogDescription,\n  ResponsiveAlertDialogFooter,\n  ResponsiveAlertDialogHeader,\n  ResponsiveAlertDialogMedia,\n  ResponsiveAlertDialogTitle,\n  ResponsiveAlertDialogTrigger,\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"
}