fix: replace PreviewModal motion with CSS animation, add motion test mock

- PreviewModal no longer uses motion/react — prevents test failures from
  AnimatePresence exit animations never completing in test env
- Add CSS animations for .ds-modal-backdrop (fade-in) and .ds-modal (scale-in)
- Add vitest alias to mock motion/react so AnimatePresence in other
  components (UpdaterPopup, ExamplesTab) completes synchronously in tests
This commit is contained in:
qiongyu1999 2026-05-30 16:22:23 +08:00
parent 9c331129e9
commit e367d2879e
4 changed files with 54 additions and 14 deletions

View file

@ -1,5 +1,4 @@
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { motion } from 'motion/react';
import { useT } from '../i18n'; import { useT } from '../i18n';
import { copyToClipboard } from '../lib/copy-to-clipboard'; import { copyToClipboard } from '../lib/copy-to-clipboard';
import { import {
@ -12,7 +11,6 @@ import {
} from '../runtime/exports'; } from '../runtime/exports';
import { buildSrcdoc } from '../runtime/srcdoc'; import { buildSrcdoc } from '../runtime/srcdoc';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { modalOverlay, modalContent } from '../motion';
export interface PreviewView { export interface PreviewView {
id: string; id: string;
@ -471,22 +469,14 @@ export function PreviewModal({
const canOpenTemplateShareMenu = canExportFiles || Boolean(previewShareUrl); const canOpenTemplateShareMenu = canExportFiles || Boolean(previewShareUrl);
return ( return (
<motion.div <div
className="ds-modal-backdrop" className="ds-modal-backdrop"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-label={`${title} preview`} aria-label={`${title} preview`}
variants={modalOverlay}
initial="hidden"
animate="visible"
exit="exit"
> >
<motion.div <div
className={`ds-modal ${fullscreen ? 'ds-modal-fullscreen' : ''}`} className={`ds-modal ${fullscreen ? 'ds-modal-fullscreen' : ''}`}
variants={modalContent}
initial="hidden"
animate="visible"
exit="exit"
> >
<header className="ds-modal-header"> <header className="ds-modal-header">
<div className="ds-modal-header-top"> <div className="ds-modal-header-top">
@ -941,7 +931,7 @@ export function PreviewModal({
</aside> </aside>
) : null} ) : null}
</div> </div>
</motion.div> </div>
</motion.div> </div>
); );
} }

View file

@ -462,6 +462,18 @@
================================================================ */ ================================================================ */
/* ================================================================
Preview modal
================================================================ */
.ds-modal-backdrop {
animation: od-fade-in 200ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
.ds-modal {
animation: od-scale-in 250ms cubic-bezier(0.23, 1, 0.32, 1) both;
}
/* ================================================================ /* ================================================================
Plugin detail view Plugin detail view
================================================================ */ ================================================================ */

View file

@ -0,0 +1,32 @@
import { forwardRef, type ComponentProps, type ElementType } from 'react';
function AnimatePresence({ children }: { children?: React.ReactNode }) {
return <>{children}</>;
}
const motionHandler: ProxyHandler<object> = {
get(_target, prop: string) {
const Component = forwardRef<unknown, ComponentProps<ElementType>>((props, ref) => {
const {
variants: _variants,
initial: _initial,
animate: _animate,
exit: _exit,
whileHover: _whileHover,
whileTap: _whileTap,
transition: _transition,
layout: _layout,
layoutId: _layoutId,
...rest
} = props as Record<string, unknown>;
const Tag = prop as ElementType;
return <Tag ref={ref} {...rest} />;
});
Component.displayName = `motion.${prop}`;
return Component;
},
};
const motion = new Proxy({}, motionHandler);
export { AnimatePresence, motion };

View file

@ -1,6 +1,12 @@
import { resolve } from 'node:path';
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';
export default defineConfig({ export default defineConfig({
resolve: {
alias: {
'motion/react': resolve(__dirname, 'tests/helpers/motion-mock.tsx'),
},
},
test: { test: {
environment: 'node', environment: 'node',
include: ['tests/**/*.test.{ts,tsx}'], include: ['tests/**/*.test.{ts,tsx}'],