Partition (keyed chunks) and split (named subdocuments) are the same shape: a set
of labels, each owning some pages. This component reduces both to one Segment[]
model and renders three reusable surfaces over it, all driven by one shared
hover/selection state:
SegmentSidebar— a selectable list of segments with page ranges and confidence.SegmentLegend— a compact color key.PageTimeline— a colored strip of pages, plus the source PDF with per-segment color overlays.
Hovering a segment anywhere dims the others everywhere; clicking one scrolls the document to its first page.
Installation
pnpm dlx shadcn@latest add @retab/segmented-document-viewer
Usage
Reduce a partition or split result to Segment[] with toSegments, then render:
import { toSegments } from "@/lib/segments"
import { SegmentedDocumentViewer } from "@/components/ui/segmented-document-viewer"
// partition: [{ key, pages }] split: [{ name, pages }]
const segments = toSegments(result.output)
export function Example() {
return (
<SegmentedDocumentViewer
segments={segments}
src="/document.pdf"
unitLabel="subdocument"
/>
)
}Legend variants
SegmentLegend owns how it attaches to the document surface, so the classify,
split, and partition viewers all share one legend instead of each re-building its
own chrome. The variant prop is the placement; orientation + side decide
which edge it lives on.
import { SegmentLegend } from "@/components/ui/segment-legend"
// flush bordered bar (default) floating overlay card
<SegmentLegend segments={segments} />
<SegmentLegend segments={segments} variant="floating" />
// gapped rounded panel vertical rail on the left
<SegmentLegend segments={segments} variant="inset" />
<SegmentLegend segments={segments} orientation="vertical" side="left" />| Prop | Type | Description |
|---|---|---|
variant | "bar" | "floating" | "inset" | "plain" | How it attaches to the surface. floating/inset need a relative parent. Defaults to bar. |
orientation | "horizontal" | "vertical" | Wrap/grid of entries, or a vertical rail. |
side | "top" | "bottom" | "left" | "right" | Edge it docks to (border for bar, anchor for floating). |
density | "comfortable" | "compact" | Swatch + label scale. |
caption | ReactNode | Muted line under the entries (e.g. a classification's reasoning). |
accessory | ReactNode | Extra content inside the same chrome (e.g. a page ribbon). |
Composing the surfaces yourself
The three surfaces are independent primitives — wire them to your own shared
activeId state for a custom layout:
import { SegmentLegend } from "@/components/ui/segment-legend"
import { SegmentSidebar } from "@/components/ui/segment-sidebar"
import { PageTimeline } from "@/components/ui/page-timeline"
const [activeId, setActiveId] = useState<string | null>(null)
<SegmentLegend segments={segments} activeId={activeId} onActivate={setActiveId} />
<SegmentSidebar segments={segments} activeId={activeId} onActivate={setActiveId} />
<PageTimeline segments={segments} activeId={activeId} onActivate={setActiveId} />Props
| Prop | Type | Description |
|---|---|---|
segments | Segment[] | Labels + pages + colors (use toSegments). |
src | string | Optional source PDF, previewed with per-segment color overlays. |
pageCount | number | Total pages; defaults to the max page across segments. |
unitLabel | string | Row noun, e.g. "chunk" or "subdocument". |
title | ReactNode | Header title. |