{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "alert-modal",
  "title": "Alert Modal",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/alert-modal/alert-modal.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { CircleAlert, CircleCheck, Info, TriangleAlert, type LucideIcon } from 'lucide-react'\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogTitle,\n  DialogTrigger,\n} from '@/components/ui/dialog'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/components/ui/button'\n\ntype AlertIcon = 'info' | 'warning' | 'error' | 'success'\n\nexport interface AlertModalProps {\n  /** Controlled open state. Pair with `onOpenChange`. */\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\n  /** Title rendered in the header. Override with the `title` node if you need markup. */\n  title?: React.ReactNode\n  /** Description rendered under the title. */\n  description?: React.ReactNode\n  /** Label for the primary action button. */\n  actionLabel?: React.ReactNode\n  /** Label for the cancel button. Pass null to hide. */\n  cancelLabel?: React.ReactNode | null\n  /** Visual tone — colors the icon and action button. */\n  tone?: 'default' | 'destructive' | 'success' | 'warning'\n  /** Quick icon shortcut, a custom icon component, or a rendered node. */\n  icon?: AlertIcon | LucideIcon | React.ReactNode | null\n  /** Show a spinner on the action button and disable both buttons. */\n  loading?: boolean\n  /** Disable the primary action without a spinner. */\n  actionDisabled?: boolean\n  className?: string\n  onAction?: (event: React.MouseEvent) => void\n  onCancel?: (event: React.MouseEvent) => void\n  /** Element that opens the modal. Rendered as the trigger when provided. */\n  trigger?: React.ReactNode\n  /** Optional free-form body content between the header and the action row. */\n  children?: React.ReactNode\n  /** Replace the default action/cancel button row. */\n  actions?: React.ReactNode\n}\n\nconst builtInIcons: Record<AlertIcon, LucideIcon> = {\n  info: Info,\n  success: CircleCheck,\n  warning: TriangleAlert,\n  error: CircleAlert,\n}\n\nfunction isAlertIcon(value: unknown): value is AlertIcon {\n  return value === 'info' || value === 'success' || value === 'warning' || value === 'error'\n}\n\nconst iconColorClasses: Record<NonNullable<AlertModalProps['tone']>, string> = {\n  destructive: 'text-destructive',\n  success: 'text-success',\n  warning: 'text-warning',\n  default: 'text-muted-foreground',\n}\n\nconst actionToneClasses: Record<NonNullable<AlertModalProps['tone']>, string> = {\n  destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n  success: 'bg-success text-success-foreground hover:bg-success/90',\n  warning: 'bg-warning text-warning-foreground hover:bg-warning/90',\n  default: '',\n}\n\nconst AlertModal = ({\n  open,\n  onOpenChange,\n  title = '',\n  description = '',\n  actionLabel = 'Continue',\n  cancelLabel = 'Cancel',\n  tone = 'default',\n  icon = null,\n  loading = false,\n  actionDisabled = false,\n  className,\n  onAction,\n  onCancel,\n  trigger,\n  children,\n  actions,\n}: AlertModalProps) => {\n  const isControlled = open !== undefined\n  const [internalOpen, setInternalOpen] = React.useState<boolean>(open ?? false)\n  const effectiveOpen = isControlled ? open : internalOpen\n\n  function setOpen(value: boolean) {\n    if (!isControlled) setInternalOpen(value)\n    onOpenChange?.(value)\n  }\n\n  const resolvedIcon = React.useMemo<React.ReactNode>(() => {\n    if (!icon) return null\n    if (isAlertIcon(icon)) {\n      const IconComp = builtInIcons[icon]\n      return <IconComp className=\"size-5\" />\n    }\n    if (typeof icon === 'function') {\n      const IconComp = icon as LucideIcon\n      return <IconComp className=\"size-5\" />\n    }\n    return icon\n  }, [icon])\n\n  function handleAction(e: React.MouseEvent) {\n    if (loading || actionDisabled) {\n      e.preventDefault()\n      return\n    }\n    onAction?.(e)\n  }\n\n  function handleCancel(e: React.MouseEvent) {\n    onCancel?.(e)\n  }\n\n  return (\n    <Dialog open={effectiveOpen} onOpenChange={setOpen}>\n      {trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}\n\n      <DialogContent\n        role=\"alertdialog\"\n        showCloseButton={false}\n        className={cn(className)}\n      >\n        <div className=\"flex flex-col gap-2 text-center sm:text-left\">\n          {resolvedIcon && (\n            <div\n              className={cn(\n                'bg-muted mb-2 flex size-10 items-center justify-center rounded-full',\n                iconColorClasses[tone],\n              )}\n            >\n              {resolvedIcon}\n            </div>\n          )}\n          <DialogTitle className=\"text-lg font-semibold\">{title}</DialogTitle>\n          {description && <DialogDescription className=\"text-muted-foreground text-sm\">{description}</DialogDescription>}\n        </div>\n\n        {children && <div className=\"text-sm\">{children}</div>}\n\n        <div className=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n          {actions ?? (\n            <>\n              {cancelLabel != null && (\n                <DialogClose asChild>\n                  <Button variant=\"outline\" disabled={loading} onClick={handleCancel}>\n                    {cancelLabel}\n                  </Button>\n                </DialogClose>\n              )}\n              <Button\n                disabled={loading || actionDisabled}\n                aria-busy={loading}\n                className={actionToneClasses[tone]}\n                onClick={handleAction}\n              >\n                {loading && (\n                  <span\n                    className=\"mr-2 inline-block size-3.5 animate-spin rounded-full border-2 border-current border-t-transparent\"\n                    aria-hidden=\"true\"\n                  />\n                )}\n                {actionLabel}\n              </Button>\n            </>\n          )}\n        </div>\n      </DialogContent>\n    </Dialog>\n  )\n}\nAlertModal.displayName = 'AlertModal'\n\nexport { AlertModal }\n",
      "type": "registry:ui",
      "target": "~/components/ui/alert-modal/alert-modal.tsx"
    },
    {
      "path": "packages/registry-react/components/alert-modal/index.ts",
      "content": "export { AlertModal, type AlertModalProps } from './alert-modal'\n",
      "type": "registry:ui",
      "target": "~/components/ui/alert-modal/index.ts"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/dialog.json",
    "https://uipkge.dev/r/react/button.json"
  ],
  "description": "Props-driven shortcut for confirm and destructive prompts — pass `title`, `description`, `actionLabel`, and a `tone` and you get a fully styled modal with a leading icon ring, action button, and optional async loading state. Skip it and use Dialog when you need a free-form modal instead.",
  "categories": [
    "overlay"
  ]
}