@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, { useState } from 'react';
4
- import { useForm, Form, Field } from '@page-speed/forms';
5
- import { useFileUpload } from '@page-speed/forms/upload';
6
- 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';
7
5
  import { clsx } from 'clsx';
8
6
  import { twMerge } from 'tailwind-merge';
9
7
  import { cva } from 'class-variance-authority';
10
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
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-careers.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,58 +455,435 @@ function CardContent({ className, ...props }) {
564
455
  }
565
456
  );
566
457
  }
567
- function Label({
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
- LabelPrimitive.Root,
475
+ Field,
573
476
  {
574
- "data-slot": "label",
575
- className: cn(
576
- "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",
577
- className
578
- ),
579
- ...props
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
+ ] })
580
657
  }
581
658
  );
582
659
  }
583
- function Input({ className, type, ...props }) {
584
- return /* @__PURE__ */ jsx(
585
- "input",
586
- {
587
- type,
588
- "data-slot": "input",
589
- className: cn(
590
- "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",
591
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
592
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
593
- className
594
- ),
595
- ...props
596
- }
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
+ {}
597
679
  );
598
680
  }
599
- function Separator({
600
- className,
601
- orientation = "horizontal",
602
- decorative = true,
603
- ...props
604
- }) {
605
- return /* @__PURE__ */ jsx(
606
- SeparatorPrimitive.Root,
607
- {
608
- "data-slot": "separator",
609
- decorative,
610
- orientation,
611
- className: cn(
612
- "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
613
- className
614
- ),
615
- ...props
616
- }
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
+ {}
710
+ );
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 useFileUpload(options) {
717
+ const [uploadTokens, setUploadTokens] = useState([]);
718
+ const [uploadProgress, setUploadProgress] = useState({});
719
+ const [isUploading, setIsUploading] = useState(false);
720
+ const endpoint = options?.endpoint || "https://api.dashtrack.com/contacts/_/contact_form_uploads";
721
+ const uploadFiles = useCallback(
722
+ async (files) => {
723
+ if (files.length === 0) return;
724
+ setIsUploading(true);
725
+ try {
726
+ const tokens = [];
727
+ for (const file of files) {
728
+ const formData = new FormData();
729
+ formData.append("contact_form_upload[file_upload]", file);
730
+ formData.append("contact_form_upload[title]", file.name);
731
+ formData.append("contact_form_upload[file_name]", file.name);
732
+ formData.append("contact_form_upload[file_size]", String(file.size));
733
+ const response = await fetch(endpoint, {
734
+ method: "POST",
735
+ body: formData
736
+ });
737
+ if (!response.ok) {
738
+ throw new Error(`Upload failed: ${response.statusText}`);
739
+ }
740
+ const data = await response.json();
741
+ if (data.contact_form_upload?.token) {
742
+ tokens.push(`upload_${data.contact_form_upload.token}`);
743
+ }
744
+ setUploadProgress((prev) => ({
745
+ ...prev,
746
+ [file.name]: 100
747
+ }));
748
+ }
749
+ setUploadTokens(tokens);
750
+ } catch (error) {
751
+ console.error("File upload error:", error);
752
+ options?.onError?.(error);
753
+ } finally {
754
+ setIsUploading(false);
755
+ }
756
+ },
757
+ [endpoint, options]
617
758
  );
759
+ const removeFile = useCallback((file, index) => {
760
+ setUploadTokens((prev) => prev.filter((_, i) => i !== index));
761
+ setUploadProgress((prev) => {
762
+ const newProgress = { ...prev };
763
+ delete newProgress[file.name];
764
+ return newProgress;
765
+ });
766
+ }, []);
767
+ const resetUpload = useCallback(() => {
768
+ setUploadTokens([]);
769
+ setUploadProgress({});
770
+ }, []);
771
+ return {
772
+ uploadTokens,
773
+ uploadProgress,
774
+ isUploading,
775
+ uploadFiles,
776
+ removeFile,
777
+ resetUpload
778
+ };
779
+ }
780
+ function useContactForm(options) {
781
+ const {
782
+ formFields,
783
+ formConfig,
784
+ onSubmit,
785
+ onSuccess,
786
+ onError,
787
+ resetOnSuccess = true,
788
+ uploadTokens = []
789
+ } = options;
790
+ const [submissionError, setSubmissionError] = useState(null);
791
+ const submissionConfig = formConfig?.submissionConfig;
792
+ const redirectUrl = submissionConfig?.redirectUrl;
793
+ const redirectNavigation = useNavigation({ href: redirectUrl });
794
+ const resetSubmissionState = useCallback(() => {
795
+ setSubmissionError(null);
796
+ }, []);
797
+ const performRedirect = useCallback(() => {
798
+ if (!redirectUrl || typeof window === "undefined") {
799
+ return;
800
+ }
801
+ const navigate = () => {
802
+ if (redirectNavigation.shouldUseRouter && redirectNavigation.normalizedHref) {
803
+ const handler = window.__opensiteNavigationHandler;
804
+ if (typeof handler === "function") {
805
+ try {
806
+ const handled = handler(redirectNavigation.normalizedHref, void 0);
807
+ if (handled !== false) {
808
+ return;
809
+ }
810
+ } catch (error) {
811
+ console.error("Internal redirect handler failed:", error);
812
+ }
813
+ }
814
+ }
815
+ const destination = redirectNavigation.normalizedHref || redirectUrl;
816
+ window.location.assign(destination);
817
+ };
818
+ window.setTimeout(navigate, 150);
819
+ }, [redirectNavigation, redirectUrl]);
820
+ const form = useForm({
821
+ initialValues: useMemo(
822
+ () => generateInitialValues(formFields),
823
+ [formFields]
824
+ ),
825
+ validationSchema: useMemo(
826
+ () => generateValidationSchema(formFields),
827
+ [formFields]
828
+ ),
829
+ onSubmit: async (values, helpers) => {
830
+ resetSubmissionState();
831
+ const shouldAutoSubmit = Boolean(formConfig?.endpoint);
832
+ if (!shouldAutoSubmit && !onSubmit) {
833
+ return;
834
+ }
835
+ try {
836
+ let result;
837
+ const submissionValues = {
838
+ ...values,
839
+ ...uploadTokens.length > 0 && {
840
+ contact_form_upload_tokens: uploadTokens
841
+ }
842
+ };
843
+ if (shouldAutoSubmit) {
844
+ result = await submitPageSpeedForm(submissionValues, formConfig);
845
+ }
846
+ if (onSubmit) {
847
+ await onSubmit(submissionValues);
848
+ }
849
+ if (shouldAutoSubmit || onSubmit) {
850
+ try {
851
+ await submissionConfig?.handleFormSubmission?.({
852
+ formData: submissionValues,
853
+ responseData: result
854
+ });
855
+ } catch (callbackError) {
856
+ console.error("handleFormSubmission callback failed:", callbackError);
857
+ }
858
+ if (resetOnSuccess) {
859
+ helpers.resetForm();
860
+ }
861
+ onSuccess?.(result);
862
+ if (submissionConfig?.behavior === "redirect" && submissionConfig.redirectUrl) {
863
+ performRedirect();
864
+ }
865
+ }
866
+ } catch (error) {
867
+ if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
868
+ helpers.setErrors(error.formErrors);
869
+ }
870
+ const errorMessage = error instanceof Error ? error.message : "Form submission failed";
871
+ setSubmissionError(errorMessage);
872
+ onError?.(error);
873
+ }
874
+ }
875
+ });
876
+ const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
877
+ return {
878
+ form,
879
+ isSubmitted: form.status === "success",
880
+ submissionError,
881
+ formMethod,
882
+ resetSubmissionState
883
+ };
618
884
  }
885
+
886
+ // lib/forms.ts
619
887
  var PageSpeedFormSubmissionError = class extends Error {
620
888
  constructor(message, options = {}) {
621
889
  super(message);
@@ -1106,141 +1374,169 @@ var Section = React__default.forwardRef(
1106
1374
  }
1107
1375
  );
1108
1376
  Section.displayName = "Section";
1109
- var POSITIONS = [
1110
- { value: "frontend", label: "Frontend Developer" },
1111
- { value: "backend", label: "Backend Developer" },
1112
- { value: "fullstack", label: "Full Stack Developer" },
1113
- { value: "designer", label: "Product Designer" },
1114
- { value: "pm", label: "Product Manager" },
1115
- { value: "marketing", label: "Marketing Manager" },
1116
- { value: "other", label: "Other" }
1117
- ];
1118
- var AVAILABILITY = [
1119
- { value: "immediately", label: "Immediately" },
1120
- { value: "2-weeks", label: "2 weeks notice" },
1121
- { value: "1-month", label: "1 month notice" },
1122
- { value: "flexible", label: "Flexible" }
1377
+ var DEFAULT_FORM_FIELDS = [
1378
+ {
1379
+ name: "position",
1380
+ type: "select",
1381
+ label: "Position Applying For",
1382
+ placeholder: "Select a position",
1383
+ required: true,
1384
+ columnSpan: 12,
1385
+ options: [
1386
+ { value: "frontend", label: "Frontend Developer" },
1387
+ { value: "backend", label: "Backend Developer" },
1388
+ { value: "fullstack", label: "Full Stack Developer" },
1389
+ { value: "designer", label: "Product Designer" },
1390
+ { value: "pm", label: "Product Manager" },
1391
+ { value: "marketing", label: "Marketing Manager" },
1392
+ { value: "other", label: "Other" }
1393
+ ]
1394
+ },
1395
+ {
1396
+ name: "firstName",
1397
+ type: "text",
1398
+ label: "First Name",
1399
+ placeholder: "John",
1400
+ required: true,
1401
+ columnSpan: 6
1402
+ },
1403
+ {
1404
+ name: "lastName",
1405
+ type: "text",
1406
+ label: "Last Name",
1407
+ placeholder: "Doe",
1408
+ required: true,
1409
+ columnSpan: 6
1410
+ },
1411
+ {
1412
+ name: "email",
1413
+ type: "email",
1414
+ label: "Email Address",
1415
+ placeholder: "john@example.com",
1416
+ required: true,
1417
+ columnSpan: 6
1418
+ },
1419
+ {
1420
+ name: "phone",
1421
+ type: "tel",
1422
+ label: "Phone Number",
1423
+ placeholder: "+1 (555) 000-0000",
1424
+ required: true,
1425
+ columnSpan: 6
1426
+ },
1427
+ {
1428
+ name: "linkedin",
1429
+ type: "url",
1430
+ label: "LinkedIn Profile",
1431
+ placeholder: "https://linkedin.com/in/yourprofile",
1432
+ required: false,
1433
+ columnSpan: 6
1434
+ },
1435
+ {
1436
+ name: "portfolio",
1437
+ type: "url",
1438
+ label: "Portfolio/Website",
1439
+ placeholder: "https://yourportfolio.com",
1440
+ required: false,
1441
+ columnSpan: 6
1442
+ },
1443
+ {
1444
+ name: "availability",
1445
+ type: "select",
1446
+ label: "Availability",
1447
+ placeholder: "Select your availability",
1448
+ required: true,
1449
+ columnSpan: 12,
1450
+ options: [
1451
+ { value: "immediately", label: "Immediately" },
1452
+ { value: "2-weeks", label: "2 weeks notice" },
1453
+ { value: "1-month", label: "1 month notice" },
1454
+ { value: "flexible", label: "Flexible" }
1455
+ ]
1456
+ },
1457
+ {
1458
+ name: "coverLetter",
1459
+ type: "textarea",
1460
+ label: "Cover Letter",
1461
+ placeholder: "Tell us why you'd be a great fit for this position...",
1462
+ required: true,
1463
+ rows: 6,
1464
+ columnSpan: 12
1465
+ },
1466
+ {
1467
+ name: "resume",
1468
+ type: "file",
1469
+ label: "Resume/CV",
1470
+ placeholder: "Upload your resume (PDF, DOC, DOCX)",
1471
+ required: true,
1472
+ columnSpan: 12,
1473
+ accept: ".pdf,.doc,.docx"
1474
+ }
1123
1475
  ];
1124
1476
  function ContactCareers({
1125
1477
  heading,
1126
1478
  description,
1127
- buttonText,
1479
+ buttonText = "Submit Application",
1128
1480
  buttonIcon,
1129
1481
  actions,
1130
1482
  actionsSlot,
1483
+ formFields,
1484
+ successMessage = "Thank you for your application! We'll review it and get back to you soon.",
1131
1485
  className,
1132
- containerClassName,
1486
+ containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
1133
1487
  headerClassName,
1134
1488
  headingClassName,
1135
1489
  descriptionClassName,
1136
1490
  cardClassName,
1137
1491
  cardContentClassName,
1138
1492
  submitClassName,
1139
- background = "white",
1140
- spacing = "xl",
1493
+ formClassName,
1494
+ successMessageClassName,
1495
+ errorMessageClassName,
1496
+ background,
1497
+ spacing = "py-8 md:py-32",
1141
1498
  pattern,
1142
- patternOpacity = 0.1,
1499
+ patternOpacity,
1143
1500
  formConfig,
1144
1501
  onSubmit,
1145
1502
  onSuccess,
1146
1503
  onError
1147
1504
  }) {
1148
- const [resume, setResume] = useState(null);
1149
- const formatFileSize = (bytes) => {
1150
- if (bytes === 0) return "0 Bytes";
1151
- const k = 1024;
1152
- const sizes = ["Bytes", "KB", "MB"];
1153
- const i = Math.floor(Math.log(bytes) / Math.log(k));
1154
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
1155
- };
1156
- const form = useForm({
1157
- initialValues: {
1158
- position: "",
1159
- linkedin: "",
1160
- portfolio: "",
1161
- availability: "2-weeks",
1162
- firstName: "",
1163
- lastName: "",
1164
- email: "",
1165
- phone: "",
1166
- coverLetter: "",
1167
- contact_form_upload_tokens: []
1168
- },
1169
- validationSchema: {
1170
- position: (value) => !value ? "Position is required" : void 0,
1171
- firstName: (value) => !value ? "First name is required" : void 0,
1172
- lastName: (value) => !value ? "Last name is required" : void 0,
1173
- email: (value) => {
1174
- if (!value) return "Email is required";
1175
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
1176
- return "Please enter a valid email address";
1177
- return void 0;
1178
- },
1179
- phone: (value) => !value ? "Phone number is required" : void 0
1505
+ const fields = useMemo(
1506
+ () => formFields || DEFAULT_FORM_FIELDS,
1507
+ [formFields]
1508
+ );
1509
+ const {
1510
+ uploadTokens,
1511
+ uploadProgress,
1512
+ isUploading,
1513
+ uploadFiles,
1514
+ removeFile,
1515
+ resetUpload
1516
+ } = useFileUpload({ onError });
1517
+ const { form, submissionError, formMethod, resetSubmissionState } = useContactForm({
1518
+ formFields: fields,
1519
+ formConfig,
1520
+ onSubmit,
1521
+ onSuccess: (data) => {
1522
+ resetUpload();
1523
+ onSuccess?.(data);
1180
1524
  },
1181
- onSubmit: async (values, helpers) => {
1182
- const shouldAutoSubmit = Boolean(formConfig?.endpoint);
1183
- if (!shouldAutoSubmit && !onSubmit) {
1184
- return;
1185
- }
1186
- try {
1187
- let result;
1188
- if (shouldAutoSubmit) {
1189
- result = await submitPageSpeedForm(values, formConfig);
1190
- }
1191
- if (onSubmit) {
1192
- await onSubmit(values);
1193
- }
1194
- if (shouldAutoSubmit || onSubmit) {
1195
- if (formConfig?.resetOnSuccess !== false) {
1196
- helpers.resetForm();
1197
- setResume(null);
1198
- }
1199
- onSuccess?.(result);
1200
- }
1201
- } catch (error) {
1202
- if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
1203
- helpers.setErrors(error.formErrors);
1204
- }
1205
- onError?.(error);
1206
- throw error;
1207
- }
1208
- }
1525
+ onError,
1526
+ uploadTokens
1209
1527
  });
1210
- const { upload, state: uploadState } = useFileUpload({
1211
- 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",
1212
- format: "legacy",
1213
- onComplete: (token) => {
1214
- const tokens = Array.isArray(token) ? token : [token];
1215
- form.setFieldValue(
1216
- "contact_form_upload_tokens",
1217
- tokens.map((value) => `upload_${value}`)
1218
- );
1219
- }
1220
- });
1221
- const handleFileChange = async (e) => {
1222
- if (e.target.files && e.target.files[0]) {
1223
- const file = e.target.files[0];
1224
- setResume(file);
1225
- try {
1226
- await upload(file);
1227
- } catch (error) {
1228
- console.error("File upload failed:", error);
1229
- setResume(null);
1230
- onError?.(error);
1231
- }
1232
- }
1233
- };
1234
- const handleRemoveFile = () => {
1235
- setResume(null);
1236
- form.setFieldValue("contact_form_upload_tokens", []);
1237
- };
1238
- const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
1239
- const actionsContent = React.useMemo(() => {
1528
+ const actionsContent = useMemo(() => {
1240
1529
  if (actionsSlot) return actionsSlot;
1241
1530
  if (actions && actions.length > 0) {
1242
1531
  return actions.map((action, index) => {
1243
- const { label, icon, iconAfter, children, className: actionClassName, ...pressableProps } = action;
1532
+ const {
1533
+ label,
1534
+ icon,
1535
+ iconAfter,
1536
+ children,
1537
+ className: actionClassName,
1538
+ ...pressableProps
1539
+ } = action;
1244
1540
  return /* @__PURE__ */ jsx(
1245
1541
  Pressable,
1246
1542
  {
@@ -1266,261 +1562,71 @@ function ContactCareers({
1266
1562
  spacing,
1267
1563
  pattern,
1268
1564
  patternOpacity,
1269
- className: cn("py-12", className),
1270
- children: /* @__PURE__ */ jsxs("div", { className: cn("mx-auto w-full max-w-4xl px-4", containerClassName), children: [
1565
+ className,
1566
+ containerClassName,
1567
+ children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1271
1568
  /* @__PURE__ */ jsxs("div", { className: cn("mb-10 text-center", headerClassName), children: [
1272
- heading && (typeof heading === "string" ? /* @__PURE__ */ jsx("h2", { className: cn("mb-3 text-3xl font-bold tracking-tight", headingClassName), children: heading }) : /* @__PURE__ */ jsx("div", { className: headingClassName, children: heading })),
1273
- description && (typeof description === "string" ? /* @__PURE__ */ jsx("p", { className: cn("leading-relaxed text-muted-foreground", descriptionClassName), children: description }) : /* @__PURE__ */ jsx("div", { className: descriptionClassName, children: description }))
1569
+ heading && (typeof heading === "string" ? /* @__PURE__ */ jsx(
1570
+ "h2",
1571
+ {
1572
+ className: cn(
1573
+ "mb-3 text-3xl font-bold tracking-tight",
1574
+ headingClassName
1575
+ ),
1576
+ children: heading
1577
+ }
1578
+ ) : /* @__PURE__ */ jsx("div", { className: headingClassName, children: heading })),
1579
+ description && (typeof description === "string" ? /* @__PURE__ */ jsx("p", { className: cn("leading-relaxed", descriptionClassName), children: description }) : /* @__PURE__ */ jsx("div", { className: descriptionClassName, children: description }))
1274
1580
  ] }),
1275
- /* @__PURE__ */ jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsx(CardContent, { className: cn("p-0", cardContentClassName), children: /* @__PURE__ */ jsx(
1581
+ /* @__PURE__ */ jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsx(CardContent, { className: cn("p-0", cardContentClassName), children: /* @__PURE__ */ jsxs(
1276
1582
  Form,
1277
1583
  {
1278
1584
  form,
1279
1585
  action: formConfig?.endpoint,
1280
1586
  method: formMethod,
1281
- children: /* @__PURE__ */ jsxs("div", { className: "grid md:grid-cols-2", children: [
1282
- /* @__PURE__ */ jsxs("div", { className: "border-b p-6 md:border-b-0 md:border-r", children: [
1283
- /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center gap-2", children: [
1284
- /* @__PURE__ */ jsx(
1285
- DynamicIcon,
1286
- {
1287
- name: "lucide/briefcase",
1288
- size: 20,
1289
- className: "text-muted-foreground"
1290
- }
1291
- ),
1292
- /* @__PURE__ */ jsx("h3", { className: "font-semibold", children: "Position Details" })
1293
- ] }),
1294
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1295
- /* @__PURE__ */ jsx(Field, { name: "position", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1296
- /* @__PURE__ */ jsx(Label, { htmlFor: "position", children: "Position" }),
1297
- /* @__PURE__ */ jsxs(
1298
- Select,
1299
- {
1300
- ...field,
1301
- id: "position",
1302
- error: meta.touched && !!meta.error,
1303
- "aria-label": "Position",
1304
- children: [
1305
- /* @__PURE__ */ jsx("option", { value: "", children: "Select a position" }),
1306
- POSITIONS.map((pos) => /* @__PURE__ */ jsx("option", { value: pos.value, children: pos.label }, pos.value))
1307
- ]
1308
- }
1309
- )
1310
- ] }) }),
1311
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1312
- /* @__PURE__ */ jsx(Label, { children: "Resume / CV" }),
1313
- !resume ? /* @__PURE__ */ jsxs(
1314
- "label",
1315
- {
1316
- htmlFor: "resume-upload",
1317
- className: cn(
1318
- "flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed p-6 transition-colors hover:border-foreground",
1319
- uploadState === "uploading" && "opacity-50 cursor-not-allowed"
1320
- ),
1321
- children: [
1322
- /* @__PURE__ */ jsx(
1323
- DynamicIcon,
1324
- {
1325
- name: uploadState === "uploading" ? "lucide/loader-2" : "lucide/upload",
1326
- size: 24,
1327
- className: cn(
1328
- "mb-2 text-muted-foreground",
1329
- uploadState === "uploading" && "animate-spin"
1330
- )
1331
- }
1332
- ),
1333
- /* @__PURE__ */ jsx("p", { className: "text-sm", children: uploadState === "uploading" ? "Uploading..." : "Upload your resume" }),
1334
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "PDF or DOCX up to 5MB" }),
1335
- /* @__PURE__ */ jsx(
1336
- Input,
1337
- {
1338
- id: "resume-upload",
1339
- type: "file",
1340
- className: "hidden",
1341
- accept: ".pdf,.doc,.docx",
1342
- onChange: handleFileChange,
1343
- disabled: uploadState === "uploading"
1344
- }
1345
- )
1346
- ]
1347
- }
1348
- ) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg border p-3", children: [
1349
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1350
- /* @__PURE__ */ jsx(
1351
- DynamicIcon,
1352
- {
1353
- name: uploadState === "completed" ? "lucide/check-circle" : "lucide/file",
1354
- size: 20,
1355
- className: cn(
1356
- "text-muted-foreground",
1357
- uploadState === "completed" && "text-success"
1358
- )
1359
- }
1360
- ),
1361
- /* @__PURE__ */ jsxs("div", { children: [
1362
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: resume.name }),
1363
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: uploadState === "completed" ? "Uploaded successfully" : formatFileSize(resume.size) })
1364
- ] })
1365
- ] }),
1366
- /* @__PURE__ */ jsx(
1367
- Pressable,
1368
- {
1369
- componentType: "button",
1370
- type: "button",
1371
- variant: "ghost",
1372
- size: "icon",
1373
- onClick: handleRemoveFile,
1374
- asButton: true,
1375
- children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/x", size: 16 })
1376
- }
1377
- )
1378
- ] })
1379
- ] }),
1380
- /* @__PURE__ */ jsx(Field, { name: "linkedin", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1381
- /* @__PURE__ */ jsx(Label, { htmlFor: "linkedin", children: "LinkedIn Profile (Optional)" }),
1382
- /* @__PURE__ */ jsx(
1383
- TextInput,
1384
- {
1385
- ...field,
1386
- id: "linkedin",
1387
- type: "url",
1388
- placeholder: "https://linkedin.com/in/yourprofile",
1389
- error: meta.touched && !!meta.error,
1390
- "aria-label": "LinkedIn Profile"
1391
- }
1392
- )
1393
- ] }) }),
1394
- /* @__PURE__ */ jsx(Field, { name: "portfolio", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1395
- /* @__PURE__ */ jsx(Label, { htmlFor: "portfolio", children: "Portfolio / Website (Optional)" }),
1396
- /* @__PURE__ */ jsx(
1397
- TextInput,
1398
- {
1399
- ...field,
1400
- id: "portfolio",
1401
- type: "url",
1402
- placeholder: "https://yourportfolio.com",
1403
- error: meta.touched && !!meta.error,
1404
- "aria-label": "Portfolio / Website"
1405
- }
1406
- )
1407
- ] }) }),
1408
- /* @__PURE__ */ jsx(Field, { name: "availability", children: ({ field }) => /* @__PURE__ */ jsx(
1409
- Radio,
1410
- {
1411
- name: "availability",
1412
- label: "Availability",
1413
- value: field.value,
1414
- onChange: field.onChange,
1415
- options: AVAILABILITY,
1416
- layout: "stacked",
1417
- className: "space-y-2"
1418
- }
1419
- ) })
1420
- ] })
1421
- ] }),
1422
- /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
1423
- /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center gap-2", children: [
1424
- /* @__PURE__ */ jsx(
1425
- DynamicIcon,
1587
+ submissionError,
1588
+ successMessage,
1589
+ successMessageClassName,
1590
+ errorMessageClassName,
1591
+ submissionConfig: formConfig?.submissionConfig,
1592
+ onNewSubmission: () => {
1593
+ resetUpload();
1594
+ resetSubmissionState();
1595
+ },
1596
+ className: cn("p-6 space-y-6", formClassName),
1597
+ children: [
1598
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-12 gap-6", children: fields.map((field) => /* @__PURE__ */ jsx(
1599
+ "div",
1600
+ {
1601
+ className: getColumnSpanClass(field.columnSpan),
1602
+ children: /* @__PURE__ */ jsx(
1603
+ DynamicFormField,
1426
1604
  {
1427
- name: "lucide/user",
1428
- size: 20,
1429
- className: "text-muted-foreground"
1605
+ field,
1606
+ uploadProgress,
1607
+ onFileUpload: uploadFiles,
1608
+ onFileRemove: removeFile,
1609
+ isUploading
1430
1610
  }
1431
- ),
1432
- /* @__PURE__ */ jsx("h3", { className: "font-semibold", children: "Your Information" })
1433
- ] }),
1434
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1435
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1436
- /* @__PURE__ */ jsx(Field, { name: "firstName", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1437
- /* @__PURE__ */ jsx(Label, { htmlFor: "first-name", children: "First Name" }),
1438
- /* @__PURE__ */ jsx(
1439
- TextInput,
1440
- {
1441
- ...field,
1442
- id: "first-name",
1443
- placeholder: "John",
1444
- error: meta.touched && !!meta.error,
1445
- "aria-label": "First Name"
1446
- }
1447
- )
1448
- ] }) }),
1449
- /* @__PURE__ */ jsx(Field, { name: "lastName", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1450
- /* @__PURE__ */ jsx(Label, { htmlFor: "last-name", children: "Last Name" }),
1451
- /* @__PURE__ */ jsx(
1452
- TextInput,
1453
- {
1454
- ...field,
1455
- id: "last-name",
1456
- placeholder: "Doe",
1457
- error: meta.touched && !!meta.error,
1458
- "aria-label": "Last Name"
1459
- }
1460
- )
1461
- ] }) })
1462
- ] }),
1463
- /* @__PURE__ */ jsx(Field, { name: "email", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1464
- /* @__PURE__ */ jsx(Label, { htmlFor: "email", children: "Email" }),
1465
- /* @__PURE__ */ jsx(
1466
- TextInput,
1467
- {
1468
- ...field,
1469
- id: "email",
1470
- type: "email",
1471
- placeholder: "john@example.com",
1472
- error: meta.touched && !!meta.error,
1473
- "aria-label": "Email"
1474
- }
1475
- )
1476
- ] }) }),
1477
- /* @__PURE__ */ jsx(Field, { name: "phone", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1478
- /* @__PURE__ */ jsx(Label, { htmlFor: "phone", children: "Phone" }),
1479
- /* @__PURE__ */ jsx(
1480
- TextInput,
1481
- {
1482
- ...field,
1483
- id: "phone",
1484
- type: "tel",
1485
- placeholder: "+1 (555) 000-0000",
1486
- error: meta.touched && !!meta.error,
1487
- "aria-label": "Phone"
1488
- }
1489
- )
1490
- ] }) }),
1491
- /* @__PURE__ */ jsx(Field, { name: "coverLetter", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1492
- /* @__PURE__ */ jsx(Label, { htmlFor: "cover-letter", children: "Cover Letter (Optional)" }),
1493
- /* @__PURE__ */ jsx(
1494
- TextArea,
1495
- {
1496
- ...field,
1497
- id: "cover-letter",
1498
- placeholder: "Tell us why you'd be a great fit for this role...",
1499
- rows: 5,
1500
- error: meta.touched && !!meta.error,
1501
- "aria-label": "Cover Letter"
1502
- }
1503
- )
1504
- ] }) }),
1505
- /* @__PURE__ */ jsx(Separator, { className: "my-4" }),
1506
- actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxs(
1507
- Pressable,
1508
- {
1509
- componentType: "button",
1510
- type: "submit",
1511
- className: cn("w-full", submitClassName),
1512
- asButton: true,
1513
- disabled: form.isSubmitting,
1514
- children: [
1515
- buttonIcon,
1516
- buttonText
1517
- ]
1518
- }
1519
- ),
1520
- /* @__PURE__ */ 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." })
1521
- ] })
1522
- ] })
1523
- ] })
1611
+ )
1612
+ },
1613
+ field.name
1614
+ )) }),
1615
+ actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxs(
1616
+ Pressable,
1617
+ {
1618
+ componentType: "button",
1619
+ type: "submit",
1620
+ className: cn("w-full", submitClassName),
1621
+ asButton: true,
1622
+ disabled: form.isSubmitting,
1623
+ children: [
1624
+ buttonIcon,
1625
+ buttonText
1626
+ ]
1627
+ }
1628
+ )
1629
+ ]
1524
1630
  }
1525
1631
  ) }) })
1526
1632
  ] })