@caffeinebounce/ui 0.62.2

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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/dist/FinancialStatementTable-BNUrdTU4.d.cts +249 -0
  4. package/dist/FinancialStatementTable-BNUrdTU4.d.ts +249 -0
  5. package/dist/NewsletterSignup-BPreFi5W.d.cts +37 -0
  6. package/dist/NewsletterSignup-BPreFi5W.d.ts +37 -0
  7. package/dist/TestimonialsSection-DtPu2OeR.d.ts +736 -0
  8. package/dist/TestimonialsSection-z-yotRfv.d.cts +736 -0
  9. package/dist/UnifiedTableControls-CG299y9h.d.ts +1233 -0
  10. package/dist/UnifiedTableControls-CIjRpNhy.d.cts +1233 -0
  11. package/dist/blog.d.cts +99 -0
  12. package/dist/blog.d.ts +99 -0
  13. package/dist/blog.js +292 -0
  14. package/dist/blog.mjs +279 -0
  15. package/dist/button-BHnoRUkF.d.cts +34 -0
  16. package/dist/button-BHnoRUkF.d.ts +34 -0
  17. package/dist/charts.d.cts +80 -0
  18. package/dist/charts.d.ts +80 -0
  19. package/dist/charts.js +19 -0
  20. package/dist/charts.mjs +2 -0
  21. package/dist/chunk-33JR5SYE.js +44 -0
  22. package/dist/chunk-3NDB4YPC.js +247 -0
  23. package/dist/chunk-5RW4PYZ6.js +1504 -0
  24. package/dist/chunk-5THF4YP6.mjs +155 -0
  25. package/dist/chunk-6BUBNZPG.mjs +164 -0
  26. package/dist/chunk-6DVWMJ3H.mjs +7029 -0
  27. package/dist/chunk-6TGV2DNP.js +120 -0
  28. package/dist/chunk-72JGQ7IJ.mjs +1498 -0
  29. package/dist/chunk-7DHKQODW.mjs +2932 -0
  30. package/dist/chunk-7IKHZKAC.js +160 -0
  31. package/dist/chunk-ADIDI7AJ.js +11 -0
  32. package/dist/chunk-BFWX5GJZ.mjs +940 -0
  33. package/dist/chunk-BMFQG6RA.mjs +5537 -0
  34. package/dist/chunk-C4UT5V3P.js +65 -0
  35. package/dist/chunk-CC4KUIKC.js +2004 -0
  36. package/dist/chunk-D3QXPL47.mjs +1814 -0
  37. package/dist/chunk-DQWHYBV2.js +36 -0
  38. package/dist/chunk-E42B4GSB.js +993 -0
  39. package/dist/chunk-ESZ7CBA2.js +1007 -0
  40. package/dist/chunk-EYJJQVV4.js +111 -0
  41. package/dist/chunk-FRRS2YZH.mjs +129 -0
  42. package/dist/chunk-G5TQWWQA.mjs +37 -0
  43. package/dist/chunk-GAIQJCAZ.js +5612 -0
  44. package/dist/chunk-GMBGWOCR.mjs +48 -0
  45. package/dist/chunk-GUSTCU2W.mjs +105 -0
  46. package/dist/chunk-HBLKVQIQ.mjs +23 -0
  47. package/dist/chunk-HMAMMC72.js +26 -0
  48. package/dist/chunk-IMKLN273.mjs +9 -0
  49. package/dist/chunk-J6OLBSZR.js +250 -0
  50. package/dist/chunk-JAE45W2T.js +28 -0
  51. package/dist/chunk-JORVSG52.js +606 -0
  52. package/dist/chunk-KB7UXIQZ.mjs +984 -0
  53. package/dist/chunk-KGGQ5NZV.js +186 -0
  54. package/dist/chunk-KHCC5UKW.mjs +22 -0
  55. package/dist/chunk-KRRIRS6A.mjs +1945 -0
  56. package/dist/chunk-LQCVRWVF.js +48 -0
  57. package/dist/chunk-MHZXUOGN.mjs +168 -0
  58. package/dist/chunk-MPZFPU4X.js +349 -0
  59. package/dist/chunk-MVYRNJA6.js +74 -0
  60. package/dist/chunk-N6AFNFZR.mjs +565 -0
  61. package/dist/chunk-OFQLWSUR.mjs +214 -0
  62. package/dist/chunk-OLAAXLXY.mjs +219 -0
  63. package/dist/chunk-OZZWDIOY.js +1859 -0
  64. package/dist/chunk-PZRI43EZ.js +111 -0
  65. package/dist/chunk-Q256HRYD.js +176 -0
  66. package/dist/chunk-Q52ZEL62.mjs +223 -0
  67. package/dist/chunk-R26Y7LGJ.js +142 -0
  68. package/dist/chunk-RBUYW6QQ.js +39 -0
  69. package/dist/chunk-RVEFPVZF.mjs +43 -0
  70. package/dist/chunk-SORWLKOK.js +96 -0
  71. package/dist/chunk-STEP6C4C.mjs +33 -0
  72. package/dist/chunk-T5R6HUDG.mjs +22 -0
  73. package/dist/chunk-T6ZBA6VJ.js +7109 -0
  74. package/dist/chunk-UM7WMXIU.js +2995 -0
  75. package/dist/chunk-VBD4Q5LL.mjs +117 -0
  76. package/dist/chunk-WOITB4JL.js +195 -0
  77. package/dist/chunk-YKZEKJMZ.mjs +71 -0
  78. package/dist/chunk-YL7RZRHM.mjs +103 -0
  79. package/dist/chunk-YSLD2HLP.mjs +346 -0
  80. package/dist/chunk-ZFLZA7UP.mjs +140 -0
  81. package/dist/chunk-ZKUIUE7W.mjs +26 -0
  82. package/dist/chunk-ZOA7B32D.js +248 -0
  83. package/dist/data-table.d.cts +46 -0
  84. package/dist/data-table.d.ts +46 -0
  85. package/dist/data-table.js +270 -0
  86. package/dist/data-table.mjs +13 -0
  87. package/dist/editor.d.cts +89 -0
  88. package/dist/editor.d.ts +89 -0
  89. package/dist/editor.js +10 -0
  90. package/dist/editor.mjs +1 -0
  91. package/dist/forms.d.cts +142 -0
  92. package/dist/forms.d.ts +142 -0
  93. package/dist/forms.js +24 -0
  94. package/dist/forms.mjs +11 -0
  95. package/dist/hooks-8ORd7T1A.d.cts +751 -0
  96. package/dist/hooks-B2jU1GVT.d.ts +751 -0
  97. package/dist/hooks.d.cts +6 -0
  98. package/dist/hooks.d.ts +6 -0
  99. package/dist/hooks.js +99 -0
  100. package/dist/hooks.mjs +10 -0
  101. package/dist/index.d.cts +3141 -0
  102. package/dist/index.d.ts +3141 -0
  103. package/dist/index.js +7332 -0
  104. package/dist/index.mjs +5876 -0
  105. package/dist/layouts-DPfDDF53.d.cts +1001 -0
  106. package/dist/layouts-DPfDDF53.d.ts +1001 -0
  107. package/dist/layouts.d.cts +4 -0
  108. package/dist/layouts.d.ts +4 -0
  109. package/dist/layouts.js +100 -0
  110. package/dist/layouts.mjs +11 -0
  111. package/dist/marketing-3d.d.cts +51 -0
  112. package/dist/marketing-3d.d.ts +51 -0
  113. package/dist/marketing-3d.js +553 -0
  114. package/dist/marketing-3d.mjs +526 -0
  115. package/dist/marketing.d.cts +583 -0
  116. package/dist/marketing.d.ts +583 -0
  117. package/dist/marketing.js +299 -0
  118. package/dist/marketing.mjs +10 -0
  119. package/dist/media.d.cts +29 -0
  120. package/dist/media.d.ts +29 -0
  121. package/dist/media.js +189 -0
  122. package/dist/media.mjs +182 -0
  123. package/dist/navigation.d.cts +591 -0
  124. package/dist/navigation.d.ts +591 -0
  125. package/dist/navigation.js +108 -0
  126. package/dist/navigation.mjs +15 -0
  127. package/dist/portal.d.cts +58 -0
  128. package/dist/portal.d.ts +58 -0
  129. package/dist/portal.js +403 -0
  130. package/dist/portal.mjs +395 -0
  131. package/dist/primitives.d.cts +508 -0
  132. package/dist/primitives.d.ts +508 -0
  133. package/dist/primitives.js +295 -0
  134. package/dist/primitives.mjs +14 -0
  135. package/dist/settings.d.cts +216 -0
  136. package/dist/settings.d.ts +216 -0
  137. package/dist/settings.js +34 -0
  138. package/dist/settings.mjs +9 -0
  139. package/dist/shared-Dqn36QHZ.d.cts +4 -0
  140. package/dist/shared-Dqn36QHZ.d.ts +4 -0
  141. package/dist/styles.css +1213 -0
  142. package/package.json +189 -0
  143. package/styles.css +1 -0
@@ -0,0 +1,346 @@
1
+ import { useLocalStorage, useDebounce } from './chunk-KB7UXIQZ.mjs';
2
+ import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from './chunk-YKZEKJMZ.mjs';
3
+ import { cn } from './chunk-IMKLN273.mjs';
4
+ import { useErrorLoggerSafe } from '@caffeinebounce/logger/client';
5
+ import { Check } from 'lucide-react';
6
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
+
9
+ function FormWizard({
10
+ steps,
11
+ currentStep,
12
+ onStepChange,
13
+ getStepStatus,
14
+ getStepTooltip,
15
+ enableKeyboardNavigation = true,
16
+ scopeToDialog = true,
17
+ onCancel,
18
+ onSubmit,
19
+ onReset,
20
+ autoFocusSelector,
21
+ persistKey,
22
+ formData,
23
+ onDataRestored,
24
+ children,
25
+ className
26
+ }) {
27
+ const totalSteps = steps.length;
28
+ const isFirstStep = currentStep === 0;
29
+ const isLastStep = currentStep === totalSteps - 1;
30
+ const { logError } = useErrorLoggerSafe();
31
+ const [storedValue, setStoredValue, , isLoaded] = useLocalStorage(
32
+ persistKey,
33
+ null
34
+ );
35
+ const debouncedData = useDebounce(formData, 500);
36
+ const [hasRestored, setHasRestored] = useState(false);
37
+ const isRestoring = useRef(false);
38
+ const formDataRef = useRef(formData);
39
+ const restoredDataRef = useRef(null);
40
+ useEffect(() => {
41
+ formDataRef.current = formData;
42
+ }, [formData]);
43
+ const hasRestoredDataPropagated = useCallback(() => {
44
+ if (!restoredDataRef.current) return true;
45
+ const restored = restoredDataRef.current;
46
+ const current = debouncedData;
47
+ for (const key of Object.keys(restored)) {
48
+ const restoredVal = restored[key];
49
+ const currentVal = current?.[key];
50
+ if (!restoredVal || restoredVal === "") continue;
51
+ if (Array.isArray(restoredVal) && restoredVal.length === 0) continue;
52
+ const isObjectLike = typeof restoredVal === "object" && restoredVal !== null;
53
+ const valuesMatch = isObjectLike ? JSON.stringify(currentVal) === JSON.stringify(restoredVal) : currentVal === restoredVal;
54
+ if (!valuesMatch) {
55
+ return false;
56
+ }
57
+ }
58
+ restoredDataRef.current = null;
59
+ return true;
60
+ }, [debouncedData]);
61
+ useEffect(() => {
62
+ if (hasRestored && persistKey && debouncedData) {
63
+ if (isRestoring.current) {
64
+ return;
65
+ }
66
+ if (restoredDataRef.current && !hasRestoredDataPropagated()) {
67
+ return;
68
+ }
69
+ setStoredValue(debouncedData);
70
+ }
71
+ }, [
72
+ hasRestored,
73
+ persistKey,
74
+ debouncedData,
75
+ setStoredValue,
76
+ hasRestoredDataPropagated
77
+ ]);
78
+ useEffect(() => {
79
+ return () => {
80
+ if (hasRestored && persistKey && formDataRef.current) {
81
+ if (isRestoring.current) {
82
+ return;
83
+ }
84
+ if (restoredDataRef.current) {
85
+ return;
86
+ }
87
+ try {
88
+ window.localStorage.setItem(
89
+ persistKey,
90
+ JSON.stringify(formDataRef.current)
91
+ );
92
+ } catch (e) {
93
+ logError(e, {
94
+ component: "FormWizard",
95
+ action: "saveOnUnmount",
96
+ metadata: { persistKey }
97
+ });
98
+ }
99
+ }
100
+ };
101
+ }, [hasRestored, persistKey, logError]);
102
+ useEffect(() => {
103
+ if (!isLoaded) {
104
+ return;
105
+ }
106
+ if (persistKey && storedValue && !hasRestored && onDataRestored) {
107
+ restoredDataRef.current = storedValue;
108
+ onDataRestored(storedValue);
109
+ isRestoring.current = true;
110
+ setTimeout(() => {
111
+ isRestoring.current = false;
112
+ }, 600);
113
+ setHasRestored(true);
114
+ } else if (persistKey && !storedValue && !hasRestored) {
115
+ setHasRestored(true);
116
+ }
117
+ }, [isLoaded, persistKey, storedValue, hasRestored, onDataRestored]);
118
+ useEffect(() => {
119
+ if (!autoFocusSelector) return;
120
+ const timeoutId = setTimeout(() => {
121
+ const element = document.querySelector(autoFocusSelector);
122
+ element?.focus();
123
+ }, 0);
124
+ return () => clearTimeout(timeoutId);
125
+ }, [autoFocusSelector]);
126
+ const stepStatuses = useMemo(() => {
127
+ return steps.map((_, index) => getStepStatus(index));
128
+ }, [steps, getStepStatus]);
129
+ const handleStepClick = useCallback(
130
+ (stepIndex) => {
131
+ onStepChange(stepIndex);
132
+ },
133
+ [onStepChange]
134
+ );
135
+ const handleNext = useCallback(() => {
136
+ if (!isLastStep) {
137
+ onStepChange(currentStep + 1);
138
+ }
139
+ }, [currentStep, isLastStep, onStepChange]);
140
+ const handleBack = useCallback(() => {
141
+ if (!isFirstStep) {
142
+ onStepChange(currentStep - 1);
143
+ }
144
+ }, [currentStep, isFirstStep, onStepChange]);
145
+ useEffect(() => {
146
+ if (!enableKeyboardNavigation) return;
147
+ const handleKeyDown = (e) => {
148
+ if (scopeToDialog) {
149
+ const target = e.target;
150
+ const isInDialog = target.closest('[role="dialog"]');
151
+ if (!isInDialog) return;
152
+ }
153
+ if (e.key === "Escape" && onCancel) {
154
+ e.preventDefault();
155
+ e.stopPropagation();
156
+ onCancel();
157
+ return;
158
+ }
159
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && onSubmit) {
160
+ e.preventDefault();
161
+ e.stopPropagation();
162
+ onSubmit();
163
+ return;
164
+ }
165
+ if ((e.key === "r" || e.key === "\xAE" || e.code === "KeyR") && e.altKey && !e.shiftKey && !e.metaKey && !e.ctrlKey && onReset) {
166
+ e.preventDefault();
167
+ e.stopPropagation();
168
+ if (persistKey) {
169
+ try {
170
+ window.localStorage.removeItem(persistKey);
171
+ setStoredValue(null);
172
+ } catch (err) {
173
+ logError(err, {
174
+ component: "FormWizard",
175
+ action: "clearStorageOnReset",
176
+ metadata: { persistKey }
177
+ });
178
+ }
179
+ }
180
+ onReset();
181
+ return;
182
+ }
183
+ if (!e.altKey) return;
184
+ if (e.key === "ArrowLeft" && !isFirstStep) {
185
+ e.preventDefault();
186
+ e.stopPropagation();
187
+ handleBack();
188
+ } else if (e.key === "ArrowRight" && !isLastStep) {
189
+ e.preventDefault();
190
+ e.stopPropagation();
191
+ handleNext();
192
+ }
193
+ };
194
+ window.addEventListener("keydown", handleKeyDown, true);
195
+ return () => window.removeEventListener("keydown", handleKeyDown, true);
196
+ }, [
197
+ enableKeyboardNavigation,
198
+ scopeToDialog,
199
+ isFirstStep,
200
+ isLastStep,
201
+ handleBack,
202
+ handleNext,
203
+ onCancel,
204
+ onSubmit,
205
+ onReset,
206
+ persistKey,
207
+ setStoredValue,
208
+ logError
209
+ ]);
210
+ const getStatusStyles = useCallback(
211
+ (stepIndex) => {
212
+ const status = stepStatuses[stepIndex];
213
+ const isCurrent = stepIndex === currentStep;
214
+ const step = steps[stepIndex];
215
+ const Icon = step.icon;
216
+ const tooltip = getStepTooltip?.(stepIndex, status) ?? null;
217
+ switch (status) {
218
+ case "error":
219
+ return {
220
+ border: "border-destructive bg-destructive",
221
+ text: "text-destructive",
222
+ icon: /* @__PURE__ */ jsx(Icon, { className: "w-5 h-5 text-white" }),
223
+ tooltip: tooltip || "This section has errors"
224
+ };
225
+ case "complete":
226
+ return {
227
+ border: "border-foreground bg-foreground text-background",
228
+ text: "text-foreground",
229
+ icon: /* @__PURE__ */ jsx(Check, { className: "w-5 h-5" }),
230
+ tooltip: null
231
+ };
232
+ case "incomplete":
233
+ return {
234
+ border: "border-yellow-500 bg-yellow-500 text-white",
235
+ text: "text-yellow-600 dark:text-yellow-500",
236
+ icon: /* @__PURE__ */ jsx(Icon, { className: "w-5 h-5" }),
237
+ tooltip: tooltip || "This section is incomplete"
238
+ };
239
+ case "in-progress":
240
+ return {
241
+ border: "border-primary bg-primary text-primary-foreground",
242
+ text: "text-primary",
243
+ icon: /* @__PURE__ */ jsx(Icon, { className: "w-5 h-5" }),
244
+ tooltip: null
245
+ };
246
+ default:
247
+ return {
248
+ border: isCurrent ? "border-muted-foreground bg-muted-foreground text-background dark:border-muted dark:bg-muted dark:text-foreground" : "border-transparent bg-background",
249
+ text: isCurrent ? "text-foreground" : "text-muted-foreground",
250
+ icon: /* @__PURE__ */ jsx(Icon, { className: "w-5 h-5" }),
251
+ tooltip: null
252
+ };
253
+ }
254
+ },
255
+ [stepStatuses, currentStep, steps, getStepTooltip]
256
+ );
257
+ const progressPercentage = useMemo(() => {
258
+ if (totalSteps <= 1) return 0;
259
+ return currentStep / (totalSteps - 1) * 100;
260
+ }, [currentStep, totalSteps]);
261
+ return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: cn("space-y-8", className), children: [
262
+ /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-between", children: [
263
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-5 left-5 right-5 h-[2px]", children: [
264
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-border/60 rounded-full" }),
265
+ /* @__PURE__ */ jsx(
266
+ "div",
267
+ {
268
+ className: "absolute inset-y-0 left-0 bg-foreground rounded-full transition-all duration-500 ease-out",
269
+ style: { width: `${progressPercentage}%` }
270
+ }
271
+ )
272
+ ] }),
273
+ steps.map((step, index) => {
274
+ const styles = getStatusStyles(index);
275
+ const stepButton = /* @__PURE__ */ jsxs(
276
+ "button",
277
+ {
278
+ type: "button",
279
+ onClick: () => handleStepClick(index),
280
+ "aria-label": `Step ${index + 1}: ${step.label}`,
281
+ "aria-current": index === currentStep ? "step" : void 0,
282
+ className: cn(
283
+ "relative z-10 flex flex-col items-center gap-2 transition-colors cursor-pointer",
284
+ styles.text
285
+ ),
286
+ children: [
287
+ /* @__PURE__ */ jsx(
288
+ "div",
289
+ {
290
+ className: cn(
291
+ "w-10 h-10 rounded-full flex items-center justify-center border-2 transition-colors",
292
+ styles.border
293
+ ),
294
+ children: styles.icon
295
+ }
296
+ ),
297
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium hidden sm:block", children: step.label })
298
+ ]
299
+ },
300
+ step.id
301
+ );
302
+ if (styles.tooltip) {
303
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
304
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: stepButton }),
305
+ /* @__PURE__ */ jsx(TooltipContent, { className: "whitespace-pre-line", children: styles.tooltip })
306
+ ] }, step.id);
307
+ }
308
+ return stepButton;
309
+ })
310
+ ] }) }),
311
+ children
312
+ ] }) });
313
+ }
314
+ function useFormWizardNavigation(currentStep, totalSteps, onStepChange) {
315
+ const isFirstStep = currentStep === 0;
316
+ const isLastStep = currentStep === totalSteps - 1;
317
+ const goNext = useCallback(() => {
318
+ if (!isLastStep) {
319
+ onStepChange(currentStep + 1);
320
+ }
321
+ }, [currentStep, isLastStep, onStepChange]);
322
+ const goBack = useCallback(() => {
323
+ if (!isFirstStep) {
324
+ onStepChange(currentStep - 1);
325
+ }
326
+ }, [currentStep, isFirstStep, onStepChange]);
327
+ const goToStep = useCallback(
328
+ (index) => {
329
+ if (index >= 0 && index < totalSteps) {
330
+ onStepChange(index);
331
+ }
332
+ },
333
+ [totalSteps, onStepChange]
334
+ );
335
+ return {
336
+ goNext,
337
+ goBack,
338
+ goToStep,
339
+ isFirstStep,
340
+ isLastStep,
341
+ currentStep,
342
+ totalSteps
343
+ };
344
+ }
345
+
346
+ export { FormWizard, useFormWizardNavigation };
@@ -0,0 +1,140 @@
1
+ import { cx } from './chunk-T5R6HUDG.mjs';
2
+ import { Button } from './chunk-VBD4Q5LL.mjs';
3
+ import { Input } from './chunk-STEP6C4C.mjs';
4
+ import { Check, Mail } from 'lucide-react';
5
+ import { useState, useEffect } from 'react';
6
+ import { toast } from 'sonner';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ var SHAKE_STYLE_ID = "newsletter-shake-keyframes";
10
+ function injectShakeKeyframes() {
11
+ if (typeof document === "undefined") return;
12
+ if (document.getElementById(SHAKE_STYLE_ID)) return;
13
+ const style = document.createElement("style");
14
+ style.id = SHAKE_STYLE_ID;
15
+ style.textContent = `
16
+ @keyframes shake {
17
+ 0%, 100% { transform: translateX(0); }
18
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
19
+ 20%, 40%, 60%, 80% { transform: translateX(4px); }
20
+ }
21
+ `;
22
+ document.head.appendChild(style);
23
+ }
24
+ function NewsletterSignup({
25
+ className,
26
+ endpoint = "/api/newsletter/subscribe",
27
+ source,
28
+ placeholder = "Enter your email",
29
+ inputLabel = "Email address",
30
+ buttonLabel = "Subscribe",
31
+ submittingLabel = "Subscribing...",
32
+ successTitle = "You're subscribed!",
33
+ successDescription = "Check your email to confirm.",
34
+ errorMessage = "Something went wrong. Please try again.",
35
+ inputClassName,
36
+ buttonClassName,
37
+ successClassName
38
+ }) {
39
+ const [email, setEmail] = useState("");
40
+ const [isSubmitting, setIsSubmitting] = useState(false);
41
+ const [submitted, setSubmitted] = useState(false);
42
+ const [hasError, setHasError] = useState(false);
43
+ useEffect(() => {
44
+ injectShakeKeyframes();
45
+ }, []);
46
+ const handleSubmit = async (e) => {
47
+ e.preventDefault();
48
+ if (!email) return;
49
+ setIsSubmitting(true);
50
+ setHasError(false);
51
+ try {
52
+ const response = await fetch(endpoint, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify({ email, source })
56
+ });
57
+ if (response.ok) {
58
+ setSubmitted(true);
59
+ setEmail("");
60
+ } else {
61
+ const data = await response.json();
62
+ toast.error(data.error || errorMessage);
63
+ setHasError(true);
64
+ setTimeout(() => setHasError(false), 500);
65
+ }
66
+ } catch {
67
+ toast.error(
68
+ "Unable to connect. Please check your connection and try again."
69
+ );
70
+ setHasError(true);
71
+ setTimeout(() => setHasError(false), 500);
72
+ } finally {
73
+ setIsSubmitting(false);
74
+ }
75
+ };
76
+ if (submitted) {
77
+ return /* @__PURE__ */ jsxs(
78
+ "div",
79
+ {
80
+ className: cx(
81
+ "flex items-center justify-center gap-3 rounded-box border border-border px-4 py-3",
82
+ className,
83
+ successClassName
84
+ ),
85
+ children: [
86
+ /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground animate-in zoom-in duration-300", children: /* @__PURE__ */ jsx(
87
+ Check,
88
+ {
89
+ className: "h-4 w-4 animate-in fade-in duration-500 delay-150",
90
+ strokeWidth: 3
91
+ }
92
+ ) }),
93
+ /* @__PURE__ */ jsxs("div", { className: "animate-in slide-in-from-left-2 fade-in duration-300 delay-100 text-left", children: [
94
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: successTitle }),
95
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: successDescription })
96
+ ] })
97
+ ]
98
+ }
99
+ );
100
+ }
101
+ return /* @__PURE__ */ jsxs(
102
+ "form",
103
+ {
104
+ onSubmit: handleSubmit,
105
+ className: cx("flex", hasError && "animate-shake", className),
106
+ style: hasError ? { animation: "shake 0.5s ease-in-out" } : void 0,
107
+ children: [
108
+ /* @__PURE__ */ jsx(
109
+ Input,
110
+ {
111
+ "aria-label": inputLabel,
112
+ type: "email",
113
+ placeholder,
114
+ value: email,
115
+ onChange: (e) => setEmail(e.target.value),
116
+ required: true,
117
+ className: cx(
118
+ "flex-1 rounded-r-none border-r-0 focus-visible:z-10",
119
+ inputClassName
120
+ )
121
+ }
122
+ ),
123
+ /* @__PURE__ */ jsxs(
124
+ Button,
125
+ {
126
+ type: "submit",
127
+ disabled: isSubmitting || !email,
128
+ className: cx("rounded-l-none", buttonClassName),
129
+ children: [
130
+ /* @__PURE__ */ jsx(Mail, { className: "mr-2 h-4 w-4" }),
131
+ isSubmitting ? submittingLabel : buttonLabel
132
+ ]
133
+ }
134
+ )
135
+ ]
136
+ }
137
+ );
138
+ }
139
+
140
+ export { NewsletterSignup };
@@ -0,0 +1,26 @@
1
+ import { cn } from './chunk-IMKLN273.mjs';
2
+ import * as SeparatorPrimitive from '@radix-ui/react-separator';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ function Separator({
6
+ className,
7
+ orientation = "horizontal",
8
+ decorative = true,
9
+ ...props
10
+ }) {
11
+ return /* @__PURE__ */ jsx(
12
+ SeparatorPrimitive.Root,
13
+ {
14
+ "data-slot": "separator",
15
+ decorative,
16
+ orientation,
17
+ className: cn(
18
+ "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
19
+ className
20
+ ),
21
+ ...props
22
+ }
23
+ );
24
+ }
25
+
26
+ export { Separator };