@douglasneuroinformatics/libui 3.7.6 → 3.8.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.
- package/dist/components.d.ts +2 -1
- package/dist/components.js +47 -9
- package/dist/components.js.map +1 -1
- package/dist/douglasneuroinformatics-libui-3.8.0.tgz +0 -0
- package/package.json +1 -1
- package/src/components/Form/Form.stories.tsx +14 -10
- package/src/components/Form/Form.tsx +42 -9
- package/dist/douglasneuroinformatics-libui-3.7.6.tgz +0 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douglasneuroinformatics/libui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.8.0",
|
|
5
5
|
"packageManager": "pnpm@9.14.2",
|
|
6
6
|
"description": "Generic UI components for DNP projects, built using React and Tailwind CSS",
|
|
7
7
|
"author": "Joshua Unrau",
|
|
@@ -18,20 +18,24 @@ const $ExampleFormData = z.object({
|
|
|
18
18
|
booleanCheck: z.boolean().optional(),
|
|
19
19
|
booleanRadio: z.boolean().optional(),
|
|
20
20
|
recordArray: z.array(
|
|
21
|
-
z
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
23
|
+
recordArrayStringInput: z.string(),
|
|
24
|
+
showRecordArrayDynamicField: z.boolean(),
|
|
25
|
+
recordArrayDynamicField: z.string()
|
|
26
|
+
})
|
|
27
|
+
.partial()
|
|
26
28
|
),
|
|
27
29
|
date: z.date().optional(),
|
|
28
30
|
numberInput: z.number().optional(),
|
|
29
31
|
numberSlider: z.number().optional(),
|
|
30
|
-
numberRecord: z
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
numberRecord: z
|
|
33
|
+
.object({
|
|
34
|
+
q1: z.number(),
|
|
35
|
+
q2: z.number(),
|
|
36
|
+
q3: z.number()
|
|
37
|
+
})
|
|
38
|
+
.partial(),
|
|
35
39
|
numberRadio: z.number().min(1).max(5).int().optional(),
|
|
36
40
|
numberSelect: z.number().min(1).max(5).int().optional(),
|
|
37
41
|
stringSelect: z.enum(['a', 'b', 'c']).optional(),
|
|
@@ -13,6 +13,7 @@ import type { Promisable } from 'type-fest';
|
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
|
|
15
15
|
import { useTranslation } from '@/hooks';
|
|
16
|
+
import { cn } from '@/utils';
|
|
16
17
|
|
|
17
18
|
import { Button } from '../Button';
|
|
18
19
|
import { Heading } from '../Heading';
|
|
@@ -40,6 +41,7 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
|
|
|
40
41
|
resetBtn?: boolean;
|
|
41
42
|
revalidateOnBlur?: boolean;
|
|
42
43
|
submitBtnLabel?: string;
|
|
44
|
+
suspendWhileSubmitting?: boolean;
|
|
43
45
|
validationSchema: z.ZodType<TData>;
|
|
44
46
|
};
|
|
45
47
|
|
|
@@ -58,6 +60,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
58
60
|
resetBtn,
|
|
59
61
|
revalidateOnBlur,
|
|
60
62
|
submitBtnLabel,
|
|
63
|
+
suspendWhileSubmitting,
|
|
61
64
|
validationSchema,
|
|
62
65
|
...props
|
|
63
66
|
}: FormProps<TSchema, TData>) => {
|
|
@@ -67,6 +70,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
67
70
|
const [values, setValues] = useState<PartialFormDataType<TData>>(
|
|
68
71
|
initialValues ? getInitialValues(initialValues) : {}
|
|
69
72
|
);
|
|
73
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
70
74
|
|
|
71
75
|
const handleError = (error: z.ZodError<TData>) => {
|
|
72
76
|
const fieldErrors: FormErrors<TData> = {};
|
|
@@ -97,14 +101,21 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
97
101
|
};
|
|
98
102
|
|
|
99
103
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
const minSubmitTime = new Promise((resolve) => setTimeout(resolve, 500));
|
|
105
|
+
try {
|
|
106
|
+
setIsSubmitting(true);
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
const result = await validationSchema.safeParseAsync(values);
|
|
109
|
+
if (result.success) {
|
|
110
|
+
reset();
|
|
111
|
+
await onSubmit(result.data);
|
|
112
|
+
} else {
|
|
113
|
+
console.error(result.error.issues);
|
|
114
|
+
handleError(result.error);
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
await minSubmitTime;
|
|
118
|
+
setIsSubmitting(false);
|
|
108
119
|
}
|
|
109
120
|
};
|
|
110
121
|
|
|
@@ -128,6 +139,8 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
128
139
|
revalidate();
|
|
129
140
|
}, [resolvedLanguage]);
|
|
130
141
|
|
|
142
|
+
const isSuspended = Boolean(suspendWhileSubmitting && isSubmitting);
|
|
143
|
+
|
|
131
144
|
return (
|
|
132
145
|
<form
|
|
133
146
|
autoComplete="off"
|
|
@@ -176,8 +189,28 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
176
189
|
<div className="flex w-full gap-3">
|
|
177
190
|
{additionalButtons?.left}
|
|
178
191
|
{/** Note - aria-label is used for testing in downstream packages */}
|
|
179
|
-
<Button
|
|
192
|
+
<Button
|
|
193
|
+
aria-label="Submit"
|
|
194
|
+
className="flex w-32 items-center justify-center gap-2"
|
|
195
|
+
disabled={readOnly || isSuspended}
|
|
196
|
+
type="submit"
|
|
197
|
+
variant="primary"
|
|
198
|
+
>
|
|
180
199
|
{submitBtnLabel ?? t('form.submit')}
|
|
200
|
+
<svg
|
|
201
|
+
className={cn('hidden h-4 w-4 animate-spin', isSuspended && 'block')}
|
|
202
|
+
fill="none"
|
|
203
|
+
height="24"
|
|
204
|
+
stroke="currentColor"
|
|
205
|
+
strokeLinecap="round"
|
|
206
|
+
strokeLinejoin="round"
|
|
207
|
+
strokeWidth="2"
|
|
208
|
+
viewBox="0 0 24 24"
|
|
209
|
+
width="24"
|
|
210
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
211
|
+
>
|
|
212
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
213
|
+
</svg>
|
|
181
214
|
</Button>
|
|
182
215
|
{resetBtn && (
|
|
183
216
|
<Button
|
|
Binary file
|