feat: manage layout fallback copy via payload

This commit is contained in:
syntaxbullet
2026-06-22 12:55:58 +02:00
parent 580a13f5e2
commit 2872f25dbf
4 changed files with 20 additions and 13 deletions

View File

@@ -49,6 +49,7 @@ export const Header: GlobalConfig = {
{ name: 'menuLabel', type: 'text', defaultValue: defaultHeaderData.menuLabel, required: true }, { name: 'menuLabel', type: 'text', defaultValue: defaultHeaderData.menuLabel, required: true },
{ name: 'openLabel', type: 'text', defaultValue: defaultHeaderData.openLabel, required: true }, { name: 'openLabel', type: 'text', defaultValue: defaultHeaderData.openLabel, required: true },
{ name: 'closeLabel', type: 'text', defaultValue: defaultHeaderData.closeLabel, required: true }, { name: 'closeLabel', type: 'text', defaultValue: defaultHeaderData.closeLabel, required: true },
{ name: 'dialogLabel', type: 'text', defaultValue: defaultHeaderData.dialogLabel, required: true },
{ name: 'overviewLabel', type: 'text', defaultValue: defaultHeaderData.overviewLabel, required: true }, { name: 'overviewLabel', type: 'text', defaultValue: defaultHeaderData.overviewLabel, required: true },
{ name: 'loadingBrand', type: 'text', defaultValue: defaultHeaderData.loadingBrand, required: true }, { name: 'loadingBrand', type: 'text', defaultValue: defaultHeaderData.loadingBrand, required: true },
{ name: 'loadingLabel', type: 'text', defaultValue: defaultHeaderData.loadingLabel, required: true }, { name: 'loadingLabel', type: 'text', defaultValue: defaultHeaderData.loadingLabel, required: true },

View File

@@ -5775,6 +5775,7 @@ export interface Header {
menuLabel: string; menuLabel: string;
openLabel: string; openLabel: string;
closeLabel: string; closeLabel: string;
dialogLabel: string;
overviewLabel: string; overviewLabel: string;
loadingBrand: string; loadingBrand: string;
loadingLabel: string; loadingLabel: string;
@@ -5898,6 +5899,7 @@ export interface HeaderSelect<T extends boolean = true> {
menuLabel?: T; menuLabel?: T;
openLabel?: T; openLabel?: T;
closeLabel?: T; closeLabel?: T;
dialogLabel?: T;
overviewLabel?: T; overviewLabel?: T;
loadingBrand?: T; loadingBrand?: T;
loadingLabel?: T; loadingLabel?: T;

View File

@@ -21,6 +21,7 @@ export type SpaHeaderData = {
menuLabel: string menuLabel: string
openLabel: string openLabel: string
closeLabel: string closeLabel: string
dialogLabel: string
overviewLabel: string overviewLabel: string
loadingBrand: string loadingBrand: string
loadingLabel: string loadingLabel: string
@@ -66,6 +67,7 @@ export const defaultHeaderData: SpaHeaderData = {
menuLabel: 'Menü', menuLabel: 'Menü',
openLabel: 'Navigation öffnen', openLabel: 'Navigation öffnen',
closeLabel: 'Menü schließen', closeLabel: 'Menü schließen',
dialogLabel: 'Navigation',
overviewLabel: 'Überblick', overviewLabel: 'Überblick',
loadingBrand: 'BMP', loadingBrand: 'BMP',
loadingLabel: 'Exzellenz wird geladen...', loadingLabel: 'Exzellenz wird geladen...',

View File

@@ -59,11 +59,13 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
const footerLogo = footer.logo?.url || '/bmp-logo.png'; const footerLogo = footer.logo?.url || '/bmp-logo.png';
const headerCtas = header.ctas; const headerCtas = header.ctas;
const footerCtas = footer.ctas; const footerCtas = footer.ctas;
const fallbackHeaderCtas = defaultHeaderData.ctas;
const fallbackFooterCtas = defaultFooterData.ctas;
const [open, setOpen] = useState(false); // desktop overlay const [open, setOpen] = useState(false); // desktop overlay
const [activeKey, setActive] = useState<string | null>(null); // desktop hover subnav const [activeKey, setActive] = useState<string | null>(null); // desktop hover subnav
const [mobileOpen, setMobileOpen] = useState(false); // mobile sidebar overlay const [mobileOpen, setMobileOpen] = useState(false); // mobile sidebar overlay
const [expandKey, setExpand] = useState<string | null>(null); // mobile accordion const [expandKey, setExpand] = useState<string | null>(null); // mobile accordion
const [lang, setLang] = useState(header.languages[0] || 'DE'); const [lang, setLang] = useState(header.languages[0] || defaultHeaderData.languages[0]);
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const [showLoader, setShowLoader] = useState(() => { const [showLoader, setShowLoader] = useState(() => {
if (typeof window === 'undefined') return false; if (typeof window === 'undefined') return false;
@@ -236,7 +238,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
</header> </header>
{/* ── DESKTOP OVERLAY ──────────────────────────── */} {/* ── DESKTOP OVERLAY ──────────────────────────── */}
<div role="dialog" aria-modal="true" aria-label="Navigation" <div role="dialog" aria-modal="true" aria-label={header.dialogLabel}
style={{ style={{
position:'fixed', top:60, left:0, right:0, bottom:0, position:'fixed', top:60, left:0, right:0, bottom:0,
background:ANTHRA, zIndex:300, background:ANTHRA, zIndex:300,
@@ -376,12 +378,12 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
style={{ display:'inline-block', background:'#EFBF04', color:'#101828', fontSize: 15, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'11px 24px', textDecoration:'none', transition:'background .2s, box-shadow .2s', borderRadius:0, fontFamily:FF_DISPLAY }} style={{ display:'inline-block', background:'#EFBF04', color:'#101828', fontSize: 15, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'11px 24px', textDecoration:'none', transition:'background .2s, box-shadow .2s', borderRadius:0, fontFamily:FF_DISPLAY }}
onMouseEnter={e=>{ e.currentTarget.style.background='#FFD130'; e.currentTarget.style.boxShadow='0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }} onMouseEnter={e=>{ e.currentTarget.style.background='#FFD130'; e.currentTarget.style.boxShadow='0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
onMouseLeave={e=>{ e.currentTarget.style.background='#EFBF04'; e.currentTarget.style.boxShadow='none'; }} onMouseLeave={e=>{ e.currentTarget.style.background='#EFBF04'; e.currentTarget.style.boxShadow='none'; }}
>{headerCtas[0]?.label || 'Jetzt bewerben'}</Link> >{headerCtas[0]?.label || fallbackHeaderCtas[0].label}</Link>
<Link to={headerCtas[1]?.path || '/mitglied-werden'} onClick={() => setOpen(false)} <Link to={headerCtas[1]?.path || '/mitglied-werden'} onClick={() => setOpen(false)}
style={{ display:'inline-block', background:'transparent', color:MUTED, fontSize: 15, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'10px 24px', textDecoration:'none', transition:'border-color .2s, color .2s', borderRadius:0, fontFamily:FF_DISPLAY, border:'1.5px solid rgba(239,191,4,0.35)' }} style={{ display:'inline-block', background:'transparent', color:MUTED, fontSize: 15, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'10px 24px', textDecoration:'none', transition:'border-color .2s, color .2s', borderRadius:0, fontFamily:FF_DISPLAY, border:'1.5px solid rgba(239,191,4,0.35)' }}
onMouseEnter={e=>{ e.currentTarget.style.borderColor='#EFBF04'; e.currentTarget.style.color='#EFBF04'; }} onMouseEnter={e=>{ e.currentTarget.style.borderColor='#EFBF04'; e.currentTarget.style.color='#EFBF04'; }}
onMouseLeave={e=>{ e.currentTarget.style.borderColor='rgba(239,191,4,0.35)'; e.currentTarget.style.color=MUTED; }} onMouseLeave={e=>{ e.currentTarget.style.borderColor='rgba(239,191,4,0.35)'; e.currentTarget.style.color=MUTED; }}
>{headerCtas[1]?.label || 'Mitglied werden'}</Link> >{headerCtas[1]?.label || fallbackHeaderCtas[1].label}</Link>
<div style={{ height:'1px', background:'rgba(239,191,4,0.12)', margin:'4px 0' }} /> <div style={{ height:'1px', background:'rgba(239,191,4,0.12)', margin:'4px 0' }} />
@@ -409,7 +411,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
e.currentTarget.style.background='rgba(255,255,255,0.04)'; e.currentTarget.style.background='rgba(255,255,255,0.04)';
}} }}
> >
<Upload size={12} strokeWidth={2} /> {headerCtas[2]?.label || 'Formular hochladen'} <Upload size={12} strokeWidth={2} /> {headerCtas[2]?.label || fallbackHeaderCtas[2].label}
</Link> </Link>
</div> </div>
@@ -457,7 +459,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
</header> </header>
{/* ── FULLSCREEN OVERLAY ───────────────────────── */} {/* ── FULLSCREEN OVERLAY ───────────────────────── */}
<div role="dialog" aria-modal="true" aria-label="Navigation" <div role="dialog" aria-modal="true" aria-label={header.dialogLabel}
style={{ style={{
position: 'fixed', inset: 0, position: 'fixed', inset: 0,
background: ANTHRA, background: ANTHRA,
@@ -551,13 +553,13 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
<div style={{ padding:'24px 24px 8px', display:'flex', flexDirection:'column', gap:12 }}> <div style={{ padding:'24px 24px 8px', display:'flex', flexDirection:'column', gap:12 }}>
<Link to={headerCtas[0]?.path || '/teilnahme#bewerben'} onClick={() => setMobileOpen(false)} <Link to={headerCtas[0]?.path || '/teilnahme#bewerben'} onClick={() => setMobileOpen(false)}
style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:52, background:GOLD, color:'#101828', fontSize:14, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', textDecoration:'none', fontFamily:FF_DISPLAY }} style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:52, background:GOLD, color:'#101828', fontSize:14, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', textDecoration:'none', fontFamily:FF_DISPLAY }}
>{headerCtas[0]?.label || 'Jetzt bewerben'}</Link> >{headerCtas[0]?.label || fallbackHeaderCtas[0].label}</Link>
<Link to={headerCtas[1]?.path || '/mitglied-werden'} onClick={() => setMobileOpen(false)} <Link to={headerCtas[1]?.path || '/mitglied-werden'} onClick={() => setMobileOpen(false)}
style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:50, background:'transparent', color:'#fff', fontSize:14, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', textDecoration:'none', fontFamily:FF_DISPLAY, border:`1.5px solid ${LINE}` }} style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:50, background:'transparent', color:'#fff', fontSize:14, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', textDecoration:'none', fontFamily:FF_DISPLAY, border:`1.5px solid ${LINE}` }}
>{headerCtas[1]?.label || 'Mitglied werden'}</Link> >{headerCtas[1]?.label || fallbackHeaderCtas[1].label}</Link>
<Link to={headerCtas[2]?.path || '/formular-hochladen'} onClick={() => setMobileOpen(false)} <Link to={headerCtas[2]?.path || '/formular-hochladen'} onClick={() => setMobileOpen(false)}
style={{ display:'flex', alignItems:'center', justifyContent:'center', gap:8, minHeight:46, background:'none', color:'rgba(255,255,255,0.5)', fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.14em', textDecoration:'none', fontFamily:FF_DISPLAY, border:'1px dashed rgba(255,255,255,0.2)' }} style={{ display:'flex', alignItems:'center', justifyContent:'center', gap:8, minHeight:46, background:'none', color:'rgba(255,255,255,0.5)', fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.14em', textDecoration:'none', fontFamily:FF_DISPLAY, border:'1px dashed rgba(255,255,255,0.2)' }}
><Upload size={13} strokeWidth={2} /> {headerCtas[2]?.label || 'Formular hochladen'}</Link> ><Upload size={13} strokeWidth={2} /> {headerCtas[2]?.label || fallbackHeaderCtas[2].label}</Link>
</div> </div>
{/* Footer zone: lang + social + secondary */} {/* Footer zone: lang + social + secondary */}
@@ -612,7 +614,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.color = '#101828'; }} onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.color = '#101828'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.color = '#fff'; }} onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.color = '#fff'; }}
> >
{headerCtas[0]?.label || 'Jetzt bewerben'} <ArrowRight size={13} /> {headerCtas[0]?.label || fallbackHeaderCtas[0].label} <ArrowRight size={13} />
</Link> </Link>
)} )}
@@ -760,7 +762,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
e.currentTarget.style.background = GOLD; e.currentTarget.style.background = GOLD;
e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.boxShadow = 'none';
}} }}
>{footerCtas[0]?.label || 'Kostenlos bewerben'}</Link> >{footerCtas[0]?.label || fallbackFooterCtas[0].label}</Link>
<Link <Link
to={footerCtas[1]?.path || '/mitglied-werden'} to={footerCtas[1]?.path || '/mitglied-werden'}
@@ -786,7 +788,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
e.currentTarget.style.borderColor = 'rgba(239,191,4,0.35)'; e.currentTarget.style.borderColor = 'rgba(239,191,4,0.35)';
e.currentTarget.style.color = MUTED; e.currentTarget.style.color = MUTED;
}} }}
>{footerCtas[1]?.label || 'Mitglied werden'}</Link> >{footerCtas[1]?.label || fallbackFooterCtas[1].label}</Link>
<Link <Link
to={footerCtas[2]?.path || '/netzwerk#sponsoring'} to={footerCtas[2]?.path || '/netzwerk#sponsoring'}
@@ -803,7 +805,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
}} }}
onMouseEnter={e => { e.currentTarget.style.color = '#fff'; }} onMouseEnter={e => { e.currentTarget.style.color = '#fff'; }}
onMouseLeave={e => { e.currentTarget.style.color = 'rgba(255,255,255,0.4)'; }} onMouseLeave={e => { e.currentTarget.style.color = 'rgba(255,255,255,0.4)'; }}
>{footerCtas[2]?.label || 'Sponsoring-Anfrage →'}</Link> >{footerCtas[2]?.label || fallbackFooterCtas[2].label}</Link>
</div> </div>
</div> </div>