Files
gridpilot.gg/apps/website/ui/Toggle.tsx
2026-01-14 23:46:04 +01:00

69 lines
2.1 KiB
TypeScript

import { motion } from 'framer-motion';
import { Box } from './Box';
import { Text } from './Text';
interface ToggleProps {
checked: boolean;
onChange: (checked: boolean) => void;
label: string;
description?: string;
disabled?: boolean;
}
export function Toggle({
checked,
onChange,
label,
description,
disabled = false,
}: ToggleProps) {
return (
<label className={`flex items-start justify-between cursor-pointer py-3 border-b border-charcoal-outline/50 last:border-b-0 ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}>
<Box style={{ flex: 1, paddingRight: '1rem' }}>
<Text weight="medium" color="text-gray-200" block>{label}</Text>
{description && (
<Text size="sm" color="text-gray-500" block mt={1}>{description}</Text>
)}
</Box>
<button
type="button"
role="switch"
aria-checked={checked}
onClick={() => !disabled && onChange(!checked)}
disabled={disabled}
className={`relative w-12 h-6 rounded-full transition-colors duration-200 flex-shrink-0 focus:outline-none focus:ring-2 focus:ring-primary-blue/50 ${
checked
? 'bg-primary-blue'
: 'bg-iron-gray'
} ${disabled ? 'cursor-not-allowed' : ''}`}
>
{/* Glow effect when active */}
{checked && (
<motion.div
className="absolute inset-0 rounded-full bg-primary-blue"
initial={{ boxShadow: '0 0 0px rgba(25, 140, 255, 0)' }}
animate={{ boxShadow: '0 0 12px rgba(25, 140, 255, 0.4)' }}
transition={{ duration: 0.2 }}
/>
)}
{/* Knob */}
<motion.span
className="absolute top-0.5 w-5 h-5 bg-white rounded-full shadow-md"
initial={false}
animate={{
x: checked ? 24 : 2,
scale: 1,
}}
whileTap={{ scale: disabled ? 1 : 0.9 }}
transition={{
type: 'spring',
stiffness: 500,
damping: 30,
}}
/>
</button>
</label>
);
}