Layout's three bands are nav rows

Top Menu, Content, Footer — the layout IS a page, and those are the only three things on it. They were
reachable by scrolling and by knowing which tab held what; as rows they take the shape the nav already
teaches with the pages under Pages, and the open one is `band` in the query string.

Fixed rather than fetched, unlike pages. A band cannot be added or removed from the admin — only the
code that draws it can — so reading them from the database would be a query that can only ever return
the same three rows in the same order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 19:19:53 +01:00
co-authored by Claude Opus 5
parent 10e5a625ff
commit 8c9e4ecb85
2 changed files with 63 additions and 1 deletions
+42 -1
View File
@@ -1,6 +1,6 @@
import { Link, useSearchParams } from 'react-router';
import { Lock } from 'lucide-react';
import { DEFAULT_SECTION, SECTIONS, isSectionKey, sectionOpen, type SectionKey } from './sections';
import { DEFAULT_SECTION, LAYOUT_BANDS, SECTIONS, isSectionKey, sectionOpen, type SectionKey } from './sections';
import { useReady } from './useReady';
import { pageParam, usePages } from './usePages';
@@ -145,6 +145,47 @@ export const NavPanel = () => {
Drawn whether or not Pages is the open section. The list of pages IS the site, and hiding it
until you click makes the nav a menu of menus.
*/
/*
Layout's three bands, as rows under Layout.
The layout IS a page — a header, the slot a page goes in, and a footer — and those three are the
only things on this screen. They were reachable by scrolling and by knowing which tab held what;
as rows they are the same shape as the pages under Pages, which is the shape the nav already
teaches.
Fixed rather than fetched: unlike pages, these three are structural. A band cannot be added or
removed from the admin, only the code that draws it can, so a list read from the database would
be a query that can only ever return the same three rows.
*/
if (section.key === 'layout') {
return (
<div key={section.key} className="flex flex-col">
{row}
{LAYOUT_BANDS.map((band) => {
const childSearch = new URLSearchParams(params);
childSearch.set('section', 'layout');
childSearch.set('band', band.key);
childSearch.delete('media');
const current = active === 'layout' && params.get('band') === band.key;
return (
<Link
key={band.key}
to={{ search: `?${childSearch.toString()}` }}
replace
className={`flex items-baseline gap-2 border-b border-border py-1.5 pr-3 pl-9 text-left transition-colors ${
current ? 'bg-muted' : 'hover:bg-muted/50'
}`}
>
<span className="min-w-0 flex-1 truncate text-xs">{band.label}</span>
</Link>
);
})}
</div>
);
}
if (section.key !== 'pages' || !pages?.length) return row;
return (