A third level, under the selected page only — every page has bands, and listing all of them would make the nav longer than the site. Announced by the builder rather than fetched again: it already reads them to know what it is looking at in the document, and it mounts exactly when a page is open. Cleared on unmount, or the nav keeps listing the last page's bands. Clicking one writes a third URL segment and the preview scrolls to it, keyed on the render count too so the position survives a save replacing the frame. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
61 lines
3.0 KiB
TypeScript
61 lines
3.0 KiB
TypeScript
import { useLocation } from 'react-router';
|
|
import { DEFAULT_SECTION, isSectionKey, type SectionKey } from './sections';
|
|
|
|
/**
|
|
* Where this admin is in the URL, read from the PATH.
|
|
*
|
|
* ── The path, not the query string ──
|
|
*
|
|
* It was `?section=layout&band=header`. Everything about where you were lived in the query, which made
|
|
* every link a fresh `URLSearchParams` and every navigation a decision about what to carry — see the
|
|
* note NavPanel used to hold about parameters leaking between sections. A path has no such question:
|
|
* `/fofos-admin/layout/header` says where you are and nothing else, and what remains in the query is
|
|
* genuinely about how you are looking rather than at what.
|
|
*
|
|
* The routes themselves are declared in the manifest, so the platform knows they exist — which is what
|
|
* lets a route carry its own arrangement of panels rather than sharing one with the whole screen.
|
|
*
|
|
* ── Why the pathname and not `useParams` ──
|
|
*
|
|
* Which parameters exist depends on which route matched: `/fofos-admin/layout/header` has a `band` and
|
|
* `/fofos-admin/galleries` has nothing. A panel would then have to know its own route to read its own
|
|
* position, and every panel is rendered on all of them.
|
|
*
|
|
* Two segments after the mount, always in the same places. One reader, every route.
|
|
*/
|
|
export const MOUNT = '/fofos-admin';
|
|
|
|
/**
|
|
* The section and its sub-item, from the two segments after the mount.
|
|
*
|
|
* `atIndex` is the bare `/fofos-admin`, which is a real state and not the same as Layout: the panel
|
|
* redirects, so the address bar ends up saying where you are. `section` is still defaulted, so
|
|
* everything that only wants to draw a highlight has one answer rather than a branch.
|
|
*/
|
|
export const useNav = (): { section: SectionKey; item: string | null; part: string | null; atIndex: boolean } => {
|
|
const { pathname } = useLocation();
|
|
const rest = pathname.startsWith(MOUNT) ? pathname.slice(MOUNT.length) : '';
|
|
const [first = '', second = '', third = ''] = rest.replace(/^\//, '').split('/');
|
|
return {
|
|
// An unknown segment falls back rather than 404s: the URL is a thing people type and edit.
|
|
section: isSectionKey(first) ? first : DEFAULT_SECTION,
|
|
item: second ? decodeURIComponent(second) : null,
|
|
/** Something inside the item — a band of the open page. Nothing else uses a third segment yet. */
|
|
part: third ? decodeURIComponent(third) : null,
|
|
atIndex: first === '',
|
|
};
|
|
};
|
|
|
|
/**
|
|
* A link to a section, or to something inside one.
|
|
*
|
|
* The query string is dropped, and that is the point rather than an omission. `?device=tablet` is how
|
|
* you are looking at the Layout preview; carrying it into Galleries would be state from one screen
|
|
* deciding something on another. A panel that wants a parameter to survive says so at its own link.
|
|
*/
|
|
export const href = (section: SectionKey, item?: string | null, part?: string | null): string => {
|
|
if (!item) return `${MOUNT}/${section}`;
|
|
const base = `${MOUNT}/${section}/${encodeURIComponent(item)}`;
|
|
return part ? `${base}/${encodeURIComponent(part)}` : base;
|
|
};
|