@userz-ai/react 1.0.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/README.md +111 -0
- package/dist/index.d.mts +89 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +317 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @userz-ai/react
|
|
2
|
+
|
|
3
|
+
Idiomatic React bindings for the [Userz](https://userz.ai) feedback widget. A provider, a hook, and a component-targeting wrapper around [`@userz-ai/browser`](https://www.npmjs.com/package/@userz-ai/browser).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @userz-ai/react @userz-ai/browser
|
|
9
|
+
# Optional peer for richer screenshots
|
|
10
|
+
pnpm add @zumer/snapdom
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
React 18 and 19 are both supported.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// app/Userz.tsx — client component
|
|
19
|
+
'use client';
|
|
20
|
+
import { UserzProvider } from '@userz-ai/react';
|
|
21
|
+
|
|
22
|
+
export function Userz({ children }: { children: React.ReactNode }) {
|
|
23
|
+
return (
|
|
24
|
+
<UserzProvider
|
|
25
|
+
publicKey="pub_..."
|
|
26
|
+
apiUrl="https://api.userz.ai"
|
|
27
|
+
// Private mode: return a JWT minted by your backend (see @userz-ai/node).
|
|
28
|
+
getUserToken={async () => sessionStore.getUserzToken()}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</UserzProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// Anywhere underneath the provider:
|
|
38
|
+
import { useUserz } from '@userz-ai/react';
|
|
39
|
+
|
|
40
|
+
function Header() {
|
|
41
|
+
const userz = useUserz();
|
|
42
|
+
return <button onClick={() => userz.open()}>Send feedback</button>;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The provider accepts every `UserzConfig` field from `@userz-ai/browser` (`publicKey`, `apiUrl`, `getUserToken`, `bubble`, `showEmailField`, `consoleCapacity`, `captureErrors`, `targetingChord`, `initialUser`). It owns one `Userz` instance for the lifetime of the component and tears it down on unmount.
|
|
47
|
+
|
|
48
|
+
> Like Sentry / Datadog SDKs, **config changes after the first render are ignored**. Use `setUser()` and `setMetadata()` via the hook for runtime updates.
|
|
49
|
+
|
|
50
|
+
## `useUserz()`
|
|
51
|
+
|
|
52
|
+
Access the instance imperatively:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
function IdentityBridge({ user }: { user: User | null }) {
|
|
56
|
+
const userz = useUserz();
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
userz.setUser(
|
|
59
|
+
user ? { externalUserId: user.id, email: user.email } : null,
|
|
60
|
+
);
|
|
61
|
+
}, [user, userz]);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## `<UserzTarget>`
|
|
67
|
+
|
|
68
|
+
Mark a child as a "feedback target" the end-user can click while the targeting overlay is active (`Ctrl+Shift+U` by default). On click, the panel opens pre-filled with the target's `name`, your `meta` payload, and a cropped screenshot of just that element.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { UserzTarget } from '@userz-ai/react';
|
|
72
|
+
|
|
73
|
+
<UserzTarget name="CheckoutButton" meta={{ variant, plan: 'pro' }}>
|
|
74
|
+
<button onClick={onCheckout}>Checkout</button>
|
|
75
|
+
</UserzTarget>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Prop | Required | Notes |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `name` | yes | Display name surfaced as the targeted-component label in feedback. |
|
|
81
|
+
| `meta` | no | Opt-in metadata shipped with the report. Keep it small + non-sensitive — it lands in our backend in plaintext. |
|
|
82
|
+
| `children` | yes | A **single** child element. We clone it and attach a ref — no wrapper div, layout is unchanged. |
|
|
83
|
+
|
|
84
|
+
The child must accept a ref. DOM elements (`<button>`, `<div>`) and `forwardRef` components are fine. Plain function components without `forwardRef` get a `display: contents` wrapper — your layout still works, but `forwardRef` is preferred.
|
|
85
|
+
|
|
86
|
+
## SSR / Next.js
|
|
87
|
+
|
|
88
|
+
The widget is browser-only. Mount the provider in a client component and render it from a server component:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// app/layout.tsx — server component
|
|
92
|
+
import { Userz } from './Userz';
|
|
93
|
+
|
|
94
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
95
|
+
return (
|
|
96
|
+
<html>
|
|
97
|
+
<body>
|
|
98
|
+
<Userz>{children}</Userz>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Docs
|
|
106
|
+
|
|
107
|
+
Full reference at [userz.ai/docs/sdk/react](https://userz.ai/docs/sdk/react). Source on [GitHub](https://github.com/UserzFeedback/userz).
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Userz, UserzConfig } from "@userz-ai/browser";
|
|
2
|
+
import { CSSProperties, ReactNode } from "react";
|
|
3
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/provider.d.ts
|
|
6
|
+
interface UserzProviderProps extends UserzConfig {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* React provider that owns one Userz instance for the lifetime of the
|
|
11
|
+
* component tree. Re-renders DON'T tear down the widget — config changes
|
|
12
|
+
* after the first render are ignored (same as Sentry/Datadog SDKs). Use
|
|
13
|
+
* `useUserz()` to call methods (setUser, submit, …) imperatively.
|
|
14
|
+
*/
|
|
15
|
+
declare function UserzProvider({
|
|
16
|
+
children,
|
|
17
|
+
...config
|
|
18
|
+
}: UserzProviderProps): _$react_jsx_runtime0.JSX.Element;
|
|
19
|
+
declare function useUserz(): Userz;
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/ScreenshotEditor.d.ts
|
|
22
|
+
/**
|
|
23
|
+
* Annotated screenshot editor.
|
|
24
|
+
*
|
|
25
|
+
* Wraps tldraw to give the end-user a quick "circle the broken thing, type
|
|
26
|
+
* an arrow, save" workflow before submitting feedback. The tldraw bundle
|
|
27
|
+
* (~600KB) is dynamic-imported on first mount so apps that never open the
|
|
28
|
+
* editor don't pay for it; the component shows a loading state until ready.
|
|
29
|
+
*
|
|
30
|
+
* tldraw is declared as an OPTIONAL peer dep in package.json. Apps that
|
|
31
|
+
* want this component MUST install `tldraw` themselves; without it the
|
|
32
|
+
* component renders an instructive fallback instead of throwing.
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* <ScreenshotEditor
|
|
36
|
+
* blob={blob}
|
|
37
|
+
* onSave={(annotated) => userz.submit({ ..., attachments: [annotated] })}
|
|
38
|
+
* onCancel={() => setOpen(false)}
|
|
39
|
+
* />
|
|
40
|
+
*/
|
|
41
|
+
interface ScreenshotEditorProps {
|
|
42
|
+
/** PNG/JPEG blob to seed the canvas with. */
|
|
43
|
+
blob: Blob;
|
|
44
|
+
/** Optional dimensions override; default is the blob's natural size. */
|
|
45
|
+
width?: number;
|
|
46
|
+
height?: number;
|
|
47
|
+
/** Receives the annotated PNG. */
|
|
48
|
+
onSave: (annotated: Blob) => void | Promise<void>;
|
|
49
|
+
onCancel?: () => void;
|
|
50
|
+
/** Inline style override on the editor container. */
|
|
51
|
+
style?: CSSProperties;
|
|
52
|
+
className?: string;
|
|
53
|
+
/** Save-button color. Defaults to the Userz brand mint. */
|
|
54
|
+
brandColor?: string;
|
|
55
|
+
/** Save-button text color. Defaults to brand foreground. */
|
|
56
|
+
brandForeground?: string;
|
|
57
|
+
}
|
|
58
|
+
declare function ScreenshotEditor(props: ScreenshotEditorProps): ReactNode;
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/UserzTarget.d.ts
|
|
61
|
+
interface UserzTargetProps {
|
|
62
|
+
/** Stable display name for this target. Surfaces in feedback as the
|
|
63
|
+
* "component the user clicked on" label. */
|
|
64
|
+
name: string;
|
|
65
|
+
/** Opt-in metadata you want forwarded with feedback. Keep small + non-sensitive. */
|
|
66
|
+
meta?: Record<string, unknown>;
|
|
67
|
+
/** A single child element (no fragments). We attach a ref to it without
|
|
68
|
+
* a wrapper div so layout is unchanged. */
|
|
69
|
+
children: ReactNode;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Wraps a child element to mark it as a "feedback target" the end-user can
|
|
73
|
+
* click on while the targeting overlay is active (Ctrl+Shift+U by default).
|
|
74
|
+
*
|
|
75
|
+
* Implementation: clones the single child, attaches a ref, and registers/
|
|
76
|
+
* unregisters with the Userz instance over the child's lifecycle. We do NOT
|
|
77
|
+
* inject a wrapper div — that would break flex/grid layouts. As a result,
|
|
78
|
+
* the child must accept a `ref`; for most DOM elements and `forwardRef`
|
|
79
|
+
* components this is fine. For function components without forwardRef, we
|
|
80
|
+
* fall back to a `display: contents` wrapper.
|
|
81
|
+
*/
|
|
82
|
+
declare function UserzTarget({
|
|
83
|
+
name,
|
|
84
|
+
meta,
|
|
85
|
+
children
|
|
86
|
+
}: UserzTargetProps): _$react_jsx_runtime0.JSX.Element;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ScreenshotEditor, type ScreenshotEditorProps, UserzProvider, type UserzProviderProps, UserzTarget, type UserzTargetProps, useUserz };
|
|
89
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/provider.tsx","../src/ScreenshotEditor.tsx","../src/UserzTarget.tsx"],"mappings":";;;;;UAKiB,kBAAA,SAA2B,WAAA;EAC1C,QAAA,EAAU,SAAA;AAAA;AADZ;;;;;;AAAA,iBAUgB,aAAA,CAAA;EAAgB,QAAA;EAAA,GAAa;AAAA,GAAU,kBAAA,GAAkB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,iBAgCzD,QAAA,CAAA,GAAY,KAAA;;;;;;;AA1C5B;;;;;;;;;AAUA;;;;;;UCeiB,qBAAA;EDfwD;ECiBvE,IAAA,EAAM,IAAA;EDjBwB;ECmB9B,KAAA;EACA,MAAA;EDpBuE;ECsBvE,MAAA,GAAS,SAAA,EAAW,IAAA,YAAgB,OAAA;EACpC,QAAA;EDvBuE;ECyBvE,KAAA,GAAQ,aAAA;EACR,SAAA;;EAEA,UAAA;EDI+B;ECF/B,eAAA;AAAA;AAAA,iBAGc,gBAAA,CAAiB,KAAA,EAAO,qBAAA,GAAwB,SAAA;;;UCpC/C,gBAAA;;;EAGf,IAAA;EFVe;EEYf,IAAA,GAAO,MAAA;;;EAGP,QAAA,EAAU,SAAA;AAAA;;;;AFLZ;;;;;;;;iBEmBgB,WAAA,CAAA;EAAc,IAAA;EAAM,IAAA;EAAM;AAAA,GAAY,gBAAA,GAAgB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { UZ_DEFAULT_BRAND, UZ_DEFAULT_BRAND_FG, createUserz } from "@userz-ai/browser";
|
|
2
|
+
import { Children, Suspense, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/provider.tsx
|
|
6
|
+
const UserzCtx = createContext(null);
|
|
7
|
+
/**
|
|
8
|
+
* React provider that owns one Userz instance for the lifetime of the
|
|
9
|
+
* component tree. Re-renders DON'T tear down the widget — config changes
|
|
10
|
+
* after the first render are ignored (same as Sentry/Datadog SDKs). Use
|
|
11
|
+
* `useUserz()` to call methods (setUser, submit, …) imperatively.
|
|
12
|
+
*/
|
|
13
|
+
function UserzProvider({ children, ...config }) {
|
|
14
|
+
const instance = useRef(null);
|
|
15
|
+
if (instance.current === null && typeof document !== "undefined") instance.current = createUserz(config);
|
|
16
|
+
const initialUser = config.initialUser;
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (initialUser !== void 0) instance.current?.setUser(initialUser);
|
|
19
|
+
}, [initialUser]);
|
|
20
|
+
useEffect(() => () => {
|
|
21
|
+
instance.current?.destroy();
|
|
22
|
+
instance.current = null;
|
|
23
|
+
}, []);
|
|
24
|
+
const value = useMemo(() => instance.current, []);
|
|
25
|
+
return /* @__PURE__ */ jsx(UserzCtx.Provider, {
|
|
26
|
+
value,
|
|
27
|
+
children
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function useUserz() {
|
|
31
|
+
const u = useContext(UserzCtx);
|
|
32
|
+
if (!u) throw new Error("useUserz must be used inside <UserzProvider>");
|
|
33
|
+
return u;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/ScreenshotEditor.tsx
|
|
38
|
+
function ScreenshotEditor(props) {
|
|
39
|
+
return /* @__PURE__ */ jsx(Suspense, {
|
|
40
|
+
fallback: /* @__PURE__ */ jsx(EditorLoadingState, {}),
|
|
41
|
+
children: /* @__PURE__ */ jsx(LazyEditor, { ...props })
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function LazyEditor(props) {
|
|
45
|
+
const [tldraw, setTldraw] = useState(null);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
loadTldraw().then((mod) => {
|
|
49
|
+
if (!cancelled) setTldraw(mod ?? "missing");
|
|
50
|
+
}, () => {
|
|
51
|
+
if (!cancelled) setTldraw("missing");
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
if (tldraw === null) return /* @__PURE__ */ jsx(EditorLoadingState, {});
|
|
58
|
+
if (tldraw === "missing") return /* @__PURE__ */ jsx(MissingPeerFallback, {});
|
|
59
|
+
return /* @__PURE__ */ jsx(TldrawEditor, {
|
|
60
|
+
tldraw,
|
|
61
|
+
...props
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function loadTldraw() {
|
|
65
|
+
try {
|
|
66
|
+
const mod = await import(["tld", "raw"].join(""));
|
|
67
|
+
if (!mod.Tldraw) return null;
|
|
68
|
+
return mod;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function TldrawEditor({ tldraw, blob, width, height, onSave, onCancel, style, className, brandColor = UZ_DEFAULT_BRAND, brandForeground = UZ_DEFAULT_BRAND_FG }) {
|
|
74
|
+
const [editor, setEditor] = useState(null);
|
|
75
|
+
const [saving, setSaving] = useState(false);
|
|
76
|
+
const onMount = useCallback(async (ed) => {
|
|
77
|
+
setEditor(ed);
|
|
78
|
+
const url = URL.createObjectURL(blob);
|
|
79
|
+
try {
|
|
80
|
+
const dims = width && height ? {
|
|
81
|
+
w: width,
|
|
82
|
+
h: height
|
|
83
|
+
} : await measureBlob(blob);
|
|
84
|
+
const assetId = tldraw.AssetRecordType.createId();
|
|
85
|
+
const shapeId = tldraw.createShapeId();
|
|
86
|
+
const editorAny = ed;
|
|
87
|
+
editorAny.createAssets([{
|
|
88
|
+
id: assetId,
|
|
89
|
+
type: "image",
|
|
90
|
+
typeName: "asset",
|
|
91
|
+
props: {
|
|
92
|
+
name: "screenshot.png",
|
|
93
|
+
src: url,
|
|
94
|
+
w: dims.w,
|
|
95
|
+
h: dims.h,
|
|
96
|
+
mimeType: blob.type || "image/png",
|
|
97
|
+
isAnimated: false
|
|
98
|
+
},
|
|
99
|
+
meta: {}
|
|
100
|
+
}]);
|
|
101
|
+
editorAny.createShape({
|
|
102
|
+
id: shapeId,
|
|
103
|
+
type: "image",
|
|
104
|
+
x: 0,
|
|
105
|
+
y: 0,
|
|
106
|
+
props: {
|
|
107
|
+
assetId,
|
|
108
|
+
w: dims.w,
|
|
109
|
+
h: dims.h
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
} catch {}
|
|
113
|
+
}, [
|
|
114
|
+
tldraw,
|
|
115
|
+
blob,
|
|
116
|
+
width,
|
|
117
|
+
height
|
|
118
|
+
]);
|
|
119
|
+
const handleSave = useCallback(async () => {
|
|
120
|
+
if (!editor || saving) return;
|
|
121
|
+
setSaving(true);
|
|
122
|
+
try {
|
|
123
|
+
const editorAny = editor;
|
|
124
|
+
const ids = Array.from(editorAny.getCurrentPageShapeIds());
|
|
125
|
+
await onSave(await tldraw.exportAs(editor, ids, "png", { background: false }));
|
|
126
|
+
} finally {
|
|
127
|
+
setSaving(false);
|
|
128
|
+
}
|
|
129
|
+
}, [
|
|
130
|
+
editor,
|
|
131
|
+
saving,
|
|
132
|
+
tldraw,
|
|
133
|
+
onSave
|
|
134
|
+
]);
|
|
135
|
+
const TldrawCmp = tldraw.Tldraw;
|
|
136
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
137
|
+
className,
|
|
138
|
+
style: {
|
|
139
|
+
position: "relative",
|
|
140
|
+
width: "100%",
|
|
141
|
+
height: "60vh",
|
|
142
|
+
minHeight: 360,
|
|
143
|
+
...style
|
|
144
|
+
},
|
|
145
|
+
children: [/* @__PURE__ */ jsx(TldrawCmp, { onMount }), /* @__PURE__ */ jsxs("div", {
|
|
146
|
+
style: {
|
|
147
|
+
position: "absolute",
|
|
148
|
+
right: 12,
|
|
149
|
+
bottom: 12,
|
|
150
|
+
display: "flex",
|
|
151
|
+
gap: 8,
|
|
152
|
+
zIndex: 1e3
|
|
153
|
+
},
|
|
154
|
+
children: [onCancel && /* @__PURE__ */ jsx("button", {
|
|
155
|
+
type: "button",
|
|
156
|
+
onClick: onCancel,
|
|
157
|
+
style: editorButtonStyle("ghost", brandColor, brandForeground),
|
|
158
|
+
disabled: saving,
|
|
159
|
+
children: "Cancel"
|
|
160
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
161
|
+
type: "button",
|
|
162
|
+
onClick: handleSave,
|
|
163
|
+
style: editorButtonStyle("primary", brandColor, brandForeground),
|
|
164
|
+
disabled: saving || !editor,
|
|
165
|
+
children: saving ? "Saving…" : "Save annotation"
|
|
166
|
+
})]
|
|
167
|
+
})]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function EditorLoadingState() {
|
|
171
|
+
return /* @__PURE__ */ jsx("div", {
|
|
172
|
+
style: loadingStyle,
|
|
173
|
+
children: /* @__PURE__ */ jsx("span", { children: "Loading editor…" })
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function MissingPeerFallback() {
|
|
177
|
+
return /* @__PURE__ */ jsx("div", {
|
|
178
|
+
style: loadingStyle,
|
|
179
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
180
|
+
style: {
|
|
181
|
+
maxWidth: 360,
|
|
182
|
+
textAlign: "center",
|
|
183
|
+
lineHeight: 1.45
|
|
184
|
+
},
|
|
185
|
+
children: [/* @__PURE__ */ jsx("strong", { children: "Screenshot annotation unavailable." }), /* @__PURE__ */ jsxs("div", {
|
|
186
|
+
style: {
|
|
187
|
+
marginTop: 8,
|
|
188
|
+
fontSize: 13,
|
|
189
|
+
opacity: .8
|
|
190
|
+
},
|
|
191
|
+
children: [
|
|
192
|
+
"Install ",
|
|
193
|
+
/* @__PURE__ */ jsx("code", { children: "tldraw" }),
|
|
194
|
+
" in your app to enable the in-widget editor:",
|
|
195
|
+
/* @__PURE__ */ jsx("pre", {
|
|
196
|
+
style: {
|
|
197
|
+
marginTop: 8,
|
|
198
|
+
padding: 8,
|
|
199
|
+
background: "rgba(0,0,0,0.05)",
|
|
200
|
+
borderRadius: 6,
|
|
201
|
+
fontSize: 12
|
|
202
|
+
},
|
|
203
|
+
children: "pnpm add tldraw"
|
|
204
|
+
})
|
|
205
|
+
]
|
|
206
|
+
})]
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const loadingStyle = {
|
|
211
|
+
display: "flex",
|
|
212
|
+
alignItems: "center",
|
|
213
|
+
justifyContent: "center",
|
|
214
|
+
width: "100%",
|
|
215
|
+
height: "60vh",
|
|
216
|
+
minHeight: 360,
|
|
217
|
+
border: "1px dashed rgba(0,0,0,0.15)",
|
|
218
|
+
borderRadius: 8,
|
|
219
|
+
fontSize: 14,
|
|
220
|
+
color: "#555"
|
|
221
|
+
};
|
|
222
|
+
function editorButtonStyle(kind, brandColor, brandForeground) {
|
|
223
|
+
if (kind === "primary") return {
|
|
224
|
+
padding: "8px 14px",
|
|
225
|
+
borderRadius: 6,
|
|
226
|
+
border: "none",
|
|
227
|
+
background: brandColor,
|
|
228
|
+
color: brandForeground,
|
|
229
|
+
fontWeight: 600,
|
|
230
|
+
cursor: "pointer",
|
|
231
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.18)"
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
padding: "8px 14px",
|
|
235
|
+
borderRadius: 6,
|
|
236
|
+
border: "1px solid rgba(0,0,0,0.18)",
|
|
237
|
+
background: "white",
|
|
238
|
+
color: "#111",
|
|
239
|
+
cursor: "pointer"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function measureBlob(blob) {
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
const img = new Image();
|
|
245
|
+
const url = URL.createObjectURL(blob);
|
|
246
|
+
img.onload = () => {
|
|
247
|
+
URL.revokeObjectURL(url);
|
|
248
|
+
resolve({
|
|
249
|
+
w: img.naturalWidth,
|
|
250
|
+
h: img.naturalHeight
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
img.onerror = (err) => {
|
|
254
|
+
URL.revokeObjectURL(url);
|
|
255
|
+
reject(err);
|
|
256
|
+
};
|
|
257
|
+
img.src = url;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/UserzTarget.tsx
|
|
263
|
+
/**
|
|
264
|
+
* Wraps a child element to mark it as a "feedback target" the end-user can
|
|
265
|
+
* click on while the targeting overlay is active (Ctrl+Shift+U by default).
|
|
266
|
+
*
|
|
267
|
+
* Implementation: clones the single child, attaches a ref, and registers/
|
|
268
|
+
* unregisters with the Userz instance over the child's lifecycle. We do NOT
|
|
269
|
+
* inject a wrapper div — that would break flex/grid layouts. As a result,
|
|
270
|
+
* the child must accept a `ref`; for most DOM elements and `forwardRef`
|
|
271
|
+
* components this is fine. For function components without forwardRef, we
|
|
272
|
+
* fall back to a `display: contents` wrapper.
|
|
273
|
+
*/
|
|
274
|
+
function UserzTarget({ name, meta, children }) {
|
|
275
|
+
const u = useUserz();
|
|
276
|
+
const ref = useRef(null);
|
|
277
|
+
const stableMeta = useMemo(() => meta, [JSON.stringify(meta ?? null)]);
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
const el = ref.current;
|
|
280
|
+
if (!el) return;
|
|
281
|
+
return u.registerTarget({
|
|
282
|
+
el,
|
|
283
|
+
name,
|
|
284
|
+
meta: stableMeta
|
|
285
|
+
});
|
|
286
|
+
}, [
|
|
287
|
+
u,
|
|
288
|
+
name,
|
|
289
|
+
stableMeta
|
|
290
|
+
]);
|
|
291
|
+
const only = Children.only(children);
|
|
292
|
+
if (isValidElement(only) && (typeof only.type === "string" || hasForwardedRef(only))) {
|
|
293
|
+
const original = only.props.ref;
|
|
294
|
+
return cloneElement(only, { ref: composeRefs(ref, original) });
|
|
295
|
+
}
|
|
296
|
+
return /* @__PURE__ */ jsx("span", {
|
|
297
|
+
ref: (el) => {
|
|
298
|
+
ref.current = el;
|
|
299
|
+
},
|
|
300
|
+
style: { display: "contents" },
|
|
301
|
+
children: only
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function hasForwardedRef(el) {
|
|
305
|
+
const t = el.type;
|
|
306
|
+
return typeof t === "object" && typeof t.$$typeof?.toString === "function";
|
|
307
|
+
}
|
|
308
|
+
function composeRefs(...refs) {
|
|
309
|
+
return (node) => {
|
|
310
|
+
for (const r of refs) if (typeof r === "function") r(node);
|
|
311
|
+
else if (r && typeof r === "object" && "current" in r) r.current = node;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
export { ScreenshotEditor, UserzProvider, UserzTarget, useUserz };
|
|
317
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/provider.tsx","../src/ScreenshotEditor.tsx","../src/UserzTarget.tsx"],"sourcesContent":["import { createUserz, type Userz, type UserzConfig } from '@userz-ai/browser';\nimport { createContext, type ReactNode, useContext, useEffect, useMemo, useRef } from 'react';\n\nconst UserzCtx = createContext<Userz | null>(null);\n\nexport interface UserzProviderProps extends UserzConfig {\n children: ReactNode;\n}\n\n/**\n * React provider that owns one Userz instance for the lifetime of the\n * component tree. Re-renders DON'T tear down the widget — config changes\n * after the first render are ignored (same as Sentry/Datadog SDKs). Use\n * `useUserz()` to call methods (setUser, submit, …) imperatively.\n */\nexport function UserzProvider({ children, ...config }: UserzProviderProps) {\n const instance = useRef<Userz | null>(null);\n\n // Initialize once, lazily — and only in the browser. Next.js, Remix, and\n // any other RSC/SSR framework will render this provider on the server to\n // emit the initial HTML; `createUserz` touches `document` immediately so\n // running it server-side crashes the render. The widget has nothing to do\n // during SSR anyway (no bubble to paint, no console to capture), so we\n // defer construction to the client pass.\n if (instance.current === null && typeof document !== 'undefined') {\n instance.current = createUserz(config);\n }\n\n // Pass through identity/metadata updates if the parent passes them via prop\n // changes after mount.\n const initialUser = config.initialUser;\n useEffect(() => {\n if (initialUser !== undefined) instance.current?.setUser(initialUser);\n }, [initialUser]);\n\n useEffect(\n () => () => {\n instance.current?.destroy();\n instance.current = null;\n },\n [],\n );\n\n const value = useMemo(() => instance.current, []);\n return <UserzCtx.Provider value={value}>{children}</UserzCtx.Provider>;\n}\n\nexport function useUserz(): Userz {\n const u = useContext(UserzCtx);\n if (!u) throw new Error('useUserz must be used inside <UserzProvider>');\n return u;\n}\n","import { UZ_DEFAULT_BRAND, UZ_DEFAULT_BRAND_FG } from '@userz-ai/browser';\nimport {\n type CSSProperties,\n type ReactNode,\n Suspense,\n useCallback,\n useEffect,\n useState,\n} from 'react';\n\n/**\n * Annotated screenshot editor.\n *\n * Wraps tldraw to give the end-user a quick \"circle the broken thing, type\n * an arrow, save\" workflow before submitting feedback. The tldraw bundle\n * (~600KB) is dynamic-imported on first mount so apps that never open the\n * editor don't pay for it; the component shows a loading state until ready.\n *\n * tldraw is declared as an OPTIONAL peer dep in package.json. Apps that\n * want this component MUST install `tldraw` themselves; without it the\n * component renders an instructive fallback instead of throwing.\n *\n * Usage:\n * <ScreenshotEditor\n * blob={blob}\n * onSave={(annotated) => userz.submit({ ..., attachments: [annotated] })}\n * onCancel={() => setOpen(false)}\n * />\n */\n\nexport interface ScreenshotEditorProps {\n /** PNG/JPEG blob to seed the canvas with. */\n blob: Blob;\n /** Optional dimensions override; default is the blob's natural size. */\n width?: number;\n height?: number;\n /** Receives the annotated PNG. */\n onSave: (annotated: Blob) => void | Promise<void>;\n onCancel?: () => void;\n /** Inline style override on the editor container. */\n style?: CSSProperties;\n className?: string;\n /** Save-button color. Defaults to the Userz brand mint. */\n brandColor?: string;\n /** Save-button text color. Defaults to brand foreground. */\n brandForeground?: string;\n}\n\nexport function ScreenshotEditor(props: ScreenshotEditorProps): ReactNode {\n return (\n <Suspense fallback={<EditorLoadingState />}>\n <LazyEditor {...props} />\n </Suspense>\n );\n}\n\nfunction LazyEditor(props: ScreenshotEditorProps) {\n const [tldraw, setTldraw] = useState<TldrawModule | null | 'missing'>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadTldraw().then(\n (mod) => {\n if (!cancelled) setTldraw(mod ?? 'missing');\n },\n () => {\n if (!cancelled) setTldraw('missing');\n },\n );\n return () => {\n cancelled = true;\n };\n }, []);\n\n if (tldraw === null) return <EditorLoadingState />;\n if (tldraw === 'missing') return <MissingPeerFallback />;\n\n return <TldrawEditor tldraw={tldraw} {...props} />;\n}\n\ninterface TldrawModule {\n Tldraw: (props: Record<string, unknown>) => ReactNode;\n exportAs: (\n editor: unknown,\n shapeIds: unknown[],\n format: string,\n opts?: Record<string, unknown>,\n ) => Promise<Blob>;\n AssetRecordType: {\n createId(): string;\n };\n createShapeId(): string;\n}\n\nasync function loadTldraw(): Promise<TldrawModule | null> {\n try {\n // The string is split with a runtime expression so bundlers don't try\n // to follow the import; tldraw stays out of the build graph entirely\n // for apps that don't install it.\n const name = ['tld', 'raw'].join('');\n const mod = (await import(/* @vite-ignore */ name)) as Partial<TldrawModule>;\n if (!mod.Tldraw) return null;\n return mod as TldrawModule;\n } catch {\n return null;\n }\n}\n\ninterface TldrawEditorProps extends ScreenshotEditorProps {\n tldraw: TldrawModule;\n}\n\nfunction TldrawEditor({\n tldraw,\n blob,\n width,\n height,\n onSave,\n onCancel,\n style,\n className,\n brandColor = UZ_DEFAULT_BRAND,\n brandForeground = UZ_DEFAULT_BRAND_FG,\n}: TldrawEditorProps) {\n const [editor, setEditor] = useState<unknown>(null);\n const [saving, setSaving] = useState(false);\n\n const onMount = useCallback(\n async (ed: unknown) => {\n setEditor(ed);\n const url = URL.createObjectURL(blob);\n try {\n const dims = width && height ? { w: width, h: height } : await measureBlob(blob);\n const assetId = tldraw.AssetRecordType.createId();\n const shapeId = tldraw.createShapeId();\n const editorAny = ed as {\n createAssets: (assets: unknown[]) => void;\n createShape: (s: unknown) => void;\n };\n editorAny.createAssets([\n {\n id: assetId,\n type: 'image',\n typeName: 'asset',\n props: {\n name: 'screenshot.png',\n src: url,\n w: dims.w,\n h: dims.h,\n mimeType: blob.type || 'image/png',\n isAnimated: false,\n },\n meta: {},\n },\n ]);\n editorAny.createShape({\n id: shapeId,\n type: 'image',\n x: 0,\n y: 0,\n props: { assetId, w: dims.w, h: dims.h },\n });\n } catch {\n // best-effort seeding; user can still draw on a blank canvas\n }\n },\n [tldraw, blob, width, height],\n );\n\n const handleSave = useCallback(async () => {\n if (!editor || saving) return;\n setSaving(true);\n try {\n const editorAny = editor as { getCurrentPageShapeIds(): Set<unknown> };\n const ids = Array.from(editorAny.getCurrentPageShapeIds());\n const png = await tldraw.exportAs(editor, ids, 'png', { background: false });\n await onSave(png);\n } finally {\n setSaving(false);\n }\n }, [editor, saving, tldraw, onSave]);\n\n const TldrawCmp = tldraw.Tldraw as unknown as (p: {\n onMount: (ed: unknown) => void;\n persistenceKey?: string;\n }) => ReactNode;\n\n return (\n <div\n className={className}\n style={{\n position: 'relative',\n width: '100%',\n height: '60vh',\n minHeight: 360,\n ...style,\n }}\n >\n <TldrawCmp onMount={onMount} />\n <div\n style={{\n position: 'absolute',\n right: 12,\n bottom: 12,\n display: 'flex',\n gap: 8,\n zIndex: 1000,\n }}\n >\n {onCancel && (\n <button\n type=\"button\"\n onClick={onCancel}\n style={editorButtonStyle('ghost', brandColor, brandForeground)}\n disabled={saving}\n >\n Cancel\n </button>\n )}\n <button\n type=\"button\"\n onClick={handleSave}\n style={editorButtonStyle('primary', brandColor, brandForeground)}\n disabled={saving || !editor}\n >\n {saving ? 'Saving…' : 'Save annotation'}\n </button>\n </div>\n </div>\n );\n}\n\nfunction EditorLoadingState() {\n return (\n <div style={loadingStyle}>\n <span>Loading editor…</span>\n </div>\n );\n}\n\nfunction MissingPeerFallback() {\n return (\n <div style={loadingStyle}>\n <div style={{ maxWidth: 360, textAlign: 'center', lineHeight: 1.45 }}>\n <strong>Screenshot annotation unavailable.</strong>\n <div style={{ marginTop: 8, fontSize: 13, opacity: 0.8 }}>\n Install <code>tldraw</code> in your app to enable the in-widget editor:\n <pre\n style={{\n marginTop: 8,\n padding: 8,\n background: 'rgba(0,0,0,0.05)',\n borderRadius: 6,\n fontSize: 12,\n }}\n >\n pnpm add tldraw\n </pre>\n </div>\n </div>\n </div>\n );\n}\n\nconst loadingStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n height: '60vh',\n minHeight: 360,\n border: '1px dashed rgba(0,0,0,0.15)',\n borderRadius: 8,\n fontSize: 14,\n color: '#555',\n};\n\nfunction editorButtonStyle(\n kind: 'primary' | 'ghost',\n brandColor: string,\n brandForeground: string,\n): CSSProperties {\n if (kind === 'primary') {\n return {\n padding: '8px 14px',\n borderRadius: 6,\n border: 'none',\n background: brandColor,\n color: brandForeground,\n fontWeight: 600,\n cursor: 'pointer',\n boxShadow: '0 4px 12px rgba(0,0,0,0.18)',\n };\n }\n return {\n padding: '8px 14px',\n borderRadius: 6,\n border: '1px solid rgba(0,0,0,0.18)',\n background: 'white',\n color: '#111',\n cursor: 'pointer',\n };\n}\n\nasync function measureBlob(blob: Blob): Promise<{ w: number; h: number }> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n const url = URL.createObjectURL(blob);\n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve({ w: img.naturalWidth, h: img.naturalHeight });\n };\n img.onerror = (err) => {\n URL.revokeObjectURL(url);\n reject(err);\n };\n img.src = url;\n });\n}\n","import {\n Children,\n cloneElement,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport { useUserz } from './provider';\n\nexport interface UserzTargetProps {\n /** Stable display name for this target. Surfaces in feedback as the\n * \"component the user clicked on\" label. */\n name: string;\n /** Opt-in metadata you want forwarded with feedback. Keep small + non-sensitive. */\n meta?: Record<string, unknown>;\n /** A single child element (no fragments). We attach a ref to it without\n * a wrapper div so layout is unchanged. */\n children: ReactNode;\n}\n\n/**\n * Wraps a child element to mark it as a \"feedback target\" the end-user can\n * click on while the targeting overlay is active (Ctrl+Shift+U by default).\n *\n * Implementation: clones the single child, attaches a ref, and registers/\n * unregisters with the Userz instance over the child's lifecycle. We do NOT\n * inject a wrapper div — that would break flex/grid layouts. As a result,\n * the child must accept a `ref`; for most DOM elements and `forwardRef`\n * components this is fine. For function components without forwardRef, we\n * fall back to a `display: contents` wrapper.\n */\nexport function UserzTarget({ name, meta, children }: UserzTargetProps) {\n const u = useUserz();\n const ref = useRef<HTMLElement | null>(null);\n // Stable meta identity so the effect only re-runs when shape actually changes.\n // The dep is intentionally the JSON string, not `meta`, so callers passing a\n // fresh object literal each render don't thrash the registration.\n const metaJson = JSON.stringify(meta ?? null);\n // biome-ignore lint/correctness/useExhaustiveDependencies: see above\n const stableMeta = useMemo(() => meta, [metaJson]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n return u.registerTarget({ el, name, meta: stableMeta });\n }, [u, name, stableMeta]);\n\n const only = Children.only(children);\n if (isValidElement(only) && (typeof only.type === 'string' || hasForwardedRef(only))) {\n const original = (only.props as { ref?: unknown }).ref;\n return cloneElement(only as ReactElement<{ ref?: unknown }>, {\n ref: composeRefs(ref, original),\n });\n }\n // Function component without forwardRef — use a `display: contents` span\n // so we can attach a ref without affecting layout.\n return (\n <span\n ref={(el) => {\n ref.current = el;\n }}\n style={{ display: 'contents' }}\n >\n {only}\n </span>\n );\n}\n\nfunction hasForwardedRef(el: ReactElement): boolean {\n // Conservative heuristic — React.forwardRef components have a \"$$typeof\"\n // marker on their type. We can't import it portably across React versions,\n // so we check the symbol indirectly.\n const t = el.type as unknown as { $$typeof?: symbol };\n return typeof t === 'object' && typeof t.$$typeof?.toString === 'function';\n}\n\nfunction composeRefs<T>(...refs: unknown[]): (node: T) => void {\n return (node: T) => {\n for (const r of refs) {\n if (typeof r === 'function') (r as (n: T) => void)(node);\n else if (r && typeof r === 'object' && 'current' in r) {\n (r as { current: T }).current = node;\n }\n }\n };\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@userz-ai/react",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "React bindings for the Userz feedback widget.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
25
|
+
"tldraw": "^4.0.0",
|
|
26
|
+
"@userz-ai/browser": "0.2.1"
|
|
27
|
+
},
|
|
28
|
+
"peerDependenciesMeta": {
|
|
29
|
+
"tldraw": {
|
|
30
|
+
"optional": true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.6.0",
|
|
35
|
+
"@types/react": "^19.2.14",
|
|
36
|
+
"react": "^19.2.5",
|
|
37
|
+
"tldraw": "^4.5.9",
|
|
38
|
+
"tsdown": "^0.21.9",
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"@userz-ai/browser": "0.2.1"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"dev": "tsdown --watch",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
47
|
+
}
|
|
48
|
+
}
|