diff --git a/package.json b/package.json index df962ff..d53613d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@c2s/payload-contracts": "github:complexcaresolutions/payload-contracts", "clsx": "^2.1.1", "framer-motion": "^12.34.0", + "lucide-react": "^0.564.0", "next": "16.0.10", "react": "19.2.1", "react-dom": "19.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc8ee7c..64d344e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: framer-motion: specifier: ^12.34.0 version: 12.34.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + lucide-react: + specifier: ^0.564.0 + version: 0.564.0(react@19.2.1) next: specifier: 16.0.10 version: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -1492,6 +1495,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.564.0: + resolution: {integrity: sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3498,6 +3506,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.564.0(react@19.2.1): + dependencies: + react: 19.2.1 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 diff --git a/src/components/blocks/CardGridBlock.tsx b/src/components/blocks/CardGridBlock.tsx index c5876e7..a6ba31d 100644 --- a/src/components/blocks/CardGridBlock.tsx +++ b/src/components/blocks/CardGridBlock.tsx @@ -5,6 +5,7 @@ import Link from 'next/link' import Image from 'next/image' import { Container } from '../ui/Container' import { SectionHeader } from '../ui/SectionHeader' +import { DynamicIcon } from '../ui/DynamicIcon' import { cn } from '@/lib/utils' interface CardGridBlockProps { @@ -40,13 +41,17 @@ export function CardGridBlock({ block }: CardGridBlockProps) { {cards.map((card, index) => { const cardTitle = (card.title as string) || '' const cardDescription = (card.description as string) || (card.text as string) || '' + const mediaType = (card.mediaType as string) || 'none' const cardIcon = card.icon as string | undefined + const iconPosition = (card.iconPosition as string) || 'top' const cardImage = card.image as Record | undefined const cardImageUrl = cardImage?.url as string | undefined const cardLink = card.link as Record | undefined const cardLinkLabel = (cardLink?.label as string) || 'Mehr erfahren' const cardLinkHref = (cardLink?.href as string) || (cardLink?.url as string) || '' + const isIconLeft = mediaType === 'icon' && iconPosition === 'left' + return ( - {/* Icon */} - {cardIcon && ( -
- {cardIcon} + {/* Icon left */} + {mediaType === 'icon' && cardIcon && isIconLeft && ( +
+
)} - {/* Image */} - {cardImageUrl && ( -
- {cardTitle} -
- )} +
+ {/* Icon top */} + {mediaType === 'icon' && cardIcon && !isIconLeft && ( +
+ +
+ )} - {/* Title */} -

- {cardTitle} -

+ {/* Image */} + {mediaType === 'image' && cardImageUrl && ( +
+ {cardTitle} +
+ )} - {/* Description */} -

- {cardDescription} -

+ {/* Title */} +

+ {cardTitle} +

- {/* Link */} - {cardLinkHref && ( - - {cardLinkLabel} → - - )} + {/* Description */} +

+ {cardDescription} +

+ + {/* Link */} + {cardLinkHref && ( + + {cardLinkLabel} → + + )} +
) })} diff --git a/src/components/ui/DynamicIcon.tsx b/src/components/ui/DynamicIcon.tsx new file mode 100644 index 0000000..2650bbd --- /dev/null +++ b/src/components/ui/DynamicIcon.tsx @@ -0,0 +1,28 @@ +import { icons, type LucideIcon } from 'lucide-react' + +interface DynamicIconProps { + name: string + size?: number + className?: string + strokeWidth?: number +} + +/** + * Renders a Lucide icon by name string. + * Names can be kebab-case ("shield-check") or PascalCase ("ShieldCheck"). + */ +export function DynamicIcon({ name, size = 24, className, strokeWidth = 2 }: DynamicIconProps) { + // Convert kebab-case to PascalCase: "shield-check" → "ShieldCheck" + const pascalName = name + .split('-') + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join('') + + const IconComponent = icons[pascalName as keyof typeof icons] as LucideIcon | undefined + + if (!IconComponent) { + return null + } + + return +}