nextui-org / nextui

🚀 Beautiful, fast and modern React UI library.
https://nextui.org
MIT License
21.54k stars 1.4k forks source link

[Feature Request] Autocomplete to work with multi-select and chips #2299

Open hackrmomo opened 8 months ago

hackrmomo commented 8 months ago

Is your feature request related to a problem? Please describe.

I like simpler UIs that functionally support complex use-cases. One such use case is the ability to create a searchable text field that "queues" up items for some request to complete later on. An example being selecting various users to delete or attaching/creating tags for a blog post.

Describe the solution you'd like

I would like for the Autocomplete component to allow adding the ability to select multiple entries and also render them as chips. Additionally it would be nice if it also had the ability to create items on the fly (referring to my earlier example - creating a tag you should be able to very easily without employing a hacky method be allowed to type an autocomplete value and hit a button in the dropdown saying "add")

Describe alternatives you've considered

Currently the only one that works for my use case is the Select component with the multi-select ability + using chips. This however does not allow for the ability to dynamically add an entry without employing a hacky solution that adds bulk to the UI.

Screenshots or Videos

Material UI currently supports this and here is an example: image

QP-MRMousavi commented 4 months ago

i wrote this code to handle it with Select input using "react-select" library and a similar design to NextUi: import React, { useState, useCallback, ReactNode, useEffect } from "react"; import Select, { ActionMeta, MultiValue, SingleValue, components, } from "react-select";

import { Chip, SelectionMode } from "@nextui-org/react";

type Item = { value: number; label: string; searchingValue: string; subHeader: ReactNode; };

interface Props { items: Item[]; label: string; selectionMode: SelectionMode; placeholder: string; selectedKeys: Set; className: string; elementKey: string; haveSearch: boolean | undefined; isRequired: boolean | undefined; isLoading: boolean | undefined; isDisabled: boolean | undefined; onSelectionChange: ((keys: Set) => void) | undefined; }

const MultiSelectInputWithSearch = ({ items, label, selectionMode, placeholder, selectedKeys, className, elementKey, onSelectionChange, haveSearch = false, isLoading = false, isDisabled = false, isRequired = false, }: Props) => { const [selectedOptions, setSelectedOptions] = useState< MultiValue | SingleValue | null

(null);

const [isDropDownOpen, setIsDropDownOpen] = useState(false);

useEffect(() => { if ( selectedKeys.size === 0 && Array.isArray(selectedOptions) && selectedOptions.length !== 0 ) { setSelectedOptions(null); } }, [selectedKeys]);

const handleChange = useCallback( ( newValue: MultiValue | SingleValue | null, actionMeta: ActionMeta ) => { setValueOfSelections(newValue); setIsDropDownOpen(true); }, [] );

function deleteSelectedItemByValue(value: number) { const allNewSelectedItems = (selectedOptions as MultiValue).filter( (item) => item.value !== value ); setValueOfSelections(allNewSelectedItems); }

function setValueOfSelections( newValue: MultiValue | SingleValue | null ) { setSelectedOptions(newValue); if (onSelectionChange) { const selectedKeys = new Set([]); if (newValue) { if (Array.isArray(newValue)) { newValue.forEach((o) => { selectedKeys.add(o.value.toString()); }); } else { selectedKeys.add((newValue as SingleValue)!.value.toString()); } } onSelectionChange(selectedKeys); } }

const formatOptionLabel = ({ value, label, subHeader }: Item) => (

{label} {subHeader && ( {subHeader} )}

);

const MultiValueContainer = (props: any) => ( <components.MultiValueContainer {...props}> <Chip color="default" variant="bordered" key={${elementKey}-${props.data.value}} onClose={() => deleteSelectedItemByValue(props.data.value)}

{props.data.label} </components.MultiValueContainer> );

const handleSelectAll = () => { const allValues = items.map((item) => item.value.toString()); const selectedKeys = new Set(allValues); setSelectedOptions(items); if (onSelectionChange) { onSelectionChange(selectedKeys); } };

return (