@metamask/design-system-react-native
Version:
101 lines • 4.32 kB
JavaScript
function $importDefault(module) {
if (module?.__esModule) {
return module.default;
}
return module;
}
import { useTailwind } from "@metamask/design-system-twrnc-preset";
import $React, { forwardRef, useRef, useImperativeHandle, useCallback, useEffect } from "react";
const React = $importDefault($React);
import { Pressable, Animated, Easing } from "react-native/index.js";
import { Icon, IconName, IconColor, IconSize } from "../Icon/index.mjs";
import { TextOrChildren } from "../temp-components/TextOrChildren/index.mjs";
const AnimatedView = Animated.View;
export const Checkbox = forwardRef(({ isSelected, isDisabled = false, isInvalid = false, label = '', labelProps, onChange, checkboxContainerProps, checkedIconProps, twClassName = '', style, ...props }, ref) => {
// Animation values
const scaleAnim = useRef(new Animated.Value(1)).current;
const iconAnim = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
// Sync icon opacity whenever selection changes
useEffect(() => {
Animated.timing(iconAnim, {
toValue: isSelected ? 1 : 0,
duration: 300,
easing: Easing.ease,
useNativeDriver: true,
}).start();
}, [isSelected, iconAnim]);
// Bounce effect when toggling
const animateScale = useCallback(() => {
Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 1.15,
duration: 100,
easing: Easing.ease,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 100,
easing: Easing.ease,
useNativeDriver: true,
}),
]).start();
}, [scaleAnim]);
// Press handler: notify parent, then bounce
const handlePress = () => {
if (isDisabled) {
return;
}
onChange?.(!isSelected);
animateScale();
};
useImperativeHandle(ref, () => ({ toggle: handlePress }), [handlePress]);
const tw = useTailwind();
const twContainerClassNames = tw `flex-row items-center ${isDisabled ? 'opacity-50' : 'opacity-100'} ${twClassName}`;
const getCheckboxContainerStyle = useCallback((pressed) => {
const baseBg = isSelected ? 'bg-primary-default' : 'bg-default';
let baseBorder = 'border-default';
if (isSelected) {
baseBorder = 'border-primary-default';
}
else if (isInvalid) {
baseBorder = 'border-error-default';
}
const pressedBg = isSelected
? 'bg-primary-default-pressed'
: 'bg-default-pressed';
let pressedBorder = 'border-default';
if (isSelected) {
pressedBorder = 'border-primary-default-pressed';
}
else if (isInvalid) {
pressedBorder = 'border-error-default';
}
return pressed
? `${pressedBg} ${pressedBorder}`
: `${baseBg} ${baseBorder}`;
}, [isSelected, isInvalid]);
return (<Pressable onPress={handlePress} accessible accessibilityRole="checkbox" accessibilityState={{
checked: isSelected,
disabled: isDisabled,
}} accessibilityLabel={typeof label === 'string' ? label : undefined} style={(state) => [
twContainerClassNames,
typeof style === 'function' ? style(state) : style,
]} disabled={isDisabled} {...props}>
{({ pressed }) => (<>
<AnimatedView {...checkboxContainerProps} style={[
tw `${getCheckboxContainerStyle(pressed)} flex size-[22px] items-center justify-center rounded border-2`,
{ transform: [{ scale: scaleAnim }] },
]}>
{/* Always render icon, opacity driven by iconAnim */}
<Animated.View style={{ opacity: iconAnim }}>
<Icon name={IconName.Check} color={IconColor.PrimaryInverse} size={IconSize.Sm} {...checkedIconProps}/>
</Animated.View>
</AnimatedView>
{label ? (<TextOrChildren textProps={{ ...labelProps, twClassName: 'ml-3' }}>
{label}
</TextOrChildren>) : null}
</>)}
</Pressable>);
});
//# sourceMappingURL=Checkbox.mjs.map