0
Files
pdf-library-app.tsx
"use client";

import { useDocumentState } from "@embedpdf/core/react";
import { LayoutGridIcon } from "lucide-react";
import * as React from "react";

import { Button } from "@/components/ui/button";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { PdfDocumentGrid } from "@/components/pdf-document-grid";
import { PdfMoreActionsMenu } from "@/components/pdf-more-actions-menu";
import { PdfPageNavigation } from "@/components/pdf-page-navigation";
import { PdfSearchPopover } from "@/components/pdf-search-panel";
import {
  PdfToolbar,
  PdfToolbarGroup,
  PdfToolbarSeparator,
} from "@/components/pdf-toolbar";
import { PdfViewer, usePdfView } from "@/components/pdf-viewer";
import { PdfViewerContent } from "@/components/pdf-viewer-content";
import { PdfZoomControls } from "@/components/pdf-zoom-controls";

/**
 * A document-library launcher assembled entirely from pdfcn's registry
 * components, after PDFSlick's MultipleDocuments example. It has two modes on
 * one engine:
 * - **Library** — `<PdfDocumentGrid>` fills the frame: every open document is a
 *   navigable card, plus a card that opens more. Picking a card routes it into
 *   the shared view and flips to the reader.
 * - **Reader** — a full-screen `<PdfViewerContent>` of the picked document with
 *   a toolbar. The toolbar's grid button returns to the library.
 *
 * Both modes read the *same* `<PdfViewer>`: the grid renders every document off
 * the thumbnail plugin without a viewer per file, and the reader shows the
 * focused view's active document — so switching modes never reloads anything.
 *
 * The root fills its parent, so give it a sized container (a viewport-height
 * flex cell, or an explicit height).
 */
export function PdfLibraryApp({
  className,
  ...props
}: Omit<React.ComponentProps<typeof PdfViewer>, "children">) {
  const [mode, setMode] = React.useState<"library" | "reader">("library");

  return (
    <PdfViewer className={cn("h-full", className)} {...props}>
      {mode === "library" ? (
        <PdfDocumentGrid
          className="min-h-0 flex-1"
          onSelectDocument={() => setMode("reader")}
        />
      ) : (
        <PdfLibraryReader onBackToLibrary={() => setMode("library")} />
      )}
    </PdfViewer>
  );
}

/**
 * The reader mode: a toolbar over the page canvas, scoped to the focused view's
 * active document (the card the user just picked). The leading grid button is
 * the way back to the library; the document's name sits beside it so it's clear
 * which one is open.
 */
function PdfLibraryReader({
  onBackToLibrary,
}: {
  onBackToLibrary: () => void;
}) {
  const { documentId } = usePdfView();
  const documentName = useDocumentState(documentId)?.name;

  return (
    <div className="flex min-h-0 flex-1 flex-col">
      {/*
        `@container/toolbar` scopes the width queries to the toolbar itself, so
        the exact-value controls (the zoom-level select, the page-number input)
        drop below `@lg/toolbar` while the steppers, search, and back button
        stay — the block reads the same embedded in a column as full-screen.
      */}
      <PdfToolbar className="@container/toolbar">
        <PdfToolbarGroup>
          <Tooltip>
            <TooltipTrigger
              render={
                <Button
                  variant="ghost"
                  size="icon-sm"
                  aria-label="Back to library"
                  onClick={onBackToLibrary}
                >
                  <LayoutGridIcon />
                </Button>
              }
            />
            <TooltipContent>Back to library</TooltipContent>
          </Tooltip>
        </PdfToolbarGroup>
        {documentName ? (
          <span
            className="text-muted-foreground min-w-0 truncate text-sm"
            title={documentName}
          >
            {documentName}
          </span>
        ) : null}

        <div className="ml-auto flex items-center gap-1">
          <PdfZoomControls className="**:data-[slot=pdf-zoom-controls-level]:@max-lg/toolbar:hidden" />
          <PdfToolbarSeparator />
          <PdfPageNavigation className="**:data-[slot=pdf-page-navigation-pages]:@max-lg/toolbar:hidden" />
          <PdfSearchPopover />
          <PdfToolbarSeparator />
          <PdfMoreActionsMenu />
        </div>
      </PdfToolbar>
      <PdfViewerContent />
    </div>
  );
}

Every open document is a card in a grid — navigate its pages in place, then pick one to open it in a full-screen reader and jump back to the library. It renders every document off one engine with embedpdf's thumbnail plugin, after PDFSlick's multiple-documents example.