mojmajstor/components/ui/button.tsx
echo 5102ed23c1 feat: Phase 1 - project setup, auth, and base UI
Set up Expo SDK 56 project with TypeScript and Expo Router (file-based).

Backend:
- Full Convex schema (users, accounts, sessions, ads, categories,
  reviews, chats, messages, posts) deployed to self-hosted instance
- Custom email/password auth with SHA-256 hashing via Convex actions
- Query/mutation scaffolds for all domain tables
- Convex environment variables configured on backend

App structure:
- Root layout with ConvexProvider, ThemeProvider, AuthContext
- (auth) group: login and register screens (Macedonian UI)
  with role selection (handyman/customer)
- (tabs) group: 5 tabs (Home, Explore, Posts, Chat, Profile)
  each with nested Stack layouts
- Placeholder detail screens for ad, post, chat, review flows
- 404 not-found screen in Macedonian

UI primitives (components/ui/):
- Button (4 variants), Input, Card, Loading, Avatar, Rating, Badge
- ThemeProvider with light/dark palette via React context

All user-facing strings in Macedonian. Code identifiers in English.
TypeScript passes with zero errors.
2026-05-29 18:18:58 +02:00

70 lines
2.1 KiB
TypeScript

import { Pressable, Text, ActivityIndicator, type ViewStyle } from "react-native";
import { useTheme } from "../theme";
type Variant = "primary" | "secondary" | "outline" | "destructive";
interface ButtonProps {
variant?: Variant;
loading?: boolean;
size?: "sm" | "md" | "lg";
disabled?: boolean;
onPress?: () => void;
children: React.ReactNode;
style?: ViewStyle;
}
export function Button({
variant = "primary",
loading = false,
size = "md",
disabled = false,
children,
style,
onPress,
}: ButtonProps) {
const theme = useTheme();
const sizeStyles: Record<string, ViewStyle> = {
sm: { paddingVertical: theme.spacing.sm, paddingHorizontal: theme.spacing.md, borderRadius: theme.radius.sm },
md: { paddingVertical: 12, paddingHorizontal: theme.spacing.lg, borderRadius: theme.radius.md },
lg: { paddingVertical: 16, paddingHorizontal: theme.spacing.xl, borderRadius: theme.radius.md },
};
const variantStyles: Record<Variant, ViewStyle> = {
primary: { backgroundColor: theme.colors.primary },
secondary: { backgroundColor: theme.colors.secondary },
outline: { backgroundColor: "transparent", borderWidth: 1, borderColor: theme.colors.primary },
destructive: { backgroundColor: theme.colors.error },
};
const textColors: Record<Variant, string> = {
primary: "#FFFFFF",
secondary: "#FFFFFF",
outline: theme.colors.primary,
destructive: "#FFFFFF",
};
return (
<Pressable
disabled={disabled || loading}
onPress={onPress}
style={[
{ alignItems: "center", justifyContent: "center" },
sizeStyles[size],
variantStyles[variant],
disabled && { opacity: 0.5 },
style,
]}
>
{loading ? (
<ActivityIndicator color={variant === "outline" ? theme.colors.primary : "#FFFFFF"} size="small" />
) : typeof children === "string" ? (
<Text style={{ color: textColors[variant], fontWeight: "600", fontSize: size === "sm" ? 14 : 16 }}>
{children}
</Text>
) : (
children
)}
</Pressable>
);
}