Skip to content

Commit a1af9c2

Browse files
authored
Merge pull request #62 from DouglasNeuroInformatics/zod-v4
feat: support zod v4
2 parents 58f5118 + 87d54f8 commit a1af9c2

File tree

6 files changed

+54
-20
lines changed

6 files changed

+54
-20
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"react": "^19.1.0",
6666
"react-dom": "^19.1.0",
6767
"tailwindcss": "^4.1.0",
68-
"zod": "^3.23.6"
68+
"zod": "^3.25.x"
6969
},
7070
"dependencies": {
7171
"@douglasneuroinformatics/libjs": "^2.8.0",

pnpm-lock.yaml

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Form/Form.stories.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import type { FormFields } from '@douglasneuroinformatics/libui-form-types';
77
import type FormTypes from '@douglasneuroinformatics/libui-form-types';
88
import type { Meta, StoryObj } from '@storybook/react';
99
import type { IntRange } from 'type-fest';
10-
import { z } from 'zod';
10+
import { z } from 'zod/v4';
1111

1212
import { Heading } from '../Heading';
1313
import { Form } from './Form';
1414

15+
import type { ZodTypeLike } from './types';
16+
1517
const DISABLED = false;
1618

1719
const $ExampleFormData = z.object({
@@ -49,7 +51,7 @@ const $ExampleFormData = z.object({
4951
stringRadio: z.enum(['a', 'b', 'c']).optional()
5052
});
5153
type ExampleFormSchemaType = typeof $ExampleFormData;
52-
type ExampleFormData = z.TypeOf<typeof $ExampleFormData>;
54+
type ExampleFormData = z.infer<typeof $ExampleFormData>;
5355

5456
const $SimpleExampleFormData = z.object({
5557
name: z.string()
@@ -513,7 +515,7 @@ export const WithPreventReset: StoryObj<typeof Form<SimpleExampleFormSchemaType>
513515
}
514516
};
515517

516-
export const WithSuspend: StoryObj<typeof Form<z.ZodType<FormTypes.Data>, { delay?: number }>> = {
518+
export const WithSuspend: StoryObj<typeof Form<ZodTypeLike<FormTypes.Data>, { delay?: number }>> = {
517519
args: {
518520
content: {
519521
delay: {

src/components/Form/Form.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
22
import { userEvent } from '@testing-library/user-event';
33
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
44
import type { Mock } from 'vitest';
5-
import { z } from 'zod';
5+
import { z } from 'zod/v4';
66

77
import { Form } from './Form';
88

src/components/Form/Form.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import type {
1010
import { get, set } from 'lodash-es';
1111
import { twMerge } from 'tailwind-merge';
1212
import type { Promisable } from 'type-fest';
13-
import { z } from 'zod';
1413

1514
import { useTranslation } from '@/hooks';
1615
import { cn } from '@/utils';
@@ -22,9 +21,9 @@ import { ErrorMessage } from './ErrorMessage';
2221
import { FieldsComponent } from './FieldsComponent';
2322
import { getInitialValues } from './utils';
2423

25-
import type { FormErrors } from './types';
24+
import type { FormErrors, ZodErrorLike, ZodTypeLike } from './types';
2625

27-
type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TSchema> = z.TypeOf<TSchema>> = {
26+
type FormProps<TSchema extends ZodTypeLike<FormDataType>, TData extends TSchema['_input'] = TSchema['_input']> = {
2827
[key: `data-${string}`]: unknown;
2928
additionalButtons?: {
3029
left?: React.ReactNode;
@@ -42,18 +41,18 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
4241
onBeforeSubmit?:
4342
| ((data: NoInfer<TData>) => Promisable<{ errorMessage: string; success: false } | { success: true }>)
4443
| null;
45-
onError?: (error: z.ZodError<NoInfer<TData>>) => void;
44+
onError?: (error: ZodErrorLike) => void;
4645
onSubmit: (data: NoInfer<TData>) => Promisable<void>;
4746
preventResetValuesOnReset?: boolean;
4847
readOnly?: boolean;
4948
resetBtn?: boolean;
5049
revalidateOnBlur?: boolean;
5150
submitBtnLabel?: string;
5251
suspendWhileSubmitting?: boolean;
53-
validationSchema: z.ZodType<TData>;
52+
validationSchema: ZodTypeLike<TData>;
5453
};
5554

56-
const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TSchema> = z.TypeOf<TSchema>>({
55+
const Form = <TSchema extends ZodTypeLike<FormDataType>, TData extends TSchema['_input'] = TSchema['_input']>({
5756
additionalButtons,
5857
className,
5958
content,
@@ -81,7 +80,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
8180
);
8281
const [isSubmitting, setIsSubmitting] = useState(false);
8382

84-
const handleError = (error: z.ZodError<TData>) => {
83+
const handleError = (error: ZodErrorLike) => {
8584
const fieldErrors: FormErrors<TData> = {};
8685
const rootErrors: string[] = [];
8786
for (const issue of error.issues) {

src/components/Form/types.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,36 @@ export type BaseFieldComponentProps<TValue extends FormFieldValue = FormFieldVal
2525
export type FormErrors<TData extends FormDataType = FormDataType> = {
2626
[K in keyof TData]?: FieldError<TData[K]>;
2727
};
28+
29+
export type ZodIssueLike = {
30+
[key: string]: any;
31+
readonly code: string;
32+
readonly message: string;
33+
readonly path: PropertyKey[];
34+
};
35+
36+
export type ZodErrorLike = {
37+
cause?: unknown;
38+
issues: ZodIssueLike[];
39+
name: string;
40+
};
41+
42+
export type ZodSafeParseResultLike<T> = ZodSafeParseErrorLike | ZodSafeParseSuccessLike<T>;
43+
44+
export type ZodSafeParseSuccessLike<TOutput> = {
45+
data: TOutput;
46+
error?: never;
47+
success: true;
48+
};
49+
50+
export type ZodSafeParseErrorLike = {
51+
data?: never;
52+
error: ZodErrorLike;
53+
success: false;
54+
};
55+
56+
export type ZodTypeLike<TOutput, TInput = TOutput> = {
57+
readonly _input: TInput;
58+
readonly _output: TOutput;
59+
safeParseAsync: (data: unknown) => Promise<ZodSafeParseResultLike<TOutput>>;
60+
};

0 commit comments

Comments
 (0)