mojmajstor/components/ui/button.tsx
2026-06-04 20:37:09 +02:00

96 lines
2.7 KiB
TypeScript

import { Pressable, Text, ActivityIndicator, type ViewStyle } from "react-native";
import { useTheme } from "../theme";
type Variant = "primary" | "secondary" | "outline" | "ghost" | "destructive";
interface ButtonProps {
variant?: Variant;
loading?: boolean;
size?: "sm" | "md" | "lg";
disabled?: boolean;
fullWidth?: boolean;
onPress?: () => void;
children: React.ReactNode;
style?: ViewStyle;
accessibilityLabel?: string;
}
export function Button({
variant = "primary",
loading = false,
size = "md",
disabled = false,
fullWidth = false,
children,
style,
onPress,
accessibilityLabel,
}: ButtonProps) {
const theme = useTheme();
const sizeStyles: Record<string, ViewStyle> = {
sm: { paddingVertical: 8, paddingHorizontal: theme.spacing.md, borderRadius: theme.radius.sm },
md: { paddingVertical: 14, paddingHorizontal: theme.spacing.lg, borderRadius: theme.radius.md },
lg: { paddingVertical: 18, paddingHorizontal: theme.spacing.xl, borderRadius: theme.radius.lg },
};
const variantStyles: Record<Variant, ViewStyle> = {
primary: { backgroundColor: theme.colors.primary, ...theme.shadows.button },
secondary: { backgroundColor: theme.colors.surface, borderWidth: 1.5, borderColor: theme.colors.border },
outline: { backgroundColor: "transparent", borderWidth: 1.5, borderColor: theme.colors.primary },
ghost: { backgroundColor: "transparent" },
destructive: { backgroundColor: theme.colors.error },
};
const textColors: Record<Variant, string> = {
primary: theme.colors.textInverse,
secondary: theme.colors.text,
outline: theme.colors.primary,
ghost: theme.colors.primary,
destructive: "#FFFFFF",
};
const label = typeof children === "string" ? children : accessibilityLabel;
return (
<Pressable
accessible
accessibilityLabel={label}
accessibilityRole="button"
accessibilityState={{ disabled: disabled || loading }}
disabled={disabled || loading}
onPress={onPress}
style={[
{
alignItems: "center",
justifyContent: "center",
flexDirection: "row",
gap: theme.spacing.sm,
},
sizeStyles[size],
variantStyles[variant],
fullWidth && { width: "100%" },
disabled && { opacity: 0.5 },
style,
]}
>
{loading ? (
<ActivityIndicator color={textColors[variant]} size="small" />
) : typeof children === "string" ? (
<Text
style={{
color: textColors[variant],
fontWeight: "600",
fontSize: size === "sm" ? 14 : 16,
letterSpacing: 0.3,
}}
>
{children}
</Text>
) : (
children
)}
</Pressable>
);
}