x
This commit is contained in:
1 parent
3ac0cb4f53
commit
5e724fae20
17 files changed
+2404
-278
No files matched your search
@@ -211,12 +211,27 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
}, [pathname])
|
||||
|
||||
const isHome = getPathWithoutLocale(pathname) === '/'
|
||||
const isTransparent = (isHome || isHeroFirst || Boolean(isTransparentPage)) && !scrolled && !mobileOpen && !searchOpen && !langSheetOpen
|
||||
const isTransparent = (isHeroFirst || Boolean(isTransparentPage)) && !scrolled && !mobileOpen && !searchOpen && !langSheetOpen
|
||||
const pathWithoutLocale = getPathWithoutLocale(pathname)
|
||||
const isCommercePath = ['/shop', '/products', '/category', '/cart', '/checkout', '/wishlist', '/profile', '/order', '/track']
|
||||
.some((route) => pathWithoutLocale === route || pathWithoutLocale.startsWith(`${route}/`))
|
||||
const hideDesktopHeader =
|
||||
(!isCommercePath && scrollingDown && !mobileOpen && !searchOpen && !langSheetOpen) ||
|
||||
const headerBehavior =
|
||||
(settings?.themeTokens as any)?.headerBehavior ||
|
||||
settings?.headerBehavior ||
|
||||
'hide_on_scroll'
|
||||
const mobileHeaderBehavior =
|
||||
(settings?.themeTokens as any)?.mobileHeaderBehavior ||
|
||||
settings?.mobileHeaderBehavior ||
|
||||
headerBehavior
|
||||
const mobileHeaderStyle =
|
||||
(settings?.themeTokens as any)?.mobileHeaderStyle ||
|
||||
settings?.mobileHeaderStyle ||
|
||||
'logo_center'
|
||||
|
||||
const isDesktopView = isClientReady && canRenderDesktopMenu
|
||||
const activeHeaderBehavior = isDesktopView ? headerBehavior : mobileHeaderBehavior
|
||||
const isHideOnScroll = activeHeaderBehavior === 'hide_on_scroll'
|
||||
const isCheckout = pathWithoutLocale === '/checkout' || pathWithoutLocale.startsWith('/checkout/')
|
||||
const hideHeader =
|
||||
(!isCheckout && isHideOnScroll && scrollingDown && !mobileOpen && !searchOpen && !langSheetOpen) ||
|
||||
(productStickyVisible && !mobileOpen && !searchOpen && !langSheetOpen)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -233,6 +248,138 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
|
||||
const isTwoTier = settings?.headerStyle === 'two_tier'
|
||||
|
||||
const renderMobileActionButtons = (includeSearch = true) => (
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
{includeSearch && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchOpen(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={iconButtonClass}
|
||||
aria-label="Search"
|
||||
>
|
||||
<Search className="h-4 w-4 transition-colors" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Link
|
||||
href="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="h-4 w-4 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleCart(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4 transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderMobileHamburger = () => (
|
||||
<button
|
||||
onClick={() => setMobileOpen((prev) => !prev)}
|
||||
className={iconButtonClass}
|
||||
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
{mobileOpen ? <X className="h-5 w-5" /> : <MenuIcon className="h-5 w-5" />}
|
||||
</button>
|
||||
)
|
||||
|
||||
const renderMobileNavbar = () => {
|
||||
if (mobileHeaderStyle === 'logo_left') {
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between gap-3 md:hidden">
|
||||
<div className="flex shrink-0 items-center">
|
||||
<Link href="/" onClick={() => setMobileOpen(false)} className="ui-brand-wordmark">
|
||||
{settings?.logoUrl ? (
|
||||
<Image src={settings.logoUrl} alt={brandName} width={150} height={38} unoptimized className="h-8 w-auto object-contain" />
|
||||
) : (
|
||||
<span className="text-xl font-bold uppercase tracking-[0.2em] text-text-main">{brandName}</span>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
{renderMobileActionButtons(true)}
|
||||
{renderMobileHamburger()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (mobileHeaderStyle === 'search_bar_below') {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2 md:hidden">
|
||||
<div className="flex w-full items-center justify-between gap-3">
|
||||
<div className="flex shrink-0 items-center">
|
||||
<Link href="/" onClick={() => setMobileOpen(false)} className="ui-brand-wordmark">
|
||||
{settings?.logoUrl ? (
|
||||
<Image src={settings.logoUrl} alt={brandName} width={140} height={35} unoptimized className="h-7 w-auto object-contain" />
|
||||
) : (
|
||||
<span className="text-lg font-bold uppercase tracking-[0.2em] text-text-main">{brandName}</span>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
{renderMobileActionButtons(false)}
|
||||
{renderMobileHamburger()}
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={handleSearch} className="relative w-full">
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={t('nav.searchPlaceholder', 'ค้นหาสินค้า กลิ่นหอม หรือหมวดหมู่...')}
|
||||
className="w-full rounded-full border border-black/10 bg-black/[0.04] py-1.5 pl-8 pr-3 text-xs text-text-main placeholder:text-muted-text/60 focus:outline-none focus:ring-1 focus:ring-brand-primary dark:border-white/10 dark:bg-white/[0.06]"
|
||||
/>
|
||||
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-text" />
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Default: 'logo_center'
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between md:hidden">
|
||||
<div className="flex items-center">
|
||||
{renderMobileHamburger()}
|
||||
</div>
|
||||
<div className="flex items-center justify-center text-center">
|
||||
<Link href="/" onClick={() => setMobileOpen(false)} className="ui-brand-wordmark text-center">
|
||||
{settings?.logoUrl ? (
|
||||
<Image src={settings.logoUrl} alt={brandName} width={150} height={38} unoptimized className="h-8 w-auto object-contain" />
|
||||
) : (
|
||||
<span className="text-xl font-bold uppercase tracking-[0.2em] text-text-main">{brandName}</span>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center justify-end">
|
||||
{renderMobileActionButtons(true)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
@@ -241,7 +388,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
className={`global-navbar fixed top-0 z-50 w-full border-b transition-all duration-300 ${
|
||||
isTwoTier ? 'px-4 py-2.5 md:px-8 md:py-3' : 'px-4 py-3.5 md:px-6 md:py-4'
|
||||
} ${
|
||||
hideDesktopHeader ? 'md:pointer-events-none md:-translate-y-full' : 'translate-y-0'
|
||||
hideHeader ? 'pointer-events-none -translate-y-full' : 'translate-y-0'
|
||||
} ${
|
||||
scrolled
|
||||
? 'border-black/5 bg-surface-page/95 shadow-lg shadow-black/5 backdrop-blur-xl'
|
||||
@@ -250,23 +397,17 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
: 'border-black/5 bg-surface-page/80 backdrop-blur-xl md:bg-surface-nav-rest md:backdrop-blur-none'
|
||||
}`}
|
||||
>
|
||||
{/* Mobile Navigation Bar (< md) */}
|
||||
{renderMobileNavbar()}
|
||||
|
||||
{/* Desktop Navigation Bar (>= md) */}
|
||||
{isTwoTier ? (
|
||||
<div className="flex w-full flex-col">
|
||||
{/* Top Tier: Logo Centered + Icons Right (Desktop) & Mobile Top Row */}
|
||||
<div className="hidden md:flex w-full flex-col">
|
||||
{/* Top Tier: Logo Centered + Icons Right (Desktop) */}
|
||||
<div className={`flex w-full items-center justify-between transition-all duration-300 md:grid md:grid-cols-3 ${
|
||||
scrolled ? 'max-h-0 opacity-0 overflow-hidden py-0' : 'max-h-24 py-1.5 opacity-100'
|
||||
}`}>
|
||||
<div className="flex items-center">
|
||||
{isClientReady && !canRenderDesktopMenu && (
|
||||
<button
|
||||
onClick={() => setMobileOpen((prev) => !prev)}
|
||||
className={iconButtonClass}
|
||||
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
{mobileOpen ? <X className="h-5 w-5" /> : <MenuIcon className="h-5 w-5" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center" />
|
||||
|
||||
<div className="flex items-center justify-center text-center">
|
||||
<Link
|
||||
@@ -422,7 +563,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="hidden md:flex w-full items-center justify-between gap-4">
|
||||
<div className="flex shrink-0 items-center">
|
||||
<Link
|
||||
href="/"
|
||||
@@ -542,16 +683,6 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isClientReady && !canRenderDesktopMenu && (
|
||||
<button
|
||||
onClick={() => setMobileOpen((prev) => !prev)}
|
||||
className={iconButtonClass}
|
||||
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
|
||||
>
|
||||
{mobileOpen ? <X className="h-5 w-5" /> : <MenuIcon className="h-5 w-5" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
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 { useState, useRef } from 'react'
|
||||
import { useState, useRef, useEffect, useMemo } from 'react'
|
||||
import type { Category, PageSection, Product } from '@homaroma/types'
|
||||
import { getCategoryImage } from '@homaroma/types'
|
||||
import ProductCard from '../ProductCard'
|
||||
@@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { customRegistry } from '../../config/customRegistry'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import { Autoplay, Pagination, Navigation } from 'swiper/modules'
|
||||
import ScrollReveal from '../ScrollReveal'
|
||||
import 'swiper/css'
|
||||
import 'swiper/css/pagination'
|
||||
import 'swiper/css/navigation'
|
||||
@@ -155,6 +156,129 @@ function HeroSection({ payload, lang }: { payload: Record<string, unknown>, lang
|
||||
)
|
||||
}
|
||||
|
||||
function HeroCarouselSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const slides = Array.isArray(payload.slides) ? payload.slides : []
|
||||
const autoplay = typeof payload.autoplay === 'boolean' ? payload.autoplay : true
|
||||
const interval = typeof payload.interval === 'number' ? payload.interval : 4500
|
||||
const showArrows = typeof payload.showArrows === 'boolean' ? payload.showArrows : true
|
||||
const showDots = typeof payload.showDots === 'boolean' ? payload.showDots : true
|
||||
const aspectRatio = typeof payload.aspectRatio === 'string' ? payload.aspectRatio : '21/7'
|
||||
const mobileAspectRatio = typeof payload.mobileAspectRatio === 'string' ? payload.mobileAspectRatio : '16/9'
|
||||
const backgroundColor = typeof payload.backgroundColor === 'string' ? payload.backgroundColor : undefined
|
||||
|
||||
if (!slides.length) return null
|
||||
|
||||
const getAspectClass = () => {
|
||||
switch (aspectRatio) {
|
||||
case '21/6': return 'md:aspect-[21/6]'
|
||||
case '21/7': return 'md:aspect-[21/7]'
|
||||
case '21/9': return 'md:aspect-[21/9]'
|
||||
case '16/5': return 'md:aspect-[16/5]'
|
||||
case '16/6': return 'md:aspect-[16/6]'
|
||||
case '4/1': return 'md:aspect-[4/1]'
|
||||
case '5/1': return 'md:aspect-[5/1]'
|
||||
default: return 'md:aspect-[21/7]'
|
||||
}
|
||||
}
|
||||
|
||||
const getMobileAspectClass = () => {
|
||||
switch (mobileAspectRatio) {
|
||||
case '16/9': return 'aspect-[16/9]'
|
||||
case '4/3': return 'aspect-[4/3]'
|
||||
case '1/1': return 'aspect-[1/1]'
|
||||
case '21/9': return 'aspect-[21/9]'
|
||||
default: return 'aspect-[16/9]'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="relative w-full overflow-hidden bg-surface-page" style={{ backgroundColor }}>
|
||||
<Swiper
|
||||
modules={[Autoplay, Pagination, Navigation]}
|
||||
slidesPerView={1}
|
||||
loop={slides.length > 1}
|
||||
autoplay={autoplay ? { delay: interval, disableOnInteraction: false } : false}
|
||||
pagination={showDots ? { clickable: true, bulletActiveClass: 'swiper-pagination-bullet-active !bg-brand-primary' } : false}
|
||||
navigation={showArrows}
|
||||
grabCursor={true}
|
||||
className="w-full select-none [&_.swiper-button-next]:text-white [&_.swiper-button-prev]:text-white [&_.swiper-button-next]:after:text-sm md:[&_.swiper-button-next]:after:text-base [&_.swiper-button-prev]:after:text-sm md:[&_.swiper-button-prev]:after:text-base [&_.swiper-button-next]:w-8 md:[&_.swiper-button-next]:w-10 [&_.swiper-button-next]:h-8 md:[&_.swiper-button-next]:h-10 [&_.swiper-button-prev]:w-8 md:[&_.swiper-button-prev]:w-10 [&_.swiper-button-prev]:h-8 md:[&_.swiper-button-prev]:h-10 [&_.swiper-button-next]:bg-black/35 [&_.swiper-button-prev]:bg-black/35 [&_.swiper-button-next]:backdrop-blur-sm [&_.swiper-button-prev]:backdrop-blur-sm [&_.swiper-button-next]:rounded-full [&_.swiper-button-prev]:rounded-full [&_.swiper-button-next]:hover:bg-black/60 [&_.swiper-button-prev]:hover:bg-black/60 [&_.swiper-pagination-bullet]:bg-white/60 [&_.swiper-pagination-bullet-active]:!bg-brand-primary [&_.swiper-pagination]:!bottom-2.5 md:[&_.swiper-pagination]:!bottom-4"
|
||||
>
|
||||
{slides.map((slide, index) => {
|
||||
const title = localizedFromRecord(slide, 'title', resolvedLang)
|
||||
const subtitle = localizedFromRecord(slide, 'subtitle', resolvedLang)
|
||||
const alt = localizedFromRecord(slide, 'alt', resolvedLang) || title || `Slide ${index + 1}`
|
||||
const buttonText = localizedFromRecord(slide, 'buttonText', resolvedLang)
|
||||
const imageUrl = typeof slide.imageUrl === 'string' ? slide.imageUrl : ''
|
||||
const mobileImageUrl = typeof slide.mobileImageUrl === 'string' && slide.mobileImageUrl ? slide.mobileImageUrl : imageUrl
|
||||
const linkUrl = typeof slide.linkUrl === 'string' ? slide.linkUrl : ''
|
||||
const focalPoint = typeof slide.focalPoint === 'object' && slide.focalPoint ? (slide.focalPoint as { x: string, y: string }) : undefined
|
||||
|
||||
if (!imageUrl) return null
|
||||
|
||||
const slideContent = (
|
||||
<div className={`relative w-full ${getMobileAspectClass()} ${getAspectClass()} overflow-hidden`}>
|
||||
{/* Desktop Image */}
|
||||
<div className="hidden md:block absolute inset-0 w-full h-full">
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={alt}
|
||||
fill
|
||||
unoptimized
|
||||
priority={index === 0}
|
||||
className="object-cover w-full h-full"
|
||||
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Mobile Image */}
|
||||
<div className="block md:hidden absolute inset-0 w-full h-full">
|
||||
<Image
|
||||
src={mobileImageUrl}
|
||||
alt={alt}
|
||||
fill
|
||||
unoptimized
|
||||
priority={index === 0}
|
||||
className="object-cover w-full h-full"
|
||||
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Optional Text Overlay */}
|
||||
{(title || subtitle || buttonText) && (
|
||||
<div className="absolute inset-0 z-10 bg-gradient-to-t from-black/70 via-black/20 to-transparent flex flex-col justify-end p-6 md:p-10">
|
||||
<div className="max-w-xl space-y-1.5 md:space-y-2">
|
||||
{subtitle && <p className="text-[10px] md:text-xs uppercase font-bold tracking-widest text-brand-accent drop-shadow-md">{subtitle}</p>}
|
||||
{title && <h3 className="text-lg md:text-3xl font-bold text-white drop-shadow-md leading-tight">{renderAccentTitle(title)}</h3>}
|
||||
{buttonText && (
|
||||
<span className="inline-block mt-2 px-4 py-1.5 bg-brand-primary text-white text-xs font-bold rounded-full shadow-lg hover:bg-brand-primary-hover transition-all">
|
||||
{buttonText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<SwiperSlide key={index}>
|
||||
{linkUrl ? (
|
||||
<Link href={linkUrl} className="block w-full h-full group cursor-pointer">
|
||||
{slideContent}
|
||||
</Link>
|
||||
) : (
|
||||
slideContent
|
||||
)}
|
||||
</SwiperSlide>
|
||||
)
|
||||
})}
|
||||
</Swiper>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function HeroVideoSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
@@ -1888,32 +2012,36 @@ function ContactFormSection({ payload, lang }: { payload: Record<string, unknown
|
||||
)
|
||||
}
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import ScrollReveal from '../ScrollReveal'
|
||||
|
||||
export default function DynamicPageRenderer({ sections, categories, lang: langProp }: DynamicPageRendererProps) {
|
||||
const i18nLang = useLang()
|
||||
// Use i18n.language reactively — switches immediately when user changes language
|
||||
const lang = langProp ?? i18nLang
|
||||
|
||||
useEffect(() => {
|
||||
const HERO_TYPES = new Set([
|
||||
'HERO',
|
||||
'HERO_V2',
|
||||
'HERO_VIDEO',
|
||||
'HERO_ARTICLE',
|
||||
'HERO_MINIMAL',
|
||||
'HERO_SPLIT_MINIMAL',
|
||||
'HERO_VIDEO_MINIMAL',
|
||||
])
|
||||
const firstActiveSection = sections.find(s => s.isActive && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR')
|
||||
if (!firstActiveSection && sections.length > 0 && sections.every(s => !s.isActive || s.type.toUpperCase() === 'ANNOUNCEMENT_BAR')) {
|
||||
return
|
||||
}
|
||||
const isHero = Boolean(firstActiveSection && HERO_TYPES.has(firstActiveSection.type.toUpperCase()))
|
||||
const HERO_TYPES = useMemo(() => new Set([
|
||||
'HERO',
|
||||
'HERO_V2',
|
||||
'HERO_VIDEO',
|
||||
'HERO_ARTICLE',
|
||||
'HERO_MINIMAL',
|
||||
'HERO_SPLIT_MINIMAL',
|
||||
'HERO_VIDEO_MINIMAL',
|
||||
'HERO_CAROUSEL',
|
||||
]), [])
|
||||
|
||||
const firstActiveSection = sections.find(s => s.isActive && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR' && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR_MINIMAL')
|
||||
const firstPayload = toRecord(firstActiveSection?.payload)
|
||||
const defaultSpacing = firstActiveSection?.type.toUpperCase() === 'HERO_CAROUSEL' ? 'below_header' : 'under_header'
|
||||
const firstHeaderSpacing = typeof firstPayload.headerSpacing === 'string' ? firstPayload.headerSpacing : defaultSpacing
|
||||
|
||||
const isHeroFirst = Boolean(
|
||||
firstActiveSection &&
|
||||
HERO_TYPES.has(firstActiveSection.type.toUpperCase()) &&
|
||||
firstHeaderSpacing === 'under_header'
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.dataset.heroFirst = isHero ? 'true' : 'false'
|
||||
document.documentElement.dataset.heroFirst = isHeroFirst ? 'true' : 'false'
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -1921,7 +2049,7 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
document.documentElement.dataset.heroFirst = 'false'
|
||||
}
|
||||
}
|
||||
}, [sections])
|
||||
}, [sections, isHeroFirst])
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
@@ -1930,10 +2058,13 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
|
||||
const payload = toRecord(section.payload)
|
||||
const type = section.type.toUpperCase()
|
||||
const isFirstContent = section.id === firstActiveSection?.id
|
||||
const needsTopSpacing = isFirstContent && !isHeroFirst
|
||||
|
||||
switch (type) {
|
||||
case 'ANNOUNCEMENT_BAR':
|
||||
return <AnnouncementBar key={section.id} payload={payload} lang={lang} />
|
||||
const rendered = (() => {
|
||||
switch (type) {
|
||||
case 'ANNOUNCEMENT_BAR':
|
||||
return <AnnouncementBar key={section.id} payload={payload} lang={lang} />
|
||||
case 'HERO':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
@@ -1948,6 +2079,12 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
)
|
||||
case 'HERO_VIDEO':
|
||||
return <HeroVideoSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'HERO_CAROUSEL':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
<HeroCarouselSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'HERO_MINIMAL':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
@@ -2124,7 +2261,18 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
}
|
||||
return null
|
||||
}
|
||||
})}
|
||||
})()
|
||||
|
||||
if (needsTopSpacing && rendered) {
|
||||
return (
|
||||
<div key={section.id} style={{ paddingTop: 'var(--navbar-bottom, 72px)' }} className="w-full">
|
||||
{rendered}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return rendered
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user