plane/web/components/ui/multi-level-dropdown.tsx
sriram veeraghanta 1e152c666c
New Directory Setup (#2065)
* chore: moved app & space from apps to root

* chore: modified workspace configuration

* chore: modified dockerfiles for space and web

* chore: modified icons for space

* feat: updated files for new svg icons supported by next-images

* chore: added /spaces base path for next

* chore: added compose config for space

* chore: updated husky configuration

* chore: updated workflows for new configuration

* chore: changed app name to web

* fix: resolved build errors with web

* chore: reset file tracing root for both projects

* chore: added nginx config for deploy

* fix: eslint and tsconfig settings for space app

* husky setup fixes based on new dir

* eslint fixes

* prettier formatting

---------

Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
2023-09-03 18:50:30 +05:30

176 lines
7.3 KiB
TypeScript

import { Fragment, useState } from "react";
// headless ui
import { Menu, Transition } from "@headlessui/react";
// ui
import { Loader } from "components/ui";
// icons
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid";
type MultiLevelDropdownProps = {
label: string;
options: {
id: string;
children?: {
id: string;
label: string | JSX.Element;
value: any;
selected?: boolean;
element?: JSX.Element;
}[];
hasChildren: boolean;
label: string;
onClick?: () => void;
selected?: boolean;
value: any;
}[];
onSelect: (value: any) => void;
direction?: "left" | "right";
height?: "sm" | "md" | "rg" | "lg";
};
export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
label,
options,
onSelect,
direction = "right",
height = "md",
}) => {
const [openChildFor, setOpenChildFor] = useState<string | null>(null);
return (
<>
<Menu as="div" className="relative z-10 inline-block text-left">
{({ open }) => (
<>
<div>
<Menu.Button
onClick={() => setOpenChildFor(null)}
className={`group flex items-center justify-between gap-2 rounded-md border border-custom-border-200 px-3 py-1.5 text-xs shadow-sm duration-300 focus:outline-none hover:text-custom-text-100 hover:bg-custom-background-90 ${
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
}`}
>
{label}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
static
className="absolute right-0 z-10 mt-1 w-36 origin-top-right select-none rounded-md bg-custom-background-90 border border-custom-border-300 text-xs shadow-lg focus:outline-none"
>
{options.map((option) => (
<div className="relative p-1" key={option.id}>
<Menu.Item
as="button"
onClick={(e: any) => {
if (option.hasChildren) {
e.stopPropagation();
e.preventDefault();
if (option.onClick) option.onClick();
if (openChildFor === option.id) setOpenChildFor(null);
else setOpenChildFor(option.id);
} else onSelect(option.value);
}}
className="w-full"
>
{({ active }) => (
<>
<div
className={`${
active || option.selected ? "bg-custom-background-80" : ""
} flex items-center gap-1 rounded px-1 py-1.5 text-custom-text-200 ${
direction === "right" ? "justify-between" : ""
}`}
>
{direction === "left" && option.hasChildren && (
<ChevronLeftIcon className="h-4 w-4" aria-hidden="true" />
)}
<span>{option.label}</span>
{direction === "right" && option.hasChildren && (
<ChevronRightIcon className="h-4 w-4" aria-hidden="true" />
)}
</div>
</>
)}
</Menu.Item>
{option.hasChildren && option.id === openChildFor && (
<div
className={`absolute top-0 min-w-36 whitespace-nowrap origin-top-right select-none overflow-y-scroll rounded-md bg-custom-background-90 border border-custom-border-300 shadow-lg focus:outline-none ${
direction === "left"
? "right-full -translate-x-1"
: "left-full translate-x-1"
} ${
height === "sm"
? "max-h-28"
: height === "md"
? "max-h-44"
: height === "rg"
? "max-h-56"
: height === "lg"
? "max-h-80"
: ""
}`}
>
{option.children ? (
<div className="space-y-1 p-1">
{option.children.length === 0 ? (
<p className="text-custom-text-200 text-center px-1 py-1.5">
No {option.label} found
</p> //if no children found, show this message.
) : (
option.children.map((child) => {
if (child.element) return child.element;
else
return (
<button
key={child.id}
type="button"
onClick={() => onSelect(child.value)}
className={`${
child.selected ? "bg-custom-background-80" : ""
} flex w-full items-center justify-between break-words rounded px-1 py-1.5 text-left text-custom-text-200 hover:bg-custom-background-80`}
>
{child.label}{" "}
<CheckIcon
className={`h-3.5 w-3.5 opacity-0 ${
child.selected ? "opacity-100" : ""
}`}
/>
</button>
);
})
)}
</div>
) : (
<Loader className="p-1 space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</div>
))}
</Menu.Items>
</Transition>
</>
)}
</Menu>
</>
);
};