Skip to content

Commit

Permalink
Merge pull request #118 from AmirhoseinBrz/master
Browse files Browse the repository at this point in the history
feat(form): integrate Radix UI and React Hook Form for enhanced form
  • Loading branch information
mohammadll authored Dec 3, 2024
2 parents 22255fe + 76a2063 commit 6a3d148
Show file tree
Hide file tree
Showing 11 changed files with 658 additions and 407 deletions.
1 change: 1 addition & 0 deletions web/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_API_CLIENT_BASE_URL=""
104 changes: 104 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-form": "^0.1.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.4",
Expand All @@ -21,12 +23,14 @@
"lucide-react": "^0.462.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.2",
"react-router": "^7.0.1",
"react-select": "^5.8.3",
"react-spinners": "^0.14.1",
"sonner": "^1.7.0",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8",
"zustand": "^5.0.1"
},
"devDependencies": {
Expand Down
55 changes: 55 additions & 0 deletions web/src/components/form/form-checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

import * as Form from '@radix-ui/react-form';
import { useFormContext } from 'react-hook-form';
import { FormFieldProps } from '../../types/form.types';
import { cn } from '@/lib/utils';

interface FormCheckboxProps extends FormFieldProps {
labelPosition?: 'left' | 'right';
checkboxClassName?: string;
}

export const FormCheckbox = ({
name,
label,
error,
labelPosition = 'right',
checkboxClassName,
...props
}: FormCheckboxProps) => {
const {
register,
formState: { errors },
} = useFormContext();

const fieldError = errors[name];
const errorMessage = fieldError?.message as string;

return (
<Form.Field className="form-field" name={name}>
<div className={cn(
'flex items-center gap-2',
labelPosition === 'right' ? 'flex-row' : 'flex-row-reverse justify-end'
)}>
<Form.Control asChild>
<input
type="checkbox"
className={cn(
'toggle border-gray-500 bg-gray-500',
'checked:bg-orange-base checked:hover:bg-orange-base/70',
checkboxClassName
)}
{...register(name)}
{...props}
/>
</Form.Control>
<Form.Label className="form-label cursor-pointer">{label}</Form.Label>
</div>
{errorMessage && (
<Form.Message className="form-message mt-1 text-red-500">
{errorMessage}
</Form.Message>
)}
</Form.Field>
);
};
44 changes: 44 additions & 0 deletions web/src/components/form/form-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as Form from '@radix-ui/react-form';
import { useFormContext } from 'react-hook-form';
import { FormFieldProps } from '../../types/form.types';
import { getNestedValue } from '@/lib/helper';
import { cn } from '@/lib/utils';

export const FormInput = ({ name, label, error, ...props }: FormFieldProps) => {
const {
register,
formState: { errors },
} = useFormContext();

const fieldError = getNestedValue(errors, name);
const errorMessage = fieldError?.message as string;

return (
<Form.Field
className={cn('form-field relative', {
'mb-6': errorMessage,
})}
name={name}
>
{label && (
<div className="mb-2 flex items-baseline justify-between">
<Form.Label className="form-label">{label} :</Form.Label>
</div>
)}
<Form.Control asChild>
<input
className="w-full rounded-md border border-gray-200 px-3 py-2 outline-none dark:border-none"
{...register(name)}
{...props}
/>
</Form.Control>
{errorMessage && (
<div className="absolute left-0 top-full">
<Form.Message className="form-message ml-auto text-sm text-red-500">
{errorMessage}
</Form.Message>
</div>
)}
</Form.Field>
);
};
78 changes: 78 additions & 0 deletions web/src/components/form/form-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// components/form/FormSelect.tsx
import * as Form from '@radix-ui/react-form';
import { Controller, useFormContext } from 'react-hook-form';
import { FormFieldProps } from '../../types/form.types';
import Select from 'react-select';
import { getNestedValue } from '@/lib/helper';
import { selectStyle } from '@/pages/helm-template/styles/helm-template.style';
import { useStyle } from '@/hooks';
import { cn } from '@/lib/utils';

interface OptionType {
value: string;
label: string;
}

interface FormSelectProps extends FormFieldProps {
options: OptionType[];
placeholder?: string;
isSearchable?: boolean;
}

export const FormSelect = ({
name,
label,
error,
options,
placeholder = 'Select...',
isSearchable = true,
...props
}: FormSelectProps) => {
const {
control,
formState: { errors },
} = useFormContext();

const { darkMode } = useStyle();

const fieldError = getNestedValue(errors, name);
const errorMessage = fieldError?.message as string;

return (
<Form.Field
className={cn('form-field relative', {
'mb-6': errorMessage,
})}
name={name}
>
{label && (
<div className="mb-2 flex items-baseline justify-between">
<Form.Label className="form-label">{label} :</Form.Label>
</div>
)}
<Form.Control asChild>
<Controller
name={name}
control={control}
render={({ field }) => (
<Select
{...field}
options={options}
placeholder={placeholder}
className="w-full"
{...props}
styles={selectStyle(darkMode)}
/>
)}
/>
</Form.Control>
{errorMessage && (
<div className="absolute left-0 top-full">
<Form.Message className="form-message ml-auto text-sm text-red-500">
{errorMessage}
</Form.Message>
</div>
)}
</Form.Field>
);
};
28 changes: 28 additions & 0 deletions web/src/components/form/form-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as Form from '@radix-ui/react-form';
import { FormProvider, UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { FormConfig } from '../../types/form.types';

interface FormWrapperProps<T extends z.ZodType>
extends Omit<FormConfig<T>, 'mode'> {
children: React.ReactNode;
onSubmit: (data: z.infer<T>) => void;
methods: UseFormReturn<z.infer<T>>;
}

export const FormWrapper = <T extends z.ZodType>({
children,
onSubmit,
methods,
}: FormWrapperProps<T>) => {
return (
<FormProvider {...methods} >
<Form.Root
onSubmit={methods.handleSubmit(onSubmit)}
className="text-black dark:text-white"
>
{children}
</Form.Root>
</FormProvider>
);
};
Loading

0 comments on commit 6a3d148

Please sign in to comment.