import * as React from 'react'; /** One in-page destination. `id` must match the `id` of an element on the page. */ type OnThisPageItem = { /** `id` of the section this entry points at. */ id: string; /** Visible link text. */ title: React.ReactNode; }; type OnThisPageProps = Omit, 'children'> & { /** In-page destinations, in document order. */ items: OnThisPageItem[]; /** * `horizontal` is the sticky bar that docks under the site chrome; * `vertical` is the left- or right-hand rail beside long-form content. * * @default 'horizontal' */ orientation?: 'horizontal' | 'vertical'; /** * Y-coordinate, in CSS pixels from the top of the viewport, of the line a * section must cross to count as the one being read. Set it to the height of * whatever chrome overlays the top of the page (plus any slack you want), * or sections scrolled to via their anchor register as active a fraction too * late. `useChromeHeight` measures that number for you. * * @default 0 */ offset?: number; /** * Active section (controlled). When set, the component reports what it * observes through `onActiveChange` but never sets its own state — pick one * mode per instance and stay in it, as with `ThemeSwitcher`. */ activeId?: string | null; /** Fired whenever the observed section changes, in both modes. */ onActiveChange?: (id: string | null) => void; /** Shown instead of the list when `items` is empty. `null` renders nothing. */ emptyMessage?: React.ReactNode; ref?: React.Ref; }; /** * In-page navigation that tracks which section the reader has reached. * * **Not to be confused with the other three navigation components**, which * this deliberately does not overlap: * * | Component | Moves you | * | ---------------- | -------------------------------- | * | `MainNav` | between sites/sections, mega menu | * | `SideNav` | between pages, as a tree | * | `StepNav` | through a multi-page journey | * | `OnThisPage` | within the page you are on | * * It is named `OnThisPage` rather than the more obvious `SectionNav` because * `SideNav` already announces itself as "Section navigation" by default, and * two components with near-identical names and different jobs is the kind of * ambiguity a design system exists to remove. * * **No Base UI primitive is involved, and none is missing.** The house rule is * never to hand-roll ARIA, focus or keyboard behaviour — but these are plain * anchors, which already have all three natively. What is added here is * position tracking, which is not an interaction pattern. * * Accessibility contract: * * - The landmark is named (`aria-label`, default "On this page") so it is * distinguishable from the page's other navigation landmarks (WCAG 2.2, * 1.3.1). * - The tracked entry is marked **`aria-current="location"`, not `"page"`**. * The reader has not navigated anywhere — every entry points at the page * they are already on — so `"page"` would announce every link as current. * `"location"` is the value defined for "the closest ancestor in a set of * related elements", which is exactly a position within a document. * - `role="list"` is explicit on the `