From 55861cf3cd65ee942bf799e54c8ba6a675e24da0 Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:36:32 -0700 Subject: [PATCH] feat: add closing animation to all modal dialogs and fix drag-dismiss bug --- src/components/common/DialogProvider.css | 25 ++++++++++ src/components/common/DialogProvider.tsx | 18 +++++-- src/components/common/FileImportModal.css | 24 ++++++++++ src/components/common/FileImportModal.tsx | 56 ++++++++++++++-------- src/components/common/OpenProjectModal.css | 24 ++++++++++ src/components/common/OpenProjectModal.tsx | 42 +++++++++++----- 6 files changed, 154 insertions(+), 35 deletions(-) diff --git a/src/components/common/DialogProvider.css b/src/components/common/DialogProvider.css index 0d7202e..4fba3e8 100644 --- a/src/components/common/DialogProvider.css +++ b/src/components/common/DialogProvider.css @@ -35,6 +35,31 @@ } } +@keyframes dialogFadeOut { + from { + opacity: 1; + transform: scale(1) translateY(0); + } + + to { + opacity: 0; + transform: scale(0.95) translateY(-10px); + } +} + +@keyframes overlayFadeOut { + from { opacity: 1; } + to { opacity: 0; } +} + +.dialog-overlay-closing { + animation: overlayFadeOut 0.15s ease-in forwards; +} + +.dialog-modal-closing { + animation: dialogFadeOut 0.15s ease-in forwards; +} + .dialog-header { display: flex; justify-content: space-between; diff --git a/src/components/common/DialogProvider.tsx b/src/components/common/DialogProvider.tsx index 15beb32..5e18460 100644 --- a/src/components/common/DialogProvider.tsx +++ b/src/components/common/DialogProvider.tsx @@ -66,11 +66,13 @@ interface DialogInfo { const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [dialog, setDialog] = useState(null); + const [isClosing, setIsClosing] = useState(false); const [inputValue, setInputValue] = useState(''); const [timeSigNumerator, setTimeSigNumerator] = useState(''); const [timeSigDenominator, setTimeSigDenominator] = useState(''); // eslint-disable-next-line @typescript-eslint/no-explicit-any const resolveRef = useRef<((value: any) => void) | null>(null); + const pendingValueRef = useRef(undefined); const openAlert = useCallback((message: string): Promise => { return new Promise((resolve) => { @@ -104,15 +106,23 @@ const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ children }) = }, []); const close = useCallback((value: unknown) => { + pendingValueRef.current = value; + setIsClosing(true); + }, []); + + const handleAnimationEnd = useCallback((e: React.AnimationEvent) => { + if (e.target !== e.currentTarget) return; + if (!isClosing) return; + setIsClosing(false); setDialog(null); setInputValue(''); setTimeSigNumerator(''); setTimeSigDenominator(''); if (resolveRef.current) { - resolveRef.current(value); + resolveRef.current(pendingValueRef.current); resolveRef.current = null; } - }, []); + }, [isClosing]); const mouseDownOnOverlay = useRef(false); @@ -161,8 +171,8 @@ const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ children }) = return ( <> {children} -
-
+
+

{title}

- +
= ({

Supported formats: {acceptedTypes.join(', ')}

- +
or
- +