feat: add imageOverlay field to AlternateHero, WideCard, UspChecklist blocks

This commit is contained in:
Jeffrey 2026-03-13 09:19:29 +01:00
parent fb8b75ecaa
commit e7df2e3cba
10 changed files with 38790 additions and 15 deletions

View File

@ -3,6 +3,17 @@ import type { FDAlternateHeroBlock as Props, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
const overlayMap: Record<string, string> = {
none: '',
navyLight: 'bg-fd-navy/20',
navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30',
blackLight: 'bg-black/20',
blackMedium: 'bg-black/40',
}
export const FDAlternateHeroBlockComponent: React.FC<Props> = ({ export const FDAlternateHeroBlockComponent: React.FC<Props> = ({
heading, heading,
description, description,
@ -12,9 +23,11 @@ export const FDAlternateHeroBlockComponent: React.FC<Props> = ({
secondaryCtaLink = '#', secondaryCtaLink = '#',
image, image,
imageCaption, imageCaption,
imageOverlay = 'none',
sectionBackground = 'white', sectionBackground = 'white',
anchorId, anchorId,
}) => { }) => {
const overlay = overlayMap[imageOverlay || 'none']
const media = image as Media | undefined const media = image as Media | undefined
const hasImage = media && typeof media === 'object' && media.url const hasImage = media && typeof media === 'object' && media.url
const isDark = sectionBackground === 'navy' const isDark = sectionBackground === 'navy'
@ -65,7 +78,7 @@ export const FDAlternateHeroBlockComponent: React.FC<Props> = ({
{/* Full-width image — no border radius, bleeds edge to edge */} {/* Full-width image — no border radius, bleeds edge to edge */}
{hasImage && ( {hasImage && (
<div className="w-full"> <div className="w-full">
<div className="relative w-full" style={{ maxHeight: '620px', height: '45vw', minHeight: '220px' }}> <div className="relative w-full overflow-hidden" style={{ maxHeight: '620px', height: '45vw', minHeight: '220px' }}>
<FDImage <FDImage
media={media} media={media}
size="hero" size="hero"
@ -75,6 +88,7 @@ export const FDAlternateHeroBlockComponent: React.FC<Props> = ({
sizes="100vw" sizes="100vw"
fallbackAlt={heading || ''} fallbackAlt={heading || ''}
/> />
{overlay && <div className={`absolute inset-0 ${overlay}`} />}
</div> </div>
{imageCaption && ( {imageCaption && (
<div className={`text-center py-3 font-joey text-fd-small opacity-60 ${bodyClass} ${bgClass}`}> <div className={`text-center py-3 font-joey text-fd-small opacity-60 ${bodyClass} ${bgClass}`}>

View File

@ -68,6 +68,25 @@ export const FDAlternateHeroBlock: Block = {
condition: (_, siblingData) => Boolean(siblingData?.image), condition: (_, siblingData) => Boolean(siblingData?.image),
}, },
}, },
{
name: 'imageOverlay',
type: 'select',
label: 'Bildöverlagring',
defaultValue: 'none',
options: [
{ label: 'Ingen', value: 'none' },
{ label: 'Navy (lätt)', value: 'navyLight' },
{ label: 'Navy (medium)', value: 'navyMedium' },
{ label: 'Gul (lätt)', value: 'yellowLight' },
{ label: 'Gul (medium)', value: 'yellowMedium' },
{ label: 'Sepia', value: 'sepia' },
{ label: 'Svart (lätt)', value: 'blackLight' },
{ label: 'Svart (medium)', value: 'blackMedium' },
],
admin: {
condition: (_, siblingData) => Boolean(siblingData?.image),
},
},
{ {
name: 'sectionBackground', name: 'sectionBackground',
type: 'select', type: 'select',

View File

@ -18,6 +18,17 @@ const bodyMap: Record<string, string> = {
white: 'text-white', white: 'text-white',
} }
const overlayMap: Record<string, string> = {
none: '',
navyLight: 'bg-fd-navy/20',
navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30',
blackLight: 'bg-black/20',
blackMedium: 'bg-black/40',
}
const checkColors: Record<string, { circle: string; check: string }> = { const checkColors: Record<string, { circle: string; check: string }> = {
navy: { circle: 'fill-fd-navy dark:fill-white/20', check: 'fill-white dark:fill-fd-yellow' }, navy: { circle: 'fill-fd-navy dark:fill-white/20', check: 'fill-white dark:fill-fd-yellow' },
yellow: { circle: 'fill-fd-yellow', check: 'fill-fd-navy' }, yellow: { circle: 'fill-fd-yellow', check: 'fill-fd-navy' },
@ -38,6 +49,7 @@ export const FDUspChecklistBlockComponent: React.FC<FDUspChecklistBlockProps> =
heading, heading,
items, items,
image, image,
imageOverlay = 'none',
imagePosition = 'right', imagePosition = 'right',
checkColor = 'navy', checkColor = 'navy',
sectionBackground = 'white', sectionBackground = 'white',
@ -49,6 +61,7 @@ export const FDUspChecklistBlockComponent: React.FC<FDUspChecklistBlockProps> =
const bodyClr = bodyMap[textColor || 'navy'] const bodyClr = bodyMap[textColor || 'navy']
const media = image as Media | undefined const media = image as Media | undefined
const hasImage = Boolean(media?.url) const hasImage = Boolean(media?.url)
const overlay = overlayMap[imageOverlay || 'none']
const textContent = ( const textContent = (
<div className="flex-1 flex flex-col gap-6 md:gap-8"> <div className="flex-1 flex flex-col gap-6 md:gap-8">
@ -65,14 +78,15 @@ export const FDUspChecklistBlockComponent: React.FC<FDUspChecklistBlockProps> =
) )
const imageContent = hasImage ? ( const imageContent = hasImage ? (
<div className="flex-1"> <div className="flex-1 relative overflow-hidden rounded-[40px]">
<FDImage <FDImage
media={media!} media={media!}
size="large" size="large"
className="w-full h-auto rounded-[40px] object-cover" className="w-full h-auto object-cover"
sizes="(max-width: 1024px) 100vw, 550px" sizes="(max-width: 1024px) 100vw, 550px"
fallbackAlt={heading || ''} fallbackAlt={heading || ''}
/> />
{overlay && <div className={`absolute inset-0 ${overlay}`} />}
</div> </div>
) : null ) : null

View File

@ -39,6 +39,25 @@ export const FDUspChecklistBlock: Block = {
relationTo: 'media', relationTo: 'media',
label: 'Bild (valfri)', label: 'Bild (valfri)',
}, },
{
name: 'imageOverlay',
type: 'select',
label: 'Bildöverlagring',
defaultValue: 'none',
options: [
{ label: 'Ingen', value: 'none' },
{ label: 'Navy (lätt)', value: 'navyLight' },
{ label: 'Navy (medium)', value: 'navyMedium' },
{ label: 'Gul (lätt)', value: 'yellowLight' },
{ label: 'Gul (medium)', value: 'yellowMedium' },
{ label: 'Sepia', value: 'sepia' },
{ label: 'Svart (lätt)', value: 'blackLight' },
{ label: 'Svart (medium)', value: 'blackMedium' },
],
admin: {
condition: (_, siblingData) => Boolean(siblingData?.image),
},
},
{ {
name: 'imagePosition', name: 'imagePosition',
type: 'select', type: 'select',

View File

@ -24,12 +24,24 @@ const btnVariantMap: Record<string, { variant: 'primary' | 'outline' }> = {
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]' const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
const overlayMap: Record<string, string> = {
none: '',
navyLight: 'bg-fd-navy/20',
navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30',
blackLight: 'bg-black/20',
blackMedium: 'bg-black/40',
}
export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
heading, heading,
body, body,
ctaText, ctaText,
ctaLink, ctaLink,
image, image,
imageOverlay = 'none',
cardBackground = 'navy', cardBackground = 'navy',
buttonColor = 'yellow', buttonColor = 'yellow',
sectionBackground = 'white', sectionBackground = 'white',
@ -40,6 +52,7 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
const { variant } = btnVariantMap[buttonColor || 'yellow'] const { variant } = btnVariantMap[buttonColor || 'yellow']
const media = image as Media | undefined const media = image as Media | undefined
const hasImage = media && typeof media === 'object' && media.url const hasImage = media && typeof media === 'object' && media.url
const overlay = overlayMap[imageOverlay || 'none']
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
@ -65,6 +78,7 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
{hasImage && ( {hasImage && (
<div className="flex items-center justify-center w-full min-[820px]:w-[45%] lg:w-[480px] flex-shrink-0 p-6 md:p-10 lg:p-12"> <div className="flex items-center justify-center w-full min-[820px]:w-[45%] lg:w-[480px] flex-shrink-0 p-6 md:p-10 lg:p-12">
<div className="relative w-full">
<FDImage <FDImage
media={media} media={media}
size="large" size="large"
@ -72,6 +86,8 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
sizes="(max-width: 820px) 80vw, 400px" sizes="(max-width: 820px) 80vw, 400px"
fallbackAlt={heading || ''} fallbackAlt={heading || ''}
/> />
{overlay && <div className={`absolute inset-0 ${overlay}`} />}
</div>
</div> </div>
)} )}
</div> </div>

View File

@ -42,6 +42,25 @@ export const FDWideCardBlock: Block = {
relationTo: 'media', relationTo: 'media',
label: 'Bild (valfri)', label: 'Bild (valfri)',
}, },
{
name: 'imageOverlay',
type: 'select',
label: 'Bildöverlagring',
defaultValue: 'none',
options: [
{ label: 'Ingen', value: 'none' },
{ label: 'Navy (lätt)', value: 'navyLight' },
{ label: 'Navy (medium)', value: 'navyMedium' },
{ label: 'Gul (lätt)', value: 'yellowLight' },
{ label: 'Gul (medium)', value: 'yellowMedium' },
{ label: 'Sepia', value: 'sepia' },
{ label: 'Svart (lätt)', value: 'blackLight' },
{ label: 'Svart (medium)', value: 'blackMedium' },
],
admin: {
condition: (_, siblingData) => Boolean(siblingData?.image),
},
},
{ {
name: 'cardBackground', name: 'cardBackground',
type: 'select', type: 'select',

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TYPE "public"."enum_pages_blocks_fd_usp_checklist_image_overlay" AS ENUM('none', 'navyLight', 'navyMedium', 'yellowLight', 'yellowMedium', 'sepia', 'blackLight', 'blackMedium');
CREATE TYPE "public"."enum_pages_blocks_fd_wide_card_image_overlay" AS ENUM('none', 'navyLight', 'navyMedium', 'yellowLight', 'yellowMedium', 'sepia', 'blackLight', 'blackMedium');
CREATE TYPE "public"."enum_pages_blocks_fd_alternate_hero_image_overlay" AS ENUM('none', 'navyLight', 'navyMedium', 'yellowLight', 'yellowMedium', 'sepia', 'blackLight', 'blackMedium');
CREATE TYPE "public"."enum__pages_v_blocks_fd_usp_checklist_image_overlay" AS ENUM('none', 'navyLight', 'navyMedium', 'yellowLight', 'yellowMedium', 'sepia', 'blackLight', 'blackMedium');
CREATE TYPE "public"."enum__pages_v_blocks_fd_wide_card_image_overlay" AS ENUM('none', 'navyLight', 'navyMedium', 'yellowLight', 'yellowMedium', 'sepia', 'blackLight', 'blackMedium');
CREATE TYPE "public"."enum__pages_v_blocks_fd_alternate_hero_image_overlay" AS ENUM('none', 'navyLight', 'navyMedium', 'yellowLight', 'yellowMedium', 'sepia', 'blackLight', 'blackMedium');
ALTER TABLE "pages_blocks_fd_usp_checklist" ADD COLUMN "image_overlay" "enum_pages_blocks_fd_usp_checklist_image_overlay" DEFAULT 'none';
ALTER TABLE "pages_blocks_fd_wide_card" ADD COLUMN "image_overlay" "enum_pages_blocks_fd_wide_card_image_overlay" DEFAULT 'none';
ALTER TABLE "pages_blocks_fd_alternate_hero" ADD COLUMN "image_overlay" "enum_pages_blocks_fd_alternate_hero_image_overlay" DEFAULT 'none';
ALTER TABLE "_pages_v_blocks_fd_usp_checklist" ADD COLUMN "image_overlay" "enum__pages_v_blocks_fd_usp_checklist_image_overlay" DEFAULT 'none';
ALTER TABLE "_pages_v_blocks_fd_wide_card" ADD COLUMN "image_overlay" "enum__pages_v_blocks_fd_wide_card_image_overlay" DEFAULT 'none';
ALTER TABLE "_pages_v_blocks_fd_alternate_hero" ADD COLUMN "image_overlay" "enum__pages_v_blocks_fd_alternate_hero_image_overlay" DEFAULT 'none';`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "pages_blocks_fd_usp_checklist" DROP COLUMN "image_overlay";
ALTER TABLE "pages_blocks_fd_wide_card" DROP COLUMN "image_overlay";
ALTER TABLE "pages_blocks_fd_alternate_hero" DROP COLUMN "image_overlay";
ALTER TABLE "_pages_v_blocks_fd_usp_checklist" DROP COLUMN "image_overlay";
ALTER TABLE "_pages_v_blocks_fd_wide_card" DROP COLUMN "image_overlay";
ALTER TABLE "_pages_v_blocks_fd_alternate_hero" DROP COLUMN "image_overlay";
DROP TYPE "public"."enum_pages_blocks_fd_usp_checklist_image_overlay";
DROP TYPE "public"."enum_pages_blocks_fd_wide_card_image_overlay";
DROP TYPE "public"."enum_pages_blocks_fd_alternate_hero_image_overlay";
DROP TYPE "public"."enum__pages_v_blocks_fd_usp_checklist_image_overlay";
DROP TYPE "public"."enum__pages_v_blocks_fd_wide_card_image_overlay";
DROP TYPE "public"."enum__pages_v_blocks_fd_alternate_hero_image_overlay";`)
}

View File

@ -27,8 +27,8 @@ export const generateMeta = async (args: {
const ogImage = getImageURL(doc?.meta?.image) const ogImage = getImageURL(doc?.meta?.image)
const title = doc?.meta?.title const title = doc?.meta?.title
? doc?.meta?.title + ' | Payload Website Template' ? doc?.meta?.title + ' | Fiber Direkt'
: 'Payload Website Template' : 'Fiber Direkt'
return { return {
description: doc?.meta?.description, description: doc?.meta?.description,

View File

@ -3,14 +3,14 @@ import { getServerSideURL } from './getURL'
const defaultOpenGraph: Metadata['openGraph'] = { const defaultOpenGraph: Metadata['openGraph'] = {
type: 'website', type: 'website',
description: 'An open-source website built with Payload and Next.js.', description: 'Fiber Direkt snabb och pålitlig fiber till ditt företag.',
images: [ images: [
{ {
url: `${getServerSideURL()}/website-template-OG.webp`, url: `${getServerSideURL()}/website-template-OG.webp`,
}, },
], ],
siteName: 'Payload Website Template', siteName: 'Fiber Direkt',
title: 'Payload Website Template', title: 'Fiber Direkt',
} }
export const mergeOpenGraph = (og?: Metadata['openGraph']): Metadata['openGraph'] => { export const mergeOpenGraph = (og?: Metadata['openGraph']): Metadata['openGraph'] => {