@sunboard/react 0.9.0 → 1.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 +1 -1
- package/README.md +234 -26
- package/dist/index.d.mts +915 -305
- package/dist/index.mjs +4322 -1735
- package/dist/styles.css +1076 -727
- package/package.json +41 -35
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,59 +1,267 @@
|
|
|
1
1
|
# @sunboard/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Product tours, tooltips and a Help launcher with an assistant, for your React app.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@sunboard/react)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+

|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
You need a Sunboard account. In the Sunboard app, **Settings › Install** has these steps for your
|
|
10
|
+
framework with your key already filled in, and a prompt you can hand to your coding agent.
|
|
8
11
|
|
|
9
|
-
##
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import "@sunboard/react/styles.css";
|
|
16
|
+
import { SunboardOverlay, SunboardProvider } from "@sunboard/react";
|
|
17
|
+
|
|
18
|
+
<SunboardProvider connection={{ publishableKey: SUNBOARD_KEY, userId: currentUser.id }}>
|
|
19
|
+
<App />
|
|
20
|
+
<SunboardOverlay />
|
|
21
|
+
</SunboardProvider>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
That is the whole integration. The steps below put it in the right place for your framework.
|
|
25
|
+
|
|
26
|
+
## Getting started
|
|
27
|
+
|
|
28
|
+
### 1. Install the package
|
|
10
29
|
|
|
11
30
|
```bash
|
|
12
|
-
npm
|
|
31
|
+
npm install @sunboard/react
|
|
32
|
+
# or
|
|
33
|
+
pnpm add @sunboard/react
|
|
34
|
+
# or
|
|
35
|
+
yarn add @sunboard/react
|
|
13
36
|
```
|
|
14
37
|
|
|
15
|
-
|
|
38
|
+
React 18 or later. Every export is client code.
|
|
39
|
+
|
|
40
|
+
### 2. Add your publishable key
|
|
41
|
+
|
|
42
|
+
Copy it from **Settings › Install**. It is safe in the browser.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Next.js, in .env.local
|
|
46
|
+
NEXT_PUBLIC_SUNBOARD_KEY=<your publishable key>
|
|
47
|
+
|
|
48
|
+
# Vite, React Router or TanStack, in .env
|
|
49
|
+
VITE_SUNBOARD_KEY=<your publishable key>
|
|
50
|
+
```
|
|
16
51
|
|
|
17
|
-
|
|
52
|
+
### 3. Create one Sunboard component
|
|
18
53
|
|
|
19
|
-
|
|
54
|
+
It connects to Sunboard, shows tours and the Help launcher, and lets a tour change pages through
|
|
55
|
+
your router. This is the Next.js version:
|
|
20
56
|
|
|
21
57
|
```tsx
|
|
22
|
-
|
|
58
|
+
// app/sunboard.tsx
|
|
59
|
+
"use client";
|
|
60
|
+
|
|
23
61
|
import "@sunboard/react/styles.css";
|
|
62
|
+
import type { ReactNode } from "react";
|
|
63
|
+
import { useRouter } from "next/navigation";
|
|
64
|
+
import { SunboardOverlay, SunboardProvider } from "@sunboard/react";
|
|
24
65
|
|
|
25
|
-
export
|
|
66
|
+
export function Sunboard({
|
|
67
|
+
userId,
|
|
68
|
+
firstName,
|
|
69
|
+
children,
|
|
70
|
+
}: {
|
|
71
|
+
userId: string;
|
|
72
|
+
firstName?: string;
|
|
73
|
+
children: ReactNode;
|
|
74
|
+
}) {
|
|
75
|
+
const router = useRouter();
|
|
26
76
|
return (
|
|
27
77
|
<SunboardProvider
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
78
|
+
connection={{
|
|
79
|
+
publishableKey: process.env.NEXT_PUBLIC_SUNBOARD_KEY!,
|
|
80
|
+
userId,
|
|
81
|
+
user: { firstName },
|
|
82
|
+
}}
|
|
83
|
+
navigate={(path) => router.push(path)}
|
|
31
84
|
>
|
|
32
85
|
{children}
|
|
33
|
-
<
|
|
86
|
+
<SunboardOverlay />
|
|
34
87
|
</SunboardProvider>
|
|
35
88
|
);
|
|
36
89
|
}
|
|
37
90
|
```
|
|
38
91
|
|
|
39
|
-
|
|
92
|
+
Other frameworks change the key, `navigate`, and where the stylesheet loads:
|
|
93
|
+
|
|
94
|
+
| Framework | Key | `navigate` |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| React Router | `import.meta.env.VITE_SUNBOARD_KEY` | `useNavigate()` from `react-router`, then `(path) => navigate(path)` |
|
|
97
|
+
| TanStack Start | `import.meta.env.VITE_SUNBOARD_KEY` | `useNavigate()` from `@tanstack/react-router`, then `(path) => navigate({ href: path })` |
|
|
98
|
+
| Vite with no router | `import.meta.env.VITE_SUNBOARD_KEY` | Leave it out. |
|
|
99
|
+
|
|
100
|
+
Outside Next.js the file has no `"use client"` line. In TanStack Start, load the stylesheet from
|
|
101
|
+
`head()` in `src/routes/__root.tsx` instead of importing it:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import sunboardCss from "@sunboard/react/styles.css?url";
|
|
105
|
+
|
|
106
|
+
// in head(): links: [{ rel: "stylesheet", href: sunboardCss }]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`useRouter` and `useNavigate` work only inside the router, so mount the component where the
|
|
110
|
+
table below says.
|
|
111
|
+
|
|
112
|
+
### 4. Wrap your signed-in pages
|
|
113
|
+
|
|
114
|
+
Put the component around the pages a signed-in user sees, and pass that user's id. Use an id that
|
|
115
|
+
stays the same for each person. The first name is optional. With it the assistant greets the
|
|
116
|
+
user ("Hey Sam,"), without it it says "Hey there,". The name stays in the browser.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { Sunboard } from "./sunboard";
|
|
120
|
+
|
|
121
|
+
<Sunboard userId={currentUser.id} firstName={currentUser.firstName}>
|
|
122
|
+
{children}
|
|
123
|
+
</Sunboard>
|
|
124
|
+
```
|
|
40
125
|
|
|
41
|
-
|
|
126
|
+
| Framework | Where |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| Next.js | `app/layout.tsx`, or the layout of your signed-in area, inside `body` |
|
|
129
|
+
| React Router | Around the `Outlet` in `app/root.tsx`, or inside `BrowserRouter` |
|
|
130
|
+
| TanStack Start | Around the page content in `src/routes/__root.tsx` |
|
|
131
|
+
| Vite | Around `App` in `src/main.tsx` |
|
|
42
132
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- **`useSunboard()`** — hook returning runtime state and imperative helpers like `startTour(key)`, useful for custom product UI.
|
|
133
|
+
A Next.js app with route groups, such as `app/(marketing)/layout.tsx` and
|
|
134
|
+
`app/(app)/layout.tsx`, wraps `children` in the signed-in group's layout only.
|
|
46
135
|
|
|
47
|
-
|
|
136
|
+
### 5. Check it works
|
|
137
|
+
|
|
138
|
+
Start your app and sign in. **Settings › Install** in the Sunboard app shows **Connected**. Then
|
|
139
|
+
add your app's address under **Settings › Project**, and use **Preview** on any guide to see it
|
|
140
|
+
in your app.
|
|
141
|
+
|
|
142
|
+
## Good to know
|
|
143
|
+
|
|
144
|
+
- **The same key works locally and in production.** Visits from `localhost`, `127.0.0.1` and
|
|
145
|
+
`[::1]` are left out of Analytics and do not count as active users.
|
|
146
|
+
- **Content Security Policy.** The package talks to `https://api.sunboardhq.com`, so a
|
|
147
|
+
`connect-src` rule has to allow it.
|
|
148
|
+
- **Server rendering.** The package is client code and says so, so a Next.js server component
|
|
149
|
+
can import your `Sunboard` component without a wrapper.
|
|
150
|
+
- **Styling.** Colors, fonts, the launcher and the tour cards are set on the **Appearance** page
|
|
151
|
+
in the Sunboard app and reach your app without a deploy. The stylesheet import above is the
|
|
152
|
+
only CSS you add.
|
|
153
|
+
- **Preview.** The **Preview** button on a guide in the Sunboard app opens your app with
|
|
154
|
+
`?sunboardPreview=<key>` in the address and shows paused guides as well as live ones, in that
|
|
155
|
+
browser only. `?sunboardPreview=off` ends it.
|
|
156
|
+
- **Users.** The active-user limit on your plan counts the ids you pass as `userId`, so give each
|
|
157
|
+
person one id that never changes.
|
|
158
|
+
|
|
159
|
+
## Reference
|
|
160
|
+
|
|
161
|
+
### SunboardProvider
|
|
162
|
+
|
|
163
|
+
| Prop | Type | Default | What it does |
|
|
164
|
+
| --- | --- | --- | --- |
|
|
165
|
+
| `connection` | `{ publishableKey: string; userId: string; user?: { firstName?: string } }` | required | Your publishable key and the signed-in user's id. `user.firstName` is for the assistant's greeting and stays in the browser. |
|
|
166
|
+
| `navigate` | `(path: string) => void \| Promise<void>` | full page load | How a tour moves to another page. Pass your router. Without it, a tour loads the next page in full and carries on there. |
|
|
167
|
+
| `labels` | `LabelOverrides` | English | Your own words for any of the SDK's buttons and text, for example `labels={{ launcher: { open: "Support" } }}`. The rest stay English. `enLabels` has every one. |
|
|
168
|
+
| `mode` | `"auto" \| "light" \| "dark"` | `"auto"` | Light or dark. `"auto"` follows a `dark` or `light` class, or a `data-theme`, `data-mode` or `data-color-scheme` attribute, on `<html>` or `<body>`, then the system setting, and tracks changes live. |
|
|
169
|
+
| `debug` | `boolean` | `false` | Logs what the SDK does to the browser console, each line starting with `[sunboard]`. The lines use `console.debug`, so Chrome shows them only with the Verbose level on. |
|
|
170
|
+
|
|
171
|
+
### SunboardOverlay
|
|
172
|
+
|
|
173
|
+
Shows tours, tooltips, announcements, banners and the Help pill. Mount it once, anywhere inside
|
|
174
|
+
the provider.
|
|
175
|
+
|
|
176
|
+
| Prop | Type | Default | What it does |
|
|
177
|
+
| --- | --- | --- | --- |
|
|
178
|
+
| `launcher` | `boolean` | `true` | Shows the Help pill. Pass `false` when you build your own, see below. |
|
|
179
|
+
|
|
180
|
+
### Address switches
|
|
181
|
+
|
|
182
|
+
Add these to any address in your app. The SDK reads each one once and removes it from the
|
|
183
|
+
address.
|
|
184
|
+
|
|
185
|
+
| Switch | What it does |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| `?sunboardDebug=1` | Turns on `debug` for that page load. |
|
|
188
|
+
| `?sunboardLaunch=<guide key>` | Starts that tour once the guides load. The Preview link on a tour in the Sunboard app carries it, so you can copy the key from there. |
|
|
189
|
+
|
|
190
|
+
## Your own help menu
|
|
191
|
+
|
|
192
|
+
The Help pill is built on public hooks, so you can show your guides and the assistant in your
|
|
193
|
+
own UI instead. Keep `<SunboardOverlay />` for tours, tooltips, announcements and banners, and
|
|
194
|
+
turn the pill off:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
<SunboardOverlay launcher={false} />
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Call `useLauncher()` once for your help menu and pass what it returns down.
|
|
201
|
+
|
|
202
|
+
| Field | What it is |
|
|
203
|
+
| --- | --- |
|
|
204
|
+
| `onThisPage` | Guides for the page the user is on. |
|
|
205
|
+
| `elsewhere` | Every other guide. |
|
|
206
|
+
| `ready` | `true` once the guides have loaded. |
|
|
207
|
+
| `tourRunning` | `true` while a tour runs. |
|
|
208
|
+
| `opened()` | Call it when your menu opens, so the open counts in Analytics. |
|
|
209
|
+
| `start(guideKey, replyId?)` | Starts a guide. Pass the id of the answer that offered it, and the conversation gets a line after that answer when the tour ends. |
|
|
210
|
+
| `messages` | The conversation with the assistant, oldest first. Each `Message` has an `id` and a `kind`: `question`, `waiting`, `reply`, `error` or `tour`. A `reply` holds an `AskReply` with the answer's text in `answer` and `guideKey` when it offers a guide to start. |
|
|
211
|
+
| `waiting` | `true` while a question is out and its answer has not come back. |
|
|
212
|
+
| `rateLimitedUntil` | While the user has asked too many questions this hour, the time they can ask again, as an ISO string. Otherwise `undefined`. |
|
|
213
|
+
| `handedOff` | `true` while a tour started from the conversation runs. It turns `false` when the tour ends, the moment to show your panel again. |
|
|
214
|
+
| `ask(question)` | Asks a question, as a follow-up when the conversation has one. A blank question is ignored, and so is one while `waiting` or while `rateLimitedUntil` is set. |
|
|
215
|
+
| `retry()` | Asks the newest question again when no answer came back for it. |
|
|
216
|
+
| `startOver()` | Clears the conversation. The next question starts a new one. |
|
|
217
|
+
|
|
218
|
+
`Menu` and `MenuItem` below stand for your own components:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import { useLauncher } from "@sunboard/react";
|
|
222
|
+
|
|
223
|
+
function HelpMenu() {
|
|
224
|
+
const { onThisPage, elsewhere, opened, start } = useLauncher();
|
|
225
|
+
return (
|
|
226
|
+
<Menu onOpen={opened}>
|
|
227
|
+
{onThisPage.map((guide) => (
|
|
228
|
+
<MenuItem key={guide.key} onSelect={() => start(guide.key)}>
|
|
229
|
+
{guide.title}
|
|
230
|
+
</MenuItem>
|
|
231
|
+
))}
|
|
232
|
+
<MenuSeparator />
|
|
233
|
+
{elsewhere.map((guide) => (
|
|
234
|
+
<MenuItem key={guide.key} onSelect={() => start(guide.key)}>
|
|
235
|
+
{guide.title}
|
|
236
|
+
</MenuItem>
|
|
237
|
+
))}
|
|
238
|
+
</Menu>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
48
242
|
|
|
49
|
-
|
|
243
|
+
Two more hooks help here. `useGuide(key)` returns `{ guide, launchTour }` for one guide, so a
|
|
244
|
+
button anywhere in your app can start a tour. `useTheme()` returns `{ theme, mode, cssVars }`,
|
|
245
|
+
the colors in use as CSS variables to spread onto your menu so it matches the tours.
|
|
50
246
|
|
|
51
|
-
##
|
|
247
|
+
## Troubleshoot
|
|
52
248
|
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
249
|
+
- **Nothing shows in my app.** Add `?sunboardDebug=1` to the page address and open the browser
|
|
250
|
+
console with the Verbose level on. A wrong key shows as a failed request. No lines at all means
|
|
251
|
+
the provider is not mounted around the page you are on.
|
|
252
|
+
- **Settings › Install does not say Connected.** It turns Connected on the first request from your
|
|
253
|
+
app, so load a page where the provider is mounted, signed in, and reload the Install tab.
|
|
254
|
+
- **A tour reloads the page instead of changing route.** Pass `navigate` with your router. Without
|
|
255
|
+
it, a tour carries on after a full page load.
|
|
256
|
+
- **The tour cards have no styling.** Import `@sunboard/react/styles.css` once, next to the
|
|
257
|
+
provider. In TanStack Start, load it from `head()` as shown above.
|
|
258
|
+
- **Requests to api.sunboardhq.com are blocked.** Your Content Security Policy needs it in
|
|
259
|
+
`connect-src`.
|
|
260
|
+
- **Next.js says the component needs "use client".** Put `"use client"` on the first line of your
|
|
261
|
+
`sunboard.tsx`, as in step 3. The package itself is already client code.
|
|
262
|
+
- **useRouter or useNavigate throws.** Mount the component inside the router, where the table in
|
|
263
|
+
step 4 says.
|
|
56
264
|
|
|
57
265
|
## License
|
|
58
266
|
|
|
59
|
-
MIT
|
|
267
|
+
MIT
|