forked from 77media/video-flow
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
'use client';
|
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { ReactNode } from 'react';
|
|
|
|
type FloatingGlassPanelProps = {
|
|
open: boolean;
|
|
clickMaskClose?: boolean;
|
|
onClose?: () => void;
|
|
children: ReactNode;
|
|
width?: string;
|
|
r_key?: string | number;
|
|
panel_style?: React.CSSProperties;
|
|
};
|
|
|
|
export default function FloatingGlassPanel({ open, onClose, children, width = '320px', r_key, panel_style, clickMaskClose = true }: FloatingGlassPanelProps) {
|
|
// 定义弹出动画
|
|
const bounceAnimation = {
|
|
scale: [0.95, 1.02, 0.98, 1],
|
|
rotate: [0, -1, 1, -1, 0],
|
|
};
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{open && (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
<motion.div
|
|
key={r_key}
|
|
className="cursor-grab active:cursor-grabbing"
|
|
drag
|
|
dragElastic={0.2}
|
|
dragMomentum={false}
|
|
initial={{ opacity: 0, scale: 0.95, rotate: 0 }}
|
|
animate={{
|
|
opacity: 1,
|
|
...bounceAnimation,
|
|
}}
|
|
exit={{ opacity: 0, scale: 0.95, rotate: 0 }}
|
|
transition={{
|
|
duration: 0.6,
|
|
ease: [0.19, 1, 0.22, 1],
|
|
scale: {
|
|
duration: 0.4,
|
|
times: [0, 0.3, 0.6, 1]
|
|
},
|
|
rotate: {
|
|
duration: 0.4,
|
|
times: [0, 0.2, 0.4, 0.6, 1]
|
|
}
|
|
}}
|
|
>
|
|
<div
|
|
style={{ width, ...panel_style }}
|
|
className="rounded-xl backdrop-blur-md bg-white/10 border border-white/20 shadow-xl text-white p-4"
|
|
>
|
|
{children}
|
|
</div>
|
|
</motion.div>
|
|
{/* 添加遮罩层,点击时关闭面板 */}
|
|
<motion.div
|
|
className="fixed inset-0 bg-black/20 backdrop-blur-sm -z-10"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
onClick={clickMaskClose ? onClose : undefined }
|
|
/>
|
|
</div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|