Fortura/apps/www/public/r/headless-switch.json
2025-08-20 04:12:49 -06:00

19 lines
4.2 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "headless-switch",
"type": "registry:ui",
"title": "Headless Switch",
"description": "Switches are a pleasant interface for toggling a value between two states, and offer the same semantics and keyboard navigation as native checkbox elements.",
"dependencies": [
"@headlessui/react",
"motion"
],
"files": [
{
"path": "registry/headless/switch/index.tsx",
"content": "'use client';\n\nimport * as React from 'react';\nimport {\n Switch as SwitchPrimitive,\n type SwitchProps as SwitchPrimitiveProps,\n} from '@headlessui/react';\nimport { motion, type HTMLMotionProps } from 'motion/react';\n\nimport { cn } from '@/lib/utils';\n\ntype SwitchProps<TTag extends React.ElementType = typeof motion.button> =\n SwitchPrimitiveProps<TTag> &\n Omit<HTMLMotionProps<'button'>, 'children'> & {\n leftIcon?: React.ReactNode;\n rightIcon?: React.ReactNode;\n thumbIcon?: React.ReactNode;\n onCheckedChange?: (checked: boolean) => void;\n as?: TTag;\n };\n\nfunction Switch({\n className,\n leftIcon,\n rightIcon,\n thumbIcon,\n onChange,\n as = motion.button,\n ...props\n}: SwitchProps) {\n const [isChecked, setIsChecked] = React.useState(\n props.checked ?? props.defaultChecked ?? false,\n );\n const [isTapped, setIsTapped] = React.useState(false);\n\n React.useEffect(() => {\n setIsChecked(props.checked ?? props.defaultChecked ?? false);\n }, [props.checked, props.defaultChecked]);\n\n const handleChange = React.useCallback(\n (checked: boolean) => {\n setIsChecked(checked);\n onChange?.(checked);\n },\n [onChange],\n );\n\n return (\n <SwitchPrimitive\n data-slot=\"switch\"\n checked={isChecked}\n onChange={handleChange}\n className={cn(\n 'relative flex p-[3px] h-6 w-10 shrink-0 cursor-pointer items-center rounded-full transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[checked]:bg-primary bg-input data-[checked]:justify-end justify-start',\n className,\n )}\n as={as}\n whileTap=\"tap\"\n initial={false}\n onTapStart={() => setIsTapped(true)}\n onTapCancel={() => setIsTapped(false)}\n onTap={() => setIsTapped(false)}\n {...props}\n >\n {leftIcon && (\n <motion.div\n data-slot=\"switch-left-icon\"\n animate={\n isChecked ? { scale: 1, opacity: 1 } : { scale: 0, opacity: 0 }\n }\n transition={{ type: 'spring', bounce: 0 }}\n className=\"absolute [&_svg]:size-3 left-1 top-1/2 -translate-y-1/2 dark:text-neutral-500 text-neutral-400\"\n >\n {typeof leftIcon !== 'string' ? leftIcon : null}\n </motion.div>\n )}\n\n {rightIcon && (\n <motion.div\n data-slot=\"switch-right-icon\"\n animate={\n isChecked ? { scale: 0, opacity: 0 } : { scale: 1, opacity: 1 }\n }\n transition={{ type: 'spring', bounce: 0 }}\n className=\"absolute [&_svg]:size-3 right-1 top-1/2 -translate-y-1/2 dark:text-neutral-400 text-neutral-500\"\n >\n {typeof rightIcon !== 'string' ? rightIcon : null}\n </motion.div>\n )}\n\n <motion.span\n data-slot=\"switch-thumb\"\n whileTap=\"tab\"\n className={cn(\n 'relative z-[1] [&_svg]:size-3 flex items-center justify-center rounded-full bg-background shadow-lg ring-0 dark:text-neutral-400 text-neutral-500',\n )}\n layout\n transition={{ type: 'spring', stiffness: 300, damping: 25 }}\n style={{\n width: 18,\n height: 18,\n }}\n animate={\n isTapped\n ? { width: 21, transition: { duration: 0.1 } }\n : { width: 18, transition: { duration: 0.1 } }\n }\n >\n {thumbIcon && typeof thumbIcon !== 'string' ? thumbIcon : null}\n </motion.span>\n </SwitchPrimitive>\n );\n}\n\nexport { Switch, type SwitchProps };\n",
"type": "registry:ui",
"target": "components/animate-ui/headless/switch.tsx"
}
]
}