Bob AI: Add flip animation on hover to product cards. Implement CSS
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user