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: /en/entwickler-handbuch/frontend-spa/block-development