@opensite/ui 2.5.0 → 2.5.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.
@@ -1,601 +1,111 @@
1
1
  "use client";
2
- import * as React from 'react';
3
- import React__default, { useMemo } from 'react';
2
+ import * as React2 from 'react';
3
+ import React2__default, { useMemo } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
7
  import { cva } from 'class-variance-authority';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { Img } from '@page-speed/img';
9
8
  import { Icon } from '@page-speed/icon';
10
9
  import { usePlatformFromUrl } from '@opensite/hooks/usePlatformFromUrl';
11
- import { Form } from '@page-speed/forms';
12
- import { useFileUpload, useContactForm, DynamicFormField } from '@page-speed/forms/integration';
10
+ import { FormEngine } from '@page-speed/forms/integration';
11
+ import { Img } from '@page-speed/img';
13
12
 
14
13
  // components/blocks/footers/footer-social-newsletter.tsx
15
14
  function cn(...inputs) {
16
15
  return twMerge(clsx(inputs));
17
16
  }
18
- function normalizePhoneNumber(input) {
19
- const trimmed = input.trim();
20
- if (trimmed.toLowerCase().startsWith("tel:")) {
21
- return trimmed;
22
- }
23
- const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
24
- if (match) {
25
- const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
26
- const extension = match[3];
27
- const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
28
- const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
29
- return `tel:${withExtension}`;
30
- }
31
- const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
32
- return `tel:${cleaned}`;
33
- }
34
- function normalizeEmail(input) {
35
- const trimmed = input.trim();
36
- if (trimmed.toLowerCase().startsWith("mailto:")) {
37
- return trimmed;
38
- }
39
- return `mailto:${trimmed}`;
40
- }
41
- function isEmail(input) {
42
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
43
- return emailRegex.test(input.trim());
44
- }
45
- function isPhoneNumber(input) {
46
- const trimmed = input.trim();
47
- if (trimmed.toLowerCase().startsWith("tel:")) {
48
- return true;
49
- }
50
- const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
51
- return phoneRegex.test(trimmed);
52
- }
53
- function isInternalUrl(href) {
54
- if (typeof window === "undefined") {
55
- return href.startsWith("/") && !href.startsWith("//");
56
- }
57
- const trimmed = href.trim();
58
- if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
59
- return true;
60
- }
61
- try {
62
- const url = new URL(trimmed, window.location.href);
63
- const currentOrigin = window.location.origin;
64
- const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
65
- return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
66
- } catch {
67
- return false;
68
- }
69
- }
70
- function toRelativePath(href) {
71
- if (typeof window === "undefined") {
72
- return href;
73
- }
74
- const trimmed = href.trim();
75
- if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
76
- return trimmed;
77
- }
78
- try {
79
- const url = new URL(trimmed, window.location.href);
80
- const currentOrigin = window.location.origin;
81
- const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
82
- if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
83
- return url.pathname + url.search + url.hash;
84
- }
85
- } catch {
17
+ function FooterCopyright({
18
+ copyright,
19
+ className
20
+ }) {
21
+ const currentYear = useMemo(() => (/* @__PURE__ */ new Date()).getFullYear(), []);
22
+ if (!copyright) {
23
+ return null;
86
24
  }
87
- return trimmed;
25
+ return /* @__PURE__ */ jsxs("p", { className: cn(className), children: [
26
+ "\xA9 ",
27
+ currentYear,
28
+ " ",
29
+ copyright,
30
+ " All Rights Reserved."
31
+ ] });
88
32
  }
89
- function useNavigation({
90
- href,
91
- onClick
92
- } = {}) {
93
- const linkType = React.useMemo(() => {
94
- if (!href || href.trim() === "") {
95
- return onClick ? "none" : "none";
96
- }
97
- const trimmed = href.trim();
98
- if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
99
- return "mailto";
100
- }
101
- if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
102
- return "tel";
103
- }
104
- if (isInternalUrl(trimmed)) {
105
- return "internal";
106
- }
107
- try {
108
- new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
109
- return "external";
110
- } catch {
111
- return "internal";
112
- }
113
- }, [href, onClick]);
114
- const normalizedHref = React.useMemo(() => {
115
- if (!href || href.trim() === "") {
116
- return void 0;
117
- }
118
- const trimmed = href.trim();
119
- switch (linkType) {
120
- case "tel":
121
- return normalizePhoneNumber(trimmed);
122
- case "mailto":
123
- return normalizeEmail(trimmed);
124
- case "internal":
125
- return toRelativePath(trimmed);
126
- case "external":
127
- return trimmed;
128
- default:
129
- return trimmed;
130
- }
131
- }, [href, linkType]);
132
- const target = React.useMemo(() => {
133
- switch (linkType) {
134
- case "external":
135
- return "_blank";
136
- case "internal":
137
- return "_self";
138
- case "mailto":
139
- case "tel":
140
- return void 0;
141
- default:
142
- return void 0;
143
- }
144
- }, [linkType]);
145
- const rel = React.useMemo(() => {
146
- if (linkType === "external") {
147
- return "noopener noreferrer";
148
- }
149
- return void 0;
150
- }, [linkType]);
151
- const isExternal = linkType === "external";
152
- const isInternal = linkType === "internal";
153
- const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
154
- const handleClick = React.useCallback(
155
- (event) => {
156
- if (onClick) {
157
- try {
158
- onClick(event);
159
- } catch (error) {
160
- console.error("Error in user onClick handler:", error);
161
- }
162
- }
163
- if (event.defaultPrevented) {
164
- return;
165
- }
166
- if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
167
- !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
168
- if (typeof window !== "undefined") {
169
- const handler = window.__opensiteNavigationHandler;
170
- if (typeof handler === "function") {
171
- try {
172
- const handled = handler(normalizedHref, event.nativeEvent || event);
173
- if (handled !== false) {
174
- event.preventDefault();
175
- }
176
- } catch (error) {
177
- console.error("Error in navigation handler:", error);
178
- }
179
- }
180
- }
33
+ var brandAttributionOptions = [
34
+ {
35
+ brand: "open_site_ai",
36
+ options: [
37
+ {
38
+ prefix: "Powered by ",
39
+ anchorText: "AI Website Builder",
40
+ href: "https://opensite.ai",
41
+ suffix: ""
42
+ },
43
+ {
44
+ prefix: "",
45
+ anchorText: "AI-Powered Web Design",
46
+ href: "https://opensite.ai",
47
+ suffix: " by OpenSite"
48
+ },
49
+ {
50
+ prefix: "Built with ",
51
+ anchorText: "OpenSite AI",
52
+ href: "https://opensite.ai"
53
+ },
54
+ {
55
+ prefix: "Website by ",
56
+ anchorText: "Intelligent Site Builder",
57
+ href: "https://opensite.ai"
58
+ },
59
+ {
60
+ prefix: "Designed using ",
61
+ anchorText: "AI Website Platform",
62
+ href: "https://opensite.ai"
63
+ },
64
+ {
65
+ prefix: "",
66
+ anchorText: "Automated Website Design",
67
+ href: "https://opensite.ai",
68
+ suffix: " \u2013 OpenSite"
69
+ },
70
+ {
71
+ prefix: "Created with ",
72
+ anchorText: "AI Digital Presence Solutions",
73
+ href: "https://opensite.ai"
74
+ },
75
+ {
76
+ prefix: "",
77
+ anchorText: "Smart Website Builder",
78
+ href: "https://opensite.ai",
79
+ suffix: " by OpenSite AI"
80
+ },
81
+ {
82
+ prefix: "Powered by ",
83
+ anchorText: "Industry-Specific AI Web Platform",
84
+ href: "https://opensite.ai"
85
+ },
86
+ {
87
+ prefix: "",
88
+ anchorText: "AI Site Generator",
89
+ href: "https://opensite.ai",
90
+ suffix: " \u2013 OpenSite"
181
91
  }
182
- },
183
- [onClick, shouldUseRouter, normalizedHref]
184
- );
185
- return {
186
- linkType,
187
- normalizedHref,
188
- target,
189
- rel,
190
- isExternal,
191
- isInternal,
192
- shouldUseRouter,
193
- handleClick
194
- };
195
- }
196
- var baseStyles = [
197
- // Layout
198
- "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
199
- // Typography - using CSS variables with sensible defaults
200
- "font-[var(--button-font-family,inherit)]",
201
- "font-[var(--button-font-weight,500)]",
202
- "tracking-[var(--button-letter-spacing,0)]",
203
- "leading-[var(--button-line-height,1.25)]",
204
- "[text-transform:var(--button-text-transform,none)]",
205
- "text-sm",
206
- // Border radius
207
- "rounded-[var(--button-radius,var(--radius,0.375rem))]",
208
- // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
209
- "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
210
- // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
211
- "[box-shadow:var(--button-shadow,none)]",
212
- "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
213
- // Disabled state
214
- "disabled:pointer-events-none disabled:opacity-50",
215
- // SVG handling
216
- "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
217
- // Focus styles
218
- "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
219
- // Invalid state
220
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
221
- ].join(" ");
222
- var buttonVariants = cva(baseStyles, {
223
- variants: {
224
- variant: {
225
- // Default (Primary) variant - full customization
226
- default: [
227
- "bg-[var(--button-default-bg,hsl(var(--primary)))]",
228
- "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
229
- "border-[length:var(--button-default-border-width,0px)]",
230
- "border-[color:var(--button-default-border,transparent)]",
231
- "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
232
- "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
233
- "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
234
- "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
235
- "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
236
- ].join(" "),
237
- // Destructive variant - full customization
238
- destructive: [
239
- "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
240
- "text-[var(--button-destructive-fg,white)]",
241
- "border-[length:var(--button-destructive-border-width,0px)]",
242
- "border-[color:var(--button-destructive-border,transparent)]",
243
- "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
244
- "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
245
- "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
246
- "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
247
- "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
248
- "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
249
- "dark:bg-destructive/60"
250
- ].join(" "),
251
- // Outline variant - full customization with proper border handling
252
- outline: [
253
- "bg-[var(--button-outline-bg,hsl(var(--background)))]",
254
- "text-[var(--button-outline-fg,inherit)]",
255
- "border-[length:var(--button-outline-border-width,1px)]",
256
- "border-[color:var(--button-outline-border,hsl(var(--border)))]",
257
- "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
258
- "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
259
- "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
260
- "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
261
- "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
262
- "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
263
- ].join(" "),
264
- // Secondary variant - full customization
265
- secondary: [
266
- "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
267
- "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
268
- "border-[length:var(--button-secondary-border-width,0px)]",
269
- "border-[color:var(--button-secondary-border,transparent)]",
270
- "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
271
- "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
272
- "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
273
- "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
274
- "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
275
- ].join(" "),
276
- // Ghost variant - full customization
277
- ghost: [
278
- "bg-[var(--button-ghost-bg,transparent)]",
279
- "text-[var(--button-ghost-fg,inherit)]",
280
- "border-[length:var(--button-ghost-border-width,0px)]",
281
- "border-[color:var(--button-ghost-border,transparent)]",
282
- "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
283
- "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
284
- "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
285
- "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
286
- "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
287
- "dark:hover:bg-accent/50"
288
- ].join(" "),
289
- // Link variant - full customization
290
- link: [
291
- "bg-[var(--button-link-bg,transparent)]",
292
- "text-[var(--button-link-fg,hsl(var(--primary)))]",
293
- "border-[length:var(--button-link-border-width,0px)]",
294
- "border-[color:var(--button-link-border,transparent)]",
295
- "[box-shadow:var(--button-link-shadow,none)]",
296
- "hover:bg-[var(--button-link-hover-bg,transparent)]",
297
- "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
298
- "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
299
- "underline-offset-4 hover:underline"
300
- ].join(" ")
301
- },
302
- size: {
303
- default: [
304
- "h-[var(--button-height-md,2.25rem)]",
305
- "px-[var(--button-padding-x-md,1rem)]",
306
- "py-[var(--button-padding-y-md,0.5rem)]",
307
- "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
308
- ].join(" "),
309
- sm: [
310
- "h-[var(--button-height-sm,2rem)]",
311
- "px-[var(--button-padding-x-sm,0.75rem)]",
312
- "py-[var(--button-padding-y-sm,0.25rem)]",
313
- "gap-1.5",
314
- "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
315
- ].join(" "),
316
- md: [
317
- "h-[var(--button-height-md,2.25rem)]",
318
- "px-[var(--button-padding-x-md,1rem)]",
319
- "py-[var(--button-padding-y-md,0.5rem)]",
320
- "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
321
- ].join(" "),
322
- lg: [
323
- "h-[var(--button-height-lg,2.5rem)]",
324
- "px-[var(--button-padding-x-lg,1.5rem)]",
325
- "py-[var(--button-padding-y-lg,0.5rem)]",
326
- "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
327
- ].join(" "),
328
- icon: "size-[var(--button-height-md,2.25rem)]",
329
- "icon-sm": "size-[var(--button-height-sm,2rem)]",
330
- "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
331
- }
92
+ ]
332
93
  },
333
- defaultVariants: {
334
- variant: "default",
335
- size: "default"
336
- }
337
- });
338
- var Pressable = React.forwardRef(
339
- ({
340
- children,
341
- className,
342
- href,
343
- onClick,
344
- variant,
345
- size,
346
- asButton = false,
347
- fallbackComponentType = "span",
348
- componentType,
349
- "aria-label": ariaLabel,
350
- "aria-describedby": ariaDescribedby,
351
- id,
352
- ...props
353
- }, ref) => {
354
- const navigation = useNavigation({ href, onClick });
355
- const {
356
- normalizedHref,
357
- target,
358
- rel,
359
- linkType,
360
- isInternal,
361
- handleClick
362
- } = navigation;
363
- const shouldRenderLink = normalizedHref && linkType !== "none";
364
- const shouldRenderButton = !shouldRenderLink && onClick;
365
- const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
366
- const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
367
- const shouldApplyButtonStyles = asButton || variant || size;
368
- const combinedClassName = cn(
369
- shouldApplyButtonStyles && buttonVariants({ variant, size }),
370
- className
371
- );
372
- const dataProps = Object.fromEntries(
373
- Object.entries(props).filter(([key]) => key.startsWith("data-"))
374
- );
375
- const buttonDataAttributes = shouldApplyButtonStyles ? {
376
- "data-slot": "button",
377
- "data-variant": variant ?? "default",
378
- "data-size": size ?? "default"
379
- } : {};
380
- const commonProps = {
381
- className: combinedClassName,
382
- onClick: handleClick,
383
- "aria-label": ariaLabel,
384
- "aria-describedby": ariaDescribedby,
385
- id,
386
- ...dataProps,
387
- ...buttonDataAttributes
388
- };
389
- if (finalComponentType === "a" && shouldRenderLink) {
390
- return /* @__PURE__ */ jsx(
391
- "a",
392
- {
393
- ref,
394
- href: normalizedHref,
395
- target,
396
- rel,
397
- ...commonProps,
398
- ...props,
399
- children
400
- }
401
- );
402
- }
403
- if (finalComponentType === "button") {
404
- return /* @__PURE__ */ jsx(
405
- "button",
406
- {
407
- ref,
408
- type: props.type || "button",
409
- ...commonProps,
410
- ...props,
411
- children
412
- }
413
- );
414
- }
415
- if (finalComponentType === "div") {
416
- return /* @__PURE__ */ jsx(
417
- "div",
418
- {
419
- ref,
420
- ...commonProps,
421
- children
422
- }
423
- );
424
- }
425
- return /* @__PURE__ */ jsx(
426
- "span",
94
+ {
95
+ brand: "dashtrack",
96
+ options: [
427
97
  {
428
- ref,
429
- ...commonProps,
430
- children
431
- }
432
- );
433
- }
434
- );
435
- Pressable.displayName = "Pressable";
436
- function isThemedLogo(logo) {
437
- return "light" in logo || "dark" in logo;
438
- }
439
- var FooterLogo = ({
440
- logo,
441
- logoSlot,
442
- logoClassName,
443
- logoImageClassName,
444
- logoTitleClassName,
445
- optixFlowConfig
446
- }) => {
447
- if (logoSlot) return /* @__PURE__ */ jsx(Fragment, { children: logoSlot });
448
- if (!logo) return null;
449
- const hasThemedSources = isThemedLogo(logo) && (logo.light || logo.dark);
450
- const hasStandardSource = !isThemedLogo(logo) && logo.src;
451
- const logoContent = hasThemedSources ? (
452
- // Themed logo with light/dark mode sources
453
- /* @__PURE__ */ jsxs(Fragment, { children: [
454
- logo.light && /* @__PURE__ */ jsx(
455
- Img,
456
- {
457
- src: logo.light,
458
- alt: logo.alt || "Logo",
459
- className: cn(
460
- "h-8 w-auto object-contain dark:hidden md:h-10",
461
- logoImageClassName
462
- ),
463
- optixFlowConfig
464
- }
465
- ),
466
- logo.dark && /* @__PURE__ */ jsx(
467
- Img,
468
- {
469
- src: logo.dark,
470
- alt: logo.alt || "Logo",
471
- className: cn(
472
- "hidden h-8 w-auto object-contain dark:block md:h-10",
473
- logoImageClassName
474
- ),
475
- optixFlowConfig
476
- }
477
- )
478
- ] })
479
- ) : hasStandardSource ? (
480
- // Standard single logo image
481
- /* @__PURE__ */ jsx(
482
- Img,
98
+ prefix: "",
99
+ anchorText: "Restaurant Website Builder",
100
+ href: "https://dashtrack.com",
101
+ suffix: " by DashTrack"
102
+ },
483
103
  {
484
- src: logo.src,
485
- alt: logo.alt || "Logo",
486
- className: cn("h-8 w-auto object-contain md:h-10", logoImageClassName),
487
- optixFlowConfig
488
- }
489
- )
490
- ) : logo.title ? (
491
- // Text-based logo fallback
492
- /* @__PURE__ */ jsx("span", { className: cn("text-lg font-semibold md:text-xl", logoTitleClassName), children: logo.title })
493
- ) : null;
494
- if (!logoContent) return null;
495
- if (logo.url) {
496
- return /* @__PURE__ */ jsx(
497
- Pressable,
498
- {
499
- href: logo.url,
500
- className: cn("inline-flex items-center", logoClassName),
501
- children: logoContent
502
- }
503
- );
504
- }
505
- return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center", logoClassName), children: logoContent });
506
- };
507
- function FooterCopyright({
508
- copyright,
509
- className
510
- }) {
511
- const currentYear = useMemo(() => (/* @__PURE__ */ new Date()).getFullYear(), []);
512
- if (!copyright) {
513
- return null;
514
- }
515
- return /* @__PURE__ */ jsxs("p", { className: cn(className), children: [
516
- "\xA9 ",
517
- currentYear,
518
- " ",
519
- copyright,
520
- " All Rights Reserved."
521
- ] });
522
- }
523
- var brandAttributionOptions = [
524
- {
525
- brand: "open_site_ai",
526
- options: [
527
- {
528
- prefix: "Powered by ",
529
- anchorText: "AI Website Builder",
530
- href: "https://opensite.ai",
531
- suffix: ""
532
- },
533
- {
534
- prefix: "",
535
- anchorText: "AI-Powered Web Design",
536
- href: "https://opensite.ai",
537
- suffix: " by OpenSite"
538
- },
539
- {
540
- prefix: "Built with ",
541
- anchorText: "OpenSite AI",
542
- href: "https://opensite.ai"
543
- },
544
- {
545
- prefix: "Website by ",
546
- anchorText: "Intelligent Site Builder",
547
- href: "https://opensite.ai"
548
- },
549
- {
550
- prefix: "Designed using ",
551
- anchorText: "AI Website Platform",
552
- href: "https://opensite.ai"
553
- },
554
- {
555
- prefix: "",
556
- anchorText: "Automated Website Design",
557
- href: "https://opensite.ai",
558
- suffix: " \u2013 OpenSite"
559
- },
560
- {
561
- prefix: "Created with ",
562
- anchorText: "AI Digital Presence Solutions",
563
- href: "https://opensite.ai"
564
- },
565
- {
566
- prefix: "",
567
- anchorText: "Smart Website Builder",
568
- href: "https://opensite.ai",
569
- suffix: " by OpenSite AI"
570
- },
571
- {
572
- prefix: "Powered by ",
573
- anchorText: "Industry-Specific AI Web Platform",
574
- href: "https://opensite.ai"
575
- },
576
- {
577
- prefix: "",
578
- anchorText: "AI Site Generator",
579
- href: "https://opensite.ai",
580
- suffix: " \u2013 OpenSite"
581
- }
582
- ]
583
- },
584
- {
585
- brand: "dashtrack",
586
- options: [
587
- {
588
- prefix: "",
589
- anchorText: "Restaurant Website Builder",
590
- href: "https://dashtrack.com",
591
- suffix: " by DashTrack"
592
- },
593
- {
594
- prefix: "Powered by ",
595
- anchorText: "DashTrack",
596
- href: "https://dashtrack.com",
597
- suffix: " Restaurant Platform"
598
- },
104
+ prefix: "Powered by ",
105
+ anchorText: "DashTrack",
106
+ href: "https://dashtrack.com",
107
+ suffix: " Restaurant Platform"
108
+ },
599
109
  {
600
110
  prefix: "",
601
111
  anchorText: "Bar & Restaurant Marketing Platform",
@@ -981,64 +491,482 @@ var brandAttributionMap = brandAttributionOptions.reduce(
981
491
  acc[entry.brand] = entry.options;
982
492
  return acc;
983
493
  },
984
- {}
985
- );
986
- function buildTrackedHref(baseHref) {
987
- if (typeof window === "undefined") {
988
- return baseHref;
494
+ {}
495
+ );
496
+ function buildTrackedHref(baseHref) {
497
+ if (typeof window === "undefined") {
498
+ return baseHref;
499
+ }
500
+ try {
501
+ const url = new URL(baseHref);
502
+ const source = window.location.host;
503
+ const sourceRef = window.location.href;
504
+ url.searchParams.set("source", source);
505
+ url.searchParams.set("sourceRef", sourceRef);
506
+ return url.toString();
507
+ } catch {
508
+ try {
509
+ const url = new URL(baseHref, window.location.origin);
510
+ const source = window.location.host;
511
+ const sourceRef = window.location.href;
512
+ url.searchParams.set("source", source);
513
+ url.searchParams.set("sourceRef", sourceRef);
514
+ return url.toString();
515
+ } catch {
516
+ return baseHref;
517
+ }
518
+ }
519
+ }
520
+ var BrandAttribution = ({
521
+ variant = "span",
522
+ containerClassName,
523
+ linkClassName,
524
+ internalBrandSlug,
525
+ optionIndex
526
+ }) => {
527
+ const options = brandAttributionMap[internalBrandSlug];
528
+ if (!options || optionIndex < 0 || optionIndex >= options.length) {
529
+ return null;
530
+ }
531
+ const { prefix = "", anchorText, href, suffix = "" } = options[optionIndex];
532
+ const ContainerEl = variant;
533
+ const [trackedHref, setTrackedHref] = React2.useState(href);
534
+ React2.useEffect(() => {
535
+ setTrackedHref(buildTrackedHref(href));
536
+ }, [href]);
537
+ return /* @__PURE__ */ jsxs(ContainerEl, { className: containerClassName, children: [
538
+ prefix,
539
+ /* @__PURE__ */ jsx(
540
+ "a",
541
+ {
542
+ href: trackedHref,
543
+ target: "_blank",
544
+ rel: "noreferrer noopener",
545
+ className: linkClassName,
546
+ children: anchorText
547
+ }
548
+ ),
549
+ suffix
550
+ ] });
551
+ };
552
+ function normalizePhoneNumber(input) {
553
+ const trimmed = input.trim();
554
+ if (trimmed.toLowerCase().startsWith("tel:")) {
555
+ return trimmed;
556
+ }
557
+ const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
558
+ if (match) {
559
+ const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
560
+ const extension = match[3];
561
+ const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
562
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
563
+ return `tel:${withExtension}`;
564
+ }
565
+ const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
566
+ return `tel:${cleaned}`;
567
+ }
568
+ function normalizeEmail(input) {
569
+ const trimmed = input.trim();
570
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
571
+ return trimmed;
572
+ }
573
+ return `mailto:${trimmed}`;
574
+ }
575
+ function isEmail(input) {
576
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
577
+ return emailRegex.test(input.trim());
578
+ }
579
+ function isPhoneNumber(input) {
580
+ const trimmed = input.trim();
581
+ if (trimmed.toLowerCase().startsWith("tel:")) {
582
+ return true;
583
+ }
584
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
585
+ return phoneRegex.test(trimmed);
586
+ }
587
+ function isInternalUrl(href) {
588
+ if (typeof window === "undefined") {
589
+ return href.startsWith("/") && !href.startsWith("//");
590
+ }
591
+ const trimmed = href.trim();
592
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
593
+ return true;
594
+ }
595
+ try {
596
+ const url = new URL(trimmed, window.location.href);
597
+ const currentOrigin = window.location.origin;
598
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
599
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
600
+ } catch {
601
+ return false;
602
+ }
603
+ }
604
+ function toRelativePath(href) {
605
+ if (typeof window === "undefined") {
606
+ return href;
607
+ }
608
+ const trimmed = href.trim();
609
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
610
+ return trimmed;
611
+ }
612
+ try {
613
+ const url = new URL(trimmed, window.location.href);
614
+ const currentOrigin = window.location.origin;
615
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
616
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
617
+ return url.pathname + url.search + url.hash;
618
+ }
619
+ } catch {
620
+ }
621
+ return trimmed;
622
+ }
623
+ function useNavigation({
624
+ href,
625
+ onClick
626
+ } = {}) {
627
+ const linkType = React2.useMemo(() => {
628
+ if (!href || href.trim() === "") {
629
+ return onClick ? "none" : "none";
630
+ }
631
+ const trimmed = href.trim();
632
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
633
+ return "mailto";
634
+ }
635
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
636
+ return "tel";
637
+ }
638
+ if (isInternalUrl(trimmed)) {
639
+ return "internal";
640
+ }
641
+ try {
642
+ new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
643
+ return "external";
644
+ } catch {
645
+ return "internal";
646
+ }
647
+ }, [href, onClick]);
648
+ const normalizedHref = React2.useMemo(() => {
649
+ if (!href || href.trim() === "") {
650
+ return void 0;
651
+ }
652
+ const trimmed = href.trim();
653
+ switch (linkType) {
654
+ case "tel":
655
+ return normalizePhoneNumber(trimmed);
656
+ case "mailto":
657
+ return normalizeEmail(trimmed);
658
+ case "internal":
659
+ return toRelativePath(trimmed);
660
+ case "external":
661
+ return trimmed;
662
+ default:
663
+ return trimmed;
664
+ }
665
+ }, [href, linkType]);
666
+ const target = React2.useMemo(() => {
667
+ switch (linkType) {
668
+ case "external":
669
+ return "_blank";
670
+ case "internal":
671
+ return "_self";
672
+ case "mailto":
673
+ case "tel":
674
+ return void 0;
675
+ default:
676
+ return void 0;
677
+ }
678
+ }, [linkType]);
679
+ const rel = React2.useMemo(() => {
680
+ if (linkType === "external") {
681
+ return "noopener noreferrer";
682
+ }
683
+ return void 0;
684
+ }, [linkType]);
685
+ const isExternal = linkType === "external";
686
+ const isInternal = linkType === "internal";
687
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
688
+ const handleClick = React2.useCallback(
689
+ (event) => {
690
+ if (onClick) {
691
+ try {
692
+ onClick(event);
693
+ } catch (error) {
694
+ console.error("Error in user onClick handler:", error);
695
+ }
696
+ }
697
+ if (event.defaultPrevented) {
698
+ return;
699
+ }
700
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
701
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
702
+ if (typeof window !== "undefined") {
703
+ const handler = window.__opensiteNavigationHandler;
704
+ if (typeof handler === "function") {
705
+ try {
706
+ const handled = handler(normalizedHref, event.nativeEvent || event);
707
+ if (handled !== false) {
708
+ event.preventDefault();
709
+ }
710
+ } catch (error) {
711
+ console.error("Error in navigation handler:", error);
712
+ }
713
+ }
714
+ }
715
+ }
716
+ },
717
+ [onClick, shouldUseRouter, normalizedHref]
718
+ );
719
+ return {
720
+ linkType,
721
+ normalizedHref,
722
+ target,
723
+ rel,
724
+ isExternal,
725
+ isInternal,
726
+ shouldUseRouter,
727
+ handleClick
728
+ };
729
+ }
730
+ var baseStyles = [
731
+ // Layout
732
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
733
+ // Typography - using CSS variables with sensible defaults
734
+ "font-[var(--button-font-family,inherit)]",
735
+ "font-[var(--button-font-weight,500)]",
736
+ "tracking-[var(--button-letter-spacing,0)]",
737
+ "leading-[var(--button-line-height,1.25)]",
738
+ "[text-transform:var(--button-text-transform,none)]",
739
+ "text-sm",
740
+ // Border radius
741
+ "rounded-[var(--button-radius,var(--radius,0.375rem))]",
742
+ // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
743
+ "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
744
+ // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
745
+ "[box-shadow:var(--button-shadow,none)]",
746
+ "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
747
+ // Disabled state
748
+ "disabled:pointer-events-none disabled:opacity-50",
749
+ // SVG handling
750
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
751
+ // Focus styles
752
+ "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
753
+ // Invalid state
754
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
755
+ ].join(" ");
756
+ var buttonVariants = cva(baseStyles, {
757
+ variants: {
758
+ variant: {
759
+ // Default (Primary) variant - full customization
760
+ default: [
761
+ "bg-[var(--button-default-bg,hsl(var(--primary)))]",
762
+ "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
763
+ "border-[length:var(--button-default-border-width,0px)]",
764
+ "border-[color:var(--button-default-border,transparent)]",
765
+ "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
766
+ "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
767
+ "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
768
+ "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
769
+ "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
770
+ ].join(" "),
771
+ // Destructive variant - full customization
772
+ destructive: [
773
+ "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
774
+ "text-[var(--button-destructive-fg,white)]",
775
+ "border-[length:var(--button-destructive-border-width,0px)]",
776
+ "border-[color:var(--button-destructive-border,transparent)]",
777
+ "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
778
+ "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
779
+ "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
780
+ "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
781
+ "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
782
+ "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
783
+ "dark:bg-destructive/60"
784
+ ].join(" "),
785
+ // Outline variant - full customization with proper border handling
786
+ outline: [
787
+ "bg-[var(--button-outline-bg,hsl(var(--background)))]",
788
+ "text-[var(--button-outline-fg,inherit)]",
789
+ "border-[length:var(--button-outline-border-width,1px)]",
790
+ "border-[color:var(--button-outline-border,hsl(var(--border)))]",
791
+ "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
792
+ "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
793
+ "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
794
+ "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
795
+ "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
796
+ "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
797
+ ].join(" "),
798
+ // Secondary variant - full customization
799
+ secondary: [
800
+ "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
801
+ "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
802
+ "border-[length:var(--button-secondary-border-width,0px)]",
803
+ "border-[color:var(--button-secondary-border,transparent)]",
804
+ "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
805
+ "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
806
+ "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
807
+ "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
808
+ "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
809
+ ].join(" "),
810
+ // Ghost variant - full customization
811
+ ghost: [
812
+ "bg-[var(--button-ghost-bg,transparent)]",
813
+ "text-[var(--button-ghost-fg,inherit)]",
814
+ "border-[length:var(--button-ghost-border-width,0px)]",
815
+ "border-[color:var(--button-ghost-border,transparent)]",
816
+ "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
817
+ "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
818
+ "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
819
+ "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
820
+ "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
821
+ "dark:hover:bg-accent/50"
822
+ ].join(" "),
823
+ // Link variant - full customization
824
+ link: [
825
+ "bg-[var(--button-link-bg,transparent)]",
826
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
827
+ "border-[length:var(--button-link-border-width,0px)]",
828
+ "border-[color:var(--button-link-border,transparent)]",
829
+ "[box-shadow:var(--button-link-shadow,none)]",
830
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
831
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
832
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
833
+ "underline-offset-4 hover:underline"
834
+ ].join(" ")
835
+ },
836
+ size: {
837
+ default: [
838
+ "h-[var(--button-height-md,2.25rem)]",
839
+ "px-[var(--button-padding-x-md,1rem)]",
840
+ "py-[var(--button-padding-y-md,0.5rem)]",
841
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
842
+ ].join(" "),
843
+ sm: [
844
+ "h-[var(--button-height-sm,2rem)]",
845
+ "px-[var(--button-padding-x-sm,0.75rem)]",
846
+ "py-[var(--button-padding-y-sm,0.25rem)]",
847
+ "gap-1.5",
848
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
849
+ ].join(" "),
850
+ md: [
851
+ "h-[var(--button-height-md,2.25rem)]",
852
+ "px-[var(--button-padding-x-md,1rem)]",
853
+ "py-[var(--button-padding-y-md,0.5rem)]",
854
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
855
+ ].join(" "),
856
+ lg: [
857
+ "h-[var(--button-height-lg,2.5rem)]",
858
+ "px-[var(--button-padding-x-lg,1.5rem)]",
859
+ "py-[var(--button-padding-y-lg,0.5rem)]",
860
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
861
+ ].join(" "),
862
+ icon: "size-[var(--button-height-md,2.25rem)]",
863
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
864
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
865
+ }
866
+ },
867
+ defaultVariants: {
868
+ variant: "default",
869
+ size: "default"
989
870
  }
990
- try {
991
- const url = new URL(baseHref);
992
- const source = window.location.host;
993
- const sourceRef = window.location.href;
994
- url.searchParams.set("source", source);
995
- url.searchParams.set("sourceRef", sourceRef);
996
- return url.toString();
997
- } catch {
998
- try {
999
- const url = new URL(baseHref, window.location.origin);
1000
- const source = window.location.host;
1001
- const sourceRef = window.location.href;
1002
- url.searchParams.set("source", source);
1003
- url.searchParams.set("sourceRef", sourceRef);
1004
- return url.toString();
1005
- } catch {
1006
- return baseHref;
871
+ });
872
+ var Pressable = React2.forwardRef(
873
+ ({
874
+ children,
875
+ className,
876
+ href,
877
+ onClick,
878
+ variant,
879
+ size,
880
+ asButton = false,
881
+ fallbackComponentType = "span",
882
+ componentType,
883
+ "aria-label": ariaLabel,
884
+ "aria-describedby": ariaDescribedby,
885
+ id,
886
+ ...props
887
+ }, ref) => {
888
+ const navigation = useNavigation({ href, onClick });
889
+ const {
890
+ normalizedHref,
891
+ target,
892
+ rel,
893
+ linkType,
894
+ isInternal,
895
+ handleClick
896
+ } = navigation;
897
+ const shouldRenderLink = normalizedHref && linkType !== "none";
898
+ const shouldRenderButton = !shouldRenderLink && onClick;
899
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
900
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
901
+ const shouldApplyButtonStyles = asButton || variant || size;
902
+ const combinedClassName = cn(
903
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
904
+ className
905
+ );
906
+ const dataProps = Object.fromEntries(
907
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
908
+ );
909
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
910
+ "data-slot": "button",
911
+ "data-variant": variant ?? "default",
912
+ "data-size": size ?? "default"
913
+ } : {};
914
+ const commonProps = {
915
+ className: combinedClassName,
916
+ onClick: handleClick,
917
+ "aria-label": ariaLabel,
918
+ "aria-describedby": ariaDescribedby,
919
+ id,
920
+ ...dataProps,
921
+ ...buttonDataAttributes
922
+ };
923
+ if (finalComponentType === "a" && shouldRenderLink) {
924
+ return /* @__PURE__ */ jsx(
925
+ "a",
926
+ {
927
+ ref,
928
+ href: normalizedHref,
929
+ target,
930
+ rel,
931
+ ...commonProps,
932
+ ...props,
933
+ children
934
+ }
935
+ );
1007
936
  }
1008
- }
1009
- }
1010
- var BrandAttribution = ({
1011
- variant = "span",
1012
- containerClassName,
1013
- linkClassName,
1014
- internalBrandSlug,
1015
- optionIndex
1016
- }) => {
1017
- const options = brandAttributionMap[internalBrandSlug];
1018
- if (!options || optionIndex < 0 || optionIndex >= options.length) {
1019
- return null;
1020
- }
1021
- const { prefix = "", anchorText, href, suffix = "" } = options[optionIndex];
1022
- const ContainerEl = variant;
1023
- const [trackedHref, setTrackedHref] = React.useState(href);
1024
- React.useEffect(() => {
1025
- setTrackedHref(buildTrackedHref(href));
1026
- }, [href]);
1027
- return /* @__PURE__ */ jsxs(ContainerEl, { className: containerClassName, children: [
1028
- prefix,
1029
- /* @__PURE__ */ jsx(
1030
- "a",
937
+ if (finalComponentType === "button") {
938
+ return /* @__PURE__ */ jsx(
939
+ "button",
940
+ {
941
+ ref,
942
+ type: props.type || "button",
943
+ ...commonProps,
944
+ ...props,
945
+ children
946
+ }
947
+ );
948
+ }
949
+ if (finalComponentType === "div") {
950
+ return /* @__PURE__ */ jsx(
951
+ "div",
952
+ {
953
+ ref,
954
+ ...commonProps,
955
+ children
956
+ }
957
+ );
958
+ }
959
+ return /* @__PURE__ */ jsx(
960
+ "span",
1031
961
  {
1032
- href: trackedHref,
1033
- target: "_blank",
1034
- rel: "noreferrer noopener",
1035
- className: linkClassName,
1036
- children: anchorText
962
+ ref,
963
+ ...commonProps,
964
+ children
1037
965
  }
1038
- ),
1039
- suffix
1040
- ] });
1041
- };
966
+ );
967
+ }
968
+ );
969
+ Pressable.displayName = "Pressable";
1042
970
  var maxWidthStyles = {
1043
971
  sm: "max-w-screen-sm",
1044
972
  md: "max-w-screen-md",
@@ -1048,7 +976,7 @@ var maxWidthStyles = {
1048
976
  "4xl": "max-w-[1536px]",
1049
977
  full: "max-w-full"
1050
978
  };
1051
- var Container = React__default.forwardRef(
979
+ var Container = React2__default.forwardRef(
1052
980
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
1053
981
  const Component = as;
1054
982
  return /* @__PURE__ */ jsx(
@@ -1354,7 +1282,7 @@ var spacingStyles = {
1354
1282
  };
1355
1283
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl"];
1356
1284
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
1357
- var Section = React__default.forwardRef(
1285
+ var Section = React2__default.forwardRef(
1358
1286
  ({
1359
1287
  id,
1360
1288
  title,
@@ -1452,7 +1380,7 @@ var platformIconMap = {
1452
1380
  dribbble: "cib/dribbble",
1453
1381
  unknown: "icon-park-solid/circular-connection"
1454
1382
  };
1455
- var SocialLinkIcon = React.forwardRef(
1383
+ var SocialLinkIcon = React2.forwardRef(
1456
1384
  ({
1457
1385
  platformName,
1458
1386
  label,
@@ -1465,13 +1393,13 @@ var SocialLinkIcon = React.forwardRef(
1465
1393
  ...pressableProps
1466
1394
  }, ref) => {
1467
1395
  const platform = usePlatformFromUrl(href);
1468
- const smartPlatformName = React.useMemo(() => {
1396
+ const smartPlatformName = React2.useMemo(() => {
1469
1397
  return platform || platformName;
1470
1398
  }, [platform, platformName]);
1471
- const iconName = React.useMemo(() => {
1399
+ const iconName = React2.useMemo(() => {
1472
1400
  return iconNameOverride || platformIconMap[smartPlatformName];
1473
1401
  }, [iconNameOverride, smartPlatformName]);
1474
- const accessibleLabel = React.useMemo(() => {
1402
+ const accessibleLabel = React2.useMemo(() => {
1475
1403
  return label || platformName;
1476
1404
  }, [label, platformName]);
1477
1405
  return /* @__PURE__ */ jsx(
@@ -1500,12 +1428,18 @@ var SocialLinkIcon = React.forwardRef(
1500
1428
  }
1501
1429
  );
1502
1430
  SocialLinkIcon.displayName = "SocialLinkIcon";
1431
+ var DEFAULT_STYLE_RULES = {
1432
+ formContainer: "flex items-stretch w-full",
1433
+ fieldsContainer: "",
1434
+ fieldClassName: "",
1435
+ formClassName: ""
1436
+ };
1503
1437
  var DEFAULT_FORM_FIELDS = [
1504
1438
  {
1505
1439
  name: "email",
1506
1440
  type: "email",
1507
1441
  label: "Email Address",
1508
- placeholder: "Enter your email",
1442
+ placeholder: "Email Address",
1509
1443
  required: true,
1510
1444
  columnSpan: 12
1511
1445
  }
@@ -1518,7 +1452,6 @@ function FooterSocialNewsletter({
1518
1452
  className,
1519
1453
  contentClassName,
1520
1454
  logoWrapperClassName,
1521
- logoClassName,
1522
1455
  gridClassName,
1523
1456
  navSectionClassName,
1524
1457
  navTitleClassName,
@@ -1527,16 +1460,7 @@ function FooterSocialNewsletter({
1527
1460
  socialColumnClassName,
1528
1461
  socialLinksClassName,
1529
1462
  socialLinkClassName,
1530
- formFields = DEFAULT_FORM_FIELDS,
1531
- formConfig,
1532
- onSubmit,
1533
- onSuccess,
1534
- onError,
1535
- successMessage,
1536
- buttonAction,
1537
- helperText,
1538
- formSlot,
1539
- privacyClassName,
1463
+ formEngineSetup,
1540
1464
  bottomClassName,
1541
1465
  copyrightClassName,
1542
1466
  background,
@@ -1546,104 +1470,36 @@ function FooterSocialNewsletter({
1546
1470
  patternOpacity,
1547
1471
  optixFlowConfig
1548
1472
  }) {
1549
- const {
1550
- uploadTokens,
1551
- uploadProgress,
1552
- isUploading,
1553
- uploadFiles,
1554
- removeFile,
1555
- resetUpload
1556
- } = useFileUpload({ onError });
1557
- const { form, submissionError, formMethod, resetSubmissionState } = useContactForm({
1558
- formFields,
1559
- formConfig,
1560
- onSubmit,
1561
- onSuccess: (data) => {
1562
- resetUpload();
1563
- onSuccess?.(data);
1564
- },
1565
- onError,
1566
- uploadTokens
1567
- });
1568
- const renderForm = React.useMemo(() => {
1569
- if (formSlot) return formSlot;
1570
- const defaultButtonAction = {
1571
- label: "Subscribe",
1473
+ const renderForm = React2.useMemo(() => {
1474
+ if (!formEngineSetup) return null;
1475
+ const action = {
1572
1476
  variant: "default",
1573
- className: "h-12"
1477
+ icon: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/arrow-right", size: 16 })
1574
1478
  };
1575
- const action = buttonAction || defaultButtonAction;
1576
- return /* @__PURE__ */ jsxs(
1577
- Form,
1578
- {
1579
- form,
1580
- fields: formFields,
1581
- notificationConfig: {
1582
- submissionError,
1583
- successMessage
1584
- },
1585
- formConfig: {
1586
- endpoint: formConfig?.endpoint,
1587
- method: formMethod,
1588
- submissionConfig: formConfig?.submissionConfig,
1589
- formLayout: "button-group",
1590
- buttonGroupSize: "sm",
1591
- submitLabel: action.label,
1592
- submitVariant: action.variant || "default",
1593
- submitIconComponent: action.icon || /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/send" })
1594
- },
1595
- onNewSubmission: () => {
1596
- resetUpload();
1597
- resetSubmissionState();
1479
+ return /* @__PURE__ */ jsx(
1480
+ FormEngine,
1481
+ {
1482
+ formEngineSetup: {
1483
+ ...formEngineSetup,
1484
+ formLayoutSettings: {
1485
+ ...formEngineSetup.formLayoutSettings,
1486
+ formLayout: "button-group",
1487
+ buttonGroupSetup: {
1488
+ ...formEngineSetup.formLayoutSettings?.buttonGroupSetup,
1489
+ size: "default",
1490
+ submitLabel: action.icon || action.label,
1491
+ submitVariant: action.variant
1492
+ }
1493
+ }
1598
1494
  },
1599
- children: [
1600
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row", children: [
1601
- formFields.map((field) => /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
1602
- DynamicFormField,
1603
- {
1604
- field,
1605
- uploadProgress,
1606
- onFileUpload: uploadFiles,
1607
- onFileRemove: removeFile,
1608
- isUploading
1609
- }
1610
- ) }, field.name)),
1611
- /* @__PURE__ */ jsxs(
1612
- Pressable,
1613
- {
1614
- onClick: form.handleSubmit,
1615
- asButton: true,
1616
- variant: action.variant,
1617
- className: cn("h-12", action.className),
1618
- disabled: form.isSubmitting,
1619
- children: [
1620
- action.label,
1621
- action.iconAfter
1622
- ]
1623
- }
1624
- )
1625
- ] }),
1626
- helperText && (typeof helperText === "string" ? /* @__PURE__ */ jsx("p", { className: cn("text-sm mt-2"), children: helperText }) : helperText)
1627
- ]
1495
+ defaultFields: DEFAULT_FORM_FIELDS,
1496
+ defaultStyleRules: {
1497
+ ...DEFAULT_STYLE_RULES,
1498
+ formContainer: cn(DEFAULT_STYLE_RULES.formContainer)
1499
+ }
1628
1500
  }
1629
1501
  );
1630
- }, [
1631
- formSlot,
1632
- formFields,
1633
- form,
1634
- formConfig,
1635
- formMethod,
1636
- buttonAction,
1637
- uploadProgress,
1638
- uploadFiles,
1639
- removeFile,
1640
- isUploading,
1641
- submissionError,
1642
- successMessage,
1643
- helperText,
1644
- resetUpload,
1645
- resetSubmissionState
1646
- ]);
1502
+ }, [formEngineSetup]);
1647
1503
  return /* @__PURE__ */ jsx(
1648
1504
  Section,
1649
1505
  {
@@ -1651,20 +1507,26 @@ function FooterSocialNewsletter({
1651
1507
  spacing,
1652
1508
  pattern,
1653
1509
  patternOpacity,
1654
- className: cn(className),
1510
+ className,
1655
1511
  containerClassName,
1656
1512
  children: /* @__PURE__ */ jsx("div", { className: cn(contentClassName), children: /* @__PURE__ */ jsxs("footer", { children: [
1657
1513
  /* @__PURE__ */ jsxs("div", { className: "mb-20", children: [
1658
1514
  logo && /* @__PURE__ */ jsx(
1659
- FooterLogo,
1515
+ Pressable,
1660
1516
  {
1661
- logo,
1662
- logoClassName: cn(
1663
- "flex items-center gap-2",
1664
- logoWrapperClassName
1665
- ),
1666
- logoImageClassName: cn("h-10", logoClassName),
1667
- optixFlowConfig
1517
+ href: logo.url || "/",
1518
+ className: cn("block mb-8 md:mb-12", logoWrapperClassName),
1519
+ children: /* @__PURE__ */ jsx(
1520
+ Img,
1521
+ {
1522
+ src: logo.src,
1523
+ className: cn(
1524
+ "h-16 object-contain w-auto max-w-full",
1525
+ logo.className
1526
+ ),
1527
+ optixFlowConfig
1528
+ }
1529
+ )
1668
1530
  }
1669
1531
  ),
1670
1532
  socialLinks && socialLinks.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("mt-6", socialColumnClassName), children: /* @__PURE__ */ jsx(
@@ -1677,10 +1539,9 @@ function FooterSocialNewsletter({
1677
1539
  children: socialLinks.map((social, idx) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
1678
1540
  SocialLinkIcon,
1679
1541
  {
1680
- href: social.href,
1681
- label: social.label,
1682
- iconNameOverride: social.iconNameOverride,
1683
- iconSize: 20,
1542
+ size: "icon-lg",
1543
+ variant: "ghost",
1544
+ ...social,
1684
1545
  className: cn(
1685
1546
  "opacity-80 transition-colors hover:opacity-100",
1686
1547
  socialLinkClassName