156 lines
3.2 KiB
TypeScript
156 lines
3.2 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
Pressable,
|
|
Text,
|
|
ActivityIndicator,
|
|
StyleSheet,
|
|
ViewStyle,
|
|
StyleProp,
|
|
} from 'react-native';
|
|
import { Colors, Spacing, BorderRadius, FontSize, Animation } from '@/constants/theme';
|
|
|
|
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'text';
|
|
type ButtonSize = 'small' | 'medium' | 'large';
|
|
|
|
interface ButtonProps {
|
|
title: string;
|
|
onPress: () => void;
|
|
variant?: ButtonVariant;
|
|
size?: ButtonSize;
|
|
disabled?: boolean;
|
|
loading?: boolean;
|
|
fullWidth?: boolean;
|
|
icon?: React.ReactNode;
|
|
style?: StyleProp<ViewStyle>;
|
|
}
|
|
|
|
const config = {
|
|
variants: {
|
|
primary: {
|
|
backgroundColor: Colors.brand.primary,
|
|
borderWidth: 0,
|
|
borderColor: 'transparent',
|
|
textColor: '#FFFFFF',
|
|
},
|
|
secondary: {
|
|
backgroundColor: Colors.brand.secondary,
|
|
borderWidth: 0,
|
|
borderColor: 'transparent',
|
|
textColor: '#FFFFFF',
|
|
},
|
|
outline: {
|
|
backgroundColor: 'transparent',
|
|
borderWidth: 1,
|
|
borderColor: Colors.brand.primary,
|
|
textColor: Colors.brand.primary,
|
|
},
|
|
text: {
|
|
backgroundColor: 'transparent',
|
|
borderWidth: 0,
|
|
borderColor: 'transparent',
|
|
textColor: Colors.brand.primary,
|
|
},
|
|
},
|
|
sizes: {
|
|
small: {
|
|
height: 36,
|
|
paddingHorizontal: Spacing.md,
|
|
fontSize: FontSize.sm,
|
|
},
|
|
medium: {
|
|
height: 48,
|
|
paddingHorizontal: Spacing.xl,
|
|
fontSize: FontSize.md,
|
|
},
|
|
large: {
|
|
height: 56,
|
|
paddingHorizontal: Spacing.xxl,
|
|
fontSize: FontSize.lg,
|
|
},
|
|
},
|
|
};
|
|
|
|
export const Button: React.FC<ButtonProps> = ({
|
|
title,
|
|
onPress,
|
|
variant = 'primary',
|
|
size = 'medium',
|
|
disabled = false,
|
|
loading = false,
|
|
fullWidth = false,
|
|
icon,
|
|
style,
|
|
}) => {
|
|
const variantConfig = config.variants[variant];
|
|
const sizeConfig = config.sizes[size];
|
|
const isInteractive = !disabled && !loading;
|
|
|
|
const buttonStyle = [
|
|
styles.base,
|
|
{
|
|
height: sizeConfig.height,
|
|
paddingHorizontal: sizeConfig.paddingHorizontal,
|
|
},
|
|
{
|
|
backgroundColor: variantConfig.backgroundColor,
|
|
borderWidth: variantConfig.borderWidth,
|
|
borderColor: variantConfig.borderColor,
|
|
opacity: disabled ? 0.5 : 1,
|
|
},
|
|
fullWidth && styles.fullWidth,
|
|
style,
|
|
];
|
|
|
|
const textStyle = [
|
|
styles.text,
|
|
{
|
|
color: variantConfig.textColor,
|
|
fontSize: sizeConfig.fontSize,
|
|
},
|
|
];
|
|
|
|
const indicatorColor = variant === 'outline' || variant === 'text'
|
|
? '#007AFF'
|
|
: '#FFFFFF';
|
|
|
|
return (
|
|
<Pressable
|
|
onPress={onPress}
|
|
disabled={!isInteractive}
|
|
style={({ pressed }) => [
|
|
buttonStyle,
|
|
isInteractive && pressed && styles.pressed,
|
|
]}
|
|
>
|
|
<>{icon}</>
|
|
{loading ? (
|
|
<ActivityIndicator size="small" color={indicatorColor} />
|
|
) : (
|
|
<Text style={textStyle}>{title}</Text>
|
|
)}
|
|
</Pressable>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
base: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: BorderRadius.full,
|
|
fontWeight: '600',
|
|
},
|
|
fullWidth: {
|
|
width: '100%',
|
|
},
|
|
pressed: {
|
|
transform: [{ scale: Animation.scale.pressed }],
|
|
},
|
|
text: {
|
|
fontWeight: '600',
|
|
textAlign: 'center',
|
|
},
|
|
});
|
|
|
|
export default Button;
|