feat: hovering thumbnail will jump to 2nd image thumbnail

This commit is contained in:
Nam Doan 2025-12-26 10:25:11 +07:00
parent 15011607ae
commit af3246770a
5 changed files with 367 additions and 400 deletions

View File

@ -103,8 +103,8 @@
"productCard": {
"badgeText": "Bestseller",
"card": "relative flex flex-col items-center bg-transparent shadow-none border-none p-0",
"badge": { "container": "absolute top-4 left-4", "text": "px-3 py-1 rounded-full bg-[#009b93] text-white text-[12px] font-semibold" },
"thumbnail": { "className": "rounded-2xl bg-white h-[320px] object-contain", "size": "full" },
"badge": { "container": "absolute top-0 left-0 z-[1]", "text": "px-3 py-1 rounded-full bg-[#009b93] text-white text-[12px] font-semibold" },
"thumbnail": { "className": "rounded-2xl bg-white h-[320px] object-contain shadow-none", "size": "full" },
"content": "flex flex-col items-center justify-start text-center p-0 mt-6",
"title": "text-[#003F31] text-[28px] font-semibold",
"description": "order-3 text-[#003f31b3]",

View File

@ -1,4 +1,5 @@
[
{
"layout": [
{
"Header": {
"config": {
@ -130,6 +131,118 @@
]
}
},
{ "PropsChildren": {} },
{
"Footer": {
"config": {
"className": "content-container border-none bg-[#1f3621] flex w-full border justify-between pb-8 gap-10 pt-16 px-[120px]",
"leftClassName": "flex ml-6 gap-x-24",
"centerClassName": "flex",
"rightClassName": "flex",
"left": [
{
"VtMenuItem": {
"config": {
"title": "Help",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white gap-8",
"itemClassName": "text-[14px] font-[400] mt-3",
"items": [
{ "text": "FAQ", "href": "/" },
{ "text": "Track my order", "href": "/categories/shoes" },
{ "text": "Placeholder", "href": "/categories/accessories" },
{ "text": "Placeholder", "href": "/categories/accessories" },
{ "text": "Placeholder", "href": "/categories/accessories" },
{ "text": "Placeholder", "href": "/categories/accessories" }
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Shop",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white",
"itemClassName": "text-[14px] font-[400] flex items-center mt-3",
"items": [
{ "text": "Twitter", "href": "/" },
{ "text": "Facebook", "href": "/categories/shoes" },
{ "text": "Pinterest", "href": "/categories/accessories" },
{ "text": "Placeholder", "href": "/categories/accessories" },
{ "text": "Placeholder", "href": "/categories/accessories" },
{ "text": "Placeholder", "href": "/categories/accessories" },
{ "text": "Placeholder", "href": "/categories/accessories" }
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Info",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white",
"itemClassName": "text-[14px] font-[400] w-[200px] mt-3",
"items": [
{ "text": "The Squatch Difference", "href": "/" },
{ "text": "Why Natural Products", "href": "/categories/shoes" },
{ "text": "No Harmful Ingredients", "href": "/categories/accessories" }
]
}
}
}
],
"center": [
{
"Logo": {
"config": {
"src": "/b-corp-logo.webp",
"alt": "MyShop",
"className": "h-auto w-[90px] mr-24",
"objectFit": "contain"
}
}
}
],
"right": [
{
"VtFooterSignUp": {
"config": {
"title": "Don't miss out on hot deals! Sign up and get up to 30% off.",
"className": "max-w-[760px]",
"titleClassName": "text-white text-[18px]",
"formClassName": "mt-2 w-full",
"inputClassName": "w-full",
"buttonClassName": "bg-[#C4622C] w-[90px]",
"socialsClassName": "mt-4 gap-8",
"socials": [
{ "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" },
{ "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" },
{ "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" },
{ "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" }
]
}
}
}
]
}
}
},
{
"VtFooterBottom": {
"config": {
"className": "text-white text-[14px] font-[400] bg-[#1f3621] flex items-center justify-center",
"text": "©2025 Vibentec IT. All rights reserved",
"linksClassName": "flex items-center text-orange-500 mt-2 pl-2",
"links": [
{ "label": "Privacy Policy", "href": "/" },
{ "label": "Terms of Service", "href": "/categories/shoes" },
{ "label": "Cookie Policy", "href": "/categories/accessories" }
]
}
}
}
],
"pages": {
"Home": [
{
"Hero": {
"config": {
@ -137,12 +250,8 @@
"ImageDisplayer": {
"config": {
"duration": 0,
"images": [
"./drsquatch-banner.jpg"
],
"links": [
"/account"
]
"images": ["./drsquatch-banner.jpg"],
"links": ["/account"]
}
},
"left": [
@ -174,7 +283,6 @@
"title": "drsquatch-best-seller",
"styles": {
"container": "content-container py-12 px-[100px] small:py-24",
"header": {
"container": "flex justify-center mb-8",
"title": "text-[28px] font-bold text-[#1f3521]",
@ -189,10 +297,7 @@
"container": "absolute z-[1] top-0 left-0 pt-4",
"text": "uppercase px-4 py-2 bg-[#3B6F47] text-white"
},
"thumbnail": {
"className": "rounded-none h-[300px] shadow-none",
"size": "full"
},
"thumbnail": { "className": "rounded-none h-[300px] shadow-none", "size": "full" },
"content": " flex flex-col flex-1",
"title": "mt-2 text-[#1f3521] text-[22px] font-bold",
"price": "mt-2 text-[#3B6F47] font-bold text-[20px] flex gap-3 flex-row-reverse justify-end",
@ -214,202 +319,14 @@
}
}
},
{
"CartMismatchBanner": {
"config": {
"show": true
}
}
},
{
"FreeShippingPriceNudge": {
"config": {
"variant": "popup"
}
}
},
{
"PropsChildren": {}
},
{
"Footer": {
"config": {
"className": "content-container border-none bg-[#1f3621] flex w-full border justify-between pb-8 gap-10 pt-16 px-[120px]",
"leftClassName": "flex ml-6 gap-x-24",
"centerClassName": "flex",
"rightClassName": "flex",
"left": [
{
"VtMenuItem": {
"config": {
"title": "Help",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white gap-8",
"itemClassName": "text-[14px] font-[400] mt-3",
"items": [
{
"text": "FAQ",
"href": "/"
},
{
"text": "Track my order",
"href": "/categories/shoes"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
}
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Shop",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white",
"itemClassName": "text-[14px] font-[400] flex items-center mt-3",
"items": [
{
"text": "Twitter",
"href": "/"
},
{
"text": "Facebook",
"href": "/categories/shoes"
},
{
"text": "Pinterest",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
}
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Info",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white",
"itemClassName": "text-[14px] font-[400] w-[200px] mt-3",
"items": [
{
"text": "The Squatch Difference",
"href": "/"
},
{
"text": "Why Natural Products",
"href": "/categories/shoes"
},
{
"text": "No Harmful Ingredients",
"href": "/categories/accessories"
}
]
}
}
}
{ "CartMismatchBanner": { "config": { "show": true } } },
{ "FreeShippingPriceNudge": { "config": { "variant": "popup" } } }
],
"center": [
{
"Logo": {
"config": {
"src": "/b-corp-logo.webp",
"alt": "MyShop",
"className": "h-auto w-[90px] mr-24",
"objectFit": "contain"
}
}
}
"Product": [
{ "VtFeaturedProducts": { "config": { "title": "drsquatch-best-seller" } } }
],
"right": [
{
"VtFooterSignUp": {
"config": {
"title": "Don't miss out on hot deals! Sign up and get up to 30% off.",
"className": "max-w-[760px]",
"titleClassName": "text-white text-[18px]",
"formClassName": "mt-2 w-full",
"inputClassName": "w-full",
"buttonClassName": "bg-[#C4622C] w-[90px]",
"socialsClassName": "mt-4 gap-8",
"socials": [
{
"icon": "Twitter",
"href": "/",
"className": "w-5 h-5 text-white"
},
{
"icon": "Twitter",
"href": "/",
"className": "w-5 h-5 text-white"
},
{
"icon": "Twitter",
"href": "/",
"className": "w-5 h-5 text-white"
},
{
"icon": "Twitter",
"href": "/",
"className": "w-5 h-5 text-white"
}
"StorePage": [
{ "VtFeaturedProducts": { "config": { "title": "drsquatch-best-seller" } } }
]
}
}
}
]
}
}
},
{
"VtFooterBottom": {
"config": {
"className": "text-white text-[14px] font-[400] bg-[#1f3621] flex items-center justify-center",
"text": "©2025 Vibentec IT. All rights reserved",
"linksClassName": "flex items-center text-orange-500 mt-2 pl-2",
"links": [
{
"label": "Privacy Policy",
"href": "/"
},
{
"label": "Terms of Service",
"href": "/categories/shoes"
},
{
"label": "Cookie Policy",
"href": "/categories/accessories"
}
]
}
}
}
]

View File

@ -352,6 +352,38 @@
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "produkten",
"styles": {
"container": "content-container py-12 px-[100px] small:py-24",
"header": {
"container": "flex justify-between mb-8",
"title": "text-[56px] text-[#11314E]",
"isShowViewAll": false
},
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
"productCard": {
"card": "relative overflow-hidden rounded-2xl border border-[#285A86] bg-ui-bg-base shadow-elevation-card-rest h-full flex flex-col",
"badge": {
"container": "p-4",
"text": "z-20 px-3 py-1 border-[0.5px] rounded bg-[#c9e0f5] txt-compact-small-plus shadow-borders-base text-[#285A86]"
},
"thumbnail": { "className": "rounded-none h-[240px]", "size": "full" },
"subtitle": "text-ui-fg-subtle text-[14px]",
"content": "flex flex-col flex-1 justify-between p-4",
"title": "text-ui-fg-subtle text-[18px]",
"price": "flex items-center gap-x-1 text-[#285A86] font-bold border-b pb-4",
"button": {
"addToCart": "w-fit h-[40px] bg-black text-white rounded-md",
"moreInfo": "w-fit h-[40px] border border-[#285A86] text-[#285A86] rounded-md"
}
}
}
}
}
},
{ "CartMismatchBanner": { "config": { "show": true } } },
{ "FreeShippingPriceNudge": { "config": { "variant": "popup" } } }
]

View File

@ -22,12 +22,18 @@ const VtThumbnail: React.FC<ThumbnailProps> = ({
className,
"data-testid": dataTestid,
}) => {
const initialImage = thumbnail || images?.[0]?.url
const imageUrls = images?.map((i: any) => i.url) || []
const initialImage = thumbnail || imageUrls?.[0]
let hoverImage: string | undefined = initialImage
if (imageUrls.length > 1) {
hoverImage = imageUrls.find((u) => u !== initialImage)
}
return (
<Container
className={clx(
"relative w-full overflow-hidden p-4 bg-ui-bg-subtle shadow-elevation-card-rest group-hover:shadow-elevation-card-hover transition-shadow ease-in-out duration-150",
"group relative w-full overflow-hidden p-4 bg-ui-bg-subtle shadow-elevation-card-rest group-hover:shadow-elevation-card-hover transition-shadow ease-in-out duration-150",
className,
{
"aspect-[11/14]": isFeatured,
@ -41,7 +47,18 @@ const VtThumbnail: React.FC<ThumbnailProps> = ({
)}
data-testid={dataTestid}
>
<ImageOrPlaceholder image={initialImage} size={size} />
<ImageOrPlaceholder
image={initialImage}
size={size}
className="opacity-100 group-hover:opacity-0 transition-opacity duration-300"
/>
{hoverImage && (
<ImageOrPlaceholder
image={hoverImage}
size={size}
className="opacity-0 group-hover:opacity-100 transition-opacity duration-300"
/>
)}
</Container>
)
}
@ -49,19 +66,20 @@ const VtThumbnail: React.FC<ThumbnailProps> = ({
const ImageOrPlaceholder = ({
image,
size,
}: Pick<ThumbnailProps, "size"> & { image?: string }) => {
className,
}: Pick<ThumbnailProps, "size"> & { image?: string; className?: string }) => {
return image ? (
<Image
src={image}
alt="Thumbnail"
className="absolute inset-0 object-cover object-center"
className={clx("absolute inset-0 object-cover object-center", className)}
draggable={false}
quality={50}
sizes="(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px"
fill
/>
) : (
<div className="w-full h-full absolute inset-0 flex items-center justify-center">
<div className={clx("w-full h-full absolute inset-0 flex items-center justify-center", className)}>
<PlaceholderImage size={size === "small" ? 16 : 24} />
</div>
)

View File

@ -2,7 +2,7 @@ import fs from "fs"
import path from "path"
import { jsonFileNames } from "./devJsonFileNames"
const fileName = jsonFileNames.nam3Bear
const fileName = jsonFileNames.namVibentec
async function readDesignFile() {
const filePath = path.join(process.cwd(), "config", fileName)