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/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
- categoryBug: "Bug",
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: all 150ms;
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: #ffffff;
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 rgba(255,255,255,0.3);
175
- border-top-color: #ffffff;
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: #ffffff;
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 rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.05);
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 rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05);
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 rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05);
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: 99999;
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(100vh - 64px);
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 cubic-bezier(0.16, 1, 0.3, 1);
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-in forwards;
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: 90vh;
454
- animation: reflet-slide-up 300ms cubic-bezier(0.16, 1, 0.3, 1);
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-in forwards;
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: 32px;
483
- height: 32px;
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
- /* ===== Category Selector ===== */
557
- .reflet-categories {
558
- display: flex;
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
- .reflet-category {
563
- flex: 1;
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
- .reflet-category:hover {
578
- border-color: var(--reflet-primary);
579
- color: var(--reflet-text);
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
- .reflet-category[data-selected="true"] {
583
- border-color: var(--reflet-primary);
584
- background: color-mix(in srgb, var(--reflet-primary) 8%, transparent);
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
- function useFeedbackDialog({ open, onOpenChange, publicKey: publicKeyProp, baseUrl, user: userProp, userToken: userTokenProp, primaryColor, defaultCategory = "feature", labels: labelsProp, onSubmit, onOpen, onClose }) {
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
- injectFeedbackStyles();
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
- useEffect(() => {
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
- }, [defaultCategory]);
660
- const handleClose = useCallback(() => {
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
- if (previousActiveElement.current instanceof HTMLElement) previousActiveElement.current.focus();
668
- }, 200);
669
- }, [
670
- onOpenChange,
671
- onClose,
672
- resetForm
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: useCallback(async (e) => {
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, defaultCategory = "feature", categories = [
769
- "feature",
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__ */ jsxs("div", {
776
+ return /* @__PURE__ */ jsx("div", {
789
777
  "data-reflet-feedback": "",
790
778
  "data-theme": theme,
791
- children: [/* @__PURE__ */ jsx("button", {
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
- onKeyDown: handleTrapFocus,
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": "Close",
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, defaultCategory = "feature", categories, labels, onSubmit, onOpen, onClose, className, asChild = false, children }) {
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,