75 lines
2.1 KiB
TypeScript
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>
|
|
);
|
|
}
|