Bob AI: Add flip animation on hover to product cards. Implement CSS

This commit is contained in:
2026-02-23 20:03:49 +00:00
parent f1c667b415
commit fb0402b239

View File

@@ -111,53 +111,106 @@ const ProductCardItem = memo(({
} }
}, [isFromApi, onBuyClick, product, quantity]); }, [isFromApi, onBuyClick, product, quantity]);
const [isFlipped, setIsFlipped] = useState(false);
return ( return (
<article <article
className={cls("card group relative h-full flex flex-col gap-4 cursor-pointer p-4 rounded-theme-capped", cardClassName)} className={cls("card group relative h-full cursor-pointer", cardClassName)}
onClick={handleClick} onClick={handleClick}
role="article" role="article"
aria-label={`${product.name} - ${product.price}`} aria-label={`${product.name} - ${product.price}`}
onMouseEnter={() => setIsFlipped(true)}
onMouseLeave={() => setIsFlipped(false)}
style={{
perspective: "1000px",
}}
> >
<ProductImage <div
imageSrc={product.imageSrc} style={{
imageAlt={product.imageAlt || product.name} position: "relative",
isFavorited={product.isFavorited} width: "100%",
onFavoriteToggle={product.onFavorite} height: "100%",
imageClassName={imageClassName} transition: "transform 0.6s",
/> transformStyle: "preserve-3d",
transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)",
<div className="relative z-1 flex flex-col gap-3"> }}
<h3 className={cls("text-xl font-medium leading-[1.15] truncate", shouldUseLightText ? "text-background" : "text-foreground", cardNameClassName)}> >
{product.name} {/* Front of card */}
</h3> <div
style={{
<div className="flex items-center justify-between gap-4"> position: "absolute",
<div className={cls("flex items-center gap-2", quantityControlsClassName)}> width: "100%",
<QuantityButton height: "100%",
onClick={handleDecrement} backfaceVisibility: "hidden",
ariaLabel="Decrease quantity" }}
Icon={Minus} className={cls("flex flex-col gap-4 p-4 rounded-theme-capped", cardClassName)}
/> >
<span className={cls("text-base font-medium min-w-[2ch] text-center leading-[1]", shouldUseLightText ? "text-background" : "text-foreground")}> <ProductImage
{quantity} imageSrc={product.imageSrc}
</span> imageAlt={product.imageAlt || product.name}
<QuantityButton isFavorited={product.isFavorited}
onClick={handleIncrement} onFavoriteToggle={product.onFavorite}
ariaLabel="Increase quantity" imageClassName={imageClassName}
Icon={Plus}
/>
</div>
<Button
{...getButtonProps(
{
text: product.price,
props: product.priceButtonProps,
},
0,
theme.defaultButtonVariant
)}
/> />
<div className="relative z-1 flex flex-col gap-3">
<h3 className={cls("text-xl font-medium leading-[1.15] truncate", shouldUseLightText ? "text-background" : "text-foreground", cardNameClassName)}>
{product.name}
</h3>
<div className="flex items-center justify-between gap-4">
<div className={cls("flex items-center gap-2", quantityControlsClassName)}>
<QuantityButton
onClick={handleDecrement}
ariaLabel="Decrease quantity"
Icon={Minus}
/>
<span className={cls("text-base font-medium min-w-[2ch] text-center leading-[1]", shouldUseLightText ? "text-background" : "text-foreground")}>
{quantity}
</span>
<QuantityButton
onClick={handleIncrement}
ariaLabel="Increase quantity"
Icon={Plus}
/>
</div>
<Button
{...getButtonProps(
{
text: product.price,
props: product.priceButtonProps,
},
0,
theme.defaultButtonVariant
)}
/>
</div>
</div>
</div>
{/* Back of card */}
<div
style={{
position: "absolute",
width: "100%",
height: "100%",
backfaceVisibility: "hidden",
transform: "rotateY(180deg)",
}}
className={cls("flex flex-col gap-4 p-4 rounded-theme-capped justify-center items-center", cardClassName)}
>
<div className="flex flex-col gap-3 text-center">
<h3 className={cls("text-lg font-medium leading-[1.15]", shouldUseLightText ? "text-background" : "text-foreground")}>
{product.name}
</h3>
<p className={cls("text-sm leading-relaxed", shouldUseLightText ? "text-background/80" : "text-foreground/80")}>
{product.description || "Premium quality product"}
</p>
<div className={cls("text-2xl font-bold mt-2", shouldUseLightText ? "text-background" : "text-foreground")}>
{product.price}
</div>
</div>
</div> </div>
</div> </div>
</article> </article>