}
@@ -134,7 +134,7 @@ export default function MailView() {
}
return (
-
+
markRead(currentMail.mail_id)}
@@ -904,6 +904,8 @@ function ReplyBar({
// 跨断点回到默认,否则“自动隐藏”只在首次生效。
const [open, setOpen] = useState(false);
const boxRef = useRef(null);
+ /** 球的矩形:打开时存下来,收起时用它做反向 morph(球那时不在 DOM 上) */
+ const originRef = useRef(null);
useEffect(() => setOpen(false), [narrow]);
/*
@@ -939,6 +941,36 @@ function ReplyBar({
return () => anim.cancel();
}, [open]);
+ /*
+ * ★ 2026-09-15 用户:「收起没有动画」。打开有 morph、收起是瞬间消失 —— 不对称。
+ * 这里补反向:从当前形状缩回球的位置与大小,播完再卸载(先卸载就没得可播)。
+ */
+ const collapse = () => {
+ const el = boxRef.current;
+ const from = originRef.current;
+ if (!el || !from || window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+ setOpen(false);
+ return;
+ }
+ const to = el.getBoundingClientRect();
+ if (!to.width) {
+ setOpen(false);
+ return;
+ }
+ const scale = Math.max(0.04, from.width / to.width);
+ const dx = from.left + from.width / 2 - (to.left + to.width / 2);
+ const dy = from.top + from.height / 2 - (to.top + to.height / 2);
+ const anim = el.animate(
+ [
+ { transform: 'none', opacity: 1, borderRadius: '0px' },
+ { transform: `translate(${dx}px, ${dy}px) scale(${scale})`, opacity: 0.15, borderRadius: '28px' }
+ ],
+ { duration: 160, easing: 'cubic-bezier(0.4, 0, 0.6, 1)' }
+ );
+ // finished 在被取消时会 reject(组件卸载等),两种都得落到"收起"
+ anim.finished.then(() => setOpen(false)).catch(() => setOpen(false));
+ };
+
if (!replyTo) return null;
// 判据是「当前登录用户名」而不是字面量 'human':后者是单用户时代的遗留,
@@ -962,7 +994,9 @@ function ReplyBar({
data-testid="reply-fab"
data-morph-trigger
onClick={e => {
- // 起点就在手边:把这个球自己的矩形记下来,回复框挂载时从它长出来
+ // 起点就在手边:把这个球自己的矩形记下来 —— 打开时用它做 morph,
+ // 收起时还要用它把框"缩回球里"(那时球已经不在 DOM 上,取不到)
+ originRef.current = e.currentTarget.getBoundingClientRect();
rememberOrigin(e.currentTarget);
setOpen(true);
}}
@@ -1040,7 +1074,7 @@ function ReplyBar({
{ccOpen ? '收起抄送' : '抄送'}