Files
gridpilot.gg/apps/website/ui/Toggle.tsx
2026-01-18 17:55:04 +01:00

75 lines
2.1 KiB
TypeScript

import React from 'react';
import { Box } from './primitives/Box';
import { Text } from './Text';
import { motion } from 'framer-motion';
interface ToggleProps {
label: string;
description?: string;
checked: boolean;
onChange: (checked: boolean) => void;
disabled?: boolean;
}
export function Toggle({ label, description, checked, onChange, disabled }: ToggleProps) {
return (
<Box
as="label"
display="flex"
alignItems="start"
justifyContent="between"
cursor={disabled ? 'not-allowed' : 'pointer'}
py={3}
borderBottom
borderColor="border-charcoal-outline/50"
className="last:border-b-0"
opacity={disabled ? 0.5 : 1}
>
<Box flex={1} pr={4}>
<Text weight="medium" color="text-gray-200" block>{label}</Text>
{description && (
<Text size="xs" color="text-gray-500" block mt={1}>
{description}
</Text>
)}
</Box>
<Box position="relative">
<Box
as="button"
type="button"
role="switch"
aria-checked={checked}
onClick={() => !disabled && onChange(!checked)}
disabled={disabled}
w="12"
h="6"
rounded="full"
transition="all 0.2s"
flexShrink={0}
ring="primary-blue/50"
bg={checked ? 'bg-primary-blue/20' : 'bg-charcoal-outline'}
className="focus:outline-none focus:ring-2"
>
<motion.div
className="absolute inset-0 rounded-full bg-primary-blue"
initial={{ boxShadow: '0 0 0px rgba(25, 140, 255, 0)' }}
animate={{
opacity: checked ? 1 : 0,
boxShadow: checked ? '0 0 10px rgba(25, 140, 255, 0.4)' : '0 0 0px rgba(25, 140, 255, 0)'
}}
/>
</Box>
<motion.span
className="absolute top-0.5 w-5 h-5 bg-white rounded-full shadow-md"
initial={false}
animate={{
left: checked ? '26px' : '2px',
}}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>
</Box>
</Box>
);
}