206 lines
5.6 KiB
TypeScript
206 lines
5.6 KiB
TypeScript
/**
|
|
* Layout Renderer
|
|
* Phase 5: Sites Renderer & Publishing
|
|
*
|
|
* Renders different layout types for sites.
|
|
*/
|
|
import React from 'react';
|
|
import type { SiteDefinition } from '../types';
|
|
import { renderTemplate } from './templateEngine';
|
|
|
|
export type LayoutType =
|
|
| 'default'
|
|
| 'centered'
|
|
| 'sidebar'
|
|
| 'grid'
|
|
| 'magazine'
|
|
| 'minimal'
|
|
| 'fullwidth';
|
|
|
|
/**
|
|
* Render site layout based on site definition.
|
|
*/
|
|
export function renderLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
const layoutType = siteDefinition.layout as LayoutType;
|
|
|
|
switch (layoutType) {
|
|
case 'centered':
|
|
return renderCenteredLayout(siteDefinition);
|
|
case 'sidebar':
|
|
return renderSidebarLayout(siteDefinition);
|
|
case 'grid':
|
|
return renderGridLayout(siteDefinition);
|
|
case 'magazine':
|
|
return renderMagazineLayout(siteDefinition);
|
|
case 'minimal':
|
|
return renderMinimalLayout(siteDefinition);
|
|
case 'fullwidth':
|
|
return renderFullwidthLayout(siteDefinition);
|
|
case 'default':
|
|
default:
|
|
return renderDefaultLayout(siteDefinition);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default layout: Standard header, content, footer.
|
|
*/
|
|
function renderDefaultLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
return (
|
|
<div className="layout-default">
|
|
<header className="site-header">
|
|
<nav className="site-navigation">
|
|
{renderNavigation(siteDefinition.navigation)}
|
|
</nav>
|
|
</header>
|
|
<main className="site-main">
|
|
{renderPages(siteDefinition.pages)}
|
|
</main>
|
|
<footer className="site-footer">
|
|
<p>© {new Date().getFullYear()} {siteDefinition.name}</p>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Centered layout: Content centered with max-width.
|
|
*/
|
|
function renderCenteredLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
return (
|
|
<div className="layout-centered">
|
|
<header className="site-header">
|
|
<nav className="site-navigation">
|
|
{renderNavigation(siteDefinition.navigation)}
|
|
</nav>
|
|
</header>
|
|
<main className="site-main" style={{ maxWidth: '1200px', margin: '0 auto', padding: '2rem' }}>
|
|
{renderPages(siteDefinition.pages)}
|
|
</main>
|
|
<footer className="site-footer">
|
|
<p>© {new Date().getFullYear()} {siteDefinition.name}</p>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sidebar layout: Sidebar navigation with main content.
|
|
*/
|
|
function renderSidebarLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
return (
|
|
<div className="layout-sidebar">
|
|
<aside className="site-sidebar">
|
|
<nav className="site-navigation">
|
|
{renderNavigation(siteDefinition.navigation)}
|
|
</nav>
|
|
</aside>
|
|
<main className="site-main">
|
|
{renderPages(siteDefinition.pages)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Grid layout: Grid-based page layout.
|
|
*/
|
|
function renderGridLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
return (
|
|
<div className="layout-grid">
|
|
<header className="site-header">
|
|
<nav className="site-navigation">
|
|
{renderNavigation(siteDefinition.navigation)}
|
|
</nav>
|
|
</header>
|
|
<main className="site-main" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '2rem', padding: '2rem' }}>
|
|
{renderPages(siteDefinition.pages)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Magazine layout: Multi-column magazine style.
|
|
*/
|
|
function renderMagazineLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
return (
|
|
<div className="layout-magazine">
|
|
<header className="site-header">
|
|
<h1>{siteDefinition.name}</h1>
|
|
<nav className="site-navigation">
|
|
{renderNavigation(siteDefinition.navigation)}
|
|
</nav>
|
|
</header>
|
|
<main className="site-main" style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '2rem', padding: '2rem' }}>
|
|
{renderPages(siteDefinition.pages)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Minimal layout: Clean, minimal design.
|
|
*/
|
|
function renderMinimalLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
return (
|
|
<div className="layout-minimal">
|
|
<main className="site-main" style={{ padding: '4rem 2rem', maxWidth: '800px', margin: '0 auto' }}>
|
|
{renderPages(siteDefinition.pages)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Fullwidth layout: Full-width content.
|
|
*/
|
|
function renderFullwidthLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
|
return (
|
|
<div className="layout-fullwidth">
|
|
<header className="site-header">
|
|
<nav className="site-navigation">
|
|
{renderNavigation(siteDefinition.navigation)}
|
|
</nav>
|
|
</header>
|
|
<main className="site-main" style={{ width: '100%' }}>
|
|
{renderPages(siteDefinition.pages)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render navigation items.
|
|
*/
|
|
function renderNavigation(navigation: SiteDefinition['navigation']): React.ReactElement {
|
|
return (
|
|
<ul style={{ listStyle: 'none', display: 'flex', gap: '1rem', padding: 0 }}>
|
|
{navigation.map((item) => (
|
|
<li key={item.slug}>
|
|
<a href={`/${item.slug}`}>{item.label}</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render pages.
|
|
*/
|
|
function renderPages(pages: SiteDefinition['pages']): React.ReactElement[] {
|
|
return pages
|
|
.filter((page) => page.status === 'ready')
|
|
.map((page) => (
|
|
<div key={page.id} className="page" data-page-slug={page.slug}>
|
|
<h2>{page.title}</h2>
|
|
{page.blocks.map((block, index) => (
|
|
<div key={index} className="block" data-block-type={block.type}>
|
|
{renderTemplate(block)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
));
|
|
}
|
|
|