1import { requireNativeView } from "expo";
2import { useState } from "react";
3import { processColor, View, type NativeSyntheticEvent, type ViewProps } from "react-native";
4
5import { flattenThemeColor } from "../../lib/mobileTheme";
6import { useUniwindTheme } from "../../lib/useUniwindTheme";
7import { SecretRevealButton } from "./SecretRevealButton";
8import type { SecretTextInputProps } from "./SecretTextInput.types";
9
10interface NativeSecretInputProps extends ViewProps {
11 readonly inputLabel: string;
12 readonly value: string;
13 readonly placeholder: string;
14 readonly textColor: number;
15 readonly placeholderColor: number;
16 readonly cursorColor: number;
17 readonly fontSize: number;
18 readonly editable: boolean;
19 readonly revealed: boolean;
20 readonly onChangeText: (event: NativeSyntheticEvent<{ readonly value: string }>) => void;
21 readonly onSubmit: (event: NativeSyntheticEvent<{ readonly value: string }>) => void;
22}
23
24const NativeSecretInput = requireNativeView<NativeSecretInputProps>("T3SecretInput");
25
26const FONT_SIZE = 16;
27const LINE_HEIGHT = 24;
28
29function nativeColor(value: string) {
30 const color = processColor(value);
31 return typeof color === "number" ? color : 0;
32}
33
34/**
35 * Android's React Native TextInput cannot opt out of credential autofill, so password managers
36 * offer logins above the keyboard. This field is a native EditText that reports no autofill type.
37 * The frame matches AppTextInput's.
38 */
39export function SecretTextInput(props: SecretTextInputProps) {
40 const theme = useUniwindTheme();
41 const surface = theme["--color-input"];
42 const [revealed, setRevealed] = useState(false);
43 return (
44 <View className="min-h-13.5 flex-row items-center gap-1 rounded-2xl border border-input-border bg-input pr-1.5 pl-3.5">
45 <NativeSecretInput
46 inputLabel={props.accessibilityLabel}
47 value={props.value}
48 placeholder={props.placeholder}
49 textColor={nativeColor(flattenThemeColor(theme["--color-foreground"], surface))}
50 placeholderColor={nativeColor(flattenThemeColor(theme["--color-placeholder"], surface))}
51 cursorColor={nativeColor(flattenThemeColor(theme["--color-focus"], surface))}
52 fontSize={FONT_SIZE}
53 editable={props.editable}
54 revealed={revealed}
55 onChangeText={(event) => props.onChangeText(event.nativeEvent.value)}
56 onSubmit={(event) => props.onSubmitEditing(event.nativeEvent.value)}
57 style={{ flex: 1, height: LINE_HEIGHT }}
58 />
59 <SecretRevealButton revealed={revealed} onPress={() => setRevealed(!revealed)} />
60 </View>
61 );
62}