feat: initial project scaffold with Watch companion app

This commit is contained in:
2026-08-04 17:28:06 +02:00
parent adbb4f01c5
commit 43b6467efb
65 changed files with 6361 additions and 166 deletions
+35
View File
@@ -0,0 +1,35 @@
import { Pressable, Text, ActivityIndicator, type PressableProps } from "react-native";
type ButtonProps = PressableProps & {
title: string;
variant?: "primary" | "secondary" | "ghost";
loading?: boolean;
};
export function Button({ title, variant = "primary", loading, disabled, ...props }: ButtonProps) {
const base = "rounded-xl px-6 py-3 items-center justify-center";
const variants = {
primary: "bg-sky-500",
secondary: "bg-slate-100",
ghost: "bg-transparent",
};
const textVariants = {
primary: "text-white font-semibold",
secondary: "text-slate-700 font-semibold",
ghost: "text-sky-500 font-medium",
};
return (
<Pressable
className={`${base} ${variants[variant]} ${disabled ? "opacity-50" : ""}`}
disabled={disabled || loading}
{...props}
>
{loading ? (
<ActivityIndicator color={variant === "primary" ? "#fff" : "#0ea5e9"} />
) : (
<Text className={textVariants[variant]}>{title}</Text>
)}
</Pressable>
);
}
+19
View File
@@ -0,0 +1,19 @@
import { View, type ViewProps } from "react-native";
type CardProps = ViewProps & {
variant?: "default" | "highlighted";
};
export function Card({ variant = "default", className = "", children, ...props }: CardProps) {
const base = "rounded-2xl p-4";
const variants = {
default: "bg-white border border-slate-200",
highlighted: "bg-sky-50 border border-sky-200",
};
return (
<View className={`${base} ${variants[variant]} ${className}`} {...props}>
{children}
</View>
);
}
+29
View File
@@ -0,0 +1,29 @@
import { View, Text, ActivityIndicator } from "react-native";
type EmptyStateProps = {
title: string;
message?: string;
icon?: string;
};
export function EmptyState({ title, message }: EmptyStateProps) {
return (
<View className="flex-1 items-center justify-center py-12 px-4">
<Text className="text-lg font-semibold text-slate-900 mb-2">{title}</Text>
{message && <Text className="text-slate-500 text-center">{message}</Text>}
</View>
);
}
type LoadingSpinnerProps = {
message?: string;
};
export function LoadingSpinner({ message }: LoadingSpinnerProps) {
return (
<View className="flex-1 items-center justify-center py-12">
<ActivityIndicator size="large" color="#0ea5e9" />
{message && <Text className="text-slate-500 mt-3">{message}</Text>}
</View>
);
}
+20
View File
@@ -0,0 +1,20 @@
import { TextInput, View, Text, type TextInputProps } from "react-native";
type InputProps = TextInputProps & {
label?: string;
error?: string;
};
export function Input({ label, error, className = "", ...props }: InputProps) {
return (
<View className="mb-3">
{label && <Text className="text-sm font-medium text-slate-500 mb-1">{label}</Text>}
<TextInput
className={`bg-white border ${error ? "border-red-400" : "border-slate-300"} rounded-lg px-3 py-2.5 text-slate-900 ${className}`}
placeholderTextColor="#94a3b8"
{...props}
/>
{error && <Text className="text-red-500 text-xs mt-1">{error}</Text>}
</View>
);
}
+20
View File
@@ -0,0 +1,20 @@
import { View, type ViewProps } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
type ScreenContainerProps = ViewProps & {
padded?: boolean;
};
export function ScreenContainer({ padded = true, className = "", children, ...props }: ScreenContainerProps) {
const insets = useSafeAreaInsets();
return (
<View
className={`flex-1 bg-white ${padded ? "p-4" : ""} ${className}`}
style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}
{...props}
>
{children}
</View>
);
}