import type { ReactNode } from "react"; import { Ionicons } from "@expo/vector-icons"; import { ActivityIndicator, Pressable, View, type PressableProps, type StyleProp, type ViewStyle, } from "react-native"; import { Text } from "@/components/ui/Text"; import { colors, radii, shadows } from "@/lib/theme"; type Variant = "primary" | "brand" | "dark" | "outline" | "ghost" | "soft" | "danger"; type Size = "sm" | "md" | "lg"; type Props = Omit & { title: string; variant?: Variant; size?: Size; icon?: keyof typeof Ionicons.glyphMap; iconRight?: keyof typeof Ionicons.glyphMap; loading?: boolean; fullWidth?: boolean; elevated?: boolean; style?: StyleProp; }; const skins: Record = { // Lime fill with ink text: on-brand and far more legible than white-on-lime. primary: { bg: colors.brand, fg: colors.ink, border: "transparent" }, brand: { bg: colors.brand, fg: colors.ink, border: "transparent" }, dark: { bg: colors.ink, fg: colors.surface, border: "transparent" }, outline: { bg: "transparent", fg: colors.ink, border: colors.line }, ghost: { bg: "transparent", fg: colors.ink, border: "transparent" }, soft: { bg: colors.brandSoft, fg: colors.brandText, border: "transparent" }, danger: { bg: colors.dangerSoft, fg: colors.danger, border: "transparent" }, }; const sizing: Record = { sm: { py: 9, px: 14, gap: 6, icon: 15 }, md: { py: 13, px: 18, gap: 8, icon: 17 }, lg: { py: 16, px: 22, gap: 9, icon: 19 }, }; export function Button({ title, variant = "primary", size = "lg", icon, iconRight, loading, disabled, fullWidth = true, elevated, style, ...rest }: Props) { const skin = skins[variant]; const dims = sizing[size]; const inactive = disabled || loading; return ( [ { backgroundColor: inactive ? colors.surfaceAlt : skin.bg, borderColor: skin.border, borderWidth: variant === "outline" ? 1.5 : 0, borderRadius: radii.pill, paddingVertical: dims.py, paddingHorizontal: dims.px, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: dims.gap, alignSelf: fullWidth ? "stretch" : "flex-start", opacity: pressed ? 0.88 : 1, transform: [{ scale: pressed ? 0.99 : 1 }], }, elevated && !inactive && shadows.sm, style, ]} {...rest} > {loading ? ( ) : ( <> {icon ? ( ) : null} {title} {iconRight ? ( ) : null} )} ); } /** Circular icon button used in headers and cards. */ export function IconButton({ icon, onPress, size = 40, tone = "surface", color, accessibilityLabel, }: { icon: keyof typeof Ionicons.glyphMap; onPress?: () => void; size?: number; tone?: "surface" | "brand" | "dark" | "plain"; color?: string; accessibilityLabel?: string; }) { const bg = tone === "brand" ? colors.brand : tone === "dark" ? colors.ink : tone === "plain" ? "transparent" : colors.surface; const fg = color ?? (tone === "dark" ? colors.surface : colors.ink); return ( [ { width: size, height: size, borderRadius: size / 2, backgroundColor: bg, alignItems: "center", justifyContent: "center", opacity: pressed ? 0.7 : 1, }, tone === "surface" && { borderWidth: 1, borderColor: colors.line }, ]} > ); } /** Small round "+" affordance on product cards. */ export function AddButton({ onPress, disabled, size = 34, }: { onPress?: () => void; disabled?: boolean; size?: number; }) { return ( ({ width: size, height: size, borderRadius: size / 2, backgroundColor: disabled ? colors.surfaceAlt : colors.brand, alignItems: "center", justifyContent: "center", opacity: pressed ? 0.8 : 1, })} > ); } export function ButtonRow({ children }: { children: ReactNode }) { return {children}; }