@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
@@ -1,20 +1,15 @@
1
1
  "use client";
2
2
  import * as React from 'react';
3
- import React__default from 'react';
4
- import { useForm, Form, Field } from '@page-speed/forms';
5
- import { Select, TextInput as TextInput$1, Radio, TextArea as TextArea$1 } from '@page-speed/forms/inputs';
3
+ import React__default, { useMemo, useState, useCallback } from 'react';
4
+ import { Form, useForm, Field } from '@page-speed/forms';
6
5
  import { clsx } from 'clsx';
7
6
  import { twMerge } from 'tailwind-merge';
8
7
  import { cva } from 'class-variance-authority';
9
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
- import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
11
- import * as LabelPrimitive from '@radix-ui/react-label';
12
- import * as SeparatorPrimitive from '@radix-ui/react-separator';
9
+ import { TextInput, TextArea, Select, MultiSelect, Radio, Checkbox, CheckboxGroup, DatePicker, DateRangePicker, TimePicker, FileInput, RichTextEditor } from '@page-speed/forms/inputs';
13
10
  import { serializeForRails, deserializeErrors } from '@page-speed/forms/integration';
14
11
 
15
12
  // components/blocks/contact/contact-catering.tsx
16
- var TextInput = TextInput$1;
17
- var TextArea = TextArea$1;
18
13
  function cn(...inputs) {
19
14
  return twMerge(clsx(inputs));
20
15
  }
@@ -361,6 +356,7 @@ var Pressable = React.forwardRef(
361
356
  rel,
362
357
  linkType,
363
358
  isInternal,
359
+ isExternal,
364
360
  handleClick
365
361
  } = navigation;
366
362
  const shouldRenderLink = normalizedHref && linkType !== "none";
@@ -436,111 +432,6 @@ var Pressable = React.forwardRef(
436
432
  }
437
433
  );
438
434
  Pressable.displayName = "Pressable";
439
- var svgCache = /* @__PURE__ */ new Map();
440
- function DynamicIcon({
441
- name,
442
- size = 28,
443
- color,
444
- className,
445
- alt
446
- }) {
447
- const [svgContent, setSvgContent] = React.useState(null);
448
- const [isLoading, setIsLoading] = React.useState(true);
449
- const [error, setError] = React.useState(null);
450
- const { url, iconName } = React.useMemo(() => {
451
- const separator = name.includes("/") ? "/" : ":";
452
- const [prefix, iconName2] = name.split(separator);
453
- const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}&key=au382bi7fsh96w9h9xlrnat2jglx`;
454
- return {
455
- url: baseUrl,
456
- iconName: iconName2
457
- };
458
- }, [name, size]);
459
- React.useEffect(() => {
460
- let isMounted = true;
461
- const fetchSvg = async () => {
462
- const cached = svgCache.get(url);
463
- if (cached) {
464
- if (isMounted) {
465
- setSvgContent(cached);
466
- setIsLoading(false);
467
- }
468
- return;
469
- }
470
- try {
471
- setIsLoading(true);
472
- setError(null);
473
- const response = await fetch(url);
474
- if (!response.ok) {
475
- throw new Error(`Failed to fetch icon: ${response.status}`);
476
- }
477
- let svg = await response.text();
478
- svg = processSvgForCurrentColor(svg);
479
- svgCache.set(url, svg);
480
- if (isMounted) {
481
- setSvgContent(svg);
482
- setIsLoading(false);
483
- }
484
- } catch (err) {
485
- if (isMounted) {
486
- setError(err instanceof Error ? err.message : "Failed to load icon");
487
- setIsLoading(false);
488
- }
489
- }
490
- };
491
- fetchSvg();
492
- return () => {
493
- isMounted = false;
494
- };
495
- }, [url]);
496
- if (isLoading) {
497
- return /* @__PURE__ */ jsx(
498
- "span",
499
- {
500
- className: cn("inline-block", className),
501
- style: { width: size, height: size },
502
- "aria-hidden": "true"
503
- }
504
- );
505
- }
506
- if (error || !svgContent) {
507
- return /* @__PURE__ */ jsx(
508
- "span",
509
- {
510
- className: cn("inline-block", className),
511
- style: { width: size, height: size },
512
- role: "img",
513
- "aria-label": alt || iconName
514
- }
515
- );
516
- }
517
- return /* @__PURE__ */ jsx(
518
- "span",
519
- {
520
- className: cn("inline-flex items-center justify-center", className),
521
- style: {
522
- width: size,
523
- height: size,
524
- color: color || "inherit"
525
- },
526
- role: "img",
527
- "aria-label": alt || iconName,
528
- dangerouslySetInnerHTML: { __html: svgContent }
529
- }
530
- );
531
- }
532
- function processSvgForCurrentColor(svg) {
533
- let processed = svg;
534
- processed = processed.replace(
535
- /stroke=["'](#000000|#000|black)["']/gi,
536
- 'stroke="currentColor"'
537
- );
538
- processed = processed.replace(
539
- /fill=["'](#000000|#000|black)["']/gi,
540
- 'fill="currentColor"'
541
- );
542
- return processed;
543
- }
544
435
  function Card({ className, ...props }) {
545
436
  return /* @__PURE__ */ jsx(
546
437
  "div",
@@ -564,66 +455,371 @@ function CardContent({ className, ...props }) {
564
455
  }
565
456
  );
566
457
  }
567
- function Checkbox({
458
+ function DynamicFormField({
459
+ field,
568
460
  className,
569
- ...props
461
+ uploadProgress = {},
462
+ onFileUpload,
463
+ onFileRemove,
464
+ isUploading = false
570
465
  }) {
466
+ const fieldId = field.name;
467
+ const usesGroupLegend = field.type === "radio" || field.type === "checkbox-group";
468
+ const usesInlineCheckboxLabel = field.type === "checkbox";
469
+ const shouldRenderFieldLabel = !usesGroupLegend && !usesInlineCheckboxLabel;
470
+ const checkboxLabel = /* @__PURE__ */ jsxs(Fragment, { children: [
471
+ field.label,
472
+ field.required ? /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" }) : null
473
+ ] });
571
474
  return /* @__PURE__ */ jsx(
572
- CheckboxPrimitive.Root,
475
+ Field,
573
476
  {
574
- "data-slot": "checkbox",
575
- className: cn(
576
- "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
577
- className
578
- ),
579
- ...props,
580
- children: /* @__PURE__ */ jsx(
581
- CheckboxPrimitive.Indicator,
582
- {
583
- "data-slot": "checkbox-indicator",
584
- className: "grid place-content-center text-current transition-none",
585
- children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/check", size: 14 })
586
- }
587
- )
477
+ name: field.name,
478
+ label: shouldRenderFieldLabel ? field.label : void 0,
479
+ description: shouldRenderFieldLabel ? field.description : void 0,
480
+ required: field.required,
481
+ className: cn("space-y-2", className),
482
+ children: ({ field: formField, meta }) => /* @__PURE__ */ jsxs("div", { children: [
483
+ (field.type === "text" || field.type === "email" || field.type === "tel" || field.type === "search" || field.type === "password" || field.type === "url") && /* @__PURE__ */ jsx(
484
+ TextInput,
485
+ {
486
+ ...formField,
487
+ id: fieldId,
488
+ type: field.type,
489
+ placeholder: field.placeholder,
490
+ error: meta.touched && !!meta.error,
491
+ disabled: field.disabled,
492
+ "aria-label": field.label
493
+ }
494
+ ),
495
+ field.type === "number" && /* @__PURE__ */ jsx(
496
+ TextInput,
497
+ {
498
+ ...formField,
499
+ id: fieldId,
500
+ type: "text",
501
+ placeholder: field.placeholder,
502
+ error: meta.touched && !!meta.error,
503
+ disabled: field.disabled,
504
+ "aria-label": field.label
505
+ }
506
+ ),
507
+ field.type === "textarea" && /* @__PURE__ */ jsx(
508
+ TextArea,
509
+ {
510
+ ...formField,
511
+ id: fieldId,
512
+ placeholder: field.placeholder,
513
+ rows: field.rows || 4,
514
+ error: meta.touched && !!meta.error,
515
+ disabled: field.disabled,
516
+ "aria-label": field.label
517
+ }
518
+ ),
519
+ field.type === "select" && field.options && /* @__PURE__ */ jsx(
520
+ Select,
521
+ {
522
+ ...formField,
523
+ id: fieldId,
524
+ options: field.options,
525
+ placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
526
+ error: meta.touched && !!meta.error,
527
+ disabled: field.disabled,
528
+ "aria-label": field.label
529
+ }
530
+ ),
531
+ field.type === "multi-select" && field.options && /* @__PURE__ */ jsx(
532
+ MultiSelect,
533
+ {
534
+ ...formField,
535
+ id: fieldId,
536
+ options: field.options,
537
+ placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
538
+ error: meta.touched && !!meta.error,
539
+ disabled: field.disabled,
540
+ "aria-label": field.label
541
+ }
542
+ ),
543
+ field.type === "radio" && field.options && /* @__PURE__ */ jsx(
544
+ Radio,
545
+ {
546
+ ...formField,
547
+ id: fieldId,
548
+ options: field.options,
549
+ label: field.label,
550
+ description: field.description,
551
+ required: field.required,
552
+ disabled: field.disabled,
553
+ layout: field.layout || "stacked",
554
+ error: meta.touched && !!meta.error,
555
+ "aria-label": field.label
556
+ }
557
+ ),
558
+ field.type === "checkbox" && /* @__PURE__ */ jsx(
559
+ Checkbox,
560
+ {
561
+ ...formField,
562
+ id: fieldId,
563
+ value: formField.value === true || formField.value === "true",
564
+ onChange: (checked) => formField.onChange(checked),
565
+ label: checkboxLabel,
566
+ description: field.description,
567
+ disabled: field.disabled,
568
+ required: field.required,
569
+ error: meta.touched && !!meta.error,
570
+ "aria-label": field.label
571
+ }
572
+ ),
573
+ field.type === "checkbox-group" && field.options && /* @__PURE__ */ jsx(
574
+ CheckboxGroup,
575
+ {
576
+ ...formField,
577
+ id: fieldId,
578
+ options: field.options,
579
+ label: field.label,
580
+ description: field.description,
581
+ required: field.required,
582
+ disabled: field.disabled,
583
+ layout: field.layout || "stacked",
584
+ error: meta.touched && !!meta.error,
585
+ "aria-label": field.label
586
+ }
587
+ ),
588
+ (field.type === "date-picker" || field.type === "date") && /* @__PURE__ */ jsx(
589
+ DatePicker,
590
+ {
591
+ ...formField,
592
+ id: fieldId,
593
+ placeholder: field.placeholder,
594
+ error: meta.touched && !!meta.error,
595
+ disabled: field.disabled,
596
+ "aria-label": field.label
597
+ }
598
+ ),
599
+ field.type === "date-range" && /* @__PURE__ */ jsx(
600
+ DateRangePicker,
601
+ {
602
+ ...formField,
603
+ id: fieldId,
604
+ placeholder: field.placeholder,
605
+ error: meta.touched && !!meta.error,
606
+ disabled: field.disabled,
607
+ "aria-label": field.label
608
+ }
609
+ ),
610
+ field.type === "time" && /* @__PURE__ */ jsx(
611
+ TimePicker,
612
+ {
613
+ ...formField,
614
+ id: fieldId,
615
+ placeholder: field.placeholder,
616
+ error: meta.touched && !!meta.error,
617
+ disabled: field.disabled,
618
+ "aria-label": field.label
619
+ }
620
+ ),
621
+ field.type === "file" && /* @__PURE__ */ jsx(
622
+ FileInput,
623
+ {
624
+ ...formField,
625
+ id: fieldId,
626
+ accept: field.accept,
627
+ maxSize: field.maxSize || 5 * 1024 * 1024,
628
+ maxFiles: field.maxFiles || 1,
629
+ multiple: field.multiple || false,
630
+ placeholder: field.placeholder || "Choose file(s)...",
631
+ error: meta.touched && !!meta.error,
632
+ disabled: field.disabled || isUploading,
633
+ showProgress: true,
634
+ uploadProgress,
635
+ onChange: (files) => {
636
+ formField.onChange(files);
637
+ if (files.length > 0 && onFileUpload) {
638
+ onFileUpload(files);
639
+ }
640
+ },
641
+ onFileRemove,
642
+ "aria-label": field.label
643
+ }
644
+ ),
645
+ field.type === "rich-text" && /* @__PURE__ */ jsx(
646
+ RichTextEditor,
647
+ {
648
+ ...formField,
649
+ id: fieldId,
650
+ placeholder: field.placeholder,
651
+ error: meta.touched && !!meta.error,
652
+ disabled: field.disabled,
653
+ "aria-label": field.label
654
+ }
655
+ )
656
+ ] })
588
657
  }
589
658
  );
590
659
  }
591
- function Label({
592
- className,
593
- ...props
594
- }) {
595
- return /* @__PURE__ */ jsx(
596
- LabelPrimitive.Root,
597
- {
598
- "data-slot": "label",
599
- className: cn(
600
- "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",
601
- className
602
- ),
603
- ...props
604
- }
660
+
661
+ // lib/form-field-types.ts
662
+ function generateInitialValues(fields) {
663
+ return fields.reduce(
664
+ (acc, field) => {
665
+ if (field.type === "checkbox") {
666
+ acc[field.name] = false;
667
+ } else if (field.type === "checkbox-group" || field.type === "multi-select") {
668
+ acc[field.name] = [];
669
+ } else if (field.type === "file") {
670
+ acc[field.name] = [];
671
+ } else if (field.type === "date-range") {
672
+ acc[field.name] = { start: null, end: null };
673
+ } else {
674
+ acc[field.name] = "";
675
+ }
676
+ return acc;
677
+ },
678
+ {}
605
679
  );
606
680
  }
607
- function Separator({
608
- className,
609
- orientation = "horizontal",
610
- decorative = true,
611
- ...props
612
- }) {
613
- return /* @__PURE__ */ jsx(
614
- SeparatorPrimitive.Root,
615
- {
616
- "data-slot": "separator",
617
- decorative,
618
- orientation,
619
- className: cn(
620
- "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
621
- className
622
- ),
623
- ...props
624
- }
681
+ function generateValidationSchema(fields) {
682
+ return fields.reduce(
683
+ (acc, field) => {
684
+ acc[field.name] = (value, allValues) => {
685
+ if (field.required) {
686
+ if (!value || typeof value === "string" && !value.trim()) {
687
+ return `${field.label} is required`;
688
+ }
689
+ }
690
+ if (field.type === "email" && value) {
691
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
692
+ return "Please enter a valid email address";
693
+ }
694
+ }
695
+ if (field.type === "url" && value) {
696
+ try {
697
+ new URL(value);
698
+ } catch {
699
+ return "Please enter a valid URL";
700
+ }
701
+ }
702
+ if (field.validator) {
703
+ return field.validator(value, allValues);
704
+ }
705
+ return void 0;
706
+ };
707
+ return acc;
708
+ },
709
+ {}
625
710
  );
626
711
  }
712
+ function getColumnSpanClass(span) {
713
+ if (!span || span === 12) return "col-span-12";
714
+ return `col-span-12 sm:col-span-${Math.min(span, 12)}`;
715
+ }
716
+ function useContactForm(options) {
717
+ const {
718
+ formFields,
719
+ formConfig,
720
+ onSubmit,
721
+ onSuccess,
722
+ onError,
723
+ resetOnSuccess = true,
724
+ uploadTokens = []
725
+ } = options;
726
+ const [submissionError, setSubmissionError] = useState(null);
727
+ const submissionConfig = formConfig?.submissionConfig;
728
+ const redirectUrl = submissionConfig?.redirectUrl;
729
+ const redirectNavigation = useNavigation({ href: redirectUrl });
730
+ const resetSubmissionState = useCallback(() => {
731
+ setSubmissionError(null);
732
+ }, []);
733
+ const performRedirect = useCallback(() => {
734
+ if (!redirectUrl || typeof window === "undefined") {
735
+ return;
736
+ }
737
+ const navigate = () => {
738
+ if (redirectNavigation.shouldUseRouter && redirectNavigation.normalizedHref) {
739
+ const handler = window.__opensiteNavigationHandler;
740
+ if (typeof handler === "function") {
741
+ try {
742
+ const handled = handler(redirectNavigation.normalizedHref, void 0);
743
+ if (handled !== false) {
744
+ return;
745
+ }
746
+ } catch (error) {
747
+ console.error("Internal redirect handler failed:", error);
748
+ }
749
+ }
750
+ }
751
+ const destination = redirectNavigation.normalizedHref || redirectUrl;
752
+ window.location.assign(destination);
753
+ };
754
+ window.setTimeout(navigate, 150);
755
+ }, [redirectNavigation, redirectUrl]);
756
+ const form = useForm({
757
+ initialValues: useMemo(
758
+ () => generateInitialValues(formFields),
759
+ [formFields]
760
+ ),
761
+ validationSchema: useMemo(
762
+ () => generateValidationSchema(formFields),
763
+ [formFields]
764
+ ),
765
+ onSubmit: async (values, helpers) => {
766
+ resetSubmissionState();
767
+ const shouldAutoSubmit = Boolean(formConfig?.endpoint);
768
+ if (!shouldAutoSubmit && !onSubmit) {
769
+ return;
770
+ }
771
+ try {
772
+ let result;
773
+ const submissionValues = {
774
+ ...values,
775
+ ...uploadTokens.length > 0 && {
776
+ contact_form_upload_tokens: uploadTokens
777
+ }
778
+ };
779
+ if (shouldAutoSubmit) {
780
+ result = await submitPageSpeedForm(submissionValues, formConfig);
781
+ }
782
+ if (onSubmit) {
783
+ await onSubmit(submissionValues);
784
+ }
785
+ if (shouldAutoSubmit || onSubmit) {
786
+ try {
787
+ await submissionConfig?.handleFormSubmission?.({
788
+ formData: submissionValues,
789
+ responseData: result
790
+ });
791
+ } catch (callbackError) {
792
+ console.error("handleFormSubmission callback failed:", callbackError);
793
+ }
794
+ if (resetOnSuccess) {
795
+ helpers.resetForm();
796
+ }
797
+ onSuccess?.(result);
798
+ if (submissionConfig?.behavior === "redirect" && submissionConfig.redirectUrl) {
799
+ performRedirect();
800
+ }
801
+ }
802
+ } catch (error) {
803
+ if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
804
+ helpers.setErrors(error.formErrors);
805
+ }
806
+ const errorMessage = error instanceof Error ? error.message : "Form submission failed";
807
+ setSubmissionError(errorMessage);
808
+ onError?.(error);
809
+ }
810
+ }
811
+ });
812
+ const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
813
+ return {
814
+ form,
815
+ isSubmitted: form.status === "success",
816
+ submissionError,
817
+ formMethod,
818
+ resetSubmissionState
819
+ };
820
+ }
821
+
822
+ // lib/forms.ts
627
823
  var PageSpeedFormSubmissionError = class extends Error {
628
824
  constructor(message, options = {}) {
629
825
  super(message);
@@ -1135,19 +1331,19 @@ var SERVICE_STYLES = [
1135
1331
  { value: "cocktail", label: "Cocktail", description: "Passed appetizers" }
1136
1332
  ];
1137
1333
  var CUISINES = [
1138
- { id: "american", label: "American" },
1139
- { id: "italian", label: "Italian" },
1140
- { id: "asian", label: "Asian Fusion" },
1141
- { id: "mexican", label: "Mexican" },
1142
- { id: "mediterranean", label: "Mediterranean" },
1143
- { id: "bbq", label: "BBQ" }
1334
+ { value: "american", label: "American" },
1335
+ { value: "italian", label: "Italian" },
1336
+ { value: "asian", label: "Asian Fusion" },
1337
+ { value: "mexican", label: "Mexican" },
1338
+ { value: "mediterranean", label: "Mediterranean" },
1339
+ { value: "bbq", label: "BBQ" }
1144
1340
  ];
1145
1341
  var DIETARY_OPTIONS = [
1146
- { id: "vegetarian", label: "Vegetarian options" },
1147
- { id: "vegan", label: "Vegan options" },
1148
- { id: "gluten-free", label: "Gluten-free options" },
1149
- { id: "kosher", label: "Kosher" },
1150
- { id: "halal", label: "Halal" }
1342
+ { value: "vegetarian", label: "Vegetarian options" },
1343
+ { value: "vegan", label: "Vegan options" },
1344
+ { value: "gluten-free", label: "Gluten-free options" },
1345
+ { value: "kosher", label: "Kosher" },
1346
+ { value: "halal", label: "Halal" }
1151
1347
  ];
1152
1348
  var GUEST_COUNTS = [
1153
1349
  { value: "10-25", label: "10-25 guests" },
@@ -1167,13 +1363,124 @@ var BUDGET_RANGES = [
1167
1363
  { value: "100-150", label: "$100-150 / person" },
1168
1364
  { value: "150+", label: "$150+ / person" }
1169
1365
  ];
1366
+ var DEFAULT_FORM_FIELDS = [
1367
+ {
1368
+ name: "eventType",
1369
+ type: "select",
1370
+ label: "Event Type",
1371
+ placeholder: "Select event type",
1372
+ required: true,
1373
+ columnSpan: 6,
1374
+ options: EVENT_TYPES
1375
+ },
1376
+ {
1377
+ name: "eventDate",
1378
+ type: "date",
1379
+ label: "Event Date",
1380
+ placeholder: "Select date",
1381
+ required: true,
1382
+ columnSpan: 6
1383
+ },
1384
+ {
1385
+ name: "guestCount",
1386
+ type: "select",
1387
+ label: "Number of Guests",
1388
+ placeholder: "Select guest count",
1389
+ required: true,
1390
+ columnSpan: 6,
1391
+ options: GUEST_COUNTS
1392
+ },
1393
+ {
1394
+ name: "budget",
1395
+ type: "select",
1396
+ label: "Budget Per Person",
1397
+ placeholder: "Select budget range",
1398
+ required: false,
1399
+ columnSpan: 6,
1400
+ options: BUDGET_RANGES
1401
+ },
1402
+ {
1403
+ name: "serviceStyle",
1404
+ type: "radio",
1405
+ label: "Service Style",
1406
+ required: true,
1407
+ columnSpan: 12,
1408
+ options: SERVICE_STYLES
1409
+ },
1410
+ {
1411
+ name: "cuisinePreferences",
1412
+ type: "checkbox-group",
1413
+ label: "Cuisine Preferences",
1414
+ required: false,
1415
+ columnSpan: 12,
1416
+ options: CUISINES
1417
+ },
1418
+ {
1419
+ name: "dietaryAccommodations",
1420
+ type: "checkbox-group",
1421
+ label: "Dietary Accommodations",
1422
+ required: false,
1423
+ columnSpan: 12,
1424
+ options: DIETARY_OPTIONS
1425
+ },
1426
+ {
1427
+ name: "name",
1428
+ type: "text",
1429
+ label: "Full Name",
1430
+ placeholder: "John Doe",
1431
+ required: true,
1432
+ columnSpan: 12
1433
+ },
1434
+ {
1435
+ name: "email",
1436
+ type: "email",
1437
+ label: "Email Address",
1438
+ placeholder: "john@example.com",
1439
+ required: true,
1440
+ columnSpan: 6
1441
+ },
1442
+ {
1443
+ name: "phone",
1444
+ type: "tel",
1445
+ label: "Phone Number",
1446
+ placeholder: "+1 (555) 000-0000",
1447
+ required: true,
1448
+ columnSpan: 6
1449
+ },
1450
+ {
1451
+ name: "venue",
1452
+ type: "text",
1453
+ label: "Venue / Location",
1454
+ placeholder: "Event venue or address",
1455
+ required: false,
1456
+ columnSpan: 12
1457
+ },
1458
+ {
1459
+ name: "details",
1460
+ type: "textarea",
1461
+ label: "Additional Details",
1462
+ placeholder: "Tell us more about your event...",
1463
+ required: false,
1464
+ rows: 4,
1465
+ columnSpan: 12
1466
+ },
1467
+ {
1468
+ name: "tasting",
1469
+ type: "checkbox",
1470
+ label: "I'm interested in scheduling a tasting",
1471
+ required: false,
1472
+ columnSpan: 12
1473
+ }
1474
+ ];
1170
1475
  function ContactCatering({
1171
1476
  heading,
1172
1477
  description,
1173
- buttonText,
1478
+ buttonText = "Request Quote",
1174
1479
  buttonIcon,
1175
1480
  actions,
1176
1481
  actionsSlot,
1482
+ formFields,
1483
+ successMessage = "Thank you for your inquiry! We'll get back to you within 24 hours with a custom proposal.",
1177
1484
  className,
1178
1485
  spacing = "py-8 md:py-32",
1179
1486
  containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
@@ -1183,6 +1490,8 @@ function ContactCatering({
1183
1490
  cardClassName,
1184
1491
  cardContentClassName,
1185
1492
  formClassName,
1493
+ successMessageClassName,
1494
+ errorMessageClassName,
1186
1495
  submitClassName,
1187
1496
  background,
1188
1497
  pattern,
@@ -1192,76 +1501,17 @@ function ContactCatering({
1192
1501
  onSuccess,
1193
1502
  onError
1194
1503
  }) {
1195
- const form = useForm({
1196
- initialValues: {
1197
- eventType: "",
1198
- eventDate: "",
1199
- guestCount: "",
1200
- startTime: "",
1201
- endTime: "",
1202
- venue: "",
1203
- serviceStyle: "buffet",
1204
- cuisinePreferences: [],
1205
- dietaryAccommodations: [],
1206
- budget: "",
1207
- name: "",
1208
- phone: "",
1209
- email: "",
1210
- details: "",
1211
- tasting: false
1212
- },
1213
- validationSchema: {
1214
- eventType: (value) => !value ? "Please select an event type" : void 0,
1215
- eventDate: (value) => !value ? "Event date is required" : void 0,
1216
- guestCount: (value) => !value ? "Please select guest count" : void 0,
1217
- name: (value) => !value ? "Name is required" : void 0,
1218
- email: (value) => {
1219
- if (!value) return "Email is required";
1220
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
1221
- return "Please enter a valid email address";
1222
- return void 0;
1223
- },
1224
- phone: (value) => !value ? "Phone number is required" : void 0
1225
- },
1226
- onSubmit: async (values, helpers) => {
1227
- const shouldAutoSubmit = Boolean(formConfig?.endpoint);
1228
- if (!shouldAutoSubmit && !onSubmit) {
1229
- return;
1230
- }
1231
- try {
1232
- let result;
1233
- if (shouldAutoSubmit) {
1234
- result = await submitPageSpeedForm(values, formConfig);
1235
- }
1236
- if (onSubmit) {
1237
- await onSubmit(values);
1238
- }
1239
- if (shouldAutoSubmit || onSubmit) {
1240
- if (formConfig?.resetOnSuccess !== false) {
1241
- helpers.resetForm();
1242
- }
1243
- onSuccess?.(result);
1244
- }
1245
- } catch (error) {
1246
- if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
1247
- helpers.setErrors(error.formErrors);
1248
- }
1249
- onError?.(error);
1250
- throw error;
1251
- }
1252
- }
1504
+ const fields = useMemo(
1505
+ () => formFields || DEFAULT_FORM_FIELDS,
1506
+ [formFields]
1507
+ );
1508
+ const { form, submissionError, formMethod, resetSubmissionState } = useContactForm({
1509
+ formFields: fields,
1510
+ formConfig,
1511
+ onSubmit,
1512
+ onSuccess,
1513
+ onError
1253
1514
  });
1254
- const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
1255
- const toggleCuisinePreference = (value) => {
1256
- const current = form.values.cuisinePreferences;
1257
- const updated = current.includes(value) ? current.filter((v) => v !== value) : [...current, value];
1258
- form.setFieldValue("cuisinePreferences", updated);
1259
- };
1260
- const toggleDietaryAccommodation = (value) => {
1261
- const current = form.values.dietaryAccommodations;
1262
- const updated = current.includes(value) ? current.filter((v) => v !== value) : [...current, value];
1263
- form.setFieldValue("dietaryAccommodations", updated);
1264
- };
1265
1515
  const actionsContent = React.useMemo(() => {
1266
1516
  if (actionsSlot) return actionsSlot;
1267
1517
  if (actions && actions.length > 0) {
@@ -1330,256 +1580,22 @@ function ContactCatering({
1330
1580
  form,
1331
1581
  action: formConfig?.endpoint,
1332
1582
  method: formMethod,
1333
- className: cn("space-y-8", formClassName),
1583
+ submissionError,
1584
+ successMessage,
1585
+ successMessageClassName,
1586
+ errorMessageClassName,
1587
+ submissionConfig: formConfig?.submissionConfig,
1588
+ onNewSubmission: resetSubmissionState,
1589
+ className: cn("space-y-6", formClassName),
1334
1590
  children: [
1335
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1336
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: "Event Details" }),
1337
- /* @__PURE__ */ jsx(Field, { name: "eventType", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1338
- /* @__PURE__ */ jsx(Label, { htmlFor: "event-type", children: "Event Type" }),
1339
- /* @__PURE__ */ jsxs(
1340
- Select,
1341
- {
1342
- ...field,
1343
- id: "event-type",
1344
- error: meta.touched && !!meta.error,
1345
- "aria-label": "Event Type",
1346
- children: [
1347
- /* @__PURE__ */ jsx("option", { value: "", children: "Select event type" }),
1348
- EVENT_TYPES.map((type) => /* @__PURE__ */ jsx("option", { value: type.value, children: type.label }, type.value))
1349
- ]
1350
- }
1351
- )
1352
- ] }) }),
1353
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1354
- /* @__PURE__ */ jsx(Field, { name: "guestCount", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1355
- /* @__PURE__ */ jsx(Label, { htmlFor: "guest-count", children: "Number of Guests" }),
1356
- /* @__PURE__ */ jsxs(
1357
- Select,
1358
- {
1359
- ...field,
1360
- id: "guest-count",
1361
- error: meta.touched && !!meta.error,
1362
- "aria-label": "Number of Guests",
1363
- children: [
1364
- /* @__PURE__ */ jsx("option", { value: "", children: "Select guest count" }),
1365
- GUEST_COUNTS.map((count) => /* @__PURE__ */ jsx("option", { value: count.value, children: count.label }, count.value))
1366
- ]
1367
- }
1368
- )
1369
- ] }) }),
1370
- /* @__PURE__ */ jsx(Field, { name: "eventDate", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1371
- /* @__PURE__ */ jsx(Label, { htmlFor: "event-date", children: "Event Date" }),
1372
- /* @__PURE__ */ jsx(
1373
- TextInput,
1374
- {
1375
- ...field,
1376
- id: "event-date",
1377
- type: "date",
1378
- error: meta.touched && !!meta.error,
1379
- "aria-label": "Event Date"
1380
- }
1381
- )
1382
- ] }) })
1383
- ] }),
1384
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1385
- /* @__PURE__ */ jsx(Field, { name: "startTime", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1386
- /* @__PURE__ */ jsx(Label, { htmlFor: "start-time", children: "Start Time" }),
1387
- /* @__PURE__ */ jsx(
1388
- TextInput,
1389
- {
1390
- ...field,
1391
- id: "start-time",
1392
- type: "time",
1393
- error: meta.touched && !!meta.error,
1394
- "aria-label": "Start Time"
1395
- }
1396
- )
1397
- ] }) }),
1398
- /* @__PURE__ */ jsx(Field, { name: "endTime", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1399
- /* @__PURE__ */ jsx(Label, { htmlFor: "end-time", children: "End Time (Optional)" }),
1400
- /* @__PURE__ */ jsx(
1401
- TextInput,
1402
- {
1403
- ...field,
1404
- id: "end-time",
1405
- type: "time",
1406
- error: meta.touched && !!meta.error,
1407
- "aria-label": "End Time"
1408
- }
1409
- )
1410
- ] }) })
1411
- ] }),
1412
- /* @__PURE__ */ jsx(Field, { name: "venue", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1413
- /* @__PURE__ */ jsx(Label, { htmlFor: "venue", children: "Venue / Location" }),
1414
- /* @__PURE__ */ jsx(
1415
- TextInput,
1416
- {
1417
- ...field,
1418
- id: "venue",
1419
- placeholder: "Event venue or location",
1420
- error: meta.touched && !!meta.error,
1421
- "aria-label": "Venue"
1422
- }
1423
- )
1424
- ] }) })
1425
- ] }),
1426
- /* @__PURE__ */ jsx(Separator, {}),
1427
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1428
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: "Service Preferences" }),
1429
- /* @__PURE__ */ jsx(Field, { name: "serviceStyle", children: ({ field }) => /* @__PURE__ */ jsx(
1430
- Radio,
1431
- {
1432
- name: "serviceStyle",
1433
- label: "Service Style",
1434
- value: field.value,
1435
- onChange: field.onChange,
1436
- options: SERVICE_STYLES,
1437
- layout: "stacked",
1438
- className: "space-y-2"
1439
- }
1440
- ) }),
1441
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1442
- /* @__PURE__ */ jsx(Label, { children: "Cuisine Preferences (Optional)" }),
1443
- /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: CUISINES.map((cuisine) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1444
- /* @__PURE__ */ jsx(
1445
- Checkbox,
1446
- {
1447
- id: cuisine.id,
1448
- checked: form.values.cuisinePreferences.includes(
1449
- cuisine.id
1450
- ),
1451
- onCheckedChange: () => toggleCuisinePreference(cuisine.id)
1452
- }
1453
- ),
1454
- /* @__PURE__ */ jsx(
1455
- Label,
1456
- {
1457
- htmlFor: cuisine.id,
1458
- className: "cursor-pointer font-normal",
1459
- children: cuisine.label
1460
- }
1461
- )
1462
- ] }, cuisine.id)) })
1463
- ] }),
1464
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1465
- /* @__PURE__ */ jsx(Label, { children: "Dietary Accommodations (Optional)" }),
1466
- /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: DIETARY_OPTIONS.map((option) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1467
- /* @__PURE__ */ jsx(
1468
- Checkbox,
1469
- {
1470
- id: option.id,
1471
- checked: form.values.dietaryAccommodations.includes(
1472
- option.id
1473
- ),
1474
- onCheckedChange: () => toggleDietaryAccommodation(option.id)
1475
- }
1476
- ),
1477
- /* @__PURE__ */ jsx(
1478
- Label,
1479
- {
1480
- htmlFor: option.id,
1481
- className: "cursor-pointer font-normal",
1482
- children: option.label
1483
- }
1484
- )
1485
- ] }, option.id)) })
1486
- ] }),
1487
- /* @__PURE__ */ jsx(Field, { name: "budget", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1488
- /* @__PURE__ */ jsx(Label, { htmlFor: "budget", children: "Budget Per Person (Optional)" }),
1489
- /* @__PURE__ */ jsxs(
1490
- Select,
1491
- {
1492
- ...field,
1493
- id: "budget",
1494
- error: meta.touched && !!meta.error,
1495
- "aria-label": "Budget Per Person",
1496
- children: [
1497
- /* @__PURE__ */ jsx("option", { value: "", children: "Select budget range" }),
1498
- BUDGET_RANGES.map((range) => /* @__PURE__ */ jsx("option", { value: range.value, children: range.label }, range.value))
1499
- ]
1500
- }
1501
- )
1502
- ] }) })
1503
- ] }),
1504
- /* @__PURE__ */ jsx(Separator, {}),
1505
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1506
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: "Your Information" }),
1507
- /* @__PURE__ */ jsx(Field, { name: "name", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1508
- /* @__PURE__ */ jsx(Label, { htmlFor: "name", children: "Full Name" }),
1509
- /* @__PURE__ */ jsx(
1510
- TextInput,
1511
- {
1512
- ...field,
1513
- id: "name",
1514
- placeholder: "John Doe",
1515
- error: meta.touched && !!meta.error,
1516
- "aria-label": "Full Name"
1517
- }
1518
- )
1519
- ] }) }),
1520
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1521
- /* @__PURE__ */ jsx(Field, { name: "email", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1522
- /* @__PURE__ */ jsx(Label, { htmlFor: "email", children: "Email" }),
1523
- /* @__PURE__ */ jsx(
1524
- TextInput,
1525
- {
1526
- ...field,
1527
- id: "email",
1528
- type: "email",
1529
- placeholder: "john@example.com",
1530
- error: meta.touched && !!meta.error,
1531
- "aria-label": "Email"
1532
- }
1533
- )
1534
- ] }) }),
1535
- /* @__PURE__ */ jsx(Field, { name: "phone", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1536
- /* @__PURE__ */ jsx(Label, { htmlFor: "phone", children: "Phone" }),
1537
- /* @__PURE__ */ jsx(
1538
- TextInput,
1539
- {
1540
- ...field,
1541
- id: "phone",
1542
- type: "tel",
1543
- placeholder: "+1 (555) 000-0000",
1544
- error: meta.touched && !!meta.error,
1545
- "aria-label": "Phone"
1546
- }
1547
- )
1548
- ] }) })
1549
- ] }),
1550
- /* @__PURE__ */ jsx(Field, { name: "details", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1551
- /* @__PURE__ */ jsx(Label, { htmlFor: "details", children: "Additional Details (Optional)" }),
1552
- /* @__PURE__ */ jsx(
1553
- TextArea,
1554
- {
1555
- ...field,
1556
- id: "details",
1557
- placeholder: "Tell us about your menu preferences, budget, or any special requirements...",
1558
- rows: 4,
1559
- error: meta.touched && !!meta.error,
1560
- "aria-label": "Additional Details"
1561
- }
1562
- )
1563
- ] }) }),
1564
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1565
- /* @__PURE__ */ jsx(
1566
- Checkbox,
1567
- {
1568
- id: "tasting",
1569
- checked: form.values.tasting,
1570
- onCheckedChange: (checked) => form.setFieldValue("tasting", checked === true)
1571
- }
1572
- ),
1573
- /* @__PURE__ */ jsx(
1574
- Label,
1575
- {
1576
- htmlFor: "tasting",
1577
- className: "cursor-pointer font-normal",
1578
- children: "I'm interested in scheduling a tasting"
1579
- }
1580
- )
1581
- ] })
1582
- ] }),
1591
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-12 gap-6", children: fields.map((field) => /* @__PURE__ */ jsx(
1592
+ "div",
1593
+ {
1594
+ className: getColumnSpanClass(field.columnSpan),
1595
+ children: /* @__PURE__ */ jsx(DynamicFormField, { field })
1596
+ },
1597
+ field.name
1598
+ )) }),
1583
1599
  actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxs(
1584
1600
  Pressable,
1585
1601
  {
@@ -1589,14 +1605,7 @@ function ContactCatering({
1589
1605
  asButton: true,
1590
1606
  disabled: form.isSubmitting,
1591
1607
  children: [
1592
- buttonIcon ?? /* @__PURE__ */ jsx(
1593
- DynamicIcon,
1594
- {
1595
- name: "lucide/utensils",
1596
- size: 16,
1597
- className: "mr-2"
1598
- }
1599
- ),
1608
+ buttonIcon,
1600
1609
  buttonText
1601
1610
  ]
1602
1611
  }