@opensite/ui 1.8.2 → 1.8.4

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 (171) hide show
  1. package/dist/about-story-gallery.cjs +3 -30
  2. package/dist/about-story-gallery.d.cts +1 -1
  3. package/dist/about-story-gallery.d.ts +1 -1
  4. package/dist/about-story-gallery.js +3 -30
  5. package/dist/components.d.cts +1 -1
  6. package/dist/components.d.ts +1 -1
  7. package/dist/contact-callback.cjs +526 -273
  8. package/dist/contact-callback.d.cts +39 -59
  9. package/dist/contact-callback.d.ts +39 -59
  10. package/dist/contact-callback.js +528 -274
  11. package/dist/contact-card.cjs +459 -183
  12. package/dist/contact-card.d.cts +26 -49
  13. package/dist/contact-card.d.ts +26 -49
  14. package/dist/contact-card.js +461 -183
  15. package/dist/contact-careers.cjs +614 -510
  16. package/dist/contact-careers.d.cts +32 -55
  17. package/dist/contact-careers.d.ts +32 -55
  18. package/dist/contact-careers.js +616 -510
  19. package/dist/contact-catering.cjs +507 -501
  20. package/dist/contact-catering.d.cts +27 -61
  21. package/dist/contact-catering.d.ts +27 -61
  22. package/dist/contact-catering.js +509 -500
  23. package/dist/contact-consultation.cjs +484 -253
  24. package/dist/contact-consultation.d.cts +29 -56
  25. package/dist/contact-consultation.d.ts +29 -56
  26. package/dist/contact-consultation.js +486 -253
  27. package/dist/contact-dark.cjs +296 -296
  28. package/dist/contact-dark.d.cts +1 -1
  29. package/dist/contact-dark.d.ts +1 -1
  30. package/dist/contact-dark.js +297 -296
  31. package/dist/contact-demo.d.cts +1 -1
  32. package/dist/contact-demo.d.ts +1 -1
  33. package/dist/contact-emergency.d.cts +1 -1
  34. package/dist/contact-emergency.d.ts +1 -1
  35. package/dist/contact-event.d.cts +1 -1
  36. package/dist/contact-event.d.ts +1 -1
  37. package/dist/contact-faq.cjs +247 -250
  38. package/dist/contact-faq.d.cts +1 -1
  39. package/dist/contact-faq.d.ts +1 -1
  40. package/dist/contact-faq.js +248 -250
  41. package/dist/contact-feedback.d.cts +1 -1
  42. package/dist/contact-feedback.d.ts +1 -1
  43. package/dist/contact-fitness.d.cts +1 -1
  44. package/dist/contact-fitness.d.ts +1 -1
  45. package/dist/contact-guest.d.cts +1 -1
  46. package/dist/contact-guest.d.ts +1 -1
  47. package/dist/contact-image.d.cts +1 -1
  48. package/dist/contact-image.d.ts +1 -1
  49. package/dist/contact-insurance.d.cts +1 -1
  50. package/dist/contact-insurance.d.ts +1 -1
  51. package/dist/contact-interview.d.cts +1 -1
  52. package/dist/contact-interview.d.ts +1 -1
  53. package/dist/contact-locations.d.cts +1 -1
  54. package/dist/contact-locations.d.ts +1 -1
  55. package/dist/contact-maintenance.d.cts +1 -1
  56. package/dist/contact-maintenance.d.ts +1 -1
  57. package/dist/contact-map.d.cts +1 -1
  58. package/dist/contact-map.d.ts +1 -1
  59. package/dist/contact-minimal.d.cts +1 -1
  60. package/dist/contact-minimal.d.ts +1 -1
  61. package/dist/contact-moving.d.cts +1 -1
  62. package/dist/contact-moving.d.ts +1 -1
  63. package/dist/contact-multistep.d.cts +1 -1
  64. package/dist/contact-multistep.d.ts +1 -1
  65. package/dist/contact-partnership.d.cts +1 -1
  66. package/dist/contact-partnership.d.ts +1 -1
  67. package/dist/contact-photography.cjs +247 -250
  68. package/dist/contact-photography.d.cts +1 -1
  69. package/dist/contact-photography.d.ts +1 -1
  70. package/dist/contact-photography.js +248 -250
  71. package/dist/contact-press.d.cts +1 -1
  72. package/dist/contact-press.d.ts +1 -1
  73. package/dist/contact-quote.d.cts +1 -1
  74. package/dist/contact-quote.d.ts +1 -1
  75. package/dist/contact-referral.d.cts +1 -1
  76. package/dist/contact-referral.d.ts +1 -1
  77. package/dist/contact-report.d.cts +1 -1
  78. package/dist/contact-report.d.ts +1 -1
  79. package/dist/contact-reservation.d.cts +1 -1
  80. package/dist/contact-reservation.d.ts +1 -1
  81. package/dist/contact-retreat.d.cts +1 -1
  82. package/dist/contact-retreat.d.ts +1 -1
  83. package/dist/contact-rsvp.d.cts +1 -1
  84. package/dist/contact-rsvp.d.ts +1 -1
  85. package/dist/contact-sales.d.cts +1 -1
  86. package/dist/contact-sales.d.ts +1 -1
  87. package/dist/contact-schedule.d.cts +1 -1
  88. package/dist/contact-schedule.d.ts +1 -1
  89. package/dist/contact-sponsorship.d.cts +1 -1
  90. package/dist/contact-sponsorship.d.ts +1 -1
  91. package/dist/contact-support.d.cts +1 -1
  92. package/dist/contact-support.d.ts +1 -1
  93. package/dist/contact-tenant.d.cts +1 -1
  94. package/dist/contact-tenant.d.ts +1 -1
  95. package/dist/contact-vendor.d.cts +1 -1
  96. package/dist/contact-vendor.d.ts +1 -1
  97. package/dist/contact-volunteer.d.cts +1 -1
  98. package/dist/contact-volunteer.d.ts +1 -1
  99. package/dist/contact-warranty.d.cts +1 -1
  100. package/dist/contact-warranty.d.ts +1 -1
  101. package/dist/contact-wedding.d.cts +1 -1
  102. package/dist/contact-wedding.d.ts +1 -1
  103. package/dist/cta-app-download-newsletter.d.cts +1 -1
  104. package/dist/cta-app-download-newsletter.d.ts +1 -1
  105. package/dist/cta-newsletter-features.d.cts +1 -1
  106. package/dist/cta-newsletter-features.d.ts +1 -1
  107. package/dist/footer-accordion-social.d.cts +1 -1
  108. package/dist/footer-accordion-social.d.ts +1 -1
  109. package/dist/footer-newsletter-contact.d.cts +1 -1
  110. package/dist/footer-newsletter-contact.d.ts +1 -1
  111. package/dist/footer-newsletter-minimal.d.cts +1 -1
  112. package/dist/footer-newsletter-minimal.d.ts +1 -1
  113. package/dist/footer-split-image-accordion.d.cts +1 -1
  114. package/dist/footer-split-image-accordion.d.ts +1 -1
  115. package/dist/{forms-nGgHUTBw.d.cts → forms-CStlFhnh.d.cts} +41 -0
  116. package/dist/{forms-nGgHUTBw.d.ts → forms-CStlFhnh.d.ts} +41 -0
  117. package/dist/hero-conversation-intelligence.cjs +1 -2
  118. package/dist/hero-conversation-intelligence.d.cts +1 -5
  119. package/dist/hero-conversation-intelligence.d.ts +1 -5
  120. package/dist/hero-conversation-intelligence.js +1 -2
  121. package/dist/hero-conversion-video-play.cjs +2 -2
  122. package/dist/hero-conversion-video-play.js +2 -2
  123. package/dist/hero-design-system-3d.cjs +162 -82
  124. package/dist/hero-design-system-3d.js +162 -82
  125. package/dist/hero-ecommerce-product-showcase.cjs +103 -81
  126. package/dist/hero-ecommerce-product-showcase.d.cts +5 -1
  127. package/dist/hero-ecommerce-product-showcase.d.ts +5 -1
  128. package/dist/hero-ecommerce-product-showcase.js +103 -81
  129. package/dist/hero-floating-images.cjs +1 -1
  130. package/dist/hero-floating-images.js +1 -1
  131. package/dist/hero-hiring-animated-text.cjs +4 -4
  132. package/dist/hero-hiring-animated-text.js +4 -4
  133. package/dist/hero-minimal-centered-dark.cjs +111 -82
  134. package/dist/hero-minimal-centered-dark.d.cts +1 -1
  135. package/dist/hero-minimal-centered-dark.d.ts +1 -1
  136. package/dist/hero-minimal-centered-dark.js +111 -82
  137. package/dist/hero-mobile-app-download.cjs +1 -1
  138. package/dist/hero-mobile-app-download.js +1 -1
  139. package/dist/hero-overlay-cta-grid.cjs +1 -1
  140. package/dist/hero-overlay-cta-grid.js +1 -1
  141. package/dist/hero-spiral-pattern-cards.cjs +1 -1
  142. package/dist/hero-spiral-pattern-cards.js +1 -1
  143. package/dist/hero-startup-launch-cta.cjs +1 -1
  144. package/dist/hero-startup-launch-cta.js +1 -1
  145. package/dist/hero-stats-social-proof.cjs +106 -90
  146. package/dist/hero-stats-social-proof.js +106 -90
  147. package/dist/hero-testimonial-image-grid.cjs +1 -1
  148. package/dist/hero-testimonial-image-grid.js +1 -1
  149. package/dist/hero-therapy-testimonial-grid.cjs +1 -1
  150. package/dist/hero-therapy-testimonial-grid.js +1 -1
  151. package/dist/hero-ui-library-showcase.cjs +63 -15
  152. package/dist/hero-ui-library-showcase.d.cts +5 -1
  153. package/dist/hero-ui-library-showcase.d.ts +5 -1
  154. package/dist/hero-ui-library-showcase.js +63 -15
  155. package/dist/index.cjs +44 -6
  156. package/dist/index.d.cts +3 -2
  157. package/dist/index.d.ts +3 -2
  158. package/dist/index.js +44 -6
  159. package/dist/link-page-newsletter-social.d.cts +1 -1
  160. package/dist/link-page-newsletter-social.d.ts +1 -1
  161. package/dist/offer-modal-membership-image.d.cts +1 -1
  162. package/dist/offer-modal-membership-image.d.ts +1 -1
  163. package/dist/offer-modal-newsletter-discount.d.cts +1 -1
  164. package/dist/offer-modal-newsletter-discount.d.ts +1 -1
  165. package/dist/offer-modal-sheet-newsletter.d.cts +1 -1
  166. package/dist/offer-modal-sheet-newsletter.d.ts +1 -1
  167. package/dist/registry.cjs +14465 -14767
  168. package/dist/registry.js +12664 -12966
  169. package/dist/resource-list-hero-filter.d.cts +1 -1
  170. package/dist/resource-list-hero-filter.d.ts +1 -1
  171. package/package.json +3 -3
@@ -3,13 +3,11 @@
3
3
 
4
4
  var React = require('react');
5
5
  var forms = require('@page-speed/forms');
6
- var inputs = require('@page-speed/forms/inputs');
7
6
  var clsx = require('clsx');
8
7
  var tailwindMerge = require('tailwind-merge');
9
8
  var classVarianceAuthority = require('class-variance-authority');
10
9
  var jsxRuntime = require('react/jsx-runtime');
11
- var LabelPrimitive = require('@radix-ui/react-label');
12
- var SeparatorPrimitive = require('@radix-ui/react-separator');
10
+ var inputs = require('@page-speed/forms/inputs');
13
11
  var integration = require('@page-speed/forms/integration');
14
12
 
15
13
  function _interopNamespace(e) {
@@ -31,12 +29,8 @@ function _interopNamespace(e) {
31
29
  }
32
30
 
33
31
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
34
- var LabelPrimitive__namespace = /*#__PURE__*/_interopNamespace(LabelPrimitive);
35
- var SeparatorPrimitive__namespace = /*#__PURE__*/_interopNamespace(SeparatorPrimitive);
36
32
 
37
33
  // components/blocks/contact/contact-consultation.tsx
38
- var TextInput = inputs.TextInput;
39
- var TextArea = inputs.TextArea;
40
34
  function cn(...inputs) {
41
35
  return tailwindMerge.twMerge(clsx.clsx(inputs));
42
36
  }
@@ -383,6 +377,7 @@ var Pressable = React__namespace.forwardRef(
383
377
  rel,
384
378
  linkType,
385
379
  isInternal,
380
+ isExternal,
386
381
  handleClick
387
382
  } = navigation;
388
383
  const shouldRenderLink = normalizedHref && linkType !== "none";
@@ -458,6 +453,231 @@ var Pressable = React__namespace.forwardRef(
458
453
  }
459
454
  );
460
455
  Pressable.displayName = "Pressable";
456
+ function Card({ className, ...props }) {
457
+ return /* @__PURE__ */ jsxRuntime.jsx(
458
+ "div",
459
+ {
460
+ "data-slot": "card",
461
+ className: cn(
462
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
463
+ className
464
+ ),
465
+ ...props
466
+ }
467
+ );
468
+ }
469
+ function CardContent({ className, ...props }) {
470
+ return /* @__PURE__ */ jsxRuntime.jsx(
471
+ "div",
472
+ {
473
+ "data-slot": "card-content",
474
+ className: cn("px-6", className),
475
+ ...props
476
+ }
477
+ );
478
+ }
479
+ function DynamicFormField({
480
+ field,
481
+ className,
482
+ uploadProgress = {},
483
+ onFileUpload,
484
+ onFileRemove,
485
+ isUploading = false
486
+ }) {
487
+ const fieldId = field.name;
488
+ const usesGroupLegend = field.type === "radio" || field.type === "checkbox-group";
489
+ const usesInlineCheckboxLabel = field.type === "checkbox";
490
+ const shouldRenderFieldLabel = !usesGroupLegend && !usesInlineCheckboxLabel;
491
+ const checkboxLabel = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
492
+ field.label,
493
+ field.required ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive ml-1", children: "*" }) : null
494
+ ] });
495
+ return /* @__PURE__ */ jsxRuntime.jsx(
496
+ forms.Field,
497
+ {
498
+ name: field.name,
499
+ label: shouldRenderFieldLabel ? field.label : void 0,
500
+ description: shouldRenderFieldLabel ? field.description : void 0,
501
+ required: field.required,
502
+ className: cn("space-y-2", className),
503
+ children: ({ field: formField, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
504
+ (field.type === "text" || field.type === "email" || field.type === "tel" || field.type === "search" || field.type === "password" || field.type === "url") && /* @__PURE__ */ jsxRuntime.jsx(
505
+ inputs.TextInput,
506
+ {
507
+ ...formField,
508
+ id: fieldId,
509
+ type: field.type,
510
+ placeholder: field.placeholder,
511
+ error: meta.touched && !!meta.error,
512
+ disabled: field.disabled,
513
+ "aria-label": field.label
514
+ }
515
+ ),
516
+ field.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(
517
+ inputs.TextInput,
518
+ {
519
+ ...formField,
520
+ id: fieldId,
521
+ type: "text",
522
+ placeholder: field.placeholder,
523
+ error: meta.touched && !!meta.error,
524
+ disabled: field.disabled,
525
+ "aria-label": field.label
526
+ }
527
+ ),
528
+ field.type === "textarea" && /* @__PURE__ */ jsxRuntime.jsx(
529
+ inputs.TextArea,
530
+ {
531
+ ...formField,
532
+ id: fieldId,
533
+ placeholder: field.placeholder,
534
+ rows: field.rows || 4,
535
+ error: meta.touched && !!meta.error,
536
+ disabled: field.disabled,
537
+ "aria-label": field.label
538
+ }
539
+ ),
540
+ field.type === "select" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
541
+ inputs.Select,
542
+ {
543
+ ...formField,
544
+ id: fieldId,
545
+ options: field.options,
546
+ placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
547
+ error: meta.touched && !!meta.error,
548
+ disabled: field.disabled,
549
+ "aria-label": field.label
550
+ }
551
+ ),
552
+ field.type === "multi-select" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
553
+ inputs.MultiSelect,
554
+ {
555
+ ...formField,
556
+ id: fieldId,
557
+ options: field.options,
558
+ placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
559
+ error: meta.touched && !!meta.error,
560
+ disabled: field.disabled,
561
+ "aria-label": field.label
562
+ }
563
+ ),
564
+ field.type === "radio" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
565
+ inputs.Radio,
566
+ {
567
+ ...formField,
568
+ id: fieldId,
569
+ options: field.options,
570
+ label: field.label,
571
+ description: field.description,
572
+ required: field.required,
573
+ disabled: field.disabled,
574
+ layout: field.layout || "stacked",
575
+ error: meta.touched && !!meta.error,
576
+ "aria-label": field.label
577
+ }
578
+ ),
579
+ field.type === "checkbox" && /* @__PURE__ */ jsxRuntime.jsx(
580
+ inputs.Checkbox,
581
+ {
582
+ ...formField,
583
+ id: fieldId,
584
+ value: formField.value === true || formField.value === "true",
585
+ onChange: (checked) => formField.onChange(checked),
586
+ label: checkboxLabel,
587
+ description: field.description,
588
+ disabled: field.disabled,
589
+ required: field.required,
590
+ error: meta.touched && !!meta.error,
591
+ "aria-label": field.label
592
+ }
593
+ ),
594
+ field.type === "checkbox-group" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
595
+ inputs.CheckboxGroup,
596
+ {
597
+ ...formField,
598
+ id: fieldId,
599
+ options: field.options,
600
+ label: field.label,
601
+ description: field.description,
602
+ required: field.required,
603
+ disabled: field.disabled,
604
+ layout: field.layout || "stacked",
605
+ error: meta.touched && !!meta.error,
606
+ "aria-label": field.label
607
+ }
608
+ ),
609
+ (field.type === "date-picker" || field.type === "date") && /* @__PURE__ */ jsxRuntime.jsx(
610
+ inputs.DatePicker,
611
+ {
612
+ ...formField,
613
+ id: fieldId,
614
+ placeholder: field.placeholder,
615
+ error: meta.touched && !!meta.error,
616
+ disabled: field.disabled,
617
+ "aria-label": field.label
618
+ }
619
+ ),
620
+ field.type === "date-range" && /* @__PURE__ */ jsxRuntime.jsx(
621
+ inputs.DateRangePicker,
622
+ {
623
+ ...formField,
624
+ id: fieldId,
625
+ placeholder: field.placeholder,
626
+ error: meta.touched && !!meta.error,
627
+ disabled: field.disabled,
628
+ "aria-label": field.label
629
+ }
630
+ ),
631
+ field.type === "time" && /* @__PURE__ */ jsxRuntime.jsx(
632
+ inputs.TimePicker,
633
+ {
634
+ ...formField,
635
+ id: fieldId,
636
+ placeholder: field.placeholder,
637
+ error: meta.touched && !!meta.error,
638
+ disabled: field.disabled,
639
+ "aria-label": field.label
640
+ }
641
+ ),
642
+ field.type === "file" && /* @__PURE__ */ jsxRuntime.jsx(
643
+ inputs.FileInput,
644
+ {
645
+ ...formField,
646
+ id: fieldId,
647
+ accept: field.accept,
648
+ maxSize: field.maxSize || 5 * 1024 * 1024,
649
+ maxFiles: field.maxFiles || 1,
650
+ multiple: field.multiple || false,
651
+ placeholder: field.placeholder || "Choose file(s)...",
652
+ error: meta.touched && !!meta.error,
653
+ disabled: field.disabled || isUploading,
654
+ showProgress: true,
655
+ uploadProgress,
656
+ onChange: (files) => {
657
+ formField.onChange(files);
658
+ if (files.length > 0 && onFileUpload) {
659
+ onFileUpload(files);
660
+ }
661
+ },
662
+ onFileRemove,
663
+ "aria-label": field.label
664
+ }
665
+ ),
666
+ field.type === "rich-text" && /* @__PURE__ */ jsxRuntime.jsx(
667
+ inputs.RichTextEditor,
668
+ {
669
+ ...formField,
670
+ id: fieldId,
671
+ placeholder: field.placeholder,
672
+ error: meta.touched && !!meta.error,
673
+ disabled: field.disabled,
674
+ "aria-label": field.label
675
+ }
676
+ )
677
+ ] })
678
+ }
679
+ );
680
+ }
461
681
  var svgCache = /* @__PURE__ */ new Map();
462
682
  function DynamicIcon({
463
683
  name,
@@ -563,65 +783,169 @@ function processSvgForCurrentColor(svg) {
563
783
  );
564
784
  return processed;
565
785
  }
566
- function Card({ className, ...props }) {
567
- return /* @__PURE__ */ jsxRuntime.jsx(
568
- "div",
569
- {
570
- "data-slot": "card",
571
- className: cn(
572
- "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
573
- className
574
- ),
575
- ...props
576
- }
786
+
787
+ // lib/form-field-types.ts
788
+ function generateInitialValues(fields) {
789
+ return fields.reduce(
790
+ (acc, field) => {
791
+ if (field.type === "checkbox") {
792
+ acc[field.name] = false;
793
+ } else if (field.type === "checkbox-group" || field.type === "multi-select") {
794
+ acc[field.name] = [];
795
+ } else if (field.type === "file") {
796
+ acc[field.name] = [];
797
+ } else if (field.type === "date-range") {
798
+ acc[field.name] = { start: null, end: null };
799
+ } else {
800
+ acc[field.name] = "";
801
+ }
802
+ return acc;
803
+ },
804
+ {}
577
805
  );
578
806
  }
579
- function CardContent({ className, ...props }) {
580
- return /* @__PURE__ */ jsxRuntime.jsx(
581
- "div",
582
- {
583
- "data-slot": "card-content",
584
- className: cn("px-6", className),
585
- ...props
586
- }
807
+ function generateValidationSchema(fields) {
808
+ return fields.reduce(
809
+ (acc, field) => {
810
+ acc[field.name] = (value, allValues) => {
811
+ if (field.required) {
812
+ if (!value || typeof value === "string" && !value.trim()) {
813
+ return `${field.label} is required`;
814
+ }
815
+ }
816
+ if (field.type === "email" && value) {
817
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
818
+ return "Please enter a valid email address";
819
+ }
820
+ }
821
+ if (field.type === "url" && value) {
822
+ try {
823
+ new URL(value);
824
+ } catch {
825
+ return "Please enter a valid URL";
826
+ }
827
+ }
828
+ if (field.validator) {
829
+ return field.validator(value, allValues);
830
+ }
831
+ return void 0;
832
+ };
833
+ return acc;
834
+ },
835
+ {}
587
836
  );
588
837
  }
589
- function Label({
590
- className,
591
- ...props
592
- }) {
593
- return /* @__PURE__ */ jsxRuntime.jsx(
594
- LabelPrimitive__namespace.Root,
595
- {
596
- "data-slot": "label",
597
- className: cn(
598
- "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
599
- className
600
- ),
601
- ...props
602
- }
603
- );
838
+ function getColumnSpanClass(span) {
839
+ if (!span || span === 12) return "col-span-12";
840
+ return `col-span-12 sm:col-span-${Math.min(span, 12)}`;
604
841
  }
605
- function Separator({
606
- className,
607
- orientation = "horizontal",
608
- decorative = true,
609
- ...props
610
- }) {
611
- return /* @__PURE__ */ jsxRuntime.jsx(
612
- SeparatorPrimitive__namespace.Root,
613
- {
614
- "data-slot": "separator",
615
- decorative,
616
- orientation,
617
- className: cn(
618
- "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
619
- className
620
- ),
621
- ...props
842
+ function useContactForm(options) {
843
+ const {
844
+ formFields,
845
+ formConfig,
846
+ onSubmit,
847
+ onSuccess,
848
+ onError,
849
+ resetOnSuccess = true,
850
+ uploadTokens = []
851
+ } = options;
852
+ const [submissionError, setSubmissionError] = React.useState(null);
853
+ const submissionConfig = formConfig?.submissionConfig;
854
+ const redirectUrl = submissionConfig?.redirectUrl;
855
+ const redirectNavigation = useNavigation({ href: redirectUrl });
856
+ const resetSubmissionState = React.useCallback(() => {
857
+ setSubmissionError(null);
858
+ }, []);
859
+ const performRedirect = React.useCallback(() => {
860
+ if (!redirectUrl || typeof window === "undefined") {
861
+ return;
622
862
  }
623
- );
863
+ const navigate = () => {
864
+ if (redirectNavigation.shouldUseRouter && redirectNavigation.normalizedHref) {
865
+ const handler = window.__opensiteNavigationHandler;
866
+ if (typeof handler === "function") {
867
+ try {
868
+ const handled = handler(redirectNavigation.normalizedHref, void 0);
869
+ if (handled !== false) {
870
+ return;
871
+ }
872
+ } catch (error) {
873
+ console.error("Internal redirect handler failed:", error);
874
+ }
875
+ }
876
+ }
877
+ const destination = redirectNavigation.normalizedHref || redirectUrl;
878
+ window.location.assign(destination);
879
+ };
880
+ window.setTimeout(navigate, 150);
881
+ }, [redirectNavigation, redirectUrl]);
882
+ const form = forms.useForm({
883
+ initialValues: React.useMemo(
884
+ () => generateInitialValues(formFields),
885
+ [formFields]
886
+ ),
887
+ validationSchema: React.useMemo(
888
+ () => generateValidationSchema(formFields),
889
+ [formFields]
890
+ ),
891
+ onSubmit: async (values, helpers) => {
892
+ resetSubmissionState();
893
+ const shouldAutoSubmit = Boolean(formConfig?.endpoint);
894
+ if (!shouldAutoSubmit && !onSubmit) {
895
+ return;
896
+ }
897
+ try {
898
+ let result;
899
+ const submissionValues = {
900
+ ...values,
901
+ ...uploadTokens.length > 0 && {
902
+ contact_form_upload_tokens: uploadTokens
903
+ }
904
+ };
905
+ if (shouldAutoSubmit) {
906
+ result = await submitPageSpeedForm(submissionValues, formConfig);
907
+ }
908
+ if (onSubmit) {
909
+ await onSubmit(submissionValues);
910
+ }
911
+ if (shouldAutoSubmit || onSubmit) {
912
+ try {
913
+ await submissionConfig?.handleFormSubmission?.({
914
+ formData: submissionValues,
915
+ responseData: result
916
+ });
917
+ } catch (callbackError) {
918
+ console.error("handleFormSubmission callback failed:", callbackError);
919
+ }
920
+ if (resetOnSuccess) {
921
+ helpers.resetForm();
922
+ }
923
+ onSuccess?.(result);
924
+ if (submissionConfig?.behavior === "redirect" && submissionConfig.redirectUrl) {
925
+ performRedirect();
926
+ }
927
+ }
928
+ } catch (error) {
929
+ if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
930
+ helpers.setErrors(error.formErrors);
931
+ }
932
+ const errorMessage = error instanceof Error ? error.message : "Form submission failed";
933
+ setSubmissionError(errorMessage);
934
+ onError?.(error);
935
+ }
936
+ }
937
+ });
938
+ const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
939
+ return {
940
+ form,
941
+ isSubmitted: form.status === "success",
942
+ submissionError,
943
+ formMethod,
944
+ resetSubmissionState
945
+ };
624
946
  }
947
+
948
+ // lib/forms.ts
625
949
  var PageSpeedFormSubmissionError = class extends Error {
626
950
  constructor(message, options = {}) {
627
951
  super(message);
@@ -1133,13 +1457,93 @@ var BUDGETS = [
1133
1457
  { value: "25k-50k", label: "$25,000 - $50,000" },
1134
1458
  { value: "50k-plus", label: "$50,000+" }
1135
1459
  ];
1460
+ var DEFAULT_FORM_FIELDS = [
1461
+ {
1462
+ name: "service",
1463
+ type: "select",
1464
+ label: "Service Needed",
1465
+ placeholder: "Select a service",
1466
+ required: true,
1467
+ columnSpan: 12,
1468
+ options: SERVICES
1469
+ },
1470
+ {
1471
+ name: "duration",
1472
+ type: "select",
1473
+ label: "Preferred Duration",
1474
+ placeholder: "Select duration",
1475
+ required: false,
1476
+ columnSpan: 6,
1477
+ options: DURATIONS
1478
+ },
1479
+ {
1480
+ name: "budget",
1481
+ type: "select",
1482
+ label: "Project Budget",
1483
+ placeholder: "Select budget range",
1484
+ required: false,
1485
+ columnSpan: 6,
1486
+ options: BUDGETS
1487
+ },
1488
+ {
1489
+ name: "firstName",
1490
+ type: "text",
1491
+ label: "First Name",
1492
+ placeholder: "John",
1493
+ required: true,
1494
+ columnSpan: 6
1495
+ },
1496
+ {
1497
+ name: "lastName",
1498
+ type: "text",
1499
+ label: "Last Name",
1500
+ placeholder: "Doe",
1501
+ required: true,
1502
+ columnSpan: 6
1503
+ },
1504
+ {
1505
+ name: "email",
1506
+ type: "email",
1507
+ label: "Email Address",
1508
+ placeholder: "john@example.com",
1509
+ required: true,
1510
+ columnSpan: 6
1511
+ },
1512
+ {
1513
+ name: "phone",
1514
+ type: "tel",
1515
+ label: "Phone Number",
1516
+ placeholder: "+1 (555) 000-0000",
1517
+ required: true,
1518
+ columnSpan: 6
1519
+ },
1520
+ {
1521
+ name: "company",
1522
+ type: "text",
1523
+ label: "Company Name",
1524
+ placeholder: "Acme Inc.",
1525
+ required: false,
1526
+ columnSpan: 12
1527
+ },
1528
+ {
1529
+ name: "details",
1530
+ type: "textarea",
1531
+ label: "Tell us about your needs",
1532
+ placeholder: "Describe your project, goals, and any specific challenges...",
1533
+ required: false,
1534
+ rows: 4,
1535
+ columnSpan: 12
1536
+ }
1537
+ ];
1136
1538
  function ContactConsultation({
1137
1539
  heading,
1138
1540
  description,
1139
- buttonText,
1541
+ buttonText = "Book Consultation",
1140
1542
  buttonIcon,
1141
1543
  actions,
1142
1544
  actionsSlot,
1545
+ formFields,
1546
+ successMessage = "Thank you for your consultation request! We'll be in touch within 24 hours to schedule your session.",
1143
1547
  className,
1144
1548
  headerClassName,
1145
1549
  headingClassName,
@@ -1147,6 +1551,8 @@ function ContactConsultation({
1147
1551
  cardClassName,
1148
1552
  cardContentClassName,
1149
1553
  formClassName,
1554
+ successMessageClassName,
1555
+ errorMessageClassName,
1150
1556
  submitClassName,
1151
1557
  spacing = "py-8 md:py-32",
1152
1558
  containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
@@ -1158,60 +1564,17 @@ function ContactConsultation({
1158
1564
  onSuccess,
1159
1565
  onError
1160
1566
  }) {
1161
- const form = forms.useForm({
1162
- initialValues: {
1163
- service: "",
1164
- duration: "60",
1165
- budget: "",
1166
- firstName: "",
1167
- lastName: "",
1168
- email: "",
1169
- phone: "",
1170
- company: "",
1171
- details: ""
1172
- },
1173
- validationSchema: {
1174
- service: (value) => !value ? "Please select a service" : void 0,
1175
- budget: (value) => !value ? "Please select a budget range" : void 0,
1176
- firstName: (value) => !value ? "First name is required" : void 0,
1177
- lastName: (value) => !value ? "Last name is required" : void 0,
1178
- email: (value) => {
1179
- if (!value) return "Email is required";
1180
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
1181
- return "Please enter a valid email address";
1182
- return void 0;
1183
- },
1184
- phone: (value) => !value ? "Phone number is required" : void 0
1185
- },
1186
- onSubmit: async (values, helpers) => {
1187
- const shouldAutoSubmit = Boolean(formConfig?.endpoint);
1188
- if (!shouldAutoSubmit && !onSubmit) {
1189
- return;
1190
- }
1191
- try {
1192
- let result;
1193
- if (shouldAutoSubmit) {
1194
- result = await submitPageSpeedForm(values, formConfig);
1195
- }
1196
- if (onSubmit) {
1197
- await onSubmit(values);
1198
- }
1199
- if (shouldAutoSubmit || onSubmit) {
1200
- if (formConfig?.resetOnSuccess !== false) {
1201
- helpers.resetForm();
1202
- }
1203
- onSuccess?.(result);
1204
- }
1205
- } catch (error) {
1206
- if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
1207
- helpers.setErrors(error.formErrors);
1208
- }
1209
- onError?.(error);
1210
- throw error;
1211
- }
1212
- }
1567
+ const fields = React.useMemo(
1568
+ () => formFields || DEFAULT_FORM_FIELDS,
1569
+ [formFields]
1570
+ );
1571
+ const { form, submissionError, formMethod, resetSubmissionState } = useContactForm({
1572
+ formFields: fields,
1573
+ formConfig,
1574
+ onSubmit,
1575
+ onSuccess,
1576
+ onError
1213
1577
  });
1214
- const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
1215
1578
  const actionsContent = React__namespace.useMemo(() => {
1216
1579
  if (actionsSlot) return actionsSlot;
1217
1580
  if (actions && actions.length > 0) {
@@ -1280,147 +1643,15 @@ function ContactConsultation({
1280
1643
  form,
1281
1644
  action: formConfig?.endpoint,
1282
1645
  method: formMethod,
1283
- className: cn("space-y-8", formClassName),
1646
+ submissionError,
1647
+ successMessage,
1648
+ successMessageClassName,
1649
+ errorMessageClassName,
1650
+ submissionConfig: formConfig?.submissionConfig,
1651
+ onNewSubmission: resetSubmissionState,
1652
+ className: cn("space-y-6", formClassName),
1284
1653
  children: [
1285
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1286
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold", children: "Consultation Details" }),
1287
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "service", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1288
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "service", children: "Service Type" }),
1289
- /* @__PURE__ */ jsxRuntime.jsxs(
1290
- inputs.Select,
1291
- {
1292
- ...field,
1293
- id: "service",
1294
- error: meta.touched && !!meta.error,
1295
- "aria-label": "Service Type",
1296
- children: [
1297
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a service" }),
1298
- SERVICES.map((service) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: service.value, children: service.label }, service.value))
1299
- ]
1300
- }
1301
- )
1302
- ] }) }),
1303
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1304
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "duration", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1305
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "duration", children: "Preferred Duration" }),
1306
- /* @__PURE__ */ jsxRuntime.jsx(
1307
- inputs.Select,
1308
- {
1309
- ...field,
1310
- id: "duration",
1311
- error: meta.touched && !!meta.error,
1312
- "aria-label": "Preferred Duration",
1313
- children: DURATIONS.map((duration) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: duration.value, children: duration.label }, duration.value))
1314
- }
1315
- )
1316
- ] }) }),
1317
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "budget", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1318
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "budget", children: "Budget Range" }),
1319
- /* @__PURE__ */ jsxRuntime.jsxs(
1320
- inputs.Select,
1321
- {
1322
- ...field,
1323
- id: "budget",
1324
- error: meta.touched && !!meta.error,
1325
- "aria-label": "Budget Range",
1326
- children: [
1327
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select budget range" }),
1328
- BUDGETS.map((budget) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: budget.value, children: budget.label }, budget.value))
1329
- ]
1330
- }
1331
- )
1332
- ] }) })
1333
- ] })
1334
- ] }),
1335
- /* @__PURE__ */ jsxRuntime.jsx(Separator, {}),
1336
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1337
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold", children: "Your Information" }),
1338
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1339
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "firstName", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1340
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "first-name", children: "First Name" }),
1341
- /* @__PURE__ */ jsxRuntime.jsx(
1342
- TextInput,
1343
- {
1344
- ...field,
1345
- id: "first-name",
1346
- placeholder: "John",
1347
- error: meta.touched && !!meta.error,
1348
- "aria-label": "First Name"
1349
- }
1350
- )
1351
- ] }) }),
1352
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "lastName", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1353
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "last-name", children: "Last Name" }),
1354
- /* @__PURE__ */ jsxRuntime.jsx(
1355
- TextInput,
1356
- {
1357
- ...field,
1358
- id: "last-name",
1359
- placeholder: "Doe",
1360
- error: meta.touched && !!meta.error,
1361
- "aria-label": "Last Name"
1362
- }
1363
- )
1364
- ] }) })
1365
- ] }),
1366
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1367
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "email", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1368
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "email", children: "Email" }),
1369
- /* @__PURE__ */ jsxRuntime.jsx(
1370
- TextInput,
1371
- {
1372
- ...field,
1373
- id: "email",
1374
- type: "email",
1375
- placeholder: "john@example.com",
1376
- error: meta.touched && !!meta.error,
1377
- "aria-label": "Email"
1378
- }
1379
- )
1380
- ] }) }),
1381
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "phone", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1382
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "phone", children: "Phone" }),
1383
- /* @__PURE__ */ jsxRuntime.jsx(
1384
- TextInput,
1385
- {
1386
- ...field,
1387
- id: "phone",
1388
- type: "tel",
1389
- placeholder: "+1 (555) 000-0000",
1390
- error: meta.touched && !!meta.error,
1391
- "aria-label": "Phone"
1392
- }
1393
- )
1394
- ] }) })
1395
- ] }),
1396
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "company", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1397
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "company", children: "Company (Optional)" }),
1398
- /* @__PURE__ */ jsxRuntime.jsx(
1399
- TextInput,
1400
- {
1401
- ...field,
1402
- id: "company",
1403
- placeholder: "Acme Inc.",
1404
- error: meta.touched && !!meta.error,
1405
- "aria-label": "Company"
1406
- }
1407
- )
1408
- ] }) }),
1409
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "details", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1410
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "details", children: "Additional Details (Optional)" }),
1411
- /* @__PURE__ */ jsxRuntime.jsx(
1412
- TextArea,
1413
- {
1414
- ...field,
1415
- id: "details",
1416
- placeholder: "Tell us more about your needs...",
1417
- rows: 4,
1418
- error: meta.touched && !!meta.error,
1419
- "aria-label": "Additional Details"
1420
- }
1421
- )
1422
- ] }) })
1423
- ] }),
1654
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-12 gap-6", children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: getColumnSpanClass(field.columnSpan), children: /* @__PURE__ */ jsxRuntime.jsx(DynamicFormField, { field }) }, field.name)) }),
1424
1655
  actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxRuntime.jsxs(
1425
1656
  Pressable,
1426
1657
  {