Files
no-whatever/src/components/SwipeGuide.tsx
T
kurihada 1b06f4fc0e feat: 新用户滑动引导,首次进入展示手势提示
- 首张卡片叠加半透明引导层,左滑不想去/右滑想去动画提示
- 支持点击消失、拖拽消失、3秒自动消失
- localStorage 记录标记,每个用户只展示一次
- 修复 React Strict Mode 下 effect 双执行导致引导不显示的问题
2026-02-24 17:43:12 +08:00

98 lines
3.4 KiB
TypeScript

"use client";
import { useEffect, useState, useCallback } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ThumbsDown, ThumbsUp } from "lucide-react";
const STORAGE_KEY = "nw_guide_v2";
interface SwipeGuideProps {
onDismiss: () => void;
}
export default function SwipeGuide({ onDismiss }: SwipeGuideProps) {
const [visible, setVisible] = useState(() => {
if (typeof window === "undefined") return false;
return !localStorage.getItem(STORAGE_KEY);
});
const dismiss = useCallback(() => {
setVisible(false);
localStorage.setItem(STORAGE_KEY, "1");
onDismiss();
}, [onDismiss]);
useEffect(() => {
if (!visible) {
onDismiss();
return;
}
const timer = setTimeout(dismiss, 3000);
return () => clearTimeout(timer);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<AnimatePresence>
{visible && (
<motion.div
className="absolute inset-0 z-20 flex items-center justify-center rounded-2xl bg-black/50 backdrop-blur-[2px]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.25 }}
onClick={dismiss}
onTouchStart={dismiss}
>
<div className="flex w-full items-center justify-between px-8">
<motion.div
className="flex flex-col items-center gap-2"
initial={{ x: 0 }}
animate={{ x: [0, -24, 0] }}
transition={{ duration: 1.2, repeat: Infinity, ease: "easeInOut" }}
>
<div className="flex h-14 w-14 items-center justify-center rounded-full bg-rose-500/80">
<ThumbsDown size={26} className="text-white" />
</div>
<span className="text-sm font-bold text-white"></span>
<span className="text-[10px] text-white/60"> </span>
</motion.div>
<motion.div
className="flex flex-col items-center gap-1"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.2 }}
>
<motion.div
className="h-8 w-0.5 rounded-full bg-white/30"
animate={{ scaleY: [1, 0.6, 1] }}
transition={{ duration: 1.5, repeat: Infinity }}
/>
<span className="text-[10px] font-medium text-white/40"></span>
<motion.div
className="h-8 w-0.5 rounded-full bg-white/30"
animate={{ scaleY: [0.6, 1, 0.6] }}
transition={{ duration: 1.5, repeat: Infinity }}
/>
</motion.div>
<motion.div
className="flex flex-col items-center gap-2"
initial={{ x: 0 }}
animate={{ x: [0, 24, 0] }}
transition={{ duration: 1.2, repeat: Infinity, ease: "easeInOut" }}
>
<div className="flex h-14 w-14 items-center justify-center rounded-full bg-emerald-500/80">
<ThumbsUp size={26} className="text-white" />
</div>
<span className="text-sm font-bold text-white"></span>
<span className="text-[10px] text-white/60"> </span>
</motion.div>
</div>
</motion.div>
)}
</AnimatePresence>
);
}