@kopjra/pdf-sentinel
v1.8.0
Published
A React and Preact component library for embedding and interacting with PDF documents using EmbedPDF.
Maintainers
Readme
pdf-sentinel Documentation
A powerful React/Preact library for viewing and interacting with PDF forms. Built on top of embed-pdf-viewer, pdf-sentinel provides a complete solution for rendering PDF documents with form fields, custom toolbars, internationalization, and signature management.
Table of Contents
Installation
npm install @kopjra/pdf-sentinelQuick Start
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
import "@kopjra/pdf-sentinel/style.css";
function MyPdfViewer() {
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
pdf: "/path/to/document.pdf",
name: "My Document",
});
return (
<div style={{ height: "100vh" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
);
}Features
Core Functionality
- 📄 PDF Viewing - Render PDF documents with zoom, scroll, and navigation
- 📋 Form Fields - Support for text inputs, checkboxes, radio buttons, and signature fields
- ✍️ Form Validation - Built-in validation with error highlighting and auto-scroll to errors
- 🎨 Toolbar - Customizable toolbar with zoom, page navigation, and download
- 🌍 Internationalization - Multi-language support with custom translation overrides
- 🔧 Custom Buttons - Extend the toolbar with custom buttons in left/center/right areas
- 📝 Signatures - Native conditional signature support with automatic group coloring
- 🎯 Responsive - Adaptive layout that responds to zoom and scroll events
Toolbar Features
- Zoom in/out and fit-to-width
- Page navigation and page count display
- PDF download capability
- Custom button support with spacers
- Multi-language tooltips and labels
Form Features
- Text field rendering with readonly support
- Checkbox field rendering with custom styling
- Radio button field rendering with custom styling
- Signature field rendering with conditional group coloring
- Form state management
- Field validation with error styling
- Initial values and readonly properties
API Reference
usePdfSentinel Hook
The main hook for initializing and controlling the PDF viewer.
Signature
function usePdfSentinel(options: PdfSentinelOptions): PdfSentinelResultOptions
interface PdfSentinelOptions {
/** PDF document URL or ArrayBuffer */
pdf: string | ArrayBuffer;
/** Document name/identifier */
name: string;
/** Initial form field values */
initialValues?: InitialValuesType;
/** Hide the default toolbar */
hideToolbar?: boolean;
/** Callback when document finishes loading */
onDocumentLoaded?: () => void;
/** Locale code (en, it, es, fr, de) */
locale?: string;
/** Custom translations for any locale */
translations?: Record<string, Record<string, string>>;
/** Custom toolbar buttons */
toolbarButtons?: ToolbarItem[];
/** Conditional signature configurations */
conditionalSignatures?: ConditionalSignature[];
}Return Value
interface PdfSentinelResult {
/** Container component that wraps the PDF viewer and toolbar */
PdfSentinelContainer: React.ComponentType<{children: React.ReactNode}>;
/** Toolbar component - should be rendered inside PdfSentinelContainer */
Toolbar: React.ComponentType<{}>;
/** Main PDF viewer component - should be rendered inside PdfSentinelContainer */
PdfSentinel: React.ComponentType<{}>;
/** Current form state */
form: UsePdfFormsResult | null;
/** Current page number (1-indexed) */
currentPage: number | null;
/** Total number of pages */
totalPages: number | null;
/** Whether PDF is loading */
loadingPdf: boolean;
/** Whether there was a loading error */
loadingError: boolean;
/** PDF engine instance */
engine: PdfEngine<Blob> | null;
/** Whether PDF has been closed */
closedPdf: boolean;
/** Helper methods */
helpers: {
zoomIn: () => void;
zoomOut: () => void;
resetZoom: () => void;
scrollToPage: (pageNumber: number) => void;
scrollToPosition: (position: { x: number; y: number; behavior?: ScrollBehavior }) => void;
download: () => void;
};
}Types
InitialValuesType
type InitialValuesType = Record<string, {
value: string | boolean;
readonly?: boolean;
}>;Initial form field values. Each key is a field name.
Example:
const initialValues: InitialValuesType = {
"fullName": { value: "John Doe", readonly: false },
"signature": { value: true, readonly: true },
"agreeToTerms": { value: true },
};ConditionalSignature
interface ConditionalSignature {
index: string;
fieldNames: string[];
}Defines a group of signature fields that are conditional on the same value.
Example:
const conditionalSignatures: ConditionalSignature[] = [
{ index: "1", fieldNames: ["signature1", "signature2"] },
{ index: "2", fieldNames: ["signature3", "signature4"] },
];UsePdfFormsResult
interface UsePdfFormsResult {
fields: Field[];
values: Record<string, string | boolean>;
setValue: (name: string, value: string | boolean) => void;
validate: () => { valid: boolean; errors: Record<string, string> };
reload: () => void;
errors?: Record<string, string>;
showErrors: boolean;
scrollToConditionalSignature: (index: string) => Generator;
}Represents the current form state.
Field
interface Field {
name: string;
type: "text" | "checkbox" | "signature" | "radiobutton";
pageIndex: number;
bbox: { x: number; y: number; width: number; height: number };
radioValue?: string;
readonly?: boolean;
required?: boolean;
conditionalIndex?: string;
}Represents a single form field.
Toolbar Customization
ToolbarItem
type ToolbarItem = ToolbarButton | ToolbarSpacer;ToolbarButton
interface ToolbarButton {
id: string;
label: string;
icon?: string; // Font Awesome icon class
title?: string; // Tooltip text
onClick: () => void;
area: "left" | "right";
disabled?: boolean;
type: "button";
}ToolbarSpacer
interface ToolbarSpacer {
id: string;
type: "spacer";
area: "left" | "right";
}Internationalization
Supported Locales
en- English (default)it- Italianes- Spanishfr- Frenchde- German
Default Translation Keys
{
zoomIn: string;
zoomOut: string;
resetZoom: string;
download: string;
pageNumberTitle: string;
signature: string;
}Custom Translations
Override or add translations for any locale:
const translations = {
en: {
zoomIn: "Enlarge",
zoomOut: "Shrink",
customKey: "Custom value",
},
it: {
customKey: "Valore personalizzato",
},
};Examples
Basic PDF Viewer
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
export function BasicViewer() {
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
pdf: "https://example.com/document.pdf",
name: "Document",
});
return (
<div style={{ height: "600px" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
);
}PDF with Form Fields
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
export function FormViewer() {
const { PdfSentinelContainer, Toolbar, PdfSentinel, form } = usePdfSentinel({
pdf: "/document.pdf",
name: "Form Document",
initialValues: {
"firstName": { value: "John" },
"lastName": { value: "Doe" },
},
});
const handleValidate = () => {
if (form?.validate()) {
console.log("Form is valid, values:", form.values);
}
};
return (
<>
<div style={{ height: "600px" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
<button onClick={handleValidate}>Validate</button>
</>
);
}Custom Toolbar Buttons
import { usePdfSentinel, ToolbarItem } from "@kopjra/pdf-sentinel";
export function CustomToolbar() {
const customButtons: ToolbarItem[] = [
{
id: "print-btn",
label: "Print",
icon: "fal fa-print",
title: "Print document",
area: "left",
type: "button",
onClick: () => window.print(),
},
{
id: "spacer",
type: "spacer",
area: "left",
},
{
id: "share-btn",
label: "Share",
icon: "fal fa-share",
title: "Share document",
area: "right",
type: "button",
onClick: () => {
console.log("Share clicked");
},
},
];
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
pdf: "/document.pdf",
name: "Document",
toolbarButtons: customButtons,
});
return (
<div style={{ height: "600px" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
);
}Multi-Language Support
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
import { useState } from "react";
export function MultiLanguageViewer() {
const [locale, setLocale] = useState("en");
const customTranslations = {
en: {
zoomIn: "Zoom In",
download: "Download PDF",
},
it: {
zoomIn: "Ingrandisci",
download: "Scarica PDF",
},
};
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
pdf: "/document.pdf",
name: "Document",
locale: locale,
translations: customTranslations,
});
return (
<div>
<select onChange={(e) => setLocale(e.target.value)} value={locale}>
<option value="en">English</option>
<option value="it">Italiano</option>
</select>
<div style={{ height: "600px", marginTop: "10px" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
</div>
);
}Conditional Signatures with Groups
import { usePdfSentinel, ConditionalSignature } from "@kopjra/pdf-sentinel";
export function SignatureViewer() {
const conditionalSignatures: ConditionalSignature[] = [
{ index: "1", fieldNames: ["authorizedBy", "authorizedDate"] },
{ index: "2", fieldNames: ["approvedBy", "approvedDate"] },
];
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
pdf: "/contract.pdf",
name: "Contract",
conditionalSignatures: conditionalSignatures,
});
return (
<div style={{ height: "600px" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
);
}Form Validation with Error Handling
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
export function FormWithValidation() {
const { PdfSentinelContainer, Toolbar, PdfSentinel, form } = usePdfSentinel({
pdf: "/form.pdf",
name: "Application Form",
initialValues: {
"email": { value: "" },
"phone": { value: "" },
},
});
const handleSubmit = () => {
if (!form) return;
// Validate required fields
let isValid = true;
const errors: Record<string, string> = {};
if (!form.values["email"]) {
errors["email"] = "Email is required";
isValid = false;
}
if (!form.values["phone"]) {
errors["phone"] = "Phone is required";
isValid = false;
}
if (!isValid) {
// Set errors and show them
Object.entries(errors).forEach(([field, error]) => {
form.setFieldError(field, error);
});
} else {
console.log("Form submitted:", form.values);
}
};
return (
<>
<div style={{ height: "600px" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
<button onClick={handleSubmit}>Submit</button>
</>
);
}Using React and Preact Variants
To use the Preact variant use import from @kopjra/pdf-sentinel/preact instead of @kopjra/pdf-sentinel.
The React is the default one, however you can also use the import @kopjra/pdf-sentinel/react for clarity.
// React variant
import { usePdfSentinel } from "@kopjra/pdf-sentinel/react";
// Preact variant (same import, different package)
import { usePdfSentinel } from "@kopjra/pdf-sentinel/preact";
export function Viewer() {
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
pdf: "/document.pdf",
name: "Document",
});
return (
<div style={{ height: "600px" }}>
<PdfSentinelContainer>
<Toolbar />
<PdfSentinel />
</PdfSentinelContainer>
</div>
);
}The same code works for both React and Preact - the library automatically uses the correct variant based on your project configuration.
Browser Support
- Chrome/Chromium (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Contributing
Contributions are welcome! Please ensure:
- TypeScript types are correctly defined
- All code follows the project style guide
npm run typecheckpasses- Changes are tested in both React and Preact variants
License
ISC
2
