import { LinearGradient } from 'expo-linear-gradient'; import type { LucideIcon } from 'lucide-react-native'; import { useCallback, useMemo, useState } from 'react'; import { ActivityIndicator, type GestureResponderEvent, Pressable, type StyleProp, StyleSheet, Text, type TextStyle, View, type ViewStyle, } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import { colors, gradientDirection, gradients } from '@/theme/colors'; import { borderRadius } from '@/theme/spacing'; import { typography } from '@/theme/typography'; const BUTTON_HEIGHT = 52; const PRESSED_SCALE = 0.97; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'destructive'; type IconOnlyShape = 'round' | 'default'; type ButtonProps = { label?: string; variant?: ButtonVariant; disabled?: boolean; loading?: boolean; loadingLabel?: string; autoLoading?: boolean; iconOnly?: boolean; iconOnlyShape?: IconOnlyShape; icon?: LucideIcon; leftIcon?: LucideIcon; rightIcon?: LucideIcon; iconSize?: number; iconStrokeWidth?: number; fullWidth?: boolean; onPress?: (event: GestureResponderEvent) => void | Promise; style?: StyleProp; textStyle?: StyleProp; accessibilityLabel?: string; testID?: string; }; export function Button({ label, variant = 'primary', disabled = false, loading = false, loadingLabel, autoLoading = false, iconOnly = false, iconOnlyShape = 'round', icon, leftIcon, rightIcon, iconSize = 20, iconStrokeWidth = 2, fullWidth = true, onPress, style, textStyle, accessibilityLabel, testID, }: ButtonProps) { const [internalLoading, setInternalLoading] = useState(false); const [isPressed, setIsPressed] = useState(false); const isLoading = loading || internalLoading; const isDisabled = disabled || isLoading; const scale = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], })); const handlePressIn = useCallback(() => { if (isDisabled) { return; } setIsPressed(true); scale.value = withTiming(PRESSED_SCALE, { duration: 120 }); }, [isDisabled, scale]); const handlePressOut = useCallback(() => { setIsPressed(false); scale.value = withTiming(1, { duration: 140 }); }, [scale]); const handlePress = useCallback( async (event: GestureResponderEvent) => { if (!onPress || isDisabled) { return; } const result = onPress(event); if (!autoLoading || !(result instanceof Promise)) { return; } setInternalLoading(true); try { await result; } finally { setInternalLoading(false); } }, [autoLoading, isDisabled, onPress], ); const labelColor = useMemo(() => { if (isDisabled) { return colors.textDisabled; } if (variant === 'primary') { return colors.textPrimary; } if (variant === 'destructive') { return colors.danger; } if (variant === 'ghost') { return colors.tertiary; } return colors.textPrimary; }, [colors, isDisabled, variant]); const loadingColor = variant === 'primary' && !isDisabled ? colors.textPrimary : labelColor; const iconOnlyRadius = iconOnlyShape === 'round' ? BUTTON_HEIGHT / 2 : borderRadius.button; const sideIconColor = !isDisabled && variant === 'secondary' ? colors.tertiary : labelColor; const iconOnlyColor = labelColor; const IconOnly = icon; const LeftIcon = leftIcon; const RightIcon = rightIcon; const containerStyle: ViewStyle = { width: fullWidth && !iconOnly ? '100%' : BUTTON_HEIGHT, height: BUTTON_HEIGHT, borderRadius: iconOnly ? iconOnlyRadius : borderRadius.button, overflow: 'hidden', }; const baseFillStyle: ViewStyle = { flex: 1, alignItems: 'center', justifyContent: 'center', flexDirection: 'row', gap: 8, borderRadius: iconOnly ? iconOnlyRadius : borderRadius.button, }; const variantFillStyle: ViewStyle = (() => { if (isDisabled) { return { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, }; } if (variant === 'secondary') { return { backgroundColor: colors.surfaceElevated, borderWidth: 1, borderColor: colors.border, }; } if (variant === 'ghost') { return { backgroundColor: 'transparent', }; } if (variant === 'destructive') { return { backgroundColor: 'rgba(255, 77, 109, 0.08)', borderWidth: 1, borderColor: colors.danger, }; } return { backgroundColor: colors.surface, }; })(); const contentPaddingStyle: ViewStyle = { paddingHorizontal: iconOnly ? 0 : 24, }; const textStyles: TextStyle = { ...typography.button, color: labelColor, }; const content = ( <> {isLoading ? : null} {!isLoading && iconOnly && IconOnly ? ( ) : null} {!iconOnly && !isLoading && LeftIcon ? ( ) : null} {!iconOnly && label ? {isLoading && loadingLabel ? loadingLabel : label} : null} {!iconOnly && !isLoading && RightIcon ? ( ) : null} ); const fillNode = variant === 'primary' && !isDisabled ? ( {content} ) : ( {content} ); return ( {fillNode} ); } export default Button; const styles = StyleSheet.create({ root: { alignSelf: 'stretch', }, });