mirror of
http://88.130.71.182:3000/BlitTech/contexta_mb.git
synced 2026-06-12 23:23:22 +00:00
133 lines
3.7 KiB
TypeScript
133 lines
3.7 KiB
TypeScript
import React, { useRef } from 'react';
|
|
import {
|
|
Animated,
|
|
TouchableWithoutFeedback,
|
|
Text,
|
|
StyleSheet,
|
|
ActivityIndicator,
|
|
ViewStyle,
|
|
TextStyle,
|
|
View,
|
|
} from 'react-native';
|
|
import { COLORS, RADIUS, SPACING, FONT_SIZE, FONT, SHADOWS } from '../../theme';
|
|
import { useTheme } from '../../theme';
|
|
|
|
type Variant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
type Size = 'sm' | 'md' | 'lg';
|
|
|
|
interface ButtonProps {
|
|
title: string;
|
|
onPress: () => void;
|
|
variant?: Variant;
|
|
size?: Size;
|
|
loading?: boolean;
|
|
disabled?: boolean;
|
|
style?: ViewStyle;
|
|
textStyle?: TextStyle;
|
|
fullWidth?: boolean;
|
|
}
|
|
|
|
export function Button({
|
|
title,
|
|
onPress,
|
|
variant = 'primary',
|
|
size = 'md',
|
|
loading = false,
|
|
disabled = false,
|
|
style,
|
|
textStyle,
|
|
fullWidth = false,
|
|
}: ButtonProps) {
|
|
const { theme, isDark } = useTheme();
|
|
const scale = useRef(new Animated.Value(1)).current;
|
|
|
|
const onPressIn = () =>
|
|
Animated.spring(scale, { toValue: 0.96, useNativeDriver: true, speed: 50, bounciness: 0 }).start();
|
|
|
|
const onPressOut = () =>
|
|
Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 30, bounciness: 4 }).start();
|
|
|
|
const containerBg = getContainerBg(variant, theme, isDark);
|
|
const labelColor = getLabelColor(variant);
|
|
const shadow = variant === 'primary' ? SHADOWS.primary : variant === 'danger' ? SHADOWS.sm : {};
|
|
|
|
return (
|
|
<TouchableWithoutFeedback
|
|
onPress={onPress}
|
|
onPressIn={onPressIn}
|
|
onPressOut={onPressOut}
|
|
disabled={disabled || loading}>
|
|
<Animated.View
|
|
style={[
|
|
styles.base,
|
|
styles[`size_${size}`],
|
|
containerBg,
|
|
shadow,
|
|
fullWidth && styles.fullWidth,
|
|
(disabled || loading) && styles.disabled,
|
|
{ transform: [{ scale }] },
|
|
style,
|
|
]}>
|
|
{loading ? (
|
|
<ActivityIndicator
|
|
size="small"
|
|
color={variant === 'primary' || variant === 'danger' ? COLORS.white : COLORS.primary}
|
|
/>
|
|
) : (
|
|
<Text style={[styles.label, styles[`label_${size}`], { color: labelColor }, textStyle]}>
|
|
{title}
|
|
</Text>
|
|
)}
|
|
</Animated.View>
|
|
</TouchableWithoutFeedback>
|
|
);
|
|
}
|
|
|
|
function getContainerBg(variant: Variant, theme: any, isDark: boolean): ViewStyle {
|
|
switch (variant) {
|
|
case 'primary':
|
|
return { backgroundColor: COLORS.primary };
|
|
case 'secondary':
|
|
return { backgroundColor: isDark ? theme.surfaceHover : theme.bgSecondary };
|
|
case 'outline':
|
|
return { backgroundColor: 'transparent', borderWidth: 1.5, borderColor: COLORS.primary };
|
|
case 'ghost':
|
|
return { backgroundColor: 'transparent' };
|
|
case 'danger':
|
|
return { backgroundColor: COLORS.error };
|
|
}
|
|
}
|
|
|
|
function getLabelColor(variant: Variant): string {
|
|
switch (variant) {
|
|
case 'primary':
|
|
case 'danger':
|
|
return COLORS.white;
|
|
case 'secondary':
|
|
return COLORS.primary;
|
|
case 'outline':
|
|
case 'ghost':
|
|
return COLORS.primary;
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
base: {
|
|
borderRadius: RADIUS.md,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
flexDirection: 'row',
|
|
},
|
|
fullWidth: { width: '100%' },
|
|
disabled: { opacity: 0.55 },
|
|
|
|
size_sm: { paddingVertical: SPACING.xs + 2, paddingHorizontal: SPACING.md, borderRadius: RADIUS.sm },
|
|
size_md: { paddingVertical: SPACING.md - 1, paddingHorizontal: SPACING.xl, borderRadius: RADIUS.md },
|
|
size_lg: { paddingVertical: SPACING.md + 1, paddingHorizontal: SPACING.xxl, borderRadius: RADIUS.lg, minHeight: 52 },
|
|
|
|
label: { fontWeight: FONT.semibold as any, letterSpacing: 0.1 },
|
|
label_sm: { fontSize: FONT_SIZE.sm },
|
|
label_md: { fontSize: FONT_SIZE.md },
|
|
label_lg: { fontSize: FONT_SIZE.md },
|
|
});
|