154 lines
5.8 KiB
TypeScript
154 lines
5.8 KiB
TypeScript
import React from 'react'
|
|
import type { FDTestimonialBlock as FDTestimonialBlockProps, Media } from '@/payload-types'
|
|
import { FDImage } from '@/components/FDImage'
|
|
|
|
const bgMap: Record<string, {
|
|
section: string; card: string
|
|
quote: string; meta: string; name: string; accent: string
|
|
}> = {
|
|
gray: {
|
|
section: 'bg-fd-gray-light dark:bg-fd-navy',
|
|
card: 'bg-white dark:bg-white/10',
|
|
quote: 'text-fd-navy dark:text-white',
|
|
meta: 'text-fd-navy/60 dark:text-white/60',
|
|
name: 'text-fd-navy dark:text-white',
|
|
accent: 'text-fd-navy dark:text-fd-yellow',
|
|
},
|
|
white: {
|
|
section: 'bg-white dark:bg-fd-navy',
|
|
card: 'bg-fd-gray-light dark:bg-white/10',
|
|
quote: 'text-fd-navy dark:text-white',
|
|
meta: 'text-fd-navy/60 dark:text-white/60',
|
|
name: 'text-fd-navy dark:text-white',
|
|
accent: 'text-fd-navy dark:text-fd-yellow',
|
|
},
|
|
navy: {
|
|
section: 'bg-fd-navy',
|
|
card: 'bg-white/10',
|
|
quote: 'text-white',
|
|
meta: 'text-white/60',
|
|
name: 'text-white',
|
|
accent: 'text-fd-yellow',
|
|
},
|
|
}
|
|
|
|
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
|
|
|
|
const Avatar: React.FC<{ media: Media | undefined; name: string; size: number }> = ({ media, name, size }) => {
|
|
if (!media?.url) return null
|
|
return (
|
|
<div
|
|
className="rounded-full overflow-hidden flex-shrink-0"
|
|
style={{ width: size, height: size }}
|
|
>
|
|
<FDImage
|
|
media={media}
|
|
size="thumbnail"
|
|
className="w-full h-full object-cover"
|
|
sizes={`${size}px`}
|
|
fallbackAlt={name}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const FDTestimonialBlockComponent: React.FC<FDTestimonialBlockProps> = ({
|
|
heading,
|
|
testimonials,
|
|
layout = 'grid',
|
|
sectionBackground = 'gray',
|
|
anchorId,
|
|
}) => {
|
|
const theme = bgMap[sectionBackground ?? 'gray'] || bgMap.gray
|
|
const isFeatured = layout === 'featured'
|
|
|
|
return (
|
|
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${theme.section}`}>
|
|
<div className="max-w-[1200px] mx-auto px-6 md:px-8">
|
|
|
|
{heading && (
|
|
<h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${theme.accent}`}>
|
|
{heading}
|
|
</h2>
|
|
)}
|
|
|
|
{isFeatured && testimonials && testimonials.length > 0 ? (
|
|
<div className="flex flex-col gap-6">
|
|
{/* First testimonial — large */}
|
|
{(() => {
|
|
const t = testimonials[0]
|
|
const avatar = t.avatar as Media | undefined
|
|
return (
|
|
<div className={`${theme.card} ${cardRadius} px-8 md:px-16 py-10 md:py-16 flex flex-col gap-8`}>
|
|
<p className={`font-joey-medium text-xl md:text-2xl lg:text-3xl leading-relaxed ${theme.quote}`}>
|
|
“{t.quote}”
|
|
</p>
|
|
<div className="flex items-center gap-4">
|
|
<Avatar media={avatar} name={t.authorName} size={56} />
|
|
<div>
|
|
<p className={`font-joey-bold text-lg ${theme.name}`}>{t.authorName}</p>
|
|
<p className={`font-joey text-sm ${theme.meta}`}>
|
|
{t.authorRole}{t.authorRole && t.authorCompany ? ' · ' : ''}{t.authorCompany}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})()}
|
|
|
|
{testimonials.length > 1 && (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{testimonials.slice(1).map((t, i) => {
|
|
const avatar = t.avatar as Media | undefined
|
|
return (
|
|
<div key={i} className={`${theme.card} ${cardRadius} px-8 md:px-12 py-10 md:py-12 flex flex-col gap-6`}>
|
|
<p className={`font-joey-medium text-lg md:text-xl leading-relaxed ${theme.quote}`}>
|
|
“{t.quote}”
|
|
</p>
|
|
<div className="flex items-center gap-3">
|
|
<Avatar media={avatar} name={t.authorName} size={40} />
|
|
<div>
|
|
<p className={`font-joey-bold text-base ${theme.name}`}>{t.authorName}</p>
|
|
<p className={`font-joey text-xs ${theme.meta}`}>
|
|
{t.authorRole}{t.authorRole && t.authorCompany ? ' · ' : ''}{t.authorCompany}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 min-[820px]:grid-cols-3 gap-6">
|
|
{testimonials?.map((t, i) => {
|
|
const avatar = t.avatar as Media | undefined
|
|
return (
|
|
<div key={i} className={`${theme.card} ${cardRadius} px-8 md:px-10 py-10 md:py-12 flex flex-col gap-6`}>
|
|
<span className={`font-joey-heavy text-5xl leading-none ${theme.accent} opacity-30`}>
|
|
“
|
|
</span>
|
|
<p className={`font-joey-medium text-lg leading-relaxed -mt-4 ${theme.quote}`}>
|
|
{t.quote}
|
|
</p>
|
|
<div className="flex items-center gap-3 mt-auto">
|
|
<Avatar media={avatar} name={t.authorName} size={40} />
|
|
<div>
|
|
<p className={`font-joey-bold text-base ${theme.name}`}>{t.authorName}</p>
|
|
<p className={`font-joey text-sm ${theme.meta}`}>
|
|
{t.authorRole}{t.authorRole && t.authorCompany ? ' · ' : ''}{t.authorCompany}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|