{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "author": "DesignX ", "name": "morph-confirm", "type": "registry:component", "title": "Morph Confirm", "description": "A button that morphs into its own confirmation card.", "dependencies": [ "lucide-react@^0.577.0", "motion@^14.0.0" ], "registryDependencies": [ "@dx/utils" ], "files": [ { "path": "dx/morph-confirm.tsx", "type": "registry:component", "target": "components/dx/morph-confirm.tsx", "content": "import * as React from \"react\";\nimport { AnimatePresence, LayoutGroup, motion, MotionConfig } from \"motion/react\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * MorphConfirma button that morphs into an inline confirmation card via a\n * shared layoutId. Inspired by family.co.\n */\n\nconst SPRING = { type: \"spring\", stiffness: 350, damping: 35 } as const;\n\ntype MorphConfirmProps = {\n /** Label of the trigger and the confirm button. */\n label: React.ReactNode;\n title?: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n cancelLabel?: React.ReactNode;\n onConfirm?: () => void;\n onCancel?: () => void;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Dim + blur the page behind the card. */\n backdrop?: boolean;\n className?: string;\n /** Tailwind classes for the primary button. */\n buttonClassName?: string;\n};\n\nfunction MorphConfirm({\n label,\n title = \"Are you sure?\",\n description,\n icon,\n cancelLabel = \"Cancel\",\n onConfirm,\n onCancel,\n open: openProp,\n onOpenChange,\n backdrop = true,\n className,\n buttonClassName,\n}: MorphConfirmProps) {\n const [inner, setInner] = React.useState(false);\n const open = openProp ?? inner;\n const setOpen = (v: boolean) => {\n setInner(v);\n onOpenChange?.(v);\n };\n const id = React.useId();\n const titleId = `${id}-title`;\n\n React.useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && (setOpen(false), onCancel?.());\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n const primary = cn(\n \"focus-ring h-10 w-full rounded-full bg-primary px-5 text-sm font-medium text-primary-foreground\",\n buttonClassName,\n );\n\n return (\n \n \n \n {open && backdrop && (\n (setOpen(false), onCancel?.())}\n className=\"fixed inset-0 z-40 bg-overlay/30 backdrop-blur-[2px]\"\n />\n )}\n \n \n
\n {!open && (\n setOpen(true)}\n className={cn(primary, \"max-w-[300px]\")}\n >\n \n {label}\n \n \n )}\n
\n \n {open && (\n \n
\n
\n {icon && {icon}}\n {title}\n
\n (setOpen(false), onCancel?.())}\n className=\"focus-ring state-layer flex size-8 items-center justify-center rounded-full text-muted-foreground\"\n >\n \n \n
\n {description &&

{description}

}\n
\n (setOpen(false), onCancel?.())}\n className=\"focus-ring state-layer h-10 w-full rounded-full bg-container-high text-sm font-medium\"\n >\n {cancelLabel}\n \n (setOpen(false), onConfirm?.())}\n className={primary}\n >\n \n {label}\n \n \n
\n \n )}\n
\n \n
\n
\n );\n}\n\nexport { MorphConfirm };\nexport type { MorphConfirmProps };\n" } ], "categories": [ "motion" ], "meta": { "group": "dx", "docs": "https://dxuireact.com/docs/components/morph-confirm" } }