NGU-Web/src/lib/sections.tsx

78 lines
2.7 KiB
TypeScript

import { useState } from "react";
/* ═══════════════════════════════════════════════════════════════
SECTION MANIFEST
A page is a list of sections, and a section is a view over data
the database already holds, narrowed by a filter. The map of
chapters and the vertical list of regions are the same rows seen
two ways; the three bands of retreats are one view seen three
times with a different filter each.
So a page declares what it wants and this turns it into what
PageShell takes. Every entry looks like:
{
id, title, blurb, accent, background, // the heading
Component, // the view
props: { ... }, // the filter
views: { // optional toggle
options: ["carousel", "grid"],
default: "carousel",
Toggle: EventCardsToggle,
},
}
Sections fetch their own data. The page never does, which is why
there's no loading or error state here — each view handles its
own, and one section failing doesn't blank the page.
Every Component receives `accent` so its empty and error notices
match the heading above them, and `view` when the entry declares
a toggle. Both are ignored harmlessly by a section that doesn't
want them.
═══════════════════════════════════════════════════════════════ */
export function useSectionManifest(manifest) {
// One entry per section that has a toggle, seeded from its
// declared default so the control is right before anything loads.
const [views, setViews] = useState(() =>
Object.fromEntries(
manifest
.filter(entry => entry.views)
.map(entry => [
entry.id,
entry.views.default ?? entry.views.options?.[0] ?? null,
]),
),
);
const setView = (id, value) => setViews(prev => ({ ...prev, [id]: value }));
return manifest.map(entry => {
const { Component, props, views: spec, ...heading } = entry;
const view = views[entry.id];
const Toggle = spec?.Toggle;
return {
...heading,
actions: Toggle ? (
<Toggle
view={view}
setView={value => setView(entry.id, value)}
accent={entry.accent}
options={spec.options}
/>
) : undefined,
content: (
<Component
{...(props ?? {})}
accent={entry.accent}
{...(spec ? { view } : {})}
/>
),
};
});
}