npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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);
      }}
    />
  );
}