From 3878a2ed2618bbb6a7fa8001e5f7e86564dcb131 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Sun, 12 Jul 2026 09:37:05 +0200 Subject: [PATCH] feat(section-grid): InfoCard + SectionGrid als CMS-Block Neuer section_grid-Block (SectionHeader + Card-Grid aus info_card-Refs) + info_card-Collection. SectionGrid/InfoCard-Komponenten, BlockRenderer-Case, content_layout erlaubt section_grid in Rows. SectionHeader.title jetzt optional (Kicker-only-Header). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/block-types.ts | 31 ++++++++ src/lib/components/BlockRenderer.svelte | 4 ++ src/lib/components/InfoCard.stories.ts | 64 +++++++++++++++++ src/lib/components/InfoCard.svelte | 61 ++++++++++++++++ src/lib/components/SectionGrid.stories.svelte | 70 +++++++++++++++++++ src/lib/components/SectionGrid.svelte | 65 +++++++++++++++++ src/lib/components/SectionHeader.svelte | 14 ++-- .../components/blocks/SectionGridBlock.svelte | 49 +++++++++++++ 8 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 src/lib/components/InfoCard.stories.ts create mode 100644 src/lib/components/InfoCard.svelte create mode 100644 src/lib/components/SectionGrid.stories.svelte create mode 100644 src/lib/components/SectionGrid.svelte create mode 100644 src/lib/components/blocks/SectionGridBlock.svelte diff --git a/src/lib/block-types.ts b/src/lib/block-types.ts index fe83350..e064fde 100644 --- a/src/lib/block-types.ts +++ b/src/lib/block-types.ts @@ -576,3 +576,34 @@ export interface StellingnahmeGeneratorBlockData { aiRules?: string; layout?: BlockLayout; } + +/** Einzelne Kachel (info_card): Icon/Bild + Titel + Beschreibung + optionaler Link. */ +export interface InfoCardData { + _type?: "info_card"; + _slug?: string; + /** Iconify-String ("mdi:email") oder Bild-URL — Komponente erkennt automatisch. */ + icon?: string; + title?: string; + description?: string; + linkLabel?: string; + linkHref?: string; + external?: boolean; +} + +/** Sektions-Grid (_type: "section_grid"): SectionHeader + info_card-Kacheln. */ +export interface SectionGridBlockData { + _type?: "section_grid"; + _slug?: string; + headline?: string; + subtitle?: string; + /** SectionHeader: Label über dem Titel (uppercase). */ + kicker?: string; + /** SectionHeader: Index vor dem Kicker, z.B. "01". */ + number?: string; + /** SectionHeader: CTA-Link (aufgelöst: url, linkName). */ + action?: string | { url?: string; linkName?: string; newTab?: boolean }; + columns?: "2" | "3" | "4"; + /** Slugs oder aufgelöste info_card-Einträge. */ + cards?: (string | InfoCardData)[]; + layout?: BlockLayout; +} diff --git a/src/lib/components/BlockRenderer.svelte b/src/lib/components/BlockRenderer.svelte index d675e17..31fcf15 100644 --- a/src/lib/components/BlockRenderer.svelte +++ b/src/lib/components/BlockRenderer.svelte @@ -23,6 +23,7 @@ import WindkarteBlock from "./blocks/WindkarteBlock.svelte"; import AdressbuchBlock from "./blocks/AdressbuchBlock.svelte"; import OrganisationsMapBlock from "./blocks/OrganisationsMapBlock.svelte"; + import SectionGridBlock from "./blocks/SectionGridBlock.svelte"; import type { Translations } from "$lib/translations"; import type { ResolvedBlock, @@ -51,6 +52,7 @@ WindMapBlockData, StellingnahmeGeneratorBlockData, OrganisationsMapBlockData, + SectionGridBlockData, } from "$lib/block-types"; let { @@ -117,6 +119,8 @@ {:else if type === "organisations_map"} +{:else if type === "section_grid"} + {:else if type === "stellungnahme_generator"} {#await import("./blocks/StellingnahmeGeneratorBlock.svelte") then m} {@const Comp = m.default} diff --git a/src/lib/components/InfoCard.stories.ts b/src/lib/components/InfoCard.stories.ts new file mode 100644 index 0000000..c196cd7 --- /dev/null +++ b/src/lib/components/InfoCard.stories.ts @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from '@storybook/sveltekit'; +import InfoCard from './InfoCard.svelte'; + +const CMS_ASSET = + 'https://cms.pm86.de/api/assets/pages/img_2814-ar4x2.webp?_environment=windwiderstand'; + +const meta = { + title: 'Molecules/InfoCard', + component: InfoCard, + tags: ['autodocs'], + parameters: { layout: 'centered' }, + argTypes: { + icon: { control: 'text' }, + title: { control: 'text' }, + description: { control: 'text' }, + linkLabel: { control: 'text' }, + linkHref: { control: 'text' }, + external: { control: 'boolean' }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const MitIcon: Story = { + args: { + icon: 'mdi:email-outline', + title: 'E-Mail', + description: 'Für alle Fragen und Anregungen', + linkLabel: 'mail@windwiderstand.de', + linkHref: 'mailto:mail@windwiderstand.de', + }, +}; + +export const ExternerLink: Story = { + args: { + icon: 'mdi:whatsapp', + title: 'WhatsApp-Community', + description: 'Aktuelles und Austausch vor Ort', + linkLabel: 'Beitreten', + linkHref: 'https://chat.whatsapp.com/xyz', + external: true, + }, +}; + +// icon = Bild-URL statt Iconify-String → wird als Bild gerendert. +export const MitBild: Story = { + args: { + icon: CMS_ASSET, + title: 'Unsere Cloud', + description: 'Dokumente und Materialien zum Mitnehmen', + linkLabel: 'cloud.windwiderstand.de', + linkHref: 'https://cloud.windwiderstand.de', + external: true, + }, +}; + +export const OhneLink: Story = { + args: { + icon: 'mdi:youtube', + title: 'YouTube-Kanal', + description: 'Aufzeichnungen und Visualisierungen', + }, +}; diff --git a/src/lib/components/InfoCard.svelte b/src/lib/components/InfoCard.svelte new file mode 100644 index 0000000..a7ce2dd --- /dev/null +++ b/src/lib/components/InfoCard.svelte @@ -0,0 +1,61 @@ + + +
+ {#if icon} +
+ {#if isImage} + + {:else} +
+ {/if} + +

{title}

+ {#if description} +

{description}

+ {/if} + + {#if hasLink} + + {linkLabel} + + + {/if} +
diff --git a/src/lib/components/SectionGrid.stories.svelte b/src/lib/components/SectionGrid.stories.svelte new file mode 100644 index 0000000..04989e4 --- /dev/null +++ b/src/lib/components/SectionGrid.stories.svelte @@ -0,0 +1,70 @@ + + + + +
+ + + + + + +
+
+ + + +
+ + + + + +
+
diff --git a/src/lib/components/SectionGrid.svelte b/src/lib/components/SectionGrid.svelte new file mode 100644 index 0000000..7c53808 --- /dev/null +++ b/src/lib/components/SectionGrid.svelte @@ -0,0 +1,65 @@ + + +
+ {#if hasHeader} +
+ +
+ {/if} + +
+ {@render children()} +
+
diff --git a/src/lib/components/SectionHeader.svelte b/src/lib/components/SectionHeader.svelte index 0637c65..0000c9a 100644 --- a/src/lib/components/SectionHeader.svelte +++ b/src/lib/components/SectionHeader.svelte @@ -13,7 +13,7 @@ let { number = undefined, kicker = undefined, - title, + title = undefined, subtitle = undefined, actionLabel = undefined, actionHref = undefined, @@ -22,7 +22,7 @@ }: { number?: string; // z. B. "03" — tabular-nums, wald-500 kicker?: string; // Label, uppercase — optional (CMS-Blocks liefern oft nur title) - title: string; + title?: string; // optional — z.B. Kicker-only-Header (nur "01 · DIREKTER DRAHT") subtitle?: string; actionLabel?: string; actionHref?: string; @@ -51,10 +51,12 @@ {/if} - - - {#each title.split('\n') as line, i}{#if i > 0}
{/if}{line}{/each} -
+ + {#if title} + + {#each title.split('\n') as line, i}{#if i > 0}
{/if}{line}{/each} +
+ {/if} {#if subtitle}

{subtitle}

diff --git a/src/lib/components/blocks/SectionGridBlock.svelte b/src/lib/components/blocks/SectionGridBlock.svelte new file mode 100644 index 0000000..d2f8afc --- /dev/null +++ b/src/lib/components/blocks/SectionGridBlock.svelte @@ -0,0 +1,49 @@ + + +
+ + {#each cards as card} + + {/each} + +