From 7eb8ccae48b0cc18a9dcaa9c3626a02df8e6d919 Mon Sep 17 00:00:00 2001 From: JustZvan Date: Fri, 6 Feb 2026 13:22:33 +0100 Subject: feat: initial commit! --- lib/ui.tsx | 846 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 846 insertions(+) create mode 100644 lib/ui.tsx (limited to 'lib/ui.tsx') diff --git a/lib/ui.tsx b/lib/ui.tsx new file mode 100644 index 0000000..e31a10d --- /dev/null +++ b/lib/ui.tsx @@ -0,0 +1,846 @@ +import { Ionicons } from "@expo/vector-icons"; +import React, { ReactNode } from "react"; +import { + ActivityIndicator, + Modal, + Pressable, + TextInput as RNTextInput, + ScrollView, + StyleProp, + StyleSheet, + Text, + TextStyle, + TouchableOpacity, + View, + ViewStyle, +} from "react-native"; +import { Device } from "../api/types"; +import { colors } from "./theme"; + +export function Screen({ + children, + contentContainerStyle, +}: { + children: ReactNode; + contentContainerStyle?: StyleProp; +}) { + return ( + + {children} + + ); +} + +export function Card({ + children, + style, +}: { + children: ReactNode; + style?: StyleProp; +}) { + return {children}; +} + +export function H1({ children }: { children: ReactNode }) { + return {children}; +} + +export function H2({ children }: { children: ReactNode }) { + return {children}; +} + +export function Body({ + children, + style, +}: { + children: ReactNode; + style?: StyleProp; +}) { + return {children}; +} + +export function Muted({ + children, + style, +}: { + children: ReactNode; + style?: StyleProp; +}) { + return {children}; +} + +export function Pill({ + label, + tone = "neutral", +}: { + label: string; + tone?: "neutral" | "good" | "attention"; +}) { + const backgroundColor = + tone === "good" + ? colors.surfaceVariant + : tone === "attention" + ? colors.primaryContainer + : colors.surfaceVariant; + + const borderColor = tone === "attention" ? colors.primary : colors.outline; + + const textColor = tone === "attention" ? colors.onPrimary : colors.onSurface; + + return ( + + {label} + + ); +} + +export function Row({ left, right }: { left: ReactNode; right?: ReactNode }) { + return ( + + {left} + {right ? {right} : null} + + ); +} + +export function Divider() { + return ; +} + +export function ActionRow({ + title, + subtitle, + onPress, + right, +}: { + title: string; + subtitle?: string; + onPress?: () => void; + right?: ReactNode; +}) { + return ( + [ + styles.actionRow, + pressed && onPress ? styles.actionRowPressed : undefined, + ]} + > + + {title} + {subtitle ? ( + {subtitle} + ) : null} + + {right ? ( + {right} + ) : null} + + ); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Dialog / Modal Components +// ───────────────────────────────────────────────────────────────────────────── + +export type DialogOption = { + label: string; + onPress: () => void; + destructive?: boolean; +}; + +/** + * A selection dialog that shows a list of options to choose from. + */ +export function SelectDialog({ + visible, + title, + options, + onClose, +}: { + visible: boolean; + title: string; + options: DialogOption[]; + onClose: () => void; +}) { + return ( + + + + {title} + {options.map((option, index) => ( + + + {option.label} + + + ))} + + Cancel + + + + + ); +} + +/** + * A confirmation dialog with a message and OK/Cancel buttons. + */ +export function ConfirmDialog({ + visible, + title, + message, + confirmLabel = "Okay", + cancelLabel = "Cancel", + onConfirm, + onCancel, + destructive = false, +}: { + visible: boolean; + title: string; + message: string; + confirmLabel?: string; + cancelLabel?: string; + onConfirm: () => void; + onCancel: () => void; + destructive?: boolean; +}) { + return ( + + + + {title} + {message} + + + {cancelLabel} + + + + {confirmLabel} + + + + + + + ); +} + +/** + * A prompt dialog with an input field, message, and Cancel/Submit buttons. + */ +export function PromptDialog({ + visible, + title, + message, + onClose, + onSubmit, + initialValue = "", +}: { + visible: boolean; + title: string; + message: string; + onClose: () => void; + onSubmit: (value: string) => void; + initialValue?: string; +}) { + const [value, setValue] = React.useState(initialValue); + + React.useEffect(() => { + setValue(initialValue); + }, [initialValue, visible]); + + return ( + + + +

{title}

+ {message} + + + + + +