Skip to content

Commit 7994107

Browse files
authored
Feat: Created shadcn MultiSelectOption comp (#10042)
* shadcn multiselect * id added * icon added * lint fixed * discard package.json * discard lock file * delete old comp * add i18n * add i18n * terminology changed * terminology changed * add i18n * Empty-Commit
1 parent c49d07b commit 7994107

File tree

5 files changed

+262
-251
lines changed

5 files changed

+262
-251
lines changed

public/locale/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,6 +1873,7 @@
18731873
"select_eligible_policy": "Select an Eligible Insurance Policy",
18741874
"select_facility": "Select Facility",
18751875
"select_facility_description": "Select the healthcare facility that will provide the requested resource.",
1876+
"select_facility_feature": "Select Facility Features",
18761877
"select_facility_for_discharged_patients_warning": "Facility needs to be selected to view discharged patients.",
18771878
"select_facility_type": "Select Facility Type",
18781879
"select_for_administration": "Select for Administration",

src/components/Facility/FacilityForm.tsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
FormMessage,
2020
} from "@/components/ui/form";
2121
import { Input } from "@/components/ui/input";
22+
import { MultiSelect } from "@/components/ui/multi-select";
2223
import {
2324
Select,
2425
SelectContent,
@@ -29,7 +30,6 @@ import {
2930
import { Textarea } from "@/components/ui/textarea";
3031

3132
import { FacilityModel } from "@/components/Facility/models";
32-
import { MultiSelectFormField } from "@/components/Form/FormFields/SelectFormField";
3333

3434
import { useStateAndDistrictFromPincode } from "@/hooks/useStateAndDistrictFromPincode";
3535

@@ -112,7 +112,6 @@ export default function FacilityForm(props: FacilityProps) {
112112
onSubmitSuccess?.();
113113
},
114114
});
115-
116115
const { mutate: updateFacility, isPending: isUpdatePending } = useMutation({
117116
mutationFn: mutate(routes.updateFacility, {
118117
pathParams: { id: facilityId || "" },
@@ -148,8 +147,8 @@ export default function FacilityForm(props: FacilityProps) {
148147
}
149148
};
150149

151-
const handleFeatureChange = (value: any) => {
152-
const { value: features }: { value: Array<number> } = value;
150+
const handleFeatureChange = (value: string[]) => {
151+
const features = value.map((val) => Number(val));
153152
form.setValue("features", features);
154153
};
155154

@@ -273,7 +272,6 @@ export default function FacilityForm(props: FacilityProps) {
273272
)}
274273
/>
275274
</div>
276-
277275
<FormField
278276
control={form.control}
279277
name="description"
@@ -291,29 +289,30 @@ export default function FacilityForm(props: FacilityProps) {
291289
</FormItem>
292290
)}
293291
/>
294-
295292
<FormField
296293
control={form.control}
297294
name="features"
298-
render={({ field }) => (
299-
<FormItem>
300-
<FormLabel>Features</FormLabel>
301-
<FormControl>
302-
<MultiSelectFormField
303-
name={field.name}
304-
value={field.value}
305-
placeholder="Select facility features"
306-
options={FACILITY_FEATURE_TYPES}
307-
optionLabel={(o) => o.name}
308-
optionValue={(o) => o.id}
309-
onChange={handleFeatureChange}
310-
error={form.formState.errors.features?.message}
311-
id="facility-features"
312-
/>
313-
</FormControl>
314-
<FormMessage />
315-
</FormItem>
316-
)}
295+
render={({ field }) => {
296+
return (
297+
<FormItem>
298+
<FormLabel>{t("features")}</FormLabel>
299+
<FormControl>
300+
<MultiSelect
301+
options={FACILITY_FEATURE_TYPES.map((obj) => ({
302+
value: obj.id.toString(),
303+
label: obj.name,
304+
icon: obj.icon,
305+
}))}
306+
onValueChange={handleFeatureChange}
307+
value={field.value.map((val) => val.toString())}
308+
placeholder={t("select_facility_feature")}
309+
id="facility-features"
310+
/>
311+
</FormControl>
312+
<FormMessage />
313+
</FormItem>
314+
);
315+
}}
317316
/>
318317
</div>
319318

@@ -519,6 +518,7 @@ export default function FacilityForm(props: FacilityProps) {
519518
<Button
520519
type="submit"
521520
className="w-full"
521+
variant="primary"
522522
disabled={facilityId ? isUpdatePending : isPending}
523523
data-cy={facilityId ? "update-facility" : "submit-facility"}
524524
>

src/components/Form/FormFields/SelectFormField.tsx

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
FormFieldBaseProps,
44
useFormFieldPropsResolver,
55
} from "@/components/Form/FormFields/Utils";
6-
import MultiSelectMenuV2 from "@/components/Form/MultiSelectMenuV2";
76
import SelectMenuV2 from "@/components/Form/SelectMenuV2";
87

98
type OptionCallback<T, R> = (option: T) => R;
@@ -49,41 +48,3 @@ export const SelectFormField = <T, V>(props: SelectFormFieldProps<T, V>) => {
4948
</FormField>
5049
);
5150
};
52-
53-
type MultiSelectFormFieldProps<T, V = T> = FormFieldBaseProps<V[]> & {
54-
placeholder?: React.ReactNode;
55-
options: readonly T[];
56-
optionLabel: OptionCallback<T, React.ReactNode>;
57-
optionSelectedLabel?: OptionCallback<T, React.ReactNode>;
58-
optionDescription?: OptionCallback<T, React.ReactNode>;
59-
optionIcon?: OptionCallback<T, React.ReactNode>;
60-
optionValue?: OptionCallback<T, V>;
61-
optionDisabled?: OptionCallback<T, boolean>;
62-
};
63-
64-
/**
65-
* @deprecated
66-
*/
67-
export const MultiSelectFormField = <T, V>(
68-
props: MultiSelectFormFieldProps<T, V>,
69-
) => {
70-
const field = useFormFieldPropsResolver(props);
71-
return (
72-
<FormField field={field}>
73-
<MultiSelectMenuV2
74-
id={field.id}
75-
disabled={field.disabled}
76-
value={field.value}
77-
onChange={(value: any) => field.handleChange(value)}
78-
options={props.options}
79-
placeholder={props.placeholder}
80-
optionLabel={props.optionLabel}
81-
optionSelectedLabel={props.optionSelectedLabel}
82-
optionDescription={props.optionDescription}
83-
optionIcon={props.optionIcon}
84-
optionDisabled={props.optionDisabled}
85-
optionValue={props.optionValue}
86-
/>
87-
</FormField>
88-
);
89-
};

src/components/Form/MultiSelectMenuV2.tsx

Lines changed: 1 addition & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1,195 +1,9 @@
1-
import {
2-
Label,
3-
Listbox,
4-
ListboxButton,
5-
ListboxOption,
6-
ListboxOptions,
7-
} from "@headlessui/react";
8-
import { ReactNode, useRef } from "react";
1+
import { ReactNode } from "react";
92

103
import CareIcon from "@/CAREUI/icons/CareIcon";
114

125
import { classNames } from "@/Utils/utils";
136

14-
type OptionCallback<T, R = void> = (option: T) => R;
15-
16-
type Props<T, V = T> = {
17-
id?: string;
18-
options: readonly T[];
19-
value: V[] | undefined;
20-
placeholder?: ReactNode;
21-
optionLabel: OptionCallback<T, ReactNode>;
22-
optionSelectedLabel?: OptionCallback<T, ReactNode>;
23-
optionDescription?: OptionCallback<T, ReactNode>;
24-
optionIcon?: OptionCallback<T, ReactNode>;
25-
optionValue?: OptionCallback<T, V>;
26-
optionDisabled?: OptionCallback<T, boolean>;
27-
className?: string;
28-
disabled?: boolean;
29-
renderSelectedOptions?: OptionCallback<T[], ReactNode>;
30-
onChange: OptionCallback<V[]>;
31-
};
32-
33-
/**
34-
* Avoid using this component directly. Use `MultiSelectFormField` instead as
35-
* its API is easier to use and compliant with `FormField` based components.
36-
*
37-
* Use this only when you want to hack into the design and get more
38-
* customizability.
39-
*/
40-
const MultiSelectMenuV2 = <T, V>(props: Props<T, V>) => {
41-
const options = props.options.map((option) => {
42-
const label = props.optionLabel(option);
43-
const selectedLabel = props.optionSelectedLabel
44-
? props.optionSelectedLabel(option)
45-
: label;
46-
47-
const value = props.optionValue ? props.optionValue(option) : option;
48-
49-
return {
50-
option,
51-
label,
52-
selectedLabel,
53-
description: props.optionDescription?.(option),
54-
icon: props.optionIcon?.(option),
55-
value,
56-
disabled: props.optionDisabled?.(option),
57-
isSelected: props.value?.includes(value as any) ?? false,
58-
displayChip: (
59-
<div className="rounded-full border border-secondary-400 bg-secondary-100 px-2 text-xs text-secondary-900">
60-
{selectedLabel}
61-
</div>
62-
),
63-
};
64-
});
65-
66-
const placeholder = props.placeholder ?? "Select";
67-
const selectedOptions = options.filter((o) => o.isSelected);
68-
69-
const Placeholder: () => any = () => {
70-
if (selectedOptions.length === 0) return placeholder;
71-
if (props.renderSelectedOptions)
72-
return props.renderSelectedOptions(selectedOptions.map((o) => o.option));
73-
};
74-
75-
const buttonRef = useRef<HTMLButtonElement>(null);
76-
77-
const handleSingleSelect = (o: any) => {
78-
if (
79-
o.option?.isSingleSelect === true &&
80-
!selectedOptions.includes(o) &&
81-
buttonRef.current
82-
) {
83-
buttonRef.current.click();
84-
}
85-
};
86-
87-
return (
88-
<div className={props.className} id={props.id}>
89-
<Listbox
90-
disabled={props.disabled}
91-
value={selectedOptions}
92-
onChange={(opts: typeof options) =>
93-
props.onChange(opts.map((o) => o.value) as any)
94-
}
95-
multiple
96-
>
97-
<>
98-
<Label className="sr-only !relative">{props.placeholder}</Label>
99-
<div className="relative">
100-
<div>
101-
<ListboxButton
102-
className="cui-input-base flex w-full rounded"
103-
ref={buttonRef}
104-
>
105-
<div className="relative z-0 flex w-full items-center">
106-
<div className="relative flex flex-1 items-center pr-4 focus:z-10">
107-
<p className="ml-2.5 text-sm font-normal text-secondary-600">
108-
<Placeholder />
109-
</p>
110-
111-
{selectedOptions.length !== 0 && (
112-
<div className="flex flex-wrap gap-2">
113-
{selectedOptions.map((option, index) => (
114-
<MultiSelectOptionChip
115-
key={index}
116-
label={option.selectedLabel}
117-
onRemove={() => {
118-
const updatedOptions = selectedOptions.filter(
119-
(selectedOption) =>
120-
selectedOption.value !== option.value,
121-
);
122-
props.onChange(
123-
updatedOptions.map((o) => o.value) as any,
124-
);
125-
}}
126-
/>
127-
))}
128-
</div>
129-
)}
130-
</div>
131-
<CareIcon
132-
id="dropdown-toggle"
133-
icon="l-angle-down"
134-
className="-mb-0.5 text-lg text-secondary-900"
135-
/>
136-
</div>
137-
</ListboxButton>
138-
</div>
139-
<ListboxOptions
140-
modal={false}
141-
as="ul"
142-
className="cui-dropdown-base absolute top-full"
143-
>
144-
{options.map((option, index) => (
145-
<ListboxOption
146-
as="li"
147-
id={`${props.id}-option-${index}`}
148-
key={index}
149-
className={dropdownOptionClassNames}
150-
value={option}
151-
onClick={() => handleSingleSelect(option)}
152-
disabled={option.disabled}
153-
>
154-
{({ focus }) => (
155-
<div className="flex flex-col gap-2">
156-
<div className="flex justify-between">
157-
{option.label}
158-
{(option.icon || option.isSelected) &&
159-
(option.isSelected ? (
160-
<CareIcon icon="l-check" className="text-lg" />
161-
) : (
162-
option.icon
163-
))}
164-
</div>
165-
{option.description && (
166-
<p
167-
className={classNames(
168-
"text-sm font-normal",
169-
option.disabled
170-
? "text-secondary-500"
171-
: focus
172-
? "text-primary-200"
173-
: "text-secondary-500",
174-
)}
175-
>
176-
{option.description}
177-
</p>
178-
)}
179-
</div>
180-
)}
181-
</ListboxOption>
182-
))}
183-
</ListboxOptions>
184-
</div>
185-
</>
186-
</Listbox>
187-
</div>
188-
);
189-
};
190-
191-
export default MultiSelectMenuV2;
192-
1937
interface MultiSelectOptionChipProps {
1948
label: ReactNode;
1959
onRemove?: () => void;

0 commit comments

Comments
 (0)