Files
paseo/packages/app/src/components/name-host-modal.tsx
T
2026-02-05 12:02:25 +07:00

136 lines
3.9 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Pressable, Text, TextInput, View } from "react-native";
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
import { AdaptiveModalSheet } from "./adaptive-modal-sheet";
const styles = StyleSheet.create((theme) => ({
helper: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
},
field: {
marginTop: theme.spacing[3],
gap: theme.spacing[2],
},
label: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.medium,
},
input: {
backgroundColor: theme.colors.surface2,
borderRadius: theme.borderRadius.lg,
paddingHorizontal: theme.spacing[4],
paddingVertical: theme.spacing[3],
color: theme.colors.foreground,
borderWidth: 1,
borderColor: theme.colors.border,
},
actions: {
flexDirection: "row",
gap: theme.spacing[3],
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 {
visible: boolean;
serverId: string;
hostname: string | null;
onSkip: () => void;
onSave: (label: string) => void;
}
export function NameHostModal({ visible, serverId, hostname, onSkip, onSave }: NameHostModalProps) {
const { theme } = useUnistyles();
const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const InputComponent = useMemo(() => (isMobile ? BottomSheetTextInput : TextInput), [isMobile]);
const [label, setLabel] = useState("");
const hasEditedRef = useRef(false);
const suggested = (hostname?.trim() || serverId).trim();
useEffect(() => {
if (!visible) return;
setLabel(suggested);
hasEditedRef.current = false;
}, [suggested, visible]);
useEffect(() => {
if (!visible) return;
if (hasEditedRef.current) return;
if (!hostname) return;
const trimmed = label.trim();
if (trimmed.length === 0 || trimmed === serverId) {
setLabel(hostname.trim());
}
}, [hostname, label, serverId, visible]);
const handleChange = useCallback((value: string) => {
hasEditedRef.current = true;
setLabel(value);
}, []);
const handleSave = useCallback(() => {
const trimmed = label.trim();
if (!trimmed) {
onSkip();
return;
}
onSave(trimmed);
}, [label, onSave, onSkip]);
return (
<AdaptiveModalSheet title="Name this host" visible={visible} onClose={onSkip} testID="name-host-modal">
<Text style={styles.helper}>
Optional. You can rename this later in Settings.
</Text>
<View style={styles.field}>
<Text style={styles.label}>Label</Text>
<InputComponent
value={label}
onChangeText={handleChange}
placeholder={suggested}
placeholderTextColor={theme.colors.foregroundMuted}
style={styles.input}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="done"
onSubmitEditing={handleSave}
/>
</View>
<View style={styles.actions}>
<Pressable style={styles.button} onPress={onSkip} testID="name-host-skip">
<Text style={styles.buttonText}>Skip</Text>
</Pressable>
<Pressable style={[styles.button, styles.primaryButton]} onPress={handleSave} testID="name-host-save">
<Text style={[styles.buttonText, styles.primaryButtonText]}>Save</Text>
</Pressable>
</View>
</AdaptiveModalSheet>
);
}