- Voice tab: simplified interface, voice picker improvements - Subscription: Stripe integration, purchase flow updates - Beneficiaries: dashboard, sharing, improved management - Profile: drawer, edit, help, privacy sections - Theme: expanded constants, new colors - New components: MockDashboard, ProfileDrawer, Toast - Backend: Stripe routes additions - Auth: activate, add-loved-one, purchase screens
201 lines
4.3 KiB
TypeScript
201 lines
4.3 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
TouchableOpacity,
|
|
Text,
|
|
StyleSheet,
|
|
ActivityIndicator,
|
|
View,
|
|
type TouchableOpacityProps,
|
|
type ViewStyle,
|
|
type TextStyle,
|
|
} from 'react-native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, Shadows } from '@/constants/theme';
|
|
|
|
interface ButtonProps extends TouchableOpacityProps {
|
|
title: string;
|
|
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
size?: 'sm' | 'md' | 'lg';
|
|
loading?: boolean;
|
|
fullWidth?: boolean;
|
|
icon?: keyof typeof Ionicons.glyphMap;
|
|
iconPosition?: 'left' | 'right';
|
|
}
|
|
|
|
export function Button({
|
|
title,
|
|
variant = 'primary',
|
|
size = 'md',
|
|
loading = false,
|
|
fullWidth = false,
|
|
icon,
|
|
iconPosition = 'left',
|
|
disabled,
|
|
style,
|
|
...props
|
|
}: ButtonProps) {
|
|
const isDisabled = disabled || loading;
|
|
|
|
const buttonStyles: ViewStyle[] = [
|
|
styles.base,
|
|
styles[variant],
|
|
styles[`size_${size}`],
|
|
fullWidth && styles.fullWidth,
|
|
isDisabled && styles.disabled,
|
|
variant === 'primary' && !isDisabled && Shadows.primary,
|
|
style as ViewStyle,
|
|
];
|
|
|
|
const textStyles: TextStyle[] = [
|
|
styles.text,
|
|
styles[`text_${variant}`],
|
|
styles[`text_${size}`],
|
|
isDisabled && styles.textDisabled,
|
|
];
|
|
|
|
const iconSize = size === 'sm' ? 16 : size === 'lg' ? 22 : 18;
|
|
const iconColor = variant === 'primary' || variant === 'danger'
|
|
? AppColors.white
|
|
: variant === 'secondary'
|
|
? AppColors.textPrimary
|
|
: AppColors.primary;
|
|
|
|
const renderContent = () => {
|
|
if (loading) {
|
|
return (
|
|
<ActivityIndicator
|
|
color={variant === 'primary' || variant === 'danger' ? AppColors.white : AppColors.primary}
|
|
size="small"
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={styles.content}>
|
|
{icon && iconPosition === 'left' && (
|
|
<Ionicons
|
|
name={icon}
|
|
size={iconSize}
|
|
color={isDisabled ? AppColors.textDisabled : iconColor}
|
|
style={styles.iconLeft}
|
|
/>
|
|
)}
|
|
<Text style={textStyles}>{title}</Text>
|
|
{icon && iconPosition === 'right' && (
|
|
<Ionicons
|
|
name={icon}
|
|
size={iconSize}
|
|
color={isDisabled ? AppColors.textDisabled : iconColor}
|
|
style={styles.iconRight}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={buttonStyles}
|
|
disabled={isDisabled}
|
|
activeOpacity={0.8}
|
|
{...props}
|
|
>
|
|
{renderContent()}
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
base: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: BorderRadius.lg,
|
|
},
|
|
content: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
primary: {
|
|
backgroundColor: AppColors.primary,
|
|
},
|
|
secondary: {
|
|
backgroundColor: AppColors.surfaceSecondary,
|
|
borderWidth: 1,
|
|
borderColor: AppColors.border,
|
|
},
|
|
outline: {
|
|
backgroundColor: 'transparent',
|
|
borderWidth: 1.5,
|
|
borderColor: AppColors.primary,
|
|
},
|
|
ghost: {
|
|
backgroundColor: 'transparent',
|
|
},
|
|
danger: {
|
|
backgroundColor: AppColors.error,
|
|
},
|
|
size_sm: {
|
|
paddingVertical: Spacing.sm,
|
|
paddingHorizontal: Spacing.md,
|
|
minHeight: 40,
|
|
borderRadius: BorderRadius.md,
|
|
},
|
|
size_md: {
|
|
paddingVertical: Spacing.sm + 4,
|
|
paddingHorizontal: Spacing.lg,
|
|
minHeight: 48,
|
|
},
|
|
size_lg: {
|
|
paddingVertical: Spacing.md,
|
|
paddingHorizontal: Spacing.xl,
|
|
minHeight: 56,
|
|
borderRadius: BorderRadius.xl,
|
|
},
|
|
fullWidth: {
|
|
width: '100%',
|
|
},
|
|
disabled: {
|
|
opacity: 0.5,
|
|
shadowOpacity: 0,
|
|
},
|
|
text: {
|
|
fontWeight: FontWeights.semibold,
|
|
letterSpacing: 0.3,
|
|
},
|
|
text_primary: {
|
|
color: AppColors.white,
|
|
},
|
|
text_secondary: {
|
|
color: AppColors.textPrimary,
|
|
},
|
|
text_outline: {
|
|
color: AppColors.primary,
|
|
},
|
|
text_ghost: {
|
|
color: AppColors.primary,
|
|
},
|
|
text_danger: {
|
|
color: AppColors.white,
|
|
},
|
|
text_sm: {
|
|
fontSize: FontSizes.sm,
|
|
},
|
|
text_md: {
|
|
fontSize: FontSizes.base,
|
|
},
|
|
text_lg: {
|
|
fontSize: FontSizes.lg,
|
|
},
|
|
textDisabled: {
|
|
color: AppColors.textDisabled,
|
|
},
|
|
iconLeft: {
|
|
marginRight: Spacing.sm,
|
|
},
|
|
iconRight: {
|
|
marginLeft: Spacing.sm,
|
|
},
|
|
});
|