diff options
| author | Lucas Faria Mendes <lucas.fariamo08@gmail.com> | 2026-08-14 18:06:35 +0000 |
|---|---|---|
| committer | Lucas Faria Mendes <lucas.fariamo08@gmail.com> | 2026-08-14 18:06:35 +0000 |
| commit | 2c2dfba45357bbd205a59145ae55a2779c911ae0 (patch) | |
| tree | 0736b848aed56ec2da3c2489c49da0519447703d /src/components/ui/button.tsx | |
| download | yaba-2c2dfba45357bbd205a59145ae55a2779c911ae0.tar.gz yaba-2c2dfba45357bbd205a59145ae55a2779c911ae0.zip | |
remake
Signed-off-by: Lucas Faria Mendes <lucas.fariamo08@gmail.com>
Diffstat (limited to 'src/components/ui/button.tsx')
| -rw-r--r-- | src/components/ui/button.tsx | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..3486ff7 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,141 @@ +import React from 'react'; +import { + ActivityIndicator, + StyleSheet, + Text, + TouchableOpacity, + type TouchableOpacityProps, + View, +} from 'react-native'; +import Animated, { + useAnimatedStyle, + useSharedValue, + withSpring, +} from 'react-native-reanimated'; +import { Colors, Radius, Spacing, Typography } from '@/constants/theme'; + +const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity); + +interface ButtonProps extends TouchableOpacityProps { + label: string; + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + loading?: boolean; + fullWidth?: boolean; +} + +export function Button({ + label, + variant = 'primary', + loading = false, + fullWidth = false, + onPress, + disabled, + style, + ...rest +}: ButtonProps) { + const scale = useSharedValue(1); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ scale: scale.value }], + })); + + function handlePressIn() { + scale.value = withSpring(0.96, { damping: 15, stiffness: 400 }); + } + + function handlePressOut() { + scale.value = withSpring(1, { damping: 15, stiffness: 400 }); + } + + const containerStyle = [ + styles.base, + styles[variant], + fullWidth && styles.fullWidth, + (disabled || loading) && styles.disabled, + style, + ]; + + const textColor = + variant === 'secondary' || variant === 'ghost' + ? Colors.textPrimary + : Colors.white; + + return ( + <AnimatedTouchable + style={[animatedStyle, containerStyle]} + onPress={onPress} + onPressIn={handlePressIn} + onPressOut={handlePressOut} + disabled={disabled || loading} + activeOpacity={1} + {...rest} + > + {loading ? ( + <ActivityIndicator color={textColor} size="small" /> + ) : ( + <Text style={[styles.label, { color: textColor }]}>{label}</Text> + )} + </AnimatedTouchable> + ); +} + +interface IconButtonProps { + onPress: () => void; + children: React.ReactNode; + accessibilityLabel: string; +} + +export function IconButton({ onPress, children, accessibilityLabel }: IconButtonProps) { + return ( + <TouchableOpacity + style={styles.iconButton} + onPress={onPress} + hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} + accessibilityLabel={accessibilityLabel} + accessibilityRole="button" + > + <View>{children}</View> + </TouchableOpacity> + ); +} + +const styles = StyleSheet.create({ + base: { + height: 52, + borderRadius: Radius.md, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: Spacing.lg, + minWidth: 44, // touch target mÃnimo + }, + primary: { + backgroundColor: Colors.primary, + }, + secondary: { + backgroundColor: Colors.surfaceElevated, + borderWidth: 1, + borderColor: Colors.border, + }, + ghost: { + backgroundColor: Colors.transparent, + }, + danger: { + backgroundColor: Colors.negative, + }, + fullWidth: { + width: '100%', + }, + disabled: { + opacity: 0.5, + }, + label: { + ...Typography.bodyLarge, + fontFamily: 'DMSans_600SemiBold', + }, + iconButton: { + width: 44, + height: 44, + alignItems: 'center', + justifyContent: 'center', + }, +}); |