️ 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,48 +30,98 @@ export const DROPDOWN_ITEMS: SelectOption[] = [
}, },
]; ];
export const renderDropdowns = () => ( export const DropdownExample = () => {
const [singleValue, setSingleValue] = useState<SelectOption>();
const [multipleValue, setMultipleValue] = useState<SelectOption[]>([]);
const handleSelect = (
type: 'single' | 'multiple',
value: SelectOption | SelectOption[],
) => {
if (type === 'single') {
setSingleValue(value as SelectOption);
} else {
setMultipleValue(value as SelectOption[]);
}
};
return (
<> <>
<p className="text-sm text-center text-gray-500 -mb-8">Single Small</p> <p className="text-sm text-center text-gray-500 -mb-8">Single Small</p>
<div className="flex gap-4 flex-wrap justify-center"> <div className="flex gap-4 flex-wrap justify-center">
<Select size="sm" placeholder="Default" options={DROPDOWN_ITEMS} /> <Select
size="sm"
placeholder="Default"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select <Select
size="sm" size="sm"
placeholder="Clearable" placeholder="Clearable"
clearable clearable
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
<Select <Select
size="sm" size="sm"
searchable searchable
placeholder="Searchable" placeholder="Searchable"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
<Select <Select
size="sm" size="sm"
placeholder="Vertical" placeholder="Vertical"
orientation="vertical" orientation="vertical"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
</div> </div>
<p className="text-sm text-center text-gray-500 -mb-8">Single Medium</p> <p className="text-sm text-center text-gray-500 -mb-8">Single Medium</p>
<div className="flex gap-4 flex-wrap justify-center"> <div className="flex gap-4 flex-wrap justify-center">
<Select placeholder="Default" options={DROPDOWN_ITEMS} /> <Select
<Select placeholder="Clearable" clearable options={DROPDOWN_ITEMS} /> placeholder="Default"
<Select searchable placeholder="Searchable" options={DROPDOWN_ITEMS} /> options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select
placeholder="Clearable"
clearable
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select
searchable
placeholder="Searchable"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select <Select
placeholder="Vertical" placeholder="Vertical"
orientation="vertical" orientation="vertical"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
</div> </div>
<p className="text-sm text-center text-gray-500 -mb-8">Multiple Small</p> <p className="text-sm text-center text-gray-500 -mb-8">
Multiple Small
</p>
<div className="flex gap-4 flex-wrap justify-center"> <div className="flex gap-4 flex-wrap justify-center">
<Select <Select
multiple multiple
size="sm" size="sm"
placeholder="Default" placeholder="Default"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
multiple multiple
@ -79,6 +129,8 @@ export const renderDropdowns = () => (
placeholder="Clearable" placeholder="Clearable"
clearable clearable
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
searchable searchable
@ -86,6 +138,8 @@ export const renderDropdowns = () => (
size="sm" size="sm"
placeholder="Searchable" placeholder="Searchable"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
multiple multiple
@ -93,6 +147,8 @@ export const renderDropdowns = () => (
placeholder="Vertical" placeholder="Vertical"
orientation="vertical" orientation="vertical"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
multiple multiple
@ -101,28 +157,44 @@ export const renderDropdowns = () => (
orientation="vertical" orientation="vertical"
placeholder="Hide values" placeholder="Hide values"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
</div> </div>
<p className="text-sm text-center text-gray-500 -mb-8">Multiple Medium</p> <p className="text-sm text-center text-gray-500 -mb-8">
Multiple Medium
</p>
<div className="flex gap-4 flex-wrap justify-center"> <div className="flex gap-4 flex-wrap justify-center">
<Select multiple placeholder="Default" options={DROPDOWN_ITEMS} /> <Select
multiple
placeholder="Default"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select <Select
multiple multiple
placeholder="Clearable" placeholder="Clearable"
clearable clearable
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
searchable searchable
multiple multiple
placeholder="Searchable" placeholder="Searchable"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
multiple multiple
placeholder="Vertical" placeholder="Vertical"
orientation="vertical" orientation="vertical"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
multiple multiple
@ -130,6 +202,8 @@ export const renderDropdowns = () => (
orientation="vertical" orientation="vertical"
placeholder="Hide values" placeholder="Hide values"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
</div> </div>
<p className="text-sm text-center text-gray-500 -mb-4"> <p className="text-sm text-center text-gray-500 -mb-4">
@ -141,6 +215,8 @@ export const renderDropdowns = () => (
description="Single select component" description="Single select component"
helperText="This is a helper text" helperText="This is a helper text"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
<Select <Select
label="Clearable" label="Clearable"
@ -148,6 +224,8 @@ export const renderDropdowns = () => (
helperText="This is a helper text" helperText="This is a helper text"
clearable clearable
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
<Select <Select
searchable searchable
@ -155,6 +233,8 @@ export const renderDropdowns = () => (
description="Single select component" description="Single select component"
helperText="This is a helper text" helperText="This is a helper text"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
<Select <Select
label="Vertical" label="Vertical"
@ -162,6 +242,8 @@ export const renderDropdowns = () => (
helperText="This is a helper text" helperText="This is a helper text"
orientation="vertical" orientation="vertical"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/> />
</div> </div>
<p className="text-sm text-center text-gray-500 -mb-4"> <p className="text-sm text-center text-gray-500 -mb-4">
@ -174,6 +256,8 @@ export const renderDropdowns = () => (
helperText="This is a helper text" helperText="This is a helper text"
multiple multiple
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
label="Clearable" label="Clearable"
@ -182,6 +266,8 @@ export const renderDropdowns = () => (
multiple multiple
clearable clearable
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
searchable searchable
@ -190,6 +276,8 @@ export const renderDropdowns = () => (
helperText="This is a helper text" helperText="This is a helper text"
multiple multiple
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
<Select <Select
label="Vertical" label="Vertical"
@ -198,6 +286,8 @@ export const renderDropdowns = () => (
multiple multiple
orientation="vertical" orientation="vertical"
options={DROPDOWN_ITEMS} options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/> />
</div> </div>
<p className="text-sm text-center text-gray-500 -mb-4"> <p className="text-sm text-center text-gray-500 -mb-4">
@ -237,4 +327,5 @@ export const renderDropdowns = () => (
/> />
</div> </div>
</> </>
); );
};