@opensite/ui 2.1.9 → 2.2.2

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.
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
- import { FormFieldConfig, PageSpeedFormConfig } from '@page-speed/forms/integration';
2
+ import { FormEngineProps } from '@page-speed/forms/integration';
3
3
  import { f as SectionBackground, g as SectionSpacing, t as PatternName } from './community-initiatives-Bi_ClKrO.js';
4
- import { A as ActionConfig } from './blocks-k17uluAz.js';
4
+ import { C as ContactDarkSocialLink } from './contact-dark-tweUQnN1.js';
5
5
  import 'react/jsx-runtime';
6
+ import './blocks-k17uluAz.js';
6
7
  import 'class-variance-authority';
7
8
  import './button-variants-lRElsmTc.js';
8
9
  import 'class-variance-authority/types';
@@ -30,10 +31,6 @@ interface ContactCardProps {
30
31
  * Description text below the heading
31
32
  */
32
33
  description?: React.ReactNode;
33
- /**
34
- * Form card heading
35
- */
36
- formHeading?: React.ReactNode;
37
34
  /**
38
35
  * Submit button text
39
36
  */
@@ -43,15 +40,7 @@ interface ContactCardProps {
43
40
  */
44
41
  buttonIcon?: React.ReactNode;
45
42
  /**
46
- * Array of action configurations for additional buttons
47
- */
48
- actions?: ActionConfig[];
49
- /**
50
- * Custom slot for rendering actions (overrides actions array and default submit)
51
- */
52
- actionsSlot?: React.ReactNode;
53
- /**
54
- * Contact options to display
43
+ * Contact options to display with icons
55
44
  */
56
45
  contactOptions?: ContactOption[];
57
46
  /**
@@ -59,15 +48,17 @@ interface ContactCardProps {
59
48
  */
60
49
  contactOptionsSlot?: React.ReactNode;
61
50
  /**
62
- * Array of form field configurations
63
- * If not provided, defaults to: firstName, lastName, email, message, privacyPolicy
51
+ * Social media links to display
52
+ */
53
+ socialLinks?: ContactDarkSocialLink[];
54
+ /**
55
+ * Custom slot for rendering social links (overrides socialLinks array)
64
56
  */
65
- formFields?: FormFieldConfig[];
57
+ socialLinksSlot?: React.ReactNode;
66
58
  /**
67
- * Success message to display after form submission
68
- * @default "Thank you! We'll be in touch soon."
59
+ * Full form engine setup and props
69
60
  */
70
- successMessage?: React.ReactNode;
61
+ formEngineSetup?: FormEngineProps;
71
62
  /**
72
63
  * Additional CSS classes for the section
73
64
  */
@@ -82,25 +73,9 @@ interface ContactCardProps {
82
73
  */
83
74
  cardClassName?: string;
84
75
  /**
85
- * Additional CSS classes for the form heading
86
- */
87
- formHeadingClassName?: string;
88
- /**
89
- * Additional CSS classes for the form
90
- */
91
- formClassName?: string;
92
- /**
93
- * Additional CSS classes for the submit button
94
- */
95
- submitClassName?: string;
96
- /**
97
- * Additional CSS classes for the success message
98
- */
99
- successMessageClassName?: string;
100
- /**
101
- * Additional CSS classes for the error message
76
+ * Additional CSS classes for the card content
102
77
  */
103
- errorMessageClassName?: string;
78
+ cardContentClassName?: string;
104
79
  /**
105
80
  * Additional CSS classes for the info panel
106
81
  */
@@ -117,13 +92,21 @@ interface ContactCardProps {
117
92
  * Additional CSS classes for the contact options container
118
93
  */
119
94
  contactOptionsClassName?: string;
95
+ /**
96
+ * Additional CSS classes for the social links container
97
+ */
98
+ socialLinksClassName?: string;
99
+ /**
100
+ * Additional CSS classes for the two-column grid wrapper
101
+ */
102
+ gridClassName?: string;
120
103
  /**
121
104
  * Background style for the section
122
105
  */
123
106
  background?: SectionBackground;
124
107
  /**
125
108
  * Vertical spacing for the section
126
- * @default "py-8 md:py-32"
109
+ * @default "py-16 md:py-32"
127
110
  */
128
111
  spacing?: SectionSpacing;
129
112
  /**
@@ -134,43 +117,24 @@ interface ContactCardProps {
134
117
  * Pattern overlay opacity (0-1)
135
118
  */
136
119
  patternOpacity?: number;
137
- /**
138
- * Optional form submission configuration.
139
- * See `FORMS_INTEGRATION_GUIDE.md` for complete examples.
140
- */
141
- formConfig?: PageSpeedFormConfig;
142
- /**
143
- * Optional custom submission handler for maximum flexibility.
144
- */
145
- onSubmit?: (values: Record<string, unknown>) => void | Promise<void>;
146
- /**
147
- * Optional success callback invoked after successful submission.
148
- */
149
- onSuccess?: (data: unknown) => void;
150
- /**
151
- * Optional error callback invoked if submission fails.
152
- */
153
- onError?: (error: Error) => void;
154
- /**
155
- * Additional CSS classes for the actions container
156
- */
157
- actionsClassName?: string;
158
120
  }
159
121
  /**
160
- * ContactCard - A simple contact form with card layout and contact information sidebar.
161
- * Perfect for basic contact pages with multiple contact methods displayed.
122
+ * ContactCard - A split-layout contact form with a card-wrapped form on one side
123
+ * and heading, description, and icon-based contact details on the other.
162
124
  *
163
125
  * @example
164
126
  * ```tsx
165
127
  * <ContactCard
166
- * heading="Get in Touch"
167
- * description="Have questions? We'd love to hear from you."
168
- * formHeading="Contact us"
169
- * buttonText="Send Message"
170
- * formConfig={{ endpoint: "/api/contact", format: "json" }}
128
+ * heading="Get In Touch"
129
+ * description="We'd love to hear from you."
130
+ * contactOptions={[
131
+ * { icon: "Phone", info: "+1 (555) 987-6543", href: "tel:+15559876543" },
132
+ * { icon: "Mail", info: "support@example.com", href: "mailto:support@example.com" },
133
+ * ]}
134
+ * formEngineSetup={{ formConfig: { endpoint: "/api/contact", format: "json" } }}
171
135
  * />
172
136
  * ```
173
137
  */
174
- declare function ContactCard({ heading, description, actions, actionsSlot, actionsClassName, contactOptions, contactOptionsSlot, formFields, successMessage, className, containerClassName, cardClassName, formClassName, successMessageClassName, errorMessageClassName, infoPanelClassName, headingClassName, descriptionClassName, contactOptionsClassName, background, spacing, pattern, patternOpacity, formConfig, onSubmit, onSuccess, onError, }: ContactCardProps): React.JSX.Element;
138
+ declare function ContactCard({ heading, description, buttonText, buttonIcon, contactOptions, contactOptionsSlot, socialLinks, socialLinksSlot, formEngineSetup, className, containerClassName, cardClassName, cardContentClassName, infoPanelClassName, headingClassName, descriptionClassName, contactOptionsClassName, socialLinksClassName, gridClassName, background, spacing, pattern, patternOpacity, }: ContactCardProps): React.JSX.Element;
175
139
 
176
140
  export { ContactCard, type ContactCardProps };
@@ -1,13 +1,12 @@
1
1
  "use client";
2
2
  import * as React from 'react';
3
- import React__default, { useMemo } from 'react';
4
- import { Form } from '@page-speed/forms';
3
+ import React__default from 'react';
4
+ import { FormEngine } from '@page-speed/forms/integration';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import { cva } from 'class-variance-authority';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
9
  import { Icon } from '@page-speed/icon';
10
- import { useContactForm, getColumnSpanClass, DynamicFormField } from '@page-speed/forms/integration';
11
10
 
12
11
  // components/blocks/contact/contact-card.tsx
13
12
  function cn(...inputs) {
@@ -448,6 +447,16 @@ function Card({ className, ...props }) {
448
447
  }
449
448
  );
450
449
  }
450
+ function CardContent({ className, ...props }) {
451
+ return /* @__PURE__ */ jsx(
452
+ "div",
453
+ {
454
+ "data-slot": "card-content",
455
+ className: cn("px-6", className),
456
+ ...props
457
+ }
458
+ );
459
+ }
451
460
  var maxWidthStyles = {
452
461
  sm: "max-w-screen-sm",
453
462
  md: "max-w-screen-md",
@@ -823,168 +832,115 @@ var Section = React__default.forwardRef(
823
832
  }
824
833
  );
825
834
  Section.displayName = "Section";
826
- var MOBILE_CLASSES = {
827
- "fit-left": "items-start md:items-center",
828
- "fit-center": "items-center",
829
- "fit-right": "items-end md:items-center",
830
- "full-left": "items-stretch md:items-center",
831
- "full-center": "items-stretch md:items-center",
832
- "full-right": "items-stretch md:items-center"
835
+ var DEFAULT_STYLE_RULES = {
836
+ formContainer: "",
837
+ fieldsContainer: "",
838
+ fieldClassName: "",
839
+ formClassName: "space-y-6"
833
840
  };
834
- function BlockActions({
835
- mobileConfig,
836
- actionsClassName,
837
- verticalSpacing = "mt-4 md:mt-8",
838
- actions,
839
- actionsSlot
840
- }) {
841
- const width = mobileConfig?.width ?? "full";
842
- const position = mobileConfig?.position ?? "center";
843
- const mobileLayoutClass = MOBILE_CLASSES[`${width}-${position}`];
844
- if (actionsSlot) {
845
- return /* @__PURE__ */ jsx("div", { children: actionsSlot });
846
- } else if (actions && actions?.length > 0) {
847
- return /* @__PURE__ */ jsx(
848
- "div",
849
- {
850
- className: cn(
851
- "flex flex-col md:flex-row flex-wrap gap-4",
852
- mobileLayoutClass,
853
- actionsClassName,
854
- verticalSpacing
855
- ),
856
- children: actions.map((action, index) => /* @__PURE__ */ jsx(ActionComponent, { action }, index))
857
- }
858
- );
859
- } else {
860
- return null;
861
- }
862
- }
863
- function ActionComponent({ action }) {
864
- const {
865
- label,
866
- icon,
867
- iconAfter,
868
- children,
869
- href,
870
- onClick,
871
- className: actionClassName,
872
- ...pressableProps
873
- } = action;
874
- return /* @__PURE__ */ jsx(
875
- Pressable,
876
- {
877
- href,
878
- onClick,
879
- asButton: action.asButton ?? true,
880
- className: actionClassName,
881
- ...pressableProps,
882
- children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
883
- icon,
884
- label,
885
- iconAfter
886
- ] })
887
- }
888
- );
889
- }
890
841
  var DEFAULT_FORM_FIELDS = [
891
842
  {
892
- name: "firstName",
843
+ name: "fullName",
893
844
  type: "text",
894
- label: "First Name",
895
- placeholder: "John",
845
+ label: "Full Name",
846
+ placeholder: "Full Name",
896
847
  required: true,
897
848
  columnSpan: 6
898
849
  },
899
850
  {
900
- name: "lastName",
901
- type: "text",
902
- label: "Last Name",
903
- placeholder: "Doe",
851
+ name: "email",
852
+ type: "email",
853
+ label: "Email",
854
+ placeholder: "your@email.com",
904
855
  required: true,
905
856
  columnSpan: 6
906
857
  },
907
858
  {
908
- name: "email",
909
- type: "email",
910
- label: "Email Address",
911
- placeholder: "john@example.com",
912
- required: true,
859
+ name: "phone",
860
+ type: "tel",
861
+ label: "Phone",
862
+ placeholder: "+1 (555) 000-0000",
913
863
  columnSpan: 12
914
864
  },
915
865
  {
916
866
  name: "message",
917
867
  type: "textarea",
918
- label: "Message",
919
- placeholder: "How can we help you?",
868
+ label: "Your Message",
869
+ placeholder: "How can we help you today?",
920
870
  required: true,
921
871
  rows: 4,
922
872
  columnSpan: 12
923
- },
924
- {
925
- name: "privacyPolicy",
926
- type: "checkbox",
927
- label: "I agree to the privacy policy",
928
- required: true,
929
- columnSpan: 12
930
873
  }
931
874
  ];
932
875
  function ContactCard({
933
- heading,
934
- description,
935
- actions,
936
- actionsSlot,
937
- actionsClassName,
876
+ heading = "Get In Touch",
877
+ description = "We'd love to hear from you. Send us a message and we'll respond as soon as possible.",
878
+ buttonText = "Send Message",
879
+ buttonIcon,
938
880
  contactOptions,
939
881
  contactOptionsSlot,
940
- formFields,
941
- successMessage = "Thank you! We'll be in touch soon.",
882
+ socialLinks,
883
+ socialLinksSlot,
884
+ formEngineSetup,
942
885
  className,
943
886
  containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
944
887
  cardClassName,
945
- formClassName,
946
- successMessageClassName,
947
- errorMessageClassName,
888
+ cardContentClassName,
948
889
  infoPanelClassName,
949
890
  headingClassName,
950
891
  descriptionClassName,
951
892
  contactOptionsClassName,
893
+ socialLinksClassName,
894
+ gridClassName,
952
895
  background,
953
896
  spacing = "py-16 md:py-32",
954
897
  pattern,
955
- patternOpacity,
956
- formConfig,
957
- onSubmit,
958
- onSuccess,
959
- onError
898
+ patternOpacity
960
899
  }) {
961
- const fields = useMemo(() => formFields || DEFAULT_FORM_FIELDS, [formFields]);
962
- const { form, submissionError, formMethod, resetSubmissionState } = useContactForm({
963
- formFields: fields,
964
- formConfig,
965
- onSubmit,
966
- onSuccess,
967
- onError
968
- });
969
- const contactOptionsContent = useMemo(() => {
900
+ const contactOptionsContent = React.useMemo(() => {
970
901
  if (contactOptionsSlot) return contactOptionsSlot;
971
902
  if (contactOptions && contactOptions.length > 0) {
972
- return contactOptions.map((option, key) => /* @__PURE__ */ jsxs(
903
+ return contactOptions.map((option, key) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
904
+ /* @__PURE__ */ jsx(
905
+ DynamicIcon,
906
+ {
907
+ name: option.icon,
908
+ size: 20,
909
+ className: "shrink-0 text-muted-foreground"
910
+ }
911
+ ),
912
+ option.href ? /* @__PURE__ */ jsx(
913
+ Pressable,
914
+ {
915
+ href: option.href,
916
+ className: "text-sm font-medium text-muted-foreground",
917
+ children: option.info
918
+ }
919
+ ) : /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: option.info })
920
+ ] }, key));
921
+ }
922
+ return null;
923
+ }, [contactOptionsSlot, contactOptions]);
924
+ const socialLinksContent = React.useMemo(() => {
925
+ if (socialLinksSlot) return socialLinksSlot;
926
+ if (socialLinks && socialLinks.length > 0) {
927
+ return socialLinks.map((social, key) => /* @__PURE__ */ jsx(
973
928
  Pressable,
974
929
  {
975
- variant: "link",
976
- href: option.href,
977
- className: "gap-4 font-bold",
978
- children: [
979
- /* @__PURE__ */ jsx(DynamicIcon, { name: option.icon, size: 20 }),
980
- option.info
981
- ]
930
+ href: social.href,
931
+ className: cn(
932
+ "flex h-9 w-9 items-center justify-center",
933
+ "rounded-full transition-colors",
934
+ "text-muted-foreground hover:text-foreground"
935
+ ),
936
+ "aria-label": social.label,
937
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: social.icon, size: 18 })
982
938
  },
983
939
  key
984
940
  ));
985
941
  }
986
942
  return null;
987
- }, [contactOptionsSlot, contactOptions]);
943
+ }, [socialLinksSlot, socialLinks]);
988
944
  return /* @__PURE__ */ jsx(
989
945
  Section,
990
946
  {
@@ -994,82 +950,89 @@ function ContactCard({
994
950
  patternOpacity,
995
951
  className,
996
952
  containerClassName,
997
- children: /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs("div", { className: "grid items-start gap-10 md:gap-12 lg:grid-cols-2", children: [
998
- /* @__PURE__ */ jsx(Card, { className: cn("p-6 lg:p-8 order-2 md:order-1", cardClassName), children: /* @__PURE__ */ jsxs(
999
- Form,
1000
- {
1001
- form,
1002
- action: formConfig?.endpoint,
1003
- method: formMethod,
1004
- submissionError,
1005
- successMessage,
1006
- successMessageClassName,
1007
- errorMessageClassName,
1008
- submissionConfig: formConfig?.submissionConfig,
1009
- onNewSubmission: resetSubmissionState,
1010
- className: cn("space-y-6", formClassName),
1011
- children: [
1012
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-12 gap-6", children: fields.map((field) => /* @__PURE__ */ jsx(
1013
- "div",
1014
- {
1015
- className: getColumnSpanClass(field.columnSpan),
1016
- children: /* @__PURE__ */ jsx(DynamicFormField, { field })
953
+ children: /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs(
954
+ "div",
955
+ {
956
+ className: cn(
957
+ "grid items-start gap-10 md:gap-12 grid-cols-1 lg:grid-cols-2",
958
+ gridClassName
959
+ ),
960
+ children: [
961
+ /* @__PURE__ */ jsx(Card, { className: cn("order-2 lg:order-1 pt-0 pb-0", cardClassName), children: /* @__PURE__ */ jsx(CardContent, { className: cn("p-6 lg:p-8", cardContentClassName), children: formEngineSetup ? /* @__PURE__ */ jsx(
962
+ FormEngine,
963
+ {
964
+ formEngineSetup: {
965
+ ...formEngineSetup,
966
+ formLayoutSettings: {
967
+ ...formEngineSetup.formLayoutSettings,
968
+ formLayout: "standard",
969
+ submitButtonSetup: {
970
+ ...formEngineSetup.formLayoutSettings?.submitButtonSetup,
971
+ submitLabel: /* @__PURE__ */ jsxs(Fragment, { children: [
972
+ buttonIcon,
973
+ buttonText
974
+ ] })
975
+ }
976
+ }
1017
977
  },
1018
- field.name
1019
- )) }),
1020
- /* @__PURE__ */ jsx(
1021
- BlockActions,
1022
- {
1023
- actions,
1024
- actionsSlot,
1025
- actionsClassName
1026
- }
1027
- )
1028
- ]
1029
- }
1030
- ) }),
1031
- /* @__PURE__ */ jsxs(
1032
- "div",
1033
- {
1034
- className: cn(
1035
- "flex flex-col items-start gap-2 md:gap-4 order-1 md:order-2",
1036
- infoPanelClassName
1037
- ),
1038
- children: [
1039
- heading && (typeof heading === "string" ? /* @__PURE__ */ jsx(
1040
- "h2",
1041
- {
1042
- className: cn(
1043
- "text-4xl lg:text-6xl font-bold tracking-tight text-pretty",
1044
- headingClassName
1045
- ),
1046
- children: heading
1047
- }
1048
- ) : heading),
1049
- description && (typeof description === "string" ? /* @__PURE__ */ jsx(
1050
- "p",
1051
- {
1052
- className: cn(
1053
- "leading-relaxed text-pretty text-lg opacity-70",
1054
- descriptionClassName
1055
- ),
1056
- children: description
1057
- }
1058
- ) : description),
1059
- /* @__PURE__ */ jsx(
1060
- "div",
1061
- {
1062
- className: cn(
1063
- "mt-4 space-y-4 w-full md:w-fit px-6 py-6 md:px-12 md:py-8 bg-muted rounded-xl text-sm md:text-normal flex flex-col items-start gap-1",
1064
- contactOptionsClassName
978
+ defaultFields: DEFAULT_FORM_FIELDS,
979
+ defaultStyleRules: DEFAULT_STYLE_RULES
980
+ }
981
+ ) : null }) }),
982
+ /* @__PURE__ */ jsxs(
983
+ "div",
984
+ {
985
+ className: cn(
986
+ "flex flex-col items-start gap-2 md:gap-4 order-1 lg:order-2",
987
+ infoPanelClassName
988
+ ),
989
+ children: [
990
+ heading && (typeof heading === "string" ? /* @__PURE__ */ jsx(
991
+ "h2",
992
+ {
993
+ className: cn(
994
+ "text-4xl lg:text-6xl font-bold tracking-tight text-pretty",
995
+ headingClassName
996
+ ),
997
+ children: heading
998
+ }
999
+ ) : heading),
1000
+ description && (typeof description === "string" ? /* @__PURE__ */ jsx(
1001
+ "p",
1002
+ {
1003
+ className: cn(
1004
+ "leading-relaxed text-pretty text-lg text-muted-foreground",
1005
+ descriptionClassName
1006
+ ),
1007
+ children: description
1008
+ }
1009
+ ) : description),
1010
+ contactOptionsContent && /* @__PURE__ */ jsx(
1011
+ "div",
1012
+ {
1013
+ className: cn(
1014
+ "mt-4 w-full rounded-xl bg-muted px-6 py-6 md:px-8 md:py-8 space-y-6",
1015
+ contactOptionsClassName
1016
+ ),
1017
+ children: contactOptionsContent
1018
+ }
1065
1019
  ),
1066
- children: contactOptionsContent
1067
- }
1068
- )
1069
- ]
1070
- }
1071
- )
1072
- ] }) })
1020
+ socialLinksContent && /* @__PURE__ */ jsx(
1021
+ "div",
1022
+ {
1023
+ className: cn(
1024
+ "mt-2 flex items-center gap-2",
1025
+ socialLinksClassName
1026
+ ),
1027
+ children: socialLinksContent
1028
+ }
1029
+ )
1030
+ ]
1031
+ }
1032
+ )
1033
+ ]
1034
+ }
1035
+ ) })
1073
1036
  }
1074
1037
  );
1075
1038
  }