mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: integrate popper js (#2398)
* chore: react-popper-js added * chore: integrate popper js in issue properties dropdown * chore: integrate popper js in custom menu component * chore: integrate popper js in custom select component * chore: integrate popper js in custom search select component * chore: popper js placement type added * chore: popper js placement type added
This commit is contained in:
parent
d88eb09fad
commit
58ea4d6ec9
@ -74,7 +74,6 @@ export const AutoArchiveAutomation: React.FC<Props> = ({
|
||||
handleChange({ archive_in: val });
|
||||
}}
|
||||
input
|
||||
verticalPosition="bottom"
|
||||
width="w-full"
|
||||
disabled={disabled}
|
||||
>
|
||||
|
@ -100,7 +100,6 @@ export const ThemeSwitch: React.FC<Props> = observer(
|
||||
}}
|
||||
input
|
||||
width="w-full"
|
||||
position="right"
|
||||
>
|
||||
{THEMES_OBJ.map(({ value, label, type, icon }) => (
|
||||
<CustomSelect.Option key={value} value={{ value, type }}>
|
||||
|
@ -255,15 +255,11 @@ export const SingleBoard: React.FC<Props> = (props) => {
|
||||
!isDraftIssuesPage && (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap">
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
position="left"
|
||||
noBorder
|
||||
>
|
||||
<CustomMenu.MenuItem
|
||||
|
@ -369,12 +369,12 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
{type && !isNotAllowed && (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
<div
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded p-1 text-left text-xs duration-300 hover:bg-custom-background-80"
|
||||
onClick={() => setIsMenuActive(!isMenuActive)}
|
||||
>
|
||||
<EllipsisHorizontalIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<CustomMenu.MenuItem
|
||||
|
@ -272,7 +272,6 @@ export const SingleList: React.FC<Props> = (props) => {
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
</div>
|
||||
}
|
||||
position="right"
|
||||
noBorder
|
||||
>
|
||||
<CustomMenu.MenuItem onClick={() => setIsCreateIssueFormOpen(true)}>
|
||||
|
@ -255,7 +255,6 @@ export const SpreadsheetView: React.FC<Props> = ({
|
||||
<CustomMenu
|
||||
customButtonClassName="!w-full"
|
||||
className="!w-full"
|
||||
position="left"
|
||||
customButton={
|
||||
<div
|
||||
className={`relative group flex items-center justify-between gap-1.5 cursor-pointer text-sm text-custom-text-200 hover:text-custom-text-100 w-full py-3 px-2 ${
|
||||
@ -641,16 +640,11 @@ export const SpreadsheetView: React.FC<Props> = ({
|
||||
<CustomMenu
|
||||
className="sticky left-0 z-10"
|
||||
customButton={
|
||||
<button
|
||||
className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
|
||||
type="button"
|
||||
>
|
||||
<div className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full">
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
New Issue
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
position="left"
|
||||
verticalPosition="top"
|
||||
optionsClassName="left-5 !w-36"
|
||||
noBorder
|
||||
>
|
||||
|
@ -92,7 +92,6 @@ export const SelectRepository: React.FC<Props> = ({
|
||||
)}
|
||||
</>
|
||||
}
|
||||
position="right"
|
||||
optionsClassName="w-full"
|
||||
/>
|
||||
);
|
||||
|
@ -143,7 +143,6 @@ export const JiraGetImportDetail: React.FC = () => {
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
verticalPosition="top"
|
||||
>
|
||||
{projects && projects.length > 0 ? (
|
||||
projects.map((project) => (
|
||||
|
@ -158,7 +158,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<CustomMenu position="left" ellipsis optionsClassName="px-1.5">
|
||||
<CustomMenu ellipsis optionsClassName="px-1.5">
|
||||
{siblingIssuesList ? (
|
||||
siblingIssuesList.length > 0 ? (
|
||||
<>
|
||||
|
@ -93,7 +93,6 @@ export const PeekOverviewHeader: React.FC<Props> = ({
|
||||
<Icon iconName={peekModes.find((m) => m.key === mode)?.icon ?? ""} />
|
||||
</button>
|
||||
}
|
||||
position="left"
|
||||
>
|
||||
{peekModes.map((mode) => (
|
||||
<CustomSelect.Option key={mode.key} value={mode.key}>
|
||||
|
@ -33,7 +33,6 @@ export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
|
||||
</div>
|
||||
}
|
||||
onChange={onChange}
|
||||
position="right"
|
||||
width="w-full min-w-[8rem]"
|
||||
noChevron
|
||||
>
|
||||
|
@ -91,7 +91,6 @@ export const SidebarCycleSelect: React.FC<Props> = ({
|
||||
: handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle);
|
||||
}}
|
||||
width="w-full"
|
||||
position="right"
|
||||
maxHeight="rg"
|
||||
disabled={disabled}
|
||||
>
|
||||
|
@ -20,17 +20,14 @@ export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabl
|
||||
<CustomSelect
|
||||
value={value}
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1.5 !text-sm bg-custom-background-80 rounded px-2.5 py-0.5"
|
||||
>
|
||||
<div className="flex items-center gap-1.5 !text-sm bg-custom-background-80 rounded px-2.5 py-0.5">
|
||||
<PlayIcon
|
||||
className={`h-4 w-4 -rotate-90 ${
|
||||
value !== null ? "text-custom-text-100" : "text-custom-text-200"
|
||||
}`}
|
||||
/>
|
||||
{estimatePoints?.find((e) => e.key === value)?.value ?? "No estimate"}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
|
@ -87,7 +87,6 @@ export const SidebarModuleSelect: React.FC<Props> = ({
|
||||
: handleModuleChange(modules?.find((m) => m.id === value) as IModule);
|
||||
}}
|
||||
width="w-full"
|
||||
position="right"
|
||||
maxHeight="rg"
|
||||
disabled={disabled}
|
||||
>
|
||||
|
@ -18,8 +18,7 @@ type Props = {
|
||||
export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabled = false }) => (
|
||||
<CustomSelect
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
className={`flex items-center gap-1.5 text-left text-xs capitalize rounded px-2.5 py-0.5 ${
|
||||
value === "urgent"
|
||||
? "border-red-500/20 bg-red-500/20 text-red-500"
|
||||
@ -36,7 +35,7 @@ export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabl
|
||||
<PriorityIcon priority={value} className="!text-sm" />
|
||||
</span>
|
||||
<span>{value ?? "None"}</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
|
@ -39,7 +39,7 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
|
||||
return (
|
||||
<CustomSelect
|
||||
customButton={
|
||||
<button type="button" className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
|
||||
<div className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
|
||||
{selectedState ? (
|
||||
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
||||
<StateGroupIcon stateGroup={selectedState.group} color={selectedState.color} />
|
||||
@ -53,12 +53,11 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
|
||||
) : (
|
||||
"None"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
optionsClassName="w-min"
|
||||
position="left"
|
||||
disabled={disabled}
|
||||
>
|
||||
{states ? (
|
||||
|
@ -247,7 +247,6 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
|
||||
</>
|
||||
}
|
||||
buttonClassName="whitespace-nowrap"
|
||||
position="left"
|
||||
noBorder
|
||||
noChevron
|
||||
>
|
||||
@ -283,7 +282,6 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
|
||||
</>
|
||||
}
|
||||
buttonClassName="whitespace-nowrap"
|
||||
position="left"
|
||||
noBorder
|
||||
noChevron
|
||||
>
|
||||
|
@ -113,10 +113,8 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
|
||||
{...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })}
|
||||
multiple
|
||||
noChevron
|
||||
position={position}
|
||||
disabled={isNotAllowed}
|
||||
onOpen={() => setFetchAssignees(true)}
|
||||
selfPositioned={selfPositioned}
|
||||
width="w-full min-w-[12rem]"
|
||||
/>
|
||||
);
|
||||
|
@ -74,8 +74,6 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
||||
maxHeight="md"
|
||||
noChevron
|
||||
disabled={isNotAllowed}
|
||||
position={position}
|
||||
selfPositioned={selfPositioned}
|
||||
width="w-full min-w-[8rem]"
|
||||
>
|
||||
<CustomSelect.Option value={null}>
|
||||
|
@ -146,9 +146,7 @@ export const ViewLabelSelect: React.FC<Props> = ({
|
||||
{...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })}
|
||||
multiple
|
||||
noChevron
|
||||
position={position}
|
||||
disabled={isNotAllowed}
|
||||
selfPositioned={selfPositioned}
|
||||
footerOption={footerOption}
|
||||
width="w-full min-w-[12rem]"
|
||||
/>
|
||||
|
@ -59,8 +59,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
||||
}}
|
||||
maxHeight="md"
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
className={`grid place-items-center rounded ${
|
||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
||||
} ${noBorder ? "" : "h-6 w-6 border shadow-sm"} ${
|
||||
@ -94,12 +93,10 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
||||
{noBorder ? capitalizeFirstLetter(issue.priority ?? "None") : ""}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
noChevron
|
||||
disabled={isNotAllowed}
|
||||
position={position}
|
||||
selfPositioned={selfPositioned}
|
||||
>
|
||||
{PRIORITIES?.map((priority) => (
|
||||
<CustomSelect.Option key={priority} value={priority} className="capitalize">
|
||||
|
@ -65,7 +65,6 @@ export const SidebarLeadSelect: React.FC<Props> = ({ value, onChange }) => {
|
||||
}
|
||||
options={options}
|
||||
maxHeight="md"
|
||||
position="right"
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
@ -64,7 +64,6 @@ export const SidebarMembersSelect: React.FC<Props> = ({ value, onChange }) => {
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
maxHeight="md"
|
||||
position="right"
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
|
@ -214,12 +214,9 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded"
|
||||
>
|
||||
<div className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded">
|
||||
<Icon iconName="schedule" className="h-5 w-5 text-custom-text-300" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
optionsClassName="!z-20"
|
||||
>
|
||||
|
@ -167,7 +167,6 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
|
||||
}
|
||||
input
|
||||
width="w-full"
|
||||
verticalPosition="top"
|
||||
>
|
||||
{USER_ROLES.map((item) => (
|
||||
<CustomSelect.Option key={item.value} value={item.value}>
|
||||
@ -197,7 +196,6 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
|
||||
}
|
||||
options={timeZoneOptions}
|
||||
onChange={onChange}
|
||||
verticalPosition="top"
|
||||
optionsClassName="w-full"
|
||||
input
|
||||
/>
|
||||
|
@ -384,12 +384,12 @@ export const SinglePageBlock: React.FC<Props> = ({
|
||||
</button>
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
<div
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded px-2.5 py-1 text-left text-xs duration-300 hover:bg-custom-background-90"
|
||||
onClick={() => setIsMenuActive(!isMenuActive)}
|
||||
>
|
||||
<BoltIcon className="h-4.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{block.issue ? (
|
||||
|
@ -418,7 +418,6 @@ export const CreateProjectModal: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
verticalPosition="top"
|
||||
noChevron
|
||||
/>
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// react-popper
|
||||
import { usePopper } from "react-popper";
|
||||
// services
|
||||
import issuesService from "services/issues.service";
|
||||
// hooks
|
||||
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
||||
// headless ui
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// component
|
||||
@ -19,6 +19,7 @@ import { PlusIcon } from "lucide-react";
|
||||
// types
|
||||
import { Tooltip } from "components/ui";
|
||||
import { ICurrentUserResponse, IIssueLabels } from "types";
|
||||
import { Placement } from "@popperjs/core";
|
||||
// constants
|
||||
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
||||
|
||||
@ -31,6 +32,7 @@ type Props = {
|
||||
buttonClassName?: string;
|
||||
optionsClassName?: string;
|
||||
maxRender?: number;
|
||||
placement?: Placement;
|
||||
hideDropdownArrow?: boolean;
|
||||
disabled?: boolean;
|
||||
user: ICurrentUserResponse | undefined;
|
||||
@ -45,21 +47,25 @@ export const LabelSelect: React.FC<Props> = ({
|
||||
buttonClassName = "",
|
||||
optionsClassName = "",
|
||||
maxRender = 2,
|
||||
placement,
|
||||
hideDropdownArrow = false,
|
||||
disabled = false,
|
||||
user,
|
||||
}) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [fetchStates, setFetchStates] = useState(false);
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const [labelModal, setLabelModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const dropdownBtn = useRef<any>(null);
|
||||
const dropdownOptions = useRef<any>(null);
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
||||
projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId) : null,
|
||||
@ -131,8 +137,6 @@ export const LabelSelect: React.FC<Props> = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
|
||||
|
||||
const footerOption = (
|
||||
<button
|
||||
type="button"
|
||||
@ -165,15 +169,13 @@ export const LabelSelect: React.FC<Props> = ({
|
||||
multiple
|
||||
>
|
||||
{({ open }: { open: boolean }) => {
|
||||
if (open) {
|
||||
if (!isOpen) setIsOpen(true);
|
||||
setFetchStates(true);
|
||||
} else if (isOpen) setIsOpen(false);
|
||||
if (open) setFetchStates(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Combobox.Button
|
||||
ref={dropdownBtn}
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||
disabled
|
||||
@ -187,11 +189,15 @@ export const LabelSelect: React.FC<Props> = ({
|
||||
{!hideDropdownArrow && !disabled && (
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
|
||||
<Combobox.Options
|
||||
ref={dropdownOptions}
|
||||
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
|
||||
|
||||
<Combobox.Options>
|
||||
<div
|
||||
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
|
||||
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
|
||||
@ -234,8 +240,8 @@ export const LabelSelect: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
{footerOption}
|
||||
</Combobox.Options>
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
@ -77,7 +77,6 @@ export const MemberSelect: React.FC<Props> = ({ value, onChange, isDisabled = fa
|
||||
]
|
||||
}
|
||||
maxHeight="md"
|
||||
position="right"
|
||||
width="w-full"
|
||||
onChange={onChange}
|
||||
disabled={isDisabled}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// react-popper
|
||||
import { usePopper } from "react-popper";
|
||||
// hooks
|
||||
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
||||
import useProjectMembers from "hooks/use-project-members";
|
||||
import useWorkspaceMembers from "hooks/use-workspace-members";
|
||||
// headless ui
|
||||
@ -15,6 +16,7 @@ import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
import { IUser } from "types";
|
||||
import { Placement } from "@popperjs/core";
|
||||
|
||||
type Props = {
|
||||
value: string | string[];
|
||||
@ -25,6 +27,7 @@ type Props = {
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
optionsClassName?: string;
|
||||
placement?: Placement;
|
||||
hideDropdownArrow?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
@ -38,18 +41,22 @@ export const MembersSelect: React.FC<Props> = ({
|
||||
className = "",
|
||||
buttonClassName = "",
|
||||
optionsClassName = "",
|
||||
placement,
|
||||
hideDropdownArrow = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [fetchStates, setFetchStates] = useState(false);
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const dropdownBtn = useRef<any>(null);
|
||||
const dropdownOptions = useRef<any>(null);
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
const { members } = useProjectMembers(
|
||||
workspaceSlug?.toString(),
|
||||
@ -105,8 +112,6 @@ export const MembersSelect: React.FC<Props> = ({
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as="div"
|
||||
@ -117,15 +122,13 @@ export const MembersSelect: React.FC<Props> = ({
|
||||
multiple
|
||||
>
|
||||
{({ open }: { open: boolean }) => {
|
||||
if (open) {
|
||||
if (!isOpen) setIsOpen(true);
|
||||
setFetchStates(true);
|
||||
} else if (isOpen) setIsOpen(false);
|
||||
if (open) setFetchStates(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Combobox.Button
|
||||
ref={dropdownBtn}
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||
disabled
|
||||
@ -137,11 +140,14 @@ export const MembersSelect: React.FC<Props> = ({
|
||||
{!hideDropdownArrow && !disabled && (
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
|
||||
<Combobox.Options
|
||||
ref={dropdownOptions}
|
||||
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
|
||||
<Combobox.Options>
|
||||
<div
|
||||
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
|
||||
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
|
||||
@ -183,8 +189,8 @@ export const MembersSelect: React.FC<Props> = ({
|
||||
<p className="text-center text-custom-text-200">Loading...</p>
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
// hooks
|
||||
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
||||
// react-popper
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// icons
|
||||
@ -12,6 +12,7 @@ import { PriorityIcon } from "components/icons";
|
||||
import { Tooltip } from "components/ui";
|
||||
// types
|
||||
import { TIssuePriorities } from "types";
|
||||
import { Placement } from "@popperjs/core";
|
||||
// constants
|
||||
import { PRIORITIES } from "constants/project";
|
||||
|
||||
@ -21,6 +22,7 @@ type Props = {
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
optionsClassName?: string;
|
||||
placement?: Placement;
|
||||
hideDropdownArrow?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
@ -31,14 +33,18 @@ export const PrioritySelect: React.FC<Props> = ({
|
||||
className = "",
|
||||
buttonClassName = "",
|
||||
optionsClassName = "",
|
||||
placement,
|
||||
hideDropdownArrow = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const dropdownBtn = useRef<any>(null);
|
||||
const dropdownOptions = useRef<any>(null);
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
const options = PRIORITIES?.map((priority) => ({
|
||||
value: priority,
|
||||
@ -87,8 +93,6 @@ export const PrioritySelect: React.FC<Props> = ({
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as="div"
|
||||
@ -97,15 +101,9 @@ export const PrioritySelect: React.FC<Props> = ({
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
>
|
||||
{({ open }: { open: boolean }) => {
|
||||
if (open) {
|
||||
if (!isOpen) setIsOpen(true);
|
||||
} else if (isOpen) setIsOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Combobox.Button
|
||||
ref={dropdownBtn}
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||
disabled
|
||||
@ -117,11 +115,14 @@ export const PrioritySelect: React.FC<Props> = ({
|
||||
{!hideDropdownArrow && !disabled && (
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
|
||||
<Combobox.Options
|
||||
ref={dropdownOptions}
|
||||
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
|
||||
<Combobox.Options>
|
||||
<div
|
||||
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
|
||||
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
|
||||
@ -163,11 +164,8 @@ export const PrioritySelect: React.FC<Props> = ({
|
||||
<p className="text-center text-custom-text-200">Loading...</p>
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
};
|
||||
|
@ -230,7 +230,6 @@ const SendProjectInvitationModal: React.FC<Props> = (props) => {
|
||||
onChange(val);
|
||||
}}
|
||||
options={options}
|
||||
position="left"
|
||||
width="w-full min-w-[12rem]"
|
||||
/>
|
||||
)}
|
||||
@ -252,12 +251,12 @@ const SendProjectInvitationModal: React.FC<Props> = (props) => {
|
||||
<CustomSelect
|
||||
{...field}
|
||||
customButton={
|
||||
<button className="flex w-full items-center justify-between gap-1 rounded-md border border-custom-border-200 shadow-sm duration-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 focus:outline-none px-3 py-2.5 text-sm text-left">
|
||||
<div className="flex w-full items-center justify-between gap-1 rounded-md border border-custom-border-200 shadow-sm duration-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 focus:outline-none px-3 py-2.5 text-sm text-left">
|
||||
<span className="capitalize">
|
||||
{field.value ? ROLE[field.value] : "Select role"}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
input
|
||||
width="w-full"
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// hooks
|
||||
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
||||
// react-popper
|
||||
import { usePopper } from "react-popper";
|
||||
// services
|
||||
import stateService from "services/state.service";
|
||||
// headless ui
|
||||
@ -16,6 +16,7 @@ import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { StateGroupIcon } from "components/icons";
|
||||
// types
|
||||
import { Tooltip } from "components/ui";
|
||||
import { Placement } from "@popperjs/core";
|
||||
// constants
|
||||
import { IState } from "types";
|
||||
import { STATES_LIST } from "constants/fetch-keys";
|
||||
@ -29,6 +30,7 @@ type Props = {
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
optionsClassName?: string;
|
||||
placement?: Placement;
|
||||
hideDropdownArrow?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
@ -40,17 +42,21 @@ export const StateSelect: React.FC<Props> = ({
|
||||
className = "",
|
||||
buttonClassName = "",
|
||||
optionsClassName = "",
|
||||
placement,
|
||||
hideDropdownArrow = false,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const dropdownBtn = useRef<any>(null);
|
||||
const dropdownOptions = useRef<any>(null);
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const [fetchStates, setFetchStates] = useState<boolean>(false);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
@ -90,8 +96,6 @@ export const StateSelect: React.FC<Props> = ({
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as="div"
|
||||
@ -103,15 +107,13 @@ export const StateSelect: React.FC<Props> = ({
|
||||
disabled={disabled}
|
||||
>
|
||||
{({ open }: { open: boolean }) => {
|
||||
if (open) {
|
||||
if (!isOpen) setIsOpen(true);
|
||||
setFetchStates(true);
|
||||
} else if (isOpen) setIsOpen(false);
|
||||
if (open) setFetchStates(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Combobox.Button
|
||||
ref={dropdownBtn}
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs px-2.5 py-1 rounded-md shadow-sm border border-custom-border-300 duration-300 focus:outline-none ${
|
||||
disabled
|
||||
@ -123,11 +125,14 @@ export const StateSelect: React.FC<Props> = ({
|
||||
{!hideDropdownArrow && !disabled && (
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
|
||||
<Combobox.Options
|
||||
ref={dropdownOptions}
|
||||
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
|
||||
<Combobox.Options>
|
||||
<div
|
||||
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
|
||||
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
|
||||
@ -169,8 +174,8 @@ export const StateSelect: React.FC<Props> = ({
|
||||
<p className="text-center text-custom-text-200">Loading...</p>
|
||||
)}
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
// react-poppper
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { Menu } from "@headlessui/react";
|
||||
// ui
|
||||
import { DropdownProps } from "components/ui";
|
||||
// icons
|
||||
@ -20,6 +22,7 @@ export type CustomMenuProps = DropdownProps & {
|
||||
const CustomMenu = ({
|
||||
buttonClassName = "",
|
||||
customButtonClassName = "",
|
||||
placement,
|
||||
children,
|
||||
className = "",
|
||||
customButton,
|
||||
@ -30,44 +33,56 @@ const CustomMenu = ({
|
||||
noBorder = false,
|
||||
noChevron = false,
|
||||
optionsClassName = "",
|
||||
position = "right",
|
||||
selfPositioned = false,
|
||||
verticalEllipsis = false,
|
||||
verticalPosition = "bottom",
|
||||
width = "auto",
|
||||
menuButtonOnClick,
|
||||
}: CustomMenuProps) => (
|
||||
<Menu as="div" className={`${selfPositioned ? "" : "relative"} w-min text-left ${className}`}>
|
||||
}: CustomMenuProps) => {
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
return (
|
||||
<Menu as="div" className={`relative w-min text-left ${className}`}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
{customButton ? (
|
||||
<Menu.Button
|
||||
as="button"
|
||||
<Menu.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
onClick={menuButtonOnClick}
|
||||
className={customButtonClassName}
|
||||
disabled={disabled}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
</Menu.Button>
|
||||
) : (
|
||||
<>
|
||||
{ellipsis || verticalEllipsis ? (
|
||||
<Menu.Button
|
||||
<Menu.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
onClick={menuButtonOnClick}
|
||||
disabled={disabled}
|
||||
className={`relative grid place-items-center rounded p-1 text-custom-text-200 hover:text-custom-text-100 outline-none ${
|
||||
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
|
||||
disabled
|
||||
? "cursor-not-allowed"
|
||||
: "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
<MoreHorizOutlined
|
||||
fontSize="small"
|
||||
className={verticalEllipsis ? "rotate-90" : ""}
|
||||
/>
|
||||
</button>
|
||||
</Menu.Button>
|
||||
) : (
|
||||
<Menu.Button
|
||||
<Menu.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 rounded-md px-2.5 py-1 text-xs whitespace-nowrap duration-300 ${
|
||||
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
|
||||
@ -88,24 +103,14 @@ const CustomMenu = ({
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</Menu.Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Transition
|
||||
as={React.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
|
||||
className={`absolute z-10 overflow-y-scroll whitespace-nowrap rounded-md border border-custom-border-300 p-1 text-xs shadow-lg focus:outline-none bg-custom-background-90 ${
|
||||
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
|
||||
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
|
||||
<Menu.Items>
|
||||
<div
|
||||
className={`z-10 overflow-y-scroll whitespace-nowrap rounded-md border border-custom-border-300 p-1 text-xs shadow-custom-shadow-rg focus:outline-none bg-custom-background-90 my-1 ${
|
||||
maxHeight === "lg"
|
||||
? "max-h-60"
|
||||
: maxHeight === "md"
|
||||
@ -116,14 +121,18 @@ const CustomMenu = ({
|
||||
? "max-h-28"
|
||||
: ""
|
||||
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="py-1">{children}</div>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
type MenuItemProps = {
|
||||
children: React.ReactNode;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
// react-poppper
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// icons
|
||||
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
@ -27,9 +29,11 @@ export type CustomSearchSelectProps = DropdownProps & {
|
||||
);
|
||||
|
||||
export const CustomSearchSelect = ({
|
||||
customButtonClassName = "",
|
||||
buttonClassName = "",
|
||||
className = "",
|
||||
customButton,
|
||||
placement,
|
||||
disabled = false,
|
||||
footerOption,
|
||||
input = false,
|
||||
@ -41,14 +45,18 @@ export const CustomSearchSelect = ({
|
||||
options,
|
||||
onOpen,
|
||||
optionsClassName = "",
|
||||
position = "left",
|
||||
selfPositioned = false,
|
||||
value,
|
||||
verticalPosition = "bottom",
|
||||
width = "auto",
|
||||
}: CustomSearchSelectProps) => {
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
const filteredOptions =
|
||||
query === ""
|
||||
? options
|
||||
@ -63,22 +71,32 @@ export const CustomSearchSelect = ({
|
||||
if (multiple) props.multiple = true;
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as="div"
|
||||
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
|
||||
{...props}
|
||||
>
|
||||
<Combobox as="div" className={`relative flex-shrink-0 text-left ${className}`} {...props}>
|
||||
{({ open }: { open: boolean }) => {
|
||||
if (open && onOpen) onOpen();
|
||||
|
||||
return (
|
||||
<>
|
||||
{customButton ? (
|
||||
<Combobox.Button as="div">{customButton}</Combobox.Button>
|
||||
) : (
|
||||
<Combobox.Button
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full rounded-md shadow-sm border border-custom-border-300 duration-300 focus:outline-none ${
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||
disabled
|
||||
? "cursor-not-allowed text-custom-text-200"
|
||||
: "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${customButtonClassName}`}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
) : (
|
||||
<Combobox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
|
||||
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
||||
} ${
|
||||
disabled
|
||||
@ -90,24 +108,17 @@ export const CustomSearchSelect = ({
|
||||
{!noChevron && !disabled && (
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</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={`absolute z-10 min-w-[10rem] border border-custom-border-300 p-2 rounded-md bg-custom-background-90 text-xs shadow-lg focus:outline-none ${
|
||||
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
|
||||
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
|
||||
<Combobox.Options as={React.Fragment}>
|
||||
<div
|
||||
className={`z-10 min-w-[10rem] border border-custom-border-300 p-2 rounded-md bg-custom-background-90 text-xs shadow-custom-shadow-rg focus:outline-none my-1 ${
|
||||
width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width
|
||||
} ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-custom-border-200 bg-custom-background-90 px-2">
|
||||
<MagnifyingGlassIcon className="h-3 w-3 text-custom-text-200" />
|
||||
@ -176,8 +187,8 @@ export const CustomSearchSelect = ({
|
||||
)}
|
||||
</div>
|
||||
{footerOption}
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
// react-popper
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
// icons
|
||||
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
import { CheckIcon } from "@heroicons/react/24/outline";
|
||||
@ -15,7 +17,9 @@ export type CustomSelectProps = DropdownProps & {
|
||||
};
|
||||
|
||||
const CustomSelect = ({
|
||||
customButtonClassName = "",
|
||||
buttonClassName = "",
|
||||
placement,
|
||||
children,
|
||||
className = "",
|
||||
customButton,
|
||||
@ -26,24 +30,43 @@ const CustomSelect = ({
|
||||
noChevron = false,
|
||||
onChange,
|
||||
optionsClassName = "",
|
||||
position = "left",
|
||||
selfPositioned = false,
|
||||
value,
|
||||
verticalPosition = "bottom",
|
||||
width = "auto",
|
||||
}: CustomSelectProps) => (
|
||||
}: CustomSelectProps) => {
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
});
|
||||
|
||||
return (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
|
||||
className={`relative flex-shrink-0 text-left ${className}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
<>
|
||||
{customButton ? (
|
||||
<Listbox.Button as={React.Fragment}>{customButton}</Listbox.Button>
|
||||
<Listbox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||
disabled
|
||||
? "cursor-not-allowed text-custom-text-200"
|
||||
: "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${customButtonClassName}`}
|
||||
>
|
||||
{customButton}
|
||||
</button>
|
||||
</Listbox.Button>
|
||||
) : (
|
||||
<Listbox.Button
|
||||
<Listbox.Button as={React.Fragment}>
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
|
||||
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
||||
@ -54,24 +77,16 @@ const CustomSelect = ({
|
||||
} ${buttonClassName}`}
|
||||
>
|
||||
{label}
|
||||
{!noChevron && !disabled && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
|
||||
{!noChevron && !disabled && (
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</Listbox.Button>
|
||||
)}
|
||||
</>
|
||||
|
||||
<Transition
|
||||
as={React.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"
|
||||
>
|
||||
<Listbox.Options
|
||||
className={`absolute z-10 border border-custom-border-300 mt-1 origin-top-right overflow-y-auto rounded-md bg-custom-background-90 text-xs shadow-lg focus:outline-none ${
|
||||
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
|
||||
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
|
||||
<Listbox.Options>
|
||||
<div
|
||||
className={`z-10 border border-custom-border-300 overflow-y-auto rounded-md bg-custom-background-90 text-xs shadow-custom-shadow-rg focus:outline-none my-1 ${
|
||||
maxHeight === "lg"
|
||||
? "max-h-60"
|
||||
: maxHeight === "md"
|
||||
@ -82,12 +97,16 @@ const CustomSelect = ({
|
||||
? "max-h-28"
|
||||
: ""
|
||||
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="space-y-1 p-2">{children}</div>
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</Listbox>
|
||||
);
|
||||
};
|
||||
|
||||
type OptionProps = {
|
||||
children: React.ReactNode;
|
||||
|
7
web/components/ui/dropdowns/types.d.ts
vendored
7
web/components/ui/dropdowns/types.d.ts
vendored
@ -1,4 +1,7 @@
|
||||
import { Placement } from "@popperjs/core";
|
||||
|
||||
export type DropdownProps = {
|
||||
customButtonClassName?: string;
|
||||
buttonClassName?: string;
|
||||
customButtonClassName?: string;
|
||||
className?: string;
|
||||
@ -10,8 +13,6 @@ export type DropdownProps = {
|
||||
noChevron?: boolean;
|
||||
onOpen?: () => void;
|
||||
optionsClassName?: string;
|
||||
position?: "right" | "left";
|
||||
selfPositioned?: boolean;
|
||||
verticalPosition?: "top" | "bottom";
|
||||
width?: "auto" | string;
|
||||
placement?: Placement;
|
||||
};
|
||||
|
@ -25,6 +25,7 @@
|
||||
"@nivo/line": "0.80.0",
|
||||
"@nivo/pie": "0.80.0",
|
||||
"@nivo/scatterplot": "0.80.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/nextjs": "^7.36.0",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
||||
"@tiptap/extension-color": "^2.0.4",
|
||||
@ -72,6 +73,7 @@
|
||||
"react-hook-form": "^7.38.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-moveable": "^0.54.1",
|
||||
"react-popper": "^2.3.0",
|
||||
"sharp": "^0.32.1",
|
||||
"sonner": "^0.6.2",
|
||||
"swr": "^2.1.3",
|
||||
|
@ -298,8 +298,6 @@ const Profile: NextPage = () => {
|
||||
buttonClassName={errors.role ? "border-red-500 bg-red-500/10" : ""}
|
||||
width="w-full"
|
||||
input
|
||||
verticalPosition="top"
|
||||
position="right"
|
||||
>
|
||||
{USER_ROLES.map((item) => (
|
||||
<CustomSelect.Option key={item.value} value={item.value}>
|
||||
@ -362,7 +360,6 @@ const Profile: NextPage = () => {
|
||||
}
|
||||
options={timeZoneOptions}
|
||||
onChange={onChange}
|
||||
verticalPosition="top"
|
||||
optionsClassName="w-full"
|
||||
input
|
||||
/>
|
||||
|
@ -427,13 +427,10 @@ const SinglePage: NextPage = () => {
|
||||
)}
|
||||
<CustomSearchSelect
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded-sm bg-custom-background-80 p-1.5 text-xs"
|
||||
>
|
||||
<div className="flex items-center gap-1 rounded-sm bg-custom-background-80 p-1.5 text-xs">
|
||||
<PlusIcon className="h-3.5 w-3.5" />
|
||||
{pageDetails.labels.length <= 0 && <span>Add Label</span>}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
value={pageDetails.labels}
|
||||
footerOption={
|
||||
|
@ -412,7 +412,7 @@ const MembersSettings: NextPage = () => {
|
||||
)}
|
||||
<CustomSelect
|
||||
customButton={
|
||||
<button className="flex item-center gap-1">
|
||||
<div className="flex item-center gap-1">
|
||||
<span
|
||||
className={`flex items-center text-sm font-medium ${
|
||||
member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400"
|
||||
@ -423,7 +423,7 @@ const MembersSettings: NextPage = () => {
|
||||
{member.memberId !== user?.id && (
|
||||
<Icon iconName="expand_more" className="text-lg font-medium" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
value={member.role}
|
||||
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
|
||||
@ -455,7 +455,6 @@ const MembersSettings: NextPage = () => {
|
||||
});
|
||||
});
|
||||
}}
|
||||
position="right"
|
||||
disabled={
|
||||
member.memberId === user?.id ||
|
||||
!member.member ||
|
||||
|
@ -269,7 +269,7 @@ const MembersSettings: NextPage = () => {
|
||||
)}
|
||||
<CustomSelect
|
||||
customButton={
|
||||
<button className="flex item-center gap-1">
|
||||
<div className="flex item-center gap-1">
|
||||
<span
|
||||
className={`flex items-center text-sm font-medium ${
|
||||
member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400"
|
||||
@ -280,7 +280,7 @@ const MembersSettings: NextPage = () => {
|
||||
{member.memberId !== user?.id && (
|
||||
<Icon iconName="expand_more" className="text-lg font-medium" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
value={member.role}
|
||||
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
|
||||
@ -307,7 +307,6 @@ const MembersSettings: NextPage = () => {
|
||||
});
|
||||
});
|
||||
}}
|
||||
position="right"
|
||||
disabled={
|
||||
member.memberId === currentUser?.member.id ||
|
||||
!member.status ||
|
||||
|
@ -6628,20 +6628,13 @@ prosemirror-menu@^1.2.1:
|
||||
prosemirror-history "^1.0.0"
|
||||
prosemirror-state "^1.0.0"
|
||||
|
||||
prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.8.1:
|
||||
prosemirror-model@1.18.1, prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1:
|
||||
version "1.18.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd"
|
||||
integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==
|
||||
dependencies:
|
||||
orderedmap "^2.0.0"
|
||||
|
||||
prosemirror-model@^1.19.0:
|
||||
version "1.19.3"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.3.tgz#f0d55285487fefd962d0ac695f716f4ec6705006"
|
||||
integrity sha512-tgSnwN7BS7/UM0sSARcW+IQryx2vODKX4MI7xpqY2X+iaepJdKBPc7I4aACIsDV/LTaTjt12Z56MhDr9LsyuZQ==
|
||||
dependencies:
|
||||
orderedmap "^2.0.0"
|
||||
|
||||
prosemirror-schema-basic@^1.2.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7"
|
||||
|
Loading…
Reference in New Issue
Block a user