Untitled

 avatar
unknown
plain_text
a month ago
3.7 kB
6
Indexable
import { Button } from "@/components/ui/button";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/utilities/cn";
import { useState } from "react";
import { Check, ChevronsUpDown, X } from "lucide-react";
import { UseFieldArrayAppend, UseFieldArrayRemove } from "react-hook-form";

type Props = {
  data: { value: string; label: string; id: string }[];
  name: string;
  notFoundMessage?: string;
  inDialog?: boolean;
  onAppend: UseFieldArrayAppend<any>;
  onRemove: UseFieldArrayRemove;
};

export function MultiSelectCombobox({
  data,
  name,
  notFoundMessage,
  inDialog,
  onAppend,
  onRemove,
}: Props) {
  const [open, setOpen] = useState(false);
  const [selectedItemsIds, setSelectedItemsIds] = useState<string[]>([]);

  const handleSetValue = (id: string) => {
    const existingIndex = selectedItemsIds.findIndex((item) => item === id);

    if (existingIndex > -1) {
      onRemove(existingIndex);
      setSelectedItemsIds((prev) => prev.filter((item) => item !== id));
    } else {
      const item = data.find((item) => item.id === id)!;
      onAppend({
        title: item.value,
        id: item.id,
      });
      setSelectedItemsIds((prev) => [...prev, id]);
    }
  };

  return (
    <Popover modal={true} open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          aria-expanded={open}
          className="h-fit w-full justify-between"
        >
          <div className="flex flex-wrap justify-start gap-2">
            {selectedItemsIds?.length
              ? selectedItemsIds.map((id, i) => (
                  <div
                    key={i}
                    className=" flex h-auto items-center gap-x-2 rounded-md bg-foreground px-2 py-1 text-xs font-medium text-background"
                  >
                    {data.find((item) => item.id === id)?.label}
                    <X
                      size={13}
                      className="hover:text-destructive"
                      onClick={() => handleSetValue(id)}
                    />
                  </div>
                ))
              : `Select one or more ${name}...`}
          </div>
          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className={`combobox-width p-0 ${inDialog && "z-[200]"}`}>
        <Command>
          <CommandInput placeholder={`Search ${name}...`} className="h-9" />
          <CommandEmpty>
            {notFoundMessage ? notFoundMessage : `No ${name} found.`}
          </CommandEmpty>
          <CommandGroup>
            <CommandList>
              {data.map((item) => (
                <CommandItem
                  isMultiSelect={true}
                  key={item.id}
                  value={item.id}
                  onSelect={() => {
                    handleSetValue(item.id);
                  }}
                >
                  <Check
                    className={cn(
                      "mr-2 h-4 w-4",
                      selectedItemsIds.includes(item.id)
                        ? "opacity-100"
                        : "opacity-0",
                    )}
                  />
                  {item.label}
                </CommandItem>
              ))}
            </CommandList>
          </CommandGroup>
        </Command>
      </PopoverContent>
    </Popover>
  );
}
Editor is loading...
Leave a Comment