88 lines
2.7 KiB
React
88 lines
2.7 KiB
React
import * as Dialog from '@radix-ui/react-dialog';
|
|
import { X } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
/**
|
|
* Modal 组件
|
|
* 使用 Radix UI Dialog 实现的模态弹窗
|
|
* @param {Object} props
|
|
* @param {boolean} props.isOpen - 是否打开
|
|
* @param {Function} props.onClose - 关闭回调
|
|
* @param {React.ReactNode} props.children - 子元素
|
|
* @param {'sm'|'md'|'lg'} props.maxWidth - 最大宽度
|
|
* @param {string} props.title - 标题(可选)
|
|
*/
|
|
const Modal = ({
|
|
isOpen,
|
|
onClose,
|
|
children,
|
|
maxWidth = 'md',
|
|
title
|
|
}) => {
|
|
// 最大宽度映射
|
|
const maxWidthMap = {
|
|
sm: 'max-w-sm',
|
|
md: 'max-w-lg',
|
|
lg: 'max-w-2xl'
|
|
};
|
|
|
|
return (
|
|
<Dialog.Root open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<Dialog.Portal forceMount>
|
|
{/* 遮罩层 */}
|
|
<Dialog.Overlay asChild>
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 bg-black/60 backdrop-blur-xl z-[100]"
|
|
/>
|
|
</Dialog.Overlay>
|
|
|
|
{/* 内容区 */}
|
|
<Dialog.Content asChild>
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
|
className={`
|
|
fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
|
|
glass-card ${maxWidthMap[maxWidth]} w-[calc(100%-2rem)] p-8
|
|
border border-white/10 shadow-2xl z-[101]
|
|
`}
|
|
>
|
|
{/* 关闭按钮 */}
|
|
<Dialog.Close asChild>
|
|
<button
|
|
className="absolute top-6 right-6 text-white/40 hover:text-white transition-colors"
|
|
aria-label="关闭"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</Dialog.Close>
|
|
|
|
{/* 标题 */}
|
|
{title && (
|
|
<Dialog.Title className="text-2xl font-serif mb-6">
|
|
{title}
|
|
</Dialog.Title>
|
|
)}
|
|
|
|
{/* 内容 */}
|
|
<div className="max-h-[70vh] overflow-y-auto pr-2 custom-scrollbar">
|
|
{children}
|
|
</div>
|
|
</motion.div>
|
|
</Dialog.Content>
|
|
</Dialog.Portal>
|
|
)}
|
|
</AnimatePresence>
|
|
</Dialog.Root>
|
|
);
|
|
};
|
|
|
|
export default Modal;
|