refactor: 提取 Modal 基础组件,消除 4 个弹窗的重复样板代码
将 backdrop 遮罩、点击关闭、AnimatePresence 动画封装为 Modal 组件, 支持 sheet(底部弹出)和 dialog(居中缩放)两种变体,净减约 110 行。
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useRef } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useCallback } from "react";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { X, Copy, Share2, QrCode } from "lucide-react";
|
||||
import type { SceneType } from "@/types";
|
||||
import { getSceneConfig } from "@/lib/sceneConfig";
|
||||
import Modal from "@/components/Modal";
|
||||
|
||||
interface QrInviteModalProps {
|
||||
open: boolean;
|
||||
@@ -27,11 +27,6 @@ export default function QrInviteModal({
|
||||
typeof window !== "undefined"
|
||||
? `${window.location.origin}/invite/${roomId}`
|
||||
: "";
|
||||
const backdropRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === backdropRef.current) onClose();
|
||||
};
|
||||
|
||||
const handleCopy = useCallback(async () => {
|
||||
try {
|
||||
@@ -62,77 +57,57 @@ export default function QrInviteModal({
|
||||
}, [inviteUrl, handleCopy, sceneConfig]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
ref={backdropRef}
|
||||
className="fixed inset-0 z-50 flex items-end justify-center bg-black/60 backdrop-blur-sm sm:items-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
onClick={handleBackdropClick}
|
||||
>
|
||||
<motion.div
|
||||
className="relative w-full max-w-sm rounded-t-3xl bg-surface px-6 pb-8 pt-5 shadow-2xl ring-1 ring-border sm:rounded-3xl sm:pb-6"
|
||||
initial={{ y: "100%" }}
|
||||
animate={{ y: 0 }}
|
||||
exit={{ y: "100%" }}
|
||||
transition={{ type: "spring", damping: 28, stiffness: 350 }}
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 flex h-8 w-8 items-center justify-center rounded-full bg-elevated text-muted transition-colors active:bg-subtle"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-center gap-2 text-heading">
|
||||
<QrCode size={18} className="text-accent" />
|
||||
<h2 className="text-lg font-bold">邀请饭搭子</h2>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
{sceneConfig.qrSubtitle}
|
||||
</p>
|
||||
|
||||
<div className="mt-5 rounded-2xl border-2 border-dashed border-subtle bg-elevated/50 p-4">
|
||||
<QRCodeSVG
|
||||
value={inviteUrl}
|
||||
size={180}
|
||||
level="M"
|
||||
bgColor="transparent"
|
||||
fgColor="#e5e7eb"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<span className="text-xs text-muted">房间号</span>
|
||||
<span className="rounded-full bg-elevated px-3 py-1 font-mono text-base font-bold tracking-[0.2em] text-accent ring-1 ring-border">
|
||||
{roomId}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex w-full gap-2.5">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="flex h-11 flex-1 items-center justify-center gap-1.5 rounded-xl bg-elevated text-sm font-semibold text-secondary ring-1 ring-border transition-colors active:bg-subtle"
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 flex h-8 w-8 items-center justify-center rounded-full bg-elevated text-muted transition-colors active:bg-subtle"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-center gap-2 text-heading">
|
||||
<QrCode size={18} className="text-accent" />
|
||||
<h2 className="text-lg font-bold">邀请饭搭子</h2>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
{sceneConfig.qrSubtitle}
|
||||
</p>
|
||||
|
||||
<div className="mt-5 rounded-2xl border-2 border-dashed border-subtle bg-elevated/50 p-4">
|
||||
<QRCodeSVG
|
||||
value={inviteUrl}
|
||||
size={180}
|
||||
level="M"
|
||||
bgColor="transparent"
|
||||
fgColor="#e5e7eb"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<span className="text-xs text-muted">房间号</span>
|
||||
<span className="rounded-full bg-elevated px-3 py-1 font-mono text-base font-bold tracking-[0.2em] text-accent ring-1 ring-border">
|
||||
{roomId}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex w-full gap-2.5">
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="flex h-11 flex-1 items-center justify-center gap-1.5 rounded-xl bg-elevated text-sm font-semibold text-secondary ring-1 ring-border transition-colors active:bg-subtle"
|
||||
>
|
||||
<Copy size={15} />
|
||||
复制链接
|
||||
</button>
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="flex h-11 flex-1 items-center justify-center gap-1.5 rounded-xl bg-accent text-sm font-semibold text-white shadow-lg shadow-accent/20 transition-colors active:bg-accent-hover"
|
||||
>
|
||||
<Share2 size={15} />
|
||||
发送邀请
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<Copy size={15} />
|
||||
复制链接
|
||||
</button>
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="flex h-11 flex-1 items-center justify-center gap-1.5 rounded-xl bg-accent text-sm font-semibold text-white shadow-lg shadow-accent/20 transition-colors active:bg-accent-hover"
|
||||
>
|
||||
<Share2 size={15} />
|
||||
发送邀请
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user