boardgame-lib / components / ui / CommandPalette.tsx
CommandPalette.tsx
Raw
import { Combobox, Dialog, Transition } from "@headlessui/react";
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
import { Dispatch, Fragment, SetStateAction } from "react";

const people = [
  { id: 1, name: "Leslie Alexander", url: "#" },
  // More people...
];

function classNames(...classes: (string | boolean)[]) {
  return classes.filter(Boolean).join(" ");
}

export default function CommandPalette({
  open,
  setOpen,
  query,
  setQuery,
}: {
  open: boolean;
  setOpen: Dispatch<SetStateAction<boolean>>;
  query: string;
  setQuery: Dispatch<SetStateAction<string>>;
}) {
  const filteredPeople =
    query === ""
      ? []
      : people.filter((person) => {
          return person.name.toLowerCase().includes(query.toLowerCase());
        });

  return (
    <Transition.Root
      show={open}
      as={Fragment}
      afterLeave={() => setQuery("")}
      appear
    >
      <Dialog className="relative z-10" onClose={setOpen}>
        <Transition.Child
          as={Fragment}
          enter="ease-out duration-300"
          enterFrom="opacity-0"
          enterTo="opacity-100"
          leave="ease-in duration-200"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
        </Transition.Child>

        <div className="fixed inset-0 z-10 w-screen overflow-y-auto p-4 sm:p-6 md:p-20">
          <Transition.Child
            as={Fragment}
            enter="ease-out duration-300"
            enterFrom="opacity-0 scale-95"
            enterTo="opacity-100 scale-100"
            leave="ease-in duration-200"
            leaveFrom="opacity-100 scale-100"
            leaveTo="opacity-0 scale-95"
          >
            <Dialog.Panel className="mx-auto max-w-xl transform divide-y divide-gray-100 overflow-hidden rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
              <Combobox onChange={(option) => (window.location = option.url)}>
                <div className="relative">
                  <MagnifyingGlassIcon
                    className="pointer-events-none absolute left-4 top-3.5 h-5 w-5 text-gray-400"
                    aria-hidden="true"
                  />
                  <Combobox.Input
                    className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm"
                    placeholder="Search..."
                    onChange={(event) => setQuery(event.target.value)}
                    onBlur={() => setQuery("")}
                  />
                </div>

                <CommandPaletteOptionsContainer
                  query={query}
                  options={filteredPeople}
                />

                {query !== "" && filteredPeople.length === 0 && (
                  <p className="p-4 text-sm text-gray-500">No people found.</p>
                )}
              </Combobox>
            </Dialog.Panel>
          </Transition.Child>
        </div>
      </Dialog>
    </Transition.Root>
  );
}

const CommandPaletteOptionsContainer = ({
  query,
  options,
}: {
  query: string;
  options: any[];
}) => {
  if (options.length > 0)
    return (
      <Combobox.Options
        static
        className="max-h-72 scroll-py-2 overflow-y-auto py-2 text-sm text-gray-800"
      >
        {options.map((option, idx) => (
          <Combobox.Option
            key={idx}
            value={option}
            className={({ active }) =>
              classNames(
                "cursor-default select-none px-4 py-2",
                active && "bg-indigo-600 text-white"
              )
            }
          >
            {option.name}
          </Combobox.Option>
        ))}
      </Combobox.Options>
    );

  if (query !== "" && options.length === 0)
    return <p className="p-4 text-sm text-gray-500">No results found.</p>;
};