Files
beenvoice-app/components/ui/Button.tsx
T

136 lines
3.0 KiB
TypeScript

import {
ActivityIndicator,
Pressable,
StyleSheet,
Text,
View,
type PressableProps,
type ViewStyle,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useAppTheme } from "@/contexts/ThemeContext";
import { fonts, radii, spacing } from "@/constants/theme";
type ButtonProps = PressableProps & {
title: string;
loading?: boolean;
variant?: "primary" | "secondary" | "danger" | "ghost";
style?: ViewStyle;
leftIcon?: keyof typeof Ionicons.glyphMap;
showArrow?: boolean;
};
export function Button({
title,
loading,
variant = "primary",
disabled,
style,
leftIcon,
showArrow = false,
...props
}: ButtonProps) {
const { colors } = useAppTheme();
const isDisabled = disabled || loading;
const variantStyles = {
primary: { backgroundColor: colors.primary },
secondary: {
backgroundColor: colors.muted,
borderWidth: 1,
borderColor: colors.border,
},
danger: {
backgroundColor: colors.destructiveBg,
borderWidth: 1,
borderColor: colors.destructive,
},
ghost: {
backgroundColor: colors.cardGlass,
borderWidth: 1,
borderColor: colors.borderGlass,
},
} as const;
const labelStyles = {
primary: { color: colors.primaryForeground },
secondary: { color: colors.foreground },
danger: { color: colors.destructive },
ghost: { color: colors.foreground },
} as const;
return (
<Pressable
accessibilityRole="button"
disabled={isDisabled}
style={({ pressed }) => [
styles.base,
variantStyles[variant],
pressed && !isDisabled && styles.pressed,
isDisabled && styles.disabled,
style,
]}
{...props}
>
{loading ? (
<ActivityIndicator
color={variant === "primary" ? colors.primaryForeground : colors.primary}
/>
) : (
<View style={styles.content}>
{leftIcon ? (
<Ionicons
name={leftIcon}
size={16}
color={labelStyles[variant].color}
/>
) : null}
<Text style={[styles.label, labelStyles[variant]]} numberOfLines={1}>
{title}
</Text>
{showArrow ? (
<Ionicons
name="arrow-forward"
size={16}
color={labelStyles[variant].color}
style={styles.arrow}
/>
) : null}
</View>
)}
</Pressable>
);
}
const styles = StyleSheet.create({
base: {
minHeight: 44,
borderRadius: radii.lg,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: spacing.md,
},
content: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: spacing.sm,
minWidth: 0,
},
arrow: {
marginTop: 1,
},
pressed: {
opacity: 0.92,
},
disabled: {
opacity: 0.55,
},
label: {
fontSize: 14,
fontFamily: fonts.bodyMedium,
flexShrink: 1,
},
});