️ feat: make the select to be controller component

This commit is contained in:
Wahyu Kurniawan 2024-02-24 14:30:47 +07:00
parent d387229e80
commit 6d8723881c
No known key found for this signature in database
GPG Key ID: 040A1549143A8E33
3 changed files with 348 additions and 212 deletions

View File

@ -53,7 +53,10 @@ export type SelectOption = {
export type SelectOrientation = 'horizontal' | 'vertical'; export type SelectOrientation = 'horizontal' | 'vertical';
interface SelectProps interface SelectProps
extends Omit<ComponentPropsWithoutRef<'input'>, 'size'>, extends Omit<
ComponentPropsWithoutRef<'input'>,
'size' | 'value' | 'onChange'
>,
SelectTheme { SelectTheme {
/** /**
* The options of the select * The options of the select
@ -95,6 +98,18 @@ interface SelectProps
* Show the values of the select if it's multiple * Show the values of the select if it's multiple
*/ */
hideValues?: boolean; hideValues?: boolean;
/**
* The value of the select
*/
value?: SelectOption | SelectOption[];
/**
* Callback function when reset the select
*/
onClear?: () => void;
/**
* Callback function when the value of the select changes
*/
onChange?: (value: SelectOption | SelectOption[]) => void;
} }
export const Select = ({ export const Select = ({
@ -113,6 +128,9 @@ export const Select = ({
helperText, helperText,
hideValues = false, hideValues = false,
placeholder: placeholderProp = 'Select an option', placeholder: placeholderProp = 'Select an option',
value,
onChange,
onClear,
}: SelectProps) => { }: SelectProps) => {
const theme = selectTheme({ size, error, variant, orientation }); const theme = selectTheme({ size, error, variant, orientation });
@ -148,9 +166,26 @@ export const Select = ({
} }
}, [dropdownOpen]); // Re-calculate whenever the dropdown is opened }, [dropdownOpen]); // Re-calculate whenever the dropdown is opened
useEffect(() => {
// If multiple selection is enabled, ensure the internal state is an array
if (multiple) {
if (Array.isArray(value)) {
// Directly use the provided array
setSelectedItems(value);
} else {
// Reset or set to empty array if the value is not an array
setSelectedItems([]);
}
} else {
// For single selection, directly set the selected item
setSelectedItem(value as SelectOption);
}
}, [value, multiple]);
const handleSelectedItemChange = (selectedItem: SelectOption | null) => { const handleSelectedItemChange = (selectedItem: SelectOption | null) => {
setSelectedItem(selectedItem); setSelectedItem(selectedItem);
setInputValue(selectedItem ? selectedItem.label : ''); setInputValue(selectedItem ? selectedItem.label : '');
onChange?.(selectedItem as SelectOption);
}; };
const { const {
@ -159,6 +194,7 @@ export const Select = ({
addSelectedItem, addSelectedItem,
removeSelectedItem, removeSelectedItem,
selectedItems, selectedItems,
setSelectedItems,
reset, reset,
} = useMultipleSelection<SelectOption>({ } = useMultipleSelection<SelectOption>({
onSelectedItemsChange: multiple onSelectedItemsChange: multiple
@ -208,9 +244,17 @@ export const Select = ({
// If the item is not already selected, add it to the selected items // If the item is not already selected, add it to the selected items
if (!selectedItems.includes(selectedItem)) { if (!selectedItems.includes(selectedItem)) {
addSelectedItem(selectedItem); addSelectedItem(selectedItem);
// Callback for `onChange`
const newSelectedItems = [...selectedItems, selectedItem];
onChange?.(newSelectedItems);
} else { } else {
// If the item is already selected, remove it from the selected items // If the item is already selected, remove it from the selected items
removeSelectedItem(selectedItem); removeSelectedItem(selectedItem);
// Callback for `onChange`
const newSelectedItems = selectedItems.filter(
(item) => selectedItem !== item,
);
onChange?.(newSelectedItems);
} }
setInputValue(''); setInputValue('');
} }
@ -237,6 +281,7 @@ export const Select = ({
reset(); reset();
setSelectedItem(null); setSelectedItem(null);
setInputValue(''); setInputValue('');
onClear?.();
}; };
const renderLabels = useMemo( const renderLabels = useMemo(

View File

@ -31,7 +31,7 @@ import {
import { renderDefaultTag, renderMinimalTag } from './renders/tag'; import { renderDefaultTag, renderMinimalTag } from './renders/tag';
import { renderToast, renderToastsWithCta } from './renders/toast'; import { renderToast, renderToastsWithCta } from './renders/toast';
import { renderTooltips } from './renders/tooltip'; import { renderTooltips } from './renders/tooltip';
import { renderDropdowns } from './renders/dropdown'; import { DropdownExample } from './renders/dropdown';
type ValuePiece = Date | null; type ValuePiece = Date | null;
type Value = ValuePiece | [ValuePiece, ValuePiece]; type Value = ValuePiece | [ValuePiece, ValuePiece];
@ -274,7 +274,7 @@ const Page: React.FC = () => {
{/* Dropdown */} {/* Dropdown */}
<div className="flex flex-col gap-10 items-center justify-between"> <div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Dropdown / Select</h1> <h1 className="text-2xl font-bold">Dropdown / Select</h1>
{renderDropdowns()} {<DropdownExample />}
</div> </div>
<div className="w-full h border border-gray-200 px-20 my-10" /> <div className="w-full h border border-gray-200 px-20 my-10" />

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import { PencilIcon } from 'components/shared/CustomIcon'; import { PencilIcon } from 'components/shared/CustomIcon';
import { SelectOption, Select } from 'components/shared/Select'; import { SelectOption, Select } from 'components/shared/Select';
@ -30,211 +30,302 @@ export const DROPDOWN_ITEMS: SelectOption[] = [
}, },
]; ];
export const renderDropdowns = () => ( export const DropdownExample = () => {
<> const [singleValue, setSingleValue] = useState<SelectOption>();
<p className="text-sm text-center text-gray-500 -mb-8">Single Small</p> const [multipleValue, setMultipleValue] = useState<SelectOption[]>([]);
<div className="flex gap-4 flex-wrap justify-center">
<Select size="sm" placeholder="Default" options={DROPDOWN_ITEMS} /> const handleSelect = (
<Select type: 'single' | 'multiple',
size="sm" value: SelectOption | SelectOption[],
placeholder="Clearable" ) => {
clearable if (type === 'single') {
options={DROPDOWN_ITEMS} setSingleValue(value as SelectOption);
/> } else {
<Select setMultipleValue(value as SelectOption[]);
size="sm" }
searchable };
placeholder="Searchable"
options={DROPDOWN_ITEMS} return (
/> <>
<Select <p className="text-sm text-center text-gray-500 -mb-8">Single Small</p>
size="sm" <div className="flex gap-4 flex-wrap justify-center">
placeholder="Vertical" <Select
orientation="vertical" size="sm"
options={DROPDOWN_ITEMS} placeholder="Default"
/> options={DROPDOWN_ITEMS}
</div> value={singleValue}
<p className="text-sm text-center text-gray-500 -mb-8">Single Medium</p> onChange={(value) => handleSelect('single', value)}
<div className="flex gap-4 flex-wrap justify-center"> />
<Select placeholder="Default" options={DROPDOWN_ITEMS} /> <Select
<Select placeholder="Clearable" clearable options={DROPDOWN_ITEMS} /> size="sm"
<Select searchable placeholder="Searchable" options={DROPDOWN_ITEMS} /> placeholder="Clearable"
<Select clearable
placeholder="Vertical" options={DROPDOWN_ITEMS}
orientation="vertical" value={singleValue}
options={DROPDOWN_ITEMS} onChange={(value) => handleSelect('single', value)}
/> />
</div> <Select
<p className="text-sm text-center text-gray-500 -mb-8">Multiple Small</p> size="sm"
<div className="flex gap-4 flex-wrap justify-center"> searchable
<Select placeholder="Searchable"
multiple options={DROPDOWN_ITEMS}
size="sm" value={singleValue}
placeholder="Default" onChange={(value) => handleSelect('single', value)}
options={DROPDOWN_ITEMS} />
/> <Select
<Select size="sm"
multiple placeholder="Vertical"
size="sm" orientation="vertical"
placeholder="Clearable" options={DROPDOWN_ITEMS}
clearable value={singleValue}
options={DROPDOWN_ITEMS} onChange={(value) => handleSelect('single', value)}
/> />
<Select </div>
searchable <p className="text-sm text-center text-gray-500 -mb-8">Single Medium</p>
multiple <div className="flex gap-4 flex-wrap justify-center">
size="sm" <Select
placeholder="Searchable" placeholder="Default"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
/> value={singleValue}
<Select onChange={(value) => handleSelect('single', value)}
multiple />
size="sm" <Select
placeholder="Vertical" placeholder="Clearable"
orientation="vertical" clearable
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
/> value={singleValue}
<Select onChange={(value) => handleSelect('single', value)}
multiple />
hideValues <Select
size="sm" searchable
orientation="vertical" placeholder="Searchable"
placeholder="Hide values" options={DROPDOWN_ITEMS}
options={DROPDOWN_ITEMS} value={singleValue}
/> onChange={(value) => handleSelect('single', value)}
</div> />
<p className="text-sm text-center text-gray-500 -mb-8">Multiple Medium</p> <Select
<div className="flex gap-4 flex-wrap justify-center"> placeholder="Vertical"
<Select multiple placeholder="Default" options={DROPDOWN_ITEMS} /> orientation="vertical"
<Select options={DROPDOWN_ITEMS}
multiple value={singleValue}
placeholder="Clearable" onChange={(value) => handleSelect('single', value)}
clearable />
options={DROPDOWN_ITEMS} </div>
/> <p className="text-sm text-center text-gray-500 -mb-8">
<Select Multiple Small
searchable </p>
multiple <div className="flex gap-4 flex-wrap justify-center">
placeholder="Searchable" <Select
options={DROPDOWN_ITEMS} multiple
/> size="sm"
<Select placeholder="Default"
multiple options={DROPDOWN_ITEMS}
placeholder="Vertical" value={multipleValue}
orientation="vertical" onChange={(value) => handleSelect('multiple', value)}
options={DROPDOWN_ITEMS} />
/> <Select
<Select multiple
multiple size="sm"
hideValues placeholder="Clearable"
orientation="vertical" clearable
placeholder="Hide values" options={DROPDOWN_ITEMS}
options={DROPDOWN_ITEMS} value={multipleValue}
/> onChange={(value) => handleSelect('multiple', value)}
</div> />
<p className="text-sm text-center text-gray-500 -mb-4"> <Select
Single With label, description, and helper text searchable
</p> multiple
<div className="flex gap-4 flex-wrap justify-center"> size="sm"
<Select placeholder="Searchable"
label="Default" options={DROPDOWN_ITEMS}
description="Single select component" value={multipleValue}
helperText="This is a helper text" onChange={(value) => handleSelect('multiple', value)}
options={DROPDOWN_ITEMS} />
/> <Select
<Select multiple
label="Clearable" size="sm"
description="Single select component" placeholder="Vertical"
helperText="This is a helper text" orientation="vertical"
clearable options={DROPDOWN_ITEMS}
options={DROPDOWN_ITEMS} value={multipleValue}
/> onChange={(value) => handleSelect('multiple', value)}
<Select />
searchable <Select
label="Searchable" multiple
description="Single select component" hideValues
helperText="This is a helper text" size="sm"
options={DROPDOWN_ITEMS} orientation="vertical"
/> placeholder="Hide values"
<Select options={DROPDOWN_ITEMS}
label="Vertical" value={multipleValue}
description="Single select component" onChange={(value) => handleSelect('multiple', value)}
helperText="This is a helper text" />
orientation="vertical" </div>
options={DROPDOWN_ITEMS} <p className="text-sm text-center text-gray-500 -mb-8">
/> Multiple Medium
</div> </p>
<p className="text-sm text-center text-gray-500 -mb-4"> <div className="flex gap-4 flex-wrap justify-center">
Multiple With label, description, and helper text <Select
</p> multiple
<div className="flex gap-4 flex-wrap justify-center"> placeholder="Default"
<Select options={DROPDOWN_ITEMS}
label="Default" value={multipleValue}
description="Multiple select component" onChange={(value) => handleSelect('multiple', value)}
helperText="This is a helper text" />
multiple <Select
options={DROPDOWN_ITEMS} multiple
/> placeholder="Clearable"
<Select clearable
label="Clearable" options={DROPDOWN_ITEMS}
description="Multiple select component" value={multipleValue}
helperText="This is a helper text" onChange={(value) => handleSelect('multiple', value)}
multiple />
clearable <Select
options={DROPDOWN_ITEMS} searchable
/> multiple
<Select placeholder="Searchable"
searchable options={DROPDOWN_ITEMS}
label="Searchable" value={multipleValue}
description="Multiple select component" onChange={(value) => handleSelect('multiple', value)}
helperText="This is a helper text" />
multiple <Select
options={DROPDOWN_ITEMS} multiple
/> placeholder="Vertical"
<Select orientation="vertical"
label="Vertical" options={DROPDOWN_ITEMS}
description="Multiple select component" value={multipleValue}
helperText="This is a helper text" onChange={(value) => handleSelect('multiple', value)}
multiple />
orientation="vertical" <Select
options={DROPDOWN_ITEMS} multiple
/> hideValues
</div> orientation="vertical"
<p className="text-sm text-center text-gray-500 -mb-4"> placeholder="Hide values"
Error With label, description, and helper text options={DROPDOWN_ITEMS}
</p> value={multipleValue}
<div className="flex gap-4 flex-wrap justify-center"> onChange={(value) => handleSelect('multiple', value)}
<Select />
error </div>
label="Default" <p className="text-sm text-center text-gray-500 -mb-4">
description="Multiple select component" Single With label, description, and helper text
helperText="This is a helper text" </p>
options={DROPDOWN_ITEMS} <div className="flex gap-4 flex-wrap justify-center">
/> <Select
<Select label="Default"
error description="Single select component"
label="Clearable" helperText="This is a helper text"
description="Multiple select component" options={DROPDOWN_ITEMS}
helperText="This is a helper text" value={singleValue}
clearable onChange={(value) => handleSelect('single', value)}
options={DROPDOWN_ITEMS} />
/> <Select
<Select label="Clearable"
error description="Single select component"
searchable helperText="This is a helper text"
label="Searchable" clearable
description="Multiple select component" options={DROPDOWN_ITEMS}
helperText="This is a helper text" value={singleValue}
options={DROPDOWN_ITEMS} onChange={(value) => handleSelect('single', value)}
/> />
<Select <Select
error searchable
label="Vertical" label="Searchable"
description="Multiple select component" description="Single select component"
helperText="This is a helper text" helperText="This is a helper text"
orientation="vertical" options={DROPDOWN_ITEMS}
options={DROPDOWN_ITEMS} value={singleValue}
/> onChange={(value) => handleSelect('single', value)}
</div> />
</> <Select
); label="Vertical"
description="Single select component"
helperText="This is a helper text"
orientation="vertical"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
</div>
<p className="text-sm text-center text-gray-500 -mb-4">
Multiple With label, description, and helper text
</p>
<div className="flex gap-4 flex-wrap justify-center">
<Select
label="Default"
description="Multiple select component"
helperText="This is a helper text"
multiple
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
label="Clearable"
description="Multiple select component"
helperText="This is a helper text"
multiple
clearable
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
searchable
label="Searchable"
description="Multiple select component"
helperText="This is a helper text"
multiple
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
label="Vertical"
description="Multiple select component"
helperText="This is a helper text"
multiple
orientation="vertical"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
</div>
<p className="text-sm text-center text-gray-500 -mb-4">
Error With label, description, and helper text
</p>
<div className="flex gap-4 flex-wrap justify-center">
<Select
error
label="Default"
description="Multiple select component"
helperText="This is a helper text"
options={DROPDOWN_ITEMS}
/>
<Select
error
label="Clearable"
description="Multiple select component"
helperText="This is a helper text"
clearable
options={DROPDOWN_ITEMS}
/>
<Select
error
searchable
label="Searchable"
description="Multiple select component"
helperText="This is a helper text"
options={DROPDOWN_ITEMS}
/>
<Select
error
label="Vertical"
description="Multiple select component"
helperText="This is a helper text"
orientation="vertical"
options={DROPDOWN_ITEMS}
/>
</div>
</>
);
};