aboutsummaryrefslogtreecommitdiff
path: root/src/components/ui/button.tsx
diff options
context:
space:
mode:
authorLucas Faria Mendes <lucas.fariamo08@gmail.com>2026-08-14 18:06:35 +0000
committerLucas Faria Mendes <lucas.fariamo08@gmail.com>2026-08-14 18:06:35 +0000
commit2c2dfba45357bbd205a59145ae55a2779c911ae0 (patch)
tree0736b848aed56ec2da3c2489c49da0519447703d /src/components/ui/button.tsx
downloadyaba-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.tsx141
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',
+ },
+});