{"version":1,"id":"dynamic-island-payment","framework":"next","kind":"block","files":[{"path":"src/blocks/gfa/dynamic-island-payment.tsx","content":"\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n} from \"motion/react\";\nimport { CheckIcon, CreditCardIcon, Loader2Icon } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type DynamicIslandPaymentState = \"idle\" | \"processing\" | \"success\";\n\nexport type DynamicIslandPaymentProps = {\n  className?: string;\n  idleLabel?: string;\n  processingLabel?: string;\n  successLabel?: string;\n  processingMs?: number;\n  successHoldMs?: number;\n  onStateChange?: (state: DynamicIslandPaymentState) => void;\n};\n\nconst shellSpring = {\n  type: \"spring\" as const,\n  stiffness: 420,\n  damping: 32,\n  mass: 0.85,\n};\n\nexport function DynamicIslandPayment({\n  className,\n  idleLabel = \"Pay\",\n  processingLabel = \"Proceed....\",\n  successLabel = \"Payment successful\",\n  processingMs = 1800,\n  successHoldMs = 2600,\n  onStateChange,\n}: DynamicIslandPaymentProps) {\n  const reduce = useReducedMotion();\n  const [state, setState] = useState<DynamicIslandPaymentState>(\"idle\");\n\n  useEffect(() => {\n    onStateChange?.(state);\n  }, [state, onStateChange]);\n\n  useEffect(() => {\n    if (state !== \"processing\") return;\n    const id = window.setTimeout(() => setState(\"success\"), processingMs);\n    return () => window.clearTimeout(id);\n  }, [state, processingMs]);\n\n  useEffect(() => {\n    if (state !== \"success\") return;\n    const id = window.setTimeout(() => setState(\"idle\"), successHoldMs);\n    return () => window.clearTimeout(id);\n  }, [state, successHoldMs]);\n\n  const transition = reduce ? { duration: 0.15 } : shellSpring;\n\n  return (\n    <div\n      data-slot=\"dynamic-island-payment\"\n      className={cn(\n        \"flex w-full flex-col items-center justify-center\",\n        className,\n      )}\n    >\n      <motion.button\n        type=\"button\"\n        layout\n        disabled={state !== \"idle\"}\n        onClick={() => setState(\"processing\")}\n        transition={transition}\n        aria-live=\"polite\"\n        className={cn(\n          \"relative inline-flex items-center justify-center overflow-hidden\",\n          \"bg-zinc-950 text-white shadow-[0_8px_30px_rgba(0,0,0,0.28)]\",\n          \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n          \"disabled:cursor-default\",\n          state === \"idle\" && \"hover:bg-zinc-900 active:scale-[0.98]\",\n        )}\n        style={{\n          borderRadius: state === \"idle\" ? 16 : 999,\n        }}\n        animate={\n          reduce\n            ? undefined\n            : {\n                borderRadius: state === \"idle\" ? 16 : 999,\n              }\n        }\n      >\n        <motion.div\n          layout\n          transition={transition}\n          className={cn(\n            \"flex items-center justify-center\",\n            state === \"idle\" && \"h-12 min-w-[9.5rem] gap-2 px-5\",\n            state === \"processing\" && \"h-11 min-w-[10.5rem] gap-2 px-5\",\n            state === \"success\" && \"h-11 min-w-[14.5rem] gap-2 px-5\",\n          )}\n        >\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            {state === \"idle\" ? (\n              <motion.span\n                key=\"idle\"\n                layout\n                initial={{ opacity: 0, y: 8, filter: \"blur(4px)\" }}\n                animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n                exit={{ opacity: 0, y: -8, filter: \"blur(4px)\" }}\n                transition={{ duration: 0.2 }}\n                className=\"inline-flex items-center gap-2 text-sm font-semibold tracking-tight\"\n              >\n                <CreditCardIcon className=\"size-4 shrink-0\" aria-hidden />\n                {idleLabel}\n              </motion.span>\n            ) : null}\n\n            {state === \"processing\" ? (\n              <motion.span\n                key=\"processing\"\n                layout\n                initial={{ opacity: 0, y: 8, filter: \"blur(4px)\" }}\n                animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n                exit={{ opacity: 0, y: -6, filter: \"blur(4px)\" }}\n                transition={{ duration: 0.22 }}\n                className=\"inline-flex items-center gap-2 text-sm font-medium tracking-tight\"\n              >\n                <Loader2Icon\n                  className=\"size-4 animate-spin text-zinc-300\"\n                  aria-hidden\n                />\n                {processingLabel}\n              </motion.span>\n            ) : null}\n\n            {state === \"success\" ? (\n              <motion.span\n                key=\"success\"\n                layout\n                initial={{ opacity: 0, y: 8, filter: \"blur(4px)\" }}\n                animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n                exit={{ opacity: 0, y: -6, filter: \"blur(4px)\" }}\n                transition={{ duration: 0.22 }}\n                className=\"inline-flex items-center gap-2 text-sm font-medium tracking-tight\"\n              >\n                <motion.span\n                  initial={{ scale: 0.4, opacity: 0 }}\n                  animate={{ scale: 1, opacity: 1 }}\n                  transition={\n                    reduce\n                      ? { duration: 0.1 }\n                      : { type: \"spring\", stiffness: 520, damping: 22 }\n                  }\n                  className=\"flex size-5 items-center justify-center rounded-full bg-emerald-500 text-white\"\n                >\n                  <CheckIcon\n                    className=\"size-3.5\"\n                    strokeWidth={2.75}\n                    aria-hidden\n                  />\n                </motion.span>\n                {successLabel}\n              </motion.span>\n            ) : null}\n          </AnimatePresence>\n        </motion.div>\n      </motion.button>\n    </div>\n  );\n}\n\n/** @deprecated Prefer DynamicIslandPayment */\nexport const DynamicIslandPaymentButton = DynamicIslandPayment;\n\nexport { DynamicIslandPayment as DynamicIslandPaymentBlock };\n"}],"meta":{"studioName":"GFA Development & Design Studio","siteUrl":"https://ui.gkhn.dev"}}