plane/apps/app/components/ui/custom-search-select.tsx
Aaryan Khandelwal 9ccc35d181
fix: make custom search select handle undefined options (#1094)
* fix: make search select handle undefined options

* fix: kanban y-scroll
2023-05-20 16:00:41 +05:30

184 lines
6.4 KiB
TypeScript

import React, { useState } from "react";
// headless ui
import { Combobox, Transition } from "@headlessui/react";
// icons
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
type CustomSearchSelectProps = {
value: any;
onChange: any;
options:
| {
value: any;
query: string;
content: JSX.Element;
}[]
| undefined;
label?: string | JSX.Element;
textAlignment?: "left" | "center" | "right";
height?: "sm" | "md" | "rg" | "lg";
position?: "right" | "left";
verticalPosition?: "top" | "bottom";
noChevron?: boolean;
customButton?: JSX.Element;
optionsClassName?: string;
input?: boolean;
disabled?: boolean;
selfPositioned?: boolean;
multiple?: boolean;
footerOption?: JSX.Element;
};
export const CustomSearchSelect = ({
label,
textAlignment,
height = "md",
value,
onChange,
options,
position = "left",
verticalPosition = "bottom",
noChevron = false,
customButton,
optionsClassName = "",
input = false,
disabled = false,
selfPositioned = false,
multiple = false,
footerOption,
}: CustomSearchSelectProps) => {
const [query, setQuery] = useState("");
const filteredOptions =
query === ""
? options
: options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
const props: any = {
value,
onChange,
disabled,
};
if (multiple) props.multiple = true;
return (
<Combobox
as="div"
className={`${!selfPositioned ? "relative" : ""} flex-shrink-0 text-left`}
{...props}
>
{({ open }: any) => (
<>
{customButton ? (
<Combobox.Button as="div">{customButton}</Combobox.Button>
) : (
<Combobox.Button
className={`flex w-full border border-brand-base ${
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-brand-surface-2"
} ${
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
} items-center justify-between gap-1 rounded-md shadow-sm duration-300 focus:outline-none focus:ring-1 focus:ring-brand-base ${
textAlignment === "right"
? "text-right"
: textAlignment === "center"
? "text-center"
: "text-left"
}`}
>
{label}
{!noChevron && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</Combobox.Button>
)}
<Transition
show={open}
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Combobox.Options
className={`${optionsClassName} absolute min-w-[10rem] border border-brand-base p-2 ${
position === "right" ? "right-0" : "left-0"
} ${
verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"
} z-10 origin-top-right rounded-md bg-brand-surface-1 text-xs shadow-lg focus:outline-none`}
>
<div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-brand-base bg-brand-surface-1 px-2">
<MagnifyingGlassIcon className="h-3 w-3 text-brand-secondary" />
<Combobox.Input
className="w-full bg-transparent py-1 px-2 text-xs text-brand-secondary focus:outline-none"
onChange={(e) => setQuery(e.target.value)}
placeholder="Type to search..."
displayValue={(assigned: any) => assigned?.name}
/>
</div>
<div
className={`mt-2 space-y-1 ${
height === "sm"
? "max-h-28"
: height === "md"
? "max-h-44"
: height === "rg"
? "max-h-56"
: height === "lg"
? "max-h-80"
: ""
} overflow-y-scroll`}
>
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions.map((option) => (
<Combobox.Option
key={option.value}
value={option.value}
className={({ active, selected }) =>
`${active || selected ? "bg-brand-surface-2" : ""} ${
selected ? "font-medium" : ""
} flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-brand-secondary`
}
>
{({ active, selected }) => (
<>
{option.content}
{multiple ? (
<div
className={`flex items-center justify-center rounded border border-gray-500 p-0.5 ${
active || selected ? "opacity-100" : "opacity-0"
}`}
>
<CheckIcon
className={`h-3 w-3 ${selected ? "opacity-100" : "opacity-0"}`}
/>
</div>
) : (
<CheckIcon
className={`h-3 w-3 ${selected ? "opacity-100" : "opacity-0"}`}
/>
)}
</>
)}
</Combobox.Option>
))
) : (
<p className="text-center text-brand-secondary">No matching results</p>
)
) : (
<p className="text-center text-brand-secondary">Loading...</p>
)}
</div>
{footerOption}
</Combobox.Options>
</Transition>
</>
)}
</Combobox>
);
};