Zum Hauptinhalt springen

Block-Development

Archiviert

Die frontend-spa-App existiert nicht mehr. Für neue UI-Arbeit: Center-Website (apps/center-website), WordPress-Themes oder Plugin-Blocks — siehe WordPress Themes.

Entwickler-Guide (Legacy): Block-Komponenten für das alte SPA-System.

Block-System-Übersicht

Das Block-System ermöglicht modulare UI-Komponenten:

  • Theme-spezifisch (HBB, SMG, CM-IMMO)
  • Dynamisch geladen (Lazy Loading)
  • Konfigurierbar (Config + Content)
  • Wiederverwendbar (Multiple Centers)

Block-Anatomie

Block-Interface:

interface BlockProps {
block: PageBlock; // Block-Konfiguration aus Dashboard
center: CenterConfig; // Center-Daten (Name, Theme, etc.)
content?: ContentData; // Content-Daten (Shops, Events, News)
}

interface PageBlock {
id: string; // Eindeutige Block-ID
blockType: string; // Block-Typ (z.B. 'hbb-hero-section')
variant?: string; // Block-Variante (z.B. 'image-background')
position: number; // Position auf der Seite
isVisible: boolean; // Sichtbarkeit
config: Record<string, any>; // Konfiguration (Layout, Styling)
content: Record<string, any>; // Inhalt (Texte, Bilder, Links)
}

Neuen Block erstellen

Schritt 1: Block-Komponente erstellen

// src/components/blocks/hbb/NewBlock.tsx
import type { BlockProps } from '../../../lib/types/blocks';

export function NewBlock({ block, center, content }: BlockProps) {
// Konfiguration extrahieren
const config = block.config || {};
const blockContent = block.content || {};

// Standardwerte definieren
const {
layout = 'default',
showTitle = true,
backgroundColor = 'transparent'
} = config;

const {
title = 'Standard Titel',
subtitle = '',
items = []
} = blockContent;

// CSS-Klassen generieren
const blockClasses = [
'hbb-new-block',
`layout-${layout}`,
`bg-${backgroundColor}`,
showTitle ? 'show-title' : 'hide-title'
].join(' ');

return (
<section className={blockClasses}>
<div className="container">
{showTitle && title && (
<div className="block-header">
<h2 className="block-title">{title}</h2>
{subtitle && <p className="block-subtitle">{subtitle}</p>}
</div>
)}

<div className="block-content">
{items.map((item: any, index: number) => (
<div key={index} className="block-item">
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
))}
</div>
</div>
</section>
);
}

Schritt 2: Block registrieren

// src/lib/blocks/blockSetup.ts
import { NewBlock } from '../../components/blocks/hbb/NewBlock';

export function setupBlocks() {
blockRegistry.register({
id: 'hbb-new-block',
name: 'HBB New Block',
category: 'content',
themes: ['hbb-standard', 'hbb-modern'],
component: NewBlock,
});
}

Schritt 3: Styles hinzufügen

/* src/styles/blocks.css */

/* HBB New Block */
.hbb-new-block {
padding: 4rem 0;
}

.hbb-new-block.bg-primary {
background: var(--theme-primary, #892831);
color: white;
}

.hbb-new-block .block-header {
text-align: center;
margin-bottom: 3rem;
}

.hbb-new-block .block-title {
font-size: 2.5rem;
color: var(--theme-primary, #892831);
margin-bottom: 1rem;
}

.hbb-new-block .block-content {
display: grid;
gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

.hbb-new-block .block-item {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

Theme-spezifische Blöcke

HBB-Block erstellen:

export function HbbSpecificBlock({ block, center }: BlockProps) {
return (
<section className="hbb-specific-block">
<div className="hbb-branding">
<img src="/hbb-logo.svg" alt="HBB" />
</div>
</section>
);
}

Universal-Block erstellen:

export function UniversalBlock({ block, center }: BlockProps) {
return (
<section className="universal-block">
<div className="universal-content">
{/* Funktioniert mit allen Themes */}
</div>
</section>
);
}

Block-Konfiguration

Config vs. Content:

// Config = Layout, Styling, Verhalten
const config = {
layout: 'grid', // Wie wird der Block dargestellt?
itemsPerRow: 3, // Layout-Parameter
showFilters: true, // Feature-Flags
backgroundColor: 'white', // Styling-Optionen
animation: 'fadeIn' // Verhalten
};

// Content = Daten, Texte, Bilder
const content = {
title: 'Mein Block Titel', // Benutzer-Eingaben
subtitle: 'Untertitel', // Texte
items: [ // Daten-Arrays
{ title: 'Item 1', description: '...' },
{ title: 'Item 2', description: '...' }
],
backgroundImage: '/hero.jpg', // Assets
ctaButton: { // Komplexe Objekte
text: 'Mehr erfahren',
link: '/mehr-info'
}
};

Content-Integration

Shops verwenden:

export function ShopBlock({ block, center, content }: BlockProps) {
const shops = content?.shops || [];
const config = block.config || {};

const {
maxItems = 6,
showCategories = true,
filterByCategory = null
} = config;

// Shops filtern und begrenzen
const filteredShops = shops
.filter(shop => !filterByCategory || shop.category === filterByCategory)
.slice(0, maxItems);

return (
<section className="shop-block">
<div className="container">
<div className="shop-grid">
{filteredShops.map(shop => (
<div key={shop.id} className="shop-card">
<img src={shop.logoUrl} alt={shop.name} />
<h3>{shop.name}</h3>
{showCategories && <p>{shop.category}</p>}
</div>
))}
</div>
</div>
</section>
);
}

Block-Testing

Unit-Tests:

import { render, screen } from '@testing-library/react';
import { NewBlock } from '../NewBlock';

const mockBlock = {
id: 'test-block',
blockType: 'hbb-new-block',
config: { showTitle: true },
content: { title: 'Test Titel' }
};

test('renders block with title', () => {
render(<NewBlock block={mockBlock} center={mockCenter} />);

expect(screen.getByText('Test Titel')).toBeInTheDocument();
});

Best Practices

1. Kleine, fokussierte Komponenten

2. DRY-Prinzip befolgen

3. Accessibility beachten

4. Performance optimieren

Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /entwickler-handbuch/frontend-spa/block-development