@lub-crm/forms 1.0.0

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 (52) hide show
  1. package/README.md +298 -0
  2. package/dist/lub-forms.css +1 -0
  3. package/dist/lub-forms.es.js +5848 -0
  4. package/dist/lub-forms.es.js.map +1 -0
  5. package/dist/lub-forms.standalone.js +10 -0
  6. package/dist/lub-forms.standalone.js.map +1 -0
  7. package/dist/lub-forms.umd.js +227 -0
  8. package/dist/lub-forms.umd.js.map +1 -0
  9. package/package.json +68 -0
  10. package/src/api/client.ts +115 -0
  11. package/src/api/index.ts +2 -0
  12. package/src/api/types.ts +202 -0
  13. package/src/core/FormProvider.tsx +228 -0
  14. package/src/core/FormRenderer.tsx +134 -0
  15. package/src/core/LubForm.tsx +476 -0
  16. package/src/core/StepManager.tsx +199 -0
  17. package/src/core/index.ts +4 -0
  18. package/src/embed.ts +188 -0
  19. package/src/fields/CheckboxField.tsx +62 -0
  20. package/src/fields/CheckboxGroupField.tsx +57 -0
  21. package/src/fields/CountryField.tsx +43 -0
  22. package/src/fields/DateField.tsx +33 -0
  23. package/src/fields/DateTimeField.tsx +33 -0
  24. package/src/fields/DividerField.tsx +16 -0
  25. package/src/fields/FieldWrapper.tsx +60 -0
  26. package/src/fields/FileField.tsx +45 -0
  27. package/src/fields/HiddenField.tsx +18 -0
  28. package/src/fields/HtmlField.tsx +17 -0
  29. package/src/fields/NumberField.tsx +39 -0
  30. package/src/fields/RadioField.tsx +57 -0
  31. package/src/fields/RecaptchaField.tsx +137 -0
  32. package/src/fields/SelectField.tsx +49 -0
  33. package/src/fields/StateField.tsx +84 -0
  34. package/src/fields/TextField.tsx +51 -0
  35. package/src/fields/TextareaField.tsx +37 -0
  36. package/src/fields/TimeField.tsx +33 -0
  37. package/src/fields/index.ts +84 -0
  38. package/src/hooks/index.ts +4 -0
  39. package/src/hooks/useConditionalLogic.ts +59 -0
  40. package/src/hooks/useFormApi.ts +118 -0
  41. package/src/hooks/useFormDesign.ts +48 -0
  42. package/src/hooks/useMultiStep.ts +98 -0
  43. package/src/index.ts +101 -0
  44. package/src/main.tsx +40 -0
  45. package/src/styles/index.css +707 -0
  46. package/src/utils/cn.ts +6 -0
  47. package/src/utils/countries.ts +163 -0
  48. package/src/utils/css-variables.ts +63 -0
  49. package/src/utils/index.ts +3 -0
  50. package/src/validation/conditional.ts +170 -0
  51. package/src/validation/index.ts +2 -0
  52. package/src/validation/schema-builder.ts +327 -0
@@ -0,0 +1,163 @@
1
+ // Country data for country/state fields
2
+
3
+ export interface Country {
4
+ code: string;
5
+ name: string;
6
+ }
7
+
8
+ export interface State {
9
+ code: string;
10
+ name: string;
11
+ countryCode: string;
12
+ }
13
+
14
+ // ISO 3166-1 alpha-2 country codes - most common countries
15
+ export const countries: Country[] = [
16
+ { code: "US", name: "United States" },
17
+ { code: "CA", name: "Canada" },
18
+ { code: "GB", name: "United Kingdom" },
19
+ { code: "AU", name: "Australia" },
20
+ { code: "DE", name: "Germany" },
21
+ { code: "FR", name: "France" },
22
+ { code: "ES", name: "Spain" },
23
+ { code: "IT", name: "Italy" },
24
+ { code: "NL", name: "Netherlands" },
25
+ { code: "BE", name: "Belgium" },
26
+ { code: "AT", name: "Austria" },
27
+ { code: "CH", name: "Switzerland" },
28
+ { code: "SE", name: "Sweden" },
29
+ { code: "NO", name: "Norway" },
30
+ { code: "DK", name: "Denmark" },
31
+ { code: "FI", name: "Finland" },
32
+ { code: "IE", name: "Ireland" },
33
+ { code: "PT", name: "Portugal" },
34
+ { code: "PL", name: "Poland" },
35
+ { code: "CZ", name: "Czech Republic" },
36
+ { code: "JP", name: "Japan" },
37
+ { code: "KR", name: "South Korea" },
38
+ { code: "CN", name: "China" },
39
+ { code: "IN", name: "India" },
40
+ { code: "BR", name: "Brazil" },
41
+ { code: "MX", name: "Mexico" },
42
+ { code: "AR", name: "Argentina" },
43
+ { code: "CL", name: "Chile" },
44
+ { code: "CO", name: "Colombia" },
45
+ { code: "PE", name: "Peru" },
46
+ { code: "NZ", name: "New Zealand" },
47
+ { code: "SG", name: "Singapore" },
48
+ { code: "HK", name: "Hong Kong" },
49
+ { code: "TW", name: "Taiwan" },
50
+ { code: "MY", name: "Malaysia" },
51
+ { code: "TH", name: "Thailand" },
52
+ { code: "PH", name: "Philippines" },
53
+ { code: "ID", name: "Indonesia" },
54
+ { code: "VN", name: "Vietnam" },
55
+ { code: "ZA", name: "South Africa" },
56
+ { code: "AE", name: "United Arab Emirates" },
57
+ { code: "SA", name: "Saudi Arabia" },
58
+ { code: "IL", name: "Israel" },
59
+ { code: "TR", name: "Turkey" },
60
+ { code: "RU", name: "Russia" },
61
+ { code: "UA", name: "Ukraine" },
62
+ { code: "GR", name: "Greece" },
63
+ { code: "HU", name: "Hungary" },
64
+ { code: "RO", name: "Romania" },
65
+ ];
66
+
67
+ // US States
68
+ export const usStates: State[] = [
69
+ { code: "AL", name: "Alabama", countryCode: "US" },
70
+ { code: "AK", name: "Alaska", countryCode: "US" },
71
+ { code: "AZ", name: "Arizona", countryCode: "US" },
72
+ { code: "AR", name: "Arkansas", countryCode: "US" },
73
+ { code: "CA", name: "California", countryCode: "US" },
74
+ { code: "CO", name: "Colorado", countryCode: "US" },
75
+ { code: "CT", name: "Connecticut", countryCode: "US" },
76
+ { code: "DE", name: "Delaware", countryCode: "US" },
77
+ { code: "FL", name: "Florida", countryCode: "US" },
78
+ { code: "GA", name: "Georgia", countryCode: "US" },
79
+ { code: "HI", name: "Hawaii", countryCode: "US" },
80
+ { code: "ID", name: "Idaho", countryCode: "US" },
81
+ { code: "IL", name: "Illinois", countryCode: "US" },
82
+ { code: "IN", name: "Indiana", countryCode: "US" },
83
+ { code: "IA", name: "Iowa", countryCode: "US" },
84
+ { code: "KS", name: "Kansas", countryCode: "US" },
85
+ { code: "KY", name: "Kentucky", countryCode: "US" },
86
+ { code: "LA", name: "Louisiana", countryCode: "US" },
87
+ { code: "ME", name: "Maine", countryCode: "US" },
88
+ { code: "MD", name: "Maryland", countryCode: "US" },
89
+ { code: "MA", name: "Massachusetts", countryCode: "US" },
90
+ { code: "MI", name: "Michigan", countryCode: "US" },
91
+ { code: "MN", name: "Minnesota", countryCode: "US" },
92
+ { code: "MS", name: "Mississippi", countryCode: "US" },
93
+ { code: "MO", name: "Missouri", countryCode: "US" },
94
+ { code: "MT", name: "Montana", countryCode: "US" },
95
+ { code: "NE", name: "Nebraska", countryCode: "US" },
96
+ { code: "NV", name: "Nevada", countryCode: "US" },
97
+ { code: "NH", name: "New Hampshire", countryCode: "US" },
98
+ { code: "NJ", name: "New Jersey", countryCode: "US" },
99
+ { code: "NM", name: "New Mexico", countryCode: "US" },
100
+ { code: "NY", name: "New York", countryCode: "US" },
101
+ { code: "NC", name: "North Carolina", countryCode: "US" },
102
+ { code: "ND", name: "North Dakota", countryCode: "US" },
103
+ { code: "OH", name: "Ohio", countryCode: "US" },
104
+ { code: "OK", name: "Oklahoma", countryCode: "US" },
105
+ { code: "OR", name: "Oregon", countryCode: "US" },
106
+ { code: "PA", name: "Pennsylvania", countryCode: "US" },
107
+ { code: "RI", name: "Rhode Island", countryCode: "US" },
108
+ { code: "SC", name: "South Carolina", countryCode: "US" },
109
+ { code: "SD", name: "South Dakota", countryCode: "US" },
110
+ { code: "TN", name: "Tennessee", countryCode: "US" },
111
+ { code: "TX", name: "Texas", countryCode: "US" },
112
+ { code: "UT", name: "Utah", countryCode: "US" },
113
+ { code: "VT", name: "Vermont", countryCode: "US" },
114
+ { code: "VA", name: "Virginia", countryCode: "US" },
115
+ { code: "WA", name: "Washington", countryCode: "US" },
116
+ { code: "WV", name: "West Virginia", countryCode: "US" },
117
+ { code: "WI", name: "Wisconsin", countryCode: "US" },
118
+ { code: "WY", name: "Wyoming", countryCode: "US" },
119
+ { code: "DC", name: "District of Columbia", countryCode: "US" },
120
+ ];
121
+
122
+ // Canadian Provinces
123
+ export const caProvinces: State[] = [
124
+ { code: "AB", name: "Alberta", countryCode: "CA" },
125
+ { code: "BC", name: "British Columbia", countryCode: "CA" },
126
+ { code: "MB", name: "Manitoba", countryCode: "CA" },
127
+ { code: "NB", name: "New Brunswick", countryCode: "CA" },
128
+ { code: "NL", name: "Newfoundland and Labrador", countryCode: "CA" },
129
+ { code: "NS", name: "Nova Scotia", countryCode: "CA" },
130
+ { code: "ON", name: "Ontario", countryCode: "CA" },
131
+ { code: "PE", name: "Prince Edward Island", countryCode: "CA" },
132
+ { code: "QC", name: "Quebec", countryCode: "CA" },
133
+ { code: "SK", name: "Saskatchewan", countryCode: "CA" },
134
+ { code: "NT", name: "Northwest Territories", countryCode: "CA" },
135
+ { code: "NU", name: "Nunavut", countryCode: "CA" },
136
+ { code: "YT", name: "Yukon", countryCode: "CA" },
137
+ ];
138
+
139
+ // Get states for a country
140
+ export function getStatesForCountry(countryCode: string): State[] {
141
+ switch (countryCode) {
142
+ case "US":
143
+ return usStates;
144
+ case "CA":
145
+ return caProvinces;
146
+ default:
147
+ return [];
148
+ }
149
+ }
150
+
151
+ // Get country by code
152
+ export function getCountryByCode(code: string): Country | undefined {
153
+ return countries.find((c) => c.code === code);
154
+ }
155
+
156
+ // Get state by code
157
+ export function getStateByCode(
158
+ stateCode: string,
159
+ countryCode: string,
160
+ ): State | undefined {
161
+ const states = getStatesForCountry(countryCode);
162
+ return states.find((s) => s.code === stateCode);
163
+ }
@@ -0,0 +1,63 @@
1
+ import type { FormDesign } from "@/api/types";
2
+
3
+ /**
4
+ * Convert FormDesign to CSS custom properties
5
+ */
6
+ export function formDesignToCssVariables(
7
+ design: FormDesign,
8
+ ): Record<string, string> {
9
+ const vars: Record<string, string> = {};
10
+
11
+ // Colors
12
+ if (design.background_color) {
13
+ vars["--lub-bg-color"] = design.background_color;
14
+ }
15
+ if (design.text_color) {
16
+ vars["--lub-text-color"] = design.text_color;
17
+ }
18
+ if (design.primary_color) {
19
+ vars["--lub-primary-color"] = design.primary_color;
20
+ }
21
+ if (design.border_color) {
22
+ vars["--lub-border-color"] = design.border_color;
23
+ }
24
+
25
+ // Typography
26
+ if (design.font_family) {
27
+ vars["--lub-font-family"] = design.font_family;
28
+ }
29
+ if (design.font_size) {
30
+ vars["--lub-font-size"] = design.font_size;
31
+ }
32
+
33
+ // Spacing
34
+ if (design.padding) {
35
+ vars["--lub-padding"] = design.padding;
36
+ }
37
+ if (design.field_spacing) {
38
+ vars["--lub-field-spacing"] = design.field_spacing;
39
+ }
40
+
41
+ // Button styling
42
+ if (design.button_style) {
43
+ if (design.button_style.background_color) {
44
+ vars["--lub-btn-bg"] = design.button_style.background_color;
45
+ }
46
+ if (design.button_style.text_color) {
47
+ vars["--lub-btn-text"] = design.button_style.text_color;
48
+ }
49
+ if (design.button_style.border_radius) {
50
+ vars["--lub-btn-radius"] = design.button_style.border_radius;
51
+ }
52
+ }
53
+
54
+ return vars;
55
+ }
56
+
57
+ /**
58
+ * Apply CSS variables to an element's style
59
+ */
60
+ export function applyCssVariables(design: FormDesign): React.CSSProperties {
61
+ const vars = formDesignToCssVariables(design);
62
+ return vars as React.CSSProperties;
63
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./cn";
2
+ export * from "./css-variables";
3
+ export * from "./countries";
@@ -0,0 +1,170 @@
1
+ import type { Condition, FormField, Operator } from "@/api/types";
2
+
3
+ /**
4
+ * Evaluate if a field should be visible based on its conditional logic
5
+ */
6
+ export function evaluateFieldVisibility(
7
+ field: FormField,
8
+ formValues: Record<string, unknown>,
9
+ ): boolean {
10
+ const logic = field.conditional_logic;
11
+ if (!logic) return true;
12
+
13
+ // Check show_if condition
14
+ if (logic.show_if) {
15
+ return evaluateCondition(logic.show_if, formValues);
16
+ }
17
+
18
+ // Check hide_if condition
19
+ if (logic.hide_if) {
20
+ return !evaluateCondition(logic.hide_if, formValues);
21
+ }
22
+
23
+ return true;
24
+ }
25
+
26
+ /**
27
+ * Evaluate if a field should be required based on its conditional logic
28
+ */
29
+ export function evaluateFieldRequired(
30
+ field: FormField,
31
+ formValues: Record<string, unknown>,
32
+ ): boolean {
33
+ const logic = field.conditional_logic;
34
+
35
+ // Start with base required state
36
+ let isRequired = field.required;
37
+
38
+ // Check required_if condition
39
+ if (logic?.required_if) {
40
+ const conditionMet = evaluateCondition(logic.required_if, formValues);
41
+ isRequired = isRequired || conditionMet;
42
+ }
43
+
44
+ return isRequired;
45
+ }
46
+
47
+ /**
48
+ * Evaluate a condition against form values
49
+ */
50
+ export function evaluateCondition(
51
+ condition: Condition,
52
+ formValues: Record<string, unknown>,
53
+ ): boolean {
54
+ const fieldValue = formValues[condition.field_name];
55
+ let result = compareValues(fieldValue, condition.operator, condition.value);
56
+
57
+ // Evaluate AND conditions
58
+ if (condition.and && condition.and.length > 0) {
59
+ for (const andCond of condition.and) {
60
+ if (!evaluateCondition(andCond, formValues)) {
61
+ return false;
62
+ }
63
+ }
64
+ }
65
+
66
+ // Evaluate OR conditions - at least one must be true
67
+ if (condition.or && condition.or.length > 0) {
68
+ let orResult = false;
69
+ for (const orCond of condition.or) {
70
+ if (evaluateCondition(orCond, formValues)) {
71
+ orResult = true;
72
+ break;
73
+ }
74
+ }
75
+ // For OR conditions to pass, main condition AND at least one OR must be true
76
+ result = result && orResult;
77
+ }
78
+
79
+ return result;
80
+ }
81
+
82
+ /**
83
+ * Compare field value against condition value using the specified operator
84
+ */
85
+ function compareValues(
86
+ fieldValue: unknown,
87
+ operator: Operator,
88
+ compareValue: unknown,
89
+ ): boolean {
90
+ switch (operator) {
91
+ case "equals":
92
+ return normalizeValue(fieldValue) === normalizeValue(compareValue);
93
+
94
+ case "not_equals":
95
+ return normalizeValue(fieldValue) !== normalizeValue(compareValue);
96
+
97
+ case "contains":
98
+ return String(fieldValue ?? "")
99
+ .toLowerCase()
100
+ .includes(String(compareValue).toLowerCase());
101
+
102
+ case "not_contains":
103
+ return !String(fieldValue ?? "")
104
+ .toLowerCase()
105
+ .includes(String(compareValue).toLowerCase());
106
+
107
+ case "greater_than":
108
+ return Number(fieldValue) > Number(compareValue);
109
+
110
+ case "less_than":
111
+ return Number(fieldValue) < Number(compareValue);
112
+
113
+ case "is_empty":
114
+ return isEmpty(fieldValue);
115
+
116
+ case "is_not_empty":
117
+ return !isEmpty(fieldValue);
118
+
119
+ default:
120
+ return true;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Normalize value for comparison
126
+ */
127
+ function normalizeValue(value: unknown): string {
128
+ if (value === null || value === undefined) return "";
129
+ if (Array.isArray(value)) return value.sort().join(",");
130
+ return String(value).toLowerCase().trim();
131
+ }
132
+
133
+ /**
134
+ * Check if value is empty
135
+ */
136
+ function isEmpty(value: unknown): boolean {
137
+ if (value === null || value === undefined) return true;
138
+ if (typeof value === "string") return value.trim() === "";
139
+ if (Array.isArray(value)) return value.length === 0;
140
+ if (typeof value === "object") return Object.keys(value).length === 0;
141
+ return false;
142
+ }
143
+
144
+ /**
145
+ * Get all fields that should be visible based on conditional logic
146
+ */
147
+ export function getVisibleFields(
148
+ fields: FormField[],
149
+ formValues: Record<string, unknown>,
150
+ ): FormField[] {
151
+ return fields.filter((field) => evaluateFieldVisibility(field, formValues));
152
+ }
153
+
154
+ /**
155
+ * Get required field names based on conditional logic
156
+ */
157
+ export function getRequiredFieldNames(
158
+ fields: FormField[],
159
+ formValues: Record<string, unknown>,
160
+ ): Set<string> {
161
+ const required = new Set<string>();
162
+
163
+ for (const field of fields) {
164
+ if (evaluateFieldRequired(field, formValues)) {
165
+ required.add(field.name);
166
+ }
167
+ }
168
+
169
+ return required;
170
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./schema-builder";
2
+ export * from "./conditional";