Mischief

29 / Documents

Page Navigator

A rail of page thumbnails for moving through a long document, with arrow-key navigation and a clear active page.

Showing page 2 of 3

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/page-navigator
import { PageNavigator } from "mischief-ui/page-navigator"
Also installs
  • lucide-react

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/page-navigator/page-navigator.tsx
"use client" import * as React from "react"import { FileText } from "lucide-react"import { cn } from "@/lib/utils" export type DocumentPage = {  number: number  src?: string  label?: string} export type PageNavigatorProps = Omit<  React.HTMLAttributes<HTMLElement>,

Usage

export function Sidebar({ pages }: { pages: DocumentPage[] }) {
  return <PageNavigator pages={pages} onActivePageChange={scrollToPage} />
}

Numbering and thumbnails

Pages carry their own number rather than being counted from their position, so a navigator over pages 40 to 60 of a long document says 40 to 60. Whatever you pass is what is shown and what onActivePageChange reports.

src is optional. Without it the page still appears, as a numbered placeholder, which is what you want while thumbnails are still being rendered -- the strip keeps its full length instead of growing as images arrive and pushing the current page around.

<PageNavigator
  pages={pages.map((page) => ({
    number: page.number,
    src: thumbnails[page.number],
    label: page.heading,
  }))}
  activePage={current}
  onActivePageChange={setCurrent}
/>

Which way it runs

Vertical is the familiar side rail beside a document, and is the better choice for a long file because a tall strip holds more thumbnails at a readable size than a wide one does. Horizontal suits a short document, or a narrow screen where a side rail would take a third of the width.

renderImage lets your own image component take over -- a framework's optimised image, a signed URL that needs refreshing, a canvas you are already painting pages onto.

API

pagesDocumentPage[]Page number, optional thumbnail src, and optional label.
activePage, defaultActivePagenumberThe current page, controlled or uncontrolled.
onActivePageChange(page: number) => voidRuns when the page changes.
orientation"vertical" | "horizontal"Rail direction. Defaults to "vertical".
renderImage(props) => ReactNodeUses a framework image component for thumbnails.

DocumentPage

numbernumberShown as the page number, and reported on change.
srcstringThumbnail. Omit for a numbered placeholder.
labelstringExtra description, such as a section heading.

Accessibility

The rail is a tab list with a single tab stop. Arrow keys move between pages along the rail's orientation, Home and End jump to the ends, and focus follows selection. Pages without a thumbnail fall back to an icon and still carry their number, so the control works before any image has loaded.