feat: add FDButton component, fix button hover on navy, make CTA image optional

This commit is contained in:
Jeffrey 2026-02-19 20:30:43 +01:00
parent 1ab4e41c00
commit 80be2c4098
8 changed files with 179 additions and 199 deletions

View File

@ -2,19 +2,7 @@ import React from 'react'
import type { FDCtaSideImageBlock as FDCtaSideImageBlockProps } from '@/payload-types' import type { FDCtaSideImageBlock as FDCtaSideImageBlockProps } from '@/payload-types'
import type { Media } from '@/payload-types' import type { Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { FDButton } from '@/components/FDButton'
const overlayColorMap: Record<string, string> = {
navy: 'bg-fd-navy',
yellow: 'bg-fd-yellow',
black: 'bg-black',
}
const overlayOpacityMap: Record<string, string> = {
'20': 'opacity-20',
'30': 'opacity-30',
'50': 'opacity-50',
'70': 'opacity-70',
}
export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> = ({ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> = ({
heading, heading,
@ -24,69 +12,61 @@ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> =
image, image,
imagePosition = 'right', imagePosition = 'right',
theme = 'dark', theme = 'dark',
customBackgroundColor,
customTextLight = true,
imageOverlay = 'none',
imageOverlayOpacity = '30',
}) => { }) => {
const isCustom = theme === 'custom' const isDark = theme === 'dark'
const isDark = isCustom ? customTextLight : theme === 'dark' const hasImage = !!image
const media = image as Media
let sectionBg: string
let sectionStyle: React.CSSProperties = {}
if (isCustom && customBackgroundColor) {
sectionStyle = { backgroundColor: customBackgroundColor }
sectionBg = ''
} else {
sectionBg = isDark ? 'bg-fd-navy' : 'bg-white'
}
const hasOverlay = imageOverlay && imageOverlay !== 'none'
const overlayColor = overlayColorMap[imageOverlay || ''] || ''
const overlayOpacity = overlayOpacityMap[imageOverlayOpacity || '30'] || 'opacity-30'
const textContent = ( const textContent = (
<div className="flex flex-col flex-1 items-start gap-8 lg:gap-[41px]"> <div className={`flex flex-col flex-1 items-start gap-8 lg:gap-[41px] ${!hasImage ? 'max-w-2xl' : ''}`}>
<div className="flex flex-col items-start gap-4 w-full"> <div className="flex flex-col items-start gap-4 w-full">
<h2 className={`w-full font-joey-heavy text-fd-h1 ${isDark ? 'text-fd-yellow' : 'text-fd-navy'}`}> <h2
className={`w-full font-joey-heavy text-3xl md:text-4xl lg:text-5xl leading-tight lg:leading-[57.6px] ${
isDark ? 'text-fd-yellow' : 'text-fd-navy'
}`}
>
{heading} {heading}
</h2> </h2>
<p className={`w-full font-joey text-fd-body-lg ${isDark ? 'text-white' : 'text-fd-navy'}`}> <p
className={`w-full font-joey text-lg md:text-xl lg:text-2xl leading-relaxed lg:leading-[38px] ${
isDark ? 'text-white' : 'text-fd-navy'
}`}
>
{body} {body}
</p> </p>
</div> </div>
{ctaText && ( {ctaText && (
<a <FDButton href={ctaLink || '#'} variant="primary" onDark={isDark}>
href={ctaLink || '#'}
className={isDark ? 'fd-btn-primary' : 'fd-btn-secondary'}
>
{ctaText} {ctaText}
</a> </FDButton>
)} )}
</div> </div>
) )
const imageContent = media?.url ? ( const imageContent = hasImage ? (
<div className="relative w-full lg:w-[575px] h-[350px] lg:h-[479px] overflow-hidden rounded-[70px]"> <div className="w-full lg:w-[575px] lg:h-[479px] flex-shrink-0">
<FDImage <FDImage
media={media} media={image as Media}
size="large" fallbackAlt={heading}
fill className="w-full h-full object-cover rounded-[70px]"
className="object-cover"
sizes="(max-width: 1024px) 100vw, 575px"
fallbackAlt={heading || ''}
/> />
{hasOverlay && (
<div className={`absolute inset-0 ${overlayColor} ${overlayOpacity}`} aria-hidden="true" />
)}
</div> </div>
) : null ) : null
return ( return (
<section className={`w-full py-16 lg:py-[79px] ${sectionBg}`} style={sectionStyle}> <section className={`w-full py-16 lg:py-[79px] ${isDark ? 'bg-fd-navy' : 'bg-white'}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col lg:flex-row items-center gap-10 lg:gap-16"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col lg:flex-row items-center gap-10 lg:gap-16">
{imagePosition === 'left' ? <>{imageContent}{textContent}</> : <>{textContent}{imageContent}</>} {imagePosition === 'left' ? (
<>
{imageContent}
{textContent}
</>
) : (
<>
{textContent}
{imageContent}
</>
)}
</div> </div>
</section> </section>
) )

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDFeatureAnnouncementBlock as FDFeatureAnnouncementBlockProps } from '@/payload-types' import type { FDFeatureAnnouncementBlock as FDFeatureAnnouncementBlockProps } from '@/payload-types'
import { FDButton } from '@/components/FDButton'
export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncementBlockProps> = ({ export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncementBlockProps> = ({
heading, heading,
@ -8,31 +9,31 @@ export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncement
ctaLink = '#', ctaLink = '#',
theme = 'gray', theme = 'gray',
}) => { }) => {
const bgClass = const isDark = theme === 'dark'
theme === 'dark'
? 'bg-fd-navy'
: theme === 'gray'
? 'bg-fd-gray'
: 'bg-white'
const headingColor = theme === 'dark' ? 'text-fd-yellow' : 'text-fd-navy' const bgClass = isDark ? 'bg-fd-navy' : theme === 'gray' ? 'bg-fd-gray' : 'bg-white'
const bodyColor = theme === 'dark' ? 'text-white' : 'text-fd-navy' const headingColor = isDark ? 'text-fd-yellow' : 'text-fd-navy'
const bodyColor = isDark ? 'text-white' : 'text-fd-navy'
return ( return (
<section className={`w-full py-20 md:py-28 lg:py-[173px] ${bgClass}`}> <section className={`w-full py-20 md:py-28 lg:py-[173px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-center gap-8"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-center gap-8">
<h2 className={`w-full max-w-[696px] font-joey-bold text-fd-h1 text-center ${headingColor}`}> <h2
className={`w-full max-w-[696px] font-joey-bold text-3xl md:text-4xl lg:text-[54px] text-center leading-tight lg:leading-[64.8px] ${headingColor}`}
>
{heading} {heading}
</h2> </h2>
<p className={`w-full max-w-[1112px] font-joey text-fd-h2 text-center ${bodyColor}`}> <p
className={`w-full max-w-[1112px] font-joey text-xl md:text-2xl lg:text-4xl text-center leading-relaxed lg:leading-[44px] ${bodyColor}`}
>
{body} {body}
</p> </p>
{ctaText && ( {ctaText && (
<a href={ctaLink || '#'} className="fd-btn-primary"> <FDButton href={ctaLink || '#'} variant="primary" onDark={isDark}>
{ctaText} {ctaText}
</a> </FDButton>
)} )}
</div> </div>
</section> </section>

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDHeroBlock as FDHeroBlockProps, Media } from '@/payload-types' import type { FDHeroBlock as FDHeroBlockProps, Media } from '@/payload-types'
import { FDButton } from '@/components/FDButton'
export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({ export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({
heading, heading,
@ -35,8 +36,7 @@ export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({
const overlayClass = const overlayClass =
overlayOpacity === '30' ? 'bg-black/30' : overlayOpacity === '70' ? 'bg-black/70' : 'bg-black/50' overlayOpacity === '30' ? 'bg-black/30' : overlayOpacity === '70' ? 'bg-black/70' : 'bg-black/50'
const secondaryBtnClass = const secondaryOnDark = textColor === 'navy' ? false : isDark
isDark && textColor !== 'navy' ? 'fd-btn-secondary-dark' : 'fd-btn-secondary'
return ( return (
<section <section
@ -44,39 +44,34 @@ export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({
> >
{hasBgImage && ( {hasBgImage && (
<> <>
<img <img src={bgImageUrl} alt="" className="absolute inset-0 w-full h-full object-cover" aria-hidden="true" />
src={bgImageUrl}
alt=""
className="absolute inset-0 w-full h-full object-cover"
aria-hidden="true"
/>
<div className={`absolute inset-0 ${overlayClass}`} aria-hidden="true" /> <div className={`absolute inset-0 ${overlayClass}`} aria-hidden="true" />
</> </>
)} )}
<div className="relative max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-start gap-6 md:gap-8"> <div className="relative max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-start gap-6 md:gap-8">
<h1 className={`w-full max-w-[884px] font-joey-heavy text-fd-display ${headingColor}`}> <h1 className={`w-full max-w-[884px] font-joey-heavy text-3xl md:text-5xl lg:text-[78px] leading-tight lg:leading-none ${headingColor}`}>
{heading} {heading}
</h1> </h1>
{subheading && ( {subheading && (
<h2 className={`w-full max-w-[884px] font-joey-medium text-fd-h1 ${textBodyColor}`}> <h2 className={`w-full max-w-[884px] font-joey-medium text-2xl md:text-4xl lg:text-[50px] leading-tight ${textBodyColor}`}>
{subheading} {subheading}
</h2> </h2>
)} )}
{body && ( {body && (
<p className={`w-full max-w-[597px] font-joey text-fd-body-lg ${textBodyColor}`}> <p className={`w-full max-w-[597px] font-joey text-lg md:text-xl lg:text-2xl lg:leading-snug ${textBodyColor}`}>
{body} {body}
</p> </p>
)} )}
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
{ctaText && ( {ctaText && (
<a href={ctaLink || '#'} className="fd-btn-primary"> <FDButton href={ctaLink || '#'} variant="primary" onDark={isDark}>
{ctaText} {ctaText}
</a> </FDButton>
)} )}
{secondaryCtaText && ( {secondaryCtaText && (
<a href={secondaryCtaLink || '#'} className={secondaryBtnClass}> <FDButton href={secondaryCtaLink || '#'} variant="outline" onDark={secondaryOnDark}>
{secondaryCtaText} {secondaryCtaText}
</a> </FDButton>
)} )}
</div> </div>
</div> </div>

View File

@ -1,64 +0,0 @@
import React from 'react'
import type { FDHeroBlock as FDHeroBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage'
export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({
heading,
subheading,
body,
ctaText,
ctaLink = '#',
secondaryCtaText,
secondaryCtaLink = '#',
backgroundImage,
overlayOpacity = '50',
textColor = 'auto',
theme = 'light',
}) => {
const media = backgroundImage as Media | undefined
const hasBgImage = Boolean(media?.url)
const isDark = hasBgImage || theme === 'dark'
let headingColor: string
let textBodyColor: string
if (textColor === 'white') {
headingColor = 'text-white'
textBodyColor = 'text-white'
} else if (textColor === 'navy') {
headingColor = 'text-fd-navy'
textBodyColor = 'text-fd-navy'
} else {
headingColor = isDark ? 'text-fd-yellow' : 'text-fd-navy'
textBodyColor = isDark ? 'text-white' : 'text-fd-navy'
}
const overlayClass = overlayOpacity === '30' ? 'bg-black/30' : overlayOpacity === '70' ? 'bg-black/70' : 'bg-black/50'
return (
<section className={`relative w-full py-16 md:py-20 lg:py-[99px] ${hasBgImage ? '' : isDark ? 'bg-fd-navy' : 'bg-white'} overflow-hidden`}>
{hasBgImage && (
<>
<FDImage
media={backgroundImage as Media}
size="hero"
fill
priority
className="absolute inset-0 w-full h-full object-cover"
sizes="100vw"
fallbackAlt=""
/>
<div className={`absolute inset-0 ${overlayClass}`} aria-hidden="true" />
</>
)}
<div className="relative max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-start gap-6 md:gap-8">
<h1 className={`w-full max-w-[884px] font-joey-heavy text-3xl md:text-5xl lg:text-[78px] leading-tight lg:leading-none ${headingColor}`}>{heading}</h1>
{subheading && <h2 className={`w-full max-w-[884px] font-joey-medium text-2xl md:text-4xl lg:text-[50px] leading-tight ${textBodyColor}`}>{subheading}</h2>}
{body && <p className={`w-full max-w-[597px] font-joey text-lg md:text-xl lg:text-2xl lg:leading-snug ${textBodyColor}`}>{body}</p>}
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
{ctaText && <a href={ctaLink || '#'} className="inline-flex items-center justify-center px-8 py-2.5 bg-fd-yellow hover:bg-fd-yellow/90 rounded-full font-joey-bold text-fd-navy text-lg md:text-2xl leading-[38px] transition-colors">{ctaText}</a>}
{secondaryCtaText && <a href={secondaryCtaLink || '#'} className={`inline-flex items-center justify-center px-8 py-2.5 rounded-full font-joey-bold text-lg md:text-2xl leading-[38px] border-2 transition-colors ${textColor === 'navy' ? 'border-fd-navy text-fd-navy hover:bg-fd-navy/5' : isDark ? 'border-white text-white hover:bg-white/10' : 'border-fd-navy text-fd-navy hover:bg-fd-navy/5'}`}>{secondaryCtaText}</a>}
</div>
</div>
</section>
)
}

View File

@ -1,33 +1,37 @@
import React from 'react' import React from 'react'
import type { FDPricingCardBlock as FDPricingCardBlockProps } from '@/payload-types' import type { FDPricingCardBlock as FDPricingCardBlockProps } from '@/payload-types'
import { FDButton } from '@/components/FDButton'
const sectionBgMap: Record<string, string> = { const sectionBgMap: Record<string, string> = {
white: 'bg-white', white: 'bg-white',
navy: 'bg-fd-navy', navy: 'bg-fd-navy',
gray: 'bg-fd-gray-light', gray: 'bg-fd-gray-light',
yellow: 'bg-fd-yellow', yellow: 'bg-fd-yellow',
} }
const titleColorMap: Record<string, string> = { const titleColorMap: Record<string, string> = {
navy: 'text-fd-navy', navy: 'text-fd-navy',
white: 'text-white', white: 'text-white',
yellow: 'text-fd-yellow', yellow: 'text-fd-yellow',
} }
const cardStyleMap: Record<string, { bg: string; border: string; title: string; subtitle: string; body: string; bullet: string }> = { const cardStyleMap: Record<string, {
outlined: { bg: 'bg-white', border: 'border-[6px] border-[#d1d5db]', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy' }, bg: string; border: string; title: string
navy: { bg: 'bg-fd-navy', border: '', title: 'text-fd-yellow', subtitle: 'text-white', body: 'text-white/80', bullet: 'text-white' }, subtitle: string; body: string; bullet: string; isDark: boolean
gray: { bg: 'bg-[#e5e5e5]', border: '', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy' }, }> = {
yellow: { bg: 'bg-fd-yellow', border: '', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy' }, outlined: { bg: 'bg-white', border: 'border-[2px] border-[#d1d5db]', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy', isDark: false },
white: { bg: 'bg-white shadow-lg', border: '', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy' }, navy: { bg: 'bg-fd-navy', border: '', title: 'text-fd-yellow', subtitle: 'text-white', body: 'text-white/80', bullet: 'text-white', isDark: true },
gray: { bg: 'bg-[#e5e5e5]', border: '', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy', isDark: false },
yellow: { bg: 'bg-fd-yellow', border: '', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy', isDark: false },
white: { bg: 'bg-white shadow-lg', border: '', title: 'text-fd-navy', subtitle: 'text-fd-navy', body: 'text-fd-navy/80', bullet: 'text-fd-navy', isDark: false },
} }
// Map to fd-btn classes; the 'a' tag just uses the class directly // Maps CMS buttonColor to FDButton variant — outline used where primary yellow wouldn't be visible
const buttonStyleMap: Record<string, string> = { const buttonVariantMap: Record<string, { variant: 'primary' | 'outline' }> = {
yellow: 'fd-btn-primary', yellow: { variant: 'primary' },
navy: 'fd-btn-navy', navy: { variant: 'primary' },
outlinedNavy: 'fd-btn-secondary', outlinedNavy: { variant: 'outline' },
outlinedWhite: 'fd-btn-secondary-dark', outlinedWhite: { variant: 'outline' },
} }
const gridColsMap: Record<number, string> = { const gridColsMap: Record<number, string> = {
@ -47,15 +51,18 @@ export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({
const sectionBg = sectionBgMap[sectionBackground || 'white'] const sectionBg = sectionBgMap[sectionBackground || 'white']
const sectionTitleColor = titleColorMap[titleColor || 'navy'] const sectionTitleColor = titleColorMap[titleColor || 'navy']
const style = cardStyleMap[cardStyle || 'outlined'] const style = cardStyleMap[cardStyle || 'outlined']
const btnClass = buttonStyleMap[buttonColor || 'yellow'] || 'fd-btn-primary' const { variant } = buttonVariantMap[buttonColor || 'yellow']
const cardCount = cards?.length || 1 const cardCount = cards?.length || 1
const gridCols = gridColsMap[cardCount] || gridColsMap[3] const gridCols = gridColsMap[cardCount] || gridColsMap[3]
// outline-dark vs outline-light is determined by the card background
const outlineOnDark = style.isDark
return ( return (
<section className={`w-full py-12 md:py-16 lg:py-20 ${sectionBg}`}> <section className={`w-full py-12 md:py-16 lg:py-20 ${sectionBg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className="max-w-[1200px] mx-auto px-6 md:px-8">
{sectionTitle && ( {sectionTitle && (
<h2 className={`font-joey-heavy text-fd-h1 text-center mb-10 md:mb-14 ${sectionTitleColor}`}> <h2 className={`font-joey-heavy text-3xl md:text-4xl lg:text-[54px] leading-tight text-center mb-10 md:mb-14 ${sectionTitleColor}`}>
{sectionTitle} {sectionTitle}
</h2> </h2>
)} )}
@ -63,28 +70,41 @@ export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({
{cards?.map((card, index) => ( {cards?.map((card, index) => (
<div <div
key={index} key={index}
className={`${style.bg} ${style.border} rounded-[32px] md:rounded-[50px] lg:rounded-[70px] px-8 md:px-10 py-10 md:py-12 flex flex-col gap-5`} className={`${style.bg} ${style.border} rounded-[32px] px-8 md:px-10 py-10 md:py-12 flex flex-col gap-5`}
> >
<h3 className={`font-joey-heavy text-fd-h2 ${style.title}`}>{card.title}</h3> <h3 className={`font-joey-heavy text-2xl md:text-3xl lg:text-4xl leading-tight ${style.title}`}>
{card.title}
</h3>
{card.subtitle && ( {card.subtitle && (
<p className={`font-joey-bold text-fd-h3 ${style.subtitle}`}>{card.subtitle}</p> <p className={`font-joey-bold text-xl md:text-2xl leading-tight ${style.subtitle}`}>
{card.subtitle}
</p>
)} )}
{card.description && ( {card.description && (
<p className={`font-joey text-fd-body ${style.body}`}>{card.description}</p> <p className={`font-joey text-base md:text-lg leading-relaxed ${style.body}`}>
{card.description}
</p>
)} )}
{card.bulletPoints && card.bulletPoints.length > 0 && ( {card.bulletPoints && card.bulletPoints.length > 0 && (
<ul className={`flex flex-col gap-1 ${style.bullet}`}> <ul className={`flex flex-col gap-2 ${style.bullet}`}>
{card.bulletPoints.map((point, i) => ( {card.bulletPoints.map((point, i) => (
<li key={i} className="flex items-start gap-3"> <li key={i} className="flex items-start gap-3">
<span className="mt-2 w-2 h-2 rounded-full bg-current flex-shrink-0" /> <span className="mt-2 w-2 h-2 rounded-full bg-current flex-shrink-0" />
<span className="font-joey text-fd-body">{point.text}</span> <span className="font-joey text-base md:text-lg">{point.text}</span>
</li> </li>
))} ))}
</ul> </ul>
)} )}
{card.ctaText && ( {card.ctaText && (
<div className="mt-auto pt-4"> <div className="mt-auto pt-4">
<a href={card.ctaLink || '#'} className={btnClass}>{card.ctaText}</a> <FDButton
href={card.ctaLink || '#'}
variant={variant}
onDark={variant === 'outline' ? outlineOnDark : style.isDark}
className="text-lg md:text-xl"
>
{card.ctaText}
</FDButton>
</div> </div>
)} )}
</div> </div>

View File

@ -1,19 +1,12 @@
import React from 'react' import React from 'react'
import type { FDWideCardBlock as FDWideCardBlockProps, Media } from '@/payload-types' import type { FDWideCardBlock as FDWideCardBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDButton } from '@/components/FDButton'
const cardBgMap: Record<string, { bg: string; heading: string; body: string }> = { const cardBgMap: Record<string, { bg: string; heading: string; body: string; isDark: boolean }> = {
navy: { bg: 'bg-fd-navy', heading: 'text-white', body: 'text-white/80' }, navy: { bg: 'bg-fd-navy', heading: 'text-white', body: 'text-white/80', isDark: true },
yellow: { bg: 'bg-fd-yellow', heading: 'text-fd-navy', body: 'text-fd-navy/80' }, yellow: { bg: 'bg-fd-yellow', heading: 'text-fd-navy', body: 'text-fd-navy/80', isDark: false },
gray: { bg: 'bg-[#e5e5e5]', heading: 'text-fd-navy', body: 'text-fd-navy/80' }, gray: { bg: 'bg-[#e5e5e5]', heading: 'text-fd-navy', body: 'text-fd-navy/80', isDark: false },
white: { bg: 'bg-white shadow-xl', heading: 'text-fd-navy', body: 'text-fd-navy/80' }, white: { bg: 'bg-white shadow-xl', heading: 'text-fd-navy', body: 'text-fd-navy/80', isDark: false },
}
// Map to fd-btn classes
const btnMap: Record<string, string> = {
yellow: 'fd-btn-primary',
navy: 'fd-btn-navy',
white: 'fd-btn-white',
} }
const sectionBgMap: Record<string, string> = { const sectionBgMap: Record<string, string> = {
@ -22,6 +15,13 @@ const sectionBgMap: Record<string, string> = {
navy: 'bg-fd-navy', navy: 'bg-fd-navy',
} }
// Button variant per CMS color choice — outline used for navy cards with navy button (would be invisible)
const btnVariantMap: Record<string, { variant: 'primary' | 'outline' }> = {
yellow: { variant: 'primary' },
navy: { variant: 'outline' },
white: { variant: 'primary' },
}
export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
heading, heading,
body, body,
@ -33,36 +33,41 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
sectionBackground = 'white', sectionBackground = 'white',
}) => { }) => {
const card = cardBgMap[cardBackground || 'navy'] const card = cardBgMap[cardBackground || 'navy']
const btnClass = btnMap[buttonColor || 'yellow'] || 'fd-btn-primary'
const sectionBg = sectionBgMap[sectionBackground || 'white'] const sectionBg = sectionBgMap[sectionBackground || 'white']
const { variant } = btnVariantMap[buttonColor || 'yellow']
const media = image as Media | undefined const media = image as Media | undefined
const hasImage = Boolean(media?.url) const imageUrl = media?.url || ''
const hasImage = Boolean(imageUrl)
return ( return (
<section className={`w-full py-12 md:py-16 lg:py-20 ${sectionBg}`}> <section className={`w-full py-12 md:py-16 lg:py-20 ${sectionBg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className="max-w-[1200px] mx-auto px-6 md:px-8">
<div className={`${card.bg} rounded-[70px] overflow-hidden flex flex-col lg:flex-row`}> <div className={`${card.bg} rounded-[70px] overflow-hidden flex flex-col lg:flex-row`}>
<div className="flex-1 flex flex-col justify-center gap-5 md:gap-6 px-10 md:px-14 lg:px-16 py-12 md:py-16"> <div className="flex-1 flex flex-col justify-center gap-5 md:gap-6 px-10 md:px-14 lg:px-16 py-12 md:py-16">
<h2 className={`font-joey-heavy text-fd-h2 ${card.heading}`}>{heading}</h2> <h2 className={`font-joey-heavy text-2xl md:text-3xl lg:text-[42px] leading-tight ${card.heading}`}>
{heading}
</h2>
{body && ( {body && (
<p className={`font-joey text-fd-body ${card.body}`}>{body}</p> <p className={`font-joey text-base md:text-lg lg:text-xl leading-relaxed ${card.body}`}>
{body}
</p>
)} )}
{ctaText && ( {ctaText && (
<div> <div>
<a href={ctaLink || '#'} className={btnClass}>{ctaText}</a> <FDButton
href={ctaLink || '#'}
variant={variant}
onDark={card.isDark}
className="text-lg md:text-xl"
>
{ctaText}
</FDButton>
</div> </div>
)} )}
</div> </div>
{hasImage && ( {hasImage && (
<div className="relative flex-1 min-h-[250px] lg:min-h-0"> <div className="flex-1 min-h-[250px] lg:min-h-0">
<FDImage <img src={imageUrl} alt={media?.alt || ''} className="w-full h-full object-cover" />
media={media!}
size="large"
fill
className="object-cover"
sizes="(max-width: 1024px) 100vw, 600px"
fallbackAlt={heading || ''}
/>
</div> </div>
)} )}
</div> </div>

View File

@ -0,0 +1,43 @@
import React from 'react'
type FDButtonProps = {
href: string
children: React.ReactNode
variant?: 'primary' | 'outline'
onDark?: boolean
className?: string
}
/**
* FDButton shared button component for all FD blocks.
*
* variant="primary" onDark={true} yellow bg, hover white (use on navy backgrounds)
* variant="primary" onDark={false} yellow bg, hover yellow/80 (use on light backgrounds)
* variant="outline" onDark={true} white border + text, hover white/10
* variant="outline" onDark={false} navy border + text, hover navy/5
*/
export const FDButton: React.FC<FDButtonProps> = ({
href,
children,
variant = 'primary',
onDark = false,
className = '',
}) => {
const base =
'inline-flex items-center justify-center px-8 py-2.5 rounded-full font-joey-bold text-lg md:text-2xl leading-[38px] transition-colors'
const styles = {
'primary-dark': 'bg-fd-yellow text-fd-navy hover:bg-white hover:text-fd-navy',
'primary-light': 'bg-fd-yellow text-fd-navy hover:bg-fd-yellow/80',
'outline-dark': 'border-2 border-white text-white hover:bg-white/10',
'outline-light': 'border-2 border-fd-navy text-fd-navy hover:bg-fd-navy/5',
}
const key = `${variant}-${onDark ? 'dark' : 'light'}` as keyof typeof styles
return (
<a href={href} className={`${base} ${styles[key]} ${className}`}>
{children}
</a>
)
}

File diff suppressed because one or more lines are too long