ui icon indicating copy to clipboard operation
ui copied to clipboard

Multi select ?

Open Yolo390 opened this issue 2 years ago • 105 comments

Hi there ✋🏼

thanks for this amazing components !

Is there a way to select multiple data with the Select component ?

Thanks !

Yolo390 avatar Feb 08 '23 09:02 Yolo390

Unfortunately no. The <Select /> element does not support multi-select. There's an issue on the Radix UI repo I'm following too: https://github.com/radix-ui/primitives/issues/1342

shadcn avatar Feb 08 '23 10:02 shadcn

👍 Would love to have a Multi Select

jackblackCH avatar Feb 10 '23 12:02 jackblackCH

@its-monotype Listbox from HeadlessUI (same creators as TailwindCSS) has support for multi-select

franciscohanna92 avatar Feb 13 '23 20:02 franciscohanna92

Hey @Flo-Slv, I also wanted a multi-select, one of my colleague suggested to use a dropdown-menu with checkboxes. It worked well for my use-case. Checked it out here.

99sdawkhar avatar Feb 19 '23 16:02 99sdawkhar

Hey @Flo-Slv, I also wanted a multi-select, one of my colleague suggested to use a dropdown-menu with checkboxes. It worked well for my use-case. Checked it out here.

Hey ! It can be a workaround until they implement a native solution ! Thanks !

Yolo390 avatar Feb 20 '23 19:02 Yolo390

https://github.com/colepeters/multiselect

What about this?

hiql avatar Mar 24 '23 00:03 hiql

I'd love see a multi-select with labels (instead of saying something like "4 items selected"), which is incredibly valuable for adding "tags" to things - a fairly common use case

Here's the one I'm currently using (not based on tailwind-css) https://react.semantic-ui.com/modules/dropdown/

Here's a version using tailwind: https://demo-react-tailwindcss-select.vercel.app/ Github repo here: https://github.com/onesine/react-tailwindcss-select ^The downside to this one is that it doesn't look like it handles aria support / keyboard navigation

evangow avatar Jun 02 '23 16:06 evangow

There is a headless multi-select combobox in Base UI (import X from @mui/base/useAutocomplete) which is supposed to be feature-rich:

Screenshot 2023-06-06 at 14 19 53

https://mui.com/material-ui/react-autocomplete/#customized-hook

and small:

Screenshot 2023-06-06 at 14 17 34

https://bundlephobia.com/package/@mui/[email protected]

maybe to consider as a base to style on top of, it could be a temporary solution for https://github.com/radix-ui/primitives/issues/1342.

oliviertassinari avatar Jun 06 '23 12:06 oliviertassinari

In case anyone else comes to this issue looking for a solution, @mxkaske just dropped a mutli-select component built with cmdk and shadcn components.

Demo here: https://craft.mxkaske.dev/post/fancy-multi-select

Source here: https://github.com/mxkaske/mxkaske.dev/blob/main/components/craft/fancy-multi-select.tsx

evangow avatar Jun 12 '23 15:06 evangow

In case anyone else comes to this issue looking for a solution, @mxkaske just dropped a mutli-select component built with cmdk and shadcn components.

Demo here: https://craft.mxkaske.dev/post/fancy-multi-select

Source here: https://github.com/mxkaske/mxkaske.dev/blob/main/components/craft/fancy-multi-select.tsx

This isn't accessible

zachrip avatar Jun 14 '23 20:06 zachrip

@zachrip you can drop an issue in the repo here: https://github.com/mxkaske/mxkaske.dev/issues

evangow avatar Jun 14 '23 20:06 evangow

I tweaked Headless UI Listbox component to achieve the desired UI.

Here is the example code:

import { Listbox, Transition } from '@headlessui/react';
import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons';
import React from 'react';

export default function MultiSelect() {
	const [selected, setSelected] = React.useState(['None']);
	const [options, setOptions] = React.useState<string[]>([]);

	React.useEffect(() => {
		setOptions(['None', 'Apple', 'Orange', 'Banana', 'Grapes']);
	}, []);

	return (
		<Listbox
			value={selected}
			onChange={setSelected}
			multiple>
			<div className='relative'>
				<Listbox.Button className='flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50'>
					<span className='block truncate'> {selected.map(option => option).join(', ')}</span>
					<CaretSortIcon className='h-4 w-4 opacity-50' />
				</Listbox.Button>
				<Transition
					as={React.Fragment}
					leave='transition ease-in duration-100'
					leaveFrom='opacity-100'
					leaveTo='opacity-0'>
					<Listbox.Options className='absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-popover py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm'>
						{options.map((option, optionIdx) => (
							<Listbox.Option
								key={optionIdx}
								className='relative cursor-default select-none py-1.5 pl-10 pr-4 text-sm rounded-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50'
								value={option}>
								{({ selected }) => (
									<>
										{option}
										{selected ? (
											<span className='absolute inset-y-0 right-2 flex items-center pl-3'>
												<CheckIcon className='h-4 w-4' />
											</span>
										) : null}
									</>
								)}
							</Listbox.Option>
						))}
					</Listbox.Options>
				</Transition>
			</div>
		</Listbox>
	);
}

Hope this works for you.

Cheers!

console-logs avatar Sep 12 '23 13:09 console-logs

Hi all,

I have created component, I hope somebody will find it helpful:


import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

dinogit avatar Sep 13 '23 21:09 dinogit

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

You can create a PR for this to be supported officially?

DarkAbhi avatar Sep 20 '23 15:09 DarkAbhi

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

thank you, this is great. also wondering did anyone successfully make a form collect inputs correctly?

zmzlois avatar Sep 21 '23 22:09 zmzlois

Unfortunately no. The <Select /> element does not support multi-select. There's an issue on the Radix UI repo I'm following too: radix-ui/primitives#1342

Is there any component that has multi-select except the dropdown? @shadcn

Syammed2429 avatar Oct 03 '23 08:10 Syammed2429

I created a multi input/select component but for tags that the user inputs rather than using a pre-defined list of options https://gist.github.com/enesien/03ba5340f628c6c812b306da5fedd1a4

enesien avatar Oct 13 '23 13:10 enesien

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

I am getting an error through the selected item, I am using react hook form

<FormField control={form.control} name="authors" render={({ field: { ...field } }) => ( <FormItem className="mb-5"> <FormLabel>Author</FormLabel> <MultiSelect selected={field.value} options={authorsData} {...field} /> </FormItem> )} /> it says that, selected is not iterable, I already check the onSelect method from CommandItem but I can't find any solution

johnLamberts avatar Oct 23 '23 12:10 johnLamberts

@johnLamberts PR is still in progress, so until this is done, copy code from here, fix some imports and let me know does it work.

dinogit avatar Oct 23 '23 12:10 dinogit

@johnLamberts PR is still in progress, so until this is done, copy code from here, fix some imports and let me know does it work.

I am still getting the same error, I have already check the code.

import * as React from "react";
import { Badge } from "@/shared/components/ui/badge";
import { Button } from "@/shared/components/ui/button";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
} from "@/shared/components/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/shared/components/ui/popover";
import { cn } from "@/shared/lib/utils";
import { Check, ChevronsUpDown, X } from "lucide-react";
import { useState } from "react";

// export type OptionType = {
//   id: string;
//   value: string;
// };

export type OptionType = Record<"id" | "value", string>;

interface MultiSelectProps {
  options: Record<"id" | "value", string>[];
  selected: Record<"id" | "value", string>[];
  onChange: React.Dispatch<
    React.SetStateAction<Record<"id" | "value", string>[]>
  >;
  className?: string;
}

const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>(
  ({ options, selected, onChange, className, ...props }, ref) => {
    const [open, setOpen] = useState(false);

    const handleUnselect = (item: Record<"id" | "value", string>) => {
      onChange(selected.filter((i) => i.id !== item.id));
    };

    return (
      <Popover open={open} onOpenChange={setOpen} {...props}>
        <PopoverTrigger asChild>
          <Button
            ref={ref}
            role="combobox"
            variant="outline"
            aria-expanded={open}
            className={`w-full justify-between  ${
              selected?.length > 1 ? "h-full" : "h-10"
            }`}
            onClick={() => setOpen(!open)}
          >
            <div className="flex gap-1 flex-wrap">
              {selected?.map((item) => (
                <Badge
                  variant="secondary"
                  key={item.id}
                  className="mr-1 mb-1"
                  onClick={() => handleUnselect(item)}
                >
                  {item.value}
                  <button
                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                    onKeyDown={(e) => {
                      if (e.key === "Enter") {
                        handleUnselect(item);
                      }
                    }}
                    onMouseDown={(e) => {
                      e.preventDefault();
                      e.stopPropagation();
                    }}
                    onClick={() => handleUnselect(item)}
                  >
                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                  </button>
                </Badge>
              ))}
            </div>
            <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
          </Button>
        </PopoverTrigger>
        <PopoverContent className="">
          <Command className={className}>
            <CommandInput placeholder="Search..." />
            <CommandEmpty>No item found.</CommandEmpty>
            <CommandGroup className="h-32 overflow-auto">
              {options.map((option) => (
                <CommandItem
                  key={option.id}
                  onSelect={() => {
                    console.log(option.value);
                    console.log(selected);
                    onChange(
                      selected?.some(
                        (item: Record<"id" | "value", string>) =>
                          item.id === option.id
                      )
                        ? selected.filter((item) => item.id !== option.id)
                        : [...selected, option]
                    );
                    setOpen(true);
                  }}
                >
                  <Check
                    className={cn(
                      "mr-2 h-4 w-4",
                      selected?.some((item) => item.id === option.id)
                        ? "opacity-100"
                        : "opacity-0"
                    )}
                  />
                  {option.value}
                </CommandItem>
              ))}
            </CommandGroup>
          </Command>
        </PopoverContent>
      </Popover>
    );
  }
);

MultiSelect.displayName = "MultiSelect";
export { MultiSelect };
``


`

btw, it the `selected` always returned me undefined, and I already check my forms well

johnLamberts avatar Oct 23 '23 13:10 johnLamberts

import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

import {
  Form,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { MultiSelect } from "@/components/ui/multi-select"
const AuthorsSchema = z.array(
    z.record(
        z.string().trim()
    )
)
const form = useForm<z.infer<typeof AuthorsSchema>>({
    resolver: zodResolver(AuthorsSchema),
    defaultValues: {
      authors: [],
    },
  });

const onHandleSubmit = (values: z.infer<typeof AuthorsSchema>) => {
    console.log({ values })
  };

const authorsData = [
    {
      value: "author1",
      label: "Author 1",
    }, {
      value: "author2",
      label: "Author 2",
    },
    {
      value: "author3",
      label: "Author 3",
    },
    {
      value: "author4",
      label: "Author 4",
    }
  ]

<Form {...form}>
              <form
                onSubmit={form.handleSubmit(onHandleSubmit)}
                className="space-y-4"
              >
                <FormField
                  control={form.control}
                  name="authors"
                  render={({ field: { ...field } }) => (
                    <FormItem className="mb-5">
                      <FormLabel>Author</FormLabel>
                      <MultiSelect
                        selected={field.value}
                        options={authorsData}
                        {...field} />
                    </FormItem>
                  )}
                />
                <Button type="submit" className="w-full">
                  Continue
                </Button>
              </form>
            </Form>

dinogit avatar Oct 23 '23 13:10 dinogit

@dinogit Try using min-w-[var(--radix-popover-trigger-width)] for the PopoverContent to keep its width same as that of trigger.

ThePiyushAggarwal avatar Nov 21 '23 16:11 ThePiyushAggarwal

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

is there a way to get only the values in form? instead of { label: "", value: "" }

kennethpole6 avatar Dec 04 '23 07:12 kennethpole6

Is there any advance in the PR? @dinogit

EnriqueCepeda avatar Dec 13 '23 13:12 EnriqueCepeda

Hi all, I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

is there a way to get only the values in form? instead of { label: "", value: "" }

got it a weird error, added "use client" on top but got an error on first render on the server and then on then client

Warning: validateDOMNesting(...): <button> cannot appear as a descendant of <button>.
    at button
    at div
    at Badge (webpack-internal:///(app-pages-browser)/./components/ui/badge.tsx:29:11)
    at div
    at button
    at _c (webpack-internal:///(app-pages-browser)/./components/ui/button.tsx:41:11)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@radix-ui/react-slot/dist/index.mjs:46:23)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@radix-ui/react-slot/dist/index.mjs:20:23)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@radix-ui/react-primitive/dist/index.mjs:44:26)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@radix-ui/react-slot/dist/index.mjs:46:23)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@radix-ui/react-slot/dist/index.mjs:20:23)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@radix-ui/react-primitive/dist/index.mjs:44:26)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@radix-ui/react-popper/dist/index.mjs:80:28)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@radix-ui/react-popover/dist/index.mjs:139:29)
    at Provider (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@radix-ui/react-context/dist/index.mjs:47:28)
    at Provider (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@radix-ui/react-context/dist/index.mjs:47:28)
    at $cf1ac5d9fe0e8206$export$badac9ada3a0bdf9 (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@radix-ui/react-popper/dist/index.mjs:65:28)
    at $cb5cc270b50c6fcd$export$5b6b19405a83ff9d (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@[email protected]_@[email protected]_@[email protected][email protected][email protected]/node_modules/@radix-ui/react-popover/dist/index.mjs:81:29)
    at MultiSelect (webpack-internal:///(app-pages-browser)/./components/ui/multi-select.tsx:26:11)
    at div
    at PlaygroundSideBar (webpack-internal:///(app-pages-browser)/./components/playground-sidebar.tsx:29:70)
    at div
    at KnowledgeBotProvider (webpack-internal:///(app-pages-browser)/./contexts/knowledge-bot-provider.tsx:16:11)
    at div
    at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:241:11)
    at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
    at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
    at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/not-found-boundary.js:62:11)
    at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:338:11)
    at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/error-boundary.js:110:11)
    at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:152:9)
    at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:227:11)
    at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/render-from-template-context.js:15:44)
    at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:348:11)
    at div
    at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:241:11)
    at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
    at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
    at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/not-found-boundary.js:54:9)
    at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/not-found-boundary.js:62:11)
    at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:338:11)
    at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/error-boundary.js:110:11)
    at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:152:9)
    at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:227:11)
    at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/render-from-template-context.js:15:44)
    at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/layout-router.js:348:11)
    at main
    at QueryClientProvider (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/react-query/es/react/QueryClientProvider.js:39:21)
    at Providers (webpack-internal:///(app-pages-browser)/./app/providers.tsx:14:11)
    at f (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/next-themes/dist/index.module.js:8:597)
    at $ (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/next-themes/dist/index.module.js:8:348)
    at ThemeProvider (webpack-internal:///(app-pages-browser)/./components/theme-provider.tsx:13:11)
    at body
    at html
    at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
    at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
    at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/not-found-boundary.js:54:9)
    at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/not-found-boundary.js:62:11)
    at DevRootNotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/dev-root-not-found-boundary.js:32:11)
    at ReactDevOverlay (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:66:9)
    at HotReload (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:294:11)
    at Router (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/app-router.js:157:11)
    at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/error-boundary.js:82:9)
    at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/error-boundary.js:110:11)
    at AppRouter (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/components/app-router.js:440:13)
    at ServerRoot (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/app-index.js:126:11)
    at RSCComponent
    at Root (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/client/app-index.js:142:11)

Great work btw!

Yeah looks like there's a <button> nested under the Badge component that acts as the onClick handler to remove an item from the select. I changed it to an a (not sure on accessibility there), but now have this error:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

itschrisabroad avatar Dec 19 '23 15:12 itschrisabroad

Those who wants to get the data from database and follow this structure

[ { "id": 1, "name": "Small", }, { "id": 2, "name": "Medium", }, { "id": 3, "name": "Large", } ]

Also added fix for validateDOMNesting(...): <button> cannot appear as a descendant of <button> and Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef() this errors.

Codes Here:

shourov00 avatar Dec 20 '23 07:12 shourov00

I've implemented all the features that React-Select offers, built entirely on Command, which is based on cmdk — no additional packages needed.

You can visit here to see demo and usage.

Features

  • Multiple selection, of course.
  • Async search with debounce.
  • Creatable selector — create option when there is no option matched.
  • Grouping functionality.
  • Working with react-hook-form.
  • Customize your own loading spinner and empty indicator by giving props.
  • Customize style with tailwind just like shadcn-ui.
  • Fixed options, maximum selected count, max input text length.
  • Ability to disable the default selection of the first item. (see more about the cmdk issue)
  • Expose ref to get your selections and input to match your needs. For example, input.focus()

The most important: simply copy and paste — the code is yours.

I hope the only reason you'll need to dive into the source code is for Tailwind customization.

hsuanyi-chou avatar Dec 29 '23 00:12 hsuanyi-chou

I've implemented all the features that React-Select offers, built entirely on Command, which is based on cmdk — no additional packages needed.

You can visit here to see demo and usage.

Features

  • Multiple selection, of course.
  • Async search with debounce.
  • Creatable selector.
  • Grouping functionality.
  • Working with react-hook-form.
  • Customize your own loading spinner and empty indicator by giving props.
  • Customize style with tailwind just like shadcn-ui.
  • Fixed options, maximum selected count, max input text length.
  • Ability to disable the default selection of the first item. (see more about the cmdk issue)

The most important: simply copy and paste — the code is yours.

I hope the only reason you'll need to dive into the source code is for Tailwind customization.

Good work!

sonicgamer0721 avatar Dec 29 '23 01:12 sonicgamer0721

I've implemented all the features that React-Select offers, built entirely on Command, which is based on cmdk — no additional packages needed.

You can visit here to see demo and usage.

Features

* Multiple selection, of course.

* **Async search with debounce**.

* **Creatable selector — create option when there is no option matched**.

* **Grouping functionality**.

* Working with `react-hook-form`.

* Customize your own loading spinner and empty indicator by giving props.

* Customize style with tailwind just like shadcn-ui.

* Fixed options, maximum selected count, max input text length.

* Ability to disable the default selection of the first item. (see more about the `cmdk` [issue](https://github.com/pacocoursey/cmdk/issues/171))

* Expose `ref` to get your `selections` and `input` to match your needs. For example, `input.focus()`

The most important: simply copy and paste — the code is yours.

I hope the only reason you'll need to dive into the source code is for Tailwind customization.

Nice! it took me a while to implement it and yours definitely works better than mine I will just go ahead and deprecate the my version and move to yours! Thank you!

FerMPY avatar Jan 02 '24 20:01 FerMPY