{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "share-button",
  "type": "registry:ui",
  "title": "Share Button",
  "description": "A morphing share button with four-phase choreography, staggered cascade transitions, and cursor glow.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/ruixenui/share-button.tsx",
      "content": "\"use client\";\n\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { useState, useEffect, useCallback, useRef } from \"react\";\n\n/**\n * Share Button — Rauno Freiberg craft.\n *\n * One surface, four phases, choreographed transitions.\n * Idle → Open (staggered cascade) → Closing (reverse cascade) → Done.\n * Icons exit BEFORE state swaps. Container holds shape during exit.\n * Cursor glow under glass. Per-role springs. Blur materialization.\n */\n\n/* ── Springs — tuned per visual weight ── */\n\nconst spring = {\n  morph: { type: \"spring\" as const, stiffness: 400, damping: 30 },\n  content: { type: \"spring\" as const, stiffness: 340, damping: 28 },\n  icon: { type: \"spring\" as const, stiffness: 440, damping: 24 },\n  press: { type: \"spring\" as const, stiffness: 600, damping: 32 },\n};\n\n/* ── Types ── */\n\ntype Phase = \"idle\" | \"open\" | \"closing\" | \"done\";\n\ninterface ShareButtonProps {\n  onShare?: (target: string) => void;\n}\n\n/* ── Share targets ── */\n\nconst TARGETS = [\n  { id: \"link\", label: \"Copy link\" },\n  { id: \"email\", label: \"Email\" },\n  { id: \"x\", label: \"Share on X\" },\n];\n\n/* ── Component ── */\n\nexport function ShareButton({ onShare }: ShareButtonProps) {\n  const [phase, setPhase] = useState<Phase>(\"idle\");\n  const [locked, setLocked] = useState(false);\n  const [glowX, setGlowX] = useState(0);\n  const [glowOn, setGlowOn] = useState(false);\n  const closeTimer = useRef<ReturnType<typeof setTimeout>>();\n\n  const lock = useCallback(() => {\n    setLocked(true);\n    setTimeout(() => setLocked(false), 450);\n  }, []);\n\n  /* Clean up timers */\n  useEffect(() => {\n    return () => {\n      if (closeTimer.current) clearTimeout(closeTimer.current);\n    };\n  }, []);\n\n  /* Auto-revert from done */\n  useEffect(() => {\n    if (phase !== \"done\") return;\n    const t = setTimeout(() => {\n      setPhase(\"idle\");\n      lock();\n    }, 2200);\n    return () => clearTimeout(t);\n  }, [phase, lock]);\n\n  /**\n   * Choreographed close:\n   * 1. Set \"closing\" — icons reverse-stagger out, container holds shape\n   * 2. After 300ms (icons invisible) — swap to target phase\n   */\n  const startClose = (nextPhase: \"idle\" | \"done\") => {\n    setPhase(\"closing\");\n    closeTimer.current = setTimeout(() => setPhase(nextPhase), 300);\n  };\n\n  const toggle = () => {\n    if (locked || phase === \"closing\") return;\n    lock();\n    if (phase === \"idle\") {\n      setPhase(\"open\");\n    } else {\n      startClose(\"idle\");\n    }\n  };\n\n  const share = (target: string) => {\n    if (locked || phase !== \"open\") return;\n    lock();\n    onShare?.(target);\n    startClose(\"done\");\n  };\n\n  const closing = phase === \"closing\";\n\n  return (\n    <div className=\"flex items-center justify-center\">\n      {/* One surface — layout-animated shell */}\n      <motion.div\n        layout\n        transition={spring.morph}\n        style={{ borderRadius: 16 }}\n        className=\"relative flex items-center bg-neutral-900 border select-none\"\n        animate={{\n          borderColor:\n            phase === \"done\"\n              ? \"rgba(52, 211, 153, 0.2)\"\n              : \"rgba(255, 255, 255, 0.08)\",\n          boxShadow:\n            phase === \"done\"\n              ? \"inset 0 1px 0 rgba(52,211,153,0.06), 0 1px 3px rgba(0,0,0,0.4), 0 4px 14px rgba(0,0,0,0.2), 0 0 20px rgba(52,211,153,0.04)\"\n              : \"inset 0 1px 0 rgba(255,255,255,0.04), 0 1px 3px rgba(0,0,0,0.4), 0 4px 14px rgba(0,0,0,0.2)\",\n        }}\n        onMouseMove={(e) => {\n          const r = e.currentTarget.getBoundingClientRect();\n          setGlowX(e.clientX - r.left);\n          if (!glowOn) setGlowOn(true);\n        }}\n        onMouseLeave={() => setGlowOn(false)}\n      >\n        {/* ── Cursor glow — light-under-glass ── */}\n        <div\n          className=\"absolute inset-0 pointer-events-none z-0\"\n          style={{\n            borderRadius: 16,\n            opacity: glowOn ? 1 : 0,\n            transition: \"opacity 0.3s ease\",\n            background: `radial-gradient(100px circle at ${glowX}px 50%, rgba(255,255,255,0.05), transparent)`,\n          }}\n        />\n\n        <AnimatePresence mode=\"popLayout\" initial={false}>\n          {/* ── IDLE ── */}\n          {phase === \"idle\" && (\n            <motion.button\n              key=\"idle\"\n              onClick={toggle}\n              disabled={locked}\n              whileHover={{ scale: 1.025 }}\n              whileTap={{ scale: 0.97 }}\n              className=\"relative z-10 flex items-center gap-2.5 px-5 h-11 text-[14px] font-medium text-white outline-none cursor-pointer disabled:cursor-default\"\n              initial={{ opacity: 0, y: 8, filter: \"blur(3px)\" }}\n              animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n              exit={{ opacity: 0, y: -8, filter: \"blur(3px)\" }}\n              transition={{\n                ...spring.content,\n                filter: { duration: 0.2, ease: \"easeOut\" },\n              }}\n            >\n              <span className=\"tracking-[-0.01em]\">Share</span>\n              <ArrowIcon />\n            </motion.button>\n          )}\n\n          {/* ── OPEN + CLOSING (same key — icons animate within, no AnimatePresence swap) ── */}\n          {(phase === \"open\" || closing) && (\n            <motion.div\n              key=\"open\"\n              className=\"relative z-10 flex items-center h-11\"\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              transition={{ duration: 0.08 }}\n            >\n              {/* Share anchor — dimmed, acts as close */}\n              <motion.button\n                onClick={!closing ? toggle : undefined}\n                whileTap={!closing ? { scale: 0.96 } : undefined}\n                animate={{ opacity: closing ? 0 : 0.45 }}\n                transition={spring.content}\n                className=\"px-4 text-[14px] font-medium tracking-[-0.01em] text-white outline-none cursor-pointer\"\n                style={{ pointerEvents: closing ? \"none\" : \"auto\" }}\n              >\n                Share\n              </motion.button>\n\n              {/* Divider */}\n              <motion.div\n                className=\"w-px h-5 bg-white/[0.08] shrink-0\"\n                initial={{ scaleY: 0, opacity: 0 }}\n                animate={{\n                  scaleY: closing ? 0 : 1,\n                  opacity: closing ? 0 : 1,\n                }}\n                transition={{ ...spring.content, delay: closing ? 0 : 0.02 }}\n              />\n\n              {/* Target icons — forward stagger in, REVERSE stagger out */}\n              {TARGETS.map((target, i) => {\n                const reverseI = TARGETS.length - 1 - i;\n                return (\n                  <motion.button\n                    key={target.id}\n                    onClick={() => share(target.id)}\n                    whileHover={!closing ? { scale: 1.12 } : undefined}\n                    whileTap={!closing ? { scale: 0.88 } : undefined}\n                    initial={{ opacity: 0, scale: 0.4, filter: \"blur(4px)\" }}\n                    animate={{\n                      opacity: closing ? 0 : 1,\n                      scale: closing ? 0.4 : 1,\n                      filter: closing ? \"blur(4px)\" : \"blur(0px)\",\n                    }}\n                    transition={{\n                      ...spring.icon,\n                      delay: closing ? reverseI * 0.04 : 0.04 + i * 0.05,\n                      filter: {\n                        duration: 0.2,\n                        delay: closing ? reverseI * 0.04 : 0.04 + i * 0.05,\n                      },\n                    }}\n                    className=\"flex items-center justify-center w-9 h-9 mx-0.5 rounded-xl text-neutral-400 hover:text-white hover:bg-white/[0.06] transition-colors duration-150 outline-none cursor-pointer\"\n                    aria-label={target.label}\n                    style={{ pointerEvents: closing ? \"none\" : \"auto\" }}\n                  >\n                    {target.id === \"link\" && <LinkIcon />}\n                    {target.id === \"email\" && <EmailIcon />}\n                    {target.id === \"x\" && <XIcon />}\n                  </motion.button>\n                );\n              })}\n\n              <div className=\"w-1.5 shrink-0\" />\n            </motion.div>\n          )}\n\n          {/* ── DONE — checkmark draws on ── */}\n          {phase === \"done\" && (\n            <motion.div\n              key=\"done\"\n              className=\"relative z-10 flex items-center gap-2.5 px-5 h-11 text-[14px] font-medium\"\n              initial={{ opacity: 0, y: 8, filter: \"blur(3px)\" }}\n              animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n              exit={{ opacity: 0, y: -8, filter: \"blur(3px)\" }}\n              transition={{\n                ...spring.content,\n                filter: { duration: 0.2, ease: \"easeOut\" },\n              }}\n            >\n              <span className=\"text-emerald-300/90 tracking-[-0.01em]\">\n                Copied\n              </span>\n              <CheckIcon />\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </motion.div>\n    </div>\n  );\n}\n\n/* ── Icons ── */\n\nfunction ArrowIcon() {\n  return (\n    <svg\n      width=\"15\"\n      height=\"15\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      className=\"text-neutral-400\"\n    >\n      <path d=\"M5 12h14\" />\n      <path d=\"m12 5 7 7-7 7\" />\n    </svg>\n  );\n}\n\nfunction LinkIcon() {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\" />\n      <path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\" />\n    </svg>\n  );\n}\n\nfunction EmailIcon() {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\" />\n      <path d=\"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7\" />\n    </svg>\n  );\n}\n\nfunction XIcon() {\n  return (\n    <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n      <path d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon() {\n  return (\n    <motion.svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      className=\"text-emerald-400\"\n    >\n      <motion.path\n        d=\"M5 13l4 4L19 7\"\n        initial={{ pathLength: 0, opacity: 0 }}\n        animate={{ pathLength: 1, opacity: 1 }}\n        transition={{\n          pathLength: {\n            type: \"spring\",\n            stiffness: 280,\n            damping: 18,\n            delay: 0.15,\n          },\n          opacity: { duration: 0.15, delay: 0.15 },\n        }}\n      />\n    </motion.svg>\n  );\n}\n\nexport default ShareButton;\n",
      "type": "registry:ui",
      "target": "components/ruixen/share-button.tsx"
    }
  ]
}