@drippr/embed-react 0.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/dist/index.js ADDED
@@ -0,0 +1,628 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DripprFlow: () => DripprFlow
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/DripprFlow.tsx
38
+ var React3 = __toESM(require("react"));
39
+
40
+ // src/InlineContainer.tsx
41
+ var import_jsx_runtime = require("react/jsx-runtime");
42
+ function InlineContainer({
43
+ src,
44
+ height,
45
+ iframeRef,
46
+ className,
47
+ style
48
+ }) {
49
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
50
+ "div",
51
+ {
52
+ className,
53
+ style: {
54
+ width: "100%",
55
+ ...style
56
+ },
57
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
58
+ "iframe",
59
+ {
60
+ ref: iframeRef,
61
+ src,
62
+ style: {
63
+ width: "100%",
64
+ height: `${height}px`,
65
+ border: "none",
66
+ display: "block"
67
+ },
68
+ loading: "lazy",
69
+ title: "Drippr Feedback"
70
+ }
71
+ )
72
+ }
73
+ );
74
+ }
75
+
76
+ // src/ModalContainer.tsx
77
+ var React = __toESM(require("react"));
78
+ var import_jsx_runtime2 = require("react/jsx-runtime");
79
+ var ANIMATION_DURATION = 200;
80
+ var MOBILE_BREAKPOINT = 640;
81
+ function ModalContainer({
82
+ src,
83
+ height,
84
+ iframeRef,
85
+ className,
86
+ style,
87
+ open,
88
+ onOpenChange,
89
+ trigger
90
+ }) {
91
+ const [mounted, setMounted] = React.useState(false);
92
+ const [visible, setVisible] = React.useState(false);
93
+ const [isMobile, setIsMobile] = React.useState(false);
94
+ React.useEffect(() => {
95
+ const checkMobile = () => {
96
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
97
+ };
98
+ checkMobile();
99
+ window.addEventListener("resize", checkMobile);
100
+ return () => window.removeEventListener("resize", checkMobile);
101
+ }, []);
102
+ React.useEffect(() => {
103
+ if (open) {
104
+ setMounted(true);
105
+ requestAnimationFrame(() => {
106
+ requestAnimationFrame(() => {
107
+ setVisible(true);
108
+ });
109
+ });
110
+ } else {
111
+ setVisible(false);
112
+ const timer = setTimeout(() => {
113
+ setMounted(false);
114
+ }, ANIMATION_DURATION);
115
+ return () => clearTimeout(timer);
116
+ }
117
+ }, [open]);
118
+ React.useEffect(() => {
119
+ if (!open) return;
120
+ const handleKeyDown = (e) => {
121
+ if (e.key === "Escape") {
122
+ onOpenChange(false);
123
+ }
124
+ };
125
+ document.addEventListener("keydown", handleKeyDown);
126
+ return () => {
127
+ document.removeEventListener("keydown", handleKeyDown);
128
+ };
129
+ }, [open, onOpenChange]);
130
+ React.useEffect(() => {
131
+ if (open) {
132
+ const originalOverflow = document.body.style.overflow;
133
+ document.body.style.overflow = "hidden";
134
+ return () => {
135
+ document.body.style.overflow = originalOverflow;
136
+ };
137
+ }
138
+ }, [open]);
139
+ const overlayStyles = {
140
+ position: "fixed",
141
+ inset: 0,
142
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
143
+ display: "flex",
144
+ alignItems: isMobile ? "flex-end" : "center",
145
+ justifyContent: "center",
146
+ zIndex: 9999,
147
+ padding: isMobile ? 0 : "16px",
148
+ // Animation
149
+ opacity: visible ? 1 : 0,
150
+ transition: `opacity ${ANIMATION_DURATION}ms ease-out`
151
+ };
152
+ const dialogStyles = isMobile ? {
153
+ // Mobile: bottom sheet
154
+ backgroundColor: "#fff",
155
+ borderRadius: "16px 16px 0 0",
156
+ width: "100%",
157
+ // Use dvh for dynamic viewport on iOS, fallback to vh
158
+ maxHeight: "calc(100dvh - 32px)",
159
+ overflow: "hidden",
160
+ position: "relative",
161
+ boxShadow: "0 -4px 25px -5px rgba(0, 0, 0, 0.1)",
162
+ // Animation - slide from bottom
163
+ transform: visible ? "translateY(0)" : "translateY(100%)",
164
+ transition: `transform ${ANIMATION_DURATION}ms cubic-bezier(0.32, 0.72, 0, 1)`
165
+ } : {
166
+ // Desktop: centered modal
167
+ backgroundColor: "#fff",
168
+ borderRadius: "12px",
169
+ width: "100%",
170
+ maxWidth: "600px",
171
+ maxHeight: "90vh",
172
+ overflow: "hidden",
173
+ position: "relative",
174
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
175
+ // Animation - scale + fade
176
+ opacity: visible ? 1 : 0,
177
+ transform: visible ? "scale(1)" : "scale(0.95)",
178
+ transition: `opacity ${ANIMATION_DURATION}ms ease-out, transform ${ANIMATION_DURATION}ms ease-out`
179
+ };
180
+ const closeButtonStyles = {
181
+ position: "absolute",
182
+ top: "8px",
183
+ right: "8px",
184
+ width: "32px",
185
+ height: "32px",
186
+ border: "none",
187
+ background: "transparent",
188
+ cursor: "pointer",
189
+ display: "flex",
190
+ alignItems: "center",
191
+ justifyContent: "center",
192
+ borderRadius: "6px",
193
+ color: "#666",
194
+ fontSize: "28px",
195
+ lineHeight: 1,
196
+ zIndex: 1
197
+ };
198
+ const handleStyles = {
199
+ width: "36px",
200
+ height: "4px",
201
+ backgroundColor: "#d1d5db",
202
+ borderRadius: "2px",
203
+ margin: "6px auto 2px"
204
+ };
205
+ if (!mounted) {
206
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: trigger });
207
+ }
208
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
209
+ trigger,
210
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
211
+ "div",
212
+ {
213
+ style: overlayStyles,
214
+ onClick: (e) => {
215
+ if (e.target === e.currentTarget) {
216
+ onOpenChange(false);
217
+ }
218
+ },
219
+ role: "dialog",
220
+ "aria-modal": "true",
221
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
222
+ "div",
223
+ {
224
+ className,
225
+ style: {
226
+ ...dialogStyles,
227
+ ...style
228
+ },
229
+ children: [
230
+ isMobile && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: handleStyles }),
231
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
232
+ "button",
233
+ {
234
+ type: "button",
235
+ style: closeButtonStyles,
236
+ onClick: () => onOpenChange(false),
237
+ "aria-label": "Close",
238
+ children: "\xD7"
239
+ }
240
+ ),
241
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
242
+ "iframe",
243
+ {
244
+ ref: iframeRef,
245
+ src,
246
+ style: {
247
+ width: "100%",
248
+ height: isMobile ? `${Math.min(height, window.innerHeight - 80)}px` : `${Math.min(height, window.innerHeight * 0.85)}px`,
249
+ border: "none",
250
+ display: "block"
251
+ },
252
+ loading: "lazy",
253
+ title: "Drippr Feedback"
254
+ }
255
+ )
256
+ ]
257
+ }
258
+ )
259
+ }
260
+ )
261
+ ] });
262
+ }
263
+
264
+ // src/DrawerContainer.tsx
265
+ var React2 = __toESM(require("react"));
266
+ var import_jsx_runtime3 = require("react/jsx-runtime");
267
+ var ANIMATION_DURATION2 = 250;
268
+ var MOBILE_BREAKPOINT2 = 640;
269
+ function DrawerContainer({
270
+ src,
271
+ height,
272
+ iframeRef,
273
+ className,
274
+ style,
275
+ open,
276
+ onOpenChange,
277
+ trigger
278
+ }) {
279
+ const [mounted, setMounted] = React2.useState(false);
280
+ const [visible, setVisible] = React2.useState(false);
281
+ const [isMobile, setIsMobile] = React2.useState(false);
282
+ React2.useEffect(() => {
283
+ const checkMobile = () => {
284
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT2);
285
+ };
286
+ checkMobile();
287
+ window.addEventListener("resize", checkMobile);
288
+ return () => window.removeEventListener("resize", checkMobile);
289
+ }, []);
290
+ React2.useEffect(() => {
291
+ if (open) {
292
+ setMounted(true);
293
+ requestAnimationFrame(() => {
294
+ requestAnimationFrame(() => {
295
+ setVisible(true);
296
+ });
297
+ });
298
+ } else {
299
+ setVisible(false);
300
+ const timer = setTimeout(() => {
301
+ setMounted(false);
302
+ }, ANIMATION_DURATION2);
303
+ return () => clearTimeout(timer);
304
+ }
305
+ }, [open]);
306
+ React2.useEffect(() => {
307
+ if (!open) return;
308
+ const handleKeyDown = (e) => {
309
+ if (e.key === "Escape") {
310
+ onOpenChange(false);
311
+ }
312
+ };
313
+ document.addEventListener("keydown", handleKeyDown);
314
+ return () => {
315
+ document.removeEventListener("keydown", handleKeyDown);
316
+ };
317
+ }, [open, onOpenChange]);
318
+ React2.useEffect(() => {
319
+ if (open) {
320
+ const originalOverflow = document.body.style.overflow;
321
+ document.body.style.overflow = "hidden";
322
+ return () => {
323
+ document.body.style.overflow = originalOverflow;
324
+ };
325
+ }
326
+ }, [open]);
327
+ const overlayStyles = {
328
+ position: "fixed",
329
+ inset: 0,
330
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
331
+ zIndex: 9999,
332
+ // Animation
333
+ opacity: visible ? 1 : 0,
334
+ transition: `opacity ${ANIMATION_DURATION2}ms ease-out`
335
+ };
336
+ const drawerStyles = isMobile ? {
337
+ // Mobile: bottom sheet
338
+ position: "fixed",
339
+ left: 0,
340
+ right: 0,
341
+ top: 32,
342
+ bottom: 0,
343
+ width: "100%",
344
+ backgroundColor: "#fff",
345
+ borderRadius: "16px 16px 0 0",
346
+ boxShadow: "0 -4px 25px -5px rgba(0, 0, 0, 0.1)",
347
+ zIndex: 1e4,
348
+ display: "flex",
349
+ flexDirection: "column",
350
+ overflow: "hidden",
351
+ // Animation - slide from bottom
352
+ transform: visible ? "translateY(0)" : "translateY(100%)",
353
+ transition: `transform ${ANIMATION_DURATION2}ms cubic-bezier(0.32, 0.72, 0, 1)`
354
+ } : {
355
+ // Desktop: right drawer
356
+ position: "fixed",
357
+ top: 0,
358
+ right: 0,
359
+ bottom: 0,
360
+ width: "100%",
361
+ maxWidth: "480px",
362
+ backgroundColor: "#fff",
363
+ boxShadow: "-4px 0 25px -5px rgba(0, 0, 0, 0.1)",
364
+ zIndex: 1e4,
365
+ display: "flex",
366
+ flexDirection: "column",
367
+ overflow: "hidden",
368
+ // Animation - slide from right
369
+ transform: visible ? "translateX(0)" : "translateX(100%)",
370
+ transition: `transform ${ANIMATION_DURATION2}ms cubic-bezier(0.32, 0.72, 0, 1)`
371
+ };
372
+ const headerStyles = {
373
+ display: "flex",
374
+ alignItems: "center",
375
+ justifyContent: "flex-end",
376
+ padding: isMobile ? "4px 8px" : "8px",
377
+ borderBottom: isMobile ? "none" : "1px solid #e5e5e5",
378
+ flexShrink: 0
379
+ };
380
+ const closeButtonStyles = {
381
+ width: "32px",
382
+ height: "32px",
383
+ border: "none",
384
+ background: "transparent",
385
+ cursor: "pointer",
386
+ display: "flex",
387
+ alignItems: "center",
388
+ justifyContent: "center",
389
+ borderRadius: "6px",
390
+ color: "#666",
391
+ fontSize: "28px",
392
+ lineHeight: 1
393
+ };
394
+ const contentStyles = {
395
+ flex: 1,
396
+ overflow: "auto"
397
+ };
398
+ const handleStyles = {
399
+ width: "36px",
400
+ height: "4px",
401
+ backgroundColor: "#d1d5db",
402
+ borderRadius: "2px",
403
+ margin: "6px auto 2px"
404
+ };
405
+ if (!mounted) {
406
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: trigger });
407
+ }
408
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
409
+ trigger,
410
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
411
+ "div",
412
+ {
413
+ style: overlayStyles,
414
+ onClick: () => onOpenChange(false),
415
+ "aria-hidden": "true"
416
+ }
417
+ ),
418
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
419
+ "div",
420
+ {
421
+ className,
422
+ style: {
423
+ ...drawerStyles,
424
+ ...style
425
+ },
426
+ role: "dialog",
427
+ "aria-modal": "true",
428
+ children: [
429
+ isMobile && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: handleStyles }),
430
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: headerStyles, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
431
+ "button",
432
+ {
433
+ type: "button",
434
+ style: closeButtonStyles,
435
+ onClick: () => onOpenChange(false),
436
+ "aria-label": "Close",
437
+ children: "\xD7"
438
+ }
439
+ ) }),
440
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: contentStyles, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
441
+ "iframe",
442
+ {
443
+ ref: iframeRef,
444
+ src,
445
+ style: {
446
+ width: "100%",
447
+ height: isMobile ? `${Math.min(height, window.innerHeight - 80)}px` : `${height}px`,
448
+ minHeight: isMobile ? void 0 : "100%",
449
+ border: "none",
450
+ display: "block"
451
+ },
452
+ loading: "lazy",
453
+ title: "Drippr Feedback"
454
+ }
455
+ ) })
456
+ ]
457
+ }
458
+ )
459
+ ] });
460
+ }
461
+
462
+ // src/DripprFlow.tsx
463
+ var import_jsx_runtime4 = require("react/jsx-runtime");
464
+ var DEFAULT_BASE_URL = "https://app.drippr.io";
465
+ var DEFAULT_HEIGHT = 700;
466
+ function generateInstanceId() {
467
+ return `drippr_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
468
+ }
469
+ function DripprFlow({
470
+ flowId,
471
+ mode = "inline",
472
+ open: controlledOpen,
473
+ onOpenChange,
474
+ prefill,
475
+ children,
476
+ triggerLabel = "Give Feedback",
477
+ onSubmitted,
478
+ className,
479
+ style,
480
+ height: fallbackHeight = DEFAULT_HEIGHT,
481
+ baseUrl = DEFAULT_BASE_URL
482
+ }) {
483
+ const [instanceId] = React3.useState(generateInstanceId);
484
+ const [height, setHeight] = React3.useState(fallbackHeight);
485
+ const [internalOpen, setInternalOpen] = React3.useState(false);
486
+ const iframeRef = React3.useRef(null);
487
+ const readyRef = React3.useRef(false);
488
+ const [expectedOrigin, setExpectedOrigin] = React3.useState("");
489
+ const isControlled = controlledOpen !== void 0;
490
+ const isOpen = isControlled ? controlledOpen : internalOpen;
491
+ const handleOpenChange = React3.useCallback(
492
+ (newOpen) => {
493
+ if (isControlled) {
494
+ onOpenChange?.(newOpen);
495
+ } else {
496
+ setInternalOpen(newOpen);
497
+ }
498
+ },
499
+ [isControlled, onOpenChange]
500
+ );
501
+ const src = React3.useMemo(() => {
502
+ const url = new URL(`/submit/${flowId}`, baseUrl);
503
+ url.searchParams.set("embed", "1");
504
+ url.searchParams.set("instanceId", instanceId);
505
+ setExpectedOrigin(url.origin);
506
+ return url.toString();
507
+ }, [flowId, baseUrl, instanceId]);
508
+ const api = React3.useMemo(
509
+ () => ({
510
+ open: () => handleOpenChange(true),
511
+ close: () => handleOpenChange(false)
512
+ }),
513
+ [handleOpenChange]
514
+ );
515
+ const sendPrefill = React3.useCallback(() => {
516
+ if (!prefill || !iframeRef.current?.contentWindow) return;
517
+ const message = {
518
+ type: "drippr:prefill",
519
+ instanceId,
520
+ payload: prefill
521
+ };
522
+ iframeRef.current.contentWindow.postMessage(message, expectedOrigin);
523
+ }, [prefill, instanceId, expectedOrigin]);
524
+ const handleMessage = React3.useCallback(
525
+ (event) => {
526
+ if (event.origin !== expectedOrigin) return;
527
+ const data = event.data;
528
+ if (!data || typeof data !== "object" || !data.type) return;
529
+ if (!data.type.startsWith("drippr:")) return;
530
+ if (data.instanceId !== instanceId) return;
531
+ switch (data.type) {
532
+ case "drippr:ready":
533
+ readyRef.current = true;
534
+ sendPrefill();
535
+ break;
536
+ case "drippr:resize":
537
+ if (typeof data.height === "number" && data.height > 0) {
538
+ setHeight(data.height);
539
+ }
540
+ break;
541
+ case "drippr:submitted":
542
+ onSubmitted?.();
543
+ if (mode !== "inline" && !isControlled) {
544
+ handleOpenChange(false);
545
+ }
546
+ break;
547
+ }
548
+ },
549
+ [expectedOrigin, instanceId, sendPrefill, onSubmitted, mode, isControlled, handleOpenChange]
550
+ );
551
+ React3.useEffect(() => {
552
+ window.addEventListener("message", handleMessage);
553
+ return () => {
554
+ window.removeEventListener("message", handleMessage);
555
+ };
556
+ }, [handleMessage]);
557
+ React3.useEffect(() => {
558
+ if (readyRef.current && prefill) {
559
+ sendPrefill();
560
+ }
561
+ }, [prefill, sendPrefill]);
562
+ const trigger = mode !== "inline" && children ? children(api) : mode !== "inline" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
563
+ "button",
564
+ {
565
+ type: "button",
566
+ onClick: () => handleOpenChange(true),
567
+ style: {
568
+ padding: "8px 16px",
569
+ borderRadius: "6px",
570
+ border: "1px solid #e5e5e5",
571
+ background: "#fff",
572
+ cursor: "pointer",
573
+ fontSize: "14px"
574
+ },
575
+ children: triggerLabel
576
+ }
577
+ ) : null;
578
+ if (mode === "inline") {
579
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
580
+ InlineContainer,
581
+ {
582
+ src,
583
+ height,
584
+ onMessage: handleMessage,
585
+ iframeRef,
586
+ className,
587
+ style
588
+ }
589
+ );
590
+ }
591
+ if (mode === "modal") {
592
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
593
+ ModalContainer,
594
+ {
595
+ src,
596
+ height,
597
+ onMessage: handleMessage,
598
+ iframeRef,
599
+ className,
600
+ style,
601
+ open: isOpen,
602
+ onOpenChange: handleOpenChange,
603
+ trigger
604
+ }
605
+ );
606
+ }
607
+ if (mode === "drawer") {
608
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
609
+ DrawerContainer,
610
+ {
611
+ src,
612
+ height,
613
+ onMessage: handleMessage,
614
+ iframeRef,
615
+ className,
616
+ style,
617
+ open: isOpen,
618
+ onOpenChange: handleOpenChange,
619
+ trigger
620
+ }
621
+ );
622
+ }
623
+ return null;
624
+ }
625
+ // Annotate the CommonJS export names for ESM import in node:
626
+ 0 && (module.exports = {
627
+ DripprFlow
628
+ });