reflet-sdk 0.3.4 → 0.4.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/CHANGELOG.md +7 -0
- package/dist/client-BYuSKJw5.js.map +1 -1
- package/dist/{client-BpEYxx4d.d.ts → client-BomgCsXm.d.ts} +2 -2
- package/dist/{client-BpEYxx4d.d.ts.map → client-BomgCsXm.d.ts.map} +1 -1
- package/dist/feedback.d.ts +11 -15
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +748 -407
- package/dist/feedback.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/{react-context-DZw_HHjj.js → react-context-3_IbhIFB.js} +11 -2
- package/dist/react-context-3_IbhIFB.js.map +1 -0
- package/dist/react.d.ts +22 -33
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +206 -241
- package/dist/react.js.map +1 -1
- package/dist/server.d.ts +5 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/{types-wANRMsGI.d.ts → types-Cak4ZkSy.d.ts} +3 -1
- package/dist/types-Cak4ZkSy.d.ts.map +1 -0
- package/package.json +44 -44
- package/dist/react-context-DZw_HHjj.js.map +0 -1
- package/dist/types-wANRMsGI.d.ts.map +0 -1
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { a as REFLET_Z_INDEX, i as useRefletContext, n as RefletProvider, r as useRefletClient, t as RefletContext } from "./react-context-3_IbhIFB.js";
|
|
1
2
|
import { t as Reflet } from "./client-BYuSKJw5.js";
|
|
2
|
-
import { i as useRefletContext, n as RefletProvider, r as useRefletClient, t as RefletContext } from "./react-context-DZw_HHjj.js";
|
|
3
3
|
import { cloneElement, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/react-changelog-widget.tsx
|
|
@@ -96,9 +96,7 @@ function ChangelogWidget({ publicKey, mode = "card", position = "bottom-right",
|
|
|
96
96
|
//#region src/react-feedback-dialog-types.ts
|
|
97
97
|
const DEFAULT_LABELS = {
|
|
98
98
|
cancel: "Cancel",
|
|
99
|
-
|
|
100
|
-
categoryFeature: "Feature",
|
|
101
|
-
categoryQuestion: "Question",
|
|
99
|
+
close: "Close",
|
|
102
100
|
descriptionPlaceholder: "Tell us more... (optional)",
|
|
103
101
|
emailLabel: "Email",
|
|
104
102
|
emailPlaceholder: "your@email.com (optional)",
|
|
@@ -109,17 +107,11 @@ const DEFAULT_LABELS = {
|
|
|
109
107
|
title: "Send Feedback",
|
|
110
108
|
titlePlaceholder: "What's on your mind?"
|
|
111
109
|
};
|
|
112
|
-
const CATEGORY_ICONS = {
|
|
113
|
-
bug: "🐛",
|
|
114
|
-
feature: "✨",
|
|
115
|
-
question: "💬"
|
|
116
|
-
};
|
|
117
110
|
const AUTO_CLOSE_DELAY = 2500;
|
|
118
111
|
//#endregion
|
|
119
112
|
//#region src/react-feedback-styles-parts.ts
|
|
120
113
|
function getFeedbackChromeCSS() {
|
|
121
114
|
return `
|
|
122
|
-
/* ===== Footer ===== */
|
|
123
115
|
.reflet-footer {
|
|
124
116
|
display: flex;
|
|
125
117
|
justify-content: flex-end;
|
|
@@ -128,6 +120,7 @@ function getFeedbackChromeCSS() {
|
|
|
128
120
|
}
|
|
129
121
|
|
|
130
122
|
.reflet-btn {
|
|
123
|
+
min-height: 40px;
|
|
131
124
|
padding: 10px 20px;
|
|
132
125
|
border: none;
|
|
133
126
|
border-radius: var(--reflet-radius-sm);
|
|
@@ -135,7 +128,7 @@ function getFeedbackChromeCSS() {
|
|
|
135
128
|
font-size: 14px;
|
|
136
129
|
font-weight: 500;
|
|
137
130
|
cursor: pointer;
|
|
138
|
-
transition:
|
|
131
|
+
transition: background 150ms, color 150ms, opacity 150ms, transform 150ms var(--reflet-ease);
|
|
139
132
|
display: inline-flex;
|
|
140
133
|
align-items: center;
|
|
141
134
|
justify-content: center;
|
|
@@ -145,7 +138,7 @@ function getFeedbackChromeCSS() {
|
|
|
145
138
|
|
|
146
139
|
.reflet-btn-primary {
|
|
147
140
|
background: var(--reflet-primary);
|
|
148
|
-
color:
|
|
141
|
+
color: var(--reflet-on-primary);
|
|
149
142
|
}
|
|
150
143
|
|
|
151
144
|
.reflet-btn-primary:hover:not(:disabled) {
|
|
@@ -167,17 +160,15 @@ function getFeedbackChromeCSS() {
|
|
|
167
160
|
color: var(--reflet-text);
|
|
168
161
|
}
|
|
169
162
|
|
|
170
|
-
/* ===== Spinner ===== */
|
|
171
163
|
.reflet-spinner {
|
|
172
164
|
width: 16px;
|
|
173
165
|
height: 16px;
|
|
174
|
-
border: 2px solid
|
|
175
|
-
border-top-color:
|
|
166
|
+
border: 2px solid var(--reflet-on-primary-soft);
|
|
167
|
+
border-top-color: var(--reflet-on-primary);
|
|
176
168
|
border-radius: 50%;
|
|
177
169
|
animation: reflet-spin 600ms linear infinite;
|
|
178
170
|
}
|
|
179
171
|
|
|
180
|
-
/* ===== Success State ===== */
|
|
181
172
|
.reflet-success {
|
|
182
173
|
display: flex;
|
|
183
174
|
flex-direction: column;
|
|
@@ -223,28 +214,27 @@ function getFeedbackChromeCSS() {
|
|
|
223
214
|
margin: 0;
|
|
224
215
|
}
|
|
225
216
|
|
|
226
|
-
/* ===== Error State ===== */
|
|
227
217
|
.reflet-error-msg {
|
|
228
218
|
font-size: 13px;
|
|
229
219
|
color: var(--reflet-error);
|
|
230
220
|
margin: 0;
|
|
231
221
|
}
|
|
232
222
|
|
|
233
|
-
/* ===== Trigger Button ===== */
|
|
234
223
|
.reflet-trigger {
|
|
235
224
|
display: inline-flex;
|
|
236
225
|
align-items: center;
|
|
237
226
|
gap: 8px;
|
|
227
|
+
min-height: 40px;
|
|
238
228
|
padding: 10px 18px;
|
|
239
229
|
background: var(--reflet-primary);
|
|
240
|
-
color:
|
|
230
|
+
color: var(--reflet-on-primary);
|
|
241
231
|
border: none;
|
|
242
232
|
border-radius: var(--reflet-radius-sm);
|
|
243
233
|
font-family: var(--reflet-font);
|
|
244
234
|
font-size: 14px;
|
|
245
235
|
font-weight: 500;
|
|
246
236
|
cursor: pointer;
|
|
247
|
-
transition: background 150ms;
|
|
237
|
+
transition: background 150ms, transform 150ms var(--reflet-ease);
|
|
248
238
|
line-height: 1;
|
|
249
239
|
}
|
|
250
240
|
|
|
@@ -257,7 +247,6 @@ function getFeedbackChromeCSS() {
|
|
|
257
247
|
height: 16px;
|
|
258
248
|
}
|
|
259
249
|
|
|
260
|
-
/* ===== Honeypot ===== */
|
|
261
250
|
.reflet-hp {
|
|
262
251
|
position: absolute;
|
|
263
252
|
top: -9999px;
|
|
@@ -268,7 +257,6 @@ function getFeedbackChromeCSS() {
|
|
|
268
257
|
z-index: -1;
|
|
269
258
|
}
|
|
270
259
|
|
|
271
|
-
/* ===== Powered by ===== */
|
|
272
260
|
.reflet-powered {
|
|
273
261
|
text-align: center;
|
|
274
262
|
padding: 0 24px 16px;
|
|
@@ -286,7 +274,6 @@ function getFeedbackChromeCSS() {
|
|
|
286
274
|
color: var(--reflet-primary);
|
|
287
275
|
}
|
|
288
276
|
|
|
289
|
-
/* ===== Mobile handle ===== */
|
|
290
277
|
@media (max-width: 640px) {
|
|
291
278
|
.reflet-header::before {
|
|
292
279
|
content: '';
|
|
@@ -305,15 +292,18 @@ function getFeedbackChromeCSS() {
|
|
|
305
292
|
padding-top: 24px;
|
|
306
293
|
}
|
|
307
294
|
}
|
|
295
|
+
|
|
296
|
+
@media (prefers-reduced-motion: reduce) {
|
|
297
|
+
.reflet-success-icon svg path {
|
|
298
|
+
stroke-dasharray: none;
|
|
299
|
+
stroke-dashoffset: 0;
|
|
300
|
+
animation: none;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
308
303
|
`;
|
|
309
304
|
}
|
|
310
305
|
//#endregion
|
|
311
306
|
//#region src/react-feedback-styles.ts
|
|
312
|
-
/**
|
|
313
|
-
* Self-contained styles for the FeedbackButton and FeedbackDialog components.
|
|
314
|
-
* Uses CSS custom properties for theming. Injected via <style> tag with a unique ID
|
|
315
|
-
* to avoid duplication. All selectors are scoped under `[data-reflet-feedback]`.
|
|
316
|
-
*/
|
|
317
307
|
const STYLE_ID = "reflet-feedback-styles";
|
|
318
308
|
function injectFeedbackStyles() {
|
|
319
309
|
if (typeof document === "undefined") return;
|
|
@@ -325,9 +315,15 @@ function injectFeedbackStyles() {
|
|
|
325
315
|
}
|
|
326
316
|
function getFeedbackCSS() {
|
|
327
317
|
return `
|
|
318
|
+
:root {
|
|
319
|
+
--reflet-scrim: rgb(0 0 0 / 45%);
|
|
320
|
+
}
|
|
321
|
+
|
|
328
322
|
[data-reflet-feedback] {
|
|
329
323
|
--reflet-primary: #6366f1;
|
|
330
324
|
--reflet-primary-hover: #4f46e5;
|
|
325
|
+
--reflet-on-primary: #ffffff;
|
|
326
|
+
--reflet-on-primary-soft: rgb(255 255 255 / 30%);
|
|
331
327
|
--reflet-bg: #ffffff;
|
|
332
328
|
--reflet-bg-secondary: #f9fafb;
|
|
333
329
|
--reflet-text: #111827;
|
|
@@ -336,10 +332,11 @@ function getFeedbackCSS() {
|
|
|
336
332
|
--reflet-border: #e5e7eb;
|
|
337
333
|
--reflet-radius: 12px;
|
|
338
334
|
--reflet-radius-sm: 8px;
|
|
339
|
-
--reflet-shadow: 0 20px 60px -12px
|
|
335
|
+
--reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 25%), 0 0 0 1px rgb(0 0 0 / 5%);
|
|
340
336
|
--reflet-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
341
337
|
--reflet-success: #22c55e;
|
|
342
338
|
--reflet-error: #ef4444;
|
|
339
|
+
--reflet-ease: cubic-bezier(0.16, 1, 0.3, 1);
|
|
343
340
|
}
|
|
344
341
|
|
|
345
342
|
@media (prefers-color-scheme: dark) {
|
|
@@ -350,7 +347,7 @@ function getFeedbackCSS() {
|
|
|
350
347
|
--reflet-text-secondary: #9ca3af;
|
|
351
348
|
--reflet-text-tertiary: #6b7280;
|
|
352
349
|
--reflet-border: #374151;
|
|
353
|
-
--reflet-shadow: 0 20px 60px -12px
|
|
350
|
+
--reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 50%), 0 0 0 1px rgb(255 255 255 / 5%);
|
|
354
351
|
}
|
|
355
352
|
}
|
|
356
353
|
|
|
@@ -361,16 +358,7 @@ function getFeedbackCSS() {
|
|
|
361
358
|
--reflet-text-secondary: #9ca3af;
|
|
362
359
|
--reflet-text-tertiary: #6b7280;
|
|
363
360
|
--reflet-border: #374151;
|
|
364
|
-
--reflet-shadow: 0 20px 60px -12px
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/* ===== Overlay ===== */
|
|
368
|
-
.reflet-overlay {
|
|
369
|
-
position: fixed;
|
|
370
|
-
inset: 0;
|
|
371
|
-
background: rgba(0,0,0,0.4);
|
|
372
|
-
z-index: 99998;
|
|
373
|
-
animation: reflet-fade-in 150ms ease-out;
|
|
361
|
+
--reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 50%), 0 0 0 1px rgb(255 255 255 / 5%);
|
|
374
362
|
}
|
|
375
363
|
|
|
376
364
|
@keyframes reflet-fade-in {
|
|
@@ -412,34 +400,42 @@ function getFeedbackCSS() {
|
|
|
412
400
|
to { transform: rotate(360deg); }
|
|
413
401
|
}
|
|
414
402
|
|
|
415
|
-
.reflet-overlay[data-closing="true"] {
|
|
416
|
-
animation: reflet-fade-out 150ms ease-in forwards;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/* ===== Dialog (Desktop) ===== */
|
|
420
403
|
.reflet-dialog {
|
|
421
404
|
position: fixed;
|
|
422
405
|
top: 50%;
|
|
423
406
|
left: 50%;
|
|
424
407
|
transform: translate(-50%, -50%);
|
|
425
|
-
z-index:
|
|
408
|
+
z-index: ${REFLET_Z_INDEX.dialogContent};
|
|
409
|
+
margin: 0;
|
|
410
|
+
border: 0;
|
|
411
|
+
padding: 0;
|
|
426
412
|
background: var(--reflet-bg);
|
|
427
413
|
border-radius: var(--reflet-radius);
|
|
428
414
|
box-shadow: var(--reflet-shadow);
|
|
429
415
|
width: 480px;
|
|
430
416
|
max-width: calc(100vw - 32px);
|
|
431
|
-
max-height: calc(
|
|
417
|
+
max-height: calc(100dvh - 64px);
|
|
432
418
|
overflow: hidden;
|
|
419
|
+
overscroll-behavior: contain;
|
|
433
420
|
font-family: var(--reflet-font);
|
|
434
421
|
color: var(--reflet-text);
|
|
435
|
-
animation: reflet-scale-in 200ms
|
|
422
|
+
animation: reflet-scale-in 200ms var(--reflet-ease);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.reflet-dialog::backdrop {
|
|
426
|
+
z-index: ${REFLET_Z_INDEX.dialogOverlay};
|
|
427
|
+
background: var(--reflet-scrim);
|
|
428
|
+
animation: reflet-fade-in 150ms ease-out;
|
|
436
429
|
}
|
|
437
430
|
|
|
438
431
|
.reflet-dialog[data-closing="true"] {
|
|
439
|
-
animation: reflet-scale-out 150ms ease-
|
|
432
|
+
animation: reflet-scale-out 150ms ease-out forwards;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.reflet-dialog[data-closing="true"]::backdrop {
|
|
436
|
+
animation: reflet-fade-out 150ms ease-out forwards;
|
|
440
437
|
}
|
|
441
438
|
|
|
442
|
-
/* ===== Sheet (Mobile) ===== */
|
|
443
439
|
@media (max-width: 640px) {
|
|
444
440
|
.reflet-dialog {
|
|
445
441
|
top: auto;
|
|
@@ -450,16 +446,16 @@ function getFeedbackCSS() {
|
|
|
450
446
|
width: 100%;
|
|
451
447
|
max-width: 100%;
|
|
452
448
|
border-radius: var(--reflet-radius) var(--reflet-radius) 0 0;
|
|
453
|
-
max-height:
|
|
454
|
-
|
|
449
|
+
max-height: 90dvh;
|
|
450
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
451
|
+
animation: reflet-slide-up 300ms var(--reflet-ease);
|
|
455
452
|
}
|
|
456
453
|
|
|
457
454
|
.reflet-dialog[data-closing="true"] {
|
|
458
|
-
animation: reflet-slide-down 200ms ease-
|
|
455
|
+
animation: reflet-slide-down 200ms ease-out forwards;
|
|
459
456
|
}
|
|
460
457
|
}
|
|
461
458
|
|
|
462
|
-
/* ===== Header ===== */
|
|
463
459
|
.reflet-header {
|
|
464
460
|
display: flex;
|
|
465
461
|
align-items: center;
|
|
@@ -479,8 +475,8 @@ function getFeedbackCSS() {
|
|
|
479
475
|
display: flex;
|
|
480
476
|
align-items: center;
|
|
481
477
|
justify-content: center;
|
|
482
|
-
width:
|
|
483
|
-
height:
|
|
478
|
+
width: 40px;
|
|
479
|
+
height: 40px;
|
|
484
480
|
border: none;
|
|
485
481
|
background: transparent;
|
|
486
482
|
border-radius: 8px;
|
|
@@ -488,7 +484,7 @@ function getFeedbackCSS() {
|
|
|
488
484
|
cursor: pointer;
|
|
489
485
|
padding: 0;
|
|
490
486
|
flex-shrink: 0;
|
|
491
|
-
transition: background 150ms, color 150ms;
|
|
487
|
+
transition: background 150ms, color 150ms, transform 150ms var(--reflet-ease);
|
|
492
488
|
}
|
|
493
489
|
|
|
494
490
|
.reflet-close:hover {
|
|
@@ -501,7 +497,6 @@ function getFeedbackCSS() {
|
|
|
501
497
|
height: 18px;
|
|
502
498
|
}
|
|
503
499
|
|
|
504
|
-
/* ===== Form ===== */
|
|
505
500
|
.reflet-form {
|
|
506
501
|
padding: 20px 24px 24px;
|
|
507
502
|
display: flex;
|
|
@@ -553,43 +548,110 @@ function getFeedbackCSS() {
|
|
|
553
548
|
min-height: 100px;
|
|
554
549
|
}
|
|
555
550
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
gap: 8px;
|
|
551
|
+
[data-reflet-feedback] :focus-visible {
|
|
552
|
+
outline: 2px solid var(--reflet-primary);
|
|
553
|
+
outline-offset: 2px;
|
|
560
554
|
}
|
|
561
555
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
padding: 8px 12px;
|
|
565
|
-
border: 1px solid var(--reflet-border);
|
|
566
|
-
border-radius: var(--reflet-radius-sm);
|
|
567
|
-
background: var(--reflet-bg);
|
|
568
|
-
color: var(--reflet-text-secondary);
|
|
569
|
-
font-family: var(--reflet-font);
|
|
570
|
-
font-size: 13px;
|
|
571
|
-
font-weight: 500;
|
|
572
|
-
cursor: pointer;
|
|
573
|
-
text-align: center;
|
|
574
|
-
transition: all 150ms;
|
|
556
|
+
[data-reflet-feedback] button:active:not(:disabled) {
|
|
557
|
+
transform: scale(0.97);
|
|
575
558
|
}
|
|
576
559
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
560
|
+
@media (pointer: coarse), (max-width: 640px) {
|
|
561
|
+
.reflet-input,
|
|
562
|
+
.reflet-textarea {
|
|
563
|
+
font-size: 16px;
|
|
564
|
+
}
|
|
580
565
|
}
|
|
566
|
+
${getFeedbackChromeCSS()}
|
|
567
|
+
@media (prefers-reduced-motion: reduce) {
|
|
568
|
+
[data-reflet-feedback] *,
|
|
569
|
+
[data-reflet-feedback] *::before,
|
|
570
|
+
[data-reflet-feedback] *::after,
|
|
571
|
+
.reflet-dialog,
|
|
572
|
+
.reflet-dialog::backdrop {
|
|
573
|
+
animation-duration: 1ms !important;
|
|
574
|
+
animation-iteration-count: 1 !important;
|
|
575
|
+
transition-duration: 1ms !important;
|
|
576
|
+
}
|
|
581
577
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
color: var(--reflet-primary);
|
|
578
|
+
[data-reflet-feedback] button:active:not(:disabled) {
|
|
579
|
+
transform: none;
|
|
580
|
+
}
|
|
586
581
|
}
|
|
587
|
-
${getFeedbackChromeCSS()}
|
|
588
582
|
`;
|
|
589
583
|
}
|
|
590
584
|
//#endregion
|
|
591
585
|
//#region src/react-feedback-dialog-hook.ts
|
|
592
|
-
|
|
586
|
+
const CLOSE_ANIMATION_MS = 200;
|
|
587
|
+
function openDialog(dialog) {
|
|
588
|
+
if (typeof dialog.showModal === "function") {
|
|
589
|
+
dialog.showModal();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
dialog.open = true;
|
|
593
|
+
}
|
|
594
|
+
function closeDialog(dialog) {
|
|
595
|
+
if (typeof dialog.close === "function") {
|
|
596
|
+
dialog.close();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
dialog.open = false;
|
|
600
|
+
}
|
|
601
|
+
function useModalDialog(open, dialogRef, titleInputRef) {
|
|
602
|
+
useEffect(() => {
|
|
603
|
+
const dialog = dialogRef.current;
|
|
604
|
+
if (!(open && dialog) || dialog.open) return;
|
|
605
|
+
openDialog(dialog);
|
|
606
|
+
titleInputRef.current?.focus();
|
|
607
|
+
return () => closeDialog(dialog);
|
|
608
|
+
}, [
|
|
609
|
+
open,
|
|
610
|
+
dialogRef,
|
|
611
|
+
titleInputRef
|
|
612
|
+
]);
|
|
613
|
+
}
|
|
614
|
+
function useBackdropDismiss(open, dialogRef, onDismiss) {
|
|
615
|
+
useEffect(() => {
|
|
616
|
+
const dialog = dialogRef.current;
|
|
617
|
+
if (!(open && dialog)) return;
|
|
618
|
+
const dismiss = (event) => {
|
|
619
|
+
if (event.target === dialog) onDismiss();
|
|
620
|
+
};
|
|
621
|
+
dialog.addEventListener("click", dismiss);
|
|
622
|
+
return () => dialog.removeEventListener("click", dismiss);
|
|
623
|
+
}, [
|
|
624
|
+
open,
|
|
625
|
+
dialogRef,
|
|
626
|
+
onDismiss
|
|
627
|
+
]);
|
|
628
|
+
}
|
|
629
|
+
function useScrollLock(open) {
|
|
630
|
+
useEffect(() => {
|
|
631
|
+
if (!open) return;
|
|
632
|
+
const root = document.documentElement;
|
|
633
|
+
const previousOverflow = root.style.overflow;
|
|
634
|
+
const previousGutter = root.style.getPropertyValue("scrollbar-gutter");
|
|
635
|
+
root.style.setProperty("scrollbar-gutter", "stable");
|
|
636
|
+
root.style.overflow = "hidden";
|
|
637
|
+
return () => {
|
|
638
|
+
root.style.overflow = previousOverflow;
|
|
639
|
+
if (previousGutter) root.style.setProperty("scrollbar-gutter", previousGutter);
|
|
640
|
+
else root.style.removeProperty("scrollbar-gutter");
|
|
641
|
+
};
|
|
642
|
+
}, [open]);
|
|
643
|
+
}
|
|
644
|
+
function usePrimaryColor(primaryColor, dialogRef) {
|
|
645
|
+
useEffect(() => {
|
|
646
|
+
injectFeedbackStyles();
|
|
647
|
+
}, []);
|
|
648
|
+
useEffect(() => {
|
|
649
|
+
if (!(primaryColor && dialogRef.current)) return;
|
|
650
|
+
const host = dialogRef.current.closest("[data-reflet-feedback]");
|
|
651
|
+
if (host instanceof HTMLElement) host.style.setProperty("--reflet-primary", primaryColor);
|
|
652
|
+
}, [primaryColor, dialogRef]);
|
|
653
|
+
}
|
|
654
|
+
function useFeedbackDialog({ open, onOpenChange, publicKey: publicKeyProp, baseUrl, user: userProp, userToken: userTokenProp, primaryColor, labels: labelsProp, onSubmit, onOpen, onClose }) {
|
|
593
655
|
const context = useContext(RefletContext);
|
|
594
656
|
const publicKey = publicKeyProp ?? context?.publicKey;
|
|
595
657
|
const user = userProp ?? context?.user;
|
|
@@ -601,7 +663,6 @@ function useFeedbackDialog({ open, onOpenChange, publicKey: publicKeyProp, baseU
|
|
|
601
663
|
const [title, setTitle] = useState("");
|
|
602
664
|
const [description, setDescription] = useState("");
|
|
603
665
|
const [email, setEmail] = useState("");
|
|
604
|
-
const [category, setCategory] = useState(defaultCategory);
|
|
605
666
|
const [honeypot, setHoneypot] = useState("");
|
|
606
667
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
607
668
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
@@ -609,151 +670,83 @@ function useFeedbackDialog({ open, onOpenChange, publicKey: publicKeyProp, baseU
|
|
|
609
670
|
const [isClosing, setIsClosing] = useState(false);
|
|
610
671
|
const titleInputRef = useRef(null);
|
|
611
672
|
const dialogRef = useRef(null);
|
|
612
|
-
const previousActiveElement = useRef(null);
|
|
613
673
|
const isAnonymous = !(user || userToken);
|
|
674
|
+
usePrimaryColor(primaryColor, dialogRef);
|
|
675
|
+
useModalDialog(open, dialogRef, titleInputRef);
|
|
676
|
+
useScrollLock(open);
|
|
614
677
|
useEffect(() => {
|
|
615
|
-
|
|
616
|
-
}, []);
|
|
617
|
-
useEffect(() => {
|
|
618
|
-
if (!(primaryColor && dialogRef.current)) return;
|
|
619
|
-
const el = dialogRef.current.closest("[data-reflet-feedback]");
|
|
620
|
-
if (el instanceof HTMLElement) el.style.setProperty("--reflet-primary", primaryColor);
|
|
621
|
-
}, [primaryColor]);
|
|
622
|
-
useEffect(() => {
|
|
623
|
-
if (open) {
|
|
624
|
-
previousActiveElement.current = document.activeElement;
|
|
625
|
-
onOpen?.();
|
|
626
|
-
requestAnimationFrame(() => {
|
|
627
|
-
titleInputRef.current?.focus();
|
|
628
|
-
});
|
|
629
|
-
}
|
|
678
|
+
if (open) onOpen?.();
|
|
630
679
|
}, [open, onOpen]);
|
|
631
|
-
|
|
632
|
-
if (!open) return;
|
|
633
|
-
const handleKeyDown = (e) => {
|
|
634
|
-
if (e.key === "Escape") {
|
|
635
|
-
e.preventDefault();
|
|
636
|
-
handleClose();
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
640
|
-
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
641
|
-
});
|
|
642
|
-
useEffect(() => {
|
|
643
|
-
if (!open) return;
|
|
644
|
-
const originalOverflow = document.body.style.overflow;
|
|
645
|
-
document.body.style.overflow = "hidden";
|
|
646
|
-
return () => {
|
|
647
|
-
document.body.style.overflow = originalOverflow;
|
|
648
|
-
};
|
|
649
|
-
}, [open]);
|
|
650
|
-
const resetForm = useCallback(() => {
|
|
680
|
+
const resetForm = () => {
|
|
651
681
|
setTitle("");
|
|
652
682
|
setDescription("");
|
|
653
683
|
setEmail("");
|
|
654
|
-
setCategory(defaultCategory);
|
|
655
684
|
setHoneypot("");
|
|
656
685
|
setError(null);
|
|
657
686
|
setIsSuccess(false);
|
|
658
687
|
setIsSubmitting(false);
|
|
659
|
-
}
|
|
660
|
-
const handleClose =
|
|
688
|
+
};
|
|
689
|
+
const handleClose = () => {
|
|
661
690
|
setIsClosing(true);
|
|
662
691
|
setTimeout(() => {
|
|
663
692
|
setIsClosing(false);
|
|
664
693
|
onOpenChange(false);
|
|
665
694
|
onClose?.();
|
|
666
695
|
resetForm();
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
696
|
+
}, CLOSE_ANIMATION_MS);
|
|
697
|
+
};
|
|
698
|
+
useBackdropDismiss(open, dialogRef, handleClose);
|
|
699
|
+
const buildDescription = () => {
|
|
700
|
+
return `${description.trim() || "No additional details provided."}${isAnonymous && email ? `\n\n---\nContact: ${email}` : ""}`;
|
|
701
|
+
};
|
|
702
|
+
const handleSubmit = async (event) => {
|
|
703
|
+
event.preventDefault();
|
|
704
|
+
if (honeypot) {
|
|
705
|
+
setIsSuccess(true);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (!publicKey) {
|
|
709
|
+
setError("Missing publicKey. Provide it as a prop or via RefletProvider.");
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (!title.trim()) {
|
|
713
|
+
setError(labels.required);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
setIsSubmitting(true);
|
|
717
|
+
setError(null);
|
|
718
|
+
try {
|
|
719
|
+
const result = await new Reflet({
|
|
720
|
+
baseUrl,
|
|
721
|
+
publicKey,
|
|
722
|
+
user,
|
|
723
|
+
userToken
|
|
724
|
+
}).create({
|
|
725
|
+
description: buildDescription(),
|
|
726
|
+
title: title.trim()
|
|
727
|
+
});
|
|
728
|
+
setIsSuccess(true);
|
|
729
|
+
onSubmit?.(result);
|
|
730
|
+
setTimeout(handleClose, AUTO_CLOSE_DELAY);
|
|
731
|
+
} catch (err) {
|
|
732
|
+
setError(err instanceof Error ? err.message : "Something went wrong. Please try again.");
|
|
733
|
+
} finally {
|
|
734
|
+
setIsSubmitting(false);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
674
737
|
return {
|
|
675
|
-
category,
|
|
676
738
|
description,
|
|
677
739
|
dialogRef,
|
|
678
740
|
email,
|
|
679
741
|
error,
|
|
680
742
|
handleClose,
|
|
681
|
-
handleSubmit
|
|
682
|
-
e.preventDefault();
|
|
683
|
-
if (honeypot) {
|
|
684
|
-
setIsSuccess(true);
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
if (!publicKey) {
|
|
688
|
-
setError("Missing publicKey. Provide it as a prop or via RefletProvider.");
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
if (!title.trim()) {
|
|
692
|
-
setError(labels.required);
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
setIsSubmitting(true);
|
|
696
|
-
setError(null);
|
|
697
|
-
try {
|
|
698
|
-
const client = new Reflet({
|
|
699
|
-
baseUrl,
|
|
700
|
-
publicKey,
|
|
701
|
-
user,
|
|
702
|
-
userToken
|
|
703
|
-
});
|
|
704
|
-
const categoryPrefix = `[${category.charAt(0).toUpperCase() + category.slice(1)}] `;
|
|
705
|
-
const fullDescription = description.trim() ? description.trim() : "No additional details provided.";
|
|
706
|
-
const result = await client.create({
|
|
707
|
-
description: `${categoryPrefix}${fullDescription}${isAnonymous && email ? `\n\n---\nContact: ${email}` : ""}`,
|
|
708
|
-
title: title.trim()
|
|
709
|
-
});
|
|
710
|
-
setIsSuccess(true);
|
|
711
|
-
onSubmit?.(result);
|
|
712
|
-
setTimeout(() => {
|
|
713
|
-
handleClose();
|
|
714
|
-
}, AUTO_CLOSE_DELAY);
|
|
715
|
-
} catch (err) {
|
|
716
|
-
const message = err instanceof Error ? err.message : "Something went wrong. Please try again.";
|
|
717
|
-
setError(message);
|
|
718
|
-
} finally {
|
|
719
|
-
setIsSubmitting(false);
|
|
720
|
-
}
|
|
721
|
-
}, [
|
|
722
|
-
honeypot,
|
|
723
|
-
publicKey,
|
|
724
|
-
title,
|
|
725
|
-
description,
|
|
726
|
-
email,
|
|
727
|
-
category,
|
|
728
|
-
baseUrl,
|
|
729
|
-
user,
|
|
730
|
-
userToken,
|
|
731
|
-
isAnonymous,
|
|
732
|
-
labels.required,
|
|
733
|
-
onSubmit,
|
|
734
|
-
handleClose
|
|
735
|
-
]),
|
|
736
|
-
handleTrapFocus: useCallback((e) => {
|
|
737
|
-
if (e.key !== "Tab" || !dialogRef.current) return;
|
|
738
|
-
const focusable = dialogRef.current.querySelectorAll("button, input, textarea, [tabindex]:not([tabindex=\"-1\"])");
|
|
739
|
-
const first = focusable[0];
|
|
740
|
-
const last = focusable[focusable.length - 1];
|
|
741
|
-
if (!(first && last)) return;
|
|
742
|
-
if (e.shiftKey && document.activeElement === first) {
|
|
743
|
-
e.preventDefault();
|
|
744
|
-
last.focus();
|
|
745
|
-
} else if (!e.shiftKey && document.activeElement === last) {
|
|
746
|
-
e.preventDefault();
|
|
747
|
-
first.focus();
|
|
748
|
-
}
|
|
749
|
-
}, []),
|
|
743
|
+
handleSubmit,
|
|
750
744
|
honeypot,
|
|
751
745
|
isAnonymous,
|
|
752
746
|
isClosing,
|
|
753
747
|
isSubmitting,
|
|
754
748
|
isSuccess,
|
|
755
749
|
labels,
|
|
756
|
-
setCategory,
|
|
757
750
|
setDescription,
|
|
758
751
|
setEmail,
|
|
759
752
|
setError,
|
|
@@ -765,14 +758,9 @@ function useFeedbackDialog({ open, onOpenChange, publicKey: publicKeyProp, baseU
|
|
|
765
758
|
}
|
|
766
759
|
//#endregion
|
|
767
760
|
//#region src/react-feedback-dialog.tsx
|
|
768
|
-
function FeedbackDialog({ open, onOpenChange, publicKey, baseUrl, user, userToken, theme = "auto", primaryColor,
|
|
769
|
-
|
|
770
|
-
"bug",
|
|
771
|
-
"question"
|
|
772
|
-
], labels: labelsProp, onSubmit, onOpen, onClose }) {
|
|
773
|
-
const { labels, title, setTitle, description, setDescription, email, setEmail, category, setCategory, honeypot, setHoneypot, isSubmitting, isSuccess, error, setError, isClosing, isAnonymous, titleInputRef, dialogRef, handleClose, handleSubmit, handleTrapFocus } = useFeedbackDialog({
|
|
761
|
+
function FeedbackDialog({ open, onOpenChange, publicKey, baseUrl, user, userToken, theme = "auto", primaryColor, labels: labelsProp, onSubmit, onOpen, onClose }) {
|
|
762
|
+
const { labels, title, setTitle, description, setDescription, email, setEmail, honeypot, setHoneypot, isSubmitting, isSuccess, error, setError, isClosing, isAnonymous, titleInputRef, dialogRef, handleClose, handleSubmit } = useFeedbackDialog({
|
|
774
763
|
baseUrl,
|
|
775
|
-
defaultCategory,
|
|
776
764
|
labels: labelsProp,
|
|
777
765
|
onClose,
|
|
778
766
|
onOpen,
|
|
@@ -785,24 +773,18 @@ function FeedbackDialog({ open, onOpenChange, publicKey, baseUrl, user, userToke
|
|
|
785
773
|
userToken
|
|
786
774
|
});
|
|
787
775
|
if (!open) return null;
|
|
788
|
-
return /* @__PURE__ */
|
|
776
|
+
return /* @__PURE__ */ jsx("div", {
|
|
789
777
|
"data-reflet-feedback": "",
|
|
790
778
|
"data-theme": theme,
|
|
791
|
-
children:
|
|
792
|
-
"aria-label": "Close feedback dialog",
|
|
793
|
-
className: "reflet-overlay",
|
|
794
|
-
"data-closing": isClosing ? "true" : void 0,
|
|
795
|
-
onClick: handleClose,
|
|
796
|
-
tabIndex: 0,
|
|
797
|
-
type: "button"
|
|
798
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
779
|
+
children: /* @__PURE__ */ jsx("dialog", {
|
|
799
780
|
"aria-label": labels.title,
|
|
800
|
-
"aria-modal": "true",
|
|
801
781
|
className: "reflet-dialog",
|
|
802
782
|
"data-closing": isClosing ? "true" : void 0,
|
|
803
|
-
|
|
783
|
+
onCancel: (event) => {
|
|
784
|
+
event.preventDefault();
|
|
785
|
+
handleClose();
|
|
786
|
+
},
|
|
804
787
|
ref: dialogRef,
|
|
805
|
-
role: "dialog",
|
|
806
788
|
children: isSuccess ? /* @__PURE__ */ jsxs("div", {
|
|
807
789
|
className: "reflet-success",
|
|
808
790
|
children: [
|
|
@@ -835,7 +817,7 @@ function FeedbackDialog({ open, onOpenChange, publicKey, baseUrl, user, userToke
|
|
|
835
817
|
className: "reflet-title",
|
|
836
818
|
children: labels.title
|
|
837
819
|
}), /* @__PURE__ */ jsx("button", {
|
|
838
|
-
"aria-label":
|
|
820
|
+
"aria-label": labels.close,
|
|
839
821
|
className: "reflet-close",
|
|
840
822
|
onClick: handleClose,
|
|
841
823
|
type: "button",
|
|
@@ -865,20 +847,6 @@ function FeedbackDialog({ open, onOpenChange, publicKey, baseUrl, user, userToke
|
|
|
865
847
|
className: "reflet-form",
|
|
866
848
|
onSubmit: handleSubmit,
|
|
867
849
|
children: [
|
|
868
|
-
categories.length > 1 && /* @__PURE__ */ jsx("div", {
|
|
869
|
-
className: "reflet-categories",
|
|
870
|
-
children: categories.map((cat) => /* @__PURE__ */ jsxs("button", {
|
|
871
|
-
className: "reflet-category",
|
|
872
|
-
"data-selected": cat === category ? "true" : void 0,
|
|
873
|
-
onClick: () => setCategory(cat),
|
|
874
|
-
type: "button",
|
|
875
|
-
children: [
|
|
876
|
-
CATEGORY_ICONS[cat],
|
|
877
|
-
" ",
|
|
878
|
-
labels[`category${cat.charAt(0).toUpperCase()}${cat.slice(1)}`]
|
|
879
|
-
]
|
|
880
|
-
}, cat))
|
|
881
|
-
}),
|
|
882
850
|
/* @__PURE__ */ jsx("div", {
|
|
883
851
|
className: "reflet-field",
|
|
884
852
|
children: /* @__PURE__ */ jsx("input", {
|
|
@@ -916,7 +884,6 @@ function FeedbackDialog({ open, onOpenChange, publicKey, baseUrl, user, userToke
|
|
|
916
884
|
htmlFor: "reflet-email",
|
|
917
885
|
children: labels.emailLabel
|
|
918
886
|
}), /* @__PURE__ */ jsx("input", {
|
|
919
|
-
"aria-label": "Email address",
|
|
920
887
|
autoComplete: "email",
|
|
921
888
|
className: "reflet-input",
|
|
922
889
|
id: "reflet-email",
|
|
@@ -972,7 +939,7 @@ function FeedbackDialog({ open, onOpenChange, publicKey, baseUrl, user, userToke
|
|
|
972
939
|
]
|
|
973
940
|
})
|
|
974
941
|
] })
|
|
975
|
-
})
|
|
942
|
+
})
|
|
976
943
|
});
|
|
977
944
|
}
|
|
978
945
|
//#endregion
|
|
@@ -991,7 +958,7 @@ function FeedbackIcon() {
|
|
|
991
958
|
children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
|
|
992
959
|
});
|
|
993
960
|
}
|
|
994
|
-
function FeedbackButton({ publicKey, baseUrl, user, userToken, theme = "auto", primaryColor,
|
|
961
|
+
function FeedbackButton({ publicKey, baseUrl, user, userToken, theme = "auto", primaryColor, labels, onSubmit, onOpen, onClose, className, asChild = false, children }) {
|
|
995
962
|
const [open, setOpen] = useState(false);
|
|
996
963
|
useState(() => {
|
|
997
964
|
injectFeedbackStyles();
|
|
@@ -1021,8 +988,6 @@ function FeedbackButton({ publicKey, baseUrl, user, userToken, theme = "auto", p
|
|
|
1021
988
|
}
|
|
1022
989
|
return /* @__PURE__ */ jsxs(Fragment, { children: [trigger, /* @__PURE__ */ jsx(FeedbackDialog, {
|
|
1023
990
|
baseUrl,
|
|
1024
|
-
categories,
|
|
1025
|
-
defaultCategory,
|
|
1026
991
|
labels,
|
|
1027
992
|
onClose,
|
|
1028
993
|
onOpen,
|