npm package: @sudarsanank/react-pdf-annotations
Description: A standalone React library for PDF annotations with real-time collaboration support
Author: Sudarsanan Kaliyamurthy
Latest version: 1.1.0

# react-annotations

A powerful, standalone React library for PDF annotations with real-time collaboration support. Works with any PDF viewer library through an adapter pattern.

## Features

- ✅ **4 Annotation Types**: Highlights, Freehand Drawing, Shapes (Rectangle/Circle/Arrow), Notes
- ✅ **Real-time Collaboration**: Built-in Socket.IO support for multi-user editing
- ✅ **Viewer Agnostic**: Works with react-pdf, PDF.js, or any custom PDF viewer
- ✅ **State Management**: Redux Toolkit for robust state handling
- ✅ **Storage Adapters**: API backend, LocalStorage, or custom implementations
- ✅ **TypeScript**: Full type safety and IntelliSense support
- ✅ **Tailwind CSS**: Beautiful, customizable UI components
- ✅ **React 19**: Built for the latest React features

## Installation

```bash
npm install @sudarsanank/react-pdf-annotations
# or
yarn add @sudarsanank/react-pdf-annotations
# or
pnpm add @sudarsanank/react-pdf-annotations
```

### Peer Dependencies

```bash
npm install react react-dom @reduxjs/toolkit react-redux socket.io-client axios
```

## Quick Start

### Basic Usage with react-pdf

```tsx
import { Document, Page } from 'react-pdf';
import {
  AnnotationProvider,
  AnnotationLayer,
  AnnotationCanvas,
  AnnotationToolbar,
  NotesPanel,
  createApiStorage,
  createSocketIOCollaboration,
} from '@sudarsanank/react-pdf-annotations';
import '@sudarsanank/react-pdf-annotations/styles';

function PDFAnnotator() {
  const storage = createApiStorage({
    baseUrl: 'http://localhost:3001/api',
    documentId: 'doc-123',
    versionId: 'v-1',
  });

  const collaboration = createSocketIOCollaboration({
    url: 'http://localhost:3001',
    documentId: 'doc-123',
    versionId: 'v-1',
    userId: 'user-1',
  });

  return (
    <AnnotationProvider
      storage={storage}
      collaboration={collaboration}
      documentId="doc-123"
      versionId="v-1"
      userId="user-1"
      config={{
        tools: ['select', 'highlight', 'draw', 'rectangle', 'circle', 'arrow', 'note'],
        defaultColor: '#FFEB3B',
        colorPalette: ['#FFEB3B', '#FF5722', '#4CAF50', '#2196F3'],
      }}
    >
      <div className="flex h-screen">
        <div className="flex-1 flex flex-col">
          <AnnotationToolbar />

          <div className="flex-1 relative overflow-auto">
            <Document file="/document.pdf">
              <Page pageNumber={1} />
            </Document>

            {/* Annotation overlays */}
            <AnnotationLayer />
            <AnnotationCanvas />
          </div>
        </div>

        <NotesPanel />
      </div>
    </AnnotationProvider>
  );
}
```

### Offline Mode with LocalStorage

```tsx
import {
  AnnotationProvider,
  createLocalStorage,
  createNoOpCollaboration,
} from '@sudarsanank/react-pdf-annotations';

function OfflineAnnotator() {
  const storage = createLocalStorage({
    documentId: 'my-document',
    versionId: 'v1',
  });

  return (
    <AnnotationProvider
      storage={storage}
      collaboration={createNoOpCollaboration()}
      documentId="my-document"
    >
      {/* Your UI components */}
    </AnnotationProvider>
  );
}
```

## Architecture

### Adapter Pattern

The library uses adapters to decouple from specific implementations:

#### Storage Adapters

```typescript
interface StorageAdapter {
  fetchAnnotations(documentId: string, versionId?: string): Promise<Annotation[]>;
  createAnnotation(annotation: CreateAnnotationDto): Promise<Annotation>;
  updateAnnotation(annotationId: string, updates: Partial<Annotation>): Promise<Annotation>;
  deleteAnnotation(annotationId: string): Promise<void>;
}
```

**Built-in Adapters:**
- `ApiStorage` - REST API backend (NestJS compatible)
- `LocalStorage` - Browser localStorage for offline use

#### Collaboration Adapters

```typescript
interface CollaborationAdapter {
  connect(): void;
  disconnect(): void;
  joinDocument(documentId: string, versionId?: string): void;
  leaveDocument(documentId: string, versionId?: string): void;
  emit(event: CollaborationEvent): void;
  on(event: string, handler: (data: any) => void): void;
  isConnected(): boolean;
}
```

**Built-in Adapters:**
- `SocketIOAdapter` - Real-time collaboration via Socket.IO
- `NoOpAdapter` - Disables collaboration (single-user mode)

#### Renderer Adapters

```typescript
interface RendererAdapter {
  getPageDimensions(pageNumber: number): { width: number; height: number };
  getCurrentPage(): number;
  getTotalPages(): number;
  getScale(): number;
  getContainerElement(): HTMLElement | null;
}
```

**Built-in Adapters:**
- `ReactPDFAdapter` - Works with react-pdf
- `GenericAdapter` - Works with any PDF viewer

## Components

### AnnotationProvider

Root provider component that sets up the annotation environment.

```tsx
<AnnotationProvider
  storage={storageAdapter}
  collaboration={collaborationAdapter}
  renderer={rendererAdapter}
  documentId="doc-123"
  versionId="v-1"
  userId="user-1"
  config={{
    tools: ['select', 'highlight', 'draw', 'note'],
    defaultTool: 'select',
    defaultColor: '#FFEB3B',
    colorPalette: ['#FFEB3B', '#FF5722', '#4CAF50'],
    ui: {
      showToolbar: true,
      showNotesPanel: true,
      toolbarPosition: 'top',
      notesPanelPosition: 'right',
    },
    permissions: {
      canCreate: true,
      canEdit: true,
      canDelete: true,
      canEditOthers: false,
    },
    callbacks: {
      onAnnotationCreate: (annotation) => console.log('Created:', annotation),
      onAnnotationUpdate: (annotation) => console.log('Updated:', annotation),
      onAnnotationDelete: (id) => console.log('Deleted:', id),
      onError: (error) => console.error('Error:', error),
    },
  }}
>
  {children}
</AnnotationProvider>
```

### AnnotationLayer

Renders existing annotations on top of the PDF.

```tsx
<AnnotationLayer />
```

### AnnotationCanvas

Captures user input for creating new annotations.

```tsx
<AnnotationCanvas />
```

### AnnotationToolbar

Tool selection and color picker UI.

```tsx
<AnnotationToolbar />
```

### NotesPanel

Sidebar panel for viewing and editing note annotations.

```tsx
<NotesPanel />
```

## Hooks

### useAnnotations

Main hook for annotation CRUD operations.

```tsx
import { useAnnotations } from '@sudarsanank/react-pdf-annotations';

function MyComponent() {
  const {
    annotations,
    loading,
    error,
    selected,
    createAnnotation,
    updateAnnotation,
    deleteAnnotation,
    selectAnnotation,
  } = useAnnotations();

  const handleCreate = async () => {
    await createAnnotation({
      documentVersionId: 'v-1',
      userId: 'user-1',
      annotationType: AnnotationType.HIGHLIGHT,
      pageNumber: 1,
      positionData: { x: 0.1, y: 0.1, width: 0.2, height: 0.05 },
      color: '#FFEB3B',
    });
  };

  return <div>{annotations.length} annotations</div>;
}
```

### useAnnotationDraw

Manages drawing state and active tool.

```tsx
import { useAnnotationDraw } from '@sudarsanank/react-pdf-annotations';

function ToolSelector() {
  const { currentTool, setTool, color, setColor } = useAnnotationDraw();

  return (
    <div>
      <button onClick={() => setTool(AnnotationTool.HIGHLIGHT)}>
        Highlight
      </button>
      <input
        type="color"
        value={color}
        onChange={(e) => setColor(e.target.value)}
      />
    </div>
  );
}
```

### useCollaboration

Manages real-time collaboration state.

```tsx
import { useCollaboration } from '@sudarsanank/react-pdf-annotations';

function CollaborationStatus() {
  const { isConnected } = useCollaboration();

  return <div>{isConnected ? 'Connected' : 'Offline'}</div>;
}
```

### useViewport

Controls zoom and page navigation.

```tsx
import { useViewport } from '@sudarsanank/react-pdf-annotations';

function Navigation() {
  const {
    currentPage,
    totalPages,
    scale,
    nextPage,
    previousPage,
    zoomIn,
    zoomOut,
  } = useViewport();

  return (
    <div>
      <button onClick={previousPage}>Previous</button>
      <span>Page {currentPage} of {totalPages}</span>
      <button onClick={nextPage}>Next</button>
      <button onClick={zoomOut}>-</button>
      <span>{Math.round(scale * 100)}%</span>
      <button onClick={zoomIn}>+</button>
    </div>
  );
}
```

## Custom Adapters

### Custom Storage Adapter

```typescript
import { StorageAdapter, Annotation, CreateAnnotationDto } from '@sudarsanank/react-pdf-annotations';

class MyCustomStorage implements StorageAdapter {
  async fetchAnnotations(documentId: string): Promise<Annotation[]> {
    // Your implementation
    return await myApi.getAnnotations(documentId);
  }

  async createAnnotation(dto: CreateAnnotationDto): Promise<Annotation> {
    return await myApi.createAnnotation(dto);
  }

  async updateAnnotation(id: string, updates: Partial<Annotation>): Promise<Annotation> {
    return await myApi.updateAnnotation(id, updates);
  }

  async deleteAnnotation(id: string): Promise<void> {
    await myApi.deleteAnnotation(id);
  }
}

// Use it
const storage = new MyCustomStorage();
```

### Custom Collaboration Adapter

```typescript
import { CollaborationAdapter, CollaborationEvent } from '@sudarsanank/react-pdf-annotations';

class MyWebSocketAdapter implements CollaborationAdapter {
  private ws: WebSocket | null = null;

  connect(): void {
    this.ws = new WebSocket('ws://localhost:8080');
  }

  disconnect(): void {
    this.ws?.close();
  }

  emit(event: CollaborationEvent): void {
    this.ws?.send(JSON.stringify(event));
  }

  // ... implement other methods
}
```

## Configuration Options

```typescript
interface AnnotationConfig {
  tools?: AnnotationTool[];
  defaultTool?: AnnotationTool;
  defaultColor?: string;
  colorPalette?: string[];

  ui?: {
    showToolbar?: boolean;
    showNotesPanel?: boolean;
    toolbarPosition?: 'top' | 'bottom' | 'left' | 'right';
    notesPanelPosition?: 'left' | 'right';
    theme?: 'light' | 'dark' | 'auto';
  };

  behavior?: {
    autoSave?: boolean;
    autoSaveDelay?: number;
    enableUndo?: boolean;
    maxUndoSteps?: number;
    enableKeyboardShortcuts?: boolean;
    selectAfterCreate?: boolean;
  };

  permissions?: {
    canCreate?: boolean;
    canEdit?: boolean;
    canDelete?: boolean;
    canEditOthers?: boolean;
  };

  callbacks?: {
    onAnnotationCreate?: (annotation: Annotation) => void;
    onAnnotationUpdate?: (annotation: Annotation) => void;
    onAnnotationDelete?: (annotationId: string) => void;
    onToolChange?: (tool: AnnotationTool) => void;
    onError?: (error: Error) => void;
  };
}
```

## TypeScript Support

The library is fully typed. Import types as needed:

```typescript
import type {
  Annotation,
  AnnotationType,
  AnnotationTool,
  CreateAnnotationDto,
  UpdateAnnotationDto,
  StorageAdapter,
  CollaborationAdapter,
  RendererAdapter,
  AnnotationConfig,
} from '@sudarsanank/react-pdf-annotations';
```

## Styling

The library uses Tailwind CSS. Import the styles:

```tsx
import '@sudarsanank/react-pdf-annotations/styles';
```

For custom styling, override the Tailwind classes or use your own CSS:

```css
.annotation-layer {
  /* Custom styles */
}

.annotation-toolbar {
  /* Custom toolbar styles */
}
```

## Backend Integration

### NestJS Example

The library works seamlessly with a NestJS backend:

```typescript
// NestJS Controller
@Controller('documents/:documentId/versions/:versionId/annotations')
export class AnnotationsController {
  @Get()
  async list(@Param('documentId') documentId: string, @Param('versionId') versionId: string) {
    return await this.annotationsService.findAll(documentId, versionId);
  }

  @Post()
  async create(@Body() dto: CreateAnnotationDto) {
    return await this.annotationsService.create(dto);
  }

  @Put(':id')
  async update(@Param('id') id: string, @Body() updates: UpdateAnnotationDto) {
    return await this.annotationsService.update(id, updates);
  }

  @Delete(':id')
  async delete(@Param('id') id: string) {
    return await this.annotationsService.delete(id);
  }
}
```

### WebSocket Gateway

```typescript
@WebSocketGateway()
export class AnnotationsGateway {
  @SubscribeMessage('joinDocument')
  handleJoinDocument(@MessageBody() data: { documentId: string; versionId: string }) {
    // Handle room joining
  }

  @SubscribeMessage('createAnnotation')
  async handleCreateAnnotation(@MessageBody() data: any) {
    const annotation = await this.annotationsService.create(data.annotation);
    this.server.to(`${data.documentId}-${data.versionId}`).emit('annotationCreated', annotation);
  }
}
```

## API Documentation

Comprehensive API documentation is available and can be generated locally:

```bash
# Generate API documentation
npm run docs

# Serve documentation locally at http://localhost:3002
npm run docs:serve
```

The documentation includes:
- Complete API reference for all exported components, hooks, and types
- TypeScript type definitions
- Usage examples and descriptions
- Source code links

## Contributing

Contributions are welcome! Please read our contributing guidelines.

## License

MIT

## Support

For issues and questions, please visit our [GitHub repository](https://github.com/sudarsanan/react-annotation).