️ 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';
interface SelectProps
extends Omit<ComponentPropsWithoutRef<'input'>, 'size'>,
extends Omit<
ComponentPropsWithoutRef<'input'>,
'size' | 'value' | 'onChange'
>,
SelectTheme {
/**
* The options of the select
@ -95,6 +98,18 @@ interface SelectProps
* Show the values of the select if it's multiple
*/
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 = ({
@ -113,6 +128,9 @@ export const Select = ({
helperText,
hideValues = false,
placeholder: placeholderProp = 'Select an option',
value,
onChange,
onClear,
}: SelectProps) => {
const theme = selectTheme({ size, error, variant, orientation });
@ -148,9 +166,26 @@ export const Select = ({
}
}, [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) => {
setSelectedItem(selectedItem);
setInputValue(selectedItem ? selectedItem.label : '');
onChange?.(selectedItem as SelectOption);
};
const {
@ -159,6 +194,7 @@ export const Select = ({
addSelectedItem,
removeSelectedItem,
selectedItems,
setSelectedItems,
reset,
} = useMultipleSelection<SelectOption>({
onSelectedItemsChange: multiple
@ -208,9 +244,17 @@ export const Select = ({
// If the item is not already selected, add it to the selected items
if (!selectedItems.includes(selectedItem)) {
addSelectedItem(selectedItem);
// Callback for `onChange`
const newSelectedItems = [...selectedItems, selectedItem];
onChange?.(newSelectedItems);
} else {
// If the item is already selected, remove it from the selected items
removeSelectedItem(selectedItem);
// Callback for `onChange`
const newSelectedItems = selectedItems.filter(
(item) => selectedItem !== item,
);
onChange?.(newSelectedItems);
}
setInputValue('');
}
@ -237,6 +281,7 @@ export const Select = ({
reset();
setSelectedItem(null);
setInputValue('');
onClear?.();
};
const renderLabels = useMemo(

View File

@ -31,7 +31,7 @@ import {
import { renderDefaultTag, renderMinimalTag } from './renders/tag';
import { renderToast, renderToastsWithCta } from './renders/toast';
import { renderTooltips } from './renders/tooltip';
import { renderDropdowns } from './renders/dropdown';
import { DropdownExample } from './renders/dropdown';
type ValuePiece = Date | null;
type Value = ValuePiece | [ValuePiece, ValuePiece];
@ -274,7 +274,7 @@ const Page: React.FC = () => {
{/* Dropdown */}
<div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Dropdown / Select</h1>
{renderDropdowns()}
{<DropdownExample />}
</div>
<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 { 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>
<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
size="sm"
placeholder="Clearable"
clearable
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select
size="sm"
searchable
placeholder="Searchable"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select
size="sm"
placeholder="Vertical"
orientation="vertical"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
</div>
<p className="text-sm text-center text-gray-500 -mb-8">Single Medium</p>
<div className="flex gap-4 flex-wrap justify-center">
<Select placeholder="Default" options={DROPDOWN_ITEMS} />
<Select placeholder="Clearable" clearable options={DROPDOWN_ITEMS} />
<Select searchable placeholder="Searchable" options={DROPDOWN_ITEMS} />
<Select
placeholder="Default"
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
placeholder="Vertical"
orientation="vertical"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
</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">
<Select
multiple
size="sm"
placeholder="Default"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
multiple
@ -79,6 +129,8 @@ export const renderDropdowns = () => (
placeholder="Clearable"
clearable
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
searchable
@ -86,6 +138,8 @@ export const renderDropdowns = () => (
size="sm"
placeholder="Searchable"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
multiple
@ -93,6 +147,8 @@ export const renderDropdowns = () => (
placeholder="Vertical"
orientation="vertical"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
multiple
@ -101,28 +157,44 @@ export const renderDropdowns = () => (
orientation="vertical"
placeholder="Hide values"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
</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">
<Select multiple placeholder="Default" options={DROPDOWN_ITEMS} />
<Select
multiple
placeholder="Default"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
multiple
placeholder="Clearable"
clearable
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
searchable
multiple
placeholder="Searchable"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
multiple
placeholder="Vertical"
orientation="vertical"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
multiple
@ -130,6 +202,8 @@ export const renderDropdowns = () => (
orientation="vertical"
placeholder="Hide values"
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
</div>
<p className="text-sm text-center text-gray-500 -mb-4">
@ -141,6 +215,8 @@ export const renderDropdowns = () => (
description="Single select component"
helperText="This is a helper text"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select
label="Clearable"
@ -148,6 +224,8 @@ export const renderDropdowns = () => (
helperText="This is a helper text"
clearable
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select
searchable
@ -155,6 +233,8 @@ export const renderDropdowns = () => (
description="Single select component"
helperText="This is a helper text"
options={DROPDOWN_ITEMS}
value={singleValue}
onChange={(value) => handleSelect('single', value)}
/>
<Select
label="Vertical"
@ -162,6 +242,8 @@ export const renderDropdowns = () => (
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">
@ -174,6 +256,8 @@ export const renderDropdowns = () => (
helperText="This is a helper text"
multiple
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
label="Clearable"
@ -182,6 +266,8 @@ export const renderDropdowns = () => (
multiple
clearable
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
searchable
@ -190,6 +276,8 @@ export const renderDropdowns = () => (
helperText="This is a helper text"
multiple
options={DROPDOWN_ITEMS}
value={multipleValue}
onChange={(value) => handleSelect('multiple', value)}
/>
<Select
label="Vertical"
@ -198,6 +286,8 @@ export const renderDropdowns = () => (
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">
@ -238,3 +328,4 @@ export const renderDropdowns = () => (
</div>
</>
);
};