pdf-tsx 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +78 -78
  2. package/README.md +678 -634
  3. package/dist/cjs/components/PDFViewer.cjs +1 -1
  4. package/dist/cjs/components/PDFViewer.module.cjs +1 -1
  5. package/dist/cjs/components/features/PDFViewModeToggle.cjs +1 -0
  6. package/dist/cjs/components/features/PDFViewModeToggle.module.cjs +1 -0
  7. package/dist/cjs/favicon.svg +9 -0
  8. package/dist/cjs/hooks/usePDFLoader.cjs +1 -1
  9. package/dist/cjs/index.cjs +1 -1
  10. package/dist/cjs/labels.cjs +1 -1
  11. package/dist/cjs/pdf-tsx.css +1 -1
  12. package/dist/cjs/vendor/pdfjs-wasm/jbig2.cjs +1 -0
  13. package/dist/cjs/vendor/pdfjs-wasm/openjpeg.cjs +1 -0
  14. package/dist/cjs/vendor/pdfjs-wasm/qcms_bg.cjs +1 -0
  15. package/dist/components/PDFSidebar.d.ts +1 -1
  16. package/dist/components/PDFViewer.d.ts +2 -2
  17. package/dist/components/features/PDFAnnotations.d.ts +2 -2
  18. package/dist/components/features/PDFAttachments.d.ts +2 -2
  19. package/dist/components/features/PDFChangeFile.d.ts +1 -1
  20. package/dist/components/features/PDFDownloadButton.d.ts +1 -1
  21. package/dist/components/features/PDFNavigation.d.ts +1 -1
  22. package/dist/components/features/PDFOutline.d.ts +1 -1
  23. package/dist/components/features/PDFPageFit.d.ts +1 -1
  24. package/dist/components/features/PDFPrintButton.d.ts +1 -1
  25. package/dist/components/features/PDFRotationControl.d.ts +1 -1
  26. package/dist/components/features/PDFSearch.d.ts +1 -1
  27. package/dist/components/features/PDFSidebarToggle.d.ts +1 -1
  28. package/dist/components/features/PDFSignatures.d.ts +2 -2
  29. package/dist/components/features/PDFThemeToggle.d.ts +1 -1
  30. package/dist/components/features/PDFThumbnails.d.ts +1 -1
  31. package/dist/components/features/PDFViewModeToggle.d.ts +11 -0
  32. package/dist/components/features/PDFZoomControls.d.ts +1 -1
  33. package/dist/components/ui/PDFToolbar.d.ts +1 -1
  34. package/dist/components/ui/PDFToolbarItem.d.ts +1 -1
  35. package/dist/components/ui/PDFToolbarSeparator.d.ts +1 -1
  36. package/dist/components/ui/ToolbarButton.d.ts +1 -1
  37. package/dist/components/ui/icons.d.ts +3 -2
  38. package/dist/context/LabelsContext.d.ts +1 -1
  39. package/dist/context/PDFViewerContext.d.ts +2 -0
  40. package/dist/context/ThemeContext.d.ts +1 -1
  41. package/dist/es/components/PDFViewer.js +247 -224
  42. package/dist/es/components/PDFViewer.module.js +26 -23
  43. package/dist/es/components/features/PDFViewModeToggle.js +70 -0
  44. package/dist/es/components/features/PDFViewModeToggle.module.js +12 -0
  45. package/dist/es/favicon.svg +9 -0
  46. package/dist/es/hooks/usePDFLoader.js +36 -16
  47. package/dist/es/index.js +13 -12
  48. package/dist/es/labels.js +8 -0
  49. package/dist/es/pdf-tsx.css +1 -1
  50. package/dist/es/vendor/pdfjs-wasm/jbig2.js +4 -0
  51. package/dist/es/vendor/pdfjs-wasm/openjpeg.js +4 -0
  52. package/dist/es/vendor/pdfjs-wasm/qcms_bg.js +4 -0
  53. package/dist/hooks/usePDFLoader.d.ts +1 -1
  54. package/dist/index.d.ts +1 -0
  55. package/dist/labels.d.ts +4 -0
  56. package/package.json +66 -65
  57. package/dist/cjs/vite.svg +0 -1
  58. package/dist/components/features/PDFSignButton.d.ts +0 -10
  59. package/dist/components/features/SignaturePad.d.ts +0 -10
  60. package/dist/es/vite.svg +0 -1
  61. package/dist/pdf-tsx.cjs.js +0 -5
  62. package/dist/pdf-tsx.css +0 -2
  63. package/dist/pdf-tsx.es.js +0 -2637
  64. package/dist/src/components/PDFSidebar.d.ts +0 -11
  65. package/dist/src/components/PDFViewer.d.ts +0 -22
  66. package/dist/src/components/features/PDFAnnotations.d.ts +0 -5
  67. package/dist/src/components/features/PDFAttachments.d.ts +0 -5
  68. package/dist/src/components/features/PDFChangeFile.d.ts +0 -9
  69. package/dist/src/components/features/PDFDownloadButton.d.ts +0 -9
  70. package/dist/src/components/features/PDFNavigation.d.ts +0 -10
  71. package/dist/src/components/features/PDFOutline.d.ts +0 -1
  72. package/dist/src/components/features/PDFPageFit.d.ts +0 -10
  73. package/dist/src/components/features/PDFPrintButton.d.ts +0 -9
  74. package/dist/src/components/features/PDFRotationControl.d.ts +0 -9
  75. package/dist/src/components/features/PDFSearch.d.ts +0 -13
  76. package/dist/src/components/features/PDFSidebarToggle.d.ts +0 -10
  77. package/dist/src/components/features/PDFSignatures.d.ts +0 -5
  78. package/dist/src/components/features/PDFThemeToggle.d.ts +0 -10
  79. package/dist/src/components/features/PDFThumbnails.d.ts +0 -1
  80. package/dist/src/components/features/PDFZoomControls.d.ts +0 -10
  81. package/dist/src/components/ui/PDFToolbar.d.ts +0 -8
  82. package/dist/src/components/ui/PDFToolbarDropdownContext.d.ts +0 -1
  83. package/dist/src/components/ui/PDFToolbarItem.d.ts +0 -7
  84. package/dist/src/components/ui/PDFToolbarSeparator.d.ts +0 -4
  85. package/dist/src/components/ui/ToolbarButton.d.ts +0 -6
  86. package/dist/src/components/ui/icons.d.ts +0 -7
  87. package/dist/src/context/LabelsContext.d.ts +0 -7
  88. package/dist/src/context/PDFViewerContext.d.ts +0 -30
  89. package/dist/src/context/ThemeContext.d.ts +0 -12
  90. package/dist/src/context/labelsContextValue.d.ts +0 -2
  91. package/dist/src/context/themeContextValue.d.ts +0 -7
  92. package/dist/src/hooks/useAnnotationLayer.d.ts +0 -34
  93. package/dist/src/hooks/useAnnotations.d.ts +0 -15
  94. package/dist/src/hooks/useAttachments.d.ts +0 -11
  95. package/dist/src/hooks/useKeyboardShortcuts.d.ts +0 -9
  96. package/dist/src/hooks/useLabels.d.ts +0 -2
  97. package/dist/src/hooks/useOutline.d.ts +0 -14
  98. package/dist/src/hooks/useOverlayRects.d.ts +0 -21
  99. package/dist/src/hooks/usePDFLoader.d.ts +0 -5
  100. package/dist/src/hooks/usePDFRenderer.d.ts +0 -16
  101. package/dist/src/hooks/useScrollTracking.d.ts +0 -10
  102. package/dist/src/hooks/useSignatures.d.ts +0 -17
  103. package/dist/src/hooks/useTextLayer.d.ts +0 -10
  104. package/dist/src/hooks/useTextSearch.d.ts +0 -12
  105. package/dist/src/hooks/useTheme.d.ts +0 -2
  106. package/dist/src/hooks/useThumbnails.d.ts +0 -10
  107. package/dist/src/index.d.ts +0 -24
  108. package/dist/src/labels.d.ts +0 -53
  109. package/dist/src/theme.d.ts +0 -27
  110. package/dist/src/utils/pdfFit.d.ts +0 -2
  111. package/dist/src/utils/url.d.ts +0 -1
  112. package/dist/utils/addSignatureToPdf.d.ts +0 -9
  113. package/dist/vite.svg +0 -1
  114. package/dist/wasm/LICENSE_JBIG2 +0 -196
  115. package/dist/wasm/LICENSE_OPENJPEG +0 -39
  116. package/dist/wasm/LICENSE_PDFJS_JBIG2 +0 -13
  117. package/dist/wasm/LICENSE_PDFJS_OPENJPEG +0 -22
  118. package/dist/wasm/LICENSE_PDFJS_QCMS +0 -22
  119. package/dist/wasm/LICENSE_QCMS +0 -21
  120. package/dist/wasm/jbig2.wasm +0 -0
  121. package/dist/wasm/jbig2_nowasm_fallback.js +0 -15
  122. package/dist/wasm/openjpeg.wasm +0 -0
  123. package/dist/wasm/openjpeg_nowasm_fallback.js +0 -17
  124. package/dist/wasm/qcms_bg.wasm +0 -0
  125. package/dist/wasm/quickjs-eval.js +0 -16
  126. package/dist/wasm/quickjs-eval.wasm +0 -0
  127. /package/dist/{src/hooks → hooks}/usePinchZoom.d.ts +0 -0
  128. /package/dist/{src/utils → utils}/pdfjsPolyfill.d.ts +0 -0
  129. /package/dist/{src/utils → utils}/zoom.d.ts +0 -0
package/README.md CHANGED
@@ -1,634 +1,678 @@
1
- # pdf-tsx
2
-
3
- A fully-featured, composable PDF viewer component for React.
4
-
5
- **🔗 [Live demo & docs → pdf-tsx.netlify.app](https://pdf-tsx.netlify.app/)**
6
-
7
- ## Installation
8
-
9
- ```bash
10
- npm install pdf-tsx
11
- ```
12
-
13
- ### Peer dependencies
14
-
15
- ```bash
16
- npm install react react-dom pdfjs-dist
17
- ```
18
-
19
- ---
20
-
21
- ## CSS
22
-
23
- Import the stylesheet once in your app entry point:
24
-
25
- ```ts
26
- import "pdf-tsx/dist/es/pdf-tsx.css";
27
- ```
28
-
29
- ---
30
-
31
- ## Quick start
32
-
33
- `PDFViewer` requires a `workerSrc` prop — the URL of the pdfjs worker script. The easiest way is to serve it from the `pdfjs-dist` package directly:
34
-
35
- ```tsx
36
- import { PDFViewer } from "pdf-tsx";
37
- import workerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url";
38
-
39
- function App() {
40
- const [file, setFile] = useState<File | null>(null);
41
-
42
- if (!file)
43
- return (
44
- <input
45
- type="file"
46
- accept=".pdf"
47
- onChange={(e) => setFile(e.target.files![0])}
48
- />
49
- );
50
-
51
- return <PDFViewer file={file} workerSrc={workerUrl} />;
52
- }
53
- ```
54
-
55
- > The `?url` suffix is a Vite feature that returns the asset URL as a string. If you use a different bundler, import the worker URL accordingly or pass a CDN URL:
56
- >
57
- > ```ts
58
- > workerSrc = "https://unpkg.com/pdfjs-dist@5/build/pdf.worker.min.mjs";
59
- > ```
60
-
61
- ---
62
-
63
- ## Icons
64
-
65
- Feature components do not bundle any icon library. Each component that shows an icon requires you to pass it explicitly as a prop — use any icon library or custom SVG you prefer.
66
-
67
- ```tsx
68
- import { LuZoomIn, LuZoomOut } from "react-icons/lu";
69
-
70
- <PDFZoomControls
71
- zoomInIcon={<LuZoomIn size={16} />}
72
- zoomOutIcon={<LuZoomOut size={16} />}
73
- />;
74
- ```
75
-
76
- ---
77
-
78
- ## PDFViewer
79
-
80
- The main component. Must wrap all feature components.
81
-
82
- ```tsx
83
- <PDFViewer
84
- file={file}
85
- workerSrc={workerUrl}
86
- sidebar={<PDFSidebar panels={[...]} />}
87
- toolbar={<>...</>}
88
- onChangeFile={(newFile) => setFile(newFile)}
89
- />
90
- ```
91
-
92
- | Prop | Type | Required | Description |
93
- | ---------------- | --------------------------------------------------- | -------- | ------------------------------------------------------- |
94
- | `file` | `File` | yes | The PDF file object to display |
95
- | `workerSrc` | `string` | yes | URL of the pdfjs worker script (see Quick start) |
96
- | `sidebar` | `ReactNode` | no | Content rendered on the left side |
97
- | `toolbar` | `ReactNode` | no | Content rendered in the top toolbar |
98
- | `onChangeFile` | `(file: File) => void` | no | Called when the user opens a different file |
99
- | `defaultTheme` | `"light" \| "dark"` | no | Initial theme mode (default: `"light"`) |
100
- | `themeOverrides` | `{ light?: Partial<Theme>; dark?: Partial<Theme> }` | no | Override individual theme tokens (see Theming) |
101
- | `defaultFit` | `"width" \| "page"` | no | Initial zoom mode when a PDF is loaded |
102
- | `language` | `"it" \| "en"` | no | UI language (default: `"en"`) |
103
- | `className` | `string` | no | CSS class applied to the root layout element |
104
- | `style` | `React.CSSProperties` | no | Inline style applied to the root layout element |
105
- | `requireRead` | `boolean` | no | Show a mandatory-reading banner (see Mandatory reading) |
106
- | `onDocumentRead` | `() => void` | no | Called once when the user reaches the last page |
107
-
108
- ---
109
-
110
- ## Theming
111
-
112
- `PDFViewer` ships with built-in light and dark themes. The default is `"light"`. Use `defaultTheme` to set the initial mode and `themeOverrides` to override individual tokens without replacing the whole theme.
113
-
114
- ```tsx
115
- import { PDFViewer, darkTheme } from 'pdf-tsx'
116
-
117
- <PDFViewer
118
- themeOverrides={{
119
- dark: { accent: '#e11d48', accentHover: '#be123c', accentDark: '#9f1239' },
120
- light: { accent: '#e11d48', accentHover: '#be123c', accentDark: '#9f1239' },
121
- }}
122
- ...
123
- />
124
- ```
125
-
126
- Both `darkTheme` and `lightTheme` are exported so you can spread them as a base:
127
-
128
- ```tsx
129
- import { darkTheme } from 'pdf-tsx'
130
-
131
- themeOverrides={{ dark: { ...darkTheme, bg: '#0f0f0f' } }}
132
- ```
133
-
134
- **Theme tokens** (`Theme` interface):
135
-
136
- | Token | CSS variable | Description |
137
- | ------------------ | ------------------------- | ------------------------------------------------------------- |
138
- | `bg` | `--pdf-bg` | Main background |
139
- | `surface` | `--pdf-surface` | Card / panel surface |
140
- | `surfaceHover` | `--pdf-surface-hover` | Surface hover state |
141
- | `border` | `--pdf-border` | Default border color |
142
- | `borderLight` | `--pdf-border-light` | Subtle border color |
143
- | `scrollArea` | `--pdf-scroll-area` | Scroll container background |
144
- | `textPrimary` | `--pdf-text-primary` | Primary text |
145
- | `textSecondary` | `--pdf-text-secondary` | Secondary text |
146
- | `textMuted` | `--pdf-text-muted` | Muted / disabled text |
147
- | `textPlaceholder` | `--pdf-text-placeholder` | Input placeholder text |
148
- | `accent` | `--pdf-accent` | Accent / interactive color |
149
- | `accentHover` | `--pdf-accent-hover` | Accent hover state |
150
- | `accentDark` | `--pdf-accent-dark` | Accent dark variant |
151
- | `accentLight` | `--pdf-accent-light` | Accent light variant |
152
- | `accentSubtle` | `--pdf-accent-subtle` | Accent with low opacity (backgrounds) |
153
- | `panelBg` | `--pdf-panel-bg` | Sidebar panel background |
154
- | `panelBorder` | `--pdf-panel-border` | Sidebar panel border |
155
- | `panelHeaderBg` | `--pdf-panel-header-bg` | Sidebar panel header background |
156
- | `shadowPage` | `--pdf-shadow-page` | Drop shadow color for PDF pages |
157
- | `badgeBg` | `--pdf-badge-bg` | Count badge background (signatures, annotations, attachments) |
158
- | `toolbarSeparator` | `--pdf-toolbar-separator` | `PDFToolbarSeparator` line color |
159
- | `fontFamily` | `--pdf-font-family` | Font family for all viewer UI text |
160
-
161
- ---
162
-
163
- ## PDFSidebar
164
-
165
- A collapsible icon sidebar. Each panel has an icon button — clicking it toggles the panel open/closed. When collapsed via `PDFSidebarToggle`, the entire sidebar is hidden (`null`).
166
-
167
- ```tsx
168
- import { PDFSidebar } from "pdf-tsx";
169
- import { LuLayoutGrid, LuBookOpen } from "react-icons/lu";
170
-
171
- <PDFSidebar
172
- panels={[
173
- {
174
- icon: <LuLayoutGrid size={18} />,
175
- label: "Thumbnails",
176
- content: <PDFThumbnails />,
177
- },
178
- {
179
- icon: <LuBookOpen size={18} />,
180
- label: "Outline",
181
- content: <PDFOutline />,
182
- },
183
- ]}
184
- />;
185
- ```
186
-
187
- | Prop | Type | Description |
188
- | -------- | ---------------- | -------------------------- |
189
- | `panels` | `SidebarPanel[]` | Array of panels to display |
190
-
191
- **SidebarPanel**
192
-
193
- | Field | Type | Description |
194
- | --------- | ----------- | -------------------------------------- |
195
- | `icon` | `ReactNode` | Icon shown in the sidebar button bar |
196
- | `label` | `string` | Panel title shown in the header |
197
- | `content` | `ReactNode` | Content rendered inside the open panel |
198
-
199
- ### Sidebar hide/show pattern
200
-
201
- Use `PDFSidebarToggle` in the toolbar alongside `PDFSidebar`. When the sidebar is open, the collapse button lives inside the sidebar. When collapsed, the expand button appears in the toolbar.
202
-
203
- ```tsx
204
- import { PDFSidebar, PDFSidebarToggle, PDFToolbar } from 'pdf-tsx'
205
- import { LuPanelLeftClose, LuPanelLeftOpen, LuEllipsis } from 'react-icons/lu'
206
-
207
- <PDFViewer
208
- sidebar={
209
- <PDFSidebar panels={[...]} />
210
- }
211
- toolbar={
212
- <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
213
- <PDFSidebarToggle
214
- collapseIcon={<LuPanelLeftClose size={16} />}
215
- expandIcon={<LuPanelLeftOpen size={16} />}
216
- />
217
- {/* other toolbar items */}
218
- </PDFToolbar>
219
- }
220
- />
221
- ```
222
-
223
- ---
224
-
225
- ## PDFToolbar (responsive toolbar)
226
-
227
- `PDFToolbar` wraps your toolbar items and automatically collapses those that don't fit the available width into an overflow `···` dropdown. It uses `ResizeObserver` to react to container size changes — no configuration required.
228
-
229
- ```tsx
230
- import { PDFToolbar } from "pdf-tsx";
231
- import {
232
- LuEllipsis,
233
- LuPanelLeftClose,
234
- LuPanelLeftOpen,
235
- LuSearch,
236
- LuChevronUp,
237
- LuChevronDown,
238
- LuX,
239
- LuZoomOut,
240
- LuZoomIn,
241
- LuChevronLeft,
242
- LuChevronRight,
243
- } from "react-icons/lu";
244
-
245
- <PDFViewer
246
- toolbar={
247
- <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
248
- <PDFSidebarToggle
249
- collapseIcon={<LuPanelLeftClose size={16} />}
250
- expandIcon={<LuPanelLeftOpen size={16} />}
251
- />
252
- <PDFSearch
253
- searchIcon={<LuSearch size={14} />}
254
- prevIcon={<LuChevronUp size={14} />}
255
- nextIcon={<LuChevronDown size={14} />}
256
- clearIcon={<LuX size={14} />}
257
- />
258
- <PDFZoomControls
259
- zoomOutIcon={<LuZoomOut size={16} />}
260
- zoomInIcon={<LuZoomIn size={16} />}
261
- />
262
- <PDFNavigation
263
- prevIcon={<LuChevronLeft size={16} />}
264
- nextIcon={<LuChevronRight size={16} />}
265
- />
266
- {/* more items */}
267
- </PDFToolbar>
268
- }
269
- />;
270
- ```
271
-
272
- | Prop | Type | Description |
273
- | -------------- | ----------- | ----------------------------------------------------- |
274
- | `children` | `ReactNode` | Toolbar items in priority order (left = highest) |
275
- | `overflowIcon` | `ReactNode` | Icon for the `···` overflow button (default: `"···"`) |
276
-
277
- **Dropdown labels** — all built-in feature components carry a `toolbarLabelKey` static property. When an item is moved to the overflow dropdown, `PDFToolbar` resolves the label from the active language automatically. No extra configuration needed.
278
-
279
- **Custom items** — wrap any custom component in `PDFToolbarItem` to give it a dropdown label:
280
-
281
- ```tsx
282
- import { PDFToolbarItem } from "pdf-tsx";
283
-
284
- <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
285
- <PDFToolbarItem label="Custom">
286
- <MyCustomButton />
287
- </PDFToolbarItem>
288
- </PDFToolbar>;
289
- ```
290
-
291
- ---
292
-
293
- ## Feature components
294
-
295
- All feature components must be rendered inside `<PDFViewer>`. They read and write state via `usePDFViewer()` internally.
296
-
297
- | Component | Icon props | Description |
298
- | --------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
299
- | `PDFToolbar` | `overflowIcon` | Responsive toolbar — collapses items into `···` dropdown when space runs out |
300
- | `PDFToolbarItem` | | Wrapper for custom toolbar items to add a dropdown label |
301
- | `PDFToolbarSeparator` | | Vertical divider between toolbar groups — hidden automatically in the overflow dropdown |
302
- | `PDFThumbnails` | | Page thumbnail strip for quick navigation |
303
- | `PDFOutline` | — | Document outline / bookmarks tree |
304
- | `PDFAnnotations` | — | Display PDF annotations |
305
- | `PDFAnnotationsIcon` | `icon` | Sidebar icon with annotation count badge |
306
- | `PDFSignatures` | — | Display PDF digital signatures |
307
- | `PDFSignaturesIcon` | `icon` | Sidebar icon with signature count badge |
308
- | `PDFAttachments` | | List embedded file attachments with download buttons |
309
- | `PDFAttachmentsIcon` | `icon` | Sidebar icon with attachment count badge |
310
- | `PDFSearch` | `searchIcon` `prevIcon` `nextIcon` `clearIcon` | Full-text search with match highlighting. Accepts optional `defaultSearch` to pre-fill and auto-navigate |
311
- | `PDFZoomControls` | `zoomInIcon` `zoomOutIcon` | Zoom in / zoom out buttons with percentage display |
312
- | `PDFPageFit` | `fitWidthIcon` `fitPageIcon` | Fit-to-width and fit-to-page buttons |
313
- | `PDFRotationControl` | `icon` | Rotate page clockwise |
314
- | `PDFNavigation` | `prevIcon` `nextIcon` | Previous / next page buttons with current page input |
315
- | `PDFDownloadButton` | `icon` | Download the current PDF file |
316
- | `PDFChangeFile` | `icon` | Button to open a new PDF file |
317
- | `PDFThemeToggle` | `lightIcon` `darkIcon` | Toggle between dark and light mode |
318
- | `PDFPrintButton` | `icon` | Print the PDF using the browser's native print dialog |
319
- | `PDFSidebarToggle` | `collapseIcon` `expandIcon` | Toolbar button to expand the sidebar visible only when sidebar is hidden (see Sidebar hide/show pattern) |
320
-
321
- ### PDFSearch
322
-
323
- | Prop | Type | Required | Description |
324
- | --------------- | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
325
- | `searchIcon` | `ReactNode` | yes | Icon shown inside the search input |
326
- | `prevIcon` | `ReactNode` | yes | Icon for the "previous match" button |
327
- | `nextIcon` | `ReactNode` | yes | Icon for the "next match" button |
328
- | `clearIcon` | `ReactNode` | yes | Icon for the "clear search" button |
329
- | `initialSearch` | `string` | no | Pre-fills the search input and automatically navigates to the first match once the PDF is loaded. Navigation fires once — subsequent manual edits are unaffected. |
330
-
331
- ```tsx
332
- <PDFSearch
333
- initialSearch="invoice"
334
- searchIcon={<LuSearch size={14} />}
335
- prevIcon={<LuChevronUp size={14} />}
336
- nextIcon={<LuChevronDown size={14} />}
337
- clearIcon={<LuX size={14} />}
338
- />
339
- ```
340
-
341
- ---
342
-
343
- ### Count badges
344
-
345
- `PDFSignaturesIcon`, `PDFAnnotationsIcon`, and `PDFAttachmentsIcon` are icon wrapper components intended for use as the `icon` prop in `PDFSidebar` panels. They render the provided icon with a small count badge in the top-right corner, automatically populated from the loaded PDF.
346
-
347
- ```tsx
348
- import {
349
- PDFSidebar,
350
- PDFSignatures,
351
- PDFSignaturesIcon,
352
- PDFAnnotations,
353
- PDFAnnotationsIcon,
354
- PDFAttachments,
355
- PDFAttachmentsIcon,
356
- } from "pdf-tsx";
357
- import { LuSignature, LuStickyNote, LuPaperclip } from "react-icons/lu";
358
-
359
- <PDFSidebar
360
- panels={[
361
- {
362
- icon: <PDFSignaturesIcon icon={<LuSignature size={17} />} />,
363
- label: "Signatures",
364
- content: <PDFSignatures />,
365
- },
366
- {
367
- icon: <PDFAnnotationsIcon icon={<LuStickyNote size={17} />} />,
368
- label: "Annotations",
369
- content: <PDFAnnotations />,
370
- },
371
- {
372
- icon: <PDFAttachmentsIcon icon={<LuPaperclip size={17} />} />,
373
- label: "Attachments",
374
- content: <PDFAttachments />,
375
- },
376
- ]}
377
- />;
378
- ```
379
-
380
- The badge uses `var(--pdf-accent)` and respects `themeOverrides`.
381
-
382
- ---
383
-
384
- ## Mandatory reading
385
-
386
- Use `requireRead` when the user must scroll through the entire document before proceeding (e.g. terms and conditions, consent forms).
387
-
388
- ```tsx
389
- const [canProceed, setCanProceed] = useState(false)
390
-
391
- <PDFViewer
392
- requireRead
393
- onDocumentRead={() => setCanProceed(true)}
394
- ...
395
- />
396
-
397
- <button disabled={!canProceed}>Accetto</button>
398
- ```
399
-
400
- `PDFViewer` renders a sticky banner at the bottom of the scroll area:
401
-
402
- - **Pending** — accent color, arrow-down icon, label `requireReadPending` ("Scroll to the end to continue")
403
- - **Confirmed** — green (`#16a34a`), checkmark icon, label `requireReadDone` ("Document read") — the banner stays visible after confirmation
404
-
405
- `onDocumentRead` fires at most once per loaded file. Both banner labels are part of the `Labels` interface and are translated automatically when `language="it"` is set.
406
-
407
- ---
408
-
409
- ## Localisation
410
-
411
- `PDFViewer` ships with English (default) and Italian label sets. Switch via the `language` prop:
412
-
413
- ```tsx
414
- <PDFViewer language="it" ... />
415
- ```
416
-
417
- Both label objects are exported if you need to inspect them:
418
-
419
- ```tsx
420
- import { italianLabels, englishLabels } from "pdf-tsx";
421
- ```
422
-
423
- The `Labels` interface (also exported) describes every string key. The built-in toolbar overflow labels are resolved automatically from the active language — no extra configuration needed.
424
-
425
- ---
426
-
427
- ## Full example
428
-
429
- ```tsx
430
- import { useState, Suspense, lazy } from "react";
431
- import {
432
- PDFSidebar,
433
- PDFSidebarToggle,
434
- PDFToolbar,
435
- PDFToolbarItem,
436
- PDFToolbarSeparator,
437
- PDFThumbnails,
438
- PDFOutline,
439
- PDFSearch,
440
- PDFZoomControls,
441
- PDFPageFit,
442
- PDFRotationControl,
443
- PDFNavigation,
444
- PDFDownloadButton,
445
- PDFChangeFile,
446
- PDFThemeToggle,
447
- PDFPrintButton,
448
- PDFSignatures,
449
- PDFSignaturesIcon,
450
- PDFAnnotations,
451
- PDFAnnotationsIcon,
452
- PDFAttachments,
453
- PDFAttachmentsIcon,
454
- } from "pdf-tsx";
455
- import {
456
- LuLayoutGrid,
457
- LuBookOpen,
458
- LuSignature,
459
- LuStickyNote,
460
- LuPaperclip,
461
- LuSearch,
462
- LuChevronUp,
463
- LuChevronDown,
464
- LuX,
465
- LuZoomIn,
466
- LuZoomOut,
467
- LuArrowLeftRight,
468
- LuScan,
469
- LuRotateCw,
470
- LuChevronLeft,
471
- LuChevronRight,
472
- LuDownload,
473
- LuFolderPlus,
474
- LuPrinter,
475
- LuSun,
476
- LuMoon,
477
- LuPanelLeftClose,
478
- LuPanelLeftOpen,
479
- LuEllipsis,
480
- } from "react-icons/lu";
481
- import workerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url";
482
-
483
- const PDFViewer = lazy(() =>
484
- import("pdf-tsx").then((m) => ({ default: m.PDFViewer })),
485
- );
486
-
487
- function App() {
488
- const [file, setFile] = useState<File | null>(null);
489
-
490
- if (!file) {
491
- return (
492
- <input
493
- type="file"
494
- accept=".pdf"
495
- onChange={(e) => setFile(e.target.files![0])}
496
- />
497
- );
498
- }
499
-
500
- return (
501
- <Suspense fallback={<div>Loading...</div>}>
502
- <PDFViewer
503
- file={file}
504
- workerSrc={workerUrl}
505
- defaultFit="width"
506
- onChangeFile={setFile}
507
- sidebar={
508
- <PDFSidebar
509
- panels={[
510
- {
511
- icon: <LuLayoutGrid size={18} />,
512
- label: "Thumbnails",
513
- content: <PDFThumbnails />,
514
- },
515
- {
516
- icon: <LuBookOpen size={18} />,
517
- label: "Outline",
518
- content: <PDFOutline />,
519
- },
520
- {
521
- icon: <PDFSignaturesIcon icon={<LuSignature size={17} />} />,
522
- label: "Signatures",
523
- content: <PDFSignatures />,
524
- },
525
- {
526
- icon: <PDFAnnotationsIcon icon={<LuStickyNote size={17} />} />,
527
- label: "Annotations",
528
- content: <PDFAnnotations />,
529
- },
530
- {
531
- icon: <PDFAttachmentsIcon icon={<LuPaperclip size={17} />} />,
532
- label: "Attachments",
533
- content: <PDFAttachments />,
534
- },
535
- ]}
536
- />
537
- }
538
- toolbar={
539
- <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
540
- <PDFSidebarToggle
541
- collapseIcon={<LuPanelLeftClose size={16} />}
542
- expandIcon={<LuPanelLeftOpen size={16} />}
543
- />
544
- <PDFToolbarSeparator />
545
- <PDFSearch
546
- searchIcon={<LuSearch size={14} />}
547
- prevIcon={<LuChevronUp size={14} />}
548
- nextIcon={<LuChevronDown size={14} />}
549
- clearIcon={<LuX size={14} />}
550
- />
551
- <PDFToolbarSeparator />
552
- <PDFZoomControls
553
- zoomOutIcon={<LuZoomOut size={16} />}
554
- zoomInIcon={<LuZoomIn size={16} />}
555
- />
556
- <PDFRotationControl icon={<LuRotateCw size={16} />} />
557
- <PDFPageFit
558
- fitWidthIcon={<LuArrowLeftRight size={16} />}
559
- fitPageIcon={<LuScan size={16} />}
560
- />
561
- <PDFToolbarSeparator />
562
- <PDFNavigation
563
- prevIcon={<LuChevronLeft size={16} />}
564
- nextIcon={<LuChevronRight size={16} />}
565
- />
566
- <PDFToolbarSeparator />
567
- <PDFThemeToggle
568
- lightIcon={<LuSun size={16} />}
569
- darkIcon={<LuMoon size={16} />}
570
- />
571
- <PDFPrintButton icon={<LuPrinter size={16} />} />
572
- <PDFChangeFile icon={<LuFolderPlus size={16} />} />
573
- <PDFDownloadButton icon={<LuDownload size={15} />} />
574
- {/* custom button with overflow label */}
575
- <PDFToolbarItem label="Share">
576
- <button onClick={() => {}}>Share</button>
577
- </PDFToolbarItem>
578
- </PDFToolbar>
579
- }
580
- />
581
- </Suspense>
582
- );
583
- }
584
- ```
585
-
586
- ---
587
-
588
- ## Keyboard shortcuts
589
-
590
- When focus is not on an input or textarea, the following keys are active:
591
-
592
- | Key | Action |
593
- | -------------------------- | ------------- |
594
- | `ArrowLeft` / `ArrowUp` | Previous page |
595
- | `ArrowRight` / `ArrowDown` | Next page |
596
- | `r` / `R` | Rotate +90° |
597
-
598
- ## Gesture & zoom controls
599
-
600
- Zoom can be controlled via toolbar buttons (`PDFZoomControls`), keyboard/trackpad gestures, and touch gestures — no configuration required.
601
-
602
- | Input | Action |
603
- | ------------------------ | ------------------------------------- |
604
- | `Ctrl + scroll` (mouse) | Zoom in / out |
605
- | `Cmd + scroll` (macOS) | Zoom in / out |
606
- | Trackpad pinch (desktop) | Zoom in / out |
607
- | Two-finger pinch (touch) | Zoom in / out |
608
- | Double tap (touch) | Toggle between 2× zoom and 1× (reset) |
609
-
610
- Zoom is clamped to the range `[0.2, 3.0]`. Any manual gesture clears the active `fitMode` (`"width"` / `"page"`).
611
-
612
- ---
613
-
614
- ## Performance
615
-
616
- `pdf-tsx` uses **virtual rendering**: only the pages near the current view are rendered at any one time. The default window is the current page ±2. Pages with an active highlight or search match are always included regardless of position.
617
-
618
- This means:
619
-
620
- - Initial load renders at most 5 pages, regardless of document length
621
- - Memory and CPU usage stay flat as the user scrolls through a large document
622
- - Pages that have already been rendered are kept in the DOM as the user scrolls, avoiding redundant re-renders
623
-
624
- Render cascades are minimised via manual memoization: the context value is wrapped in `useMemo` so a zoom or page change does not re-render unrelated consumers, and inner sub-components (`AnnotationCard`, `SignatureCard`, `OutlineItem`) are wrapped in `React.memo`.
625
-
626
- No configuration is needed — this is the default behaviour.
627
-
628
- ---
629
-
630
- ## License
631
-
632
- **PolyForm Noncommercial License 1.0.0** © Riccardo Grossano — see [LICENSE](./LICENSE) for full terms.
633
-
634
- Free for noncommercial use (personal, research, education, non-profit). **Commercial use requires a separate commercial license** — _coming soon_.
1
+ # pdf-tsx
2
+
3
+ A fully-featured, composable PDF viewer component for React.
4
+
5
+ **🔗 [Live demo & docs → pdf-tsx.netlify.app](https://pdf-tsx.netlify.app/)**
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install pdf-tsx
11
+ ```
12
+
13
+ ### Peer dependencies
14
+
15
+ ```bash
16
+ npm install react react-dom pdfjs-dist
17
+ ```
18
+
19
+ ---
20
+
21
+ ## CSS
22
+
23
+ Import the stylesheet once in your app entry point:
24
+
25
+ ```ts
26
+ import "pdf-tsx/dist/es/pdf-tsx.css";
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Quick start
32
+
33
+ `PDFViewer` requires a `workerSrc` prop — the URL of the pdfjs worker script. The easiest way is to serve it from the `pdfjs-dist` package directly:
34
+
35
+ ```tsx
36
+ import { PDFViewer } from "pdf-tsx";
37
+ import workerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url";
38
+
39
+ function App() {
40
+ const [file, setFile] = useState<File | null>(null);
41
+
42
+ if (!file)
43
+ return (
44
+ <input
45
+ type="file"
46
+ accept=".pdf"
47
+ onChange={(e) => setFile(e.target.files![0])}
48
+ />
49
+ );
50
+
51
+ return <PDFViewer file={file} workerSrc={workerUrl} />;
52
+ }
53
+ ```
54
+
55
+ > The `?url` suffix is a Vite feature that returns the asset URL as a string. If you use a different bundler, import the worker URL accordingly or pass a CDN URL:
56
+ >
57
+ > ```ts
58
+ > workerSrc = "https://unpkg.com/pdfjs-dist@5/build/pdf.worker.min.mjs";
59
+ > ```
60
+
61
+ PDFs containing JPEG2000 (JPX) or JBIG2-encoded images (common in scanned documents) render correctly out of the box — the required decoders are bundled with the library, no extra setup needed.
62
+
63
+ ---
64
+
65
+ ## Icons
66
+
67
+ Feature components do not bundle any icon library. Each component that shows an icon requires you to pass it explicitly as a prop — use any icon library or custom SVG you prefer.
68
+
69
+ ```tsx
70
+ import { LuZoomIn, LuZoomOut } from "react-icons/lu";
71
+
72
+ <PDFZoomControls
73
+ zoomInIcon={<LuZoomIn size={16} />}
74
+ zoomOutIcon={<LuZoomOut size={16} />}
75
+ />;
76
+ ```
77
+
78
+ ---
79
+
80
+ ## PDFViewer
81
+
82
+ The main component. Must wrap all feature components.
83
+
84
+ ```tsx
85
+ <PDFViewer
86
+ file={file}
87
+ workerSrc={workerUrl}
88
+ sidebar={<PDFSidebar panels={[...]} />}
89
+ toolbar={<>...</>}
90
+ onChangeFile={(newFile) => setFile(newFile)}
91
+ />
92
+ ```
93
+
94
+ | Prop | Type | Required | Description |
95
+ | ---------------- | --------------------------------------------------- | -------- | ------------------------------------------------------- |
96
+ | `file` | `File` | yes | The PDF file object to display |
97
+ | `workerSrc` | `string` | yes | URL of the pdfjs worker script (see Quick start) |
98
+ | `sidebar` | `ReactNode` | no | Content rendered on the left side |
99
+ | `toolbar` | `ReactNode` | no | Content rendered in the top toolbar |
100
+ | `onChangeFile` | `(file: File) => void` | no | Called when the user opens a different file |
101
+ | `defaultTheme` | `"light" \| "dark"` | no | Initial theme mode (default: `"light"`) |
102
+ | `themeOverrides` | `{ light?: Partial<Theme>; dark?: Partial<Theme> }` | no | Override individual theme tokens (see Theming) |
103
+ | `defaultFit` | `"width" \| "page"` | no | Initial zoom mode when a PDF is loaded |
104
+ | `defaultLayout` | `"continuous" \| "single" \| "spread"` | no | Initial page layout (default: `"continuous"`, see Page layouts) |
105
+ | `language` | `"it" \| "en"` | no | UI language (default: `"en"`) |
106
+ | `className` | `string` | no | CSS class applied to the root layout element |
107
+ | `style` | `React.CSSProperties` | no | Inline style applied to the root layout element |
108
+ | `requireRead` | `boolean` | no | Show a mandatory-reading banner (see Mandatory reading) |
109
+ | `onDocumentRead` | `() => void` | no | Called once when the user reaches the last page |
110
+
111
+ ---
112
+
113
+ ## Theming
114
+
115
+ `PDFViewer` ships with built-in light and dark themes. The default is `"light"`. Use `defaultTheme` to set the initial mode and `themeOverrides` to override individual tokens without replacing the whole theme.
116
+
117
+ ```tsx
118
+ import { PDFViewer, darkTheme } from 'pdf-tsx'
119
+
120
+ <PDFViewer
121
+ themeOverrides={{
122
+ dark: { accent: '#e11d48', accentHover: '#be123c', accentDark: '#9f1239' },
123
+ light: { accent: '#e11d48', accentHover: '#be123c', accentDark: '#9f1239' },
124
+ }}
125
+ ...
126
+ />
127
+ ```
128
+
129
+ Both `darkTheme` and `lightTheme` are exported so you can spread them as a base:
130
+
131
+ ```tsx
132
+ import { darkTheme } from 'pdf-tsx'
133
+
134
+ themeOverrides={{ dark: { ...darkTheme, bg: '#0f0f0f' } }}
135
+ ```
136
+
137
+ **Theme tokens** (`Theme` interface):
138
+
139
+ | Token | CSS variable | Description |
140
+ | ------------------ | ------------------------- | ------------------------------------------------------------- |
141
+ | `bg` | `--pdf-bg` | Main background |
142
+ | `surface` | `--pdf-surface` | Card / panel surface |
143
+ | `surfaceHover` | `--pdf-surface-hover` | Surface hover state |
144
+ | `border` | `--pdf-border` | Default border color |
145
+ | `borderLight` | `--pdf-border-light` | Subtle border color |
146
+ | `scrollArea` | `--pdf-scroll-area` | Scroll container background |
147
+ | `textPrimary` | `--pdf-text-primary` | Primary text |
148
+ | `textSecondary` | `--pdf-text-secondary` | Secondary text |
149
+ | `textMuted` | `--pdf-text-muted` | Muted / disabled text |
150
+ | `textPlaceholder` | `--pdf-text-placeholder` | Input placeholder text |
151
+ | `accent` | `--pdf-accent` | Accent / interactive color |
152
+ | `accentHover` | `--pdf-accent-hover` | Accent hover state |
153
+ | `accentDark` | `--pdf-accent-dark` | Accent dark variant |
154
+ | `accentLight` | `--pdf-accent-light` | Accent light variant |
155
+ | `accentSubtle` | `--pdf-accent-subtle` | Accent with low opacity (backgrounds) |
156
+ | `panelBg` | `--pdf-panel-bg` | Sidebar panel background |
157
+ | `panelBorder` | `--pdf-panel-border` | Sidebar panel border |
158
+ | `panelHeaderBg` | `--pdf-panel-header-bg` | Sidebar panel header background |
159
+ | `shadowPage` | `--pdf-shadow-page` | Drop shadow color for PDF pages |
160
+ | `badgeBg` | `--pdf-badge-bg` | Count badge background (signatures, annotations, attachments) |
161
+ | `toolbarSeparator` | `--pdf-toolbar-separator` | `PDFToolbarSeparator` line color |
162
+ | `fontFamily` | `--pdf-font-family` | Font family for all viewer UI text |
163
+
164
+ ---
165
+
166
+ ## PDFSidebar
167
+
168
+ A collapsible icon sidebar. Each panel has an icon button — clicking it toggles the panel open/closed. When collapsed via `PDFSidebarToggle`, the entire sidebar is hidden (`null`).
169
+
170
+ ```tsx
171
+ import { PDFSidebar } from "pdf-tsx";
172
+ import { LuLayoutGrid, LuBookOpen } from "react-icons/lu";
173
+
174
+ <PDFSidebar
175
+ panels={[
176
+ {
177
+ icon: <LuLayoutGrid size={18} />,
178
+ label: "Thumbnails",
179
+ content: <PDFThumbnails />,
180
+ },
181
+ {
182
+ icon: <LuBookOpen size={18} />,
183
+ label: "Outline",
184
+ content: <PDFOutline />,
185
+ },
186
+ ]}
187
+ />;
188
+ ```
189
+
190
+ | Prop | Type | Description |
191
+ | -------- | ---------------- | -------------------------- |
192
+ | `panels` | `SidebarPanel[]` | Array of panels to display |
193
+
194
+ **SidebarPanel**
195
+
196
+ | Field | Type | Description |
197
+ | --------- | ----------- | -------------------------------------- |
198
+ | `icon` | `ReactNode` | Icon shown in the sidebar button bar |
199
+ | `label` | `string` | Panel title shown in the header |
200
+ | `content` | `ReactNode` | Content rendered inside the open panel |
201
+
202
+ ### Sidebar hide/show pattern
203
+
204
+ Use `PDFSidebarToggle` in the toolbar alongside `PDFSidebar`. When the sidebar is open, the collapse button lives inside the sidebar. When collapsed, the expand button appears in the toolbar.
205
+
206
+ ```tsx
207
+ import { PDFSidebar, PDFSidebarToggle, PDFToolbar } from 'pdf-tsx'
208
+ import { LuPanelLeftClose, LuPanelLeftOpen, LuEllipsis } from 'react-icons/lu'
209
+
210
+ <PDFViewer
211
+ sidebar={
212
+ <PDFSidebar panels={[...]} />
213
+ }
214
+ toolbar={
215
+ <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
216
+ <PDFSidebarToggle
217
+ collapseIcon={<LuPanelLeftClose size={16} />}
218
+ expandIcon={<LuPanelLeftOpen size={16} />}
219
+ />
220
+ {/* other toolbar items */}
221
+ </PDFToolbar>
222
+ }
223
+ />
224
+ ```
225
+
226
+ ---
227
+
228
+ ## PDFToolbar (responsive toolbar)
229
+
230
+ `PDFToolbar` wraps your toolbar items and automatically collapses those that don't fit the available width into an overflow `···` dropdown. It uses `ResizeObserver` to react to container size changes — no configuration required.
231
+
232
+ ```tsx
233
+ import { PDFToolbar } from "pdf-tsx";
234
+ import {
235
+ LuEllipsis,
236
+ LuPanelLeftClose,
237
+ LuPanelLeftOpen,
238
+ LuSearch,
239
+ LuChevronUp,
240
+ LuChevronDown,
241
+ LuX,
242
+ LuZoomOut,
243
+ LuZoomIn,
244
+ LuChevronLeft,
245
+ LuChevronRight,
246
+ } from "react-icons/lu";
247
+
248
+ <PDFViewer
249
+ toolbar={
250
+ <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
251
+ <PDFSidebarToggle
252
+ collapseIcon={<LuPanelLeftClose size={16} />}
253
+ expandIcon={<LuPanelLeftOpen size={16} />}
254
+ />
255
+ <PDFSearch
256
+ searchIcon={<LuSearch size={14} />}
257
+ prevIcon={<LuChevronUp size={14} />}
258
+ nextIcon={<LuChevronDown size={14} />}
259
+ clearIcon={<LuX size={14} />}
260
+ />
261
+ <PDFZoomControls
262
+ zoomOutIcon={<LuZoomOut size={16} />}
263
+ zoomInIcon={<LuZoomIn size={16} />}
264
+ />
265
+ <PDFNavigation
266
+ prevIcon={<LuChevronLeft size={16} />}
267
+ nextIcon={<LuChevronRight size={16} />}
268
+ />
269
+ {/* … more items */}
270
+ </PDFToolbar>
271
+ }
272
+ />;
273
+ ```
274
+
275
+ | Prop | Type | Description |
276
+ | -------------- | ----------- | ----------------------------------------------------- |
277
+ | `children` | `ReactNode` | Toolbar items in priority order (left = highest) |
278
+ | `overflowIcon` | `ReactNode` | Icon for the `···` overflow button (default: `"···"`) |
279
+
280
+ **Dropdown labels** — all built-in feature components carry a `toolbarLabelKey` static property. When an item is moved to the overflow dropdown, `PDFToolbar` resolves the label from the active language automatically. No extra configuration needed.
281
+
282
+ **Custom items** — wrap any custom component in `PDFToolbarItem` to give it a dropdown label:
283
+
284
+ ```tsx
285
+ import { PDFToolbarItem } from "pdf-tsx";
286
+
287
+ <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
288
+ <PDFToolbarItem label="Custom">
289
+ <MyCustomButton />
290
+ </PDFToolbarItem>
291
+ </PDFToolbar>;
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Feature components
297
+
298
+ All feature components must be rendered inside `<PDFViewer>`. They read and write state via `usePDFViewer()` internally.
299
+
300
+ | Component | Icon props | Description |
301
+ | --------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
302
+ | `PDFToolbar` | `overflowIcon` | Responsive toolbar collapses items into `···` dropdown when space runs out |
303
+ | `PDFToolbarItem` | — | Wrapper for custom toolbar items to add a dropdown label |
304
+ | `PDFToolbarSeparator` | — | Vertical divider between toolbar groups — hidden automatically in the overflow dropdown |
305
+ | `PDFThumbnails` | | Page thumbnail strip for quick navigation |
306
+ | `PDFOutline` | — | Document outline / bookmarks tree |
307
+ | `PDFAnnotations` | | Display PDF annotations |
308
+ | `PDFAnnotationsIcon` | `icon` | Sidebar icon with annotation count badge |
309
+ | `PDFSignatures` | | Display PDF digital signatures |
310
+ | `PDFSignaturesIcon` | `icon` | Sidebar icon with signature count badge |
311
+ | `PDFAttachments` | | List embedded file attachments with download buttons |
312
+ | `PDFAttachmentsIcon` | `icon` | Sidebar icon with attachment count badge |
313
+ | `PDFSearch` | `searchIcon` `prevIcon` `nextIcon` `clearIcon` | Full-text search with match highlighting. Accepts optional `defaultSearch` to pre-fill and auto-navigate |
314
+ | `PDFZoomControls` | `zoomInIcon` `zoomOutIcon` | Zoom in / zoom out buttons with percentage display |
315
+ | `PDFPageFit` | `fitWidthIcon` `fitPageIcon` | Fit-to-width and fit-to-page buttons |
316
+ | `PDFViewModeToggle` | `continuousIcon` `singleIcon` `spreadIcon` | Dropdown to switch page layout (continuous / single / spread) — see Page layouts |
317
+ | `PDFRotationControl` | `icon` | Rotate page clockwise |
318
+ | `PDFNavigation` | `prevIcon` `nextIcon` | Previous / next page buttons with current page input |
319
+ | `PDFDownloadButton` | `icon` | Download the current PDF file |
320
+ | `PDFChangeFile` | `icon` | Button to open a new PDF file |
321
+ | `PDFThemeToggle` | `lightIcon` `darkIcon` | Toggle between dark and light mode |
322
+ | `PDFPrintButton` | `icon` | Print the PDF using the browser's native print dialog |
323
+ | `PDFSidebarToggle` | `collapseIcon` `expandIcon` | Toolbar button to expand the sidebar — visible only when sidebar is hidden (see Sidebar hide/show pattern) |
324
+
325
+ ### PDFSearch
326
+
327
+ | Prop | Type | Required | Description |
328
+ | --------------- | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
329
+ | `searchIcon` | `ReactNode` | yes | Icon shown inside the search input |
330
+ | `prevIcon` | `ReactNode` | yes | Icon for the "previous match" button |
331
+ | `nextIcon` | `ReactNode` | yes | Icon for the "next match" button |
332
+ | `clearIcon` | `ReactNode` | yes | Icon for the "clear search" button |
333
+ | `initialSearch` | `string` | no | Pre-fills the search input and automatically navigates to the first match once the PDF is loaded. Navigation fires once — subsequent manual edits are unaffected. |
334
+
335
+ ```tsx
336
+ <PDFSearch
337
+ initialSearch="invoice"
338
+ searchIcon={<LuSearch size={14} />}
339
+ prevIcon={<LuChevronUp size={14} />}
340
+ nextIcon={<LuChevronDown size={14} />}
341
+ clearIcon={<LuX size={14} />}
342
+ />
343
+ ```
344
+
345
+ ---
346
+
347
+ ### Count badges
348
+
349
+ `PDFSignaturesIcon`, `PDFAnnotationsIcon`, and `PDFAttachmentsIcon` are icon wrapper components intended for use as the `icon` prop in `PDFSidebar` panels. They render the provided icon with a small count badge in the top-right corner, automatically populated from the loaded PDF.
350
+
351
+ ```tsx
352
+ import {
353
+ PDFSidebar,
354
+ PDFSignatures,
355
+ PDFSignaturesIcon,
356
+ PDFAnnotations,
357
+ PDFAnnotationsIcon,
358
+ PDFAttachments,
359
+ PDFAttachmentsIcon,
360
+ } from "pdf-tsx";
361
+ import { LuSignature, LuStickyNote, LuPaperclip } from "react-icons/lu";
362
+
363
+ <PDFSidebar
364
+ panels={[
365
+ {
366
+ icon: <PDFSignaturesIcon icon={<LuSignature size={17} />} />,
367
+ label: "Signatures",
368
+ content: <PDFSignatures />,
369
+ },
370
+ {
371
+ icon: <PDFAnnotationsIcon icon={<LuStickyNote size={17} />} />,
372
+ label: "Annotations",
373
+ content: <PDFAnnotations />,
374
+ },
375
+ {
376
+ icon: <PDFAttachmentsIcon icon={<LuPaperclip size={17} />} />,
377
+ label: "Attachments",
378
+ content: <PDFAttachments />,
379
+ },
380
+ ]}
381
+ />;
382
+ ```
383
+
384
+ The badge uses `var(--pdf-accent)` and respects `themeOverrides`.
385
+
386
+ ---
387
+
388
+ ## Page layouts
389
+
390
+ `PDFViewer` arranges pages in one of three layouts, controlled by the `defaultLayout` prop and switchable at runtime with `PDFViewModeToggle`:
391
+
392
+ | Layout | Behaviour |
393
+ | ------------ | ----------------------------------------------------------------- |
394
+ | `continuous` | Vertical scroll, one page after another (default) |
395
+ | `single` | One page at a time — navigate with `PDFNavigation` / arrow keys |
396
+ | `spread` | Pages flow side by side, as many per row as fit the viewport width |
397
+
398
+ `PDFViewModeToggle` renders a single toolbar button (showing the active layout's icon plus a chevron) that opens a dropdown to pick the layout:
399
+
400
+ ```tsx
401
+ import { PDFViewModeToggle } from "pdf-tsx";
402
+ import { LuScrollText, LuFile, LuBookOpenText } from "react-icons/lu";
403
+
404
+ <PDFViewModeToggle
405
+ continuousIcon={<LuScrollText size={16} />}
406
+ singleIcon={<LuFile size={16} />}
407
+ spreadIcon={<LuBookOpenText size={16} />}
408
+ />;
409
+ ```
410
+
411
+ | Prop | Type | Required | Description |
412
+ | ---------------- | ----------- | -------- | ------------------------------------ |
413
+ | `continuousIcon` | `ReactNode` | yes | Icon for the continuous layout option |
414
+ | `singleIcon` | `ReactNode` | yes | Icon for the single-page layout option |
415
+ | `spreadIcon` | `ReactNode` | yes | Icon for the spread layout option |
416
+
417
+ ---
418
+
419
+ ## Mandatory reading
420
+
421
+ Use `requireRead` when the user must scroll through the entire document before proceeding (e.g. terms and conditions, consent forms).
422
+
423
+ ```tsx
424
+ const [canProceed, setCanProceed] = useState(false)
425
+
426
+ <PDFViewer
427
+ requireRead
428
+ onDocumentRead={() => setCanProceed(true)}
429
+ ...
430
+ />
431
+
432
+ <button disabled={!canProceed}>Accetto</button>
433
+ ```
434
+
435
+ `PDFViewer` renders a sticky banner at the bottom of the scroll area:
436
+
437
+ - **Pending** — accent color, arrow-down icon, label `requireReadPending` ("Scroll to the end to continue")
438
+ - **Confirmed** — green (`#16a34a`), checkmark icon, label `requireReadDone` ("Document read") — the banner stays visible after confirmation
439
+
440
+ `onDocumentRead` fires at most once per loaded file. Both banner labels are part of the `Labels` interface and are translated automatically when `language="it"` is set.
441
+
442
+ ---
443
+
444
+ ## Localisation
445
+
446
+ `PDFViewer` ships with English (default) and Italian label sets. Switch via the `language` prop:
447
+
448
+ ```tsx
449
+ <PDFViewer language="it" ... />
450
+ ```
451
+
452
+ Both label objects are exported if you need to inspect them:
453
+
454
+ ```tsx
455
+ import { italianLabels, englishLabels } from "pdf-tsx";
456
+ ```
457
+
458
+ The `Labels` interface (also exported) describes every string key. The built-in toolbar overflow labels are resolved automatically from the active language — no extra configuration needed.
459
+
460
+ ---
461
+
462
+ ## Full example
463
+
464
+ ```tsx
465
+ import { useState, Suspense, lazy } from "react";
466
+ import {
467
+ PDFSidebar,
468
+ PDFSidebarToggle,
469
+ PDFToolbar,
470
+ PDFToolbarItem,
471
+ PDFToolbarSeparator,
472
+ PDFThumbnails,
473
+ PDFOutline,
474
+ PDFSearch,
475
+ PDFZoomControls,
476
+ PDFPageFit,
477
+ PDFViewModeToggle,
478
+ PDFRotationControl,
479
+ PDFNavigation,
480
+ PDFDownloadButton,
481
+ PDFChangeFile,
482
+ PDFThemeToggle,
483
+ PDFPrintButton,
484
+ PDFSignatures,
485
+ PDFSignaturesIcon,
486
+ PDFAnnotations,
487
+ PDFAnnotationsIcon,
488
+ PDFAttachments,
489
+ PDFAttachmentsIcon,
490
+ } from "pdf-tsx";
491
+ import {
492
+ LuLayoutGrid,
493
+ LuBookOpen,
494
+ LuSignature,
495
+ LuStickyNote,
496
+ LuPaperclip,
497
+ LuSearch,
498
+ LuChevronUp,
499
+ LuChevronDown,
500
+ LuX,
501
+ LuZoomIn,
502
+ LuZoomOut,
503
+ LuArrowLeftRight,
504
+ LuScan,
505
+ LuScrollText,
506
+ LuFile,
507
+ LuBookOpenText,
508
+ LuRotateCw,
509
+ LuChevronLeft,
510
+ LuChevronRight,
511
+ LuDownload,
512
+ LuFolderPlus,
513
+ LuPrinter,
514
+ LuSun,
515
+ LuMoon,
516
+ LuPanelLeftClose,
517
+ LuPanelLeftOpen,
518
+ LuEllipsis,
519
+ } from "react-icons/lu";
520
+ import workerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url";
521
+
522
+ const PDFViewer = lazy(() =>
523
+ import("pdf-tsx").then((m) => ({ default: m.PDFViewer })),
524
+ );
525
+
526
+ function App() {
527
+ const [file, setFile] = useState<File | null>(null);
528
+
529
+ if (!file) {
530
+ return (
531
+ <input
532
+ type="file"
533
+ accept=".pdf"
534
+ onChange={(e) => setFile(e.target.files![0])}
535
+ />
536
+ );
537
+ }
538
+
539
+ return (
540
+ <Suspense fallback={<div>Loading...</div>}>
541
+ <PDFViewer
542
+ file={file}
543
+ workerSrc={workerUrl}
544
+ defaultFit="width"
545
+ onChangeFile={setFile}
546
+ sidebar={
547
+ <PDFSidebar
548
+ panels={[
549
+ {
550
+ icon: <LuLayoutGrid size={18} />,
551
+ label: "Thumbnails",
552
+ content: <PDFThumbnails />,
553
+ },
554
+ {
555
+ icon: <LuBookOpen size={18} />,
556
+ label: "Outline",
557
+ content: <PDFOutline />,
558
+ },
559
+ {
560
+ icon: <PDFSignaturesIcon icon={<LuSignature size={17} />} />,
561
+ label: "Signatures",
562
+ content: <PDFSignatures />,
563
+ },
564
+ {
565
+ icon: <PDFAnnotationsIcon icon={<LuStickyNote size={17} />} />,
566
+ label: "Annotations",
567
+ content: <PDFAnnotations />,
568
+ },
569
+ {
570
+ icon: <PDFAttachmentsIcon icon={<LuPaperclip size={17} />} />,
571
+ label: "Attachments",
572
+ content: <PDFAttachments />,
573
+ },
574
+ ]}
575
+ />
576
+ }
577
+ toolbar={
578
+ <PDFToolbar overflowIcon={<LuEllipsis size={16} />}>
579
+ <PDFSidebarToggle
580
+ collapseIcon={<LuPanelLeftClose size={16} />}
581
+ expandIcon={<LuPanelLeftOpen size={16} />}
582
+ />
583
+ <PDFToolbarSeparator />
584
+ <PDFSearch
585
+ searchIcon={<LuSearch size={14} />}
586
+ prevIcon={<LuChevronUp size={14} />}
587
+ nextIcon={<LuChevronDown size={14} />}
588
+ clearIcon={<LuX size={14} />}
589
+ />
590
+ <PDFToolbarSeparator />
591
+ <PDFZoomControls
592
+ zoomOutIcon={<LuZoomOut size={16} />}
593
+ zoomInIcon={<LuZoomIn size={16} />}
594
+ />
595
+ <PDFRotationControl icon={<LuRotateCw size={16} />} />
596
+ <PDFPageFit
597
+ fitWidthIcon={<LuArrowLeftRight size={16} />}
598
+ fitPageIcon={<LuScan size={16} />}
599
+ />
600
+ <PDFViewModeToggle
601
+ continuousIcon={<LuScrollText size={16} />}
602
+ singleIcon={<LuFile size={16} />}
603
+ spreadIcon={<LuBookOpenText size={16} />}
604
+ />
605
+ <PDFToolbarSeparator />
606
+ <PDFNavigation
607
+ prevIcon={<LuChevronLeft size={16} />}
608
+ nextIcon={<LuChevronRight size={16} />}
609
+ />
610
+ <PDFToolbarSeparator />
611
+ <PDFThemeToggle
612
+ lightIcon={<LuSun size={16} />}
613
+ darkIcon={<LuMoon size={16} />}
614
+ />
615
+ <PDFPrintButton icon={<LuPrinter size={16} />} />
616
+ <PDFChangeFile icon={<LuFolderPlus size={16} />} />
617
+ <PDFDownloadButton icon={<LuDownload size={15} />} />
618
+ {/* custom button with overflow label */}
619
+ <PDFToolbarItem label="Share">
620
+ <button onClick={() => {}}>Share</button>
621
+ </PDFToolbarItem>
622
+ </PDFToolbar>
623
+ }
624
+ />
625
+ </Suspense>
626
+ );
627
+ }
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Keyboard shortcuts
633
+
634
+ When focus is not on an input or textarea, the following keys are active:
635
+
636
+ | Key | Action |
637
+ | -------------------------- | ------------- |
638
+ | `ArrowLeft` / `ArrowUp` | Previous page |
639
+ | `ArrowRight` / `ArrowDown` | Next page |
640
+ | `r` / `R` | Rotate +90° |
641
+
642
+ ## Gesture & zoom controls
643
+
644
+ Zoom can be controlled via toolbar buttons (`PDFZoomControls`), keyboard/trackpad gestures, and touch gestures — no configuration required.
645
+
646
+ | Input | Action |
647
+ | ------------------------ | ------------------------------------- |
648
+ | `Ctrl + scroll` (mouse) | Zoom in / out |
649
+ | `Cmd + scroll` (macOS) | Zoom in / out |
650
+ | Trackpad pinch (desktop) | Zoom in / out |
651
+ | Two-finger pinch (touch) | Zoom in / out |
652
+ | Double tap (touch) | Toggle between 2× zoom and 1× (reset) |
653
+
654
+ Zoom is clamped to the range `[0.2, 3.0]`. Any manual gesture clears the active `fitMode` (`"width"` / `"page"`).
655
+
656
+ ---
657
+
658
+ ## Performance
659
+
660
+ `pdf-tsx` uses **virtual rendering**: only the pages that are actually in (or near) the viewport are rendered at any one time. A page is rendered when it intersects the scroll container (via `IntersectionObserver`), plus the current page ±2 as a buffer. Pages with an active highlight or search match are always included regardless of position. This holds across all layouts — including `spread` and when zoomed far out, where many pages are on screen at once.
661
+
662
+ This means:
663
+
664
+ - A long document only renders the handful of pages on screen, regardless of total length
665
+ - Memory and CPU usage stay flat as the user scrolls through a large document
666
+ - Pages that have already been rendered are kept in the DOM as the user scrolls, avoiding redundant re-renders
667
+
668
+ Render cascades are minimised via manual memoization: the context value is wrapped in `useMemo` so a zoom or page change does not re-render unrelated consumers, and inner sub-components (`AnnotationCard`, `SignatureCard`, `OutlineItem`) are wrapped in `React.memo`.
669
+
670
+ No configuration is needed — this is the default behaviour.
671
+
672
+ ---
673
+
674
+ ## License
675
+
676
+ **PolyForm Noncommercial License 1.0.0** © Riccardo Grossano — see [LICENSE](./LICENSE) for full terms.
677
+
678
+ Free for noncommercial use (personal, research, education, non-profit). **Commercial use requires a separate commercial license** — _coming soon_.