This commit is contained in:
Your Name committed 2026-06-29 19:51:04 -04:00
1 parent 9922e8a5bc
commit 0144545166
4 files changed
+197 -16

No files matched your search

@@ -0,0 +1,65 @@
"use client"
import { useEffect, useState } from 'react';
import { useScrollReveal } from '../hooks/useScrollReveal';
// Animation style -> initial transform mapping
const STYLE_TRANSFORMS: Record<string, string> = {
'fade-up': 'translateY(28px)',
'fade-in': 'translateY(0)',
'slide-left': 'translateX(28px)',
'zoom-in': 'scale(0.95)',
'none': 'translateY(0)',
}
interface ScrollRevealProps {
children: React.ReactNode;
delay?: number; // ms
className?: string;
}
export default function ScrollReveal({ children, delay = 0, className = '' }: ScrollRevealProps) {
const { ref, isVisible } = useScrollReveal<HTMLDivElement>({
threshold: 0.08,
rootMargin: '0px 0px -40px 0px',
});
const [clientSettings, setClientSettings] = useState({
enabled: true,
style: 'fade-up',
durationMs: 700
});
useEffect(() => {
// Read from CSS variables injected by layout.tsx on the body tag
const body = document.body;
const enabled = body.style.getPropertyValue('--scroll-reveal-enabled').trim() !== '0';
const style = body.style.getPropertyValue('--scroll-reveal-style').trim() || 'fade-up';
const durationVar = body.style.getPropertyValue('--scroll-reveal-duration').trim() || '700ms';
const durationMs = parseInt(durationVar) || 700;
setClientSettings({ enabled, style, durationMs });
}, []);
// If animation is disabled or set to none, just render the children normally
if (!clientSettings.enabled || clientSettings.style === 'none') {
return <div className={className}>{children}</div>
}
const initialTransform = STYLE_TRANSFORMS[clientSettings.style] ?? STYLE_TRANSFORMS['fade-up'];
return (
<div
ref={ref}
className={className}
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translateY(0) translateX(0) scale(1)' : initialTransform,
transition: `opacity ${clientSettings.durationMs}ms ease-out ${delay}ms, transform ${clientSettings.durationMs}ms ease-out ${delay}ms`,
willChange: 'opacity, transform',
}}
>
{children}
</div>
);
}
@@ -865,6 +865,8 @@ function ContactFormSection({ payload, lang }: { payload: Record<string, unknown
)
}
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
@@ -881,37 +883,101 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
case 'ANNOUNCEMENT_BAR':
return <AnnouncementBar key={section.id} payload={payload} lang={lang} />
case 'HERO':
return <HeroSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<HeroSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'CATEGORIES':
return <CategoriesSection key={section.id} payload={payload} categories={categories} lang={lang} />
return (
<ScrollReveal key={section.id}>
<CategoriesSection payload={payload} categories={categories} lang={lang} />
</ScrollReveal>
)
case 'PRODUCTS':
return <ProductsSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<ProductsSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'PRODUCT_CAROUSEL':
return <ProductsSection key={section.id} payload={{ ...payload, layout: 'carousel' }} lang={lang} />
return (
<ScrollReveal key={section.id}>
<ProductsSection payload={{ ...payload, layout: 'carousel' }} lang={lang} />
</ScrollReveal>
)
case 'BANNER':
return <BannerSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<BannerSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'TEXT':
return <TextSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<TextSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'NEWSLETTER':
return <NewsletterSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<NewsletterSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'EDITORIAL_TILE':
return <EditorialTileSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<EditorialTileSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'EDITORIAL_CAROUSEL':
return <EditorialCarouselSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<EditorialCarouselSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'HERO_ARTICLE':
return <HeroArticleSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<HeroArticleSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'RICH_TEXT':
return <TextSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<TextSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'SCENT_NOTES_BREAKDOWN':
return <ScentNotesBreakdownSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<ScentNotesBreakdownSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'QUOTE_CALLOUT':
return <QuoteCalloutSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<QuoteCalloutSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'ACCORDION':
return <AccordionSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<AccordionSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'CONTACT_INFO':
return <ContactInfoSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<ContactInfoSection payload={payload} lang={lang} />
</ScrollReveal>
)
case 'CONTACT_FORM':
return <ContactFormSection key={section.id} payload={payload} lang={lang} />
return (
<ScrollReveal key={section.id}>
<ContactFormSection payload={payload} lang={lang} />
</ScrollReveal>
)
default:
if (process.env.NODE_ENV !== 'production') {
console.warn(`[DynamicPageRenderer] Unsupported section type: ${section.type}`)
@@ -922,3 +988,4 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
</div>
)
}