Treat the appearance setting as the rendered base size so the type ramp scales predictably. Native starts one pixel larger while web and desktop preserve the authored 14px base, with legacy values migrated on load.
125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
import { useCallback, useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Pressable, Text, View } from "react-native";
|
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
import { QrCode, Link2, ClipboardPaste } from "lucide-react-native";
|
|
import { AdaptiveModalSheet, type SheetHeader } from "./adaptive-modal-sheet";
|
|
import { isFdroidBuild } from "@/constants/build-profile";
|
|
import { isNative } from "@/constants/platform";
|
|
|
|
const styles = StyleSheet.create((theme) => ({
|
|
option: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: theme.spacing[4],
|
|
padding: theme.spacing[4],
|
|
borderRadius: theme.borderRadius.xl,
|
|
backgroundColor: theme.colors.surface2,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
},
|
|
optionText: {
|
|
color: theme.colors.foreground,
|
|
fontSize: theme.fontSize.base,
|
|
fontWeight: theme.fontWeight.normal,
|
|
},
|
|
optionSubtext: {
|
|
color: theme.colors.foregroundMuted,
|
|
fontSize: theme.fontSize.base,
|
|
marginTop: theme.spacing[1],
|
|
},
|
|
optionBody: {
|
|
flex: 1,
|
|
},
|
|
}));
|
|
|
|
export interface AddHostMethodModalProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
onDirectConnection: () => void;
|
|
onScanQr: () => void;
|
|
onPasteLink: () => void;
|
|
}
|
|
|
|
export function AddHostMethodModal({
|
|
visible,
|
|
onClose,
|
|
onDirectConnection,
|
|
onScanQr,
|
|
onPasteLink,
|
|
}: AddHostMethodModalProps) {
|
|
const { theme } = useUnistyles();
|
|
const { t } = useTranslation();
|
|
const header = useMemo<SheetHeader>(() => ({ title: t("pairing.connectionMethods.title") }), [t]);
|
|
|
|
const handleDirect = useCallback(() => {
|
|
onDirectConnection();
|
|
}, [onDirectConnection]);
|
|
|
|
const handleScan = useCallback(() => {
|
|
onScanQr();
|
|
}, [onScanQr]);
|
|
|
|
const handlePaste = useCallback(() => {
|
|
onPasteLink();
|
|
}, [onPasteLink]);
|
|
|
|
return (
|
|
<AdaptiveModalSheet
|
|
header={header}
|
|
visible={visible}
|
|
onClose={onClose}
|
|
testID="add-host-method-modal"
|
|
>
|
|
<Pressable
|
|
style={styles.option}
|
|
onPress={handleDirect}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={t("pairing.connectionMethods.direct.title")}
|
|
testID="add-host-method-direct"
|
|
>
|
|
<Link2 size={18} color={theme.colors.foreground} />
|
|
<View style={styles.optionBody}>
|
|
<Text style={styles.optionText}>{t("pairing.connectionMethods.direct.title")}</Text>
|
|
<Text style={styles.optionSubtext}>
|
|
{t("pairing.connectionMethods.direct.description")}
|
|
</Text>
|
|
</View>
|
|
</Pressable>
|
|
|
|
{isNative && !isFdroidBuild ? (
|
|
<Pressable
|
|
style={styles.option}
|
|
onPress={handleScan}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={t("pairing.connectionMethods.scanQr.title")}
|
|
>
|
|
<QrCode size={18} color={theme.colors.foreground} />
|
|
<View style={styles.optionBody}>
|
|
<Text style={styles.optionText}>{t("pairing.connectionMethods.scanQr.title")}</Text>
|
|
<Text style={styles.optionSubtext}>
|
|
{t("pairing.connectionMethods.scanQr.description")}
|
|
</Text>
|
|
</View>
|
|
</Pressable>
|
|
) : null}
|
|
|
|
<Pressable
|
|
style={styles.option}
|
|
onPress={handlePaste}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={t("pairing.connectionMethods.pasteLink.title")}
|
|
testID="add-host-method-pair-link"
|
|
>
|
|
<ClipboardPaste size={18} color={theme.colors.foreground} />
|
|
<View style={styles.optionBody}>
|
|
<Text style={styles.optionText}>{t("pairing.connectionMethods.pasteLink.title")}</Text>
|
|
<Text style={styles.optionSubtext}>
|
|
{t("pairing.connectionMethods.pasteLink.description")}
|
|
</Text>
|
|
</View>
|
|
</Pressable>
|
|
</AdaptiveModalSheet>
|
|
);
|
|
}
|