fdbck-react 0.2.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 +145 -0
- package/dist/index.css +418 -0
- package/dist/index.d.mts +115 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +716 -0
- package/dist/index.mjs +688 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 fdbck
|
|
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,145 @@
|
|
|
1
|
+
# fdbck-react
|
|
2
|
+
|
|
3
|
+
Official React SDK for [fdbck](https://fdbck.sh) — embed feedback questions natively in your React app with Shadow DOM CSS isolation.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install fdbck-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires React 18+.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
### 1. Direct token (simplest)
|
|
16
|
+
|
|
17
|
+
Generate a response token on your backend (via the Node.js or Python SDK), then pass it to the widget:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { FdbckWidget } from 'fdbck-react';
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return (
|
|
24
|
+
<FdbckWidget
|
|
25
|
+
token="V7xH2kQ9mPn4wR1j"
|
|
26
|
+
mode="inline"
|
|
27
|
+
onSubmit={(value) => console.log('Submitted:', value)}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Imperative API (provider pattern)
|
|
34
|
+
|
|
35
|
+
For showing feedback modals/popovers imperatively:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { FdbckProvider, useFdbck } from 'fdbck-react';
|
|
39
|
+
|
|
40
|
+
function App() {
|
|
41
|
+
return (
|
|
42
|
+
<FdbckProvider>
|
|
43
|
+
<FeedbackButton />
|
|
44
|
+
</FdbckProvider>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function FeedbackButton() {
|
|
49
|
+
const { show } = useFdbck();
|
|
50
|
+
|
|
51
|
+
async function handleClick() {
|
|
52
|
+
const result = await show({
|
|
53
|
+
token: 'V7xH2kQ9mPn4wR1j',
|
|
54
|
+
mode: 'modal',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (result.status === 'submitted') {
|
|
58
|
+
console.log('Response:', result.value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return <button onClick={handleClick}>Give feedback</button>;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Display modes
|
|
67
|
+
|
|
68
|
+
| Mode | Description |
|
|
69
|
+
|------|-------------|
|
|
70
|
+
| `inline` | Renders the card in the document flow |
|
|
71
|
+
| `modal` | Centered overlay with backdrop, focus trap, Escape to dismiss |
|
|
72
|
+
| `popover` | Bottom-right floating card (desktop), bottom sheet (mobile ≤640px) |
|
|
73
|
+
|
|
74
|
+
## API reference
|
|
75
|
+
|
|
76
|
+
### `<FdbckWidget>`
|
|
77
|
+
|
|
78
|
+
Takes a pre-resolved response token and renders the feedback widget.
|
|
79
|
+
|
|
80
|
+
| Prop | Type | Default | Description |
|
|
81
|
+
|------|------|---------|-------------|
|
|
82
|
+
| `token` | `string` | required | Response token |
|
|
83
|
+
| `mode` | `'inline' \| 'modal' \| 'popover'` | `'inline'` | Display mode |
|
|
84
|
+
| `open` | `boolean` | `true` | Whether the widget is visible |
|
|
85
|
+
| `baseUrl` | `string` | `'https://api.fdbck.sh'` | API base URL |
|
|
86
|
+
| `delay` | `number` | `0` | Delay in ms before showing |
|
|
87
|
+
| `autoCloseAfter` | `number` | — | Auto-dismiss after submission (ms) |
|
|
88
|
+
| `closeOnOverlayClick` | `boolean` | `true` | Dismiss modal on backdrop click |
|
|
89
|
+
| `closeOnEscape` | `boolean` | `true` | Dismiss on Escape key |
|
|
90
|
+
| `locale` | `FdbckLocale` | — | Custom text strings |
|
|
91
|
+
| `style` | `FdbckStyle` | — | Style overrides |
|
|
92
|
+
| `onSubmit` | `(value: ResponseValue) => void` | — | Called on successful submission |
|
|
93
|
+
| `onDismiss` | `() => void` | — | Called when dismissed |
|
|
94
|
+
| `onError` | `(error: FdbckError) => void` | — | Called on error |
|
|
95
|
+
| `onLoad` | `(question: QuestionData) => void` | — | Called when question data loads |
|
|
96
|
+
|
|
97
|
+
### `<FdbckProvider>` + `useFdbck()`
|
|
98
|
+
|
|
99
|
+
Provider for the imperative API.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
const { show, dismiss, isActive } = useFdbck();
|
|
103
|
+
|
|
104
|
+
// show() accepts token + display options, returns Promise<FdbckResult>
|
|
105
|
+
const result = await show({ token: '...', mode: 'modal' });
|
|
106
|
+
// result: { status: 'submitted', value: 'Yes' } or { status: 'dismissed' }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Theming
|
|
110
|
+
|
|
111
|
+
The widget inherits theme settings from the question (`theme_color`, `theme_mode`). Override layout with the `style` prop:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<FdbckWidget
|
|
115
|
+
token="..."
|
|
116
|
+
style={{
|
|
117
|
+
maxWidth: '400px',
|
|
118
|
+
borderRadius: '1rem',
|
|
119
|
+
fontFamily: 'Inter, sans-serif',
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## CSS isolation
|
|
125
|
+
|
|
126
|
+
All styles are injected into a Shadow DOM, so the widget's CSS won't leak into your app and your app's CSS won't affect the widget.
|
|
127
|
+
|
|
128
|
+
## Accessibility
|
|
129
|
+
|
|
130
|
+
- Modal mode includes a focus trap and `role="dialog"` with `aria-modal="true"`
|
|
131
|
+
- Popover mode includes `role="dialog"`
|
|
132
|
+
- All animations respect `prefers-reduced-motion`
|
|
133
|
+
- Interactive elements are keyboard-navigable
|
|
134
|
+
|
|
135
|
+
## SSR compatibility
|
|
136
|
+
|
|
137
|
+
The widget uses `useEffect` for all DOM operations (Shadow DOM, fetch). It renders nothing on the server and hydrates on the client. Safe for Next.js, Remix, and other SSR frameworks.
|
|
138
|
+
|
|
139
|
+
## Bundle size
|
|
140
|
+
|
|
141
|
+
~7KB gzipped (including all CSS).
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/* src/styles/base.css */
|
|
2
|
+
*,
|
|
3
|
+
*::before,
|
|
4
|
+
*::after {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
}
|
|
9
|
+
:host {
|
|
10
|
+
display: block;
|
|
11
|
+
font-family: var(--fdbck-font-family);
|
|
12
|
+
font-size: var(--fdbck-font-size);
|
|
13
|
+
line-height: 1.5;
|
|
14
|
+
color: var(--fdbck-text-primary);
|
|
15
|
+
-webkit-font-smoothing: antialiased;
|
|
16
|
+
}
|
|
17
|
+
.fdbck-card {
|
|
18
|
+
width: var(--fdbck-width);
|
|
19
|
+
max-width: var(--fdbck-max-width);
|
|
20
|
+
background: var(--fdbck-card-bg);
|
|
21
|
+
border: 1px solid var(--fdbck-border);
|
|
22
|
+
border-radius: var(--fdbck-border-radius);
|
|
23
|
+
padding: 1.5rem;
|
|
24
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
25
|
+
}
|
|
26
|
+
.fdbck-welcome {
|
|
27
|
+
font-size: var(--fdbck-font-size);
|
|
28
|
+
color: var(--fdbck-text-secondary);
|
|
29
|
+
margin-bottom: 1rem;
|
|
30
|
+
white-space: pre-line;
|
|
31
|
+
}
|
|
32
|
+
.fdbck-question {
|
|
33
|
+
font-size: 1.125rem;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
color: var(--fdbck-text-primary);
|
|
36
|
+
margin-bottom: 1.5rem;
|
|
37
|
+
}
|
|
38
|
+
.fdbck-error-text {
|
|
39
|
+
color: #ef4444;
|
|
40
|
+
font-size: var(--fdbck-font-size);
|
|
41
|
+
margin-bottom: 1rem;
|
|
42
|
+
}
|
|
43
|
+
.fdbck-center {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
text-align: center;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
}
|
|
50
|
+
.fdbck-loading {
|
|
51
|
+
min-height: 200px;
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
}
|
|
56
|
+
.fdbck-spinner {
|
|
57
|
+
width: 1.5rem;
|
|
58
|
+
height: 1.5rem;
|
|
59
|
+
border: 2px solid transparent;
|
|
60
|
+
border-top-color: var(--fdbck-accent);
|
|
61
|
+
border-right-color: var(--fdbck-accent);
|
|
62
|
+
border-radius: 50%;
|
|
63
|
+
animation: fdbck-spin 0.6s linear infinite;
|
|
64
|
+
}
|
|
65
|
+
@keyframes fdbck-spin {
|
|
66
|
+
to {
|
|
67
|
+
transform: rotate(360deg);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
.fdbck-error-state {
|
|
71
|
+
padding: 2rem 0;
|
|
72
|
+
text-align: center;
|
|
73
|
+
}
|
|
74
|
+
.fdbck-error-state h2 {
|
|
75
|
+
font-size: 1.125rem;
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
color: var(--fdbck-text-primary);
|
|
78
|
+
margin-bottom: 0.25rem;
|
|
79
|
+
}
|
|
80
|
+
.fdbck-error-state p {
|
|
81
|
+
color: var(--fdbck-text-secondary);
|
|
82
|
+
}
|
|
83
|
+
.fdbck-retry-btn {
|
|
84
|
+
margin-top: 1rem;
|
|
85
|
+
padding: 0.5rem 1rem;
|
|
86
|
+
border: 1px solid var(--fdbck-border);
|
|
87
|
+
border-radius: 0.5rem;
|
|
88
|
+
background: transparent;
|
|
89
|
+
color: var(--fdbck-text-primary);
|
|
90
|
+
font-size: var(--fdbck-font-size);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: border-color 0.15s;
|
|
93
|
+
}
|
|
94
|
+
.fdbck-retry-btn:hover {
|
|
95
|
+
border-color: var(--fdbck-accent);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* src/styles/question-types.css */
|
|
99
|
+
.fdbck-option-btn {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 0.75rem;
|
|
103
|
+
width: 100%;
|
|
104
|
+
padding: 0.75rem 1rem;
|
|
105
|
+
border: 1px solid var(--fdbck-border);
|
|
106
|
+
border-radius: 0.5rem;
|
|
107
|
+
background: transparent;
|
|
108
|
+
color: var(--fdbck-text-primary);
|
|
109
|
+
font-size: var(--fdbck-font-size);
|
|
110
|
+
text-align: left;
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
transition: border-color 0.15s;
|
|
113
|
+
}
|
|
114
|
+
.fdbck-option-btn:hover:not(:disabled) {
|
|
115
|
+
border-color: var(--fdbck-accent);
|
|
116
|
+
}
|
|
117
|
+
.fdbck-option-btn:disabled {
|
|
118
|
+
opacity: 0.5;
|
|
119
|
+
cursor: not-allowed;
|
|
120
|
+
}
|
|
121
|
+
.fdbck-option-btn[data-selected=true] {
|
|
122
|
+
border-color: var(--fdbck-accent);
|
|
123
|
+
}
|
|
124
|
+
.fdbck-yesno-grid {
|
|
125
|
+
display: grid;
|
|
126
|
+
grid-template-columns: 1fr 1fr;
|
|
127
|
+
gap: 0.75rem;
|
|
128
|
+
}
|
|
129
|
+
.fdbck-yesno-grid .fdbck-option-btn {
|
|
130
|
+
justify-content: center;
|
|
131
|
+
font-weight: 500;
|
|
132
|
+
}
|
|
133
|
+
.fdbck-choice-list {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
gap: 0.5rem;
|
|
137
|
+
}
|
|
138
|
+
.fdbck-radio {
|
|
139
|
+
width: 1rem;
|
|
140
|
+
height: 1rem;
|
|
141
|
+
border: 2px solid var(--fdbck-indicator-border);
|
|
142
|
+
border-radius: 50%;
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
transition: border-color 0.15s;
|
|
148
|
+
}
|
|
149
|
+
.fdbck-radio[data-selected=true] {
|
|
150
|
+
border-color: var(--fdbck-accent);
|
|
151
|
+
}
|
|
152
|
+
.fdbck-radio-dot {
|
|
153
|
+
width: 0.5rem;
|
|
154
|
+
height: 0.5rem;
|
|
155
|
+
border-radius: 50%;
|
|
156
|
+
background: var(--fdbck-accent);
|
|
157
|
+
}
|
|
158
|
+
.fdbck-checkbox {
|
|
159
|
+
width: 1rem;
|
|
160
|
+
height: 1rem;
|
|
161
|
+
border: 2px solid var(--fdbck-indicator-border);
|
|
162
|
+
border-radius: 0.25rem;
|
|
163
|
+
flex-shrink: 0;
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
justify-content: center;
|
|
167
|
+
transition: border-color 0.15s, background-color 0.15s;
|
|
168
|
+
}
|
|
169
|
+
.fdbck-checkbox[data-selected=true] {
|
|
170
|
+
border-color: var(--fdbck-accent);
|
|
171
|
+
background-color: var(--fdbck-accent);
|
|
172
|
+
}
|
|
173
|
+
.fdbck-checkbox svg {
|
|
174
|
+
width: 0.75rem;
|
|
175
|
+
height: 0.75rem;
|
|
176
|
+
color: #fff;
|
|
177
|
+
}
|
|
178
|
+
.fdbck-submit-btn {
|
|
179
|
+
margin-top: 0.5rem;
|
|
180
|
+
padding: 0.625rem 1rem;
|
|
181
|
+
border: none;
|
|
182
|
+
border-radius: 0.5rem;
|
|
183
|
+
background: var(--fdbck-accent);
|
|
184
|
+
color: #fff;
|
|
185
|
+
font-size: var(--fdbck-font-size);
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
transition: opacity 0.15s;
|
|
189
|
+
}
|
|
190
|
+
.fdbck-submit-btn:disabled {
|
|
191
|
+
opacity: 0.4;
|
|
192
|
+
cursor: not-allowed;
|
|
193
|
+
}
|
|
194
|
+
.fdbck-rating-grid {
|
|
195
|
+
display: grid;
|
|
196
|
+
gap: 0.375rem;
|
|
197
|
+
}
|
|
198
|
+
.fdbck-rating-btn {
|
|
199
|
+
aspect-ratio: 1;
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
padding: 0;
|
|
204
|
+
border: 1px solid var(--fdbck-border);
|
|
205
|
+
border-radius: 0.5rem;
|
|
206
|
+
background: transparent;
|
|
207
|
+
color: var(--fdbck-text-primary);
|
|
208
|
+
font-size: var(--fdbck-font-size);
|
|
209
|
+
font-weight: 500;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
transition:
|
|
212
|
+
border-color 0.15s,
|
|
213
|
+
background-color 0.15s,
|
|
214
|
+
color 0.15s;
|
|
215
|
+
}
|
|
216
|
+
.fdbck-rating-btn:hover:not(:disabled) {
|
|
217
|
+
border-color: var(--fdbck-accent);
|
|
218
|
+
}
|
|
219
|
+
.fdbck-rating-btn:disabled {
|
|
220
|
+
opacity: 0.5;
|
|
221
|
+
cursor: not-allowed;
|
|
222
|
+
}
|
|
223
|
+
.fdbck-rating-btn[data-selected=true] {
|
|
224
|
+
background-color: var(--fdbck-accent);
|
|
225
|
+
border-color: var(--fdbck-accent);
|
|
226
|
+
color: #fff;
|
|
227
|
+
}
|
|
228
|
+
.fdbck-rating-labels {
|
|
229
|
+
display: flex;
|
|
230
|
+
justify-content: space-between;
|
|
231
|
+
margin-top: 0.5rem;
|
|
232
|
+
font-size: 0.75rem;
|
|
233
|
+
color: var(--fdbck-text-muted);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* src/styles/confirmation.css */
|
|
237
|
+
.fdbck-confirmation {
|
|
238
|
+
padding: 2rem 0;
|
|
239
|
+
text-align: center;
|
|
240
|
+
}
|
|
241
|
+
.fdbck-check-circle {
|
|
242
|
+
width: 3rem;
|
|
243
|
+
height: 3rem;
|
|
244
|
+
border-radius: 50%;
|
|
245
|
+
background: var(--fdbck-accent-alpha);
|
|
246
|
+
display: flex;
|
|
247
|
+
align-items: center;
|
|
248
|
+
justify-content: center;
|
|
249
|
+
margin: 0 auto 1rem;
|
|
250
|
+
animation: fdbck-scale-in 0.3s ease-out;
|
|
251
|
+
}
|
|
252
|
+
.fdbck-check-circle svg {
|
|
253
|
+
width: 1.5rem;
|
|
254
|
+
height: 1.5rem;
|
|
255
|
+
color: var(--fdbck-accent);
|
|
256
|
+
}
|
|
257
|
+
.fdbck-confirmation h2 {
|
|
258
|
+
font-size: 1.125rem;
|
|
259
|
+
font-weight: 600;
|
|
260
|
+
color: var(--fdbck-text-primary);
|
|
261
|
+
margin-bottom: 0.25rem;
|
|
262
|
+
}
|
|
263
|
+
.fdbck-confirmation p {
|
|
264
|
+
color: var(--fdbck-text-secondary);
|
|
265
|
+
white-space: pre-line;
|
|
266
|
+
}
|
|
267
|
+
@keyframes fdbck-scale-in {
|
|
268
|
+
from {
|
|
269
|
+
transform: scale(0.5);
|
|
270
|
+
opacity: 0;
|
|
271
|
+
}
|
|
272
|
+
to {
|
|
273
|
+
transform: scale(1);
|
|
274
|
+
opacity: 1;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* src/styles/branding.css */
|
|
279
|
+
.fdbck-branding {
|
|
280
|
+
display: flex;
|
|
281
|
+
justify-content: center;
|
|
282
|
+
margin-top: 1rem;
|
|
283
|
+
padding-top: 0.75rem;
|
|
284
|
+
border-top: 1px solid var(--fdbck-border);
|
|
285
|
+
}
|
|
286
|
+
.fdbck-branding a {
|
|
287
|
+
font-size: 0.75rem;
|
|
288
|
+
color: var(--fdbck-text-muted);
|
|
289
|
+
text-decoration: none;
|
|
290
|
+
transition: color 0.15s;
|
|
291
|
+
}
|
|
292
|
+
.fdbck-branding a:hover {
|
|
293
|
+
color: var(--fdbck-text-secondary);
|
|
294
|
+
}
|
|
295
|
+
.fdbck-branding-name {
|
|
296
|
+
font-family: "JetBrains Mono", monospace;
|
|
297
|
+
font-weight: 500;
|
|
298
|
+
color: var(--fdbck-accent);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* src/styles/modal.css */
|
|
302
|
+
.fdbck-modal-backdrop {
|
|
303
|
+
position: fixed;
|
|
304
|
+
inset: 0;
|
|
305
|
+
background: rgba(0, 0, 0, 0.5);
|
|
306
|
+
display: flex;
|
|
307
|
+
align-items: center;
|
|
308
|
+
justify-content: center;
|
|
309
|
+
padding: 1rem;
|
|
310
|
+
z-index: 2147483647;
|
|
311
|
+
animation: fdbck-fade-in 0.2s ease-out;
|
|
312
|
+
}
|
|
313
|
+
.fdbck-modal-content {
|
|
314
|
+
position: relative;
|
|
315
|
+
animation: fdbck-scale-up 0.2s ease-out;
|
|
316
|
+
}
|
|
317
|
+
.fdbck-close-btn {
|
|
318
|
+
position: absolute;
|
|
319
|
+
top: -0.5rem;
|
|
320
|
+
right: -0.5rem;
|
|
321
|
+
width: 1.75rem;
|
|
322
|
+
height: 1.75rem;
|
|
323
|
+
border: 1px solid var(--fdbck-border);
|
|
324
|
+
border-radius: 50%;
|
|
325
|
+
background: var(--fdbck-card-bg);
|
|
326
|
+
color: var(--fdbck-text-secondary);
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
justify-content: center;
|
|
331
|
+
font-size: 1rem;
|
|
332
|
+
line-height: 1;
|
|
333
|
+
transition: border-color 0.15s;
|
|
334
|
+
z-index: 1;
|
|
335
|
+
}
|
|
336
|
+
.fdbck-close-btn:hover {
|
|
337
|
+
border-color: var(--fdbck-accent);
|
|
338
|
+
}
|
|
339
|
+
@keyframes fdbck-fade-in {
|
|
340
|
+
from {
|
|
341
|
+
opacity: 0;
|
|
342
|
+
}
|
|
343
|
+
to {
|
|
344
|
+
opacity: 1;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
@keyframes fdbck-scale-up {
|
|
348
|
+
from {
|
|
349
|
+
transform: scale(0.95);
|
|
350
|
+
opacity: 0;
|
|
351
|
+
}
|
|
352
|
+
to {
|
|
353
|
+
transform: scale(1);
|
|
354
|
+
opacity: 1;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
@media (prefers-reduced-motion: reduce) {
|
|
358
|
+
.fdbck-modal-backdrop,
|
|
359
|
+
.fdbck-modal-content {
|
|
360
|
+
animation: none;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* src/styles/popover.css */
|
|
365
|
+
.fdbck-popover {
|
|
366
|
+
position: fixed;
|
|
367
|
+
bottom: 1.5rem;
|
|
368
|
+
right: 1.5rem;
|
|
369
|
+
z-index: 2147483647;
|
|
370
|
+
animation: fdbck-slide-up 0.25s ease-out;
|
|
371
|
+
}
|
|
372
|
+
.fdbck-popover .fdbck-close-btn {
|
|
373
|
+
position: absolute;
|
|
374
|
+
top: -0.5rem;
|
|
375
|
+
right: -0.5rem;
|
|
376
|
+
}
|
|
377
|
+
@media (max-width: 640px) {
|
|
378
|
+
.fdbck-popover {
|
|
379
|
+
bottom: 0;
|
|
380
|
+
right: 0;
|
|
381
|
+
left: 0;
|
|
382
|
+
padding: 0;
|
|
383
|
+
animation: fdbck-sheet-up 0.25s ease-out;
|
|
384
|
+
}
|
|
385
|
+
.fdbck-popover .fdbck-card {
|
|
386
|
+
max-width: 100%;
|
|
387
|
+
width: 100%;
|
|
388
|
+
border-radius: var(--fdbck-border-radius) var(--fdbck-border-radius) 0 0;
|
|
389
|
+
border-bottom: none;
|
|
390
|
+
}
|
|
391
|
+
.fdbck-popover .fdbck-close-btn {
|
|
392
|
+
top: 0.75rem;
|
|
393
|
+
right: 0.75rem;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
@keyframes fdbck-slide-up {
|
|
397
|
+
from {
|
|
398
|
+
transform: translateY(1rem);
|
|
399
|
+
opacity: 0;
|
|
400
|
+
}
|
|
401
|
+
to {
|
|
402
|
+
transform: translateY(0);
|
|
403
|
+
opacity: 1;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
@keyframes fdbck-sheet-up {
|
|
407
|
+
from {
|
|
408
|
+
transform: translateY(100%);
|
|
409
|
+
}
|
|
410
|
+
to {
|
|
411
|
+
transform: translateY(0);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
@media (prefers-reduced-motion: reduce) {
|
|
415
|
+
.fdbck-popover {
|
|
416
|
+
animation: none;
|
|
417
|
+
}
|
|
418
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/** Error codes produced by the React SDK */
|
|
5
|
+
type FdbckErrorCode = 'invalid_token' | 'already_responded' | 'question_expired' | 'network_error' | 'dismissed' | 'unknown';
|
|
6
|
+
/** Typed error for fdbck React SDK */
|
|
7
|
+
declare class FdbckError extends Error {
|
|
8
|
+
readonly code: FdbckErrorCode;
|
|
9
|
+
constructor(code: FdbckErrorCode, message: string);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Question type */
|
|
13
|
+
type QuestionType = 'yes_no' | 'single_choice' | 'multiple_choice' | 'rating';
|
|
14
|
+
/** Rating scale options as returned by the API */
|
|
15
|
+
interface RatingOptions {
|
|
16
|
+
min: number;
|
|
17
|
+
max: number;
|
|
18
|
+
min_label?: string;
|
|
19
|
+
max_label?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Question data from GET /v1/f/:token */
|
|
22
|
+
interface QuestionData {
|
|
23
|
+
id: string;
|
|
24
|
+
question: string;
|
|
25
|
+
type: QuestionType;
|
|
26
|
+
options: string[] | RatingOptions;
|
|
27
|
+
theme_color: string | null;
|
|
28
|
+
theme_mode: 'light' | 'dark';
|
|
29
|
+
hide_branding: boolean;
|
|
30
|
+
welcome_message: string | null;
|
|
31
|
+
thank_you_message: string | null;
|
|
32
|
+
}
|
|
33
|
+
/** Response value submitted by the respondent */
|
|
34
|
+
type ResponseValue = string | string[] | number;
|
|
35
|
+
/** Display mode for the widget */
|
|
36
|
+
type FdbckMode = 'inline' | 'modal' | 'popover';
|
|
37
|
+
/** Result returned when a response is submitted or dismissed */
|
|
38
|
+
interface FdbckResult {
|
|
39
|
+
status: 'submitted' | 'dismissed';
|
|
40
|
+
value?: ResponseValue;
|
|
41
|
+
}
|
|
42
|
+
/** Custom locale strings */
|
|
43
|
+
interface FdbckLocale {
|
|
44
|
+
submit?: string;
|
|
45
|
+
poweredBy?: string;
|
|
46
|
+
loading?: string;
|
|
47
|
+
errorTitle?: string;
|
|
48
|
+
errorMessage?: string;
|
|
49
|
+
retry?: string;
|
|
50
|
+
close?: string;
|
|
51
|
+
}
|
|
52
|
+
/** Style overrides for the widget */
|
|
53
|
+
interface FdbckStyle {
|
|
54
|
+
width?: string;
|
|
55
|
+
maxWidth?: string;
|
|
56
|
+
borderRadius?: string;
|
|
57
|
+
fontFamily?: string;
|
|
58
|
+
fontSize?: string;
|
|
59
|
+
}
|
|
60
|
+
/** Props for the low-level FdbckWidget component (takes a pre-resolved token) */
|
|
61
|
+
interface FdbckWidgetProps {
|
|
62
|
+
token: string;
|
|
63
|
+
mode?: FdbckMode;
|
|
64
|
+
open?: boolean;
|
|
65
|
+
baseUrl?: string;
|
|
66
|
+
delay?: number;
|
|
67
|
+
autoCloseAfter?: number;
|
|
68
|
+
closeOnOverlayClick?: boolean;
|
|
69
|
+
closeOnEscape?: boolean;
|
|
70
|
+
locale?: FdbckLocale;
|
|
71
|
+
style?: FdbckStyle;
|
|
72
|
+
onSubmit?: (value: ResponseValue) => void;
|
|
73
|
+
onDismiss?: () => void;
|
|
74
|
+
onError?: (error: FdbckError) => void;
|
|
75
|
+
onLoad?: (question: QuestionData) => void;
|
|
76
|
+
children?: ReactNode;
|
|
77
|
+
}
|
|
78
|
+
/** Props for the FdbckProvider context */
|
|
79
|
+
interface FdbckProviderProps {
|
|
80
|
+
baseUrl?: string;
|
|
81
|
+
locale?: FdbckLocale;
|
|
82
|
+
style?: FdbckStyle;
|
|
83
|
+
children: ReactNode;
|
|
84
|
+
}
|
|
85
|
+
/** Return type from useFdbck hook */
|
|
86
|
+
interface UseFdbckReturn {
|
|
87
|
+
show: (options: UseFdbckOptions) => Promise<FdbckResult>;
|
|
88
|
+
dismiss: () => void;
|
|
89
|
+
isActive: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** Options passed to useFdbck().show() */
|
|
92
|
+
type UseFdbckOptions = {
|
|
93
|
+
token: string;
|
|
94
|
+
} & Omit<FdbckWidgetProps, 'token' | 'onSubmit' | 'onDismiss'>;
|
|
95
|
+
/** Token page response from GET /v1/f/:token */
|
|
96
|
+
interface TokenPageResponse {
|
|
97
|
+
status: 'valid' | 'invalid' | 'already_responded' | 'token_expired' | 'question_ended';
|
|
98
|
+
question?: QuestionData;
|
|
99
|
+
token?: string;
|
|
100
|
+
expires_at?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Low-level public component. Takes a pre-resolved token and renders the
|
|
105
|
+
* feedback widget in inline, modal, or popover mode.
|
|
106
|
+
*/
|
|
107
|
+
declare function FdbckWidget({ token, mode, open, baseUrl, delay, autoCloseAfter, closeOnOverlayClick, closeOnEscape, locale: localeProp, style: styleProp, onSubmit, onDismiss, onError, onLoad, }: FdbckWidgetProps): react_jsx_runtime.JSX.Element | null;
|
|
108
|
+
|
|
109
|
+
/** Context provider for the imperative useFdbck() hook */
|
|
110
|
+
declare function FdbckProvider({ baseUrl, locale, style, children }: FdbckProviderProps): react_jsx_runtime.JSX.Element;
|
|
111
|
+
|
|
112
|
+
/** Imperative API for showing/dismissing feedback widgets. Must be used within FdbckProvider. */
|
|
113
|
+
declare function useFdbck(): UseFdbckReturn;
|
|
114
|
+
|
|
115
|
+
export { FdbckError, type FdbckErrorCode, type FdbckLocale, type FdbckMode, FdbckProvider, type FdbckProviderProps, type FdbckResult, type FdbckStyle, FdbckWidget, type FdbckWidgetProps, type QuestionData, type QuestionType, type RatingOptions, type ResponseValue, type TokenPageResponse, type UseFdbckOptions, type UseFdbckReturn, useFdbck };
|