mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-20 10:08:06 +00:00
Replaces `className` prop with `extraClassName` across Input and PasswordInput components for consistency. Updates PasswordInput to use numeric `topOffset` instead of string `topPosition` for better type safety and clearer intent. Adds uncontrolled mode support to PasswordInput with internal state management and optional `defaultShowPassword` prop. Removes unnecessary margin classes from various View components to clean up spacing.
103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
import { Ionicons } from "@expo/vector-icons";
|
|
import type React from "react";
|
|
import { useState } from "react";
|
|
import { TouchableOpacity } from "react-native";
|
|
import { Input } from "./common/Input";
|
|
|
|
// Discriminated union for password visibility control
|
|
type PasswordVisibilityControlled = {
|
|
value?: string;
|
|
onChangeText: (text: string) => void;
|
|
placeholder: string;
|
|
showPassword: boolean;
|
|
onShowPasswordChange: (show: boolean) => void;
|
|
topOffset?: number;
|
|
layout?: "tv" | "mobile";
|
|
};
|
|
|
|
type PasswordVisibilityUncontrolled = {
|
|
value?: string;
|
|
onChangeText: (text: string) => void;
|
|
placeholder: string;
|
|
showPassword?: never;
|
|
onShowPasswordChange?: never;
|
|
topOffset?: number;
|
|
layout?: "tv" | "mobile";
|
|
defaultShowPassword?: boolean;
|
|
};
|
|
|
|
type PasswordInputProps =
|
|
| PasswordVisibilityControlled
|
|
| PasswordVisibilityUncontrolled;
|
|
|
|
export const PasswordInput: React.FC<PasswordInputProps> = (props) => {
|
|
const {
|
|
value = "",
|
|
onChangeText,
|
|
placeholder,
|
|
topOffset = 14, // Default 14px for mobile
|
|
layout = "mobile",
|
|
} = props;
|
|
|
|
// Type guard to check if we're in controlled mode
|
|
const isControlled =
|
|
"showPassword" in props && "onShowPasswordChange" in props;
|
|
|
|
// Internal state for uncontrolled mode
|
|
const [internalShowPassword, setInternalShowPassword] = useState(() =>
|
|
!isControlled && "defaultShowPassword" in props
|
|
? ((props as PasswordVisibilityUncontrolled).defaultShowPassword ?? false)
|
|
: false,
|
|
);
|
|
|
|
// Use controlled value if available, otherwise use internal state
|
|
const showPassword = isControlled
|
|
? (props as PasswordVisibilityControlled).showPassword
|
|
: internalShowPassword;
|
|
|
|
const handleTogglePassword = () => {
|
|
if (isControlled) {
|
|
(props as PasswordVisibilityControlled).onShowPasswordChange(
|
|
!showPassword,
|
|
);
|
|
} else {
|
|
// For uncontrolled mode, toggle internal state
|
|
setInternalShowPassword(!showPassword);
|
|
}
|
|
};
|
|
|
|
// Generate top position style with validation
|
|
const getTopStyle = () => {
|
|
if (typeof topOffset !== "number" || Number.isNaN(topOffset)) {
|
|
console.warn(`Invalid topOffset value: ${topOffset}`);
|
|
return { top: 14 }; // Default fallback (14px for mobile)
|
|
}
|
|
return { top: topOffset };
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Input
|
|
placeholder={placeholder}
|
|
onChangeText={onChangeText}
|
|
value={value}
|
|
secureTextEntry={!showPassword}
|
|
extraClassName='pr-4'
|
|
/>
|
|
<TouchableOpacity
|
|
onPress={handleTogglePassword}
|
|
className={`absolute right-3 p-1 ${
|
|
layout === "tv" ? "h-10 justify-center" : ""
|
|
}`}
|
|
style={getTopStyle()}
|
|
>
|
|
<Ionicons
|
|
name={showPassword ? "eye-off" : "eye"}
|
|
size={24}
|
|
color='white'
|
|
/>
|
|
</TouchableOpacity>
|
|
</>
|
|
);
|
|
};
|