refactor(forms): Enhance MultiSelect with controlled state and styling

This commit overhauls the `MultiSelect` component to improve its functionality, flexibility, and integration with form libraries.

The component is now a controlled component, accepting a `value` prop and using the `onChange` handler to propagate updates. This allows for seamless integration with libraries like React Hook Form.

Key changes include:
- Added `value`, `disabled`, `active`, and `height` props for more granular control over the component's state and appearance.
- Refactored styling logic to use the `cn` utility for cleaner conditional class management.
- Simplified the internal JSX structure and SVGs for better readability and maintenance.
- Updated the component's state management to correctly reflect the incoming `value` prop.
This commit is contained in:
AmirReza Jamali
2025-09-20 18:28:53 +03:30
parent e6493f5d83
commit 0ea2ff635b
7 changed files with 388 additions and 159 deletions
@@ -1,7 +1,9 @@
"use client";
import { GlobeIcon, MoneyIcon } from "@/assets/icons";
import InputGroup from "@/components/FormElements/InputGroup";
import TagInput from "@/components/FormElements/InputGroup/tag-input";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import MultiSelect from "@/components/FormElements/MultiSelect";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import FormFooter from "@/components/ui/FormFooter";
@@ -17,12 +19,19 @@ interface IProps {
}
const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
const { errors, handleSubmit, onSubmit, register } =
useCreateCompanyPresenter({
editMode,
defaultValues,
id,
});
const {
errors,
handleSubmit,
onSubmit,
register,
watch,
setValue,
companyCategories,
} = useCreateCompanyPresenter({
editMode,
defaultValues,
id,
});
return (
<form
className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7 sm:grid-cols-2"
@@ -458,35 +467,56 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
)}
</ShowcaseSection>
<ShowcaseSection title="Tags" className="grid grid-cols-1 gap-5">
<InputGroup
<TagInput
{...register("tags.keywords")}
label="Keywords"
name="tags.keywords"
type="text"
label="Keywords"
placeholder="Enter Tag Keywords"
register={register}
setValue={setValue}
watch={watch}
/>
{errors.tags?.keywords && (
<p className="mt-1 text-sm text-red-500">
{errors.tags.keywords.message}
</p>
)}
<InputGroup
<MultiSelect
{...register("tags.categories")}
label="Categories"
value={watch("tags.categories")}
name="tags.categories"
type="text"
placeholder="Enter Tag Categories"
label="Categories"
items={
companyCategories
? companyCategories?.data?.categories?.map((c) => {
return {
text: c.name,
value: c.id,
};
})
: []
}
placeholder="Please select categories"
/>
{errors.tags?.categories && (
<p className="mt-1 text-sm text-red-500">
{errors.tags.categories.message}
</p>
)}
<InputGroup
<TagInput
{...register("tags.cpv_codes")}
label="CPV codes"
name="tags.cpv_codes"
watch={watch}
setValue={setValue}
placeholder="Enter Tag CPV codes"
/>
<TagInput
{...register("tags.certifications")}
label="Certifications"
name="tags.certifications"
watch={watch}
setValue={setValue}
placeholder="Enter Tag Certifications"
/>
<TagInput
{...register("tags.specializations")}
label="Specializations"
name="tags.specializations"
type="text"
watch={watch}
setValue={setValue}
placeholder="Enter Tag Specializations"
/>
{errors.tags?.specializations && (
@@ -1,5 +1,9 @@
"use client";
import { useCreateCompany, useUpdateCompany } from "@/hooks/queries";
import {
useCompanyCategoriesQuery,
useCreateCompany,
useUpdateCompany,
} from "@/hooks/queries";
import { ICreateCompanyCredentials } from "@/lib/api";
import { useIsMutating } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
@@ -17,9 +21,24 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
register,
formState: { errors },
handleSubmit,
watch,
setValue,
reset,
} = useForm<ICreateCompanyCredentials>({ mode: "onChange", defaultValues });
} = useForm<ICreateCompanyCredentials>({
mode: "onChange",
defaultValues: {
...defaultValues,
tags: {
...defaultValues?.tags,
categories: defaultValues?.tags.categories.map((item) => ({
value: item.id,
text: item.name,
})),
},
},
});
const router = useRouter();
const { data: companyCategories } = useCompanyCategoriesQuery();
const { mutate: createCompany, isPending: isCreating } = useCreateCompany();
const { mutate: updateCompany, isPending: isUpdating } = useUpdateCompany(
id as string,
@@ -30,33 +49,6 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
founded_year: data.founded_year ? +data.founded_year : 0,
employee_count: data.employee_count ? +data.employee_count : 0,
annual_revenue: data.annual_revenue ? +data.annual_revenue : 0,
tags: {
keywords: data.tags?.keywords
? (data.tags.keywords as unknown as string)
.split(",")
.map((s) => s.trim())
: [],
categories: data.tags?.categories
? (data.tags.categories as unknown as string)
.split(",")
.map((s) => s.trim())
: [],
specializations: data.tags?.specializations
? (data.tags.specializations as unknown as string)
.split(",")
.map((s) => s.trim())
: [],
certifications: data.tags?.certifications
? (data.tags.certifications as unknown as string)
.split(",")
.map((s) => s.trim())
: [],
cpv_codes: data.tags?.cpv_codes
? (data.tags.cpv_codes as unknown as string)
.split(",")
.map((s) => s.trim())
: [],
},
};
if (editMode) {
updateCompany({
@@ -80,7 +72,10 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
isMutating,
isCreating,
isUpdating,
router
router,
watch,
setValue,
companyCategories,
};
};
@@ -1,13 +1,13 @@
"use client";
import { GlobeIcon } from "@/assets/icons";
import InputGroup from "@/components/FormElements/InputGroup";
import MultiSelect from "@/components/FormElements/MultiSelect";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts";
import { CreateCustomerCredentials } from "@/lib/api";
import useCreateCustomerPresenter from "./useCreateCustomerPresenter";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import InputGroup from "@/components/FormElements/InputGroup";
import { FormErrorMessages } from "@/constants/Texts";
import { Select } from "@/components/FormElements/select";
import MultiSelect from "@/components/FormElements/MultiSelect";
import { GlobeIcon, UserIcon } from "@/assets/icons";
interface IProps {
editMode?: boolean;