feat: anchor links, smooth scroll, back-to-top

This commit is contained in:
Jeffrey 2026-02-24 10:10:29 +01:00
parent a018a65db9
commit 21e3778397
59 changed files with 600 additions and 48 deletions

260
add-anchor-links.sh Normal file
View File

@ -0,0 +1,260 @@
#!/bin/bash
# ============================================================================
# add-anchor-links.sh (macOS + Linux compatible)
# Run from your project root: bash add-anchor-links.sh
# ============================================================================
set -e
echo "🔗 Adding anchor link support to all FD blocks..."
echo ""
# ── Step 1: Create shared anchorField ──────────────────────────────────────
mkdir -p src/fields
cat > src/fields/anchorField.ts << 'FIELDEOF'
import type { Field } from 'payload'
/**
* Shared anchorId field for all FD blocks.
* Allows editors to set an anchor link ID on any section,
* enabling direct linking like /page#section-name
*
* Usage in block config:
* import { anchorField } from '@/fields/anchorField'
* fields: [ ...contentFields, anchorField ]
*
* Usage in block component:
* <section id={anchorId || undefined} ...>
*/
export const anchorField: Field = {
name: 'anchorId',
type: 'text',
label: 'Ankarlänk-ID',
admin: {
description:
'Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.',
position: 'sidebar',
},
validate: (value: unknown) => {
if (!value) return true
if (typeof value === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(value)) return true
return 'Använd bara små bokstäver (a-z), siffror (0-9) och bindestreck (-). Inga mellanslag.'
},
}
FIELDEOF
echo "✅ Created src/fields/anchorField.ts"
# ── Step 2: Add anchorField to all block configs ───────────────────────────
BLOCKS=(
FDHeroBlock
FDAlternateHeroBlock
FDTextBlock
FDPricingCardBlock
FDFaqBlock
FDContactBlock
FDContactFormBlock
FDDataTableBlock
FDCardGridBlock
FDCtaSideImageBlock
FDCtaBannerBlock
FDCodeEmbedBlock
FDFeatureAnnouncementBlock
FDHeaderTextImageBlock
FDServicesGridBlock
FDServiceChooserBlock
FDUspChecklistBlock
FDUspTableBlock
FDTechPropertiesBlock
FDStatisticsBlock
FDTestimonialBlock
FDTeamBlock
FDVideoBlock
FDVpsCalculatorBlock
FDWideCardBlock
FDServiceCalculatorBlock
)
for BLOCK in "${BLOCKS[@]}"; do
CONFIG="src/blocks/$BLOCK/config.ts"
if [ ! -f "$CONFIG" ]; then
echo "⚠️ Skipped $BLOCK — config not found"
continue
fi
# Skip if already has anchorField
if grep -q "anchorField" "$CONFIG"; then
echo "⏭️ Skipped $BLOCK — anchorField already present"
continue
fi
# Add import after the first line
perl -i -pe 'if ($. == 1) { $_ .= "import { anchorField } from '\''\@/fields/anchorField'\''\n" }' "$CONFIG"
# Add anchorField before the last "]," in the file
perl -i -0pe 's/( \],\n\})\s*$/ anchorField,\n$1/s' "$CONFIG"
echo "✅ Updated $CONFIG"
done
echo ""
# ── Step 3: Add anchorId to all block components ──────────────────────────
echo "🔧 Updating block components..."
for BLOCK in "${BLOCKS[@]}"; do
COMP="src/blocks/$BLOCK/Component.tsx"
if [ ! -f "$COMP" ]; then
echo "⚠️ Skipped $BLOCK component — not found"
continue
fi
# Skip if already has anchorId
if grep -q "anchorId" "$COMP"; then
echo "⏭️ Skipped $BLOCK component — anchorId already present"
continue
fi
# Add anchorId to destructured props: insert before "}) => {"
perl -i -pe 's/\}\) => \{/ anchorId,\n}) => {/' "$COMP"
# Add id={anchorId || undefined} to the first <section
# Handle single-line: <section className=...
perl -i -pe 'if (!$done && s/<section className/<section id={anchorId || undefined} className/) { $done=1 }' "$COMP"
# Handle multi-line: <section alone on a line (like FDHeroBlock)
if ! grep -q 'id={anchorId' "$COMP"; then
perl -i -pe 'if (!$done && /^\s+<section\s*$/) { $done=1; s|<section|<section id={anchorId \|\| undefined}| }' "$COMP"
fi
echo "✅ Updated $COMP"
done
echo ""
# ── Step 4: Add smooth scrolling to globals.css ───────────────────────────
GLOBALS_CSS="src/app/(frontend)/globals.css"
if grep -q "scroll-behavior" "$GLOBALS_CSS" 2>/dev/null; then
echo "⏭️ Smooth scroll already in globals.css"
else
TMPFILE=$(mktemp)
cat > "$TMPFILE" << 'CSSEOF'
/* Smooth scroll + header offset for anchor links */
html {
scroll-behavior: smooth;
scroll-padding-top: 100px;
}
CSSEOF
cat "$GLOBALS_CSS" >> "$TMPFILE"
mv "$TMPFILE" "$GLOBALS_CSS"
echo "✅ Added smooth scroll to globals.css"
fi
echo ""
# ── Step 5: Create BackToTop component ────────────────────────────────────
mkdir -p src/components/BackToTop
cat > src/components/BackToTop/index.tsx << 'BTEOF'
'use client'
import React, { useState, useEffect, useCallback } from 'react'
import { ChevronUpIcon } from 'lucide-react'
/**
* Floating "back to top" button.
* Appears after scrolling down 400px, smooth-scrolls to top on click.
* Styled to match FD design system.
*/
export const BackToTop: React.FC = () => {
const [visible, setVisible] = useState(false)
useEffect(() => {
const onScroll = () => {
setVisible(window.scrollY > 400)
}
window.addEventListener('scroll', onScroll, { passive: true })
return () => window.removeEventListener('scroll', onScroll)
}, [])
const scrollToTop = useCallback(() => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}, [])
return (
<button
onClick={scrollToTop}
aria-label="Tillbaka till toppen"
className={`
fixed bottom-6 right-6 z-40
w-11 h-11 rounded-full
bg-fd-navy/80 backdrop-blur-sm
text-white hover:text-fd-yellow
border border-white/10 hover:border-fd-yellow/30
shadow-lg hover:shadow-xl
flex items-center justify-center
transition-all duration-300 ease-in-out cursor-pointer
${visible
? 'opacity-100 translate-y-0 pointer-events-auto'
: 'opacity-0 translate-y-4 pointer-events-none'
}
`}
>
<ChevronUpIcon className="w-5 h-5" />
</button>
)
}
BTEOF
echo "✅ Created src/components/BackToTop/index.tsx"
# ── Step 6: Add BackToTop to layout ───────────────────────────────────────
LAYOUT="src/app/(frontend)/layout.tsx"
if grep -q "BackToTop" "$LAYOUT" 2>/dev/null; then
echo "⏭️ BackToTop already in layout"
else
# Add import after Footer import
perl -i -pe 'if (/import \{ Footer \}/) { $_ .= "import { BackToTop } from '\''\@/components/BackToTop'\''\n" }' "$LAYOUT"
# Add component after <Footer />
perl -i -pe 's|<Footer />|<Footer />\n <BackToTop />|' "$LAYOUT"
echo "✅ Added BackToTop to layout.tsx"
fi
echo ""
echo "════════════════════════════════════════════════════════════════"
echo "✅ All done! Now follow the deployment steps below."
echo "════════════════════════════════════════════════════════════════"
echo ""
echo "LOCAL:"
echo " 1. Review changes: git diff"
echo " 2. Clean local migrations:"
echo " rm -f src/migrations/2025*.ts src/migrations/2026*.ts"
echo " echo 'export {}' > src/migrations/index.ts"
echo " 3. Commit and push:"
echo " git add -A"
echo " git commit -m 'feat: anchor links, smooth scroll, back-to-top'"
echo " git push origin main"
echo ""
echo "SERVER:"
echo " cd /var/www/fiberdirekt"
echo " git pull origin main"
echo " npm install"
echo " npx payload generate:types"
echo " npx payload migrate:create add-anchor-links"
echo " npx payload migrate"
echo " npm run build && pm2 restart fiberdirekt"
echo ""
echo "If migrate:create says 'no changes detected', skip migrate"
echo "steps and go straight to build. Block fields are stored as"
echo "JSON so new optional fields usually don't need SQL migration."
echo ""

View File

@ -1,3 +1,9 @@
/* Smooth scroll + header offset for anchor links */
html {
scroll-behavior: smooth;
scroll-padding-top: 100px;
}
@import 'tailwindcss'; @import 'tailwindcss';
@import 'tw-animate-css'; @import 'tw-animate-css';

View File

@ -6,6 +6,7 @@ import { GeistSans } from 'geist/font/sans'
import React from 'react' import React from 'react'
import { AdminBar } from '@/components/AdminBar' import { AdminBar } from '@/components/AdminBar'
import { Footer } from '@/Footer/Component' import { Footer } from '@/Footer/Component'
import { BackToTop } from '@/components/BackToTop'
import { Header } from '@/Header/Component' import { Header } from '@/Header/Component'
import { Providers } from '@/providers' import { Providers } from '@/providers'
import { InitTheme } from '@/providers/Theme/InitTheme' import { InitTheme } from '@/providers/Theme/InitTheme'
@ -67,6 +68,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<Header /> <Header />
{children} {children}
<Footer /> <Footer />
<BackToTop />
<PopupAnnouncementComponent {...popupData} /> <PopupAnnouncementComponent {...popupData} />
{cookieEnabled && ( {cookieEnabled && (
<CookieConsent <CookieConsent

View File

@ -13,6 +13,7 @@ export const FDAlternateHeroBlockComponent: React.FC<Props> = ({
image, image,
imageCaption, imageCaption,
sectionBackground = 'white', sectionBackground = 'white',
anchorId,
}) => { }) => {
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
@ -34,7 +35,7 @@ export const FDAlternateHeroBlockComponent: React.FC<Props> = ({
: 'text-fd-navy dark:text-white' : 'text-fd-navy dark:text-white'
return ( return (
<section className={`w-full pt-16 md:pt-20 lg:pt-[99px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full pt-16 md:pt-20 lg:pt-[99px] ${bgClass}`}>
{/* Centered content */} {/* Centered content */}
<div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center flex flex-col items-center gap-6 pb-12 md:pb-16"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center flex flex-col items-center gap-6 pb-12 md:pb-16">
<h1 className={`w-full max-w-[820px] font-joey-heavy text-fd-display ${titleClass}`}> <h1 className={`w-full max-w-[820px] font-joey-heavy text-fd-display ${titleClass}`}>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDAlternateHeroBlock: Block = { export const FDAlternateHeroBlock: Block = {
slug: 'fdAlternateHero', slug: 'fdAlternateHero',
@ -78,5 +79,6 @@ export const FDAlternateHeroBlock: Block = {
{ label: 'Grå', value: 'gray' }, { label: 'Grå', value: 'gray' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -71,13 +71,14 @@ export const FDCardGridBlockComponent: React.FC<FDCardGridBlockProps> = ({
cardStyle = 'outlined', cardStyle = 'outlined',
cards, cards,
sectionBackground = 'white', sectionBackground = 'white',
anchorId,
}) => { }) => {
const style = cardStyleMap[cardStyle] || cardStyleMap.outlined const style = cardStyleMap[cardStyle] || cardStyleMap.outlined
const sectionBg = sectionBgMap[sectionBackground || 'white'] const sectionBg = sectionBgMap[sectionBackground || 'white']
const gridCols = layoutGridMap[layout] || layoutGridMap['1-1-1'] const gridCols = layoutGridMap[layout] || layoutGridMap['1-1-1']
return ( return (
<section 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}`}>
<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={`grid grid-cols-1 md:grid-cols-2 ${gridCols} gap-4 md:gap-6`}> <div className={`grid grid-cols-1 md:grid-cols-2 ${gridCols} gap-4 md:gap-6`}>
{cards?.map((card, index) => { {cards?.map((card, index) => {

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDCardGridBlock: Block = { export const FDCardGridBlock: Block = {
slug: 'fdCardGrid', slug: 'fdCardGrid',
@ -134,5 +135,6 @@ export const FDCardGridBlock: Block = {
{ label: 'Grå', value: 'gray' }, { label: 'Grå', value: 'gray' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -36,6 +36,7 @@ export const FDCodeEmbedBlockComponent: React.FC<FDCodeEmbedBlockProps> = ({
sectionBackground = 'white', sectionBackground = 'white',
textColor = 'auto', textColor = 'auto',
embedBackground = 'none', embedBackground = 'none',
anchorId,
}) => { }) => {
const customCodeRef = useRef<HTMLDivElement>(null) const customCodeRef = useRef<HTMLDivElement>(null)
@ -96,7 +97,7 @@ export const FDCodeEmbedBlockComponent: React.FC<FDCodeEmbedBlockProps> = ({
: '' : ''
return ( return (
<section className={`relative w-full py-16 md:py-20 lg:py-[99px] ${bgClass} overflow-hidden`}> <section id={anchorId || undefined} className={`relative w-full py-16 md:py-20 lg:py-[99px] ${bgClass} overflow-hidden`}>
<div className={`relative ${containerClass} mx-auto px-6 md:px-8`}> <div className={`relative ${containerClass} mx-auto px-6 md:px-8`}>
{(heading || description) && ( {(heading || description) && (
<div className="mb-8 md:mb-12 text-center"> <div className="mb-8 md:mb-12 text-center">

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDCodeEmbedBlock: Block = { export const FDCodeEmbedBlock: Block = {
slug: 'fdCodeEmbed', slug: 'fdCodeEmbed',
@ -151,5 +152,6 @@ export const FDCodeEmbedBlock: Block = {
{ label: 'Navy kort', value: 'navy-card' }, { label: 'Navy kort', value: 'navy-card' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -8,9 +8,10 @@ const imageRadius = 'rounded-[16px] md:rounded-[24px] lg:rounded-[30px]'
export const FDContactBlockComponent: React.FC<FDContactBlockProps> = ({ export const FDContactBlockComponent: React.FC<FDContactBlockProps> = ({
heading, heading,
contactMethods, contactMethods,
anchorId,
}) => { }) => {
return ( return (
<section className="relative w-full bg-fd-navy py-16 md:py-20 lg:pt-[100px] lg:pb-[120px]"> <section id={anchorId || undefined} className="relative w-full bg-fd-navy py-16 md:py-20 lg:pt-[100px] lg:pb-[120px]">
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-center gap-8 lg:gap-10"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-center gap-8 lg:gap-10">
<h2 className="w-full font-joey-heavy text-fd-h1 text-fd-yellow text-center"> <h2 className="w-full font-joey-heavy text-fd-h1 text-fd-yellow text-center">
{heading} {heading}

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDContactBlock: Block = { export const FDContactBlock: Block = {
slug: 'fdContact', slug: 'fdContact',
@ -48,5 +49,6 @@ export const FDContactBlock: Block = {
}, },
], ],
}, },
anchorField,
], ],
} }

View File

@ -37,6 +37,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
sectionBackground = 'white', sectionBackground = 'white',
layout = 'standard', layout = 'standard',
externalApi, externalApi,
anchorId,
}) => { }) => {
const [formData, setFormData] = useState<Record<string, string>>({}) const [formData, setFormData] = useState<Record<string, string>>({})
const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle') const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
@ -278,7 +279,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
if (status === 'sent') { if (status === 'sent') {
return ( return (
<section 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}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center">
<div className={`inline-flex items-center justify-center w-16 h-16 rounded-full mb-6 ${dark ? 'bg-fd-yellow/20' : 'bg-fd-mint/20 dark:bg-fd-yellow/20'}`}> <div className={`inline-flex items-center justify-center w-16 h-16 rounded-full mb-6 ${dark ? 'bg-fd-yellow/20' : 'bg-fd-mint/20 dark:bg-fd-yellow/20'}`}>
<svg className={`w-8 h-8 ${dark ? 'text-fd-yellow' : 'text-fd-mint dark:text-fd-yellow'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}> <svg className={`w-8 h-8 ${dark ? 'text-fd-yellow' : 'text-fd-mint dark:text-fd-yellow'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDContactFormBlock: Block = { export const FDContactFormBlock: Block = {
slug: 'fdContactForm', slug: 'fdContactForm',
@ -153,5 +154,6 @@ export const FDContactFormBlock: Block = {
}, },
], ],
}, },
anchorField,
], ],
} }

View File

@ -51,13 +51,14 @@ export const FDCtaBannerBlockComponent: React.FC<FDCtaBannerBlockProps> = ({
sectionBackground = 'yellow', sectionBackground = 'yellow',
alignment = 'center', alignment = 'center',
size = 'medium', size = 'medium',
anchorId,
}) => { }) => {
const theme = bgMap[sectionBackground ?? 'yellow'] || bgMap.yellow const theme = bgMap[sectionBackground ?? 'yellow'] || bgMap.yellow
const sizing = sizeMap[size ?? 'medium'] || sizeMap.medium const sizing = sizeMap[size ?? 'medium'] || sizeMap.medium
const isCenter = alignment === 'center' const isCenter = alignment === 'center'
return ( return (
<section className={`w-full ${sizing.py} ${theme.section}`}> <section id={anchorId || undefined} className={`w-full ${sizing.py} ${theme.section}`}>
<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={`flex flex-col gap-6 md:gap-8 ${isCenter ? 'items-center text-center' : 'items-start text-left'} max-w-[800px] ${isCenter ? 'mx-auto' : ''}`}> <div className={`flex flex-col gap-6 md:gap-8 ${isCenter ? 'items-center text-center' : 'items-start text-left'} max-w-[800px] ${isCenter ? 'mx-auto' : ''}`}>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDCtaBannerBlock: Block = { export const FDCtaBannerBlock: Block = {
slug: 'fdCtaBanner', slug: 'fdCtaBanner',
@ -84,5 +85,6 @@ export const FDCtaBannerBlock: Block = {
{ label: 'Stor', value: 'large' }, { label: 'Stor', value: 'large' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -28,6 +28,7 @@ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> =
imageOverlay = 'none', imageOverlay = 'none',
imagePosition = 'right', imagePosition = 'right',
theme = 'dark', theme = 'dark',
anchorId,
}) => { }) => {
const isDark = theme === 'dark' const isDark = theme === 'dark'
const hasImage = !!image const hasImage = !!image
@ -79,7 +80,7 @@ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> =
) : null ) : null
return ( return (
<section 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}`}>
<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' ? ( {imagePosition === 'left' ? (
<>{imageContent}{textContent}</> <>{imageContent}{textContent}</>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDCtaSideImageBlock: Block = { export const FDCtaSideImageBlock: Block = {
slug: 'fdCtaSideImage', slug: 'fdCtaSideImage',
@ -83,5 +84,6 @@ export const FDCtaSideImageBlock: Block = {
{ label: 'Mörkt', value: 'dark' }, { label: 'Mörkt', value: 'dark' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -83,6 +83,7 @@ export const FDDataTableBlockComponent: React.FC<Props> = ({
stripeRows = true, stripeRows = true,
bordered = false, bordered = false,
firstColumnBold = false, firstColumnBold = false,
anchorId,
}) => { }) => {
const [tableData, setTableData] = useState<TableData | null>(null) const [tableData, setTableData] = useState<TableData | null>(null)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@ -166,7 +167,7 @@ export const FDDataTableBlockComponent: React.FC<Props> = ({
: '' : ''
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className="max-w-[1200px] mx-auto px-6 md:px-8">
{(heading || description) && ( {(heading || description) && (

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDDataTableBlock: Block = { export const FDDataTableBlock: Block = {
slug: 'fdDataTable', slug: 'fdDataTable',
@ -143,5 +144,6 @@ export const FDDataTableBlock: Block = {
description: 'Gör den första kolumnen fet — användbart för namnkolumner.', description: 'Gör den första kolumnen fet — användbart för namnkolumner.',
}, },
}, },
anchorField,
], ],
} }

View File

@ -7,6 +7,7 @@ export const FDFaqBlockComponent: React.FC<FDFaqBlockProps> = ({
heading, heading,
items, items,
theme = 'gray', theme = 'gray',
anchorId,
}) => { }) => {
const [openIndex, setOpenIndex] = useState<number | null>(null) const [openIndex, setOpenIndex] = useState<number | null>(null)
/* Generate a stable unique prefix for this block instance */ /* Generate a stable unique prefix for this block instance */
@ -37,7 +38,7 @@ export const FDFaqBlockComponent: React.FC<FDFaqBlockProps> = ({
: 'text-fd-navy/80 dark:text-white/80' : 'text-fd-navy/80 dark:text-white/80'
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[130px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[130px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-start gap-6"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-start gap-6">
<h2 className={`w-full max-w-[550px] font-joey-heavy text-fd-h1 ${headingColor}`}> <h2 className={`w-full max-w-[550px] font-joey-heavy text-fd-h1 ${headingColor}`}>
{heading} {heading}

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDFaqBlock: Block = { export const FDFaqBlock: Block = {
slug: 'fdFaq', slug: 'fdFaq',
@ -50,5 +51,6 @@ export const FDFaqBlock: Block = {
{ label: 'Mörkt', value: 'dark' }, { label: 'Mörkt', value: 'dark' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -8,6 +8,7 @@ export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncement
ctaText, ctaText,
ctaLink = '#', ctaLink = '#',
theme = 'gray', theme = 'gray',
anchorId,
}) => { }) => {
const isDark = theme === 'dark' const isDark = theme === 'dark'
@ -31,7 +32,7 @@ export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncement
const onDark = isDark || true // once dark: kicks in bg is navy anyway const onDark = isDark || true // once dark: kicks in bg is navy anyway
return ( return (
<section className={`w-full py-20 md:py-28 lg:py-[173px] ${bgClass}`}> <section id={anchorId || undefined} 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 <h2
className={`w-full max-w-[696px] font-joey-bold text-fd-h1 text-center leading-tight ${headingColor}`} className={`w-full max-w-[696px] font-joey-bold text-fd-h1 text-center leading-tight ${headingColor}`}

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDFeatureAnnouncementBlock: Block = { export const FDFeatureAnnouncementBlock: Block = {
slug: 'fdFeatureAnnouncement', slug: 'fdFeatureAnnouncement',
@ -47,5 +48,6 @@ export const FDFeatureAnnouncementBlock: Block = {
{ label: 'Mörkt', value: 'dark' }, { label: 'Mörkt', value: 'dark' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -47,6 +47,7 @@ export const FDHeaderTextImageBlockComponent: React.FC<FDHeaderTextImageBlockPro
textAlign = 'center', textAlign = 'center',
sectionBackground = 'white', sectionBackground = 'white',
textColor = 'navy', textColor = 'navy',
anchorId,
}) => { }) => {
const bg = bgMap[sectionBackground || 'white'] const bg = bgMap[sectionBackground || 'white']
const headClr = headingMap[textColor || 'navy'] const headClr = headingMap[textColor || 'navy']
@ -57,7 +58,7 @@ export const FDHeaderTextImageBlockComponent: React.FC<FDHeaderTextImageBlockPro
const media = image as Media const media = image as Media
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col gap-8 md:gap-10"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col gap-8 md:gap-10">
{(heading || body) && ( {(heading || body) && (
<div className={`flex flex-col gap-4 md:gap-6 ${align} ${textAlign === 'center' ? 'max-w-[900px] mx-auto' : ''}`}> <div className={`flex flex-col gap-4 md:gap-6 ${align} ${textAlign === 'center' ? 'max-w-[900px] mx-auto' : ''}`}>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDHeaderTextImageBlock: Block = { export const FDHeaderTextImageBlock: Block = {
slug: 'fdHeaderTextImage', slug: 'fdHeaderTextImage',
@ -87,5 +88,6 @@ export const FDHeaderTextImageBlock: Block = {
{ label: 'Vit', value: 'white' }, { label: 'Vit', value: 'white' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -15,6 +15,7 @@ export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({
overlayOpacity = '50', overlayOpacity = '50',
textColor = 'auto', textColor = 'auto',
theme = 'light', theme = 'light',
anchorId,
}) => { }) => {
const media = backgroundImage as Media | undefined const media = backgroundImage as Media | undefined
const hasBgImage = Boolean(media?.url) const hasBgImage = Boolean(media?.url)
@ -39,7 +40,7 @@ export const FDHeroBlockComponent: React.FC<FDHeroBlockProps> = ({
const secondaryOnDark = textColor === 'navy' ? false : isDark const secondaryOnDark = textColor === 'navy' ? false : isDark
return ( return (
<section <section id={anchorId || undefined}
className={`relative w-full py-16 md:py-20 lg:py-[99px] ${ className={`relative w-full py-16 md:py-20 lg:py-[99px] ${
hasBgImage ? '' : isDark ? 'bg-fd-navy' : 'bg-white dark:bg-fd-navy' hasBgImage ? '' : isDark ? 'bg-fd-navy' : 'bg-white dark:bg-fd-navy'
} overflow-hidden`} } overflow-hidden`}

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDHeroBlock: Block = { export const FDHeroBlock: Block = {
slug: 'fdHero', slug: 'fdHero',
@ -109,5 +110,6 @@ export const FDHeroBlock: Block = {
{ label: 'Mörkt', value: 'dark' }, { label: 'Mörkt', value: 'dark' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -78,6 +78,7 @@ export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({
buttonColor = 'yellow', buttonColor = 'yellow',
sectionBackground = 'white', sectionBackground = 'white',
titleColor = 'navy', titleColor = 'navy',
anchorId,
}) => { }) => {
const sectionBg = sectionBgMap[sectionBackground || 'white'] const sectionBg = sectionBgMap[sectionBackground || 'white']
const sectionTitleColor = titleColorMap[titleColor || 'navy'] const sectionTitleColor = titleColorMap[titleColor || 'navy']
@ -88,7 +89,7 @@ export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({
const outlineOnDark = style.isDark const outlineOnDark = style.isDark
return ( return (
<section 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}`}>
<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-fd-h1 text-center mb-10 md:mb-14 ${sectionTitleColor}`}>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDPricingCardBlock: Block = { export const FDPricingCardBlock: Block = {
slug: 'fdPricingCard', slug: 'fdPricingCard',
@ -131,5 +132,6 @@ export const FDPricingCardBlock: Block = {
{ label: 'Gul', value: 'yellow' }, { label: 'Gul', value: 'yellow' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -82,6 +82,7 @@ export const FDServiceCalculatorBlockComponent: React.FC<Props> = ({
fixedFees = [], fixedFees = [],
discountPercent, discountPercent,
discountLabel, discountLabel,
anchorId,
}) => { }) => {
/* ── State: one selected index per option group ──────────────────────── */ /* ── State: one selected index per option group ──────────────────────── */
const [selectedOptions, setSelectedOptions] = useState<Record<number, number>>(() => { const [selectedOptions, setSelectedOptions] = useState<Record<number, number>>(() => {
@ -202,7 +203,7 @@ export const FDServiceCalculatorBlockComponent: React.FC<Props> = ({
: `${discountPercent}% rabatt på alla resurser` : `${discountPercent}% rabatt på alla resurser`
return ( return (
<section className={`fd-section ${bgClass}`}> <section id={anchorId || undefined} className={`fd-section ${bgClass}`}>
<div className="fd-container"> <div className="fd-container">
{/* ── Header ──────────────────────────────────────────────────── */} {/* ── Header ──────────────────────────────────────────────────── */}

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDServiceCalculatorBlock: Block = { export const FDServiceCalculatorBlock: Block = {
slug: 'fdServiceCalculator', slug: 'fdServiceCalculator',
@ -192,5 +193,6 @@ export const FDServiceCalculatorBlock: Block = {
}, },
], ],
}, },
anchorField,
], ],
} }

View File

@ -10,6 +10,7 @@ export const FDServiceChooserBlockComponent: React.FC<Props> = ({
description, description,
categories = [], categories = [],
sectionBackground = 'gray', sectionBackground = 'gray',
anchorId,
}) => { }) => {
const [activeIndex, setActiveIndex] = useState(0) const [activeIndex, setActiveIndex] = useState(0)
const [animating, setAnimating] = useState(false) const [animating, setAnimating] = useState(false)
@ -51,7 +52,7 @@ export const FDServiceChooserBlockComponent: React.FC<Props> = ({
const activeCategory = (categories ?? [])[activeIndex] const activeCategory = (categories ?? [])[activeIndex]
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}>
<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="text-center mb-10 md:mb-12"> <div className="text-center mb-10 md:mb-12">

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDServiceChooserBlock: Block = { export const FDServiceChooserBlock: Block = {
slug: 'fdServiceChooser', slug: 'fdServiceChooser',
@ -92,5 +93,6 @@ export const FDServiceChooserBlock: Block = {
{ label: 'Navy', value: 'navy' }, { label: 'Navy', value: 'navy' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -15,9 +15,10 @@ export const FDServicesGridBlockComponent: React.FC<FDServicesGridBlockProps> =
heading, heading,
services, services,
columns = '4', columns = '4',
anchorId,
}) => { }) => {
return ( return (
<section className="relative w-full bg-white dark:bg-fd-navy py-16 md:py-20 lg:py-[99px]"> <section id={anchorId || undefined} className="relative w-full bg-white dark:bg-fd-navy py-16 md:py-20 lg:py-[99px]">
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className="max-w-[1200px] mx-auto px-6 md:px-8">
<h2 className="font-joey-heavy text-fd-h1 text-fd-navy dark:text-fd-yellow mb-8 lg:mb-12"> <h2 className="font-joey-heavy text-fd-h1 text-fd-navy dark:text-fd-yellow mb-8 lg:mb-12">
{heading} {heading}

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDServicesGridBlock: Block = { export const FDServicesGridBlock: Block = {
slug: 'fdServicesGrid', slug: 'fdServicesGrid',
@ -63,5 +64,6 @@ export const FDServicesGridBlock: Block = {
{ label: '4 kolumner', value: '4' }, { label: '4 kolumner', value: '4' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -7,6 +7,7 @@ export const FDStatisticsBlockComponent: React.FC<Props> = ({
stats = [], stats = [],
sectionBackground = 'white', sectionBackground = 'white',
numberColor = 'gradient', numberColor = 'gradient',
anchorId,
}) => { }) => {
const [visible, setVisible] = useState(false) const [visible, setVisible] = useState(false)
/* Priority #8: Detect prefers-reduced-motion */ /* Priority #8: Detect prefers-reduced-motion */
@ -61,7 +62,7 @@ export const FDStatisticsBlockComponent: React.FC<Props> = ({
} }
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`} ref={ref}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`} ref={ref}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center"> <div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center">
{heading && ( {heading && (
<h2 className={`font-joey-heavy text-fd-h1 mb-12 md:mb-16 ${titleClass}`}> <h2 className={`font-joey-heavy text-fd-h1 mb-12 md:mb-16 ${titleClass}`}>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDStatisticsBlock: Block = { export const FDStatisticsBlock: Block = {
slug: 'fdStatistics', slug: 'fdStatistics',
@ -69,5 +70,6 @@ export const FDStatisticsBlock: Block = {
{ label: 'Vit', value: 'white' }, { label: 'Vit', value: 'white' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -48,6 +48,7 @@ export const FDTeamBlockComponent: React.FC<FDTeamBlockProps> = ({
columns = '3', columns = '3',
cardStyle = 'navy', cardStyle = 'navy',
sectionBackground = 'white', sectionBackground = 'white',
anchorId,
}) => { }) => {
const sectionBg = sectionBgMap[sectionBackground ?? 'white'] || sectionBgMap.white const sectionBg = sectionBgMap[sectionBackground ?? 'white'] || sectionBgMap.white
const card = cardMap[cardStyle ?? 'navy'] || cardMap.navy const card = cardMap[cardStyle ?? 'navy'] || cardMap.navy
@ -55,7 +56,7 @@ export const FDTeamBlockComponent: React.FC<FDTeamBlockProps> = ({
const isNavySection = sectionBackground === 'navy' const isNavySection = sectionBackground === 'navy'
return ( return (
<section 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}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className="max-w-[1200px] mx-auto px-6 md:px-8">
{(heading || subheading) && ( {(heading || subheading) && (

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDTeamBlock: Block = { export const FDTeamBlock: Block = {
slug: 'fdTeam', slug: 'fdTeam',
@ -104,5 +105,6 @@ export const FDTeamBlock: Block = {
{ label: 'Navy', value: 'navy' }, { label: 'Navy', value: 'navy' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -26,6 +26,7 @@ export const FDTechPropertiesBlockComponent: React.FC<FDTechPropertiesBlockProps
sectionBackground = 'navy', sectionBackground = 'navy',
categoryColor = 'white', categoryColor = 'white',
valueColor = 'yellow', valueColor = 'yellow',
anchorId,
}) => { }) => {
const bg = bgMap[sectionBackground || 'navy'] const bg = bgMap[sectionBackground || 'navy']
const catColor = catColorMap[categoryColor || 'white'] const catColor = catColorMap[categoryColor || 'white']
@ -35,7 +36,7 @@ export const FDTechPropertiesBlockComponent: React.FC<FDTechPropertiesBlockProps
count <= 2 ? 'grid-cols-2' : count === 3 ? 'grid-cols-3' : 'grid-cols-2 md:grid-cols-4' count <= 2 ? 'grid-cols-2' : count === 3 ? 'grid-cols-3' : 'grid-cols-2 md:grid-cols-4'
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<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={`grid ${gridCols} gap-8 md:gap-12`}> <div className={`grid ${gridCols} gap-8 md:gap-12`}>
{properties?.map((prop, index) => ( {properties?.map((prop, index) => (

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDTechPropertiesBlock: Block = { export const FDTechPropertiesBlock: Block = {
slug: 'fdTechProperties', slug: 'fdTechProperties',
@ -68,5 +69,6 @@ export const FDTechPropertiesBlock: Block = {
{ label: 'Navy', value: 'navy' }, { label: 'Navy', value: 'navy' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -57,12 +57,13 @@ export const FDTestimonialBlockComponent: React.FC<FDTestimonialBlockProps> = ({
testimonials, testimonials,
layout = 'grid', layout = 'grid',
sectionBackground = 'gray', sectionBackground = 'gray',
anchorId,
}) => { }) => {
const theme = bgMap[sectionBackground ?? 'gray'] || bgMap.gray const theme = bgMap[sectionBackground ?? 'gray'] || bgMap.gray
const isFeatured = layout === 'featured' const isFeatured = layout === 'featured'
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${theme.section}`}> <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"> <div className="max-w-[1200px] mx-auto px-6 md:px-8">
{heading && ( {heading && (

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDTestimonialBlock: Block = { export const FDTestimonialBlock: Block = {
slug: 'fdTestimonial', slug: 'fdTestimonial',
@ -85,5 +86,6 @@ export const FDTestimonialBlock: Block = {
{ label: 'Navy', value: 'navy' }, { label: 'Navy', value: 'navy' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -41,6 +41,7 @@ export const FDTextBlockComponent: React.FC<FDTextBlockProps> = ({
textColor = 'navy', textColor = 'navy',
sectionBackground = 'white', sectionBackground = 'white',
maxWidth = 'wide', maxWidth = 'wide',
anchorId,
}) => { }) => {
const bg = bgMap[sectionBackground || 'white'] const bg = bgMap[sectionBackground || 'white']
const align = alignMap[alignment || 'left'] const align = alignMap[alignment || 'left']
@ -52,7 +53,7 @@ export const FDTextBlockComponent: React.FC<FDTextBlockProps> = ({
if (!heading && !subheading && !body) return null if (!heading && !subheading && !body) return null
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<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={`${width} ${containerAlign} ${align} flex flex-col gap-4 md:gap-6`}> <div className={`${width} ${containerAlign} ${align} flex flex-col gap-4 md:gap-6`}>
{heading && ( {heading && (

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDTextBlock: Block = { export const FDTextBlock: Block = {
slug: 'fdText', slug: 'fdText',
@ -74,5 +75,6 @@ export const FDTextBlock: Block = {
{ label: 'Full', value: 'full' }, { label: 'Full', value: 'full' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -42,6 +42,7 @@ export const FDUspChecklistBlockComponent: React.FC<FDUspChecklistBlockProps> =
checkColor = 'navy', checkColor = 'navy',
sectionBackground = 'white', sectionBackground = 'white',
textColor = 'navy', textColor = 'navy',
anchorId,
}) => { }) => {
const bg = bgMap[sectionBackground || 'white'] const bg = bgMap[sectionBackground || 'white']
const headClr = headingMap[textColor || 'navy'] const headClr = headingMap[textColor || 'navy']
@ -76,7 +77,7 @@ export const FDUspChecklistBlockComponent: React.FC<FDUspChecklistBlockProps> =
) : null ) : null
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<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>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDUspChecklistBlock: Block = { export const FDUspChecklistBlock: Block = {
slug: 'fdUspChecklist', slug: 'fdUspChecklist',
@ -80,5 +81,6 @@ export const FDUspChecklistBlock: Block = {
{ label: 'Vit', value: 'white' }, { label: 'Vit', value: 'white' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -52,6 +52,7 @@ export const FDUspTableBlockComponent: React.FC<FDUspTableBlockProps> = ({
checkColor = 'navy', checkColor = 'navy',
sectionBackground = 'white', sectionBackground = 'white',
textColor = 'navy', textColor = 'navy',
anchorId,
}) => { }) => {
const bg = bgMap[sectionBackground || 'white'] const bg = bgMap[sectionBackground || 'white']
const headClr = headingMap[textColor || 'navy'] const headClr = headingMap[textColor || 'navy']
@ -60,7 +61,7 @@ export const FDUspTableBlockComponent: React.FC<FDUspTableBlockProps> = ({
const border = borderMap[textColor || 'navy'] const border = borderMap[textColor || 'navy']
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className="max-w-[1200px] mx-auto px-6 md:px-8">
{heading && ( {heading && (
<h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${headClr}`}>{heading}</h2> <h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${headClr}`}>{heading}</h2>

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDUspTableBlock: Block = { export const FDUspTableBlock: Block = {
slug: 'fdUspTable', slug: 'fdUspTable',
@ -70,5 +71,6 @@ export const FDUspTableBlock: Block = {
{ label: 'Vit', value: 'white' }, { label: 'Vit', value: 'white' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -58,6 +58,7 @@ export const FDVideoBlockComponent: React.FC<FDVideoBlockProps> = ({
maxWidth = 'default', maxWidth = 'default',
sectionBackground = 'white', sectionBackground = 'white',
textColor = 'auto', textColor = 'auto',
anchorId,
}) => { }) => {
const [isPlaying, setIsPlaying] = useState(false) const [isPlaying, setIsPlaying] = useState(false)
@ -113,7 +114,7 @@ export const FDVideoBlockComponent: React.FC<FDVideoBlockProps> = ({
) )
return ( return (
<section className={`relative w-full py-16 md:py-20 lg:py-[99px] ${bgClass} overflow-hidden`}> <section id={anchorId || undefined} className={`relative w-full py-16 md:py-20 lg:py-[99px] ${bgClass} overflow-hidden`}>
<div className={`relative ${containerClass} mx-auto px-6 md:px-8`}> <div className={`relative ${containerClass} mx-auto px-6 md:px-8`}>
{(heading || description) && ( {(heading || description) && (

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDVideoBlock: Block = { export const FDVideoBlock: Block = {
slug: 'fdVideo', slug: 'fdVideo',
@ -145,5 +146,6 @@ export const FDVideoBlock: Block = {
{ label: 'Vit', value: 'white' }, { label: 'Vit', value: 'white' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -82,6 +82,7 @@ export const FDVpsCalculatorBlockComponent: React.FC<FDVpsCalculatorBlockProps>
showAdminFee, showAdminFee,
adminFeeAmount, adminFeeAmount,
additionalServices = [], additionalServices = [],
anchorId,
}) => { }) => {
const pricing = { const pricing = {
windows: pricingWindowsLicense ?? DEFAULT_PRICING.windows, windows: pricingWindowsLicense ?? DEFAULT_PRICING.windows,
@ -153,7 +154,7 @@ export const FDVpsCalculatorBlockComponent: React.FC<FDVpsCalculatorBlockProps>
const hasTillval = (additionalServices ?? []).length > 0 const hasTillval = (additionalServices ?? []).length > 0
return ( return (
<section className={`fd-section ${bgClass}`}> <section id={anchorId || undefined} className={`fd-section ${bgClass}`}>
<div className="fd-container"> <div className="fd-container">
{(heading || description) && ( {(heading || description) && (

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDVpsCalculatorBlock: Block = { export const FDVpsCalculatorBlock: Block = {
slug: 'fdVpsCalculator', slug: 'fdVpsCalculator',
@ -125,5 +126,6 @@ export const FDVpsCalculatorBlock: Block = {
}, },
], ],
}, },
anchorField,
], ],
} }

View File

@ -33,6 +33,7 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
cardBackground = 'navy', cardBackground = 'navy',
buttonColor = 'yellow', buttonColor = 'yellow',
sectionBackground = 'white', sectionBackground = 'white',
anchorId,
}) => { }) => {
const card = cardBgMap[cardBackground || 'navy'] const card = cardBgMap[cardBackground || 'navy']
const sectionBg = sectionBgMap[sectionBackground || 'white'] const sectionBg = sectionBgMap[sectionBackground || 'white']
@ -41,7 +42,7 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
const hasImage = media && typeof media === 'object' && media.url const hasImage = media && typeof media === 'object' && media.url
return ( return (
<section 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}`}>
<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} ${cardRadius} overflow-hidden flex flex-col lg:flex-row`}> <div className={`${card.bg} ${cardRadius} overflow-hidden flex flex-col lg:flex-row`}>
<div className="flex-1 flex flex-col justify-center gap-5 md:gap-6 px-8 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-8 md:px-14 lg:px-16 py-12 md:py-16">

View File

@ -1,4 +1,5 @@
import type { Block } from 'payload' import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDWideCardBlock: Block = { export const FDWideCardBlock: Block = {
slug: 'fdWideCard', slug: 'fdWideCard',
@ -75,5 +76,6 @@ export const FDWideCardBlock: Block = {
{ label: 'Navy', value: 'navy' }, { label: 'Navy', value: 'navy' },
], ],
}, },
anchorField,
], ],
} }

View File

@ -0,0 +1,47 @@
'use client'
import React, { useState, useEffect, useCallback } from 'react'
import { ChevronUpIcon } from 'lucide-react'
/**
* Floating "back to top" button.
* Appears after scrolling down 400px, smooth-scrolls to top on click.
* Styled to match FD design system.
*/
export const BackToTop: React.FC = () => {
const [visible, setVisible] = useState(false)
useEffect(() => {
const onScroll = () => {
setVisible(window.scrollY > 400)
}
window.addEventListener('scroll', onScroll, { passive: true })
return () => window.removeEventListener('scroll', onScroll)
}, [])
const scrollToTop = useCallback(() => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}, [])
return (
<button
onClick={scrollToTop}
aria-label="Tillbaka till toppen"
className={`
fixed bottom-6 right-6 z-40
w-11 h-11 rounded-full
bg-fd-navy/80 backdrop-blur-sm
text-white hover:text-fd-yellow
border border-white/10 hover:border-fd-yellow/30
shadow-lg hover:shadow-xl
flex items-center justify-center
transition-all duration-300 ease-in-out cursor-pointer
${visible
? 'opacity-100 translate-y-0 pointer-events-auto'
: 'opacity-0 translate-y-4 pointer-events-none'
}
`}
>
<ChevronUpIcon className="w-5 h-5" />
</button>
)
}

29
src/fields/anchorField.ts Normal file
View File

@ -0,0 +1,29 @@
import type { Field } from 'payload'
/**
* Shared anchorId field for all FD blocks.
* Allows editors to set an anchor link ID on any section,
* enabling direct linking like /page#section-name
*
* Usage in block config:
* import { anchorField } from '@/fields/anchorField'
* fields: [ ...contentFields, anchorField ]
*
* Usage in block component:
* <section id={anchorId || undefined} ...>
*/
export const anchorField: Field = {
name: 'anchorId',
type: 'text',
label: 'Ankarlänk-ID',
admin: {
description:
'Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.',
position: 'sidebar',
},
validate: (value: unknown) => {
if (!value) return true
if (typeof value === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(value)) return true
return 'Använd bara små bokstäver (a-z), siffror (0-9) och bindestreck (-). Inga mellanslag.'
},
}

View File

@ -233,6 +233,10 @@ export interface FDHeroBlock {
* Ignoreras om bakgrundsbild är vald * Ignoreras om bakgrundsbild är vald
*/ */
theme?: ('light' | 'dark') | null; theme?: ('light' | 'dark') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdHero'; blockType: 'fdHero';
@ -305,6 +309,10 @@ export interface FDCtaSideImageBlock {
| null; | null;
imagePosition?: ('right' | 'left') | null; imagePosition?: ('right' | 'left') | null;
theme?: ('light' | 'dark') | null; theme?: ('light' | 'dark') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdCtaSideImage'; blockType: 'fdCtaSideImage';
@ -319,6 +327,10 @@ export interface FDFeatureAnnouncementBlock {
ctaText?: string | null; ctaText?: string | null;
ctaLink?: string | null; ctaLink?: string | null;
theme?: ('gray' | 'light' | 'dark') | null; theme?: ('gray' | 'light' | 'dark') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdFeatureAnnouncement'; blockType: 'fdFeatureAnnouncement';
@ -339,6 +351,10 @@ export interface FDServicesGridBlock {
}[] }[]
| null; | null;
columns?: ('2' | '3' | '4') | null; columns?: ('2' | '3' | '4') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdServicesGrid'; blockType: 'fdServicesGrid';
@ -360,6 +376,10 @@ export interface FDContactBlock {
id?: string | null; id?: string | null;
}[] }[]
| null; | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdContact'; blockType: 'fdContact';
@ -392,6 +412,10 @@ export interface FDFaqBlock {
}[] }[]
| null; | null;
theme?: ('gray' | 'light' | 'dark') | null; theme?: ('gray' | 'light' | 'dark') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdFaq'; blockType: 'fdFaq';
@ -430,6 +454,10 @@ export interface FDCardGridBlock {
}[] }[]
| null; | null;
sectionBackground?: ('white' | 'navy' | 'gray') | null; sectionBackground?: ('white' | 'navy' | 'gray') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdCardGrid'; blockType: 'fdCardGrid';
@ -472,6 +500,10 @@ export interface FDPricingCardBlock {
* Färg blockrubriken * Färg blockrubriken
*/ */
titleColor?: ('navy' | 'white' | 'yellow') | null; titleColor?: ('navy' | 'white' | 'yellow') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdPricingCard'; blockType: 'fdPricingCard';
@ -525,6 +557,10 @@ export interface FDUspChecklistBlock {
checkColor?: ('navy' | 'yellow' | 'gray') | null; checkColor?: ('navy' | 'yellow' | 'gray') | null;
sectionBackground?: ('white' | 'gray' | 'navy') | null; sectionBackground?: ('white' | 'gray' | 'navy') | null;
textColor?: ('navy' | 'white') | null; textColor?: ('navy' | 'white') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdUspChecklist'; blockType: 'fdUspChecklist';
@ -542,6 +578,10 @@ export interface FDWideCardBlock {
cardBackground?: ('navy' | 'yellow' | 'gray' | 'white') | null; cardBackground?: ('navy' | 'yellow' | 'gray' | 'white') | null;
buttonColor?: ('yellow' | 'navy' | 'white') | null; buttonColor?: ('yellow' | 'navy' | 'white') | null;
sectionBackground?: ('white' | 'gray' | 'navy') | null; sectionBackground?: ('white' | 'gray' | 'navy') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdWideCard'; blockType: 'fdWideCard';
@ -567,6 +607,10 @@ export interface FDTechPropertiesBlock {
sectionBackground?: ('navy' | 'white' | 'gray' | 'yellow') | null; sectionBackground?: ('navy' | 'white' | 'gray' | 'yellow') | null;
categoryColor?: ('white' | 'navy') | null; categoryColor?: ('white' | 'navy') | null;
valueColor?: ('yellow' | 'white' | 'navy') | null; valueColor?: ('yellow' | 'white' | 'navy') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdTechProperties'; blockType: 'fdTechProperties';
@ -604,6 +648,10 @@ export interface FDUspTableBlock {
checkColor?: ('navy' | 'yellow' | 'gray') | null; checkColor?: ('navy' | 'yellow' | 'gray') | null;
sectionBackground?: ('white' | 'gray' | 'navy') | null; sectionBackground?: ('white' | 'gray' | 'navy') | null;
textColor?: ('navy' | 'white') | null; textColor?: ('navy' | 'white') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdUspTable'; blockType: 'fdUspTable';
@ -623,6 +671,10 @@ export interface FDHeaderTextImageBlock {
textAlign?: ('left' | 'center') | null; textAlign?: ('left' | 'center') | null;
sectionBackground?: ('white' | 'gray' | 'navy') | null; sectionBackground?: ('white' | 'gray' | 'navy') | null;
textColor?: ('navy' | 'white') | null; textColor?: ('navy' | 'white') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdHeaderTextImage'; blockType: 'fdHeaderTextImage';
@ -662,6 +714,10 @@ export interface FDContactFormBlock {
*/ */
authToken?: string | null; authToken?: string | null;
}; };
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdContactForm'; blockType: 'fdContactForm';
@ -881,6 +937,10 @@ export interface FDAlternateHeroBlock {
image?: (number | null) | Media; image?: (number | null) | Media;
imageCaption?: string | null; imageCaption?: string | null;
sectionBackground?: ('white' | 'navy' | 'gray') | null; sectionBackground?: ('white' | 'navy' | 'gray') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdAlternateHero'; blockType: 'fdAlternateHero';
@ -906,6 +966,10 @@ export interface FDStatisticsBlock {
| null; | null;
sectionBackground?: ('white' | 'navy' | 'gray') | null; sectionBackground?: ('white' | 'navy' | 'gray') | null;
numberColor?: ('gradient' | 'yellow' | 'mint' | 'navy' | 'white') | null; numberColor?: ('gradient' | 'yellow' | 'mint' | 'navy' | 'white') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdStatistics'; blockType: 'fdStatistics';
@ -996,6 +1060,10 @@ export interface FDServiceChooserBlock {
}[] }[]
| null; | null;
sectionBackground?: ('gray' | 'white' | 'navy') | null; sectionBackground?: ('gray' | 'white' | 'navy') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdServiceChooser'; blockType: 'fdServiceChooser';
@ -1050,6 +1118,10 @@ export interface FDDataTableBlock {
* Gör den första kolumnen fet användbart för namnkolumner. * Gör den första kolumnen fet användbart för namnkolumner.
*/ */
firstColumnBold?: boolean | null; firstColumnBold?: boolean | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdDataTable'; blockType: 'fdDataTable';
@ -1093,6 +1165,10 @@ export interface FDVpsCalculatorBlock {
id?: string | null; id?: string | null;
}[] }[]
| null; | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdVpsCalculator'; blockType: 'fdVpsCalculator';
@ -1179,6 +1255,10 @@ export interface FDServiceCalculatorBlock {
* T.ex. "{percent}% rabatt på alla resurser". Använd {percent} som variabel. * T.ex. "{percent}% rabatt på alla resurser". Använd {percent} som variabel.
*/ */
discountLabel?: string | null; discountLabel?: string | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdServiceCalculator'; blockType: 'fdServiceCalculator';
@ -1230,6 +1310,10 @@ export interface FDTextBlock {
textColor?: ('navy' | 'white' | 'yellow') | null; textColor?: ('navy' | 'white' | 'yellow') | null;
sectionBackground?: ('white' | 'navy' | 'gray' | 'yellow') | null; sectionBackground?: ('white' | 'navy' | 'gray' | 'yellow') | null;
maxWidth?: ('narrow' | 'medium' | 'wide' | 'full') | null; maxWidth?: ('narrow' | 'medium' | 'wide' | 'full') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdText'; blockType: 'fdText';
@ -1276,6 +1360,10 @@ export interface FDCodeEmbedBlock {
sectionBackground?: ('white' | 'navy' | 'gray' | 'yellow' | 'transparent') | null; sectionBackground?: ('white' | 'navy' | 'gray' | 'yellow' | 'transparent') | null;
textColor?: ('auto' | 'navy' | 'white') | null; textColor?: ('auto' | 'navy' | 'white') | null;
embedBackground?: ('none' | 'card' | 'navy-card') | null; embedBackground?: ('none' | 'card' | 'navy-card') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdCodeEmbed'; blockType: 'fdCodeEmbed';
@ -1313,6 +1401,10 @@ export interface FDVideoBlock {
maxWidth?: ('default' | 'narrow' | 'wide') | null; maxWidth?: ('default' | 'narrow' | 'wide') | null;
sectionBackground?: ('white' | 'navy' | 'gray' | 'yellow' | 'transparent') | null; sectionBackground?: ('white' | 'navy' | 'gray' | 'yellow' | 'transparent') | null;
textColor?: ('auto' | 'navy' | 'white') | null; textColor?: ('auto' | 'navy' | 'white') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdVideo'; blockType: 'fdVideo';
@ -1334,6 +1426,10 @@ export interface FDCtaBannerBlock {
sectionBackground?: ('yellow' | 'navy' | 'gray' | 'white') | null; sectionBackground?: ('yellow' | 'navy' | 'gray' | 'white') | null;
alignment?: ('center' | 'left') | null; alignment?: ('center' | 'left') | null;
size?: ('small' | 'medium' | 'large') | null; size?: ('small' | 'medium' | 'large') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdCtaBanner'; blockType: 'fdCtaBanner';
@ -1365,6 +1461,10 @@ export interface FDTestimonialBlock {
| null; | null;
layout?: ('grid' | 'featured') | null; layout?: ('grid' | 'featured') | null;
sectionBackground?: ('gray' | 'white' | 'navy') | null; sectionBackground?: ('gray' | 'white' | 'navy') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdTestimonial'; blockType: 'fdTestimonial';
@ -1396,6 +1496,10 @@ export interface FDTeamBlock {
columns?: ('2' | '3' | '4') | null; columns?: ('2' | '3' | '4') | null;
cardStyle?: ('navy' | 'white' | 'gray') | null; cardStyle?: ('navy' | 'white' | 'gray') | null;
sectionBackground?: ('white' | 'gray' | 'navy') | null; sectionBackground?: ('white' | 'gray' | 'navy') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null; id?: string | null;
blockName?: string | null; blockName?: string | null;
blockType: 'fdTeam'; blockType: 'fdTeam';
@ -1808,6 +1912,7 @@ export interface FDHeroBlockSelect<T extends boolean = true> {
overlayOpacity?: T; overlayOpacity?: T;
textColor?: T; textColor?: T;
theme?: T; theme?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1824,6 +1929,7 @@ export interface FDCtaSideImageBlockSelect<T extends boolean = true> {
imageOverlay?: T; imageOverlay?: T;
imagePosition?: T; imagePosition?: T;
theme?: T; theme?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1837,6 +1943,7 @@ export interface FDFeatureAnnouncementBlockSelect<T extends boolean = true> {
ctaText?: T; ctaText?: T;
ctaLink?: T; ctaLink?: T;
theme?: T; theme?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1856,6 +1963,7 @@ export interface FDServicesGridBlockSelect<T extends boolean = true> {
id?: T; id?: T;
}; };
columns?: T; columns?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1873,6 +1981,7 @@ export interface FDContactBlockSelect<T extends boolean = true> {
link?: T; link?: T;
id?: T; id?: T;
}; };
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1890,6 +1999,7 @@ export interface FDFaqBlockSelect<T extends boolean = true> {
id?: T; id?: T;
}; };
theme?: T; theme?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1918,6 +2028,7 @@ export interface FDCardGridBlockSelect<T extends boolean = true> {
id?: T; id?: T;
}; };
sectionBackground?: T; sectionBackground?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1947,6 +2058,7 @@ export interface FDPricingCardBlockSelect<T extends boolean = true> {
buttonColor?: T; buttonColor?: T;
sectionBackground?: T; sectionBackground?: T;
titleColor?: T; titleColor?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -1997,6 +2109,7 @@ export interface FDUspChecklistBlockSelect<T extends boolean = true> {
checkColor?: T; checkColor?: T;
sectionBackground?: T; sectionBackground?: T;
textColor?: T; textColor?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2013,6 +2126,7 @@ export interface FDWideCardBlockSelect<T extends boolean = true> {
cardBackground?: T; cardBackground?: T;
buttonColor?: T; buttonColor?: T;
sectionBackground?: T; sectionBackground?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2031,6 +2145,7 @@ export interface FDTechPropertiesBlockSelect<T extends boolean = true> {
sectionBackground?: T; sectionBackground?: T;
categoryColor?: T; categoryColor?: T;
valueColor?: T; valueColor?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2050,6 +2165,7 @@ export interface FDUspTableBlockSelect<T extends boolean = true> {
checkColor?: T; checkColor?: T;
sectionBackground?: T; sectionBackground?: T;
textColor?: T; textColor?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2066,6 +2182,7 @@ export interface FDHeaderTextImageBlockSelect<T extends boolean = true> {
textAlign?: T; textAlign?: T;
sectionBackground?: T; sectionBackground?: T;
textColor?: T; textColor?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2091,6 +2208,7 @@ export interface FDContactFormBlockSelect<T extends boolean = true> {
endpoint?: T; endpoint?: T;
authToken?: T; authToken?: T;
}; };
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2131,6 +2249,7 @@ export interface FDAlternateHeroBlockSelect<T extends boolean = true> {
image?: T; image?: T;
imageCaption?: T; imageCaption?: T;
sectionBackground?: T; sectionBackground?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2149,6 +2268,7 @@ export interface FDStatisticsBlockSelect<T extends boolean = true> {
}; };
sectionBackground?: T; sectionBackground?: T;
numberColor?: T; numberColor?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2215,6 +2335,7 @@ export interface FDServiceChooserBlockSelect<T extends boolean = true> {
id?: T; id?: T;
}; };
sectionBackground?: T; sectionBackground?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2244,6 +2365,7 @@ export interface FDDataTableBlockSelect<T extends boolean = true> {
stripeRows?: T; stripeRows?: T;
bordered?: T; bordered?: T;
firstColumnBold?: T; firstColumnBold?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2274,6 +2396,7 @@ export interface FDVpsCalculatorBlockSelect<T extends boolean = true> {
price?: T; price?: T;
id?: T; id?: T;
}; };
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2335,6 +2458,7 @@ export interface FDServiceCalculatorBlockSelect<T extends boolean = true> {
}; };
discountPercent?: T; discountPercent?: T;
discountLabel?: T; discountLabel?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2370,6 +2494,7 @@ export interface FDTextBlockSelect<T extends boolean = true> {
textColor?: T; textColor?: T;
sectionBackground?: T; sectionBackground?: T;
maxWidth?: T; maxWidth?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2391,6 +2516,7 @@ export interface FDCodeEmbedBlockSelect<T extends boolean = true> {
sectionBackground?: T; sectionBackground?: T;
textColor?: T; textColor?: T;
embedBackground?: T; embedBackground?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2412,6 +2538,7 @@ export interface FDVideoBlockSelect<T extends boolean = true> {
maxWidth?: T; maxWidth?: T;
sectionBackground?: T; sectionBackground?: T;
textColor?: T; textColor?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2429,6 +2556,7 @@ export interface FDCtaBannerBlockSelect<T extends boolean = true> {
sectionBackground?: T; sectionBackground?: T;
alignment?: T; alignment?: T;
size?: T; size?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2450,6 +2578,7 @@ export interface FDTestimonialBlockSelect<T extends boolean = true> {
}; };
layout?: T; layout?: T;
sectionBackground?: T; sectionBackground?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }
@ -2474,6 +2603,7 @@ export interface FDTeamBlockSelect<T extends boolean = true> {
columns?: T; columns?: T;
cardStyle?: T; cardStyle?: T;
sectionBackground?: T; sectionBackground?: T;
anchorId?: T;
id?: T; id?: T;
blockName?: T; blockName?: T;
} }

File diff suppressed because one or more lines are too long