Open hackrmomo opened 8 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
const MultiSelectInputWithSearch = ({
items,
label,
selectionMode,
placeholder,
selectedKeys,
className,
elementKey,
onSelectionChange,
haveSearch = false,
isLoading = false,
isDisabled = false,
isRequired = false,
}: Props) => {
const [selectedOptions, setSelectedOptions] = useState<
MultiValue
(null);
const [isDropDownOpen, setIsDropDownOpen] = useState
useEffect(() => { if ( selectedKeys.size === 0 && Array.isArray(selectedOptions) && selectedOptions.length !== 0 ) { setSelectedOptions(null); } }, [selectedKeys]);
const handleChange = useCallback(
(
newValue: MultiValue
function deleteSelectedItemByValue(value: number) {
const allNewSelectedItems = (selectedOptions as MultiValue
function setValueOfSelections(
newValue: MultiValue
const formatOptionLabel = ({ value, label, subHeader }: Item) => (
);
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
return (
); };
export default MultiSelectInputWithSearch;
and here is the CSS file: / Light Mode / .nextui-select__control { @apply bg-white border border-gray-300 rounded-md shadow-sm px-4 py-2 text-sm text-gray-700; }
.nextui-select__single-value { @apply text-gray-700; }
.nextui-select__multi-value { background-color: unset !important; }
.nextui-select__indicator { @apply text-gray-400; }
.nextui-select__dropdown-indicator { @apply text-gray-400; }
.nextui-select__menu { @apply border border-gray-300 rounded-md shadow-lg; background-color: #ffffff !important; }
.nextui-select__option { @apply px-4 py-2 text-sm text-gray-700 cursor-pointer; }
.nextui-select__option--is-focused { @apply bg-gray-100; }
.nextui-selectoption--is-selected { @apply bg-blue-500 text-white; } .nextui-selectinput-container { @apply text-gray-800; }
/ Dark Mode / .dark .nextui-select__control { @apply bg-gray-800 border border-gray-700 rounded-md shadow-sm px-4 py-2 text-sm text-white; }
.dark .nextui-select__single-value { @apply text-white; }
.dark .nextui-select__indicator { @apply text-gray-400; }
.dark .nextui-select__dropdown-indicator { @apply text-gray-400; }
.dark .nextui-select__menu { @apply border border-gray-700 rounded-md shadow-lg; background-color: #18181b !important; }
.dark .nextui-select__option { @apply px-4 py-2 text-sm text-white cursor-pointer; }
.dark .nextui-select__option--is-focused { @apply bg-gray-700; }
.dark .nextui-selectoption--is-selected { @apply bg-blue-500 text-white; } .dark .nextui-selectmulti-value { background-color: unset !important; } .dark .nextui-select__input-container { @apply text-gray-50; }
I have a simple version without external libraries that worked well for us, display selected items in endContent
(ChipGroup is a row div with chips):
'use client'
import { useState } from 'react'
import { Autocomplete, ChipGroup } from '@ui/components'
export interface AutocompleteWithChipsProps {
items: string[]
label: string
}
export function AutocompleteWithChips({
items: defaultItem,
label,
}: AutocompleteWithChipsProps) {
const [items, setItems] = useState<string[]>(defaultItem)
const [selectedItems, setSelectedItems] = useState<string[]>([])
const [inputValue, setInputValue] = useState('')
const handleSelectItem = (item: string) => {
if (!selectedItems.includes(item)) {
item && setSelectedItems([...selectedItems, item])
} else {
setSelectedItems(selectedItems.filter((f) => f !== item))
}
setInputValue('')
}
const filteredItems = inputValue
? [
...items.filter((item) =>
item.toLowerCase().includes(inputValue.toLowerCase()),
),
`Add: ${inputValue}`,
]
: items
const handleRemoveItem = (item: string) => {
setSelectedItems(
selectedItems.filter((selectedItem) => selectedItem !== item),
)
}
return (
<Autocomplete
items={filteredItems}
label={label}
classNames={{
endContentWrapper: 'w-full',
}}
endContent={
<ChipGroup
className='w-full justify-end'
items={selectedItems.map((item) => ({
key: item,
label: item,
className: 'cursor-pointer',
variant: 'dot',
onClick: () => handleRemoveItem(item),
}))}
/>
}
onSelectionChange={(item) => {
if (item && typeof item === 'string') {
if (item.startsWith('Add: ')) {
if (!items.includes(inputValue)) {
setItems([...items, inputValue])
}
handleSelectItem(inputValue)
} else {
handleSelectItem(item)
}
}
}}
selectedKey={''}
inputValue={inputValue}
onInputChange={setInputValue}
/>
)
}
I have a simple version without external libraries that worked well for us, display selected items in
endContent
(ChipGroup is a row div with chips):'use client' import { useState } from 'react' import { Autocomplete, ChipGroup } from '@ui/components' export interface AutocompleteWithChipsProps { items: string[] label: string } export function AutocompleteWithChips({ items: defaultItem, label, }: AutocompleteWithChipsProps) { const [items, setItems] = useState<string[]>(defaultItem) const [selectedItems, setSelectedItems] = useState<string[]>([]) const [inputValue, setInputValue] = useState('') const handleSelectItem = (item: string) => { if (!selectedItems.includes(item)) { item && setSelectedItems([...selectedItems, item]) } else { setSelectedItems(selectedItems.filter((f) => f !== item)) } setInputValue('') } const filteredItems = inputValue ? [ ...items.filter((item) => item.toLowerCase().includes(inputValue.toLowerCase()), ), `Add: ${inputValue}`, ] : items const handleRemoveItem = (item: string) => { setSelectedItems( selectedItems.filter((selectedItem) => selectedItem !== item), ) } return ( <Autocomplete items={filteredItems} label={label} classNames={{ endContentWrapper: 'w-full', }} endContent={ <ChipGroup className='w-full justify-end' items={selectedItems.map((item) => ({ key: item, label: item, className: 'cursor-pointer', variant: 'dot', onClick: () => handleRemoveItem(item), }))} /> } onSelectionChange={(item) => { if (item && typeof item === 'string') { if (item.startsWith('Add: ')) { if (!items.includes(inputValue)) { setItems([...items, inputValue]) } handleSelectItem(inputValue) } else { handleSelectItem(item) } } }} selectedKey={''} inputValue={inputValue} onInputChange={setInputValue} /> ) }
thanks! can you share the code of ChipGroup please
+1 Need this feature
I have a simple version without external libraries that worked well for us, display selected items in
endContent
(ChipGroup is a row div with chips):'use client' import { useState } from 'react' import { Autocomplete, ChipGroup } from '@ui/components' export interface AutocompleteWithChipsProps { items: string[] label: string } export function AutocompleteWithChips({ items: defaultItem, label, }: AutocompleteWithChipsProps) { const [items, setItems] = useState<string[]>(defaultItem) const [selectedItems, setSelectedItems] = useState<string[]>([]) const [inputValue, setInputValue] = useState('') const handleSelectItem = (item: string) => { if (!selectedItems.includes(item)) { item && setSelectedItems([...selectedItems, item]) } else { setSelectedItems(selectedItems.filter((f) => f !== item)) } setInputValue('') } const filteredItems = inputValue ? [ ...items.filter((item) => item.toLowerCase().includes(inputValue.toLowerCase()), ), `Add: ${inputValue}`, ] : items const handleRemoveItem = (item: string) => { setSelectedItems( selectedItems.filter((selectedItem) => selectedItem !== item), ) } return ( <Autocomplete items={filteredItems} label={label} classNames={{ endContentWrapper: 'w-full', }} endContent={ <ChipGroup className='w-full justify-end' items={selectedItems.map((item) => ({ key: item, label: item, className: 'cursor-pointer', variant: 'dot', onClick: () => handleRemoveItem(item), }))} /> } onSelectionChange={(item) => { if (item && typeof item === 'string') { if (item.startsWith('Add: ')) { if (!items.includes(inputValue)) { setItems([...items, inputValue]) } handleSelectItem(inputValue) } else { handleSelectItem(item) } } }} selectedKey={''} inputValue={inputValue} onInputChange={setInputValue} /> ) }
Kindly share the ChipGroup code.
I have just implemented a new solution for us regarding multiselect with the option to search for the listed elements. An array is set in the props (it can also be set directly, but in our case it was not convenient.
Feel free to use and wish you a good coding 🤲🏼
import { useState } from 'react'
import { Autocomplete, AutocompleteItem, Chip } from "@nextui-org/react";
import { X, Check } from 'lucide-react';
const MultiselectSearch = ({array}) => {
const [selectedItems, setSelectedItems] = useState([])
const handleSelect = (item) => {
if (!selectedItems.includes(item)) {
setSelectedItems([...selectedItems, item])
} else {
setSelectedItems(selectedItems.filter(selection => selection !== item))
}
}
const handleDeleteSelection = (item) => {
setSelectedItems(selectedItems.filter(selection => selection !== item))
}
return (
<div>
<Autocomplete
className="w-96"
selectedKey={''}>
{array.map((item, index) => (
<AutocompleteItem
key={index}
value={item}
onClick={() => handleSelect(item)}
endContent={
setSelectedItems.includes(item) && (
<Check size={16} className="mr-2 text-green-500" />
)
}
>
{item}
</AutocompleteItem>
))}
</Autocomplete>
<div className="flex mt-2 w-96 flex-wrap">
{setSelectedItems.map((item) => (
<Chip
color={"primary"}
className="mr-2 mt-2"
endContent={<X
size={14}
className="mr-1 cursor-pointer"
onClick={() => handleDeleteSelection(item)}
/>}>
{item}
</Chip>
))}
</div>
</div>
)
}
export default MultiselectSearch
+1
I end up use React-Select. I need to use Autocomplete with form. Hope to have Multi select mode in Autocomplete soon...
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: