Skip to content

Commit 6a3d148

Browse files
authored
Merge pull request #118 from AmirhoseinBrz/master
feat(form): integrate Radix UI and React Hook Form for enhanced form
2 parents 22255fe + 76a2063 commit 6a3d148

11 files changed

Lines changed: 658 additions & 407 deletions

File tree

web/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_API_CLIENT_BASE_URL=""

web/package-lock.json

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

web/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"@hookform/resolvers": "^3.9.1",
1314
"@radix-ui/react-dialog": "^1.1.2",
15+
"@radix-ui/react-form": "^0.1.0",
1416
"@radix-ui/react-separator": "^1.1.0",
1517
"@radix-ui/react-slot": "^1.1.0",
1618
"@radix-ui/react-tooltip": "^1.1.4",
@@ -21,12 +23,14 @@
2123
"lucide-react": "^0.462.0",
2224
"react": "^18.3.1",
2325
"react-dom": "^18.3.1",
26+
"react-hook-form": "^7.53.2",
2427
"react-router": "^7.0.1",
2528
"react-select": "^5.8.3",
2629
"react-spinners": "^0.14.1",
2730
"sonner": "^1.7.0",
2831
"tailwind-merge": "^2.5.5",
2932
"tailwindcss-animate": "^1.0.7",
33+
"zod": "^3.23.8",
3034
"zustand": "^5.0.1"
3135
},
3236
"devDependencies": {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
import * as Form from '@radix-ui/react-form';
3+
import { useFormContext } from 'react-hook-form';
4+
import { FormFieldProps } from '../../types/form.types';
5+
import { cn } from '@/lib/utils';
6+
7+
interface FormCheckboxProps extends FormFieldProps {
8+
labelPosition?: 'left' | 'right';
9+
checkboxClassName?: string;
10+
}
11+
12+
export const FormCheckbox = ({
13+
name,
14+
label,
15+
error,
16+
labelPosition = 'right',
17+
checkboxClassName,
18+
...props
19+
}: FormCheckboxProps) => {
20+
const {
21+
register,
22+
formState: { errors },
23+
} = useFormContext();
24+
25+
const fieldError = errors[name];
26+
const errorMessage = fieldError?.message as string;
27+
28+
return (
29+
<Form.Field className="form-field" name={name}>
30+
<div className={cn(
31+
'flex items-center gap-2',
32+
labelPosition === 'right' ? 'flex-row' : 'flex-row-reverse justify-end'
33+
)}>
34+
<Form.Control asChild>
35+
<input
36+
type="checkbox"
37+
className={cn(
38+
'toggle border-gray-500 bg-gray-500',
39+
'checked:bg-orange-base checked:hover:bg-orange-base/70',
40+
checkboxClassName
41+
)}
42+
{...register(name)}
43+
{...props}
44+
/>
45+
</Form.Control>
46+
<Form.Label className="form-label cursor-pointer">{label}</Form.Label>
47+
</div>
48+
{errorMessage && (
49+
<Form.Message className="form-message mt-1 text-red-500">
50+
{errorMessage}
51+
</Form.Message>
52+
)}
53+
</Form.Field>
54+
);
55+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as Form from '@radix-ui/react-form';
2+
import { useFormContext } from 'react-hook-form';
3+
import { FormFieldProps } from '../../types/form.types';
4+
import { getNestedValue } from '@/lib/helper';
5+
import { cn } from '@/lib/utils';
6+
7+
export const FormInput = ({ name, label, error, ...props }: FormFieldProps) => {
8+
const {
9+
register,
10+
formState: { errors },
11+
} = useFormContext();
12+
13+
const fieldError = getNestedValue(errors, name);
14+
const errorMessage = fieldError?.message as string;
15+
16+
return (
17+
<Form.Field
18+
className={cn('form-field relative', {
19+
'mb-6': errorMessage,
20+
})}
21+
name={name}
22+
>
23+
{label && (
24+
<div className="mb-2 flex items-baseline justify-between">
25+
<Form.Label className="form-label">{label} :</Form.Label>
26+
</div>
27+
)}
28+
<Form.Control asChild>
29+
<input
30+
className="w-full rounded-md border border-gray-200 px-3 py-2 outline-none dark:border-none"
31+
{...register(name)}
32+
{...props}
33+
/>
34+
</Form.Control>
35+
{errorMessage && (
36+
<div className="absolute left-0 top-full">
37+
<Form.Message className="form-message ml-auto text-sm text-red-500">
38+
{errorMessage}
39+
</Form.Message>
40+
</div>
41+
)}
42+
</Form.Field>
43+
);
44+
};
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// components/form/FormSelect.tsx
2+
import * as Form from '@radix-ui/react-form';
3+
import { Controller, useFormContext } from 'react-hook-form';
4+
import { FormFieldProps } from '../../types/form.types';
5+
import Select from 'react-select';
6+
import { getNestedValue } from '@/lib/helper';
7+
import { selectStyle } from '@/pages/helm-template/styles/helm-template.style';
8+
import { useStyle } from '@/hooks';
9+
import { cn } from '@/lib/utils';
10+
11+
interface OptionType {
12+
value: string;
13+
label: string;
14+
}
15+
16+
interface FormSelectProps extends FormFieldProps {
17+
options: OptionType[];
18+
placeholder?: string;
19+
isSearchable?: boolean;
20+
}
21+
22+
export const FormSelect = ({
23+
name,
24+
label,
25+
error,
26+
options,
27+
placeholder = 'Select...',
28+
isSearchable = true,
29+
...props
30+
}: FormSelectProps) => {
31+
const {
32+
control,
33+
formState: { errors },
34+
} = useFormContext();
35+
36+
const { darkMode } = useStyle();
37+
38+
const fieldError = getNestedValue(errors, name);
39+
const errorMessage = fieldError?.message as string;
40+
41+
return (
42+
<Form.Field
43+
className={cn('form-field relative', {
44+
'mb-6': errorMessage,
45+
})}
46+
name={name}
47+
>
48+
{label && (
49+
<div className="mb-2 flex items-baseline justify-between">
50+
<Form.Label className="form-label">{label} :</Form.Label>
51+
</div>
52+
)}
53+
<Form.Control asChild>
54+
<Controller
55+
name={name}
56+
control={control}
57+
render={({ field }) => (
58+
<Select
59+
{...field}
60+
options={options}
61+
placeholder={placeholder}
62+
className="w-full"
63+
{...props}
64+
styles={selectStyle(darkMode)}
65+
/>
66+
)}
67+
/>
68+
</Form.Control>
69+
{errorMessage && (
70+
<div className="absolute left-0 top-full">
71+
<Form.Message className="form-message ml-auto text-sm text-red-500">
72+
{errorMessage}
73+
</Form.Message>
74+
</div>
75+
)}
76+
</Form.Field>
77+
);
78+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as Form from '@radix-ui/react-form';
2+
import { FormProvider, UseFormReturn } from 'react-hook-form';
3+
import { z } from 'zod';
4+
import { FormConfig } from '../../types/form.types';
5+
6+
interface FormWrapperProps<T extends z.ZodType>
7+
extends Omit<FormConfig<T>, 'mode'> {
8+
children: React.ReactNode;
9+
onSubmit: (data: z.infer<T>) => void;
10+
methods: UseFormReturn<z.infer<T>>;
11+
}
12+
13+
export const FormWrapper = <T extends z.ZodType>({
14+
children,
15+
onSubmit,
16+
methods,
17+
}: FormWrapperProps<T>) => {
18+
return (
19+
<FormProvider {...methods} >
20+
<Form.Root
21+
onSubmit={methods.handleSubmit(onSubmit)}
22+
className="text-black dark:text-white"
23+
>
24+
{children}
25+
</Form.Root>
26+
</FormProvider>
27+
);
28+
};

0 commit comments

Comments
 (0)