- PageShell names the page with a visually hidden h1; a view with its own title renders it as PageHeading and the hidden one steps aside (contract: no hand <h1> inside the shell). - Section captions are SectionLabel headings (h2, h3 in a dialog), ui/form Section included. - "Skip to content" is the first Tab stop and focuses the scroller's <main>. - Below 600px of height the band's filter row folds into "Filters · n" (Feed, Plex, server log), the same row shown in place. - The band holds controls only: Downloads and Notifications lose their title cards, the Config and channel-discovery intros scroll with the content, the Stats tabs and the audit pager join the band, and the server log's toolbar sticks with the fade under it.
16 lines
851 B
TypeScript
16 lines
851 B
TypeScript
import { createContext, useContext, useLayoutEffect } from "react";
|
|
|
|
// B4 / UX-15: every page has exactly one `h1`. The shell renders the page's name as a visually hidden
|
|
// one; a view that shows its OWN title (a channel, a Plex show, a playlist) renders that as the page's
|
|
// `h1` through `PageHeading`, which says so here, and the shell drops its hidden one while it is
|
|
// mounted. The same shape as ./stickyTop: the element announces itself, no page opts out by hand.
|
|
const PageHeadingContext = createContext<(() => () => void) | null>(null);
|
|
|
|
export const PageHeadingProvider = PageHeadingContext.Provider;
|
|
|
|
/** Called by `PageHeading`: while mounted, the shell's hidden page name steps aside. */
|
|
export function useOwnPageHeading(): void {
|
|
const register = useContext(PageHeadingContext);
|
|
useLayoutEffect(() => register?.(), [register]);
|
|
}
|