From 7f98e191bb6ecb8385c80a14c28da66ab495c3b6 Mon Sep 17 00:00:00 2001 From: Cho-P4 Date: Fri, 1 May 2026 18:48:43 +0800 Subject: [PATCH] Add open/close transition animations to SearchBar, image popover, delete confirm modal, and page routing - SearchBar: overlay fade + panel slide-down with spring enter / ease-in exit (200ms) - MarkdownToolbar image popover: scale+slide with same pattern (180ms) - ConfirmModal: new component replacing window.confirm, scale+translate animations - AdminPage: uses ConfirmModal for article deletion - AppShell: enter-only page transition (key={location.key}) avoids double-load issue All @keyframes defined locally in each CSS module to ensure reliable scoping. Co-Authored-By: Claude Sonnet 4.6 --- .claude/settings.local.json | 6 +- src/components/admin/MarkdownToolbar.jsx | 32 +++++++-- .../admin/MarkdownToolbar.module.css | 34 ++++++---- src/components/layout/AppShell.jsx | 5 +- src/components/layout/AppShell.module.css | 8 +++ src/components/ui/ConfirmModal.jsx | 55 +++++++++++++++ src/components/ui/ConfirmModal.module.css | 68 +++++++++++++++++++ src/components/ui/SearchBar.jsx | 50 ++++++++++---- src/components/ui/SearchBar.module.css | 26 ++++++- src/pages/AdminPage.jsx | 21 ++++-- 10 files changed, 262 insertions(+), 43 deletions(-) create mode 100644 src/components/layout/AppShell.module.css create mode 100644 src/components/ui/ConfirmModal.jsx create mode 100644 src/components/ui/ConfirmModal.module.css diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 735cc3c..949332b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,7 +6,11 @@ "mcp__Claude_in_Chrome__tabs_context_mcp", "Bash(git init *)", "Bash(git add *)", - "Bash(git commit -m ' *)" + "Bash(git commit -m ' *)", + "Bash(git remote *)", + "Bash(git push *)", + "Bash(git commit -m 'docs: 重写 README 为中文,添加英文版跳转链接 *)", + "Bash(git commit -m 'Add open/close transition animations to SearchBar, image popover, delete confirm modal, and page routing *)" ] } } diff --git a/src/components/admin/MarkdownToolbar.jsx b/src/components/admin/MarkdownToolbar.jsx index 64ebac2..5dc41c0 100644 --- a/src/components/admin/MarkdownToolbar.jsx +++ b/src/components/admin/MarkdownToolbar.jsx @@ -1,4 +1,4 @@ -import { useState, useRef } from 'react'; +import { useState, useRef, useCallback } from 'react'; import styles from './MarkdownToolbar.module.css'; const TOOLS = [ @@ -9,12 +9,30 @@ const TOOLS = [ { label: '', title: '代码', wrap: (s) => `${s}` }, ]; +const CLOSE_MS = 180; + export default function MarkdownToolbar({ textareaRef }) { const [imgPopover, setImgPopover] = useState(false); + const [popoverClosing, setPopoverClosing] = useState(false); const [imgUrl, setImgUrl] = useState(''); const [imgAlt, setImgAlt] = useState(''); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); + const closeTimer = useRef(null); + + const closePopover = useCallback(() => { + setPopoverClosing(true); + clearTimeout(closeTimer.current); + closeTimer.current = setTimeout(() => { + setImgPopover(false); + setPopoverClosing(false); + }, CLOSE_MS); + }, []); + + const togglePopover = useCallback(() => { + if (imgPopover) closePopover(); + else { setImgPopover(true); setPopoverClosing(false); } + }, [imgPopover, closePopover]); const insertAt = (html) => { const ta = textareaRef.current; @@ -52,7 +70,7 @@ export default function MarkdownToolbar({ textareaRef }) { insertAt(`${alt}`); setImgUrl(''); setImgAlt(''); - setImgPopover(false); + closePopover(); }; const handleFileChange = (e) => { @@ -69,7 +87,7 @@ export default function MarkdownToolbar({ textareaRef }) { const alt = file.name.replace(/\.[^.]+$/, '') || '图片'; insertAt(`${alt}`); setUploading(false); - setImgPopover(false); + closePopover(); e.target.value = ''; }; reader.readAsDataURL(file); @@ -94,16 +112,16 @@ export default function MarkdownToolbar({ textareaRef }) { {imgPopover && ( -
+

插入图片

@@ -162,7 +180,7 @@ export default function MarkdownToolbar({ textareaRef }) { diff --git a/src/components/admin/MarkdownToolbar.module.css b/src/components/admin/MarkdownToolbar.module.css index 25bbfcd..8248740 100644 --- a/src/components/admin/MarkdownToolbar.module.css +++ b/src/components/admin/MarkdownToolbar.module.css @@ -1,3 +1,12 @@ +@keyframes popoverIn { + from { opacity: 0; transform: translateY(-6px) scale(0.96); } + to { opacity: 1; transform: translateY(0) scale(1); } +} +@keyframes popoverOut { + from { opacity: 1; transform: translateY(0) scale(1); } + to { opacity: 0; transform: translateY(-6px) scale(0.96); } +} + .toolbarWrap { position: relative; } @@ -32,6 +41,12 @@ border-color: var(--color-muted); } +.toolActive { + background: var(--color-surface-2); + color: var(--color-ink); + border-color: var(--color-muted); +} + .separator { width: 1px; height: 20px; @@ -51,9 +66,12 @@ display: flex; flex-direction: column; gap: var(--space-4); - animation: slideDown 200ms cubic-bezier(0.34, 1.56, 0.64, 1); + transform-origin: top left; } +.popoverIn { animation: popoverIn 220ms cubic-bezier(0.34, 1.4, 0.64, 1) both; } +.popoverOut { animation: popoverOut 180ms cubic-bezier(0.4, 0, 0.8, 0) both; } + .popoverTitle { font-size: 0.875rem; font-weight: 600; @@ -93,14 +111,8 @@ width: 100%; } -.popoverInput:focus { - border-color: var(--color-accent-strong); -} - -.popoverInput::placeholder { - color: var(--color-muted); - font-size: 0.75rem; -} +.popoverInput:focus { border-color: var(--color-accent-strong); } +.popoverInput::placeholder { color: var(--color-muted); font-size: 0.75rem; } .divider { display: flex; @@ -130,6 +142,4 @@ transition: color var(--transition-fast); } -.popoverClose:hover { - color: var(--color-ink); -} +.popoverClose:hover { color: var(--color-ink); } diff --git a/src/components/layout/AppShell.jsx b/src/components/layout/AppShell.jsx index 10a1671..ac69aa1 100644 --- a/src/components/layout/AppShell.jsx +++ b/src/components/layout/AppShell.jsx @@ -3,6 +3,7 @@ import { useState, useEffect, useRef } from 'react'; import Header from './Header'; import Footer from './Footer'; import PetalCanvas from '../ui/PetalCanvas'; +import styles from './AppShell.module.css'; export default function AppShell() { const location = useLocation(); @@ -22,7 +23,9 @@ export default function AppShell() {
- +
+ +