{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hold-to-confirm",
  "type": "registry:block",
  "title": "Hold to confirm",
  "description": "Hold to confirm",
  "files": [
    {
      "path": "components/usages/holdtoconfirmusage.tsx",
      "content": "'use client';\r\n\r\nimport { HoldToConfirm } from '@/registry/open-source/hold-to-confirm';\r\nimport { motion } from 'motion/react';\r\n\r\nexport default function HoldToConfirmDemo() {\r\n    return (\r\n        <div className='flex  items-center justify-center '>\r\n            <div className='w-full max-w-md rounded-2xl border bg-card p-6 shadow-lg text-center space-y-4'>\r\n                <h2 className='text-xl font-semibold text-foreground'>\r\n                    Confirm Payment\r\n                </h2>\r\n                <p className='text-sm text-muted-foreground'>\r\n                    You are about to{' '}\r\n                    <span className='font-medium'>buy the Pro subscription</span>\r\n                    for <span className='font-medium'>$49.99</span>. Hold the button below\r\n                    to confirm. This purchase will be processed securely.\r\n                </p>\r\n\r\n                <HoldToConfirm\r\n                    variant='outline'\r\n                    size='lg'\r\n                    animation='fill'\r\n                    fillClassName='bg-green-600 dark:bg-green-500 text-white dark:text-black'\r\n                    confirmedClassName='text-white dark:text-black font-semibold'\r\n                    showProgressOnConfirm={true}\r\n                    confirmedChildren={\r\n                        <>\r\n                            <motion.svg\r\n                                xmlns='http://www.w3.org/2000/svg'\r\n                                viewBox='0 0 24 24'\r\n                                fill='none'\r\n                                strokeWidth={2}\r\n                                stroke='currentColor'\r\n                                className='w-5 h-5 text-white dark:text-black'\r\n                                initial={{ scale: 0.8 }}\r\n                                animate={{ scale: 1 }}\r\n                                transition={{\r\n                                    delay: 0.6,\r\n                                    type: 'spring',\r\n                                    stiffness: 300,\r\n                                    damping: 10,\r\n                                }}\r\n                            >\r\n                                <motion.path\r\n                                    strokeLinecap='round'\r\n                                    strokeLinejoin='round'\r\n                                    d='M5 13l4 4L19 7'\r\n                                    initial={{ pathLength: 0 }}\r\n                                    animate={{ pathLength: 1 }}\r\n                                    transition={{\r\n                                        duration: 0.6,\r\n                                        ease: 'easeInOut',\r\n                                    }}\r\n                                />\r\n                            </motion.svg>\r\n                            <span className='ml-2'>Payment Successful!</span>\r\n                        </>\r\n                    }\r\n                    resetAfter={3000}\r\n                >\r\n                    Hold to Pay $49.99\r\n                </HoldToConfirm>\r\n            </div>\r\n        </div>\r\n    );\r\n}\r\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/holdtoconfirmusage.tsx",
      "content": "'use client';\r\n\r\nimport { HoldToConfirm } from '@/registry/open-source/hold-to-confirm';\r\nimport { motion } from 'motion/react';\r\n\r\nexport default function HoldToConfirmDemo() {\r\n    return (\r\n        <div className='flex  items-center justify-center '>\r\n            <div className='w-full max-w-md rounded-2xl border bg-card p-6 shadow-lg text-center space-y-4'>\r\n                <h2 className='text-xl font-semibold text-foreground'>\r\n                    Confirm Payment\r\n                </h2>\r\n                <p className='text-sm text-muted-foreground'>\r\n                    You are about to{' '}\r\n                    <span className='font-medium'>buy the Pro subscription</span>\r\n                    for <span className='font-medium'>$49.99</span>. Hold the button below\r\n                    to confirm. This purchase will be processed securely.\r\n                </p>\r\n\r\n                <HoldToConfirm\r\n                    variant='outline'\r\n                    size='lg'\r\n                    animation='fill'\r\n                    fillClassName='bg-green-600 dark:bg-green-500 text-white dark:text-black'\r\n                    confirmedClassName='text-white dark:text-black font-semibold'\r\n                    showProgressOnConfirm={true}\r\n                    confirmedChildren={\r\n                        <>\r\n                            <motion.svg\r\n                                xmlns='http://www.w3.org/2000/svg'\r\n                                viewBox='0 0 24 24'\r\n                                fill='none'\r\n                                strokeWidth={2}\r\n                                stroke='currentColor'\r\n                                className='w-5 h-5 text-white dark:text-black'\r\n                                initial={{ scale: 0.8 }}\r\n                                animate={{ scale: 1 }}\r\n                                transition={{\r\n                                    delay: 0.6,\r\n                                    type: 'spring',\r\n                                    stiffness: 300,\r\n                                    damping: 10,\r\n                                }}\r\n                            >\r\n                                <motion.path\r\n                                    strokeLinecap='round'\r\n                                    strokeLinejoin='round'\r\n                                    d='M5 13l4 4L19 7'\r\n                                    initial={{ pathLength: 0 }}\r\n                                    animate={{ pathLength: 1 }}\r\n                                    transition={{\r\n                                        duration: 0.6,\r\n                                        ease: 'easeInOut',\r\n                                    }}\r\n                                />\r\n                            </motion.svg>\r\n                            <span className='ml-2'>Payment Successful!</span>\r\n                        </>\r\n                    }\r\n                    resetAfter={3000}\r\n                >\r\n                    Hold to Pay $49.99\r\n                </HoldToConfirm>\r\n            </div>\r\n        </div>\r\n    );\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/hold-to-confirm.tsx",
      "content": "'use client';\r\n\r\nimport * as React from 'react';\r\nimport {\r\n    motion,\r\n    AnimatePresence,\r\n    useMotionValue,\r\n    animate,\r\n    useTransform,\r\n} from 'motion/react';\r\nimport { cn } from '../utilities/cn';\r\nimport { buttonVariants, type ButtonProps } from '@/components/ui/button';\r\n\r\n// Credit:\r\n// https://scrollxui.dev/docs/components/hold-toconfirm\r\n\r\ninterface HoldToConfirmProps extends Omit<ButtonProps, 'asChild'> {\r\n    asChild?: boolean;\r\n    duration?: number;\r\n    onConfirm?: () => void;\r\n    animation?: 'border' | 'fill';\r\n    fillClassName?: string;\r\n    confirmedChildren?: React.ReactNode;\r\n    confirmedClassName?: string;\r\n    resetAfter?: number;\r\n    showProgressOnConfirm?: boolean;\r\n}\r\n\r\nconst HoldToConfirm = React.forwardRef<HTMLButtonElement, HoldToConfirmProps>(\r\n    (\r\n        {\r\n            duration = 2000,\r\n            onConfirm,\r\n            animation = 'fill',\r\n            variant,\r\n            size,\r\n            className,\r\n            fillClassName,\r\n            confirmedChildren,\r\n            confirmedClassName,\r\n            resetAfter = 2000,\r\n            showProgressOnConfirm = false,\r\n            asChild = false,\r\n            children = 'Hold to confirm',\r\n            ...props\r\n        },\r\n        ref,\r\n    ) => {\r\n        const [confirmed, setConfirmed] = React.useState(false);\r\n        const textRef = React.useRef<HTMLSpanElement>(null);\r\n\r\n        const progress = useMotionValue(0);\r\n        const controlsRef = React.useRef<ReturnType<typeof animate> | null>(null);\r\n        const holdTimerRef = React.useRef<NodeJS.Timeout | null>(null);\r\n        const resetTimerRef = React.useRef<NodeJS.Timeout | null>(null);\r\n\r\n        const resetHold = (smooth: boolean) => {\r\n            controlsRef.current?.stop();\r\n            if (smooth) {\r\n                controlsRef.current = animate(progress, 0, {\r\n                    duration: 0.3,\r\n                    ease: 'easeOut',\r\n                });\r\n            } else {\r\n                progress.set(0);\r\n            }\r\n        };\r\n\r\n        const startHold = () => {\r\n            if (confirmed) return;\r\n\r\n            controlsRef.current = animate(progress, 1, {\r\n                duration: duration / 1000,\r\n                ease: 'linear',\r\n            });\r\n\r\n            holdTimerRef.current = setTimeout(() => {\r\n                setConfirmed(true);\r\n                onConfirm?.();\r\n\r\n                if (!showProgressOnConfirm) {\r\n                    resetHold(false);\r\n                } else {\r\n                    controlsRef.current?.stop();\r\n                    progress.set(1);\r\n                }\r\n\r\n                if (resetAfter > 0) {\r\n                    resetTimerRef.current = setTimeout(() => {\r\n                        setConfirmed(false);\r\n                        resetHold(true);\r\n                    }, resetAfter);\r\n                }\r\n            }, duration);\r\n        };\r\n\r\n        const cancelHold = () => {\r\n            if (holdTimerRef.current) clearTimeout(holdTimerRef.current);\r\n            if (!confirmed) resetHold(true);\r\n        };\r\n\r\n        const width = useTransform(progress, [0, 1], ['0%', '100%']);\r\n        const borderClip = useTransform(\r\n            progress,\r\n            [0, 1],\r\n            ['inset(0 100% 0 0 round 0.375rem)', 'inset(0 0% 0 0 round 0.375rem)'],\r\n        );\r\n\r\n        const textProgress = useTransform(progress, (value) => {\r\n            if (!textRef.current) return 0;\r\n\r\n            const buttonRect = textRef.current\r\n                .closest('button')\r\n                ?.getBoundingClientRect();\r\n            const textRect = textRef.current.getBoundingClientRect();\r\n\r\n            if (!buttonRect || !textRect) return 0;\r\n\r\n            const textStartPercent =\r\n                (textRect.left - buttonRect.left) / buttonRect.width;\r\n            const fillPosition = value;\r\n\r\n            if (fillPosition <= textStartPercent) return 0;\r\n\r\n            const textWidth = textRect.width / buttonRect.width;\r\n            const adjustedProgress = (fillPosition - textStartPercent) / textWidth;\r\n\r\n            return Math.min(Math.max(adjustedProgress, 0), 1);\r\n        });\r\n\r\n        const textWidth = useTransform(textProgress, [0, 1], ['0%', '100%']);\r\n\r\n        React.useEffect(() => {\r\n            return () => {\r\n                controlsRef.current?.stop();\r\n                if (holdTimerRef.current) clearTimeout(holdTimerRef.current);\r\n                if (resetTimerRef.current) clearTimeout(resetTimerRef.current);\r\n            };\r\n        }, []);\r\n\r\n        const Comp = asChild ? 'span' : 'button';\r\n\r\n        return (\r\n            <Comp\r\n                ref={ref}\r\n                {...props}\r\n                onMouseDown={startHold}\r\n                onMouseUp={cancelHold}\r\n                onMouseLeave={cancelHold}\r\n                onTouchStart={startHold}\r\n                onTouchEnd={cancelHold}\r\n                className={cn(\r\n                    'relative overflow-hidden',\r\n                    buttonVariants({ variant, size }),\r\n                    className,\r\n                )}\r\n            >\r\n                {animation === 'fill' && (!confirmed || showProgressOnConfirm) && (\r\n                    <motion.div\r\n                        className={cn('absolute left-0 top-0 h-full', fillClassName)}\r\n                        style={{ width }}\r\n                    />\r\n                )}\r\n\r\n                {animation === 'border' && (!confirmed || showProgressOnConfirm) && (\r\n                    <motion.div\r\n                        className={cn(\r\n                            'absolute inset-0 border-2 rounded-md pointer-events-none',\r\n                            fillClassName,\r\n                        )}\r\n                        style={{ clipPath: borderClip }}\r\n                    />\r\n                )}\r\n\r\n                <span className='relative z-10 flex items-center justify-center w-full'>\r\n                    <AnimatePresence mode='wait'>\r\n                        {confirmed && confirmedChildren ? (\r\n                            <motion.span\r\n                                key='confirmed'\r\n                                initial={{ opacity: 0, scale: 0.9, y: 8 }}\r\n                                animate={{ opacity: 1, scale: 1, y: 0 }}\r\n                                exit={{ opacity: 0, scale: 0.9, y: -8 }}\r\n                                transition={{ duration: 0.3 }}\r\n                                className={cn('flex items-center gap-2', confirmedClassName)}\r\n                            >\r\n                                {confirmedChildren}\r\n                            </motion.span>\r\n                        ) : (\r\n                            <motion.span\r\n                                key='default'\r\n                                initial={{ opacity: 0, scale: 0.9 }}\r\n                                animate={{ opacity: 1, scale: 1 }}\r\n                                exit={{ opacity: 0, scale: 0.9 }}\r\n                                transition={{ duration: 0.25 }}\r\n                                className='relative flex items-center gap-2'\r\n                            >\r\n                                <span ref={textRef} className='relative'>\r\n                                    {children}\r\n                                    {animation === 'fill' && (\r\n                                        <motion.span\r\n                                            className={cn(\r\n                                                'absolute inset-0 overflow-hidden',\r\n                                                fillClassName?.includes('text-')\r\n                                                    ? fillClassName\r\n                                                    : 'text-white dark:text-black',\r\n                                            )}\r\n                                            style={{ width: textWidth }}\r\n                                        >\r\n                                            {children}\r\n                                        </motion.span>\r\n                                    )}\r\n                                </span>\r\n                            </motion.span>\r\n                        )}\r\n                    </AnimatePresence>\r\n                </span>\r\n            </Comp>\r\n        );\r\n    },\r\n);\r\n\r\nHoldToConfirm.displayName = 'HoldToConfirm';\r\n\r\nexport { HoldToConfirm };\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/button.tsx",
      "content": "import * as React from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport { Slot } from \"@radix-ui/react-slot\";\r\nimport { cva, type VariantProps } from \"class-variance-authority\";\r\n\r\nconst buttonVariants = cva(\r\n\t\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm text-white hover:text-gray-400 font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\",\r\n\t{\r\n\t\tvariants: {\r\n\t\t\tvariant: {\r\n\t\t\t\tsimple:\r\n\t\t\t\t\t\"bg-secondary relative z-10 bg-transparent hover:border-secondary hover:bg-secondary/50  border border-transparent dark:text-white text-sm md:text-sm transition font-medium duration-200  rounded-md px-4 py-2  flex items-center justify-center\",\r\n\t\t\t\tdefault: \"bg-primary text-primary-foreground hover:bg-primary/90\",\r\n\t\t\t\tdestructive:\r\n\t\t\t\t\t\"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\r\n\t\t\t\toutline:\r\n\t\t\t\t\t\"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\r\n\t\t\t\tsecondary:\r\n\t\t\t\t\t\"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\r\n\t\t\t\tghost: \"hover:bg-accent hover:text-black hover:stroke-black dark:text-white text-black\",\r\n\t\t\t\tlink: \"text-primary underline-offset-4 hover:underline\",\r\n\t\t\t},\r\n\t\t\tsize: {\r\n\t\t\t\tdefault: \"h-10 px-4 py-2\",\r\n\t\t\t\tsm: \"h-9 rounded-md px-3\",\r\n\t\t\t\tlg: \"h-11 rounded-md px-8\",\r\n\t\t\t\ticon: \"h-10 w-10\",\r\n\t\t\t},\r\n\t\t},\r\n\t\tdefaultVariants: {\r\n\t\t\tvariant: \"default\",\r\n\t\t\tsize: \"default\",\r\n\t\t},\r\n\t}\r\n);\r\n\r\nexport interface ButtonProps\r\n\textends React.ButtonHTMLAttributes<HTMLButtonElement>,\r\n\t\tVariantProps<typeof buttonVariants> {\r\n\tasChild?: boolean;\r\n}\r\n\r\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\r\n\t({ className, variant, size, asChild = false, ...props }, ref) => {\r\n\t\tconst Comp = asChild ? Slot : \"button\";\r\n\t\treturn (\r\n\t\t\t<Comp\r\n\t\t\t\tclassName={cn(buttonVariants({ variant, size, className }))}\r\n\t\t\t\tref={ref}\r\n\t\t\t\t{...props}\r\n\t\t\t/>\r\n\t\t);\r\n\t}\r\n);\r\nButton.displayName = \"Button\";\r\n\r\nexport { Button, buttonVariants };\r\n",
      "type": "registry:ui"
    }
  ]
}