Initial commit (unpacked platform)

This commit is contained in:
Sven (AAMOS AI)
2026-08-05 19:21:11 +07:00
commit ac5340195a
314 changed files with 57584 additions and 0 deletions
+256
View File
@@ -0,0 +1,256 @@
import type { PropsWithChildren, ReactNode } from "react";
import {
ActivityIndicator,
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
type StyleProp,
type TextInputProps,
type ViewStyle,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { colors, radius, spacing, typography } from "@/lib/theme";
import { t } from "@/lib/i18n";
export function Screen({
children,
scroll = true,
style,
}: PropsWithChildren<{ scroll?: boolean; style?: StyleProp<ViewStyle> }>) {
const content = scroll ? (
<ScrollView
contentContainerStyle={[styles.screenContent, style]}
keyboardShouldPersistTaps="handled"
>
{children}
</ScrollView>
) : (
<View style={[styles.screenContent, { flex: 1 }, style]}>{children}</View>
);
return (
<SafeAreaView style={styles.screen} edges={["top"]}>
{content}
</SafeAreaView>
);
}
export function Title({ children }: { children: ReactNode }) {
return <Text style={typography.title}>{children}</Text>;
}
export function Heading({ children }: { children: ReactNode }) {
return <Text style={[typography.heading, { marginBottom: spacing.sm }]}>{children}</Text>;
}
export function Body({ children, muted = false }: { children: ReactNode; muted?: boolean }) {
return <Text style={[typography.body, muted && { color: colors.textMuted }]}>{children}</Text>;
}
export function Small({ children }: { children: ReactNode }) {
return <Text style={typography.small}>{children}</Text>;
}
export function Card({
children,
onPress,
style,
}: PropsWithChildren<{ onPress?: () => void; style?: StyleProp<ViewStyle> }>) {
if (onPress) {
return (
<Pressable
onPress={onPress}
style={({ pressed }) => [styles.card, style, pressed && { opacity: 0.85 }]}
>
{children}
</Pressable>
);
}
return <View style={[styles.card, style]}>{children}</View>;
}
export function Button({
label,
onPress,
variant = "primary",
disabled = false,
loading = false,
}: {
label: string;
onPress: () => void;
variant?: "primary" | "secondary" | "ghost" | "danger";
disabled?: boolean;
loading?: boolean;
}) {
const bg =
variant === "primary"
? colors.primary
: variant === "danger"
? colors.dangerSoft
: variant === "secondary"
? colors.primarySoft
: "transparent";
const fg =
variant === "primary" ? "#fff" : variant === "danger" ? colors.danger : colors.primaryDark;
return (
<Pressable
onPress={onPress}
disabled={disabled || loading}
style={({ pressed }) => [
styles.button,
{ backgroundColor: bg, opacity: disabled ? 0.5 : pressed ? 0.85 : 1 },
variant === "ghost" && { borderWidth: 1, borderColor: colors.border },
]}
>
{loading ? (
<ActivityIndicator color={fg} />
) : (
<Text style={{ color: fg, fontWeight: "600", fontSize: 15 }}>{label}</Text>
)}
</Pressable>
);
}
export function Input(props: TextInputProps) {
return (
<TextInput
placeholderTextColor={colors.textMuted}
{...props}
style={[styles.input, props.style]}
/>
);
}
export function Tag({
label,
tone = "neutral",
}: {
label: string;
tone?: "neutral" | "success" | "warning" | "danger" | "accent";
}) {
const palette = {
neutral: { bg: colors.surfaceAlt, fg: colors.textMuted },
success: { bg: colors.primarySoft, fg: colors.primaryDark },
warning: { bg: colors.warningSoft, fg: colors.warning },
danger: { bg: colors.dangerSoft, fg: colors.danger },
accent: { bg: colors.accentSoft, fg: colors.accent },
}[tone];
return (
<View style={[styles.tag, { backgroundColor: palette.bg }]}>
<Text style={{ color: palette.fg, fontSize: 12, fontWeight: "600" }}>{label}</Text>
</View>
);
}
export function ProgressBar({
progress,
tone = "primary",
}: {
progress: number;
tone?: "primary" | "warning";
}) {
const clamped = Math.max(0, Math.min(1, progress));
const over = progress > 1;
return (
<View style={styles.progressTrack}>
<View
style={[
styles.progressFill,
{
width: `${clamped * 100}%`,
backgroundColor: over
? colors.warning
: tone === "warning"
? colors.warning
: colors.primary,
},
]}
/>
</View>
);
}
export function LoadingView() {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color={colors.primary} />
<Small>{t("common.loading")}</Small>
</View>
);
}
export function ErrorView({ message, onRetry }: { message?: string; onRetry?: () => void }) {
return (
<View style={styles.center}>
<Body>{message ?? t("common.error")}</Body>
{onRetry && <Button label={t("common.retry")} onPress={onRetry} variant="secondary" />}
</View>
);
}
export function EmptyState({ text }: { text: string }) {
return (
<Card style={{ alignItems: "center", paddingVertical: spacing.xl }}>
<Body muted>{text}</Body>
</Card>
);
}
export function Row({ children, style }: PropsWithChildren<{ style?: StyleProp<ViewStyle> }>) {
return <View style={[styles.row, style]}>{children}</View>;
}
export function Spacer({ size = spacing.md }: { size?: number }) {
return <View style={{ height: size }} />;
}
const styles = StyleSheet.create({
screen: { flex: 1, backgroundColor: colors.background },
screenContent: { padding: spacing.md, gap: spacing.sm, paddingBottom: spacing.xl },
card: {
backgroundColor: colors.surface,
borderRadius: radius.lg,
padding: spacing.md,
borderWidth: 1,
borderColor: colors.border,
gap: spacing.xs,
},
button: {
paddingVertical: 13,
paddingHorizontal: spacing.lg,
borderRadius: radius.md,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
},
input: {
backgroundColor: colors.surface,
borderWidth: 1,
borderColor: colors.border,
borderRadius: radius.md,
paddingHorizontal: spacing.md,
paddingVertical: 12,
fontSize: 15,
color: colors.text,
},
tag: {
paddingHorizontal: 10,
paddingVertical: 3,
borderRadius: radius.full,
alignSelf: "flex-start",
},
progressTrack: {
height: 8,
backgroundColor: colors.surfaceAlt,
borderRadius: radius.full,
overflow: "hidden",
},
progressFill: { height: 8, borderRadius: radius.full },
center: {
flex: 1,
alignItems: "center",
justifyContent: "center",
gap: spacing.md,
padding: spacing.xl,
},
row: { flexDirection: "row", alignItems: "center", gap: spacing.sm, flexWrap: "wrap" },
});