"use client"; import clsx from "clsx"; import { useState, useLayoutEffect, useRef } from "react"; import Portal from "@/components/Portal"; /** * 为了弹窗里的表单校验开发整个组件: * 当弹窗关闭,校验弹窗里的表单时要保证弹窗里的表单元素还存在文档流中,所以这个组件采用display block/none 来控制弹窗的显示和隐藏 */ export default function CommonModal({ isOpen, contentClassName, clickOutsideClose, onClose, header, body, footer }: { isOpen: boolean; contentClassName?: string; clickOutsideClose?: boolean; onClose: (e: boolean) => void; header: React.ReactNode; body: React.ReactNode; footer: React.ReactNode; }) { // show - 显示 // outing - 隐藏动画中 // hidden - 隐藏 const [rootVisible, setRootVisible] = useState<'show' | 'outing' | 'hidden'>('hidden'); const cssInit = useRef(false); const rootRef = useRef(null); const footerRef = useRef(null); const headerRef = useRef(null); const [rootPddingBottom, setRootPddingBottom] = useState(0); const [rootPddingTop, setRootPddingTop] = useState(0); const baseClassNname = "absolute box-border w-full bg-white transition-transform duration-300 ease-out"; let applyClassName = "bottom-0 left-0 h-17/20"; if(contentClassName) { applyClassName = contentClassName; } useLayoutEffect(() => { const original = document.documentElement.style.overflow; if(isOpen) { document.documentElement.style.overflow = "hidden"; } else { document.documentElement.style.overflow = ""; } if(isOpen) { setRootVisible('show'); } else { if(cssInit.current) { setRootVisible('outing'); } } return () => { document.documentElement.style.overflow = original; }; },[isOpen]); // 当弹窗完全显示后计算 footer 高度 useLayoutEffect(() => { if (rootVisible === 'show' && !cssInit.current && footerRef.current && headerRef.current) { const footerHeight = footerRef.current.getBoundingClientRect().height; const headerHeight = headerRef.current.getBoundingClientRect().height; setRootPddingBottom(footerHeight); setRootPddingTop(headerHeight); cssInit.current = true; } }, [rootVisible]); const rootClick = (e: React.MouseEvent) => { e.stopPropagation(); if(clickOutsideClose) { onClose(false); } } // 如果是关闭,动画结束之后才隐藏 const handlerAnimationEnd = (e: React.AnimationEvent) => { if(e.animationName === "fade-out") { setRootVisible('hidden'); } } // 初始化时是隐藏状态 // fade-in的时候display block + fade-in // fade-out的时候需要先加fade-out 类名,动画效果结束之后才能display none return (
{header}
{body}
{footer}
); }