@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,45 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+
6
+ interface FileFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ export function FileField({ field }: FileFieldProps) {
11
+ const { register, formState } = useFormContext();
12
+ const error = formState.errors[field.name];
13
+ const hasError = !!error;
14
+
15
+ const rules = field.validation_rules;
16
+ const acceptTypes = rules?.allowed_types?.join(",");
17
+
18
+ return (
19
+ <FieldWrapper field={field}>
20
+ <input
21
+ id={field.name}
22
+ type="file"
23
+ className={cn(
24
+ "lub-form__file-input",
25
+ hasError && "lub-form__file-input--error",
26
+ )}
27
+ accept={acceptTypes}
28
+ aria-invalid={hasError}
29
+ aria-describedby={
30
+ hasError
31
+ ? `${field.name}-error`
32
+ : field.help_text
33
+ ? `${field.name}-help`
34
+ : undefined
35
+ }
36
+ {...register(field.name)}
37
+ />
38
+ {rules?.max_file_size && (
39
+ <p className="lub-form__file-hint">
40
+ Max file size: {rules.max_file_size}MB
41
+ </p>
42
+ )}
43
+ </FieldWrapper>
44
+ );
45
+ }
@@ -0,0 +1,18 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+
4
+ interface HiddenFieldProps {
5
+ field: FormField;
6
+ }
7
+
8
+ export function HiddenField({ field }: HiddenFieldProps) {
9
+ const { register } = useFormContext();
10
+
11
+ return (
12
+ <input
13
+ type="hidden"
14
+ {...register(field.name)}
15
+ defaultValue={field.default_value}
16
+ />
17
+ );
18
+ }
@@ -0,0 +1,17 @@
1
+ import type { FormField } from "@/api/types";
2
+
3
+ interface HtmlFieldProps {
4
+ field: FormField;
5
+ }
6
+
7
+ export function HtmlField({ field }: HtmlFieldProps) {
8
+ // HTML fields display static content from default_value
9
+ if (!field.default_value) return null;
10
+
11
+ return (
12
+ <div
13
+ className="lub-form__html-content"
14
+ dangerouslySetInnerHTML={{ __html: field.default_value }}
15
+ />
16
+ );
17
+ }
@@ -0,0 +1,39 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+
6
+ interface NumberFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ export function NumberField({ field }: NumberFieldProps) {
11
+ const { register, formState } = useFormContext();
12
+ const error = formState.errors[field.name];
13
+ const hasError = !!error;
14
+
15
+ const rules = field.validation_rules;
16
+
17
+ return (
18
+ <FieldWrapper field={field}>
19
+ <input
20
+ id={field.name}
21
+ type="number"
22
+ className={cn("lub-form__input", hasError && "lub-form__input--error")}
23
+ placeholder={field.placeholder}
24
+ min={rules?.min}
25
+ max={rules?.max}
26
+ step="any"
27
+ aria-invalid={hasError}
28
+ aria-describedby={
29
+ hasError
30
+ ? `${field.name}-error`
31
+ : field.help_text
32
+ ? `${field.name}-help`
33
+ : undefined
34
+ }
35
+ {...register(field.name)}
36
+ />
37
+ </FieldWrapper>
38
+ );
39
+ }
@@ -0,0 +1,57 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+
6
+ interface RadioFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ export function RadioField({ field }: RadioFieldProps) {
11
+ const { register, formState } = useFormContext();
12
+ const error = formState.errors[field.name];
13
+ const hasError = !!error;
14
+
15
+ const options = field.options?.options ?? [];
16
+
17
+ return (
18
+ <FieldWrapper field={field}>
19
+ <div
20
+ className="lub-form__radio-group"
21
+ role="radiogroup"
22
+ aria-labelledby={`${field.name}-label`}
23
+ >
24
+ {options.map((option) => (
25
+ <label key={option.value} className="lub-form__radio-label">
26
+ <input
27
+ type="radio"
28
+ className={cn(
29
+ "lub-form__radio",
30
+ hasError && "lub-form__radio--error",
31
+ )}
32
+ value={option.value}
33
+ {...register(field.name)}
34
+ />
35
+ <span className="lub-form__radio-text">{option.label}</span>
36
+ </label>
37
+ ))}
38
+ {field.options?.allow_other && (
39
+ <label className="lub-form__radio-label">
40
+ <input
41
+ type="radio"
42
+ className={cn(
43
+ "lub-form__radio",
44
+ hasError && "lub-form__radio--error",
45
+ )}
46
+ value="__other__"
47
+ {...register(field.name)}
48
+ />
49
+ <span className="lub-form__radio-text">
50
+ {field.options.other_label || "Other"}
51
+ </span>
52
+ </label>
53
+ )}
54
+ </div>
55
+ </FieldWrapper>
56
+ );
57
+ }
@@ -0,0 +1,137 @@
1
+ import { useEffect, useRef, useCallback } from "react";
2
+ import { useFormContext } from "react-hook-form";
3
+ import type { FormField } from "@/api/types";
4
+ import { useLubFormContext } from "@/core/FormProvider";
5
+
6
+ interface RecaptchaFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ declare global {
11
+ interface Window {
12
+ grecaptcha?: {
13
+ ready: (callback: () => void) => void;
14
+ execute: (
15
+ siteKey: string,
16
+ options: { action: string },
17
+ ) => Promise<string>;
18
+ render: (
19
+ container: HTMLElement,
20
+ options: {
21
+ sitekey: string;
22
+ callback: (token: string) => void;
23
+ "expired-callback"?: () => void;
24
+ "error-callback"?: () => void;
25
+ },
26
+ ) => number;
27
+ reset: (widgetId?: number) => void;
28
+ };
29
+ }
30
+ }
31
+
32
+ export function RecaptchaField({ field }: RecaptchaFieldProps) {
33
+ const { setValue, setError, clearErrors } = useFormContext();
34
+ const { form } = useLubFormContext();
35
+ const containerRef = useRef<HTMLDivElement>(null);
36
+ const widgetIdRef = useRef<number | null>(null);
37
+ const scriptLoadedRef = useRef(false);
38
+
39
+ const siteKey = form.settings.recaptcha_site_key;
40
+
41
+ // Callback for v2 checkbox
42
+ const handleV2Callback = useCallback(
43
+ (token: string) => {
44
+ setValue("_recaptcha_token", token);
45
+ clearErrors("_recaptcha_token");
46
+ },
47
+ [setValue, clearErrors],
48
+ );
49
+
50
+ const handleExpired = useCallback(() => {
51
+ setValue("_recaptcha_token", "");
52
+ setError("_recaptcha_token", {
53
+ type: "manual",
54
+ message: "reCAPTCHA expired, please try again",
55
+ });
56
+ }, [setValue, setError]);
57
+
58
+ // Load reCAPTCHA script
59
+ useEffect(() => {
60
+ if (!siteKey || scriptLoadedRef.current) return;
61
+
62
+ // Check if script already exists
63
+ const existingScript = document.querySelector('script[src*="recaptcha"]');
64
+ if (existingScript) {
65
+ scriptLoadedRef.current = true;
66
+ return;
67
+ }
68
+
69
+ const script = document.createElement("script");
70
+ script.src = `https://www.google.com/recaptcha/api.js?render=explicit`;
71
+ script.async = true;
72
+ script.defer = true;
73
+ script.onload = () => {
74
+ scriptLoadedRef.current = true;
75
+ };
76
+ document.head.appendChild(script);
77
+
78
+ return () => {
79
+ // Don't remove script on cleanup as it might be used elsewhere
80
+ };
81
+ }, [siteKey]);
82
+
83
+ // Render v2 widget
84
+ useEffect(() => {
85
+ if (!siteKey || !containerRef.current) return;
86
+
87
+ const renderWidget = () => {
88
+ if (
89
+ window.grecaptcha &&
90
+ containerRef.current &&
91
+ widgetIdRef.current === null
92
+ ) {
93
+ window.grecaptcha.ready(() => {
94
+ if (containerRef.current) {
95
+ widgetIdRef.current = window.grecaptcha!.render(
96
+ containerRef.current,
97
+ {
98
+ sitekey: siteKey,
99
+ callback: handleV2Callback,
100
+ "expired-callback": handleExpired,
101
+ "error-callback": handleExpired,
102
+ },
103
+ );
104
+ }
105
+ });
106
+ }
107
+ };
108
+
109
+ // Try rendering, with retry if script isn't loaded yet
110
+ const attemptRender = () => {
111
+ if (window.grecaptcha) {
112
+ renderWidget();
113
+ } else {
114
+ setTimeout(attemptRender, 100);
115
+ }
116
+ };
117
+
118
+ attemptRender();
119
+
120
+ return () => {
121
+ if (widgetIdRef.current !== null && window.grecaptcha) {
122
+ window.grecaptcha.reset(widgetIdRef.current);
123
+ widgetIdRef.current = null;
124
+ }
125
+ };
126
+ }, [siteKey, handleV2Callback, handleExpired]);
127
+
128
+ if (!siteKey) {
129
+ return null;
130
+ }
131
+
132
+ return (
133
+ <div className="lub-form__recaptcha">
134
+ <div ref={containerRef} className="lub-form__recaptcha-widget" />
135
+ </div>
136
+ );
137
+ }
@@ -0,0 +1,49 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+
6
+ interface SelectFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ export function SelectField({ field }: SelectFieldProps) {
11
+ const { register, formState } = useFormContext();
12
+ const error = formState.errors[field.name];
13
+ const hasError = !!error;
14
+
15
+ const options = field.options?.options ?? [];
16
+
17
+ return (
18
+ <FieldWrapper field={field}>
19
+ <select
20
+ id={field.name}
21
+ className={cn(
22
+ "lub-form__select",
23
+ hasError && "lub-form__select--error",
24
+ )}
25
+ aria-invalid={hasError}
26
+ aria-describedby={
27
+ hasError
28
+ ? `${field.name}-error`
29
+ : field.help_text
30
+ ? `${field.name}-help`
31
+ : undefined
32
+ }
33
+ {...register(field.name)}
34
+ >
35
+ <option value="">{field.placeholder || "Select an option..."}</option>
36
+ {options.map((option) => (
37
+ <option key={option.value} value={option.value}>
38
+ {option.label}
39
+ </option>
40
+ ))}
41
+ {field.options?.allow_other && (
42
+ <option value="__other__">
43
+ {field.options.other_label || "Other"}
44
+ </option>
45
+ )}
46
+ </select>
47
+ </FieldWrapper>
48
+ );
49
+ }
@@ -0,0 +1,84 @@
1
+ import { useFormContext, useWatch } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+ import { getStatesForCountry } from "@/utils/countries";
6
+
7
+ interface StateFieldProps {
8
+ field: FormField;
9
+ /** Name of the country field to watch for state filtering */
10
+ countryFieldName?: string;
11
+ }
12
+
13
+ export function StateField({
14
+ field,
15
+ countryFieldName = "country",
16
+ }: StateFieldProps) {
17
+ const { register, formState, control } = useFormContext();
18
+ const error = formState.errors[field.name];
19
+ const hasError = !!error;
20
+
21
+ // Watch country field to filter states
22
+ const countryValue = useWatch({
23
+ control,
24
+ name: countryFieldName,
25
+ defaultValue: "",
26
+ });
27
+
28
+ const states = getStatesForCountry(countryValue as string);
29
+ const hasStates = states.length > 0;
30
+
31
+ // If no country selected or country has no states, show text input
32
+ if (!hasStates) {
33
+ return (
34
+ <FieldWrapper field={field}>
35
+ <input
36
+ id={field.name}
37
+ type="text"
38
+ className={cn(
39
+ "lub-form__input",
40
+ hasError && "lub-form__input--error",
41
+ )}
42
+ placeholder={field.placeholder || "State/Province"}
43
+ aria-invalid={hasError}
44
+ aria-describedby={
45
+ hasError
46
+ ? `${field.name}-error`
47
+ : field.help_text
48
+ ? `${field.name}-help`
49
+ : undefined
50
+ }
51
+ {...register(field.name)}
52
+ />
53
+ </FieldWrapper>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <FieldWrapper field={field}>
59
+ <select
60
+ id={field.name}
61
+ className={cn(
62
+ "lub-form__select",
63
+ hasError && "lub-form__select--error",
64
+ )}
65
+ aria-invalid={hasError}
66
+ aria-describedby={
67
+ hasError
68
+ ? `${field.name}-error`
69
+ : field.help_text
70
+ ? `${field.name}-help`
71
+ : undefined
72
+ }
73
+ {...register(field.name)}
74
+ >
75
+ <option value="">{field.placeholder || "Select a state..."}</option>
76
+ {states.map((state) => (
77
+ <option key={state.code} value={state.code}>
78
+ {state.name}
79
+ </option>
80
+ ))}
81
+ </select>
82
+ </FieldWrapper>
83
+ );
84
+ }
@@ -0,0 +1,51 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+
6
+ interface TextFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ export function TextField({ field }: TextFieldProps) {
11
+ const { register, formState } = useFormContext();
12
+ const error = formState.errors[field.name];
13
+ const hasError = !!error;
14
+
15
+ // Determine input type based on field_type
16
+ const inputType = getInputType(field.field_type);
17
+
18
+ return (
19
+ <FieldWrapper field={field}>
20
+ <input
21
+ id={field.name}
22
+ type={inputType}
23
+ className={cn("lub-form__input", hasError && "lub-form__input--error")}
24
+ placeholder={field.placeholder}
25
+ aria-invalid={hasError}
26
+ aria-describedby={
27
+ hasError
28
+ ? `${field.name}-error`
29
+ : field.help_text
30
+ ? `${field.name}-help`
31
+ : undefined
32
+ }
33
+ {...register(field.name)}
34
+ />
35
+ </FieldWrapper>
36
+ );
37
+ }
38
+
39
+ function getInputType(fieldType: FormField["field_type"]): string {
40
+ switch (fieldType) {
41
+ case "email":
42
+ return "email";
43
+ case "phone":
44
+ return "tel";
45
+ case "url":
46
+ return "url";
47
+ case "text":
48
+ default:
49
+ return "text";
50
+ }
51
+ }
@@ -0,0 +1,37 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+
6
+ interface TextareaFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ export function TextareaField({ field }: TextareaFieldProps) {
11
+ const { register, formState } = useFormContext();
12
+ const error = formState.errors[field.name];
13
+ const hasError = !!error;
14
+
15
+ return (
16
+ <FieldWrapper field={field}>
17
+ <textarea
18
+ id={field.name}
19
+ className={cn(
20
+ "lub-form__textarea",
21
+ hasError && "lub-form__textarea--error",
22
+ )}
23
+ placeholder={field.placeholder}
24
+ rows={4}
25
+ aria-invalid={hasError}
26
+ aria-describedby={
27
+ hasError
28
+ ? `${field.name}-error`
29
+ : field.help_text
30
+ ? `${field.name}-help`
31
+ : undefined
32
+ }
33
+ {...register(field.name)}
34
+ />
35
+ </FieldWrapper>
36
+ );
37
+ }
@@ -0,0 +1,33 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { FormField } from "@/api/types";
3
+ import { FieldWrapper } from "./FieldWrapper";
4
+ import { cn } from "@/utils/cn";
5
+
6
+ interface TimeFieldProps {
7
+ field: FormField;
8
+ }
9
+
10
+ export function TimeField({ field }: TimeFieldProps) {
11
+ const { register, formState } = useFormContext();
12
+ const error = formState.errors[field.name];
13
+ const hasError = !!error;
14
+
15
+ return (
16
+ <FieldWrapper field={field}>
17
+ <input
18
+ id={field.name}
19
+ type="time"
20
+ className={cn("lub-form__input", hasError && "lub-form__input--error")}
21
+ aria-invalid={hasError}
22
+ aria-describedby={
23
+ hasError
24
+ ? `${field.name}-error`
25
+ : field.help_text
26
+ ? `${field.name}-help`
27
+ : undefined
28
+ }
29
+ {...register(field.name)}
30
+ />
31
+ </FieldWrapper>
32
+ );
33
+ }
@@ -0,0 +1,84 @@
1
+ import type { ComponentType } from "react";
2
+ import type { FieldType, FormField } from "@/api/types";
3
+
4
+ import { TextField } from "./TextField";
5
+ import { TextareaField } from "./TextareaField";
6
+ import { NumberField } from "./NumberField";
7
+ import { SelectField } from "./SelectField";
8
+ import { RadioField } from "./RadioField";
9
+ import { CheckboxField } from "./CheckboxField";
10
+ import { CheckboxGroupField } from "./CheckboxGroupField";
11
+ import { DateField } from "./DateField";
12
+ import { TimeField } from "./TimeField";
13
+ import { DateTimeField } from "./DateTimeField";
14
+ import { FileField } from "./FileField";
15
+ import { HiddenField } from "./HiddenField";
16
+ import { CountryField } from "./CountryField";
17
+ import { StateField } from "./StateField";
18
+ import { HtmlField } from "./HtmlField";
19
+ import { DividerField } from "./DividerField";
20
+ import { RecaptchaField } from "./RecaptchaField";
21
+
22
+ // Field component type
23
+ export type FieldComponent = ComponentType<{ field: FormField }>;
24
+
25
+ // Field component registry
26
+ const fieldRegistry: Record<FieldType, FieldComponent> = {
27
+ text: TextField,
28
+ email: TextField,
29
+ phone: TextField,
30
+ url: TextField,
31
+ textarea: TextareaField,
32
+ number: NumberField,
33
+ select: SelectField,
34
+ radio: RadioField,
35
+ checkbox: CheckboxField,
36
+ checkbox_group: CheckboxGroupField,
37
+ date: DateField,
38
+ time: TimeField,
39
+ datetime: DateTimeField,
40
+ file: FileField,
41
+ hidden: HiddenField,
42
+ country: CountryField,
43
+ state: StateField,
44
+ html: HtmlField,
45
+ divider: DividerField,
46
+ recaptcha: RecaptchaField,
47
+ };
48
+
49
+ /**
50
+ * Get the component for a field type
51
+ */
52
+ export function getFieldComponent(type: FieldType): FieldComponent | null {
53
+ return fieldRegistry[type] ?? null;
54
+ }
55
+
56
+ /**
57
+ * Register a custom field component
58
+ */
59
+ export function registerFieldComponent(
60
+ type: string,
61
+ component: FieldComponent,
62
+ ): void {
63
+ (fieldRegistry as Record<string, FieldComponent>)[type] = component;
64
+ }
65
+
66
+ // Export all field components
67
+ export { FieldWrapper } from "./FieldWrapper";
68
+ export { TextField } from "./TextField";
69
+ export { TextareaField } from "./TextareaField";
70
+ export { NumberField } from "./NumberField";
71
+ export { SelectField } from "./SelectField";
72
+ export { RadioField } from "./RadioField";
73
+ export { CheckboxField } from "./CheckboxField";
74
+ export { CheckboxGroupField } from "./CheckboxGroupField";
75
+ export { DateField } from "./DateField";
76
+ export { TimeField } from "./TimeField";
77
+ export { DateTimeField } from "./DateTimeField";
78
+ export { FileField } from "./FileField";
79
+ export { HiddenField } from "./HiddenField";
80
+ export { CountryField } from "./CountryField";
81
+ export { StateField } from "./StateField";
82
+ export { HtmlField } from "./HtmlField";
83
+ export { DividerField } from "./DividerField";
84
+ export { RecaptchaField } from "./RecaptchaField";
@@ -0,0 +1,4 @@
1
+ export { useFormApi } from "./useFormApi";
2
+ export { useConditionalLogic } from "./useConditionalLogic";
3
+ export { useMultiStep } from "./useMultiStep";
4
+ export { useFormDesign } from "./useFormDesign";