import { Pressable, StyleSheet, Text, TextInput, View, type TextInputProps, type ViewStyle } from "react-native";
import { C, radius, shadow, space, type } from "../lib/theme";
/**
* Screen background + consistent gutter. Every screen sits on the grouped
* background so white cards read as raised paper.
*/
export function Screen({ children, style }: { children: React.ReactNode; style?: ViewStyle }) {
return {children};
}
/**
* Large in-content heading. Nav-bar titles are tiny and centred; Things puts
* the screen name in the content at display size, which is what gives each
* screen its sense of place.
*/
export function Title({ children, count }: { children: React.ReactNode; count?: number }) {
return (
{children}
{count !== undefined && count > 0 && {count}}
);
}
/** Small caps label above a group. Sentence case — SHOUTING CAPS reads dated. */
export function SectionHeader({ children, tone }: { children: React.ReactNode; tone?: string }) {
return {children};
}
/**
* Rounded white group. Rows inside are separated by inset hairlines rather
* than full-bleed dividers, so the card keeps a clean silhouette.
*/
export function Card({ children, style }: { children: React.ReactNode; style?: ViewStyle }) {
return {children};
}
/** Inset hairline for use between rows inside a `Card`. */
export function Separator({ inset = 0 }: { inset?: number }) {
return ;
}
/** Compact count/status pill. */
export function Pill({ label, tone }: { label: string; tone?: string }) {
return (
{label}
);
}
/** Centred empty state. One quiet line — no illustration, no exclamation. */
export function Empty({ children }: { children: React.ReactNode }) {
return {children};
}
export function Field({ label, ...props }: TextInputProps & { label: string }) {
return (
{label}
);
}
export function Chips({
label,
options,
value,
onChange,
labels,
}: {
label: string;
options: T[];
value: T | null;
onChange: (v: T) => void;
labels?: Record;
}) {
return (
{label}
{options.map((o) => {
const active = value === o;
return (
[u.chip, active && u.chipActive, pressed && !active && u.chipPressed]}
onPress={() => onChange(o)}
>
{labels?.[o] ?? o}
);
})}
);
}
export function Button({
title,
onPress,
danger,
secondary,
}: {
title: string;
onPress: () => void;
danger?: boolean;
secondary?: boolean;
}) {
const variant = danger ? u.buttonDanger : secondary ? u.buttonSecondary : null;
const textVariant = danger ? u.buttonTextDanger : secondary ? u.buttonTextSecondary : null;
return (
[u.button, variant, pressed && { opacity: 0.75 }]}
onPress={onPress}
>
{title}
);
}
const u = StyleSheet.create({
screen: { flex: 1, backgroundColor: C.bg },
titleRow: {
flexDirection: "row",
alignItems: "center",
gap: space.md,
paddingHorizontal: space.lg,
paddingTop: space.md,
paddingBottom: space.lg,
},
title: { ...type.title, color: C.text },
titleCount: { ...type.title, color: C.muted, fontWeight: "600" },
sectionHeader: {
...type.section,
color: C.textSecondary,
paddingHorizontal: space.xs,
paddingTop: space.xl,
paddingBottom: space.sm,
},
card: {
backgroundColor: C.surface,
borderRadius: radius.lg,
borderWidth: StyleSheet.hairlineWidth,
borderColor: C.border,
overflow: "hidden",
...shadow.card,
},
separator: { height: StyleSheet.hairlineWidth, backgroundColor: C.separator },
pill: {
paddingHorizontal: space.sm,
paddingVertical: 3,
borderRadius: radius.pill,
backgroundColor: C.surface2,
},
pillText: { ...type.meta, color: C.textSecondary, fontWeight: "500" },
empty: { color: C.muted, textAlign: "center", marginTop: 56, ...type.body },
label: {
...type.section,
color: C.textSecondary,
marginBottom: space.sm,
},
input: {
backgroundColor: C.surface,
color: C.text,
borderRadius: radius.md,
borderWidth: StyleSheet.hairlineWidth,
borderColor: C.border,
paddingHorizontal: space.md,
paddingVertical: space.md,
fontSize: 16,
},
chipRow: { flexDirection: "row", flexWrap: "wrap", gap: space.sm },
chip: {
paddingVertical: space.sm,
paddingHorizontal: space.lg - 2,
borderRadius: radius.pill,
backgroundColor: C.surface,
borderWidth: StyleSheet.hairlineWidth,
borderColor: C.border,
},
chipActive: { backgroundColor: C.accentSoft, borderColor: C.accent },
chipPressed: { backgroundColor: C.surface2 },
chipText: { ...type.chip, color: C.textSecondary },
chipTextActive: { color: C.accent, fontWeight: "600" },
button: {
backgroundColor: C.accent,
borderRadius: radius.md,
paddingVertical: space.lg - 2,
alignItems: "center",
marginTop: space.sm,
marginBottom: space.md,
},
buttonDanger: { backgroundColor: "transparent", borderWidth: StyleSheet.hairlineWidth, borderColor: C.danger },
buttonSecondary: { backgroundColor: C.surface, borderWidth: StyleSheet.hairlineWidth, borderColor: C.border },
buttonText: { fontSize: 16, fontWeight: "600", color: C.onAccent },
buttonTextDanger: { color: C.danger },
buttonTextSecondary: { color: C.text },
});