import { tva } from '@gluestack-ui/nativewind-utils/tva' import { VariantProps } from '@gluestack-ui/nativewind-utils/types' import { ComponentRef, FC, forwardRef, useEffect, useState } from 'react' import { Pressable, TouchableOpacity } from 'react-native' import Animated, { Extrapolation, interpolate, interpolateColor, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated' import { variables } from '@/figma-vars' const sizes = { md: { switchWidth: variables['[selectors]']['switch-track-width'], switchBorderSize: variables['[selectors]']['switch-track-border-size'], switchThumbContainerSize: variables['[selectors]']['switch-thumb-size-false'], switchThumbContainerOffset: variables['[selectors]']['switch-track-padding-left'], }, lg: { switchWidth: variables['[selectors]']['lg-switch-track-width'], switchBorderSize: variables['[selectors]']['lg-switch-track-border-size'], switchThumbContainerSize: variables['[selectors]']['lg-switch-thumb-size-false'], switchThumbContainerOffset: variables['[selectors]']['lg-switch-track-padding-left'], }, } const switchStyles = tva({ base: 'justify-center border-[var(--color-selector-border)] bg-[var(--color-selector-track-background)]', variants: { size: { md: 'h-[var(--switch-track-height)] w-[var(--switch-track-width)] rounded-[var(--switch-track-radius)] border-[length:var(--switch-track-border-size)]', lg: 'h-[var(--lg-switch-track-height)] w-[var(--lg-switch-track-width)] rounded-[var(--lg-switch-track-radius)] border-[length:var(--lg-switch-track-border-size)]', }, }, }) const switchThumbStyles = tva({ base: 'rounded-full border-[length:var(--switch-thumb-border-size)] border-[var(--color-selector-border-thumb-false)] bg-[var(--color-selector-thumb-false)]', variants: { size: { md: 'h-[var(--switch-thumb-size-false)] w-[var(--switch-thumb-size-false)]', lg: 'h-[var(--lg-switch-thumb-size-false)] w-[var(--lg-switch-thumb-size-false)]', }, }, }) type SwitchProps = VariantProps & { isChecked?: boolean value?: string | number onChange?: (isChecked: boolean) => void className?: string isDisabled?: boolean } const AnimatedSwitch: FC = ({ size = 'md', isDisabled, isChecked, pressed }) => { const isOn = useSharedValue(isChecked ? 1 : 0) useEffect(() => { isOn.value = withTiming(pressed ? 0.5 : isChecked ? 1 : 0, { duration: 200 }) }, [isOn, isChecked, pressed]) const { switchWidth, switchThumbContainerSize, switchThumbContainerOffset, switchBorderSize } = sizes[size] const animatedTrackStyle = useAnimatedStyle(() => { return { backgroundColor: interpolateColor( isOn.value, [0, 0.5, 1], [ isDisabled ? variables['theme-color']['disabled'] : variables['theme-color']['selector-track-background'], variables['theme-color']['selector-accent-onTap'], isDisabled ? variables['theme-color']['disabled-dark'] : variables['theme-color']['selector-accent-true'], ], ), borderColor: interpolateColor( isOn.value, [0, 0.5, 1], [ isDisabled ? variables['theme-color']['disabled-border'] : variables['theme-color']['selector-border'], variables['theme-color']['selector-border-onTap'], isDisabled ? variables['theme-color']['disabled-border-ondark'] : variables['theme-color']['selector-border-active'], ], ), } }, [isDisabled]) const animatedThumbStyle = useAnimatedStyle(() => { const inactiveOffset = Number(switchThumbContainerOffset) const activeOffset = Number(switchWidth) - Number(switchThumbContainerSize) - Number(switchThumbContainerOffset) - Number(switchBorderSize) * 2 const middleOffset = inactiveOffset + (activeOffset - inactiveOffset) / 2 return { transform: [ { translateX: interpolate( isOn.value, [0, 0.5, 1], [inactiveOffset, middleOffset, activeOffset], Extrapolation.CLAMP, ), }, ], } }, [switchWidth, switchThumbContainerOffset, switchThumbContainerSize, switchBorderSize]) return ( ) } const Switch = forwardRef, SwitchProps>(function Switch( { className, size = 'md', onChange, ...props }, ref, ) { const [isCheckedLocally, setIsCheckedLocally] = useState(false) const isChecked = props.isChecked ?? isCheckedLocally const handleChange = () => { if (onChange) { onChange(!isChecked) } else { setIsCheckedLocally(!isChecked) } } return ( {({ pressed }) => } ) }) Switch.displayName = 'Switch' export { Switch }