import React, { ReactElement, useEffect, useMemo, useRef, useState } from 'react' import { createPortal } from 'react-dom' import EditIcon from 'remixicon-react/EditLineIcon' import { safeSetState } from '../../utils/common' import { Button } from '../Button/Button' import '../../styles/global.scss' import './RenameFileModal.scss' const maxFileNameLength = 60 interface RenameFileModalProps { currentName: string takenNames?: Set | string[] onCancelClick: () => void onProceed: (newName: string) => void | Promise } export function RenameFileModal({ currentName, takenNames, onCancelClick, onProceed, }: RenameFileModalProps): ReactElement { const [value, setValue] = useState(currentName) const [touched, setTouched] = useState(false) const [submitting, setSubmitting] = useState(false) const inputRef = useRef(null) const isMountedRef = useRef(true) useEffect(() => { isMountedRef.current = true return () => { isMountedRef.current = false } }, []) useEffect(() => { const t = setTimeout(() => inputRef.current?.focus(), 0) return () => clearTimeout(t) }, []) const taken = useMemo(() => { if (!takenNames) return new Set() return Array.isArray(takenNames) ? new Set(takenNames) : takenNames }, [takenNames]) const trimmed = useMemo(() => value.trim(), [value]) const error = useMemo(() => { if (!touched) return '' if (!trimmed) return 'Name is required.' if (trimmed === currentName) return 'Enter a different name.' if (/[\\/:*?"<>|]+/.test(trimmed)) return 'Name contains invalid characters.' if (taken.has(trimmed)) return 'A different file already uses this name. Please choose another.' return '' }, [touched, trimmed, currentName, taken]) const canSubmit = trimmed.length > 0 && trimmed !== currentName && !/[\\/:*?"<>|]+/.test(trimmed) && !taken.has(trimmed) const handleSubmit = async () => { if (!canSubmit || submitting) { setTouched(true) return } try { setSubmitting(true) await onProceed(trimmed) } finally { safeSetState(isMountedRef, setSubmitting)(false) } } const onKeyDown: React.KeyboardEventHandler = e => { if (e.key === 'Enter') { e.preventDefault() void handleSubmit() } else if (e.key === 'Escape') { e.preventDefault() onCancelClick() } } const modalRoot = (document.querySelector('.fm-main') as HTMLElement) || document.body return createPortal(
Rename file
setValue(e.target.value)} onBlur={() => setTouched(true)} onKeyDown={onKeyDown} placeholder="Enter a new file name" maxLength={maxFileNameLength} /> {error && (
{error}
)}
This creates a new version that only changes metadata (no re-upload).
, modalRoot, ) }