{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "save-toggle",
  "type": "registry:component",
  "title": "Save Toggle",
  "description": "An animated save toggle button that smoothly transitions between multiple states.",
  "dependencies": [
    "motion",
    "next-themes",
    "react-icons"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "components/watermelon/save-toggle.tsx",
      "type": "registry:component",
      "content": "import React, { useState, useEffect } from 'react';\nimport { motion, AnimatePresence, MotionConfig } from 'motion/react';\nimport { BsCheckCircleFill } from 'react-icons/bs';\nimport { cn } from '@/lib/utils';\nimport { useTheme } from 'next-themes';\n\ntype ButtonStatus = 'idle' | 'loading' | 'success' | 'saved';\ntype Size = 'sm' | 'md' | 'lg';\n\nconst SIZE_CONFIG = {\n  sm: {\n    height: 52,\n    circleWidth: 52,\n    idleWidth: 108,\n    savedWidth: 128,\n    text: 'text-[18px]',\n    icon: 'text-2xl',\n    spinner: 'w-7 h-7',\n    gap: 'gap-2',\n    padding: 'px-4',\n  },\n  md: {\n    height: 56,\n    circleWidth: 56,\n    idleWidth: 120,\n    savedWidth: 140,\n    text: 'text-[20px]',\n    icon: 'text-3xl',\n    spinner: 'w-8 h-8',\n    gap: 'gap-3',\n    padding: 'px-5',\n  },\n  lg: {\n    height: 68,\n    circleWidth: 68,\n    idleWidth: 144,\n    savedWidth: 168,\n    text: 'text-[22px]',\n    icon: 'text-[28px]',\n    spinner: 'w-9 h-9',\n    gap: 'gap-4',\n    padding: 'px-5',\n  },\n};\n\ninterface SaveToggleProps {\n  size?: Size;\n  idleText?: string;\n  savedText?: string;\n  loadingDuration?: number;\n  successDuration?: number;\n  onStatusChange?: (status: ButtonStatus) => void;\n}\n\nexport const SaveToggle: React.FC<SaveToggleProps> = ({\n  size = 'md',\n  idleText = 'Save',\n  savedText = 'Saved',\n  loadingDuration = 1000,\n  successDuration = 800,\n  onStatusChange,\n}) => {\n  const [status, setStatus] = useState<ButtonStatus>('idle');\n  const { theme } = useTheme();\n\n  const cfg = SIZE_CONFIG[size];\n\n  const stableWidth = Math.max(cfg.idleWidth, cfg.savedWidth);\n\n  useEffect(() => {\n    onStatusChange?.(status);\n  }, [status, onStatusChange]);\n\n  const handleClick = () => {\n    if (status === 'idle') {\n      setStatus('loading');\n\n      setTimeout(() => {\n        setStatus('success');\n\n        setTimeout(() => {\n          setStatus('saved');\n        }, successDuration);\n      }, loadingDuration);\n    } else if (status === 'saved') {\n      setStatus('idle');\n    }\n  };\n\n  const isCircle = status === 'loading' || status === 'success';\n\n  const getBackgroundColor = () => {\n    if (status === 'loading' || status === 'success') {\n      return theme === 'dark' ? '#ffffff' : '#18181b';\n    }\n    if (status === 'saved') {\n      return theme === 'dark' ? '#27272a' : '#ffffff';\n    }\n    return theme === 'dark' ? '#27272a' : '#E8E7E0';\n  };\n\n  const getBorderColor = () => {\n    if (status === 'saved') {\n      return theme === 'dark' ? '#ffffff10' : '#00000030';\n    }\n    return 'transparent';\n  };\n\n  const getCheckColor = () => {\n    if (status === 'success') {\n      return theme === 'dark' ? '#27272a' : '#ffffff';\n    }\n    return theme === 'dark' ? '#a1a1aa' : '#27272a';\n  };\n\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <MotionConfig\n        transition={{\n          type: 'spring',\n          stiffness: 400,\n          damping: 30,\n          mass: 1,\n        }}\n      >\n        <motion.button\n          onClick={handleClick}\n          initial={false}\n          animate={{\n            width: isCircle ? cfg.circleWidth : stableWidth,\n            height: cfg.height,\n            backgroundColor: getBackgroundColor(),\n          }}\n          style={{\n            borderWidth: status === 'saved' ? '2px' : '0',\n            borderColor: getBorderColor(),\n          }}\n          transition={{\n            type: 'spring',\n            stiffness: 200,\n            damping: 15,\n            mass: 1.2,\n            backgroundColor: {\n              duration: 0.2,\n            },\n          }}\n          className={cn(\n            'relative z-0 flex cursor-pointer items-center justify-center overflow-hidden rounded-full select-none focus:outline-none active:scale-[0.97]',\n          )}\n        >\n          <AnimatePresence mode=\"popLayout\">\n            {status === 'idle' && (\n              <motion.span\n                key=\"idle\"\n                initial={{ opacity: 0, y: 15 }}\n                animate={{ opacity: 1, y: 0 }}\n                exit={{ opacity: 0, y: -15, x: -20 }}\n                className={`absolute inset-0 flex items-center justify-center font-bold tracking-tight ${cfg.text} text-[#2C2A26] dark:text-zinc-200`}\n              >\n                {idleText}\n              </motion.span>\n            )}\n\n            {status === 'loading' && (\n              <motion.div\n                key=\"loading\"\n                initial={{ opacity: 0, scale: 0.8, filter: 'blur(4px)' }}\n                animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}\n                exit={{ opacity: 0, scale: 0.8, filter: 'blur(4px)' }}\n                className=\"absolute inset-0 flex items-center justify-center\"\n              >\n                <motion.svg\n                  viewBox=\"0 0 26 26\"\n                  className={cfg.spinner}\n                  animate={{ rotate: 360 }}\n                  transition={{\n                    repeat: Infinity,\n                    duration: 0.7,\n                    ease: 'linear',\n                  }}\n                >\n                  <circle\n                    cx=\"13\"\n                    cy=\"13\"\n                    r=\"10\"\n                    stroke={theme === 'dark' ? '#52525b' : '#D0CCC6'}\n                    strokeWidth=\"3\"\n                    fill=\"none\"\n                  />\n                  <path\n                    d=\"M13 3 A10 10 0 0 1 23 13\"\n                    stroke={theme === 'dark' ? '#a1a1aa' : 'white'}\n                    strokeWidth=\"3\"\n                    strokeLinecap=\"round\"\n                    fill=\"none\"\n                  />\n                </motion.svg>\n              </motion.div>\n            )}\n\n            {(status === 'success' || status === 'saved') && (\n              <motion.div\n                key=\"check-state\"\n                layout\n                initial={\n                  status === 'success'\n                    ? { opacity: 0, scale: 0.5, filter: 'blur(4px)' }\n                    : { opacity: 1 }\n                }\n                animate={\n                  status === 'success'\n                    ? { opacity: 1, scale: 1.15, filter: 'blur(0px)' }\n                    : { opacity: 1, scale: 1, y: 0 }\n                }\n                exit={{ opacity: 0, y: 15, filter: 'blur(4px)' }}\n                className={`absolute inset-0 flex items-center justify-center ${status === 'saved' ? `${cfg.gap} ${cfg.padding}` : ''\n                  }`}\n              >\n                <motion.div\n                  layout\n                  animate={{\n                    color: getCheckColor(),\n                  }}\n                >\n                  <BsCheckCircleFill className={`${cfg.icon} z-20`} />\n                </motion.div>\n\n                <AnimatePresence mode=\"popLayout\">\n                  {status === 'saved' && (\n                    <motion.span\n                      initial={{ opacity: 0, x: -10 }}\n                      animate={{ opacity: 1, x: 0 }}\n                      exit={{ opacity: 0 }}\n                      transition={{ delay: 0.1 }}\n                      className={`font-bold tracking-tight whitespace-nowrap ${cfg.text} z-20 text-zinc-900 dark:text-zinc-400`}\n                    >\n                      {savedText}\n                    </motion.span>\n                  )}\n                </AnimatePresence>\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </motion.button>\n      </MotionConfig>\n    </div>\n  );\n};\n"
    }
  ]
}
