Skip to content

Commit

Permalink
feat: allow multiple selection
Browse files Browse the repository at this point in the history
  • Loading branch information
abelflopes committed Jul 4, 2024
1 parent 5d3701f commit a5642a8
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 12 deletions.
44 changes: 32 additions & 12 deletions packages/components/select/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,62 @@ interface SelectProps extends Omit<React.HTMLAttributes<HTMLElement>, "onChange"
emptyStateMessage: (value: string) => React.ReactNode;
};
onChange?: React.SelectHTMLAttributes<HTMLSelectElement>["onChange"];
value?: React.SelectHTMLAttributes<HTMLSelectElement>["value"];
value?: string | string[];
name?: React.SelectHTMLAttributes<HTMLSelectElement>["name"];
multiple?: React.SelectHTMLAttributes<HTMLSelectElement>["multiple"];
}

function valueAsArray(value: string | string[]): string[] {
return value instanceof Array ? value : [value];
}

/**
* Select is a type of input that allows users to choose one or more options from a list of choices.
* The options are hidden by default and revealed when a user interacts with an element. It shows the currently selected option in its default collapsed state.
* @param props - {@link SelectProps}
* @returns a React element
*/

// eslint-disable-next-line max-lines-per-function
const Select = ({
children,
className,
onFocus,
search: searchOptions,
onChange: selectOnChange,
name: selectName,
value: selectValue,
multiple: selectMultiple,
...props
}: Readonly<SelectProps>): React.ReactElement => {
const onNextRender = useNextRender();
const searchRef = useRef<HTMLInputElement>(null);
const selectRef = useRef<HTMLSelectElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
const [selectedValues, setSelectedValues] = useState(selectValue);
const [open, setOpen] = useState(false);
const [selectedValues, setSelectedValues] = useState<string[] | undefined>(undefined);
const [search, setSearch] = useState("");

const onNextRender = useNextRender();

const filteredOptions = useMemo(
() => options.filter((i) => i.toLowerCase().includes(search.toLowerCase())),
[search],
);

const selectedValuesList = useMemo(() => valueAsArray(selectedValues ?? []), [selectedValues]);

const handleChange = useCallback(
(value: string) => {
setSelectedValues([value]);
(value: string, mode: "select" | "deselect") => {
setSelectedValues((v) => {
if (!selectMultiple && mode === "select") return value;
else if (!selectMultiple && mode === "deselect") return undefined;
else if (mode === "deselect" && v !== undefined)
return valueAsArray(v).filter((i) => i !== value);
else if (v !== undefined) return [...valueAsArray(v), value];
return [value];
});

setOpen(false);
if (!selectMultiple) setOpen(false);

onNextRender(() => {
if (!selectRef.current)
Expand All @@ -71,7 +86,7 @@ const Select = ({
);
});
},
[onNextRender],
[onNextRender, selectMultiple],
);

useOnClickOutside(open, [dropdownRef, inputRef], () => {
Expand All @@ -94,14 +109,19 @@ const Select = ({
setSearch("");
}, [open]);

useEffect(() => {
setSelectedValues(selectValue);
}, [selectValue]);

return (
<>
<select
ref={selectRef}
name={selectName}
multiple={selectMultiple}
onChange={selectOnChange}
value={selectedValues}>
{selectedValues?.map((i) => (
{selectedValuesList.map((i) => (
<option key={i} value={i}>
{i}
</option>
Expand All @@ -112,7 +132,7 @@ const Select = ({
{...props}
rootRef={inputRef}
className={classNames(styles.root, className)}
value={selectedValues?.join(", ") || ""}
value={selectedValuesList.join(", ") || ""}
readOnly
onFocus={(e) => {
setOpen(true);
Expand Down Expand Up @@ -149,9 +169,9 @@ const Select = ({
{filteredOptions.map((i) => (
<Menu.Item
key={i}
skin={selectedValues?.includes(i) ? "primary" : "default"}
skin={selectedValuesList.includes(i) ? "primary" : "default"}
onClick={() => {
handleChange(i);
handleChange(i, selectedValuesList.includes(i) ? "deselect" : "select");
}}>
{i}
</Menu.Item>
Expand Down
7 changes: 7 additions & 0 deletions packages/docs/stories/src/select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ export const WithSearch: Story = {
},
};

export const Multiple: Story = {
args: {
...args,
multiple: true,
},
};

export const Validation: Story = {
args: {
...args,
Expand Down

0 comments on commit a5642a8

Please sign in to comment.