import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Text, TextInput, View } from "react-native"; import { StyleSheet } from "react-native-unistyles"; import { AdaptiveModalSheet, AdaptiveTextInput, type SheetHeader, } from "@/components/adaptive-modal-sheet"; import { Button } from "@/components/ui/button"; import { isWeb } from "@/constants/platform"; export interface AdaptiveRenameModalProps { visible: boolean; title: string; initialValue: string; placeholder?: string; submitLabel?: string; onClose: () => void; onSubmit: (value: string) => Promise | void; validate?: (value: string) => string | null; maxLength?: number; testID?: string; } export function AdaptiveRenameModal({ visible, title, initialValue, placeholder, submitLabel = "Rename", onClose, onSubmit, validate, maxLength, testID, }: AdaptiveRenameModalProps) { const [draft, setDraft] = useState(initialValue); const [error, setError] = useState(null); const [isPending, setIsPending] = useState(false); const inputRef = useRef(null); useEffect(() => { if (!visible) return; setDraft(initialValue); setError(null); setIsPending(false); }, [visible, initialValue]); useEffect(() => { if (!visible) return; const length = initialValue.length; const timeout = setTimeout(() => { const node = inputRef.current; if (!node) return; node.focus(); if (isWeb && node instanceof HTMLInputElement) { node.setSelectionRange(0, length); } else if (!isWeb && length > 0) { node.setNativeProps({ selection: { start: 0, end: length } }); } }, 50); return () => clearTimeout(timeout); }, [visible, initialValue]); const computeError = useCallback( (value: string): string | null => { if (!value.trim()) return "Name is required"; return validate ? validate(value) : null; }, [validate], ); const handleChange = useCallback((value: string) => { setDraft(value); setError(null); }, []); const handleSubmit = useCallback(async () => { if (isPending) return; const value = draft; if (value === initialValue) return; const validationError = computeError(value); if (validationError) { setError(validationError); return; } try { setIsPending(true); await onSubmit(value); setIsPending(false); onClose(); } catch (err) { setIsPending(false); const message = err instanceof Error && err.message ? err.message : "Unable to save"; setError(message); } }, [isPending, draft, initialValue, computeError, onSubmit, onClose]); const handleCancel = useCallback(() => { if (isPending) return; onClose(); }, [isPending, onClose]); const handleSubmitVoid = useCallback(() => { void handleSubmit(); }, [handleSubmit]); const submitDisabled = isPending || draft === initialValue || computeError(draft) !== null; const inputTestID = testID ? `${testID}-input` : undefined; const errorTestID = testID ? `${testID}-error` : undefined; const submitTestID = testID ? `${testID}-submit` : undefined; const cancelTestID = testID ? `${testID}-cancel` : undefined; const sheetHeader = useMemo(() => ({ title }), [title]); return ( {error ? ( {error} ) : null} ); } const styles = StyleSheet.create((theme) => ({ body: { gap: theme.spacing[3], paddingBottom: theme.spacing[2], }, input: { backgroundColor: theme.colors.surface0, color: theme.colors.foreground, paddingVertical: theme.spacing[3], paddingHorizontal: theme.spacing[3], borderRadius: theme.borderRadius.md, borderWidth: 1, borderColor: theme.colors.border, fontSize: theme.fontSize.base, }, errorText: { color: theme.colors.palette.red[300], fontSize: theme.fontSize.sm, }, actions: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], }, actionButton: { flex: 1, }, }));