ui: 滑卡互动增强 — like 徽章动画、分类标签可读性、进度数字区分
- RestaurantCard: like 徽章实时弹跳动画,首次出现滑入效果 - RestaurantCard: 分类标签改为白底黑字,图片上始终清晰 - SwipeDeck: 进度数字加背景色块包裹,与用户名视觉分离
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useState, useEffect } from "react";
|
import { useCallback, useState, useEffect, useRef } from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { Star, MapPin, Clock, ExternalLink, Flame, Bookmark, ChevronLeft, ChevronRight } from "lucide-react";
|
import { Star, MapPin, Clock, ExternalLink, Flame, Bookmark, ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { Restaurant } from "@/types";
|
import { Restaurant } from "@/types";
|
||||||
import { getUserId, isRegistered } from "@/lib/userId";
|
import { getUserId, isRegistered } from "@/lib/userId";
|
||||||
@@ -118,6 +119,17 @@ function ImageGallery({ images, name }: { images: string[]; name: string }) {
|
|||||||
|
|
||||||
export default function RestaurantCard({ restaurant, likeCount = 0 }: RestaurantCardProps) {
|
export default function RestaurantCard({ restaurant, likeCount = 0 }: RestaurantCardProps) {
|
||||||
const [favorited, setFavorited] = useState(false);
|
const [favorited, setFavorited] = useState(false);
|
||||||
|
const [likeBounce, setLikeBounce] = useState(false);
|
||||||
|
const prevLikeRef = useRef(likeCount);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (likeCount > prevLikeRef.current) {
|
||||||
|
setLikeBounce(true);
|
||||||
|
const t = setTimeout(() => setLikeBounce(false), 600);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}
|
||||||
|
prevLikeRef.current = likeCount;
|
||||||
|
}, [likeCount]);
|
||||||
|
|
||||||
const images = restaurant.images?.filter(Boolean);
|
const images = restaurant.images?.filter(Boolean);
|
||||||
const hasImage = images && images.length > 0;
|
const hasImage = images && images.length > 0;
|
||||||
@@ -156,16 +168,32 @@ export default function RestaurantCard({ restaurant, likeCount = 0 }: Restaurant
|
|||||||
|
|
||||||
<div className="absolute bottom-3 left-4 flex items-center gap-1.5">
|
<div className="absolute bottom-3 left-4 flex items-center gap-1.5">
|
||||||
{restaurant.category && (
|
{restaurant.category && (
|
||||||
<span className="rounded-full bg-elevated/90 px-2.5 py-0.5 text-xs font-semibold text-gray-200 shadow-sm backdrop-blur-sm">
|
<span className="rounded-full bg-white/80 px-2.5 py-0.5 text-xs font-semibold text-gray-800 shadow-sm backdrop-blur-sm">
|
||||||
{restaurant.category}
|
{restaurant.category}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{likeCount > 0 && (
|
<AnimatePresence>
|
||||||
<span className="flex items-center gap-0.5 rounded-full bg-rose-500/90 px-2 py-0.5 text-xs font-semibold text-white shadow-sm backdrop-blur-sm">
|
{likeCount > 0 && (
|
||||||
<Flame size={11} />
|
<motion.span
|
||||||
{likeCount} 人想去
|
key="like-badge"
|
||||||
</span>
|
className="flex items-center gap-0.5 rounded-full bg-rose-500/90 px-2 py-0.5 text-xs font-semibold text-white shadow-sm backdrop-blur-sm"
|
||||||
)}
|
initial={{ opacity: 0, scale: 0.5, x: -8 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: likeBounce ? [1, 1.3, 1] : 1,
|
||||||
|
x: 0,
|
||||||
|
}}
|
||||||
|
exit={{ opacity: 0, scale: 0.5 }}
|
||||||
|
transition={likeBounce
|
||||||
|
? { scale: { duration: 0.4, ease: "easeInOut" }, default: { type: "spring", stiffness: 400, damping: 20 } }
|
||||||
|
: { type: "spring", stiffness: 400, damping: 20 }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Flame size={11} className={likeBounce ? "animate-pulse" : ""} />
|
||||||
|
{likeCount} 人想去
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,14 @@ function UserProgressBar({
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-x-3">
|
<div className="flex items-center gap-x-3">
|
||||||
<div className="flex flex-1 flex-wrap items-center gap-x-3 gap-y-1">
|
<div className="flex flex-1 flex-wrap items-center gap-x-3 gap-y-1">
|
||||||
<span className="flex items-center gap-1 text-[11px] tabular-nums text-accent">
|
<span className="flex items-center gap-1.5 text-[11px] text-accent">
|
||||||
<span className={`inline-flex h-4 w-4 items-center justify-center rounded-full ${myAvatarBg} text-[10px] leading-none`}>
|
<span className={`inline-flex h-4 w-4 items-center justify-center rounded-full ${myAvatarBg} text-[10px] leading-none`}>
|
||||||
{myAvatar}
|
{myAvatar}
|
||||||
</span>
|
</span>
|
||||||
你 {localIndex}/{total}
|
你
|
||||||
|
<span className="rounded bg-accent/10 px-1 py-px tabular-nums font-medium">
|
||||||
|
{localIndex}/{total}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{others.map(([id, count]) => {
|
{others.map(([id, count]) => {
|
||||||
const finished = count >= total;
|
const finished = count >= total;
|
||||||
@@ -49,13 +52,15 @@ function UserProgressBar({
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={id}
|
key={id}
|
||||||
className={`flex items-center gap-1 text-[11px] tabular-nums ${finished ? "text-emerald-400" : "text-muted"}`}
|
className={`flex items-center gap-1.5 text-[11px] ${finished ? "text-emerald-400" : "text-muted"}`}
|
||||||
>
|
>
|
||||||
<span className={`inline-flex h-4 w-4 items-center justify-center rounded-full ${bg} text-[10px] leading-none`}>
|
<span className={`inline-flex h-4 w-4 items-center justify-center rounded-full ${bg} text-[10px] leading-none`}>
|
||||||
{emoji}
|
{emoji}
|
||||||
</span>
|
</span>
|
||||||
{label && <span className="max-w-12 truncate">{label}</span>}
|
{label && <span className="max-w-12 truncate">{label}</span>}
|
||||||
{count}/{total}
|
<span className={`rounded px-1 py-px tabular-nums font-medium ${finished ? "bg-emerald-500/10" : "bg-elevated"}`}>
|
||||||
|
{count}/{total}
|
||||||
|
</span>
|
||||||
{finished && <Check size={10} className="text-emerald-400" />}
|
{finished && <Check size={10} className="text-emerald-400" />}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user