Update files

This commit is contained in:
Mohamed Boudra
2026-02-05 12:35:22 +07:00
parent 45869e699f
commit 1d0b2808c7
8 changed files with 215 additions and 148 deletions
+43 -41
View File
@@ -14,7 +14,6 @@ import Constants from "expo-constants";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles"; import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { Sun, Moon, Monitor, MoreVertical, Globe } from "lucide-react-native"; import { Sun, Moon, Monitor, MoreVertical, Globe } from "lucide-react-native";
import { Fonts } from "@/constants/theme";
import { useAppSettings, type AppSettings } from "@/hooks/use-settings"; import { useAppSettings, type AppSettings } from "@/hooks/use-settings";
import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context"; import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-context";
import { useDaemonConnections, type ActiveConnection, type ConnectionStatus } from "@/contexts/daemon-connections-context"; import { useDaemonConnections, type ActiveConnection, type ConnectionStatus } from "@/contexts/daemon-connections-context";
@@ -34,6 +33,7 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet"; import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
const delay = (ms: number) => const delay = (ms: number) =>
@@ -65,6 +65,9 @@ const styles = StyleSheet.create((theme) => ({
content: { content: {
padding: theme.spacing[4], padding: theme.spacing[4],
paddingTop: theme.spacing[6], paddingTop: theme.spacing[6],
width: "100%",
maxWidth: 720,
alignSelf: "center",
}, },
section: { section: {
marginBottom: theme.spacing[6], marginBottom: theme.spacing[6],
@@ -72,7 +75,7 @@ const styles = StyleSheet.create((theme) => ({
sectionTitle: { sectionTitle: {
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs, fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
letterSpacing: 0.6, letterSpacing: 0.6,
textTransform: "uppercase", textTransform: "uppercase",
marginBottom: theme.spacing[3], marginBottom: theme.spacing[3],
@@ -81,7 +84,7 @@ const styles = StyleSheet.create((theme) => ({
label: { label: {
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs, fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
letterSpacing: 0.4, letterSpacing: 0.4,
textTransform: "uppercase", textTransform: "uppercase",
marginBottom: theme.spacing[2], marginBottom: theme.spacing[2],
@@ -95,9 +98,6 @@ const styles = StyleSheet.create((theme) => ({
borderColor: theme.colors.border, borderColor: theme.colors.border,
fontSize: theme.fontSize.base, fontSize: theme.fontSize.base,
}, },
inputUrl: {
fontFamily: Fonts.mono,
},
// Host card styles // Host card styles
hostCard: { hostCard: {
backgroundColor: theme.colors.surface2, backgroundColor: theme.colors.surface2,
@@ -124,7 +124,7 @@ const styles = StyleSheet.create((theme) => ({
hostLabel: { hostLabel: {
color: theme.colors.foreground, color: theme.colors.foreground,
fontSize: theme.fontSize.base, fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
}, },
hostError: { hostError: {
color: theme.colors.palette.red[300], color: theme.colors.palette.red[300],
@@ -146,7 +146,7 @@ const styles = StyleSheet.create((theme) => ({
}, },
statusText: { statusText: {
fontSize: theme.fontSize.xs, fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
}, },
connectionPill: { connectionPill: {
flexDirection: "row", flexDirection: "row",
@@ -162,7 +162,7 @@ const styles = StyleSheet.create((theme) => ({
}, },
connectionText: { connectionText: {
fontSize: theme.fontSize.xs, fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
flexShrink: 1, flexShrink: 1,
}, },
@@ -197,7 +197,7 @@ const styles = StyleSheet.create((theme) => ({
addButtonText: { addButtonText: {
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
}, },
// Add/Edit form // Add/Edit form
formCard: { formCard: {
@@ -212,7 +212,7 @@ const styles = StyleSheet.create((theme) => ({
formTitle: { formTitle: {
color: theme.colors.foreground, color: theme.colors.foreground,
fontSize: theme.fontSize.base, fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
}, },
formField: { formField: {
gap: theme.spacing[2], gap: theme.spacing[2],
@@ -236,7 +236,7 @@ const styles = StyleSheet.create((theme) => ({
formButtonText: { formButtonText: {
color: theme.colors.foreground, color: theme.colors.foreground,
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
}, },
formButtonPrimaryText: { formButtonPrimaryText: {
color: theme.colors.palette.white, color: theme.colors.palette.white,
@@ -339,7 +339,7 @@ const styles = StyleSheet.create((theme) => ({
}, },
themeToggleText: { themeToggleText: {
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.medium, fontWeight: theme.fontWeight.normal,
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
}, },
themeToggleTextActive: { themeToggleTextActive: {
@@ -698,15 +698,19 @@ export default function SettingsScreen() {
Remove {pendingRemoveHost.label}? This will delete its saved connections. Remove {pendingRemoveHost.label}? This will delete its saved connections.
</Text> </Text>
<View style={[styles.formActionsRow, { marginTop: theme.spacing[4] }]}> <View style={[styles.formActionsRow, { marginTop: theme.spacing[4] }]}>
<Pressable <Button
style={[styles.formButton, isRemovingHost && styles.disabled]} variant="secondary"
size="sm"
style={{ flex: 1 }}
onPress={() => setPendingRemoveHost(null)} onPress={() => setPendingRemoveHost(null)}
disabled={isRemovingHost} disabled={isRemovingHost}
> >
<Text style={styles.formButtonText}>Cancel</Text> Cancel
</Pressable> </Button>
<Pressable <Button
style={[styles.formButton, isRemovingHost && styles.disabled, { backgroundColor: theme.colors.destructive }]} variant="destructive"
size="sm"
style={{ flex: 1 }}
onPress={() => { onPress={() => {
const serverId = pendingRemoveHost.serverId; const serverId = pendingRemoveHost.serverId;
setIsRemovingHost(true); setIsRemovingHost(true);
@@ -721,10 +725,8 @@ export default function SettingsScreen() {
disabled={isRemovingHost} disabled={isRemovingHost}
testID="remove-host-confirm" testID="remove-host-confirm"
> >
<Text style={[styles.formButtonText, { color: theme.colors.palette.white }]}> Remove
Remove </Button>
</Text>
</Pressable>
</View> </View>
</AdaptiveModalSheet> </AdaptiveModalSheet>
) : null} ) : null}
@@ -771,13 +773,13 @@ export default function SettingsScreen() {
backgroundColor: theme.colors.surface2, backgroundColor: theme.colors.surface2,
}} }}
> >
<Text style={{ color: theme.colors.foreground, fontSize: 12, fontFamily: Fonts.mono, flex: 1 }}> <Text style={{ color: theme.colors.foreground, fontSize: 12, flex: 1 }}>
{title} {title}
</Text> </Text>
<Pressable <Pressable
onPress={() => void handleRemoveConnection(editingDaemon.serverId, conn.id)} onPress={() => void handleRemoveConnection(editingDaemon.serverId, conn.id)}
> >
<Text style={{ color: theme.colors.destructive, fontSize: 12, fontWeight: "600" }}> <Text style={{ color: theme.colors.destructive, fontSize: 12, fontWeight: "500" }}>
Remove Remove
</Text> </Text>
</Pressable> </Pressable>
@@ -790,8 +792,10 @@ export default function SettingsScreen() {
{editingDaemon ? ( {editingDaemon ? (
<View style={styles.formField}> <View style={styles.formField}>
<Pressable <Button
style={[styles.formButton, styles.formButtonPrimary, { alignSelf: "flex-start" }]} variant="default"
size="sm"
style={{ alignSelf: "flex-start" }}
onPress={() => { onPress={() => {
const serverId = editingDaemon.serverId; const serverId = editingDaemon.serverId;
handleCloseEditDaemon(); handleCloseEditDaemon();
@@ -801,30 +805,28 @@ export default function SettingsScreen() {
}} }}
testID="edit-host-add-connection" testID="edit-host-add-connection"
> >
<Text style={[styles.formButtonText, styles.formButtonPrimaryText]}> Add connection
Add connection </Button>
</Text>
</Pressable>
</View> </View>
) : null} ) : null}
<View style={styles.formActionsRow}> <View style={styles.formActionsRow}>
<Pressable <Button
style={[styles.formButton, isSavingEdit && styles.disabled]} variant="secondary"
size="sm"
onPress={handleCloseEditDaemon} onPress={handleCloseEditDaemon}
disabled={isSavingEdit} disabled={isSavingEdit}
> >
<Text style={styles.formButtonText}>Cancel</Text> Cancel
</Pressable> </Button>
<Pressable <Button
style={[styles.formButton, styles.formButtonPrimary, isSavingEdit && styles.disabled]} variant="default"
size="sm"
onPress={() => void handleSaveEditDaemon()} onPress={() => void handleSaveEditDaemon()}
disabled={isSavingEdit} disabled={isSavingEdit}
> >
<Text style={[styles.formButtonText, styles.formButtonPrimaryText]}> {isSavingEdit ? "Saving..." : "Save"}
{isSavingEdit ? "Saving..." : "Save"} </Button>
</Text>
</Pressable>
</View> </View>
</AdaptiveModalSheet> </AdaptiveModalSheet>
@@ -51,7 +51,7 @@ const styles = StyleSheet.create((theme) => ({
title: { title: {
color: theme.colors.foreground, color: theme.colors.foreground,
fontSize: theme.fontSize.lg, fontSize: theme.fontSize.lg,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.medium,
}, },
closeButton: { closeButton: {
padding: theme.spacing[2], padding: theme.spacing[2],
@@ -121,13 +121,16 @@ export function AdaptiveModalSheet({
const isMobile = const isMobile =
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const sheetRef = useRef<BottomSheetModal>(null); const sheetRef = useRef<BottomSheetModal>(null);
const dismissingForVisibilityRef = useRef(false);
const resolvedSnapPoints = useMemo(() => snapPoints ?? ["65%", "90%"], [snapPoints]); const resolvedSnapPoints = useMemo(() => snapPoints ?? ["65%", "90%"], [snapPoints]);
useEffect(() => { useEffect(() => {
if (!isMobile) return; if (!isMobile) return;
if (visible) { if (visible) {
dismissingForVisibilityRef.current = false;
sheetRef.current?.present(); sheetRef.current?.present();
} else { } else {
dismissingForVisibilityRef.current = true;
sheetRef.current?.dismiss(); sheetRef.current?.dismiss();
} }
}, [visible, isMobile]); }, [visible, isMobile]);
@@ -135,6 +138,10 @@ export function AdaptiveModalSheet({
const handleSheetChange = useCallback( const handleSheetChange = useCallback(
(index: number) => { (index: number) => {
if (index === -1) { if (index === -1) {
if (dismissingForVisibilityRef.current) {
dismissingForVisibilityRef.current = false;
return;
}
onClose(); onClose();
} }
}, },
@@ -18,7 +18,7 @@ const styles = StyleSheet.create((theme) => ({
optionText: { optionText: {
color: theme.colors.foreground, color: theme.colors.foreground,
fontSize: theme.fontSize.base, fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.normal,
}, },
optionSubtext: { optionSubtext: {
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
+11 -37
View File
@@ -1,5 +1,5 @@
import { useCallback, useMemo, useRef, useState } from "react"; import { useCallback, useMemo, useRef, useState } from "react";
import { Alert, Pressable, Text, TextInput, View } from "react-native"; import { Alert, Text, TextInput, View } from "react-native";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles"; import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { BottomSheetTextInput } from "@gorhom/bottom-sheet"; import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
import { Link2 } from "lucide-react-native"; import { Link2 } from "lucide-react-native";
@@ -7,6 +7,7 @@ import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-
import { normalizeHostPort } from "@/utils/daemon-endpoints"; import { normalizeHostPort } from "@/utils/daemon-endpoints";
import { DaemonConnectionTestError, probeDaemonEndpoint } from "@/utils/test-daemon-connection"; import { DaemonConnectionTestError, probeDaemonEndpoint } from "@/utils/test-daemon-connection";
import { AdaptiveModalSheet } from "./adaptive-modal-sheet"; import { AdaptiveModalSheet } from "./adaptive-modal-sheet";
import { Button } from "@/components/ui/button";
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
field: { field: {
@@ -31,24 +32,6 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[3], gap: theme.spacing[3],
marginTop: theme.spacing[2], marginTop: theme.spacing[2],
}, },
button: {
flex: 1,
paddingVertical: theme.spacing[3],
borderRadius: theme.borderRadius.lg,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.surface2,
},
primaryButton: {
backgroundColor: theme.colors.palette.blue[500],
},
buttonText: {
color: theme.colors.foreground,
fontWeight: theme.fontWeight.semibold,
},
primaryButtonText: {
color: theme.colors.palette.white,
},
helper: { helper: {
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
@@ -57,12 +40,6 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.destructive, color: theme.colors.destructive,
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
}, },
connectRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: theme.spacing[2],
},
})); }));
function isHostPortOnly(raw: string): boolean { function isHostPortOnly(raw: string): boolean {
@@ -264,21 +241,18 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved, targetServer
</View> </View>
<View style={styles.actions}> <View style={styles.actions}>
<Pressable style={styles.button} onPress={handleCancel} disabled={isSaving}> <Button style={{ flex: 1 }} variant="secondary" onPress={handleCancel} disabled={isSaving}>
<Text style={styles.buttonText}>{onCancel ? "Back" : "Cancel"}</Text> Cancel
</Pressable> </Button>
<Pressable <Button
style={[styles.button, styles.primaryButton, isSaving ? { opacity: 0.7 } : null]} style={{ flex: 1 }}
variant="default"
onPress={() => void handleSave()} onPress={() => void handleSave()}
disabled={isSaving} disabled={isSaving}
leftIcon={<Link2 size={16} color={theme.colors.palette.white} />}
> >
<View style={styles.connectRow}> {isSaving ? "Connecting..." : "Connect"}
<Link2 size={16} color={theme.colors.palette.white} /> </Button>
<Text style={[styles.buttonText, styles.primaryButtonText]}>
{isSaving ? "Connecting..." : "Connect & Save"}
</Text>
</View>
</Pressable>
</View> </View>
</AdaptiveModalSheet> </AdaptiveModalSheet>
); );
@@ -1,8 +1,9 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Pressable, Text, TextInput, View } from "react-native"; import { Text, TextInput, View } from "react-native";
import { BottomSheetTextInput } from "@gorhom/bottom-sheet"; import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles"; import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { AdaptiveModalSheet } from "./adaptive-modal-sheet"; import { AdaptiveModalSheet } from "./adaptive-modal-sheet";
import { Button } from "@/components/ui/button";
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
helper: { helper: {
@@ -32,24 +33,6 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[3], gap: theme.spacing[3],
marginTop: theme.spacing[4], marginTop: theme.spacing[4],
}, },
button: {
flex: 1,
paddingVertical: theme.spacing[3],
borderRadius: theme.borderRadius.lg,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.surface2,
},
primaryButton: {
backgroundColor: theme.colors.palette.blue[500],
},
buttonText: {
color: theme.colors.foreground,
fontWeight: theme.fontWeight.semibold,
},
primaryButtonText: {
color: theme.colors.palette.white,
},
})); }));
export interface NameHostModalProps { export interface NameHostModalProps {
@@ -122,14 +105,13 @@ export function NameHostModal({ visible, serverId, hostname, onSkip, onSave }: N
</View> </View>
<View style={styles.actions}> <View style={styles.actions}>
<Pressable style={styles.button} onPress={onSkip} testID="name-host-skip"> <Button style={{ flex: 1 }} variant="secondary" onPress={onSkip} testID="name-host-skip">
<Text style={styles.buttonText}>Skip</Text> Skip
</Pressable> </Button>
<Pressable style={[styles.button, styles.primaryButton]} onPress={handleSave} testID="name-host-save"> <Button style={{ flex: 1 }} variant="default" onPress={handleSave} testID="name-host-save">
<Text style={[styles.buttonText, styles.primaryButtonText]}>Save</Text> Save
</Pressable> </Button>
</View> </View>
</AdaptiveModalSheet> </AdaptiveModalSheet>
); );
} }
+13 -40
View File
@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { Alert, Pressable, Text, TextInput, View } from "react-native"; import { Alert, Text, TextInput, View } from "react-native";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles"; import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { BottomSheetTextInput } from "@gorhom/bottom-sheet"; import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
import { Link } from "lucide-react-native"; import { Link } from "lucide-react-native";
@@ -7,6 +7,7 @@ import { useDaemonRegistry, type HostProfile } from "@/contexts/daemon-registry-
import { decodeOfferFragmentPayload } from "@/utils/daemon-endpoints"; import { decodeOfferFragmentPayload } from "@/utils/daemon-endpoints";
import { ConnectionOfferSchema } from "@server/shared/connection-offer"; import { ConnectionOfferSchema } from "@server/shared/connection-offer";
import { AdaptiveModalSheet } from "./adaptive-modal-sheet"; import { AdaptiveModalSheet } from "./adaptive-modal-sheet";
import { Button } from "@/components/ui/button";
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
helper: { helper: {
@@ -39,30 +40,6 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[3], gap: theme.spacing[3],
marginTop: theme.spacing[2], marginTop: theme.spacing[2],
}, },
button: {
flex: 1,
paddingVertical: theme.spacing[3],
borderRadius: theme.borderRadius.lg,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.surface2,
},
primaryButton: {
backgroundColor: theme.colors.palette.blue[500],
},
buttonText: {
color: theme.colors.foreground,
fontWeight: theme.fontWeight.semibold,
},
primaryButtonText: {
color: theme.colors.palette.white,
},
connectRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: theme.spacing[2],
},
})); }));
export interface PairLinkModalProps { export interface PairLinkModalProps {
@@ -184,33 +161,29 @@ export function PairLinkModal({ visible, onClose, onCancel, onSaved, targetServe
</View> </View>
<View style={styles.actions}> <View style={styles.actions}>
<Pressable <Button
style={styles.button} style={{ flex: 1 }}
variant="secondary"
onPress={handleCancel} onPress={handleCancel}
disabled={isSaving} disabled={isSaving}
testID="pair-link-cancel" testID="pair-link-cancel"
accessible
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel="Cancel" accessibilityLabel="Cancel"
> >
<Text style={styles.buttonText}>{onCancel ? "Back" : "Cancel"}</Text> Cancel
</Pressable> </Button>
<Pressable <Button
style={[styles.button, styles.primaryButton, isSaving ? { opacity: 0.7 } : null]} style={{ flex: 1 }}
variant="default"
onPress={() => void handleSave()} onPress={() => void handleSave()}
disabled={isSaving} disabled={isSaving}
testID="pair-link-submit" testID="pair-link-submit"
accessible
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel="Pair" accessibilityLabel="Pair"
leftIcon={<Link size={16} color={theme.colors.palette.white} />}
> >
<View style={styles.connectRow}> {isSaving ? "Pairing..." : "Pair"}
<Link size={16} color={theme.colors.palette.white} /> </Button>
<Text style={[styles.buttonText, styles.primaryButtonText]}>
{isSaving ? "Pairing..." : "Pair"}
</Text>
</View>
</Pressable>
</View> </View>
</AdaptiveModalSheet> </AdaptiveModalSheet>
); );
+129
View File
@@ -0,0 +1,129 @@
import type { PropsWithChildren, ReactElement } from "react";
import { Pressable, Text, View } from "react-native";
import type { PressableProps, StyleProp, TextStyle, ViewStyle } from "react-native";
import { StyleSheet } from "react-native-unistyles";
type ButtonVariant = "default" | "secondary" | "outline" | "ghost" | "destructive";
type ButtonSize = "sm" | "md" | "lg";
const styles = StyleSheet.create((theme) => ({
base: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: theme.spacing[2],
borderRadius: theme.borderRadius.lg,
borderWidth: 1,
borderColor: "transparent",
},
md: {
paddingVertical: theme.spacing[3],
paddingHorizontal: theme.spacing[4],
},
sm: {
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[3],
borderRadius: theme.borderRadius.md,
},
lg: {
paddingVertical: theme.spacing[4],
paddingHorizontal: theme.spacing[6],
borderRadius: theme.borderRadius.xl,
},
default: {
backgroundColor: theme.colors.palette.blue[500],
borderColor: theme.colors.palette.blue[500],
},
secondary: {
backgroundColor: theme.colors.surface2,
borderColor: theme.colors.border,
},
outline: {
backgroundColor: "transparent",
borderColor: theme.colors.border,
},
ghost: {
backgroundColor: "transparent",
borderColor: "transparent",
},
destructive: {
backgroundColor: theme.colors.destructive,
borderColor: theme.colors.destructive,
},
pressed: {
opacity: 0.85,
},
disabled: {
opacity: theme.opacity[50],
},
text: {
color: theme.colors.foreground,
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.medium,
},
textDefault: {
color: theme.colors.palette.white,
},
textDestructive: {
color: theme.colors.palette.white,
},
}));
export function Button({
children,
variant = "secondary",
size = "md",
leftIcon,
style,
textStyle,
disabled,
accessibilityRole,
...props
}: PropsWithChildren<
Omit<PressableProps, "style"> & {
variant?: ButtonVariant;
size?: ButtonSize;
leftIcon?: ReactElement | null;
style?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
}
>) {
const variantStyle =
variant === "default"
? styles.default
: variant === "secondary"
? styles.secondary
: variant === "outline"
? styles.outline
: variant === "ghost"
? styles.ghost
: styles.destructive;
const sizeStyle = size === "sm" ? styles.sm : size === "lg" ? styles.lg : styles.md;
const resolvedTextStyle = [
styles.text,
variant === "default" ? styles.textDefault : null,
variant === "destructive" ? styles.textDestructive : null,
textStyle,
];
return (
<Pressable
{...props}
accessibilityRole={accessibilityRole ?? "button"}
disabled={disabled}
style={({ pressed }) => [
styles.base,
sizeStyle,
variantStyle,
pressed ? styles.pressed : null,
disabled ? styles.disabled : null,
style,
]}
>
{leftIcon ? <View>{leftIcon}</View> : null}
<Text style={resolvedTextStyle}>{children}</Text>
</Pressable>
);
}
@@ -26,7 +26,7 @@ const styles = StyleSheet.create((theme) => ({
title: { title: {
color: theme.colors.foreground, color: theme.colors.foreground,
fontSize: theme.fontSize.xl, fontSize: theme.fontSize.xl,
fontWeight: theme.fontWeight.bold, fontWeight: theme.fontWeight.medium,
marginBottom: theme.spacing[3], marginBottom: theme.spacing[3],
textAlign: "center", textAlign: "center",
}, },
@@ -59,7 +59,7 @@ const styles = StyleSheet.create((theme) => ({
actionText: { actionText: {
color: theme.colors.foreground, color: theme.colors.foreground,
fontSize: theme.fontSize.base, fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.medium,
}, },
actionTextPrimary: { actionTextPrimary: {
color: theme.colors.palette.white, color: theme.colors.palette.white,