@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,14 +3,11 @@
3
3
 
4
4
  var React = require('react');
5
5
  var forms = require('@page-speed/forms');
6
- var upload = require('@page-speed/forms/upload');
7
- var inputs = require('@page-speed/forms/inputs');
8
6
  var clsx = require('clsx');
9
7
  var tailwindMerge = require('tailwind-merge');
10
8
  var classVarianceAuthority = require('class-variance-authority');
11
9
  var jsxRuntime = require('react/jsx-runtime');
12
- var LabelPrimitive = require('@radix-ui/react-label');
13
- var SeparatorPrimitive = require('@radix-ui/react-separator');
10
+ var inputs = require('@page-speed/forms/inputs');
14
11
  var integration = require('@page-speed/forms/integration');
15
12
 
16
13
  function _interopNamespace(e) {
@@ -32,12 +29,8 @@ function _interopNamespace(e) {
32
29
  }
33
30
 
34
31
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
35
- var LabelPrimitive__namespace = /*#__PURE__*/_interopNamespace(LabelPrimitive);
36
- var SeparatorPrimitive__namespace = /*#__PURE__*/_interopNamespace(SeparatorPrimitive);
37
32
 
38
33
  // components/blocks/contact/contact-careers.tsx
39
- var TextInput = inputs.TextInput;
40
- var TextArea = inputs.TextArea;
41
34
  function cn(...inputs) {
42
35
  return tailwindMerge.twMerge(clsx.clsx(inputs));
43
36
  }
@@ -384,6 +377,7 @@ var Pressable = React__namespace.forwardRef(
384
377
  rel,
385
378
  linkType,
386
379
  isInternal,
380
+ isExternal,
387
381
  handleClick
388
382
  } = navigation;
389
383
  const shouldRenderLink = normalizedHref && linkType !== "none";
@@ -459,111 +453,6 @@ var Pressable = React__namespace.forwardRef(
459
453
  }
460
454
  );
461
455
  Pressable.displayName = "Pressable";
462
- var svgCache = /* @__PURE__ */ new Map();
463
- function DynamicIcon({
464
- name,
465
- size = 28,
466
- color,
467
- className,
468
- alt
469
- }) {
470
- const [svgContent, setSvgContent] = React__namespace.useState(null);
471
- const [isLoading, setIsLoading] = React__namespace.useState(true);
472
- const [error, setError] = React__namespace.useState(null);
473
- const { url, iconName } = React__namespace.useMemo(() => {
474
- const separator = name.includes("/") ? "/" : ":";
475
- const [prefix, iconName2] = name.split(separator);
476
- const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}&key=au382bi7fsh96w9h9xlrnat2jglx`;
477
- return {
478
- url: baseUrl,
479
- iconName: iconName2
480
- };
481
- }, [name, size]);
482
- React__namespace.useEffect(() => {
483
- let isMounted = true;
484
- const fetchSvg = async () => {
485
- const cached = svgCache.get(url);
486
- if (cached) {
487
- if (isMounted) {
488
- setSvgContent(cached);
489
- setIsLoading(false);
490
- }
491
- return;
492
- }
493
- try {
494
- setIsLoading(true);
495
- setError(null);
496
- const response = await fetch(url);
497
- if (!response.ok) {
498
- throw new Error(`Failed to fetch icon: ${response.status}`);
499
- }
500
- let svg = await response.text();
501
- svg = processSvgForCurrentColor(svg);
502
- svgCache.set(url, svg);
503
- if (isMounted) {
504
- setSvgContent(svg);
505
- setIsLoading(false);
506
- }
507
- } catch (err) {
508
- if (isMounted) {
509
- setError(err instanceof Error ? err.message : "Failed to load icon");
510
- setIsLoading(false);
511
- }
512
- }
513
- };
514
- fetchSvg();
515
- return () => {
516
- isMounted = false;
517
- };
518
- }, [url]);
519
- if (isLoading) {
520
- return /* @__PURE__ */ jsxRuntime.jsx(
521
- "span",
522
- {
523
- className: cn("inline-block", className),
524
- style: { width: size, height: size },
525
- "aria-hidden": "true"
526
- }
527
- );
528
- }
529
- if (error || !svgContent) {
530
- return /* @__PURE__ */ jsxRuntime.jsx(
531
- "span",
532
- {
533
- className: cn("inline-block", className),
534
- style: { width: size, height: size },
535
- role: "img",
536
- "aria-label": alt || iconName
537
- }
538
- );
539
- }
540
- return /* @__PURE__ */ jsxRuntime.jsx(
541
- "span",
542
- {
543
- className: cn("inline-flex items-center justify-center", className),
544
- style: {
545
- width: size,
546
- height: size,
547
- color: color || "inherit"
548
- },
549
- role: "img",
550
- "aria-label": alt || iconName,
551
- dangerouslySetInnerHTML: { __html: svgContent }
552
- }
553
- );
554
- }
555
- function processSvgForCurrentColor(svg) {
556
- let processed = svg;
557
- processed = processed.replace(
558
- /stroke=["'](#000000|#000|black)["']/gi,
559
- 'stroke="currentColor"'
560
- );
561
- processed = processed.replace(
562
- /fill=["'](#000000|#000|black)["']/gi,
563
- 'fill="currentColor"'
564
- );
565
- return processed;
566
- }
567
456
  function Card({ className, ...props }) {
568
457
  return /* @__PURE__ */ jsxRuntime.jsx(
569
458
  "div",
@@ -587,58 +476,435 @@ function CardContent({ className, ...props }) {
587
476
  }
588
477
  );
589
478
  }
590
- function Label({
479
+ function DynamicFormField({
480
+ field,
591
481
  className,
592
- ...props
482
+ uploadProgress = {},
483
+ onFileUpload,
484
+ onFileRemove,
485
+ isUploading = false
593
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
+ ] });
594
495
  return /* @__PURE__ */ jsxRuntime.jsx(
595
- LabelPrimitive__namespace.Root,
496
+ forms.Field,
596
497
  {
597
- "data-slot": "label",
598
- className: cn(
599
- "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",
600
- className
601
- ),
602
- ...props
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
+ ] })
603
678
  }
604
679
  );
605
680
  }
606
- function Input({ className, type, ...props }) {
607
- return /* @__PURE__ */ jsxRuntime.jsx(
608
- "input",
609
- {
610
- type,
611
- "data-slot": "input",
612
- className: cn(
613
- "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
614
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
615
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
616
- className
617
- ),
618
- ...props
619
- }
681
+
682
+ // lib/form-field-types.ts
683
+ function generateInitialValues(fields) {
684
+ return fields.reduce(
685
+ (acc, field) => {
686
+ if (field.type === "checkbox") {
687
+ acc[field.name] = false;
688
+ } else if (field.type === "checkbox-group" || field.type === "multi-select") {
689
+ acc[field.name] = [];
690
+ } else if (field.type === "file") {
691
+ acc[field.name] = [];
692
+ } else if (field.type === "date-range") {
693
+ acc[field.name] = { start: null, end: null };
694
+ } else {
695
+ acc[field.name] = "";
696
+ }
697
+ return acc;
698
+ },
699
+ {}
620
700
  );
621
701
  }
622
- function Separator({
623
- className,
624
- orientation = "horizontal",
625
- decorative = true,
626
- ...props
627
- }) {
628
- return /* @__PURE__ */ jsxRuntime.jsx(
629
- SeparatorPrimitive__namespace.Root,
630
- {
631
- "data-slot": "separator",
632
- decorative,
633
- orientation,
634
- className: cn(
635
- "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
636
- className
637
- ),
638
- ...props
639
- }
702
+ function generateValidationSchema(fields) {
703
+ return fields.reduce(
704
+ (acc, field) => {
705
+ acc[field.name] = (value, allValues) => {
706
+ if (field.required) {
707
+ if (!value || typeof value === "string" && !value.trim()) {
708
+ return `${field.label} is required`;
709
+ }
710
+ }
711
+ if (field.type === "email" && value) {
712
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
713
+ return "Please enter a valid email address";
714
+ }
715
+ }
716
+ if (field.type === "url" && value) {
717
+ try {
718
+ new URL(value);
719
+ } catch {
720
+ return "Please enter a valid URL";
721
+ }
722
+ }
723
+ if (field.validator) {
724
+ return field.validator(value, allValues);
725
+ }
726
+ return void 0;
727
+ };
728
+ return acc;
729
+ },
730
+ {}
731
+ );
732
+ }
733
+ function getColumnSpanClass(span) {
734
+ if (!span || span === 12) return "col-span-12";
735
+ return `col-span-12 sm:col-span-${Math.min(span, 12)}`;
736
+ }
737
+ function useFileUpload(options) {
738
+ const [uploadTokens, setUploadTokens] = React.useState([]);
739
+ const [uploadProgress, setUploadProgress] = React.useState({});
740
+ const [isUploading, setIsUploading] = React.useState(false);
741
+ const endpoint = options?.endpoint || "https://api.dashtrack.com/contacts/_/contact_form_uploads";
742
+ const uploadFiles = React.useCallback(
743
+ async (files) => {
744
+ if (files.length === 0) return;
745
+ setIsUploading(true);
746
+ try {
747
+ const tokens = [];
748
+ for (const file of files) {
749
+ const formData = new FormData();
750
+ formData.append("contact_form_upload[file_upload]", file);
751
+ formData.append("contact_form_upload[title]", file.name);
752
+ formData.append("contact_form_upload[file_name]", file.name);
753
+ formData.append("contact_form_upload[file_size]", String(file.size));
754
+ const response = await fetch(endpoint, {
755
+ method: "POST",
756
+ body: formData
757
+ });
758
+ if (!response.ok) {
759
+ throw new Error(`Upload failed: ${response.statusText}`);
760
+ }
761
+ const data = await response.json();
762
+ if (data.contact_form_upload?.token) {
763
+ tokens.push(`upload_${data.contact_form_upload.token}`);
764
+ }
765
+ setUploadProgress((prev) => ({
766
+ ...prev,
767
+ [file.name]: 100
768
+ }));
769
+ }
770
+ setUploadTokens(tokens);
771
+ } catch (error) {
772
+ console.error("File upload error:", error);
773
+ options?.onError?.(error);
774
+ } finally {
775
+ setIsUploading(false);
776
+ }
777
+ },
778
+ [endpoint, options]
640
779
  );
780
+ const removeFile = React.useCallback((file, index) => {
781
+ setUploadTokens((prev) => prev.filter((_, i) => i !== index));
782
+ setUploadProgress((prev) => {
783
+ const newProgress = { ...prev };
784
+ delete newProgress[file.name];
785
+ return newProgress;
786
+ });
787
+ }, []);
788
+ const resetUpload = React.useCallback(() => {
789
+ setUploadTokens([]);
790
+ setUploadProgress({});
791
+ }, []);
792
+ return {
793
+ uploadTokens,
794
+ uploadProgress,
795
+ isUploading,
796
+ uploadFiles,
797
+ removeFile,
798
+ resetUpload
799
+ };
800
+ }
801
+ function useContactForm(options) {
802
+ const {
803
+ formFields,
804
+ formConfig,
805
+ onSubmit,
806
+ onSuccess,
807
+ onError,
808
+ resetOnSuccess = true,
809
+ uploadTokens = []
810
+ } = options;
811
+ const [submissionError, setSubmissionError] = React.useState(null);
812
+ const submissionConfig = formConfig?.submissionConfig;
813
+ const redirectUrl = submissionConfig?.redirectUrl;
814
+ const redirectNavigation = useNavigation({ href: redirectUrl });
815
+ const resetSubmissionState = React.useCallback(() => {
816
+ setSubmissionError(null);
817
+ }, []);
818
+ const performRedirect = React.useCallback(() => {
819
+ if (!redirectUrl || typeof window === "undefined") {
820
+ return;
821
+ }
822
+ const navigate = () => {
823
+ if (redirectNavigation.shouldUseRouter && redirectNavigation.normalizedHref) {
824
+ const handler = window.__opensiteNavigationHandler;
825
+ if (typeof handler === "function") {
826
+ try {
827
+ const handled = handler(redirectNavigation.normalizedHref, void 0);
828
+ if (handled !== false) {
829
+ return;
830
+ }
831
+ } catch (error) {
832
+ console.error("Internal redirect handler failed:", error);
833
+ }
834
+ }
835
+ }
836
+ const destination = redirectNavigation.normalizedHref || redirectUrl;
837
+ window.location.assign(destination);
838
+ };
839
+ window.setTimeout(navigate, 150);
840
+ }, [redirectNavigation, redirectUrl]);
841
+ const form = forms.useForm({
842
+ initialValues: React.useMemo(
843
+ () => generateInitialValues(formFields),
844
+ [formFields]
845
+ ),
846
+ validationSchema: React.useMemo(
847
+ () => generateValidationSchema(formFields),
848
+ [formFields]
849
+ ),
850
+ onSubmit: async (values, helpers) => {
851
+ resetSubmissionState();
852
+ const shouldAutoSubmit = Boolean(formConfig?.endpoint);
853
+ if (!shouldAutoSubmit && !onSubmit) {
854
+ return;
855
+ }
856
+ try {
857
+ let result;
858
+ const submissionValues = {
859
+ ...values,
860
+ ...uploadTokens.length > 0 && {
861
+ contact_form_upload_tokens: uploadTokens
862
+ }
863
+ };
864
+ if (shouldAutoSubmit) {
865
+ result = await submitPageSpeedForm(submissionValues, formConfig);
866
+ }
867
+ if (onSubmit) {
868
+ await onSubmit(submissionValues);
869
+ }
870
+ if (shouldAutoSubmit || onSubmit) {
871
+ try {
872
+ await submissionConfig?.handleFormSubmission?.({
873
+ formData: submissionValues,
874
+ responseData: result
875
+ });
876
+ } catch (callbackError) {
877
+ console.error("handleFormSubmission callback failed:", callbackError);
878
+ }
879
+ if (resetOnSuccess) {
880
+ helpers.resetForm();
881
+ }
882
+ onSuccess?.(result);
883
+ if (submissionConfig?.behavior === "redirect" && submissionConfig.redirectUrl) {
884
+ performRedirect();
885
+ }
886
+ }
887
+ } catch (error) {
888
+ if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
889
+ helpers.setErrors(error.formErrors);
890
+ }
891
+ const errorMessage = error instanceof Error ? error.message : "Form submission failed";
892
+ setSubmissionError(errorMessage);
893
+ onError?.(error);
894
+ }
895
+ }
896
+ });
897
+ const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
898
+ return {
899
+ form,
900
+ isSubmitted: form.status === "success",
901
+ submissionError,
902
+ formMethod,
903
+ resetSubmissionState
904
+ };
641
905
  }
906
+
907
+ // lib/forms.ts
642
908
  var PageSpeedFormSubmissionError = class extends Error {
643
909
  constructor(message, options = {}) {
644
910
  super(message);
@@ -1129,141 +1395,169 @@ var Section = React__namespace.default.forwardRef(
1129
1395
  }
1130
1396
  );
1131
1397
  Section.displayName = "Section";
1132
- var POSITIONS = [
1133
- { value: "frontend", label: "Frontend Developer" },
1134
- { value: "backend", label: "Backend Developer" },
1135
- { value: "fullstack", label: "Full Stack Developer" },
1136
- { value: "designer", label: "Product Designer" },
1137
- { value: "pm", label: "Product Manager" },
1138
- { value: "marketing", label: "Marketing Manager" },
1139
- { value: "other", label: "Other" }
1140
- ];
1141
- var AVAILABILITY = [
1142
- { value: "immediately", label: "Immediately" },
1143
- { value: "2-weeks", label: "2 weeks notice" },
1144
- { value: "1-month", label: "1 month notice" },
1145
- { value: "flexible", label: "Flexible" }
1398
+ var DEFAULT_FORM_FIELDS = [
1399
+ {
1400
+ name: "position",
1401
+ type: "select",
1402
+ label: "Position Applying For",
1403
+ placeholder: "Select a position",
1404
+ required: true,
1405
+ columnSpan: 12,
1406
+ options: [
1407
+ { value: "frontend", label: "Frontend Developer" },
1408
+ { value: "backend", label: "Backend Developer" },
1409
+ { value: "fullstack", label: "Full Stack Developer" },
1410
+ { value: "designer", label: "Product Designer" },
1411
+ { value: "pm", label: "Product Manager" },
1412
+ { value: "marketing", label: "Marketing Manager" },
1413
+ { value: "other", label: "Other" }
1414
+ ]
1415
+ },
1416
+ {
1417
+ name: "firstName",
1418
+ type: "text",
1419
+ label: "First Name",
1420
+ placeholder: "John",
1421
+ required: true,
1422
+ columnSpan: 6
1423
+ },
1424
+ {
1425
+ name: "lastName",
1426
+ type: "text",
1427
+ label: "Last Name",
1428
+ placeholder: "Doe",
1429
+ required: true,
1430
+ columnSpan: 6
1431
+ },
1432
+ {
1433
+ name: "email",
1434
+ type: "email",
1435
+ label: "Email Address",
1436
+ placeholder: "john@example.com",
1437
+ required: true,
1438
+ columnSpan: 6
1439
+ },
1440
+ {
1441
+ name: "phone",
1442
+ type: "tel",
1443
+ label: "Phone Number",
1444
+ placeholder: "+1 (555) 000-0000",
1445
+ required: true,
1446
+ columnSpan: 6
1447
+ },
1448
+ {
1449
+ name: "linkedin",
1450
+ type: "url",
1451
+ label: "LinkedIn Profile",
1452
+ placeholder: "https://linkedin.com/in/yourprofile",
1453
+ required: false,
1454
+ columnSpan: 6
1455
+ },
1456
+ {
1457
+ name: "portfolio",
1458
+ type: "url",
1459
+ label: "Portfolio/Website",
1460
+ placeholder: "https://yourportfolio.com",
1461
+ required: false,
1462
+ columnSpan: 6
1463
+ },
1464
+ {
1465
+ name: "availability",
1466
+ type: "select",
1467
+ label: "Availability",
1468
+ placeholder: "Select your availability",
1469
+ required: true,
1470
+ columnSpan: 12,
1471
+ options: [
1472
+ { value: "immediately", label: "Immediately" },
1473
+ { value: "2-weeks", label: "2 weeks notice" },
1474
+ { value: "1-month", label: "1 month notice" },
1475
+ { value: "flexible", label: "Flexible" }
1476
+ ]
1477
+ },
1478
+ {
1479
+ name: "coverLetter",
1480
+ type: "textarea",
1481
+ label: "Cover Letter",
1482
+ placeholder: "Tell us why you'd be a great fit for this position...",
1483
+ required: true,
1484
+ rows: 6,
1485
+ columnSpan: 12
1486
+ },
1487
+ {
1488
+ name: "resume",
1489
+ type: "file",
1490
+ label: "Resume/CV",
1491
+ placeholder: "Upload your resume (PDF, DOC, DOCX)",
1492
+ required: true,
1493
+ columnSpan: 12,
1494
+ accept: ".pdf,.doc,.docx"
1495
+ }
1146
1496
  ];
1147
1497
  function ContactCareers({
1148
1498
  heading,
1149
1499
  description,
1150
- buttonText,
1500
+ buttonText = "Submit Application",
1151
1501
  buttonIcon,
1152
1502
  actions,
1153
1503
  actionsSlot,
1504
+ formFields,
1505
+ successMessage = "Thank you for your application! We'll review it and get back to you soon.",
1154
1506
  className,
1155
- containerClassName,
1507
+ containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
1156
1508
  headerClassName,
1157
1509
  headingClassName,
1158
1510
  descriptionClassName,
1159
1511
  cardClassName,
1160
1512
  cardContentClassName,
1161
1513
  submitClassName,
1162
- background = "white",
1163
- spacing = "xl",
1514
+ formClassName,
1515
+ successMessageClassName,
1516
+ errorMessageClassName,
1517
+ background,
1518
+ spacing = "py-8 md:py-32",
1164
1519
  pattern,
1165
- patternOpacity = 0.1,
1520
+ patternOpacity,
1166
1521
  formConfig,
1167
1522
  onSubmit,
1168
1523
  onSuccess,
1169
1524
  onError
1170
1525
  }) {
1171
- const [resume, setResume] = React.useState(null);
1172
- const formatFileSize = (bytes) => {
1173
- if (bytes === 0) return "0 Bytes";
1174
- const k = 1024;
1175
- const sizes = ["Bytes", "KB", "MB"];
1176
- const i = Math.floor(Math.log(bytes) / Math.log(k));
1177
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
1178
- };
1179
- const form = forms.useForm({
1180
- initialValues: {
1181
- position: "",
1182
- linkedin: "",
1183
- portfolio: "",
1184
- availability: "2-weeks",
1185
- firstName: "",
1186
- lastName: "",
1187
- email: "",
1188
- phone: "",
1189
- coverLetter: "",
1190
- contact_form_upload_tokens: []
1191
- },
1192
- validationSchema: {
1193
- position: (value) => !value ? "Position is required" : void 0,
1194
- firstName: (value) => !value ? "First name is required" : void 0,
1195
- lastName: (value) => !value ? "Last name is required" : void 0,
1196
- email: (value) => {
1197
- if (!value) return "Email is required";
1198
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
1199
- return "Please enter a valid email address";
1200
- return void 0;
1201
- },
1202
- phone: (value) => !value ? "Phone number is required" : void 0
1526
+ const fields = React.useMemo(
1527
+ () => formFields || DEFAULT_FORM_FIELDS,
1528
+ [formFields]
1529
+ );
1530
+ const {
1531
+ uploadTokens,
1532
+ uploadProgress,
1533
+ isUploading,
1534
+ uploadFiles,
1535
+ removeFile,
1536
+ resetUpload
1537
+ } = useFileUpload({ onError });
1538
+ const { form, submissionError, formMethod, resetSubmissionState } = useContactForm({
1539
+ formFields: fields,
1540
+ formConfig,
1541
+ onSubmit,
1542
+ onSuccess: (data) => {
1543
+ resetUpload();
1544
+ onSuccess?.(data);
1203
1545
  },
1204
- onSubmit: async (values, helpers) => {
1205
- const shouldAutoSubmit = Boolean(formConfig?.endpoint);
1206
- if (!shouldAutoSubmit && !onSubmit) {
1207
- return;
1208
- }
1209
- try {
1210
- let result;
1211
- if (shouldAutoSubmit) {
1212
- result = await submitPageSpeedForm(values, formConfig);
1213
- }
1214
- if (onSubmit) {
1215
- await onSubmit(values);
1216
- }
1217
- if (shouldAutoSubmit || onSubmit) {
1218
- if (formConfig?.resetOnSuccess !== false) {
1219
- helpers.resetForm();
1220
- setResume(null);
1221
- }
1222
- onSuccess?.(result);
1223
- }
1224
- } catch (error) {
1225
- if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
1226
- helpers.setErrors(error.formErrors);
1227
- }
1228
- onError?.(error);
1229
- throw error;
1230
- }
1231
- }
1546
+ onError,
1547
+ uploadTokens
1232
1548
  });
1233
- const { upload: upload$1, state: uploadState } = upload.useFileUpload({
1234
- endpoint: formConfig?.endpoint ? `${new URL(formConfig.endpoint, typeof window !== "undefined" ? window.location.origin : "http://localhost").origin}/contacts/_/contact_form_uploads` : "https://api.toastability.com/contacts/_/contact_form_uploads",
1235
- format: "legacy",
1236
- onComplete: (token) => {
1237
- const tokens = Array.isArray(token) ? token : [token];
1238
- form.setFieldValue(
1239
- "contact_form_upload_tokens",
1240
- tokens.map((value) => `upload_${value}`)
1241
- );
1242
- }
1243
- });
1244
- const handleFileChange = async (e) => {
1245
- if (e.target.files && e.target.files[0]) {
1246
- const file = e.target.files[0];
1247
- setResume(file);
1248
- try {
1249
- await upload$1(file);
1250
- } catch (error) {
1251
- console.error("File upload failed:", error);
1252
- setResume(null);
1253
- onError?.(error);
1254
- }
1255
- }
1256
- };
1257
- const handleRemoveFile = () => {
1258
- setResume(null);
1259
- form.setFieldValue("contact_form_upload_tokens", []);
1260
- };
1261
- const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
1262
- const actionsContent = React__namespace.useMemo(() => {
1549
+ const actionsContent = React.useMemo(() => {
1263
1550
  if (actionsSlot) return actionsSlot;
1264
1551
  if (actions && actions.length > 0) {
1265
1552
  return actions.map((action, index) => {
1266
- const { label, icon, iconAfter, children, className: actionClassName, ...pressableProps } = action;
1553
+ const {
1554
+ label,
1555
+ icon,
1556
+ iconAfter,
1557
+ children,
1558
+ className: actionClassName,
1559
+ ...pressableProps
1560
+ } = action;
1267
1561
  return /* @__PURE__ */ jsxRuntime.jsx(
1268
1562
  Pressable,
1269
1563
  {
@@ -1289,261 +1583,71 @@ function ContactCareers({
1289
1583
  spacing,
1290
1584
  pattern,
1291
1585
  patternOpacity,
1292
- className: cn("py-12", className),
1293
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("mx-auto w-full max-w-4xl px-4", containerClassName), children: [
1586
+ className,
1587
+ containerClassName,
1588
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
1294
1589
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("mb-10 text-center", headerClassName), children: [
1295
- heading && (typeof heading === "string" ? /* @__PURE__ */ jsxRuntime.jsx("h2", { className: cn("mb-3 text-3xl font-bold tracking-tight", headingClassName), children: heading }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: headingClassName, children: heading })),
1296
- description && (typeof description === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("leading-relaxed text-muted-foreground", descriptionClassName), children: description }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: descriptionClassName, children: description }))
1590
+ heading && (typeof heading === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
1591
+ "h2",
1592
+ {
1593
+ className: cn(
1594
+ "mb-3 text-3xl font-bold tracking-tight",
1595
+ headingClassName
1596
+ ),
1597
+ children: heading
1598
+ }
1599
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: headingClassName, children: heading })),
1600
+ description && (typeof description === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("leading-relaxed", descriptionClassName), children: description }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: descriptionClassName, children: description }))
1297
1601
  ] }),
1298
- /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: cn("p-0", cardContentClassName), children: /* @__PURE__ */ jsxRuntime.jsx(
1602
+ /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: cn("p-0", cardContentClassName), children: /* @__PURE__ */ jsxRuntime.jsxs(
1299
1603
  forms.Form,
1300
1604
  {
1301
1605
  form,
1302
1606
  action: formConfig?.endpoint,
1303
1607
  method: formMethod,
1304
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid md:grid-cols-2", children: [
1305
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b p-6 md:border-b-0 md:border-r", children: [
1306
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center gap-2", children: [
1307
- /* @__PURE__ */ jsxRuntime.jsx(
1308
- DynamicIcon,
1608
+ submissionError,
1609
+ successMessage,
1610
+ successMessageClassName,
1611
+ errorMessageClassName,
1612
+ submissionConfig: formConfig?.submissionConfig,
1613
+ onNewSubmission: () => {
1614
+ resetUpload();
1615
+ resetSubmissionState();
1616
+ },
1617
+ className: cn("p-6 space-y-6", formClassName),
1618
+ children: [
1619
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-12 gap-6", children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
1620
+ "div",
1621
+ {
1622
+ className: getColumnSpanClass(field.columnSpan),
1623
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1624
+ DynamicFormField,
1309
1625
  {
1310
- name: "lucide/briefcase",
1311
- size: 20,
1312
- className: "text-muted-foreground"
1626
+ field,
1627
+ uploadProgress,
1628
+ onFileUpload: uploadFiles,
1629
+ onFileRemove: removeFile,
1630
+ isUploading
1313
1631
  }
1314
- ),
1315
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Position Details" })
1316
- ] }),
1317
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1318
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "position", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1319
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "position", children: "Position" }),
1320
- /* @__PURE__ */ jsxRuntime.jsxs(
1321
- inputs.Select,
1322
- {
1323
- ...field,
1324
- id: "position",
1325
- error: meta.touched && !!meta.error,
1326
- "aria-label": "Position",
1327
- children: [
1328
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a position" }),
1329
- POSITIONS.map((pos) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: pos.value, children: pos.label }, pos.value))
1330
- ]
1331
- }
1332
- )
1333
- ] }) }),
1334
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1335
- /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Resume / CV" }),
1336
- !resume ? /* @__PURE__ */ jsxRuntime.jsxs(
1337
- "label",
1338
- {
1339
- htmlFor: "resume-upload",
1340
- className: cn(
1341
- "flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed p-6 transition-colors hover:border-foreground",
1342
- uploadState === "uploading" && "opacity-50 cursor-not-allowed"
1343
- ),
1344
- children: [
1345
- /* @__PURE__ */ jsxRuntime.jsx(
1346
- DynamicIcon,
1347
- {
1348
- name: uploadState === "uploading" ? "lucide/loader-2" : "lucide/upload",
1349
- size: 24,
1350
- className: cn(
1351
- "mb-2 text-muted-foreground",
1352
- uploadState === "uploading" && "animate-spin"
1353
- )
1354
- }
1355
- ),
1356
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: uploadState === "uploading" ? "Uploading..." : "Upload your resume" }),
1357
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "PDF or DOCX up to 5MB" }),
1358
- /* @__PURE__ */ jsxRuntime.jsx(
1359
- Input,
1360
- {
1361
- id: "resume-upload",
1362
- type: "file",
1363
- className: "hidden",
1364
- accept: ".pdf,.doc,.docx",
1365
- onChange: handleFileChange,
1366
- disabled: uploadState === "uploading"
1367
- }
1368
- )
1369
- ]
1370
- }
1371
- ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between rounded-lg border p-3", children: [
1372
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1373
- /* @__PURE__ */ jsxRuntime.jsx(
1374
- DynamicIcon,
1375
- {
1376
- name: uploadState === "completed" ? "lucide/check-circle" : "lucide/file",
1377
- size: 20,
1378
- className: cn(
1379
- "text-muted-foreground",
1380
- uploadState === "completed" && "text-success"
1381
- )
1382
- }
1383
- ),
1384
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1385
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: resume.name }),
1386
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: uploadState === "completed" ? "Uploaded successfully" : formatFileSize(resume.size) })
1387
- ] })
1388
- ] }),
1389
- /* @__PURE__ */ jsxRuntime.jsx(
1390
- Pressable,
1391
- {
1392
- componentType: "button",
1393
- type: "button",
1394
- variant: "ghost",
1395
- size: "icon",
1396
- onClick: handleRemoveFile,
1397
- asButton: true,
1398
- children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/x", size: 16 })
1399
- }
1400
- )
1401
- ] })
1402
- ] }),
1403
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "linkedin", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1404
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "linkedin", children: "LinkedIn Profile (Optional)" }),
1405
- /* @__PURE__ */ jsxRuntime.jsx(
1406
- TextInput,
1407
- {
1408
- ...field,
1409
- id: "linkedin",
1410
- type: "url",
1411
- placeholder: "https://linkedin.com/in/yourprofile",
1412
- error: meta.touched && !!meta.error,
1413
- "aria-label": "LinkedIn Profile"
1414
- }
1415
- )
1416
- ] }) }),
1417
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "portfolio", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1418
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "portfolio", children: "Portfolio / Website (Optional)" }),
1419
- /* @__PURE__ */ jsxRuntime.jsx(
1420
- TextInput,
1421
- {
1422
- ...field,
1423
- id: "portfolio",
1424
- type: "url",
1425
- placeholder: "https://yourportfolio.com",
1426
- error: meta.touched && !!meta.error,
1427
- "aria-label": "Portfolio / Website"
1428
- }
1429
- )
1430
- ] }) }),
1431
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "availability", children: ({ field }) => /* @__PURE__ */ jsxRuntime.jsx(
1432
- inputs.Radio,
1433
- {
1434
- name: "availability",
1435
- label: "Availability",
1436
- value: field.value,
1437
- onChange: field.onChange,
1438
- options: AVAILABILITY,
1439
- layout: "stacked",
1440
- className: "space-y-2"
1441
- }
1442
- ) })
1443
- ] })
1444
- ] }),
1445
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6", children: [
1446
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center gap-2", children: [
1447
- /* @__PURE__ */ jsxRuntime.jsx(
1448
- DynamicIcon,
1449
- {
1450
- name: "lucide/user",
1451
- size: 20,
1452
- className: "text-muted-foreground"
1453
- }
1454
- ),
1455
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Your Information" })
1456
- ] }),
1457
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1458
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1459
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "firstName", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1460
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "first-name", children: "First Name" }),
1461
- /* @__PURE__ */ jsxRuntime.jsx(
1462
- TextInput,
1463
- {
1464
- ...field,
1465
- id: "first-name",
1466
- placeholder: "John",
1467
- error: meta.touched && !!meta.error,
1468
- "aria-label": "First Name"
1469
- }
1470
- )
1471
- ] }) }),
1472
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "lastName", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1473
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "last-name", children: "Last Name" }),
1474
- /* @__PURE__ */ jsxRuntime.jsx(
1475
- TextInput,
1476
- {
1477
- ...field,
1478
- id: "last-name",
1479
- placeholder: "Doe",
1480
- error: meta.touched && !!meta.error,
1481
- "aria-label": "Last Name"
1482
- }
1483
- )
1484
- ] }) })
1485
- ] }),
1486
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "email", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1487
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "email", children: "Email" }),
1488
- /* @__PURE__ */ jsxRuntime.jsx(
1489
- TextInput,
1490
- {
1491
- ...field,
1492
- id: "email",
1493
- type: "email",
1494
- placeholder: "john@example.com",
1495
- error: meta.touched && !!meta.error,
1496
- "aria-label": "Email"
1497
- }
1498
- )
1499
- ] }) }),
1500
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "phone", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1501
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "phone", children: "Phone" }),
1502
- /* @__PURE__ */ jsxRuntime.jsx(
1503
- TextInput,
1504
- {
1505
- ...field,
1506
- id: "phone",
1507
- type: "tel",
1508
- placeholder: "+1 (555) 000-0000",
1509
- error: meta.touched && !!meta.error,
1510
- "aria-label": "Phone"
1511
- }
1512
- )
1513
- ] }) }),
1514
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "coverLetter", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1515
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "cover-letter", children: "Cover Letter (Optional)" }),
1516
- /* @__PURE__ */ jsxRuntime.jsx(
1517
- TextArea,
1518
- {
1519
- ...field,
1520
- id: "cover-letter",
1521
- placeholder: "Tell us why you'd be a great fit for this role...",
1522
- rows: 5,
1523
- error: meta.touched && !!meta.error,
1524
- "aria-label": "Cover Letter"
1525
- }
1526
- )
1527
- ] }) }),
1528
- /* @__PURE__ */ jsxRuntime.jsx(Separator, { className: "my-4" }),
1529
- actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxRuntime.jsxs(
1530
- Pressable,
1531
- {
1532
- componentType: "button",
1533
- type: "submit",
1534
- className: cn("w-full", submitClassName),
1535
- asButton: true,
1536
- disabled: form.isSubmitting,
1537
- children: [
1538
- buttonIcon,
1539
- buttonText
1540
- ]
1541
- }
1542
- ),
1543
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center text-xs text-muted-foreground", children: "We'll review your application and get back to you within 5 business days." })
1544
- ] })
1545
- ] })
1546
- ] })
1632
+ )
1633
+ },
1634
+ field.name
1635
+ )) }),
1636
+ actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxRuntime.jsxs(
1637
+ Pressable,
1638
+ {
1639
+ componentType: "button",
1640
+ type: "submit",
1641
+ className: cn("w-full", submitClassName),
1642
+ asButton: true,
1643
+ disabled: form.isSubmitting,
1644
+ children: [
1645
+ buttonIcon,
1646
+ buttonText
1647
+ ]
1648
+ }
1649
+ )
1650
+ ]
1547
1651
  }
1548
1652
  ) }) })
1549
1653
  ] })