⚡️ feat: make the select to be controller component
This commit is contained in:
parent
d387229e80
commit
6d8723881c
@ -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(
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user