feat: initial project scaffold with Watch companion app
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user