@ldlework/feedback 0.1.0
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 +227 -0
- package/dist/index.d.ts +190 -0
- package/dist/index.js +1370 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/styles.css +478 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dustin Lacewell
|
|
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
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ฌ feedback
|
|
4
|
+
|
|
5
|
+
**Drag-and-drop feedback, right on your live React page.**
|
|
6
|
+
|
|
7
|
+
Reviewers drop sticky notes onto the running app, draw arrows at exactly what they mean, and send it all back as one JSON file. A built-in review mode steps through every note.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@ldlework/feedback)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://www.npmjs.com/package/@ldlework/feedback)
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
**[Live demo](https://feedback.ldlework.com)** ยท **[Docs](https://feedback.ldlework.com/docs)** ยท **[npm](https://www.npmjs.com/package/@ldlework/feedback)**
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
`@ldlework/feedback` is a drop-in annotation overlay for React. Mount one provider and one component and a dock appears in the corner: drag a note onto the page, type what you're flagging, and draw an arrow at any element on the page. Notes save to `localStorage` as you go and export to a single file โ arrows carry a CSS path to their target so a developer can find it without the picture.
|
|
21
|
+
|
|
22
|
+
- ๐ฏ **Drag to annotate** โ drop notes anywhere, draw arrows from a note's edge straight at what you mean.
|
|
23
|
+
- ๐ **Pinned to content** โ positions are stored in *content coordinates*, so notes stay glued to your layout across every window size.
|
|
24
|
+
- ๐งฑ **Inner scroll panes** โ wrap a pane's content in a region so notes scroll and clip with it, not just the window.
|
|
25
|
+
- ๐งญ **Review mode** โ step through every note; it routes to the right page and section and scrolls it into view.
|
|
26
|
+
- ๐จ **Themeable** โ one stylesheet, every colour a CSS variable; re-skin it all from a single `--fb-hue`.
|
|
27
|
+
- ๐พ **Save to a file** โ export/import one JSON document; nothing leaves the browser unless you send it.
|
|
28
|
+
- ๐ฎ **Submit to a server** โ point `submit` at an endpoint (or hand it a function) and the dock grows a send button.
|
|
29
|
+
- ๐ชถ **Zero runtime dependencies** โ ships ESM + types, router-agnostic, no CSS framework required.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add @ldlework/feedback
|
|
35
|
+
# npm i @ldlework/feedback ยท yarn add @ldlework/feedback
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`react` and `react-dom` (v18 or v19) are peer dependencies. Import the stylesheet once, anywhere in your app:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import '@ldlework/feedback/styles.css'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
Wrap your tree in `FeedbackProvider` and mount `FeedbackLayer` once inside it. On a page that scrolls the window, that's the entire integration.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { FeedbackProvider, FeedbackLayer } from '@ldlework/feedback'
|
|
50
|
+
import '@ldlework/feedback/styles.css'
|
|
51
|
+
|
|
52
|
+
export function App() {
|
|
53
|
+
return (
|
|
54
|
+
<FeedbackProvider>
|
|
55
|
+
<YourApp />
|
|
56
|
+
<FeedbackLayer />
|
|
57
|
+
</FeedbackProvider>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Inner scroll & sections
|
|
63
|
+
|
|
64
|
+
When your content scrolls inside a panel rather than the window, wrap that content in a `<FeedbackRegion>` โ inside whatever scroll container you already have. Notes then live in the region: they ride along with the scroll and clip with the pane.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { FeedbackRegion } from '@ldlework/feedback'
|
|
68
|
+
|
|
69
|
+
function DocumentPane() {
|
|
70
|
+
return (
|
|
71
|
+
<div style={{ overflow: 'auto', height: '100vh' }}>
|
|
72
|
+
<FeedbackRegion anchorX="left">
|
|
73
|
+
{/* content */}
|
|
74
|
+
</FeedbackRegion>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A `section` is an optional sub-scope of a page โ notes tagged with one only show while that section is active, so a carousel step or tab doesn't leak its notes onto its siblings. Provide `onReveal` so review can switch to a section before it scrolls to a note:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<FeedbackRegion section={currentStepId} onReveal={(stepId) => goToStep(stepId)}>
|
|
84
|
+
{steps[currentStepId]}
|
|
85
|
+
</FeedbackRegion>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Submitting to a server
|
|
89
|
+
|
|
90
|
+
By default feedback leaves the browser only as a downloaded file. Give the provider a `submit` target and the dock adds a send button that delivers the document over the network instead โ the button reports sending, sent, and failure states, and file export stays available alongside it.
|
|
91
|
+
|
|
92
|
+
An endpoint URL is the whole integration โ the document is POSTed there as JSON with a `submittedAt` timestamp:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<FeedbackProvider submit="https://api.example.com/feedback">
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Need auth headers or a different method? Build the submitter with `endpointSubmitter`:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { endpointSubmitter } from '@ldlework/feedback'
|
|
102
|
+
|
|
103
|
+
<FeedbackProvider
|
|
104
|
+
submit={endpointSubmitter('https://api.example.com/feedback', {
|
|
105
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
106
|
+
credentials: 'include',
|
|
107
|
+
})}
|
|
108
|
+
>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or supply any `(doc: FeedbackDoc) => Promise<void>` for full control โ an SDK call, a queue, whatever. Throw (or reject) to signal failure; the dock shows the error state and the reviewer can retry.
|
|
112
|
+
|
|
113
|
+
## The coordinate model
|
|
114
|
+
|
|
115
|
+
A note remembers where it belongs in the **content**, not on the screen. Its `y` is measured from the top of the content; its `x` is measured from an **anchor line** chosen by `anchorX`:
|
|
116
|
+
|
|
117
|
+
- **`left`** โ x from the content's left edge (absolute).
|
|
118
|
+
- **`center`** *(default)* โ x signed from the horizontal centre line, so a note dropped on a centred column stays glued to it as the window narrows.
|
|
119
|
+
- **`right`** โ x from the content's right edge.
|
|
120
|
+
|
|
121
|
+
Because positions are stored this way, they survive a resize the way the content itself does. A **region** is the element those coordinates live in โ the document/viewport by default, or the content you wrapped in a `<FeedbackRegion>`.
|
|
122
|
+
|
|
123
|
+
## Router integration
|
|
124
|
+
|
|
125
|
+
Review mode walks notes across pages. With no configuration it reads `location.pathname` and navigates through the History API. If you use a router, hand it an adapter:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { useMemo } from 'react'
|
|
129
|
+
import { FeedbackProvider, type FeedbackNavigation } from '@ldlework/feedback'
|
|
130
|
+
import { useLocation, useNavigate } from 'react-router-dom'
|
|
131
|
+
|
|
132
|
+
function Providers({ children }: { children: React.ReactNode }) {
|
|
133
|
+
const { pathname } = useLocation()
|
|
134
|
+
const navigate = useNavigate()
|
|
135
|
+
const navigation = useMemo<FeedbackNavigation>(
|
|
136
|
+
() => ({ page: pathname, navigate: (to) => navigate(to) }),
|
|
137
|
+
[pathname, navigate],
|
|
138
|
+
)
|
|
139
|
+
return <FeedbackProvider navigation={navigation}>{children}</FeedbackProvider>
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Theming
|
|
144
|
+
|
|
145
|
+
Every class is `fb-` prefixed and every colour is a `--fb-*` custom property scoped to `.fb-root`, so nothing leaks in or out. Re-skin the whole overlay from one knob:
|
|
146
|
+
|
|
147
|
+
```css
|
|
148
|
+
/* Shift the accent and the chrome together to a cool blue. */
|
|
149
|
+
.fb-root { --fb-hue: 220; }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Or override individual tokens:
|
|
153
|
+
|
|
154
|
+
```css
|
|
155
|
+
.fb-root {
|
|
156
|
+
--fb-accent: #7c5cff;
|
|
157
|
+
--fb-accent-contrast: #ffffff;
|
|
158
|
+
--fb-surface-overlay: #1b1b2b;
|
|
159
|
+
--fb-radius: 10px;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
You can also scope a theme to one provider โ `className` and `style` are applied to every portal root, including the ones the overlay teleports to.
|
|
164
|
+
|
|
165
|
+
Key tokens: `--fb-hue`, `--fb-accent`, `--fb-accent-hover`, `--fb-accent-contrast`, `--fb-surface`, `--fb-surface-raised`, `--fb-surface-overlay`, `--fb-surface-sunken`, `--fb-border`, `--fb-border-strong`, `--fb-text`, `--fb-text-secondary`, `--fb-text-muted`, `--fb-success`, `--fb-danger`, `--fb-radius`, `--fb-shadow`, `--fb-font`.
|
|
166
|
+
|
|
167
|
+
## API
|
|
168
|
+
|
|
169
|
+
### `<FeedbackProvider>`
|
|
170
|
+
|
|
171
|
+
| Prop | Type | Default โ description |
|
|
172
|
+
| --- | --- | --- |
|
|
173
|
+
| `storageKey` | `string` | `"feedback"` โ localStorage key the document is saved under. |
|
|
174
|
+
| `fileName` | `string` | `"feedback.json"` โ suggested name for exported files. |
|
|
175
|
+
| `navigation` | `FeedbackNavigation` | History-API adapter โ `{ page, navigate }` for router integration. |
|
|
176
|
+
| `submit` | `string \| FeedbackSubmitter` | Unset โ an endpoint URL (document POSTed as JSON) or a custom async submitter; adds a send button to the dock. |
|
|
177
|
+
| `defaultAnchorX` | `AnchorX` | `"center"` โ x anchoring for pages that mount no region. |
|
|
178
|
+
| `className` | `string` | Extra class on every portal root โ e.g. a theme scope. |
|
|
179
|
+
| `style` | `CSSProperties` | CSS-variable overrides applied to every portal root. |
|
|
180
|
+
|
|
181
|
+
### `<FeedbackRegion>`
|
|
182
|
+
|
|
183
|
+
| Prop | Type | Description |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| `anchorX` | `'left' \| 'center' \| 'right'` | How x is anchored within this region. |
|
|
186
|
+
| `section` | `string \| null` | A sub-scope so notes on one view don't leak onto siblings. |
|
|
187
|
+
| `onReveal` | `(section: string) => void` | How review reveals a section before scrolling to a note. |
|
|
188
|
+
| `className`, `style` | | Applied to the wrapper element (`position` is reserved). |
|
|
189
|
+
|
|
190
|
+
### Also exported
|
|
191
|
+
|
|
192
|
+
`FeedbackLayer` (the overlay, no props) ยท types `FeedbackProviderProps`, `FeedbackRegionProps`, `FeedbackNavigation`, `FeedbackSubmitter`, `EndpointOptions`, `AnchorX`, `Region`, `FeedbackNote`, `FeedbackEdge`, `FeedbackDoc`, `EdgeTarget`, `Port` ยท helpers `endpointSubmitter(url, options)`, `normalizeDoc(json)`, and `EMPTY_DOC` for reading an untrusted file back into a document.
|
|
193
|
+
|
|
194
|
+
## File format
|
|
195
|
+
|
|
196
|
+
Export writes one JSON file โ an `exportedAt` timestamp plus the notes and edges. Each arrow carries a `target` (a CSS path and a text excerpt of the element it points at):
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"exportedAt": "2026-07-08T12:00:00.000Z",
|
|
201
|
+
"notes": [
|
|
202
|
+
{ "id": "โฆ", "page": "/pricing", "section": null,
|
|
203
|
+
"x": -120, "y": 480, "text": "This CTA is unclear", "resolved": false }
|
|
204
|
+
],
|
|
205
|
+
"edges": [
|
|
206
|
+
{ "id": "โฆ", "page": "/pricing", "section": null, "noteId": "โฆ",
|
|
207
|
+
"port": "right", "x": 40, "y": 500,
|
|
208
|
+
"target": { "selector": "section.hero > button.cta", "text": "Start free" } }
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Contributing
|
|
214
|
+
|
|
215
|
+
This is a pnpm workspace: the library at the root, the demo site in `site/`.
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
pnpm install
|
|
219
|
+
pnpm build # build the library (tsup โ dist/)
|
|
220
|
+
pnpm --filter feedback-site dev # run the demo + docs site
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The site builds the library straight from source, so there's no build-then-run dance during development.
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT ยฉ [Dustin Lacewell](https://github.com/dustinlacewell)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
/** Which knob on a note's border an edge grows from. */
|
|
5
|
+
type Port = 'left' | 'bottom' | 'right';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The coordinate system.
|
|
9
|
+
*
|
|
10
|
+
* A note stores its position in *content* coordinates โ independent of
|
|
11
|
+
* how the page is scrolled or where its region sits on screen. `y` is
|
|
12
|
+
* measured from the content top; `x` is measured from an **anchor line**
|
|
13
|
+
* chosen by `anchorX`, so positions survive a resize the way the content
|
|
14
|
+
* itself does:
|
|
15
|
+
*
|
|
16
|
+
* - `left` โ x from the content's left edge (absolute).
|
|
17
|
+
* - `center` โ x signed from the horizontal centre line. A note dropped
|
|
18
|
+
* on a centred column stays glued to it as the window narrows. This is
|
|
19
|
+
* the default, because most sites centre their main content.
|
|
20
|
+
* - `right` โ x from the content's right edge.
|
|
21
|
+
*
|
|
22
|
+
* A **region** is the element those coordinates live in: either the
|
|
23
|
+
* document/viewport (the default) or the content a page wrapped in a
|
|
24
|
+
* `<FeedbackRegion>`. This module is the single seam that converts a live
|
|
25
|
+
* pointer into stored coordinates and back; every part above it stays
|
|
26
|
+
* origin-agnostic.
|
|
27
|
+
*/
|
|
28
|
+
type AnchorX = 'left' | 'center' | 'right';
|
|
29
|
+
interface Region {
|
|
30
|
+
/** The bound content element, or null for the document/viewport. */
|
|
31
|
+
element: HTMLElement | null;
|
|
32
|
+
/** Which edge of the content x is measured from. */
|
|
33
|
+
anchorX: AnchorX;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The feedback document: notes and edges, plus the coercion that turns
|
|
38
|
+
* untrusted data (a loaded file, or restored storage) into a valid one.
|
|
39
|
+
*
|
|
40
|
+
* Notes are the sticky notes a reviewer drops; edges are arrows drawn
|
|
41
|
+
* from a note's knob to a free point. Coordinates are stored in content
|
|
42
|
+
* space (see `coordinates.ts`) so they track the layout across window
|
|
43
|
+
* sizes. Pure data โ the React binding lives in `useFeedbackDoc`.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
interface FeedbackNote {
|
|
47
|
+
id: string;
|
|
48
|
+
/** Top-level scope the note was dropped on (a route/path); revisited during review. */
|
|
49
|
+
page: string;
|
|
50
|
+
/** Sub-scope within the page (e.g. a carousel step); null spans the page. */
|
|
51
|
+
section: string | null;
|
|
52
|
+
/** Stored in content coordinates โ see `coordinates.ts`. */
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
text: string;
|
|
56
|
+
/** Marked done during review; kept, not deleted. */
|
|
57
|
+
resolved: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** The page element an arrow lands on, addressed for later identification. */
|
|
60
|
+
interface EdgeTarget {
|
|
61
|
+
/** A CSS path carrying every class at each level, e.g. `article.prose > p.lead`. */
|
|
62
|
+
selector: string;
|
|
63
|
+
/** A short text excerpt from the element, or null when it has none. */
|
|
64
|
+
text: string | null;
|
|
65
|
+
}
|
|
66
|
+
interface FeedbackEdge {
|
|
67
|
+
id: string;
|
|
68
|
+
page: string;
|
|
69
|
+
section: string | null;
|
|
70
|
+
/** The note this arrow's tail is pinned to. */
|
|
71
|
+
noteId: string;
|
|
72
|
+
/** Which of the note's knobs the tail grows from. */
|
|
73
|
+
port: Port;
|
|
74
|
+
/** The arrowhead, in the same content coordinates as a note. */
|
|
75
|
+
x: number;
|
|
76
|
+
y: number;
|
|
77
|
+
/** What the arrowhead points at, captured on draw and re-aim. */
|
|
78
|
+
target: EdgeTarget | null;
|
|
79
|
+
}
|
|
80
|
+
interface FeedbackDoc {
|
|
81
|
+
notes: FeedbackNote[];
|
|
82
|
+
edges: FeedbackEdge[];
|
|
83
|
+
}
|
|
84
|
+
declare const EMPTY_DOC: FeedbackDoc;
|
|
85
|
+
/**
|
|
86
|
+
* Coerce untrusted data into a document. Accepts a `{ notes, edges }`
|
|
87
|
+
* envelope, or a bare notes array from an older export. Edges whose note
|
|
88
|
+
* is gone are dropped.
|
|
89
|
+
*/
|
|
90
|
+
declare function normalizeDoc(data: unknown): FeedbackDoc;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* How feedback learns which "page" it's on and how to move between them.
|
|
94
|
+
*
|
|
95
|
+
* A note remembers the page it was left on so review can return to it.
|
|
96
|
+
* Apps that route client-side (React Router, TanStack Router, Next) pass
|
|
97
|
+
* their own adapter so review navigates through the router rather than
|
|
98
|
+
* reloading. With no adapter, feedback reads `location.pathname` and
|
|
99
|
+
* navigates by pushing history state โ enough for a single-page site.
|
|
100
|
+
*/
|
|
101
|
+
interface FeedbackNavigation {
|
|
102
|
+
/** The current page identity โ the scope a new note is filed under. */
|
|
103
|
+
page: string;
|
|
104
|
+
/** Move to a page so review can reveal a note left there. */
|
|
105
|
+
navigate: (page: string) => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Submission: the network counterpart to file export. A page that has
|
|
110
|
+
* somewhere to send feedback configures `<FeedbackProvider submit>` with
|
|
111
|
+
* either an endpoint URL โ the document is POSTed there as JSON โ or its
|
|
112
|
+
* own async function for anything richer (auth flows, an SDK, a queue).
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/** Delivers a document to wherever feedback is collected. Throw to signal failure. */
|
|
116
|
+
type FeedbackSubmitter = (doc: FeedbackDoc) => Promise<void>;
|
|
117
|
+
interface EndpointOptions {
|
|
118
|
+
/** HTTP method. Default `POST`. */
|
|
119
|
+
method?: string;
|
|
120
|
+
/** Extra headers merged over the JSON content type โ e.g. authorization. */
|
|
121
|
+
headers?: Record<string, string>;
|
|
122
|
+
/** Passed through to `fetch`, for cookie-authenticated endpoints. */
|
|
123
|
+
credentials?: RequestCredentials;
|
|
124
|
+
}
|
|
125
|
+
/** Send the document as a JSON request to `url`; any non-2xx response is a failure. */
|
|
126
|
+
declare function endpointSubmitter(url: string, options?: EndpointOptions): FeedbackSubmitter;
|
|
127
|
+
|
|
128
|
+
interface FeedbackProviderProps {
|
|
129
|
+
children: ReactNode;
|
|
130
|
+
/** localStorage key the document is saved under. Default `"feedback"`. */
|
|
131
|
+
storageKey?: string;
|
|
132
|
+
/** Suggested name for exported files. Default `"feedback.json"`. */
|
|
133
|
+
fileName?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Seeds the document the first time `storageKey` is used, so a page can
|
|
136
|
+
* ship with notes already on it โ a live demo, or a review handed back
|
|
137
|
+
* to its author. Ignored once anything has been stored under the key.
|
|
138
|
+
*/
|
|
139
|
+
initialDoc?: FeedbackDoc;
|
|
140
|
+
/** Router adapter. Default reads `location.pathname`. */
|
|
141
|
+
navigation?: FeedbackNavigation;
|
|
142
|
+
/**
|
|
143
|
+
* Where "send feedback" delivers the document: an endpoint URL (the
|
|
144
|
+
* document is POSTed there as JSON) or a custom async submitter.
|
|
145
|
+
* Omitted, the dock offers file export only.
|
|
146
|
+
*/
|
|
147
|
+
submit?: string | FeedbackSubmitter;
|
|
148
|
+
/** How x is anchored on pages that mount no region. Default `"center"`. */
|
|
149
|
+
defaultAnchorX?: AnchorX;
|
|
150
|
+
/** Extra class on every portal root โ e.g. a theme scope. */
|
|
151
|
+
className?: string;
|
|
152
|
+
/** CSS-variable overrides applied to every portal root. */
|
|
153
|
+
style?: CSSProperties;
|
|
154
|
+
}
|
|
155
|
+
declare function FeedbackProvider({ children, storageKey, fileName, navigation, defaultAnchorX, className, style, initialDoc, submit, }: FeedbackProviderProps): react.JSX.Element;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The always-on feedback layer. Mount it once inside a
|
|
159
|
+
* `<FeedbackProvider>` and it draws itself: the overlay of notes and
|
|
160
|
+
* arrows portals into the active region; the dock, review bar, and help
|
|
161
|
+
* pin to the viewport. Wiring only โ every behaviour lives in its own
|
|
162
|
+
* delegate.
|
|
163
|
+
*/
|
|
164
|
+
declare function FeedbackLayer(): react.JSX.Element;
|
|
165
|
+
|
|
166
|
+
interface FeedbackRegionProps {
|
|
167
|
+
children: ReactNode;
|
|
168
|
+
/** How x is anchored within this region. Overrides the provider default. */
|
|
169
|
+
anchorX?: AnchorX;
|
|
170
|
+
/** A sub-scope so notes on one view don't leak onto its siblings. */
|
|
171
|
+
section?: string | null;
|
|
172
|
+
/** How review asks this page to reveal a section before scrolling to a note. */
|
|
173
|
+
onReveal?: (section: string) => void;
|
|
174
|
+
/** Extra class on the wrapper element. */
|
|
175
|
+
className?: string;
|
|
176
|
+
/** Extra styles on the wrapper element (`position` is reserved). */
|
|
177
|
+
style?: CSSProperties;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The element notes live in. Wrap your page's content โ inside whatever
|
|
181
|
+
* scroll container you already have โ and notes drop into it, ride along
|
|
182
|
+
* with its scroll, and measure their coordinates against it. Pages that
|
|
183
|
+
* scroll the window and have no sub-scopes don't need this at all โ the
|
|
184
|
+
* layer works out of the box. Reach for it to keep notes inside a
|
|
185
|
+
* scrollable pane, change how x is anchored, or split a page into
|
|
186
|
+
* review-navigable sections.
|
|
187
|
+
*/
|
|
188
|
+
declare function FeedbackRegion({ children, anchorX, section, onReveal, className, style }: FeedbackRegionProps): react.JSX.Element;
|
|
189
|
+
|
|
190
|
+
export { type AnchorX, EMPTY_DOC, type EdgeTarget, type EndpointOptions, type FeedbackDoc, type FeedbackEdge, FeedbackLayer, type FeedbackNavigation, type FeedbackNote, FeedbackProvider, type FeedbackProviderProps, FeedbackRegion, type FeedbackRegionProps, type FeedbackSubmitter, type Port, type Region, endpointSubmitter, normalizeDoc };
|