@indora-labs/redaction-react
v0.1.14
Published
A React component library for rendering and reviewing redactions in text, audio, and video files. Provides headless, prop-driven UI components designed to embed cleanly into regulated data workflows and custom applications.
Readme
==========================================
Document Redaction Viewer (FileViewer)
==========================================
A frontend-only React component for viewing documents and creating, editing, and reviewing redaction boxes.
What This Library Does
Renders documents
Displays multiple document pages
Allows users to create redaction boxes via:
OCR word selection
Freeform drag
Allows moving and resizing redaction boxes
Supports editable metadata (type, reason)
Emits all state changes upward
Supports optional policy guidance
Supports keyboard deletion
What This Library Explicitly Does NOT Do
❌ Fetch documents
❌ Upload files
❌ Persist redactions
❌ Call APIs
❌ Enforce policy decisions
❌ Manage workflow state
This is deliberate. The viewer is a rendering + interaction surface only.
==========================================
Core Concepts
==========================================
Redaction Boxes
Redactions are represented as rectangles in page-space coordinates.
export type Rect = {
id: string; // REQUIRED, stable unique ID
page: number; // 0-based page index
x: number; // PDF coordinate space
y: number; // PDF coordinate space (origin = bottom-left)
w: number;
h: number;
label: string;
type?: string; // Policy type (PII, SSN, Custom, etc.)
reason?: string; // Human-readable explanation
spanIds?: string[]; // Optional linkage to OCR spans
source?: "auto" | "manual"; // Optional origin indicator
};
OCR Word Boxes (Optional)
If provided, the viewer enables text-style selection by grouping OCR word boxes.
export type OcrWordBox = {
x: number;
y: number;
w: number;
h: number;
str: string;
start: number;
end: number;
};OCR is optional.
Without it, users can still create boxes via freeform drag.
Coordinate System
All rectangle coordinates are:
- Based on intrinsic PDF page dimensions
- Origin at bottom-left (true PDF space)
- Independent of DOM scaling
Zoom does not change stored coordinates.
==========================================
Primary Components
==========================================
DocumentRedactionViewer
Low-level rendering + interaction component.
<DocumentRedactionViewer
pages={pages}
redactionBoxes={redactionBoxes}
onRedactionBoxesChange={setRedactionBoxes}
ocrByPage={ocrByPage}
defaultRectLabel="REDACT"
allowCreate
allowEdit
allowFreeformDragCreate
zoom={1}
onRectSelect={setSelectedRect}
/>Props
export type DocumentRedactionViewerProps = {
pages: DocumentPage[];
/** Optional OCR word boxes (enables text-style selection). */
ocrByPage?: Record<number, OcrWordBox[]>;
/** Controlled redaction rectangles. */
rects: Rect[];
/** Called whenever rect list changes (create/move/resize/delete). */
onRectsChange: (next: Rect[]) => void;
/** Called when a single rect is updated. */
onRectUpdate: (next: Rect) => void;
/** Called when a rect is deleted. */
onRectDelete: (next: Rect) => void;
/** Selection control (fully controlled). */
onRectSelect?: (rect: Rect | null) => void;
selectedRectId?: string | null;
/** Default label for new rectangles. */
defaultRectLabel?: string;
allowCreate?: boolean;
allowEdit?: boolean;
/** Scale factor (1 = native page size). */
zoom?: number;
className?: string;
style?: React.CSSProperties;
pageFilter?: (page: DocumentPage) => boolean;
/** Enables drag-create even without OCR data. */
allowFreeformDragCreate?: boolean;
};PdfRedactionViewer
Wrapper component that:
- Fetches a PDF from a provided URL
- Renders pages to canvases using pdf.js
- Converts each page to an image surface
- Delegates redaction interaction to DocumentRedactionViewer
<PdfRedactionViewer
pdfUrl={signedUrl}
rects={rects}
onRectsChange={setRects}
onRectUpdate={handleUpdate}
onRectDelete={handleDelete}
allowCreate
allowEdit
allowFreeformDragCreate
zoom={1}
/>This component:
- Does NOT handle authentication
- Does NOT handle persistence
- Fetches the PDF directly via fetch
- Renders fully client-side
FileViewer
A higher-level viewer that combines:
- Document rendering
- Redaction interaction
- Sidebar inspectors
- Zoom controls
- Metadata panels
- Footer actions
<FileViewer
file={file}
documentUrl={signedUrl}
rects={rects}
rules={policyRules}
hideFileDetails={false}
hideAICaseFindings={false}
onRectsChange={setRects}
onRectUpdate={handleUpdate}
onDeleteRedactionBox={handleDelete}
onClose={closeViewer}
onFinalizeRedaction={finalize}
onMarkRelevant={markRelevant}
/>Props
interface FileViewerProps {
file: ViewerFileRecord;
documentUrl: string;
rects: Rect[];
rules: PolicyRuleInput[];
hideFileDetails: boolean;
hideAICaseFindings: boolean;
onRectsChange: (rects: Rect[]) => void;
onRectUpdate: (rect: Rect) => void;
onDeleteRedactionBox: (rect: Rect | null) => void;
onClose: () => void;
onFinalizeRedaction: () => void;
onMarkRelevant?: (status: "relevant" | "irrelevant") => void;
}
Controlled State Model (Important)
This library is 100% controlled.
You own:
- redactionBoxes
- Selection persistence
- Saving / deleting
- Workflow transitions
The viewer never mutates internal authoritative state.
Selection Model (Fully Controlled)
Selection is controlled externally.
You must pass:
- selectedRectId
- onRectSelect
The viewer does not maintain authoritative selection state internally.
This allows:
- Cross-panel inspectors
- Keyboard deletion
- External workflow coordination
Keyboard Support
- Delete / Backspace deletes selected redaction box
- Selection state is emitted upward
Redaction Creation Rules
A redaction box is created when:
- OCR selection results in a non-trivial bounding box
- OR freeform drag exceeds minimum size
IDs are generated internally for new rectangles using:
- crypto.randomUUID() when available
- Time + randomness fallback
Persisted IDs are never modified.
Summary
This library is:
- A precision tool, not a workflow engine
- Designed to plug into serious systems
- Honest about what it does and doesn’t do
Quick Setup Guide
import { useState } from "react";
import { FileViewer } from "@your-org/file-viewer";
import type { Rect } from "@your-org/file-viewer";
/**
* Example: Using FileViewer in a real application.
*
* This example demonstrates:
* - Fully controlled redaction state
* - Proper rect update handling
* - Proper delete handling
* - Finalization workflow hook
*
* IMPORTANT:
* This library is 100% controlled.
* You own all state.
*/
export function FileViewerExample() {
/**
* Controlled list of redaction rectangles.
*
* The viewer NEVER owns authoritative state.
* Every create / move / resize / delete emits changes upward.
*/
const [rects, setRects] = useState<Rect[]>([]);
/**
* Optional: track which rect is currently selected.
*
* This is useful if you have:
* - External side panels
* - Custom inspector UI
* - Audit logging
* - Cross-component coordination
*/
const [selectedRectId, setSelectedRectId] = useState<string | null>(null);
/**
* Called whenever the entire rect list changes.
*
* This fires on:
* - Creation
* - Move
* - Resize
* - Delete
*
* You typically just replace state.
*/
const handleRectsChange = (next: Rect[]) => {
setRects(next);
};
/**
* Called when a single rectangle is updated.
*
* This allows you to:
* - Persist incremental edits
* - Audit changes
* - Trigger autosave
*/
const handleRectUpdate = (updated: Rect) => {
setRects(prev =>
prev.map(r => (r.id === updated.id ? updated : r))
);
};
/**
* Called when a rectangle is deleted.
*
* The viewer already removed it from its internal rendering,
* but you must remove it from your controlled state.
*/
const handleDelete = (rect: Rect | null) => {
if (!rect) return;
setRects(prev =>
prev.filter(r => r.id !== rect.id)
);
};
/**
* Called when the viewer is closed.
*
* You decide what closing means:
* - Hide modal
* - Route change
* - Cancel workflow
*/
const handleClose = () => {
console.log("Viewer closed");
};
/**
* Called when the user finalizes redaction.
*
* This is where you would:
* - Send rects to your backend
* - Trigger a burn/redact job
* - Lock the document
* - Advance workflow state
*/
const handleFinalize = () => {
console.log("Finalizing with rects:", rects);
// Example:
// await api.submitRedactions(fileId, rects);
};
return (
<FileViewer
/**
* File metadata.
*
* This drives UI display only.
* The viewer does NOT fetch or persist anything.
*/
file={{
id: "file_123",
name: "Incident_Report.pdf",
size: 1024,
type: "application/pdf",
uploadedAt: new Date().toISOString(),
department: "Internal Affairs",
tags: [],
relevancyScore: 87,
reviewStatus: "pending",
redactionFlags: [],
caseIds: [],
}}
theme={{
colors: {
overlayBackground: 'rgba(0,0,0,0.85)',
modalBackground: '#111827', // dark slate
panelBackground: '#1f2937', // slightly lighter
mutedBackground: '#374151', // control panels
borderColor: '#374151',
primaryText: '#f9fafb', // near white
secondaryText: '#9ca3af', // soft gray
accent: '#3b82f6', // blue-500
success: '#22c55e', // green-500
danger: '#ef4444', // red-500
tagBackground: '#374151',
tagText: '#e5e7eb',
unsupportedBackground: '#1f2937',
blueBackground: '#1e3a8a33', // subtle blue tint
blueBorder: '#1e40af',
blueText: '#93c5fd',
redIndicator: '#f87171',
},
}}
/**
* A signed or accessible URL to the original document.
*
* The PdfRedactionViewer will:
* - fetch() the file
* - render pages client-side
* - convert pages to image surfaces
*/
documentUrl="https://example.com/presigned.pdf"
/**
* Controlled redaction rectangles.
*/
rects={rects}
/**
* Optional policy rules.
*
* These power the RedactionInspector rule dropdown.
* You can pass [] if not using policies.
*/
rules={[]}
/**
* Optional UI configuration.
*/
hideFileDetails={false}
hideAICaseFindings={false}
/**
* State handlers.
*/
onRectsChange={handleRectsChange}
onRectUpdate={handleRectUpdate}
onDeleteRedactionBox={handleDelete}
/**
* Workflow actions.
*/
onClose={handleClose}
onFinalizeRedaction={handleFinalize}
/**
* Optional:
* If you are running relevancy workflows.
*/
onMarkRelevant={(status) => {
console.log("Marked file as:", status);
}}
/>
);
}
