{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"ring-switch","type":"registry:ui","title":"Ring Switch","description":"A notification switch whose bell swings and rings when enabled, and gets a drawn slash when muted.","dependencies":["motion"],"registryDependencies":[],"files":[{"path":"registry/ui/ring-switch.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useState, type ButtonHTMLAttributes } from \"react\";\nimport { AnimatePresence, MotionConfig, motion } from \"motion/react\";\n\nexport interface RingSwitchProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n  /** Controlled on/off state. */\n  checked?: boolean;\n  /** Initial state when uncontrolled. */\n  defaultChecked?: boolean;\n  /** Called with the new state after each toggle. */\n  onCheckedChange?: (checked: boolean) => void;\n  /** Visible label next to the switch; also the accessible name. */\n  label?: string;\n  /** Track and bell color when enabled. */\n  color?: string;\n}\n\nconst knobSpring = { type: \"spring\", stiffness: 520, damping: 30 } as const;\n\nexport function RingSwitch({\n  checked,\n  defaultChecked = true,\n  onCheckedChange,\n  label = \"Notifications\",\n  color = \"#f59e0b\",\n  className,\n  onClick,\n  ...props\n}: RingSwitchProps) {\n  const [inner, setInner] = useState(defaultChecked);\n  const on = checked ?? inner;\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <div className={`inline-flex items-center gap-4 ${className ?? \"\"}`}>\n        <button\n          type=\"button\"\n          role=\"switch\"\n          aria-checked={on}\n          aria-label={label}\n          {...props}\n          onClick={(e) => {\n            setInner(!on);\n            onCheckedChange?.(!on);\n            onClick?.(e);\n          }}\n          className=\"group relative h-11 w-[84px] shrink-0 rounded-full p-1 outline-none transition-[background-color,box-shadow] duration-300 focus-visible:ring-[3px] focus-visible:ring-ring/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50\"\n          style={{\n            backgroundColor: on ? color : \"var(--muted)\",\n            boxShadow: on ? `inset 0 1px 2px rgb(0 0 0/.18), 0 6px 22px -8px ${color}` : \"inset 0 1px 3px rgb(0 0 0/.25)\",\n          }}\n        >\n          <motion.span\n            initial={false}\n            animate={{ x: on ? 40 : 0 }}\n            whileTap={{ scaleX: 1.18, scaleY: 0.9 }}\n            transition={knobSpring}\n            className=\"relative grid size-9 place-items-center rounded-full bg-background shadow-[0_2px_6px_rgb(0_0_0/.25),inset_0_1px_0_rgb(255_255_255/.35)]\"\n          >\n            {/* Sound waves that burst out once each time the bell turns on. */}\n            <AnimatePresence>\n              {on && (\n                <motion.svg\n                  key=\"waves\"\n                  aria-hidden\n                  viewBox=\"0 0 64 36\"\n                  className=\"pointer-events-none absolute -inset-x-3.5 top-0 h-9 w-16 overflow-visible\"\n                  initial={{ opacity: 0, scale: 0.7 }}\n                  animate={{ opacity: [0, 1, 1, 0], scale: [0.7, 1, 1.15, 1.3] }}\n                  exit={{ opacity: 0 }}\n                  transition={{ duration: 0.9, times: [0, 0.2, 0.6, 1], delay: 0.08 }}\n                  fill=\"none\"\n                  stroke={color}\n                  strokeWidth={2.2}\n                  strokeLinecap=\"round\"\n                >\n                  <path d=\"M8 11q-4 7 0 14M3 7q-6 11 0 22M56 11q4 7 0 14M61 7q6 11 0 22\" />\n                </motion.svg>\n              )}\n            </AnimatePresence>\n\n            <motion.span\n              aria-hidden\n              className=\"block size-6\"\n              style={{ transformOrigin: \"50% 10%\", color: on ? color : \"var(--muted-foreground)\" }}\n              initial={false}\n              animate={on ? { rotate: [0, 26, -22, 16, -11, 6, -3, 0], scale: [1, 1.12, 1] } : { rotate: [0, -8, 0], scale: [1, 0.9, 1] }}\n              transition={{ rotate: { duration: on ? 0.9 : 0.3, ease: \"easeOut\" }, scale: { duration: 0.35 } }}\n            >\n              <svg viewBox=\"0 0 24 24\" className=\"size-6 overflow-visible transition-colors duration-300\">\n                <motion.circle\n                  cx={12}\n                  cy={19.6}\n                  r={2.1}\n                  fill=\"currentColor\"\n                  initial={false}\n                  animate={on ? { cx: [12, 9.4, 14.6, 10.2, 13.4, 11.3, 12] } : { cx: 12 }}\n                  transition={{ duration: 0.95, delay: 0.05, ease: \"easeOut\" }}\n                />\n                <path\n                  fill=\"currentColor\"\n                  d=\"M18 16.4V11a6 6 0 0 0-4.6-5.84V4.4a1.4 1.4 0 0 0-2.8 0v.76A6 6 0 0 0 6 11v5.4L4.4 18.2h15.2z\"\n                />\n                {/* Slash: a background-colored cut behind a thin stroke. */}\n                <motion.path\n                  d=\"M4 3.5L20 20.5\"\n                  strokeWidth={4.4}\n                  strokeLinecap=\"round\"\n                  className=\"stroke-background\"\n                  initial={false}\n                  animate={{ pathLength: on ? 0 : 1, opacity: on ? 0 : 1 }}\n                  transition={{ duration: 0.28, ease: \"easeOut\", delay: on ? 0 : 0.08 }}\n                />\n                <motion.path\n                  d=\"M4 3.5L20 20.5\"\n                  strokeWidth={1.9}\n                  strokeLinecap=\"round\"\n                  stroke=\"currentColor\"\n                  initial={false}\n                  animate={{ pathLength: on ? 0 : 1, opacity: on ? 0 : 1 }}\n                  transition={{ duration: 0.28, ease: \"easeOut\", delay: on ? 0 : 0.08 }}\n                />\n              </svg>\n            </motion.span>\n          </motion.span>\n        </button>\n\n        {label && (\n          <span aria-hidden className=\"grid select-none leading-tight\">\n            <span className=\"text-sm font-medium text-foreground\">{label}</span>\n            <span className=\"relative h-4 overflow-hidden text-xs text-muted-foreground\">\n              <AnimatePresence mode=\"popLayout\" initial={false}>\n                <motion.span\n                  key={on ? \"on\" : \"off\"}\n                  className=\"block\"\n                  initial={{ y: on ? 14 : -14, opacity: 0 }}\n                  animate={{ y: 0, opacity: 1 }}\n                  exit={{ y: on ? -14 : 14, opacity: 0 }}\n                  transition={{ type: \"spring\", stiffness: 500, damping: 34 }}\n                >\n                  {on ? \"On\" : \"Muted\"}\n                </motion.span>\n              </AnimatePresence>\n            </span>\n          </span>\n        )}\n      </div>\n    </MotionConfig>\n  );\n}\n"}]}