@inkbar/react 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +140 -2
- package/dist/index.d.mts +1958 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1280 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Redactr Platforms Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,141 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/inkbar/inkbar/main/.github/assets/inkbar-mark-on-dark.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/inkbar/inkbar/main/.github/assets/inkbar-mark-on-light.svg" alt="Inkbar" width="72" height="72">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
<h1 align="center">@inkbar/react</h1>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
A PDF viewer and editor for React 19. No licence fee.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://inkbar.dev">Website</a> ·
|
|
16
|
+
<a href="https://inkbar.dev/docs/">Docs</a> ·
|
|
17
|
+
<a href="https://sandbox.inkbar.dev">Sandbox</a> ·
|
|
18
|
+
<a href="https://github.com/inkbar/inkbar">GitHub</a>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
Drop a PDF viewer into your React app and let people highlight text, leave notes with replies and review redactions. `@inkbar/react` gives you a default toolbar and popovers styled by `@inkbar/theme`, plus the unstyled components and hooks they are made from, so you can keep the defaults or build your own UI.
|
|
24
|
+
|
|
25
|
+
Annotations are plain typed objects. Control them from your own state, or let Inkbar hold them with undo and redo.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
pnpm add @inkbar/react @inkbar/core @inkbar/theme
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
React 19 is a peer dependency. Add the Vite plugin from `@inkbar/core/vite` so the PDF engine can load its worker and fonts; the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#serving-pdfjs-assets) covers it. The package is marked `'use client'`, so a React Server Components app can import it from a server component.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { useState } from 'react'
|
|
39
|
+
import {
|
|
40
|
+
InkRoot,
|
|
41
|
+
InkViewport,
|
|
42
|
+
DefaultToolbar,
|
|
43
|
+
InkNotePopover,
|
|
44
|
+
InkSuggestionCard,
|
|
45
|
+
} from '@inkbar/react'
|
|
46
|
+
import type { Annotation } from '@inkbar/core'
|
|
47
|
+
import { assets } from 'virtual:inkbar-assets'
|
|
48
|
+
import '@inkbar/theme/theme.css'
|
|
49
|
+
|
|
50
|
+
export function Review({
|
|
51
|
+
signedUrl,
|
|
52
|
+
documentId,
|
|
53
|
+
}: {
|
|
54
|
+
signedUrl: string
|
|
55
|
+
documentId: string
|
|
56
|
+
}) {
|
|
57
|
+
const [annotations, setAnnotations] = useState<Annotation[]>([])
|
|
58
|
+
return (
|
|
59
|
+
<InkRoot
|
|
60
|
+
src={{ url: signedUrl, key: documentId }}
|
|
61
|
+
assets={assets}
|
|
62
|
+
annotations={annotations}
|
|
63
|
+
onAnnotationsChange={setAnnotations}
|
|
64
|
+
>
|
|
65
|
+
<DefaultToolbar />
|
|
66
|
+
<InkViewport />
|
|
67
|
+
<InkNotePopover />
|
|
68
|
+
<InkSuggestionCard />
|
|
69
|
+
</InkRoot>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`annotations` with `onAnnotationsChange` makes your list the source of truth. Leave them off and Inkbar keeps the state itself, with undo and redo.
|
|
75
|
+
|
|
76
|
+
## Controlled mode
|
|
77
|
+
|
|
78
|
+
With `annotations` and `onAnnotationsChange`, Inkbar proposes each change through `onAnnotationsChange` and shows whatever list you pass back. To change annotations yourself, set a new array: `setAnnotations([...annotations, next])`. In-place mutation (`push`, `splice`, editing an item) is not observed, so the viewer never sees it.
|
|
79
|
+
|
|
80
|
+
Undo and redo are off in controlled mode unless you pass `history`. Inkbar then keeps a stack of your list's past values and applies undo and redo through `onAnnotationsChange`. Every change to the list is a step, including the list you load from your server when the document opens. Clear the history once that load has rendered, so the first undo does not empty the list:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { useEffect, useRef, useState } from 'react'
|
|
84
|
+
import type { InkRootHandle } from '@inkbar/react'
|
|
85
|
+
import type { InkDocument } from '@inkbar/core'
|
|
86
|
+
|
|
87
|
+
const root = useRef<InkRootHandle>(null)
|
|
88
|
+
const [loaded, setLoaded] = useState<string | null>(null)
|
|
89
|
+
|
|
90
|
+
async function onReady(doc: InkDocument) {
|
|
91
|
+
setAnnotations(await loadAnnotations(doc.key))
|
|
92
|
+
setLoaded(doc.key)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Runs after InkRoot has seen the loaded list.
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (loaded) root.current?.history.clear()
|
|
98
|
+
}, [loaded])
|
|
99
|
+
|
|
100
|
+
<InkRoot
|
|
101
|
+
src={{ url: signedUrl, key: documentId }}
|
|
102
|
+
assets={assets}
|
|
103
|
+
annotations={annotations}
|
|
104
|
+
onAnnotationsChange={setAnnotations}
|
|
105
|
+
history
|
|
106
|
+
onReady={onReady}
|
|
107
|
+
ref={root}
|
|
108
|
+
>
|
|
109
|
+
…
|
|
110
|
+
</InkRoot>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Inside `InkRoot`, `useHistory()` returns the same object. Its `batch(fn)` runs `fn` and records what it changes as one undo step. With `history` on a controlled list, you still get one `onAnnotationsChange` call per add. The step lands when the current task ends, and `canUndo` updates then. Any other change you apply to the list in that window joins the step. In both modes, `fn` must be synchronous.
|
|
114
|
+
|
|
115
|
+
`key` identifies the document. Reuse the same key when only the signed URL changes and the document is not reloaded.
|
|
116
|
+
|
|
117
|
+
## Note popover
|
|
118
|
+
|
|
119
|
+
`InkNotePopover` opens the thread of the activated note. A new note has no text yet, so the form first writes the note's own text (labelled "Comment"); after that it adds replies.
|
|
120
|
+
|
|
121
|
+
A root note with no text and no replies is removed when its popover closes, or when activation moves to another annotation, so abandoned notes do not stay on the page. Locked notes are never removed. If you add notes yourself (`add({ type: 'note', ... })`), give them `contents`, or they will be removed the first time someone opens and closes them.
|
|
122
|
+
|
|
123
|
+
## Custom rendering
|
|
124
|
+
|
|
125
|
+
Slots are render props. `InkViewport` takes `loading`, `empty`, `error`, `renderAnnotation` and `renderPageError`; `InkNotePopover` and `InkSuggestionCard` take a function as `children`; `InkToolButton` takes a node or a function of `{ active, label }`. The props each receives are typed as `AnnotationSlotProps`, `NotePopoverSlotProps` and `SuggestionCardSlotProps`, the same types the Vue package exports.
|
|
126
|
+
|
|
127
|
+
## Keyboard
|
|
128
|
+
|
|
129
|
+
The scroll element takes focus. With the highlight or redact tool active, `Enter` turns the text selection into a mark; with the note tool, `Enter` places a note in the middle of the current page and opens it. On a focused annotation, `Enter` or `Space` selects and activates it (a note opens its thread and a suggested redaction its card; other types are only selected), `ArrowUp` and `ArrowDown` move between annotations, `Shift` + arrow moves it by 1 pt (add `Alt` for 10 pt), `Ctrl` or `Cmd` + `Shift` + arrow resizes an area mark, and `Delete` removes it. The full table is in the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#tools-and-keyboard).
|
|
130
|
+
|
|
131
|
+
## Styling
|
|
132
|
+
|
|
133
|
+
Components carry state on data attributes, for example `[data-annotation][data-state="selected"]` and `[data-tool="highlight"][data-state="on"]`. `@inkbar/theme` gives a default look. Your own CSS overrides it without `!important`. `InkRoot`, `InkViewport`, `DefaultToolbar` and the toolbar controls accept `className`.
|
|
134
|
+
|
|
135
|
+
## Hooks
|
|
136
|
+
|
|
137
|
+
`useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory` and `useEmbedded` read the state of the nearest `InkRoot`. Build your own toolbar or side panel from them.
|
|
138
|
+
|
|
139
|
+
## Licence
|
|
140
|
+
|
|
141
|
+
[MIT](https://github.com/inkbar/inkbar/blob/main/LICENSE). From the team behind [Redactr](https://redactr.io).
|