x
This commit is contained in:
1 parent
46968782b6
commit
08045eabff
18 files changed
+943
-104
No files matched your search
@@ -2,11 +2,12 @@
|
||||
|
||||
import Image from 'next/image'
|
||||
import Link from '../LocalizedLink'
|
||||
import { Clock, Mail, MapPin, MessageCircle, Send, ChevronLeft, ChevronRight, Leaf, Droplet, Sparkles, Gift, CheckCircle, ShieldCheck, Heart, Download, FileText } from 'lucide-react'
|
||||
import { Clock, Mail, MapPin, MessageCircle, Send, ChevronLeft, ChevronRight, Leaf, Droplet, Sparkles, Gift, CheckCircle, ShieldCheck, Heart, Download, FileText, ArrowRight } from 'lucide-react'
|
||||
import { useState, useRef, useEffect, useMemo } from 'react'
|
||||
import type { Category, DividerSectionPayload, PageSection, Product } from '@homaroma/types'
|
||||
import { getCategoryImage, resolveCategoryMediaUrl } from '@homaroma/types'
|
||||
import ProductCard from '../ProductCard'
|
||||
import ProductSwatchCard from '../ProductSwatchCard'
|
||||
import { localizedFromRecord, resolveCategoryName, resolveLocalized, toRecord } from '../../lib/localization'
|
||||
import { useLang } from '../../hooks/useLang'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -1121,52 +1122,83 @@ function ProductsSection({ payload, lang }: { payload: Record<string, unknown>,
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang) || 'Featured Picks'
|
||||
const subtitle = localizedFromRecord(payload, 'subtitle', resolvedLang)
|
||||
const layout = typeof payload.layout === 'string' ? payload.layout : 'grid'
|
||||
const cardStyle = typeof payload.cardStyle === 'string' ? payload.cardStyle : 'standard'
|
||||
const viewAllLink = typeof payload.viewAllLink === 'string' && payload.viewAllLink.trim() ? payload.viewAllLink.trim() : '/shop'
|
||||
const viewAllText = localizedFromRecord(payload, 'viewAllText', resolvedLang) || t('dynamic.viewAll', { lng: resolvedLang }) || 'View All'
|
||||
const badgeText = localizedFromRecord(payload, 'badgeText', resolvedLang)
|
||||
const products = getProducts(payload)
|
||||
|
||||
const carouselRef = useRef<HTMLDivElement>(null)
|
||||
const scrollLeft = () => carouselRef.current?.scrollBy({ left: -300, behavior: 'smooth' })
|
||||
const scrollRight = () => carouselRef.current?.scrollBy({ left: 300, behavior: 'smooth' })
|
||||
|
||||
const isMinimal = cardStyle === 'minimal_swatch'
|
||||
|
||||
return (
|
||||
<section className={`bg-surface-page ${getSectionPaddingClasses(payload)}`}>
|
||||
<div className="ui-section-shell space-y-10">
|
||||
<div className="flex flex-col gap-4 border-b border-line-primary/20 pb-6 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<h2 className="ui-section-title">{title}</h2>
|
||||
{subtitle && <p className="mt-2 ui-section-subtitle">{subtitle}</p>}
|
||||
<div className="ui-section-shell space-y-8">
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-end md:justify-between border-b border-line-primary/10 pb-5">
|
||||
<div className="flex flex-col md:flex-row md:items-baseline md:gap-4">
|
||||
<h2 className={isMinimal ? "text-2xl md:text-3xl font-serif text-text-main tracking-tight" : "ui-section-title"}>
|
||||
{title}
|
||||
</h2>
|
||||
{subtitle && (
|
||||
<p className={isMinimal ? "text-xs md:text-sm text-muted-text font-normal mt-1 md:mt-0" : "mt-2 ui-section-subtitle"}>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Link href="/shop" className="ui-section-link">
|
||||
{t('dynamic.viewAll', { lng: resolvedLang })}
|
||||
<Link href={viewAllLink} className="inline-flex items-center gap-1.5 text-xs font-semibold text-text-main hover:text-brand-primary transition-colors underline-offset-4 hover:underline self-start md:self-auto">
|
||||
<span>{viewAllText}</span>
|
||||
<ArrowRight className="w-3.5 h-3.5" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{products.length ? (
|
||||
layout === 'carousel' ? (
|
||||
<div className="relative -mx-4 px-4 sm:mx-0 sm:px-0">
|
||||
<button
|
||||
onClick={scrollLeft}
|
||||
className="hidden md:flex bg-surface-card rounded-full shadow-card hover:text-brand-primary h-10 w-10 items-center justify-center absolute z-10 top-1/2 -translate-y-1/2 -left-5"
|
||||
className="hidden md:flex bg-surface-card rounded-full shadow-card hover:text-brand-primary h-9 w-9 items-center justify-center absolute z-10 top-1/2 -translate-y-1/2 -left-4 border border-line-primary/15"
|
||||
aria-label="Scroll left"
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</button>
|
||||
<div ref={carouselRef} className="flex overflow-x-auto snap-x snap-mandatory hide-scrollbar gap-4 pb-4">
|
||||
<div ref={carouselRef} className="flex overflow-x-auto snap-x snap-mandatory hide-scrollbar gap-4 md:gap-5 pb-4">
|
||||
{products.map((product) => (
|
||||
<div key={product.id} className="w-[85vw] sm:w-[300px] flex-none snap-start">
|
||||
<ProductCard product={product} />
|
||||
<div
|
||||
key={product.id}
|
||||
className={
|
||||
isMinimal
|
||||
? "w-[42vw] sm:w-[200px] md:w-[220px] lg:w-[240px] flex-none snap-start"
|
||||
: "w-[85vw] sm:w-[300px] flex-none snap-start"
|
||||
}
|
||||
>
|
||||
{isMinimal ? (
|
||||
<ProductSwatchCard product={product} badgeText={badgeText} />
|
||||
) : (
|
||||
<ProductCard product={product} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={scrollRight}
|
||||
className="hidden md:flex bg-surface-card rounded-full shadow-card hover:text-brand-primary h-10 w-10 items-center justify-center absolute z-10 top-1/2 -translate-y-1/2 -right-5"
|
||||
className="hidden md:flex bg-surface-card rounded-full shadow-card hover:text-brand-primary h-9 w-9 items-center justify-center absolute z-10 top-1/2 -translate-y-1/2 -right-4 border border-line-primary/15"
|
||||
aria-label="Scroll right"
|
||||
>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||
{products.map((product) => <ProductCard key={product.id} product={product} />)}
|
||||
<div className={isMinimal ? "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6" : "grid grid-cols-1 gap-8 md:grid-cols-3"}>
|
||||
{products.map((product) =>
|
||||
isMinimal ? (
|
||||
<ProductSwatchCard key={product.id} product={product} badgeText={badgeText} />
|
||||
) : (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
|
||||
Reference in new issue
Block a user