Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions web/signing/signing-demo-overlay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
# Nutrient Signing Demo

A React-based signing demo application using Nutrient Viewer with custom widget overlays for PDF document signing workflows.

## Features

- πŸ–‹οΈ **Interactive Signature Widgets** - Click-to-sign functionality
- πŸ“ **Editable Text Fields** - Double-click to edit text content
- πŸ“… **Date Picker Widgets** - Select dates with a calendar interface
- 🎯 **Drag & Drop** - Move widgets around in form creator mode
- 🎨 **Custom Widget Components** - Easily extensible widget system
- ⚑ **React Hook API** - Clean, reusable hook-based architecture

## Quick Start

1. **Install dependencies:**
```bash
npm install
```

2. **Start development server:**
```bash
npm run dev
```

3. **Open your browser to:** `http://localhost:3000`

The demo includes a sample PDF (`public/sample.pdf`) that will load automatically. To use your own PDF, simply replace `sample.pdf` with your document.

## Project Structure

```
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ main.jsx # Vite entry point
β”‚ β”œβ”€β”€ App.jsx # Main demo component
β”‚ └── index.css # Global styles
β”œβ”€β”€ hooks/
β”‚ └── useNutrientViewer.js # Custom hook for Nutrient Viewer
β”œβ”€β”€ components/
β”‚ β”œβ”€β”€ WidgetOverlay.jsx # Base widget overlay component
β”‚ β”œβ”€β”€ SignatureWidget.jsx # Signature field component
β”‚ β”œβ”€β”€ TextWidget.jsx # Text input component
β”‚ └── DateWidget.jsx # Date picker component
β”œβ”€β”€ public/
β”‚ └── sample.pdf # Sample PDF for demo (replace with your own)
β”œβ”€β”€ index.html # Vite HTML template
└── package.json # Dependencies and scripts
```

## Usage

### Basic Hook Usage

```jsx
import { useNutrientViewer } from './hooks/useNutrientViewer';
import SignatureWidget from './components/SignatureWidget';

const MyApp = () => {
const { containerRef, createWidget, isLoaded } = useNutrientViewer({
document: "path/to/document.pdf",
customWidgetComponent: SignatureWidget,
onReady: (instance) => console.log("Ready!", instance)
});

return (
<div ref={containerRef} style={{ width: "100%", height: "100vh" }} />
);
};
```

### Creating Custom Widgets

```jsx
import WidgetOverlay from './WidgetOverlay';

const MyCustomWidget = ({ annotation, customData, onUpdate, onDelete }) => {
const handleClick = () => {
onUpdate({ myCustomField: "updated value" });
};

return (
<WidgetOverlay
annotation={annotation}
customData={customData}
onUpdate={onUpdate}
onDelete={onDelete}
>
<div onClick={handleClick}>
Custom Widget Content
</div>
</WidgetOverlay>
);
};
```

### Hook API

```typescript
const {
containerRef, // Ref for PDF container
instance, // NutrientViewer instance
isLoaded, // Loading state
selectedAnnotations, // Currently selected annotations
currentMode, // Current interaction mode
isFormCreatorMode, // Whether in form creator mode
activeOverlays, // Number of active overlays
createWidget, // Function to create widgets
toggleFormCreator, // Toggle form creator mode
} = useNutrientViewer(options);
```

### Hook Options

```typescript
useNutrientViewer({
document: string, // PDF document URL/path
theme?: "DARK" | "LIGHT", // Viewer theme
styleSheets?: string[], // Custom CSS files
customWidgetComponent?: Component, // Custom widget component
enableFormCreator?: boolean, // Enable form creator toolbar
onReady?: (instance) => void, // Callback when ready
})
```

## Widget Types

### Signature Widget
- Click to toggle signed/unsigned state
- Visual feedback with checkmark when signed
- Stores signature timestamp

### Text Widget
- Double-click to edit text content
- Inline text editing with save/cancel
- Supports multi-line text

### Date Widget
- Click to open date picker
- Formatted date display
- Stores ISO date string

## Demo Instructions

1. **Open Signing Controls** - Click the "Signing Controls" button in the sidebar dropdown menu
2. **Select Widget Type** - Choose from signature, text, or date widgets in the sidebar
3. **Create Widget** - Click "Create Widget" to add a new widget to the document
4. **Enter Editor Mode** - Click "Enter Editor" to enable drag & drop mode
5. **Interact with Widgets**:
- Drag widgets to reposition them
- Click signature widgets to sign
- Double-click text widgets to edit
- Click date widgets to select dates
6. **Delete Widgets** - Click the Γ— button on any widget

## Development

### Build for Production

```bash
npm run build
```

### Preview Production Build

```bash
npm run preview
```

### Lint Code

```bash
npm run lint
```

## Architecture

### SDK-Driven Design
- **Dynamic Field Types**: Field types are automatically detected from available `NutrientViewer.FormFields`
- **Flexible Widget Creation**: Uses FormField classes directly instead of hardcoded types
- **Extensible Configuration**: Easy to add new field types as they become available in the SDK

### Performance Optimizations
- **Memoized Field Configurations**: Field type detection is cached and reused
- **Debounced Validation**: Collision detection is optimized for smooth drag operations
- **Efficient Event Handling**: Minimal DOM updates and smart event delegation
- **Memory Management**: Automatic cleanup of event listeners and references

### Developer-Friendly Features
- **TypeScript-Ready**: Structured for easy TypeScript integration
- **Modular Architecture**: Separated concerns with dedicated utility modules
- **Comprehensive Error Handling**: Graceful degradation when SDK features are unavailable
- **Performance Monitoring**: Built-in performance tracking utilities
Comment on lines +188 to +192
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines can be removed.


## Dependencies

- **React 18+** - UI framework
- **Vite** - Build tool and dev server
- **Nutrient Viewer** - PDF viewing and annotation (via CDN)

## Enhanced Features

### Advanced Field Types
The demo now supports all available NutrientViewer FormField types:
- **SignatureFormField**: Signature and Initials
- **TextFormField**: Text, Name, Email, Date (with format validation)
- **CheckBoxFormField**: Checkbox controls
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong but the UI didn't show this,

- **RadioButtonFormField**: Radio button groups
- **ListBoxFormField**: Multi-select lists
- **ComboBoxFormField**: Dropdown selectors

### Collision Detection & Boundaries
- **8px Gap Enforcement**: Automatic spacing between widgets
- **Page Boundary Validation**: Widgets cannot be placed outside document edges
- **Real-time Collision Detection**: Prevents overlapping during drag, drop, and resize
- **Smooth User Experience**: Operations stop at boundaries without jarring movements

### Performance Features
- **Cached Field Configurations**: Reduces repeated SDK queries
- **Optimized Validation**: Efficient bounds checking with minimal overhead
- **Smart Event Handling**: Debounced and throttled operations where appropriate

## License

MIT

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request

## Support

For issues related to:
- **Nutrient Viewer**: Check the [Nutrient documentation](https://docs.nutrient.io/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This URL is incorrect. Should be https://www.nutrient.io/guides/web/

- **This demo**: Open an issue in this repository
111 changes: 111 additions & 0 deletions web/signing/signing-demo-overlay/components/DateWidget.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, { useState } from "react";
import WidgetOverlay from "./WidgetOverlay";

export const DateWidget = ({
annotation,
customData,
boundingBox,
onUpdate,
onDelete,
}) => {
const [selectedDate, setSelectedDate] = useState(
customData?.selectedDate || new Date().toISOString().split("T")[0]
);
const [isOpen, setIsOpen] = useState(false);

const handleDateChange = (e) => {
const newDate = e.target.value;
setSelectedDate(newDate);
setIsOpen(false);

if (onUpdate) {
onUpdate({
selectedDate: newDate,
text: formatDisplayDate(newDate),
});
}
};

const formatDisplayDate = (dateString) => {
const date = new Date(dateString + "T00:00:00");
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
};

const handleClick = () => {
setIsOpen(!isOpen);
};

return (
<WidgetOverlay
annotation={annotation}
customData={customData}
boundingBox={boundingBox}
onUpdate={onUpdate}
onDelete={onDelete}
style={{
backgroundColor: "#fff3cd",
borderColor: "#ffc107",
}}
>
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "4px",
position: "relative",
cursor: "pointer",
pointerEvents: "auto",
}}
onClick={handleClick}
>
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
<span style={{ fontSize: "14px" }}>πŸ“…</span>
<span style={{ fontSize: "12px", fontWeight: "500" }}>
{formatDisplayDate(selectedDate)}
</span>
</div>

{isOpen && (
<div
style={{
position: "absolute",
top: "100%",
left: 0,
zIndex: 1000,
backgroundColor: "white",
border: "2px solid #ffc107",
borderRadius: "4px",
boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
padding: "8px",
marginTop: "2px",
}}
onClick={(e) => e.stopPropagation()}
>
<input
type="date"
value={selectedDate}
onChange={handleDateChange}
style={{
border: "1px solid #ddd",
borderRadius: "2px",
padding: "4px",
fontSize: "12px",
width: "140px",
}}
autoFocus
/>
</div>
)}
</div>
</WidgetOverlay>
);
};

export default DateWidget;
Loading