@opensite/ui 1.7.5 → 1.7.6

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.
@@ -3,11 +3,11 @@
3
3
 
4
4
  var React = require('react');
5
5
  var forms = require('@page-speed/forms');
6
- var inputs = require('@page-speed/forms/inputs');
7
6
  var clsx = require('clsx');
8
7
  var tailwindMerge = require('tailwind-merge');
9
8
  var classVarianceAuthority = require('class-variance-authority');
10
9
  var jsxRuntime = require('react/jsx-runtime');
10
+ var inputs = require('@page-speed/forms/inputs');
11
11
  var LabelPrimitive = require('@radix-ui/react-label');
12
12
  var AccordionPrimitive = require('@radix-ui/react-accordion');
13
13
  var integration = require('@page-speed/forms/integration');
@@ -495,6 +495,200 @@ function Label({
495
495
  }
496
496
  );
497
497
  }
498
+ function DynamicFormField({
499
+ field,
500
+ className,
501
+ uploadProgress = {},
502
+ onFileUpload,
503
+ onFileRemove,
504
+ isUploading = false
505
+ }) {
506
+ const fieldId = `field-${field.name}`;
507
+ return /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: field.name, children: ({ field: formField, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-2", className), children: [
508
+ field.type !== "checkbox" && /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
509
+ field.label,
510
+ field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive ml-1", children: "*" })
511
+ ] }),
512
+ (field.type === "text" || field.type === "email" || field.type === "tel" || field.type === "search" || field.type === "password" || field.type === "url") && /* @__PURE__ */ jsxRuntime.jsx(
513
+ inputs.TextInput,
514
+ {
515
+ ...formField,
516
+ id: fieldId,
517
+ type: field.type,
518
+ placeholder: field.placeholder,
519
+ error: meta.touched && !!meta.error,
520
+ disabled: field.disabled,
521
+ "aria-label": field.label
522
+ }
523
+ ),
524
+ field.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(
525
+ inputs.TextInput,
526
+ {
527
+ ...formField,
528
+ id: fieldId,
529
+ type: "text",
530
+ placeholder: field.placeholder,
531
+ error: meta.touched && !!meta.error,
532
+ disabled: field.disabled,
533
+ "aria-label": field.label
534
+ }
535
+ ),
536
+ field.type === "textarea" && /* @__PURE__ */ jsxRuntime.jsx(
537
+ inputs.TextArea,
538
+ {
539
+ ...formField,
540
+ id: fieldId,
541
+ placeholder: field.placeholder,
542
+ rows: field.rows || 4,
543
+ error: meta.touched && !!meta.error,
544
+ disabled: field.disabled,
545
+ "aria-label": field.label
546
+ }
547
+ ),
548
+ field.type === "select" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
549
+ inputs.Select,
550
+ {
551
+ ...formField,
552
+ id: fieldId,
553
+ options: field.options,
554
+ placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
555
+ error: meta.touched && !!meta.error,
556
+ disabled: field.disabled,
557
+ "aria-label": field.label
558
+ }
559
+ ),
560
+ field.type === "multi-select" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
561
+ inputs.Select,
562
+ {
563
+ ...formField,
564
+ id: fieldId,
565
+ options: field.options,
566
+ placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
567
+ error: meta.touched && !!meta.error,
568
+ disabled: field.disabled,
569
+ "aria-label": field.label,
570
+ multiple: true
571
+ }
572
+ ),
573
+ field.type === "radio" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
574
+ inputs.Radio,
575
+ {
576
+ ...formField,
577
+ id: fieldId,
578
+ options: field.options,
579
+ disabled: field.disabled,
580
+ layout: field.layout || "stacked",
581
+ error: meta.touched && !!meta.error,
582
+ "aria-label": field.label
583
+ }
584
+ ),
585
+ field.type === "checkbox" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start space-x-2", children: [
586
+ /* @__PURE__ */ jsxRuntime.jsx(
587
+ inputs.Checkbox,
588
+ {
589
+ ...formField,
590
+ id: fieldId,
591
+ value: formField.value === true || formField.value === "true",
592
+ onChange: (checked) => formField.onChange(checked),
593
+ disabled: field.disabled,
594
+ error: meta.touched && !!meta.error,
595
+ "aria-label": field.label
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsxRuntime.jsxs(
599
+ Label,
600
+ {
601
+ htmlFor: fieldId,
602
+ className: "font-normal cursor-pointer leading-relaxed",
603
+ children: [
604
+ field.label,
605
+ field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive ml-1", children: "*" })
606
+ ]
607
+ }
608
+ )
609
+ ] }),
610
+ field.type === "checkbox-group" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
611
+ inputs.CheckboxGroup,
612
+ {
613
+ ...formField,
614
+ id: fieldId,
615
+ options: field.options,
616
+ disabled: field.disabled,
617
+ layout: field.layout || "stacked",
618
+ error: meta.touched && !!meta.error,
619
+ "aria-label": field.label
620
+ }
621
+ ),
622
+ (field.type === "date-picker" || field.type === "date") && /* @__PURE__ */ jsxRuntime.jsx(
623
+ inputs.DatePicker,
624
+ {
625
+ ...formField,
626
+ id: fieldId,
627
+ placeholder: field.placeholder,
628
+ error: meta.touched && !!meta.error,
629
+ disabled: field.disabled,
630
+ "aria-label": field.label
631
+ }
632
+ ),
633
+ field.type === "date-range" && /* @__PURE__ */ jsxRuntime.jsx(
634
+ inputs.DateRangePicker,
635
+ {
636
+ ...formField,
637
+ id: fieldId,
638
+ error: meta.touched && !!meta.error,
639
+ disabled: field.disabled,
640
+ "aria-label": field.label
641
+ }
642
+ ),
643
+ field.type === "time" && /* @__PURE__ */ jsxRuntime.jsx(
644
+ inputs.TimePicker,
645
+ {
646
+ ...formField,
647
+ id: fieldId,
648
+ placeholder: field.placeholder,
649
+ error: meta.touched && !!meta.error,
650
+ disabled: field.disabled,
651
+ "aria-label": field.label
652
+ }
653
+ ),
654
+ field.type === "file" && /* @__PURE__ */ jsxRuntime.jsx(
655
+ inputs.FileInput,
656
+ {
657
+ ...formField,
658
+ id: fieldId,
659
+ accept: field.accept,
660
+ maxSize: field.maxSize || 5 * 1024 * 1024,
661
+ maxFiles: field.maxFiles || 1,
662
+ multiple: field.multiple || false,
663
+ placeholder: field.placeholder || "Choose file(s)...",
664
+ error: meta.touched && !!meta.error,
665
+ disabled: field.disabled || isUploading,
666
+ showProgress: true,
667
+ uploadProgress,
668
+ onChange: (files) => {
669
+ formField.onChange(files);
670
+ if (files.length > 0 && onFileUpload) {
671
+ onFileUpload(files);
672
+ }
673
+ },
674
+ onFileRemove,
675
+ "aria-label": field.label
676
+ }
677
+ ),
678
+ field.type === "rich-text" && /* @__PURE__ */ jsxRuntime.jsx(
679
+ inputs.RichTextEditor,
680
+ {
681
+ ...formField,
682
+ id: fieldId,
683
+ placeholder: field.placeholder,
684
+ error: meta.touched && !!meta.error,
685
+ disabled: field.disabled,
686
+ "aria-label": field.label
687
+ }
688
+ ),
689
+ meta.touched && meta.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: meta.error })
690
+ ] }) });
691
+ }
498
692
  var svgCache = /* @__PURE__ */ new Map();
499
693
  function DynamicIcon({
500
694
  name,
@@ -660,6 +854,197 @@ function AccordionContent({
660
854
  }
661
855
  );
662
856
  }
857
+ function useFileUpload(options) {
858
+ const [uploadTokens, setUploadTokens] = React.useState([]);
859
+ const [uploadProgress, setUploadProgress] = React.useState({});
860
+ const [isUploading, setIsUploading] = React.useState(false);
861
+ const endpoint = options?.endpoint || "https://api.dashtrack.com/contacts/_/contact_form_uploads";
862
+ const uploadFiles = React.useCallback(
863
+ async (files) => {
864
+ if (files.length === 0) return;
865
+ setIsUploading(true);
866
+ try {
867
+ const tokens = [];
868
+ for (const file of files) {
869
+ const formData = new FormData();
870
+ formData.append("contact_form_upload[file_upload]", file);
871
+ formData.append("contact_form_upload[title]", file.name);
872
+ formData.append("contact_form_upload[file_name]", file.name);
873
+ formData.append("contact_form_upload[file_size]", String(file.size));
874
+ const response = await fetch(endpoint, {
875
+ method: "POST",
876
+ body: formData
877
+ });
878
+ if (!response.ok) {
879
+ throw new Error(`Upload failed: ${response.statusText}`);
880
+ }
881
+ const data = await response.json();
882
+ if (data.contact_form_upload?.token) {
883
+ tokens.push(`upload_${data.contact_form_upload.token}`);
884
+ }
885
+ setUploadProgress((prev) => ({
886
+ ...prev,
887
+ [file.name]: 100
888
+ }));
889
+ }
890
+ setUploadTokens(tokens);
891
+ } catch (error) {
892
+ console.error("File upload error:", error);
893
+ options?.onError?.(error);
894
+ } finally {
895
+ setIsUploading(false);
896
+ }
897
+ },
898
+ [endpoint, options]
899
+ );
900
+ const removeFile = React.useCallback((file, index) => {
901
+ setUploadTokens((prev) => prev.filter((_, i) => i !== index));
902
+ setUploadProgress((prev) => {
903
+ const newProgress = { ...prev };
904
+ delete newProgress[file.name];
905
+ return newProgress;
906
+ });
907
+ }, []);
908
+ const resetUpload = React.useCallback(() => {
909
+ setUploadTokens([]);
910
+ setUploadProgress({});
911
+ }, []);
912
+ return {
913
+ uploadTokens,
914
+ uploadProgress,
915
+ isUploading,
916
+ uploadFiles,
917
+ removeFile,
918
+ resetUpload
919
+ };
920
+ }
921
+
922
+ // lib/form-field-types.ts
923
+ function generateInitialValues(fields) {
924
+ return fields.reduce(
925
+ (acc, field) => {
926
+ if (field.type === "checkbox") {
927
+ acc[field.name] = false;
928
+ } else if (field.type === "checkbox-group" || field.type === "multi-select") {
929
+ acc[field.name] = [];
930
+ } else if (field.type === "file") {
931
+ acc[field.name] = [];
932
+ } else if (field.type === "date-range") {
933
+ acc[field.name] = { start: null, end: null };
934
+ } else {
935
+ acc[field.name] = "";
936
+ }
937
+ return acc;
938
+ },
939
+ {}
940
+ );
941
+ }
942
+ function generateValidationSchema(fields) {
943
+ return fields.reduce(
944
+ (acc, field) => {
945
+ acc[field.name] = (value, allValues) => {
946
+ if (field.required) {
947
+ if (!value || typeof value === "string" && !value.trim()) {
948
+ return `${field.label} is required`;
949
+ }
950
+ }
951
+ if (field.type === "email" && value) {
952
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
953
+ return "Please enter a valid email address";
954
+ }
955
+ }
956
+ if (field.type === "url" && value) {
957
+ try {
958
+ new URL(value);
959
+ } catch {
960
+ return "Please enter a valid URL";
961
+ }
962
+ }
963
+ if (field.validator) {
964
+ return field.validator(value, allValues);
965
+ }
966
+ return void 0;
967
+ };
968
+ return acc;
969
+ },
970
+ {}
971
+ );
972
+ }
973
+ function getColumnSpanClass(span) {
974
+ if (!span || span === 12) return "col-span-12";
975
+ return `col-span-12 sm:col-span-${Math.min(span, 12)}`;
976
+ }
977
+
978
+ // lib/forms/use-contact-form.ts
979
+ function useContactForm(options) {
980
+ const {
981
+ formFields,
982
+ formConfig,
983
+ onSubmit,
984
+ onSuccess,
985
+ onError,
986
+ resetOnSuccess = true,
987
+ uploadTokens = []
988
+ } = options;
989
+ const [isSubmitted, setIsSubmitted] = React.useState(false);
990
+ const [submissionError, setSubmissionError] = React.useState(null);
991
+ const form = forms.useForm({
992
+ initialValues: React.useMemo(
993
+ () => generateInitialValues(formFields),
994
+ [formFields]
995
+ ),
996
+ validationSchema: React.useMemo(
997
+ () => generateValidationSchema(formFields),
998
+ [formFields]
999
+ ),
1000
+ onSubmit: async (values, helpers) => {
1001
+ setSubmissionError(null);
1002
+ const shouldAutoSubmit = Boolean(formConfig?.endpoint);
1003
+ if (!shouldAutoSubmit && !onSubmit) {
1004
+ return;
1005
+ }
1006
+ try {
1007
+ let result;
1008
+ const submissionValues = {
1009
+ ...values,
1010
+ ...uploadTokens.length > 0 && {
1011
+ contact_form_upload_tokens: uploadTokens
1012
+ }
1013
+ };
1014
+ if (shouldAutoSubmit) {
1015
+ result = await submitPageSpeedForm(submissionValues, formConfig);
1016
+ }
1017
+ if (onSubmit) {
1018
+ await onSubmit(submissionValues);
1019
+ }
1020
+ if (shouldAutoSubmit || onSubmit) {
1021
+ setIsSubmitted(true);
1022
+ if (resetOnSuccess) {
1023
+ helpers.resetForm();
1024
+ }
1025
+ onSuccess?.(result);
1026
+ setTimeout(() => setIsSubmitted(false), 5e3);
1027
+ }
1028
+ } catch (error) {
1029
+ if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
1030
+ helpers.setErrors(error.formErrors);
1031
+ }
1032
+ const errorMessage = error instanceof Error ? error.message : "Form submission failed";
1033
+ setSubmissionError(errorMessage);
1034
+ onError?.(error);
1035
+ }
1036
+ }
1037
+ });
1038
+ const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
1039
+ return {
1040
+ form,
1041
+ isSubmitted,
1042
+ submissionError,
1043
+ formMethod
1044
+ };
1045
+ }
1046
+
1047
+ // lib/forms.ts
663
1048
  var PageSpeedFormSubmissionError = class extends Error {
664
1049
  constructor(message, options = {}) {
665
1050
  super(message);
@@ -1150,18 +1535,57 @@ var Section = React__namespace.default.forwardRef(
1150
1535
  }
1151
1536
  );
1152
1537
  Section.displayName = "Section";
1538
+ var DEFAULT_FORM_FIELDS = [
1539
+ {
1540
+ name: "name",
1541
+ type: "text",
1542
+ label: "Name",
1543
+ placeholder: "Full Name",
1544
+ required: true,
1545
+ columnSpan: 6
1546
+ },
1547
+ {
1548
+ name: "email",
1549
+ type: "email",
1550
+ label: "Email",
1551
+ placeholder: "your@email.com",
1552
+ required: true,
1553
+ columnSpan: 6
1554
+ },
1555
+ {
1556
+ name: "subject",
1557
+ type: "text",
1558
+ label: "Subject",
1559
+ placeholder: "What is this regarding?",
1560
+ required: true,
1561
+ columnSpan: 12
1562
+ },
1563
+ {
1564
+ name: "message",
1565
+ type: "textarea",
1566
+ label: "Message",
1567
+ placeholder: "Your message...",
1568
+ required: true,
1569
+ rows: 4,
1570
+ columnSpan: 12
1571
+ }
1572
+ ];
1153
1573
  function ContactFaq({
1154
1574
  heading,
1155
1575
  description,
1156
1576
  formHeading,
1157
- buttonText,
1577
+ buttonText = "Submit",
1158
1578
  buttonIcon,
1159
1579
  actions,
1160
1580
  actionsSlot,
1161
1581
  items,
1162
1582
  itemsSlot,
1163
1583
  faqHeading,
1584
+ formFields = DEFAULT_FORM_FIELDS,
1585
+ successMessage = "Thank you! Your message has been sent successfully.",
1586
+ errorMessage = "There was an error sending your message. Please try again.",
1164
1587
  className,
1588
+ containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
1165
1589
  headerClassName,
1166
1590
  headingClassName,
1167
1591
  descriptionClassName,
@@ -1177,9 +1601,10 @@ function ContactFaq({
1177
1601
  accordionTriggerClassName,
1178
1602
  accordionContentClassName,
1179
1603
  gridClassName,
1604
+ successMessageClassName,
1605
+ errorMessageClassName,
1180
1606
  background,
1181
1607
  spacing = "py-8 md:py-32",
1182
- containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
1183
1608
  pattern,
1184
1609
  patternOpacity,
1185
1610
  formConfig,
@@ -1187,54 +1612,27 @@ function ContactFaq({
1187
1612
  onSuccess,
1188
1613
  onError
1189
1614
  }) {
1190
- const form = forms.useForm({
1191
- initialValues: {
1192
- name: "",
1193
- email: "",
1194
- subject: "",
1195
- message: ""
1615
+ const {
1616
+ uploadTokens,
1617
+ uploadProgress,
1618
+ isUploading,
1619
+ uploadFiles,
1620
+ removeFile,
1621
+ resetUpload
1622
+ } = useFileUpload({ onError });
1623
+ const { form, isSubmitted, submissionError, formMethod } = useContactForm({
1624
+ formFields,
1625
+ formConfig,
1626
+ onSubmit,
1627
+ onSuccess: (data) => {
1628
+ resetUpload();
1629
+ onSuccess?.(data);
1196
1630
  },
1197
- validationSchema: {
1198
- name: (value) => !value ? "Name is required" : void 0,
1199
- email: (value) => {
1200
- if (!value) return "Email is required";
1201
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
1202
- return "Please enter a valid email address";
1203
- return void 0;
1204
- },
1205
- subject: (value) => !value ? "Subject is required" : void 0,
1206
- message: (value) => !value ? "Message is required" : void 0
1207
- },
1208
- onSubmit: async (values, helpers) => {
1209
- const shouldAutoSubmit = Boolean(formConfig?.endpoint);
1210
- if (!shouldAutoSubmit && !onSubmit) {
1211
- return;
1212
- }
1213
- try {
1214
- let result;
1215
- if (shouldAutoSubmit) {
1216
- result = await submitPageSpeedForm(values, formConfig);
1217
- }
1218
- if (onSubmit) {
1219
- await onSubmit(values);
1220
- }
1221
- if (shouldAutoSubmit || onSubmit) {
1222
- if (formConfig?.resetOnSuccess !== false) {
1223
- helpers.resetForm();
1224
- }
1225
- onSuccess?.(result);
1226
- }
1227
- } catch (error) {
1228
- if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
1229
- helpers.setErrors(error.formErrors);
1230
- }
1231
- onError?.(error);
1232
- throw error;
1233
- }
1234
- }
1631
+ onError,
1632
+ resetOnSuccess: formConfig?.resetOnSuccess !== false,
1633
+ uploadTokens
1235
1634
  });
1236
- const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
1237
- const actionsContent = React__namespace.useMemo(() => {
1635
+ const actionsContent = React.useMemo(() => {
1238
1636
  if (actionsSlot) return actionsSlot;
1239
1637
  if (actions && actions.length > 0) {
1240
1638
  return actions.map((action, index) => {
@@ -1295,8 +1693,7 @@ function ContactFaq({
1295
1693
  accordionClassName,
1296
1694
  accordionItemClassName,
1297
1695
  accordionTriggerClassName,
1298
- accordionContentClassName,
1299
- background
1696
+ accordionContentClassName
1300
1697
  ]);
1301
1698
  return /* @__PURE__ */ jsxRuntime.jsx(
1302
1699
  Section,
@@ -1359,6 +1756,26 @@ function ContactFaq({
1359
1756
  children: formHeading
1360
1757
  }
1361
1758
  ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: formHeadingClassName, children: formHeading })),
1759
+ isSubmitted && /* @__PURE__ */ jsxRuntime.jsx(
1760
+ "div",
1761
+ {
1762
+ className: cn(
1763
+ "mb-6 p-4 bg-primary/10 border border-primary rounded-md",
1764
+ successMessageClassName
1765
+ ),
1766
+ children: typeof successMessage === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-primary-foreground/90 text-center", children: successMessage }) : successMessage
1767
+ }
1768
+ ),
1769
+ submissionError && /* @__PURE__ */ jsxRuntime.jsx(
1770
+ "div",
1771
+ {
1772
+ className: cn(
1773
+ "mb-6 p-4 bg-destructive/10 border border-destructive rounded-md",
1774
+ errorMessageClassName
1775
+ ),
1776
+ children: typeof errorMessage === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive text-center", children: submissionError }) : errorMessage
1777
+ }
1778
+ ),
1362
1779
  /* @__PURE__ */ jsxRuntime.jsxs(
1363
1780
  forms.Form,
1364
1781
  {
@@ -1367,62 +1784,23 @@ function ContactFaq({
1367
1784
  method: formMethod,
1368
1785
  className: cn("space-y-4", formClassName),
1369
1786
  children: [
1370
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
1371
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "name", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1372
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "name", children: "Name" }),
1373
- /* @__PURE__ */ jsxRuntime.jsx(
1374
- inputs.TextInput,
1375
- {
1376
- ...field,
1377
- id: "name",
1378
- placeholder: "John Doe",
1379
- error: meta.touched && !!meta.error,
1380
- "aria-label": "Name"
1381
- }
1382
- )
1383
- ] }) }),
1384
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "email", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1385
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "email", children: "Email" }),
1386
- /* @__PURE__ */ jsxRuntime.jsx(
1387
- inputs.TextInput,
1787
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-12 gap-4", children: formFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
1788
+ "div",
1789
+ {
1790
+ className: getColumnSpanClass(field.columnSpan),
1791
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1792
+ DynamicFormField,
1388
1793
  {
1389
- ...field,
1390
- id: "email",
1391
- type: "email",
1392
- placeholder: "john@example.com",
1393
- error: meta.touched && !!meta.error,
1394
- "aria-label": "Email"
1794
+ field,
1795
+ uploadProgress,
1796
+ onFileUpload: uploadFiles,
1797
+ onFileRemove: removeFile,
1798
+ isUploading
1395
1799
  }
1396
1800
  )
1397
- ] }) })
1398
- ] }),
1399
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "subject", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1400
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "subject", children: "Subject" }),
1401
- /* @__PURE__ */ jsxRuntime.jsx(
1402
- inputs.TextInput,
1403
- {
1404
- ...field,
1405
- id: "subject",
1406
- placeholder: "What is this regarding?",
1407
- error: meta.touched && !!meta.error,
1408
- "aria-label": "Subject"
1409
- }
1410
- )
1411
- ] }) }),
1412
- /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "message", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1413
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "message", children: "Message" }),
1414
- /* @__PURE__ */ jsxRuntime.jsx(
1415
- inputs.TextArea,
1416
- {
1417
- ...field,
1418
- id: "message",
1419
- placeholder: "Your question...",
1420
- rows: 4,
1421
- error: meta.touched && !!meta.error,
1422
- "aria-label": "Message"
1423
- }
1424
- )
1425
- ] }) }),
1801
+ },
1802
+ field.name
1803
+ )) }),
1426
1804
  actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxRuntime.jsxs(
1427
1805
  Pressable,
1428
1806
  {