@perspective-ai/sdk-react 0.0.0-pr-21-20260224144030
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 +339 -0
- package/dist/index.cjs +607 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +175 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +597 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
- package/src/FloatBubble.test.tsx +134 -0
- package/src/FloatBubble.tsx +57 -0
- package/src/Fullpage.test.tsx +164 -0
- package/src/Fullpage.tsx +83 -0
- package/src/Widget.test.tsx +308 -0
- package/src/Widget.tsx +100 -0
- package/src/hooks/useAutoOpen.test.ts +168 -0
- package/src/hooks/useAutoOpen.ts +63 -0
- package/src/hooks/useFloatBubble.test.ts +91 -0
- package/src/hooks/useFloatBubble.ts +180 -0
- package/src/hooks/usePopup.test.ts +219 -0
- package/src/hooks/usePopup.ts +190 -0
- package/src/hooks/useSlider.test.ts +150 -0
- package/src/hooks/useSlider.ts +181 -0
- package/src/hooks/useStableCallback.test.ts +97 -0
- package/src/hooks/useStableCallback.ts +24 -0
- package/src/hooks/useThemeSync.test.ts +127 -0
- package/src/hooks/useThemeSync.ts +36 -0
- package/src/index.ts +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# @perspective-ai/sdk-react
|
|
2
|
+
|
|
3
|
+
React hooks and components for [Perspective AI](https://getperspective.ai).
|
|
4
|
+
|
|
5
|
+
> **Not using React?** Use [`@perspective-ai/sdk`](https://www.npmjs.com/package/@perspective-ai/sdk) for vanilla JavaScript.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @perspective-ai/sdk-react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Peer Dependencies:** React 18+ or 19+
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { usePopup } from "@perspective-ai/sdk-react";
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
const { open } = usePopup({
|
|
22
|
+
researchId: "your-research-id",
|
|
23
|
+
onSubmit: () => console.log("Interview completed!"),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return <button onClick={open}>Give Feedback</button>;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API Overview
|
|
31
|
+
|
|
32
|
+
| API | Type | Description |
|
|
33
|
+
| ---------------- | --------- | -------------------------------------- |
|
|
34
|
+
| `usePopup` | Hook | Open popup modal programmatically |
|
|
35
|
+
| `useSlider` | Hook | Open slider panel programmatically |
|
|
36
|
+
| `useAutoOpen` | Hook | Auto-trigger popup (timeout/exit) |
|
|
37
|
+
| `useFloatBubble` | Hook | Floating chat bubble lifecycle |
|
|
38
|
+
| `Widget` | Component | Inline embed in a container |
|
|
39
|
+
| `Fullpage` | Component | Full viewport takeover |
|
|
40
|
+
| `FloatBubble` | Component | Convenience wrapper for useFloatBubble |
|
|
41
|
+
|
|
42
|
+
**Mental Model:**
|
|
43
|
+
|
|
44
|
+
- **Hooks** for overlays (popup, slider, float bubble) - you control the trigger
|
|
45
|
+
- **Components** for embeds (widget, fullpage) - render inline DOM
|
|
46
|
+
|
|
47
|
+
## Hooks
|
|
48
|
+
|
|
49
|
+
### usePopup
|
|
50
|
+
|
|
51
|
+
Open a popup modal with your own trigger element.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { usePopup } from "@perspective-ai/sdk-react";
|
|
55
|
+
|
|
56
|
+
function App() {
|
|
57
|
+
const { open, close, isOpen } = usePopup({
|
|
58
|
+
researchId: "your-research-id",
|
|
59
|
+
onSubmit: () => console.log("Done!"),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
<button onClick={open}>Take Survey</button>
|
|
65
|
+
{isOpen && <span>Survey is open</span>}
|
|
66
|
+
</>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Programmatic trigger:**
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
const { open } = usePopup({ researchId: "xxx" });
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (userCompletedCheckout) {
|
|
78
|
+
open();
|
|
79
|
+
}
|
|
80
|
+
}, [userCompletedCheckout, open]);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Controlled mode:**
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
87
|
+
|
|
88
|
+
const popup = usePopup({
|
|
89
|
+
researchId: "xxx",
|
|
90
|
+
open: isOpen,
|
|
91
|
+
onOpenChange: setIsOpen,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// External control
|
|
95
|
+
<button onClick={() => setIsOpen(true)}>Open from anywhere</button>;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### useSlider
|
|
99
|
+
|
|
100
|
+
Open a slider panel with your own trigger element.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { useSlider } from "@perspective-ai/sdk-react";
|
|
104
|
+
|
|
105
|
+
function App() {
|
|
106
|
+
const { open, close, isOpen } = useSlider({
|
|
107
|
+
researchId: "your-research-id",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return <button onClick={open}>Open Feedback Panel</button>;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### useAutoOpen
|
|
115
|
+
|
|
116
|
+
Auto-trigger a popup based on a timeout or exit intent — no user click needed.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { useAutoOpen } from "@perspective-ai/sdk-react";
|
|
120
|
+
|
|
121
|
+
function FeedbackTrigger() {
|
|
122
|
+
const { cancel, triggered } = useAutoOpen({
|
|
123
|
+
researchId: "your-research-id",
|
|
124
|
+
trigger: { type: "timeout", delay: 5000 },
|
|
125
|
+
showOnce: "session",
|
|
126
|
+
onSubmit: () => console.log("Completed!"),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Renders nothing — popup opens automatically after 5s
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Exit intent:**
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
useAutoOpen({
|
|
138
|
+
researchId: "your-research-id",
|
|
139
|
+
trigger: { type: "exit-intent" },
|
|
140
|
+
showOnce: "visitor",
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Options:**
|
|
145
|
+
|
|
146
|
+
| Option | Type | Default | Description |
|
|
147
|
+
| ------------ | --------------- | ----------- | ------------------------------------------------------------------------ |
|
|
148
|
+
| `researchId` | `string` | — | Research ID (required) |
|
|
149
|
+
| `trigger` | `TriggerConfig` | — | `{ type: "timeout", delay: ms }` or `{ type: "exit-intent" }` (required) |
|
|
150
|
+
| `showOnce` | `ShowOnce` | `"session"` | `"session"`, `"visitor"`, or `false` |
|
|
151
|
+
|
|
152
|
+
Plus all standard `EmbedConfig` options (`theme`, `params`, `brand`, callbacks).
|
|
153
|
+
|
|
154
|
+
**Returns:**
|
|
155
|
+
|
|
156
|
+
| Property | Type | Description |
|
|
157
|
+
| ----------- | ------------ | ----------------------------- |
|
|
158
|
+
| `cancel` | `() => void` | Cancel the pending trigger |
|
|
159
|
+
| `triggered` | `boolean` | Whether the trigger has fired |
|
|
160
|
+
|
|
161
|
+
### useFloatBubble
|
|
162
|
+
|
|
163
|
+
Manage a floating chat bubble lifecycle.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import { useFloatBubble } from "@perspective-ai/sdk-react";
|
|
167
|
+
|
|
168
|
+
function App() {
|
|
169
|
+
const { open, close, isOpen } = useFloatBubble({
|
|
170
|
+
researchId: "your-research-id",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Bubble mounts on component mount
|
|
174
|
+
// Use open/close for programmatic control
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Components
|
|
180
|
+
|
|
181
|
+
### Widget
|
|
182
|
+
|
|
183
|
+
Inline embed that renders in a container.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { Widget } from "@perspective-ai/sdk-react";
|
|
187
|
+
|
|
188
|
+
<Widget
|
|
189
|
+
researchId="your-research-id"
|
|
190
|
+
onSubmit={() => console.log("Done!")}
|
|
191
|
+
className="my-widget"
|
|
192
|
+
style={{ height: 600 }}
|
|
193
|
+
/>;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Fullpage
|
|
197
|
+
|
|
198
|
+
Full viewport takeover embed.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import { Fullpage } from "@perspective-ai/sdk-react";
|
|
202
|
+
|
|
203
|
+
<Fullpage researchId="your-research-id" />;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### FloatBubble
|
|
207
|
+
|
|
208
|
+
Convenience wrapper around `useFloatBubble` hook.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { FloatBubble } from "@perspective-ai/sdk-react";
|
|
212
|
+
|
|
213
|
+
<FloatBubble researchId="your-research-id" />;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Hook Options
|
|
217
|
+
|
|
218
|
+
All hooks accept options from `EmbedConfig`:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
interface UsePopupOptions {
|
|
222
|
+
researchId: string;
|
|
223
|
+
host?: string;
|
|
224
|
+
theme?: "light" | "dark" | "system";
|
|
225
|
+
params?: Record<string, string>;
|
|
226
|
+
brand?: {
|
|
227
|
+
light?: {
|
|
228
|
+
primary?: string;
|
|
229
|
+
secondary?: string;
|
|
230
|
+
bg?: string;
|
|
231
|
+
text?: string;
|
|
232
|
+
};
|
|
233
|
+
dark?: { primary?: string; secondary?: string; bg?: string; text?: string };
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Callbacks
|
|
237
|
+
onReady?: () => void;
|
|
238
|
+
onSubmit?: (data: { researchId: string }) => void;
|
|
239
|
+
onClose?: () => void;
|
|
240
|
+
onNavigate?: (url: string) => void;
|
|
241
|
+
onError?: (error: EmbedError) => void;
|
|
242
|
+
|
|
243
|
+
// Controlled mode
|
|
244
|
+
open?: boolean;
|
|
245
|
+
onOpenChange?: (open: boolean) => void;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Hook Return Types
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
interface UsePopupReturn {
|
|
253
|
+
open: () => void;
|
|
254
|
+
close: () => void;
|
|
255
|
+
toggle: () => void;
|
|
256
|
+
isOpen: boolean;
|
|
257
|
+
handle: EmbedHandle | null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface UseSliderReturn {
|
|
261
|
+
open: () => void;
|
|
262
|
+
close: () => void;
|
|
263
|
+
toggle: () => void;
|
|
264
|
+
isOpen: boolean;
|
|
265
|
+
handle: EmbedHandle | null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
interface UseFloatBubbleReturn {
|
|
269
|
+
open: () => void;
|
|
270
|
+
close: () => void;
|
|
271
|
+
toggle: () => void;
|
|
272
|
+
unmount: () => void;
|
|
273
|
+
isOpen: boolean;
|
|
274
|
+
handle: FloatHandle | null;
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Other Hooks
|
|
279
|
+
|
|
280
|
+
### useThemeSync
|
|
281
|
+
|
|
282
|
+
Sync theme between your app and embedded interviews:
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
import { useThemeSync } from "@perspective-ai/sdk-react";
|
|
286
|
+
|
|
287
|
+
function App() {
|
|
288
|
+
const [theme, setTheme] = useState<"light" | "dark">("light");
|
|
289
|
+
|
|
290
|
+
// Syncs theme changes to all active embeds
|
|
291
|
+
useThemeSync(theme);
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<button onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}>
|
|
295
|
+
Toggle Theme
|
|
296
|
+
</button>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## TypeScript
|
|
302
|
+
|
|
303
|
+
All types are exported:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import type {
|
|
307
|
+
// Hook types
|
|
308
|
+
UsePopupOptions,
|
|
309
|
+
UsePopupReturn,
|
|
310
|
+
UseSliderOptions,
|
|
311
|
+
UseSliderReturn,
|
|
312
|
+
UseFloatBubbleOptions,
|
|
313
|
+
UseFloatBubbleReturn,
|
|
314
|
+
UseAutoOpenOptions,
|
|
315
|
+
UseAutoOpenReturn,
|
|
316
|
+
// Component types
|
|
317
|
+
WidgetProps,
|
|
318
|
+
FloatBubbleProps,
|
|
319
|
+
FullpageProps,
|
|
320
|
+
} from "@perspective-ai/sdk-react";
|
|
321
|
+
|
|
322
|
+
// Re-exported from @perspective-ai/sdk
|
|
323
|
+
import type {
|
|
324
|
+
EmbedConfig,
|
|
325
|
+
EmbedHandle,
|
|
326
|
+
FloatHandle,
|
|
327
|
+
BrandColors,
|
|
328
|
+
ThemeValue,
|
|
329
|
+
EmbedError,
|
|
330
|
+
} from "@perspective-ai/sdk-react";
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## SSR Safety
|
|
334
|
+
|
|
335
|
+
All hooks and components are SSR-safe and include the `"use client"` directive. Works with Next.js, Remix, and other React frameworks.
|
|
336
|
+
|
|
337
|
+
## License
|
|
338
|
+
|
|
339
|
+
MIT
|