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:
Anmol Singh Bhatia 2023-10-11 12:05:53 +05:30 committed by GitHub
parent d88eb09fad
commit 58ea4d6ec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 449 additions and 452 deletions

View File

@ -74,7 +74,6 @@ export const AutoArchiveAutomation: React.FC<Props> = ({
handleChange({ archive_in: val }); handleChange({ archive_in: val });
}} }}
input input
verticalPosition="bottom"
width="w-full" width="w-full"
disabled={disabled} disabled={disabled}
> >

View File

@ -100,7 +100,6 @@ export const ThemeSwitch: React.FC<Props> = observer(
}} }}
input input
width="w-full" width="w-full"
position="right"
> >
{THEMES_OBJ.map(({ value, label, type, icon }) => ( {THEMES_OBJ.map(({ value, label, type, icon }) => (
<CustomSelect.Option key={value} value={{ value, type }}> <CustomSelect.Option key={value} value={{ value, type }}>

View File

@ -255,15 +255,11 @@ export const SingleBoard: React.FC<Props> = (props) => {
!isDraftIssuesPage && ( !isDraftIssuesPage && (
<CustomMenu <CustomMenu
customButton={ customButton={
<button <div className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap">
type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap"
>
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
Add Issue Add Issue
</button> </div>
} }
position="left"
noBorder noBorder
> >
<CustomMenu.MenuItem <CustomMenu.MenuItem

View File

@ -369,12 +369,12 @@ export const SingleBoardIssue: React.FC<Props> = ({
{type && !isNotAllowed && ( {type && !isNotAllowed && (
<CustomMenu <CustomMenu
customButton={ 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" 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)} onClick={() => setIsMenuActive(!isMenuActive)}
> >
<EllipsisHorizontalIcon className="h-4 w-4" /> <EllipsisHorizontalIcon className="h-4 w-4" />
</button> </div>
} }
> >
<CustomMenu.MenuItem <CustomMenu.MenuItem

View File

@ -272,7 +272,6 @@ export const SingleList: React.FC<Props> = (props) => {
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
</div> </div>
} }
position="right"
noBorder noBorder
> >
<CustomMenu.MenuItem onClick={() => setIsCreateIssueFormOpen(true)}> <CustomMenu.MenuItem onClick={() => setIsCreateIssueFormOpen(true)}>

View File

@ -255,7 +255,6 @@ export const SpreadsheetView: React.FC<Props> = ({
<CustomMenu <CustomMenu
customButtonClassName="!w-full" customButtonClassName="!w-full"
className="!w-full" className="!w-full"
position="left"
customButton={ customButton={
<div <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 ${ 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 <CustomMenu
className="sticky left-0 z-10" className="sticky left-0 z-10"
customButton={ customButton={
<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">
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"
>
<PlusIcon className="h-4 w-4" /> <PlusIcon className="h-4 w-4" />
New Issue New Issue
</button> </div>
} }
position="left"
verticalPosition="top"
optionsClassName="left-5 !w-36" optionsClassName="left-5 !w-36"
noBorder noBorder
> >

View File

@ -92,7 +92,6 @@ export const SelectRepository: React.FC<Props> = ({
)} )}
</> </>
} }
position="right"
optionsClassName="w-full" optionsClassName="w-full"
/> />
); );

View File

@ -143,7 +143,6 @@ export const JiraGetImportDetail: React.FC = () => {
)} )}
</span> </span>
} }
verticalPosition="top"
> >
{projects && projects.length > 0 ? ( {projects && projects.length > 0 ? (
projects.map((project) => ( projects.map((project) => (

View File

@ -158,7 +158,7 @@ export const IssueMainContent: React.FC<Props> = ({
</a> </a>
</Link> </Link>
<CustomMenu position="left" ellipsis optionsClassName="px-1.5"> <CustomMenu ellipsis optionsClassName="px-1.5">
{siblingIssuesList ? ( {siblingIssuesList ? (
siblingIssuesList.length > 0 ? ( siblingIssuesList.length > 0 ? (
<> <>

View File

@ -93,7 +93,6 @@ export const PeekOverviewHeader: React.FC<Props> = ({
<Icon iconName={peekModes.find((m) => m.key === mode)?.icon ?? ""} /> <Icon iconName={peekModes.find((m) => m.key === mode)?.icon ?? ""} />
</button> </button>
} }
position="left"
> >
{peekModes.map((mode) => ( {peekModes.map((mode) => (
<CustomSelect.Option key={mode.key} value={mode.key}> <CustomSelect.Option key={mode.key} value={mode.key}>

View File

@ -33,7 +33,6 @@ export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
</div> </div>
} }
onChange={onChange} onChange={onChange}
position="right"
width="w-full min-w-[8rem]" width="w-full min-w-[8rem]"
noChevron noChevron
> >

View File

@ -91,7 +91,6 @@ export const SidebarCycleSelect: React.FC<Props> = ({
: handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle); : handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle);
}} }}
width="w-full" width="w-full"
position="right"
maxHeight="rg" maxHeight="rg"
disabled={disabled} disabled={disabled}
> >

View File

@ -20,17 +20,14 @@ export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabl
<CustomSelect <CustomSelect
value={value} value={value}
customButton={ customButton={
<button <div className="flex items-center gap-1.5 !text-sm bg-custom-background-80 rounded px-2.5 py-0.5">
type="button"
className="flex items-center gap-1.5 !text-sm bg-custom-background-80 rounded px-2.5 py-0.5"
>
<PlayIcon <PlayIcon
className={`h-4 w-4 -rotate-90 ${ className={`h-4 w-4 -rotate-90 ${
value !== null ? "text-custom-text-100" : "text-custom-text-200" value !== null ? "text-custom-text-100" : "text-custom-text-200"
}`} }`}
/> />
{estimatePoints?.find((e) => e.key === value)?.value ?? "No estimate"} {estimatePoints?.find((e) => e.key === value)?.value ?? "No estimate"}
</button> </div>
} }
onChange={onChange} onChange={onChange}
disabled={disabled} disabled={disabled}

View File

@ -87,7 +87,6 @@ export const SidebarModuleSelect: React.FC<Props> = ({
: handleModuleChange(modules?.find((m) => m.id === value) as IModule); : handleModuleChange(modules?.find((m) => m.id === value) as IModule);
}} }}
width="w-full" width="w-full"
position="right"
maxHeight="rg" maxHeight="rg"
disabled={disabled} disabled={disabled}
> >

View File

@ -18,8 +18,7 @@ type Props = {
export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabled = false }) => ( export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabled = false }) => (
<CustomSelect <CustomSelect
customButton={ customButton={
<button <div
type="button"
className={`flex items-center gap-1.5 text-left text-xs capitalize rounded px-2.5 py-0.5 ${ className={`flex items-center gap-1.5 text-left text-xs capitalize rounded px-2.5 py-0.5 ${
value === "urgent" value === "urgent"
? "border-red-500/20 bg-red-500/20 text-red-500" ? "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" /> <PriorityIcon priority={value} className="!text-sm" />
</span> </span>
<span>{value ?? "None"}</span> <span>{value ?? "None"}</span>
</button> </div>
} }
value={value} value={value}
onChange={onChange} onChange={onChange}

View File

@ -39,7 +39,7 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
return ( return (
<CustomSelect <CustomSelect
customButton={ 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 ? ( {selectedState ? (
<div className="flex items-center gap-1.5 text-left text-custom-text-100"> <div className="flex items-center gap-1.5 text-left text-custom-text-100">
<StateGroupIcon stateGroup={selectedState.group} color={selectedState.color} /> <StateGroupIcon stateGroup={selectedState.group} color={selectedState.color} />
@ -53,12 +53,11 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
) : ( ) : (
"None" "None"
)} )}
</button> </div>
} }
value={value} value={value}
onChange={onChange} onChange={onChange}
optionsClassName="w-min" optionsClassName="w-min"
position="left"
disabled={disabled} disabled={disabled}
> >
{states ? ( {states ? (

View File

@ -247,7 +247,6 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
</> </>
} }
buttonClassName="whitespace-nowrap" buttonClassName="whitespace-nowrap"
position="left"
noBorder noBorder
noChevron noChevron
> >
@ -283,7 +282,6 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
</> </>
} }
buttonClassName="whitespace-nowrap" buttonClassName="whitespace-nowrap"
position="left"
noBorder noBorder
noChevron noChevron
> >

View File

@ -113,10 +113,8 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
{...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })} {...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })}
multiple multiple
noChevron noChevron
position={position}
disabled={isNotAllowed} disabled={isNotAllowed}
onOpen={() => setFetchAssignees(true)} onOpen={() => setFetchAssignees(true)}
selfPositioned={selfPositioned}
width="w-full min-w-[12rem]" width="w-full min-w-[12rem]"
/> />
); );

View File

@ -74,8 +74,6 @@ export const ViewEstimateSelect: React.FC<Props> = ({
maxHeight="md" maxHeight="md"
noChevron noChevron
disabled={isNotAllowed} disabled={isNotAllowed}
position={position}
selfPositioned={selfPositioned}
width="w-full min-w-[8rem]" width="w-full min-w-[8rem]"
> >
<CustomSelect.Option value={null}> <CustomSelect.Option value={null}>

View File

@ -146,9 +146,7 @@ export const ViewLabelSelect: React.FC<Props> = ({
{...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })} {...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })}
multiple multiple
noChevron noChevron
position={position}
disabled={isNotAllowed} disabled={isNotAllowed}
selfPositioned={selfPositioned}
footerOption={footerOption} footerOption={footerOption}
width="w-full min-w-[12rem]" width="w-full min-w-[12rem]"
/> />

View File

@ -59,8 +59,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
}} }}
maxHeight="md" maxHeight="md"
customButton={ customButton={
<button <div
type="button"
className={`grid place-items-center rounded ${ className={`grid place-items-center rounded ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer" isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
} ${noBorder ? "" : "h-6 w-6 border shadow-sm"} ${ } ${noBorder ? "" : "h-6 w-6 border shadow-sm"} ${
@ -94,12 +93,10 @@ export const ViewPrioritySelect: React.FC<Props> = ({
{noBorder ? capitalizeFirstLetter(issue.priority ?? "None") : ""} {noBorder ? capitalizeFirstLetter(issue.priority ?? "None") : ""}
</span> </span>
</Tooltip> </Tooltip>
</button> </div>
} }
noChevron noChevron
disabled={isNotAllowed} disabled={isNotAllowed}
position={position}
selfPositioned={selfPositioned}
> >
{PRIORITIES?.map((priority) => ( {PRIORITIES?.map((priority) => (
<CustomSelect.Option key={priority} value={priority} className="capitalize"> <CustomSelect.Option key={priority} value={priority} className="capitalize">

View File

@ -65,7 +65,6 @@ export const SidebarLeadSelect: React.FC<Props> = ({ value, onChange }) => {
} }
options={options} options={options}
maxHeight="md" maxHeight="md"
position="right"
onChange={onChange} onChange={onChange}
/> />
</div> </div>

View File

@ -64,7 +64,6 @@ export const SidebarMembersSelect: React.FC<Props> = ({ value, onChange }) => {
options={options} options={options}
onChange={onChange} onChange={onChange}
maxHeight="md" maxHeight="md"
position="right"
multiple multiple
/> />
</div> </div>

View File

@ -214,12 +214,9 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
e.stopPropagation(); e.stopPropagation();
}} }}
customButton={ customButton={
<button <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">
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"
>
<Icon iconName="schedule" className="h-5 w-5 text-custom-text-300" /> <Icon iconName="schedule" className="h-5 w-5 text-custom-text-300" />
</button> </div>
} }
optionsClassName="!z-20" optionsClassName="!z-20"
> >

View File

@ -167,7 +167,6 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
} }
input input
width="w-full" width="w-full"
verticalPosition="top"
> >
{USER_ROLES.map((item) => ( {USER_ROLES.map((item) => (
<CustomSelect.Option key={item.value} value={item.value}> <CustomSelect.Option key={item.value} value={item.value}>
@ -197,7 +196,6 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
} }
options={timeZoneOptions} options={timeZoneOptions}
onChange={onChange} onChange={onChange}
verticalPosition="top"
optionsClassName="w-full" optionsClassName="w-full"
input input
/> />

View File

@ -384,12 +384,12 @@ export const SinglePageBlock: React.FC<Props> = ({
</button> </button>
<CustomMenu <CustomMenu
customButton={ 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" 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)} onClick={() => setIsMenuActive(!isMenuActive)}
> >
<BoltIcon className="h-4.5 w-3.5" /> <BoltIcon className="h-4.5 w-3.5" />
</button> </div>
} }
> >
{block.issue ? ( {block.issue ? (

View File

@ -418,7 +418,6 @@ export const CreateProjectModal: React.FC<Props> = ({
)} )}
</div> </div>
} }
verticalPosition="top"
noChevron noChevron
/> />
); );

View File

@ -1,13 +1,13 @@
import React, { useRef, useState } from "react"; import React, { useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// react-popper
import { usePopper } from "react-popper";
// services // services
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
// hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// headless ui // headless ui
import { Combobox } from "@headlessui/react"; import { Combobox } from "@headlessui/react";
// component // component
@ -19,6 +19,7 @@ import { PlusIcon } from "lucide-react";
// types // types
import { Tooltip } from "components/ui"; import { Tooltip } from "components/ui";
import { ICurrentUserResponse, IIssueLabels } from "types"; import { ICurrentUserResponse, IIssueLabels } from "types";
import { Placement } from "@popperjs/core";
// constants // constants
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
@ -31,6 +32,7 @@ type Props = {
buttonClassName?: string; buttonClassName?: string;
optionsClassName?: string; optionsClassName?: string;
maxRender?: number; maxRender?: number;
placement?: Placement;
hideDropdownArrow?: boolean; hideDropdownArrow?: boolean;
disabled?: boolean; disabled?: boolean;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
@ -45,21 +47,25 @@ export const LabelSelect: React.FC<Props> = ({
buttonClassName = "", buttonClassName = "",
optionsClassName = "", optionsClassName = "",
maxRender = 2, maxRender = 2,
placement,
hideDropdownArrow = false, hideDropdownArrow = false,
disabled = false, disabled = false,
user, user,
}) => { }) => {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [fetchStates, setFetchStates] = 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 [labelModal, setLabelModal] = useState(false);
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const dropdownBtn = useRef<any>(null); const { styles, attributes } = usePopper(referenceElement, popperElement, {
const dropdownOptions = useRef<any>(null); placement: placement ?? "bottom-start",
});
const { data: issueLabels } = useSWR<IIssueLabels[]>( const { data: issueLabels } = useSWR<IIssueLabels[]>(
projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId) : null, projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId) : null,
@ -131,8 +137,6 @@ export const LabelSelect: React.FC<Props> = ({
</div> </div>
); );
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
const footerOption = ( const footerOption = (
<button <button
type="button" type="button"
@ -165,33 +169,35 @@ export const LabelSelect: React.FC<Props> = ({
multiple multiple
> >
{({ open }: { open: boolean }) => { {({ open }: { open: boolean }) => {
if (open) { if (open) setFetchStates(true);
if (!isOpen) setIsOpen(true);
setFetchStates(true);
} else if (isOpen) setIsOpen(false);
return ( return (
<> <>
<Combobox.Button <Combobox.Button as={React.Fragment}>
ref={dropdownBtn} <button
type="button" ref={setReferenceElement}
className={`flex items-center justify-between gap-1 w-full text-xs ${ type="button"
disabled className={`flex items-center justify-between gap-1 w-full text-xs ${
? "cursor-not-allowed text-custom-text-200" disabled
: value.length <= maxRender ? "cursor-not-allowed text-custom-text-200"
? "cursor-pointer" : value.length <= maxRender
: "cursor-pointer hover:bg-custom-background-80" ? "cursor-pointer"
} ${buttonClassName}`} : "cursor-pointer hover:bg-custom-background-80"
> } ${buttonClassName}`}
{label} >
{!hideDropdownArrow && !disabled && ( {label}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> {!hideDropdownArrow && !disabled && (
)} <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button> </Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
<Combobox.Options <Combobox.Options>
ref={dropdownOptions} <div
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}`} 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"> <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" /> <MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
@ -234,8 +240,8 @@ export const LabelSelect: React.FC<Props> = ({
)} )}
</div> </div>
{footerOption} {footerOption}
</Combobox.Options> </div>
</div> </Combobox.Options>
</> </>
); );
}} }}

View File

@ -77,7 +77,6 @@ export const MemberSelect: React.FC<Props> = ({ value, onChange, isDisabled = fa
] ]
} }
maxHeight="md" maxHeight="md"
position="right"
width="w-full" width="w-full"
onChange={onChange} onChange={onChange}
disabled={isDisabled} disabled={isDisabled}

View File

@ -1,9 +1,10 @@
import React, { useRef, useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// react-popper
import { usePopper } from "react-popper";
// hooks // hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
import useProjectMembers from "hooks/use-project-members"; import useProjectMembers from "hooks/use-project-members";
import useWorkspaceMembers from "hooks/use-workspace-members"; import useWorkspaceMembers from "hooks/use-workspace-members";
// headless ui // headless ui
@ -15,6 +16,7 @@ import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
// types // types
import { IUser } from "types"; import { IUser } from "types";
import { Placement } from "@popperjs/core";
type Props = { type Props = {
value: string | string[]; value: string | string[];
@ -25,6 +27,7 @@ type Props = {
className?: string; className?: string;
buttonClassName?: string; buttonClassName?: string;
optionsClassName?: string; optionsClassName?: string;
placement?: Placement;
hideDropdownArrow?: boolean; hideDropdownArrow?: boolean;
disabled?: boolean; disabled?: boolean;
}; };
@ -38,18 +41,22 @@ export const MembersSelect: React.FC<Props> = ({
className = "", className = "",
buttonClassName = "", buttonClassName = "",
optionsClassName = "", optionsClassName = "",
placement,
hideDropdownArrow = false, hideDropdownArrow = false,
disabled = false, disabled = false,
}) => { }) => {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [fetchStates, setFetchStates] = 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 router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const dropdownBtn = useRef<any>(null); const { styles, attributes } = usePopper(referenceElement, popperElement, {
const dropdownOptions = useRef<any>(null); placement: placement ?? "bottom-start",
});
const { members } = useProjectMembers( const { members } = useProjectMembers(
workspaceSlug?.toString(), workspaceSlug?.toString(),
@ -105,8 +112,6 @@ export const MembersSelect: React.FC<Props> = ({
</Tooltip> </Tooltip>
); );
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
return ( return (
<Combobox <Combobox
as="div" as="div"
@ -117,31 +122,32 @@ export const MembersSelect: React.FC<Props> = ({
multiple multiple
> >
{({ open }: { open: boolean }) => { {({ open }: { open: boolean }) => {
if (open) { if (open) setFetchStates(true);
if (!isOpen) setIsOpen(true);
setFetchStates(true);
} else if (isOpen) setIsOpen(false);
return ( return (
<> <>
<Combobox.Button <Combobox.Button as={React.Fragment}>
ref={dropdownBtn} <button
type="button" ref={setReferenceElement}
className={`flex items-center justify-between gap-1 w-full text-xs ${ type="button"
disabled className={`flex items-center justify-between gap-1 w-full text-xs ${
? "cursor-not-allowed text-custom-text-200" disabled
: "cursor-pointer hover:bg-custom-background-80" ? "cursor-not-allowed text-custom-text-200"
} ${buttonClassName}`} : "cursor-pointer hover:bg-custom-background-80"
> } ${buttonClassName}`}
{label} >
{!hideDropdownArrow && !disabled && ( {label}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> {!hideDropdownArrow && !disabled && (
)} <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button> </Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}> <Combobox.Options>
<Combobox.Options <div
ref={dropdownOptions} 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}`}
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}`} 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"> <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" /> <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> <p className="text-center text-custom-text-200">Loading...</p>
)} )}
</div> </div>
</Combobox.Options> </div>
</div> </Combobox.Options>
</> </>
); );
}} }}

View File

@ -1,7 +1,7 @@
import React, { useRef, useState } from "react"; import React, { useState } from "react";
// hooks // react-popper
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; import { usePopper } from "react-popper";
// headless ui // headless ui
import { Combobox } from "@headlessui/react"; import { Combobox } from "@headlessui/react";
// icons // icons
@ -12,6 +12,7 @@ import { PriorityIcon } from "components/icons";
import { Tooltip } from "components/ui"; import { Tooltip } from "components/ui";
// types // types
import { TIssuePriorities } from "types"; import { TIssuePriorities } from "types";
import { Placement } from "@popperjs/core";
// constants // constants
import { PRIORITIES } from "constants/project"; import { PRIORITIES } from "constants/project";
@ -21,6 +22,7 @@ type Props = {
className?: string; className?: string;
buttonClassName?: string; buttonClassName?: string;
optionsClassName?: string; optionsClassName?: string;
placement?: Placement;
hideDropdownArrow?: boolean; hideDropdownArrow?: boolean;
disabled?: boolean; disabled?: boolean;
}; };
@ -31,14 +33,18 @@ export const PrioritySelect: React.FC<Props> = ({
className = "", className = "",
buttonClassName = "", buttonClassName = "",
optionsClassName = "", optionsClassName = "",
placement,
hideDropdownArrow = false, hideDropdownArrow = false,
disabled = false, disabled = false,
}) => { }) => {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const dropdownBtn = useRef<any>(null); const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const dropdownOptions = useRef<any>(null); const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const options = PRIORITIES?.map((priority) => ({ const options = PRIORITIES?.map((priority) => ({
value: priority, value: priority,
@ -87,8 +93,6 @@ export const PrioritySelect: React.FC<Props> = ({
</Tooltip> </Tooltip>
); );
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
return ( return (
<Combobox <Combobox
as="div" as="div"
@ -97,77 +101,71 @@ export const PrioritySelect: React.FC<Props> = ({
onChange={onChange} onChange={onChange}
disabled={disabled} disabled={disabled}
> >
{({ open }: { open: boolean }) => { <Combobox.Button as={React.Fragment}>
if (open) { <button
if (!isOpen) setIsOpen(true); ref={setReferenceElement}
} else if (isOpen) setIsOpen(false); type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
return ( disabled
<> ? "cursor-not-allowed text-custom-text-200"
<Combobox.Button : "cursor-pointer hover:bg-custom-background-80"
ref={dropdownBtn} } ${buttonClassName}`}
type="button" >
className={`flex items-center justify-between gap-1 w-full text-xs ${ {label}
disabled {!hideDropdownArrow && !disabled && (
? "cursor-not-allowed text-custom-text-200" <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
: "cursor-pointer hover:bg-custom-background-80" )}
} ${buttonClassName}`} </button>
> </Combobox.Button>
{label} <Combobox.Options>
{!hideDropdownArrow && !disabled && ( <div
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> 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}
</Combobox.Button> style={styles.popper}
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}> {...attributes.popper}
<Combobox.Options >
ref={dropdownOptions} <div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
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}`} <MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
> <Combobox.Input
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2"> className="w-full bg-transparent py-1 px-2 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" /> value={query}
<Combobox.Input onChange={(e) => setQuery(e.target.value)}
className="w-full bg-transparent py-1 px-2 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none" placeholder="Search"
value={query} displayValue={(assigned: any) => assigned?.name}
onChange={(e) => setQuery(e.target.value)} />
placeholder="Search" </div>
displayValue={(assigned: any) => assigned?.name} <div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
/> {filteredOptions ? (
</div> filteredOptions.length > 0 ? (
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}> filteredOptions.map((option) => (
{filteredOptions ? ( <Combobox.Option
filteredOptions.length > 0 ? ( key={option.value}
filteredOptions.map((option) => ( value={option.value}
<Combobox.Option className={({ active, selected }) =>
key={option.value} `flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
value={option.value} active && !selected ? "bg-custom-background-80" : ""
className={({ active, selected }) => } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${ }
active && !selected ? "bg-custom-background-80" : "" >
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}` {({ selected }) => (
} <>
> {option.content}
{({ selected }) => ( {selected && <CheckIcon className={`h-3.5 w-3.5`} />}
<> </>
{option.content} )}
{selected && <CheckIcon className={`h-3.5 w-3.5`} />} </Combobox.Option>
</> ))
)} ) : (
</Combobox.Option> <span className="flex items-center gap-2 p-1">
)) <p className="text-left text-custom-text-200 ">No matching results</p>
) : ( </span>
<span className="flex items-center gap-2 p-1"> )
<p className="text-left text-custom-text-200 ">No matching results</p> ) : (
</span> <p className="text-center text-custom-text-200">Loading...</p>
) )}
) : ( </div>
<p className="text-center text-custom-text-200">Loading...</p> </div>
)} </Combobox.Options>
</div>
</Combobox.Options>
</div>
</>
);
}}
</Combobox> </Combobox>
); );
}; };

View File

@ -230,7 +230,6 @@ const SendProjectInvitationModal: React.FC<Props> = (props) => {
onChange(val); onChange(val);
}} }}
options={options} options={options}
position="left"
width="w-full min-w-[12rem]" width="w-full min-w-[12rem]"
/> />
)} )}
@ -252,12 +251,12 @@ const SendProjectInvitationModal: React.FC<Props> = (props) => {
<CustomSelect <CustomSelect
{...field} {...field}
customButton={ 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"> <span className="capitalize">
{field.value ? ROLE[field.value] : "Select role"} {field.value ? ROLE[field.value] : "Select role"}
</span> </span>
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</button> </div>
} }
input input
width="w-full" width="w-full"

View File

@ -1,11 +1,11 @@
import React, { useRef, useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// hooks // react-popper
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; import { usePopper } from "react-popper";
// services // services
import stateService from "services/state.service"; import stateService from "services/state.service";
// headless ui // headless ui
@ -16,6 +16,7 @@ import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { StateGroupIcon } from "components/icons"; import { StateGroupIcon } from "components/icons";
// types // types
import { Tooltip } from "components/ui"; import { Tooltip } from "components/ui";
import { Placement } from "@popperjs/core";
// constants // constants
import { IState } from "types"; import { IState } from "types";
import { STATES_LIST } from "constants/fetch-keys"; import { STATES_LIST } from "constants/fetch-keys";
@ -29,6 +30,7 @@ type Props = {
className?: string; className?: string;
buttonClassName?: string; buttonClassName?: string;
optionsClassName?: string; optionsClassName?: string;
placement?: Placement;
hideDropdownArrow?: boolean; hideDropdownArrow?: boolean;
disabled?: boolean; disabled?: boolean;
}; };
@ -40,17 +42,21 @@ export const StateSelect: React.FC<Props> = ({
className = "", className = "",
buttonClassName = "", buttonClassName = "",
optionsClassName = "", optionsClassName = "",
placement,
hideDropdownArrow = false, hideDropdownArrow = false,
disabled = false, disabled = false,
}) => { }) => {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const dropdownBtn = useRef<any>(null); const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const dropdownOptions = useRef<any>(null); const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [fetchStates, setFetchStates] = useState<boolean>(false); const [fetchStates, setFetchStates] = useState<boolean>(false);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
@ -90,8 +96,6 @@ export const StateSelect: React.FC<Props> = ({
</Tooltip> </Tooltip>
); );
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
return ( return (
<Combobox <Combobox
as="div" as="div"
@ -103,31 +107,32 @@ export const StateSelect: React.FC<Props> = ({
disabled={disabled} disabled={disabled}
> >
{({ open }: { open: boolean }) => { {({ open }: { open: boolean }) => {
if (open) { if (open) setFetchStates(true);
if (!isOpen) setIsOpen(true);
setFetchStates(true);
} else if (isOpen) setIsOpen(false);
return ( return (
<> <>
<Combobox.Button <Combobox.Button as={React.Fragment}>
ref={dropdownBtn} <button
type="button" ref={setReferenceElement}
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 ${ type="button"
disabled 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 ${
? "cursor-not-allowed text-custom-text-200" disabled
: "cursor-pointer hover:bg-custom-background-80" ? "cursor-not-allowed text-custom-text-200"
} ${buttonClassName}`} : "cursor-pointer hover:bg-custom-background-80"
> } ${buttonClassName}`}
{label} >
{!hideDropdownArrow && !disabled && ( {label}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> {!hideDropdownArrow && !disabled && (
)} <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button> </Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}> <Combobox.Options>
<Combobox.Options <div
ref={dropdownOptions} 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}`}
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}`} 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"> <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" /> <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> <p className="text-center text-custom-text-200">Loading...</p>
)} )}
</div> </div>
</Combobox.Options> </div>
</div> </Combobox.Options>
</> </>
); );
}} }}

View File

@ -1,9 +1,11 @@
import React from "react"; import React, { useState } from "react";
import Link from "next/link"; import Link from "next/link";
// react-poppper
import { usePopper } from "react-popper";
// headless ui // headless ui
import { Menu, Transition } from "@headlessui/react"; import { Menu } from "@headlessui/react";
// ui // ui
import { DropdownProps } from "components/ui"; import { DropdownProps } from "components/ui";
// icons // icons
@ -20,6 +22,7 @@ export type CustomMenuProps = DropdownProps & {
const CustomMenu = ({ const CustomMenu = ({
buttonClassName = "", buttonClassName = "",
customButtonClassName = "", customButtonClassName = "",
placement,
children, children,
className = "", className = "",
customButton, customButton,
@ -30,100 +33,106 @@ const CustomMenu = ({
noBorder = false, noBorder = false,
noChevron = false, noChevron = false,
optionsClassName = "", optionsClassName = "",
position = "right",
selfPositioned = false,
verticalEllipsis = false, verticalEllipsis = false,
verticalPosition = "bottom",
width = "auto", width = "auto",
menuButtonOnClick, menuButtonOnClick,
}: CustomMenuProps) => ( }: CustomMenuProps) => {
<Menu as="div" className={`${selfPositioned ? "" : "relative"} w-min text-left ${className}`}> const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
{({ open }) => ( const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
<>
{customButton ? ( const { styles, attributes } = usePopper(referenceElement, popperElement, {
<Menu.Button placement: placement ?? "bottom-start",
as="button" });
type="button" return (
onClick={menuButtonOnClick} <Menu as="div" className={`relative w-min text-left ${className}`}>
className={customButtonClassName} {({ open }) => (
disabled={disabled} <>
> {customButton ? (
{customButton} <Menu.Button as={React.Fragment}>
</Menu.Button> <button
) : ( ref={setReferenceElement}
<>
{ellipsis || verticalEllipsis ? (
<Menu.Button
type="button" type="button"
onClick={menuButtonOnClick} onClick={menuButtonOnClick}
disabled={disabled} className={customButtonClassName}
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"
} ${buttonClassName}`}
> >
<MoreHorizOutlined {customButton}
fontSize="small" </button>
className={verticalEllipsis ? "rotate-90" : ""} </Menu.Button>
/> ) : (
</Menu.Button> <>
) : ( {ellipsis || verticalEllipsis ? (
<Menu.Button <Menu.Button as={React.Fragment}>
type="button" <button
className={`flex items-center justify-between gap-1 rounded-md px-2.5 py-1 text-xs whitespace-nowrap duration-300 ${ ref={setReferenceElement}
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200" type="button"
} ${ onClick={menuButtonOnClick}
noBorder ? "" : "border border-custom-border-300 shadow-sm focus:outline-none" disabled={disabled}
} ${ className={`relative grid place-items-center rounded p-1 text-custom-text-200 hover:text-custom-text-100 outline-none ${
disabled disabled
? "cursor-not-allowed text-custom-text-200" ? "cursor-not-allowed"
: "cursor-pointer hover:bg-custom-background-80" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`} } ${buttonClassName}`}
> >
{label} <MoreHorizOutlined
{!noChevron && ( fontSize="small"
<ExpandMoreOutlined className={verticalEllipsis ? "rotate-90" : ""}
sx={{ />
fontSize: 14, </button>
}} </Menu.Button>
aria-hidden="true" ) : (
/> <Menu.Button as={React.Fragment}>
)} <button
</Menu.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"
} ${
<Transition noBorder ? "" : "border border-custom-border-300 shadow-sm focus:outline-none"
as={React.Fragment} } ${
enter="transition ease-out duration-100" disabled
enterFrom="transform opacity-0 scale-95" ? "cursor-not-allowed text-custom-text-200"
enterTo="transform opacity-100 scale-100" : "cursor-pointer hover:bg-custom-background-80"
leave="transition ease-in duration-75" } ${buttonClassName}`}
leaveFrom="transform opacity-100 scale-100" >
leaveTo="transform opacity-0 scale-95" {label}
> {!noChevron && (
<Menu.Items <ExpandMoreOutlined
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 ${ sx={{
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right" fontSize: 14,
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${ }}
maxHeight === "lg" aria-hidden="true"
? "max-h-60" />
: maxHeight === "md" )}
? "max-h-48" </button>
: maxHeight === "rg" </Menu.Button>
? "max-h-36" )}
: maxHeight === "sm" </>
? "max-h-28" )}
: "" <Menu.Items>
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`} <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 ${
<div className="py-1">{children}</div> maxHeight === "lg"
? "max-h-60"
: maxHeight === "md"
? "max-h-48"
: maxHeight === "rg"
? "max-h-36"
: maxHeight === "sm"
? "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> </Menu.Items>
</Transition> </>
</> )}
)} </Menu>
</Menu> );
); };
type MenuItemProps = { type MenuItemProps = {
children: React.ReactNode; children: React.ReactNode;

View File

@ -1,7 +1,9 @@
import React, { useState } from "react"; import React, { useState } from "react";
// react-poppper
import { usePopper } from "react-popper";
// headless ui // headless ui
import { Combobox, Transition } from "@headlessui/react"; import { Combobox } from "@headlessui/react";
// icons // icons
import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
@ -27,9 +29,11 @@ export type CustomSearchSelectProps = DropdownProps & {
); );
export const CustomSearchSelect = ({ export const CustomSearchSelect = ({
customButtonClassName = "",
buttonClassName = "", buttonClassName = "",
className = "", className = "",
customButton, customButton,
placement,
disabled = false, disabled = false,
footerOption, footerOption,
input = false, input = false,
@ -41,14 +45,18 @@ export const CustomSearchSelect = ({
options, options,
onOpen, onOpen,
optionsClassName = "", optionsClassName = "",
position = "left",
selfPositioned = false,
value, value,
verticalPosition = "bottom",
width = "auto", width = "auto",
}: CustomSearchSelectProps) => { }: CustomSearchSelectProps) => {
const [query, setQuery] = useState(""); 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 = const filteredOptions =
query === "" query === ""
? options ? options
@ -63,51 +71,54 @@ export const CustomSearchSelect = ({
if (multiple) props.multiple = true; if (multiple) props.multiple = true;
return ( return (
<Combobox <Combobox as="div" className={`relative flex-shrink-0 text-left ${className}`} {...props}>
as="div"
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
{...props}
>
{({ open }: { open: boolean }) => { {({ open }: { open: boolean }) => {
if (open && onOpen) onOpen(); if (open && onOpen) onOpen();
return ( return (
<> <>
{customButton ? ( {customButton ? (
<Combobox.Button as="div">{customButton}</Combobox.Button> <Combobox.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>
</Combobox.Button>
) : ( ) : (
<Combobox.Button <Combobox.Button as={React.Fragment}>
type="button" <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 ${ ref={setReferenceElement}
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs" 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 ${
disabled input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
? "cursor-not-allowed text-custom-text-200" } ${
: "cursor-pointer hover:bg-custom-background-80" disabled
} ${buttonClassName}`} ? "cursor-not-allowed text-custom-text-200"
> : "cursor-pointer hover:bg-custom-background-80"
{label} } ${buttonClassName}`}
{!noChevron && !disabled && ( >
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> {label}
)} {!noChevron && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button> </Combobox.Button>
)} )}
<Transition <Combobox.Options as={React.Fragment}>
show={open} <div
as={React.Fragment} 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 ${
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"} ${
width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width
} ${optionsClassName}`} } ${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"> <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" /> <MagnifyingGlassIcon className="h-3 w-3 text-custom-text-200" />
@ -176,8 +187,8 @@ export const CustomSearchSelect = ({
)} )}
</div> </div>
{footerOption} {footerOption}
</Combobox.Options> </div>
</Transition> </Combobox.Options>
</> </>
); );
}} }}

View File

@ -1,7 +1,9 @@
import React from "react"; import React, { useState } from "react";
// react-popper
import { usePopper } from "react-popper";
// headless ui // headless ui
import { Listbox, Transition } from "@headlessui/react"; import { Listbox } from "@headlessui/react";
// icons // icons
import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { CheckIcon } from "@heroicons/react/24/outline"; import { CheckIcon } from "@heroicons/react/24/outline";
@ -15,7 +17,9 @@ export type CustomSelectProps = DropdownProps & {
}; };
const CustomSelect = ({ const CustomSelect = ({
customButtonClassName = "",
buttonClassName = "", buttonClassName = "",
placement,
children, children,
className = "", className = "",
customButton, customButton,
@ -26,68 +30,83 @@ const CustomSelect = ({
noChevron = false, noChevron = false,
onChange, onChange,
optionsClassName = "", optionsClassName = "",
position = "left",
selfPositioned = false,
value, value,
verticalPosition = "bottom",
width = "auto", width = "auto",
}: CustomSelectProps) => ( }: CustomSelectProps) => {
<Listbox const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
as="div" const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
value={value}
onChange={onChange}
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
disabled={disabled}
>
<>
{customButton ? (
<Listbox.Button as={React.Fragment}>{customButton}</Listbox.Button>
) : (
<Listbox.Button
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
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && !disabled && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
</Listbox.Button>
)}
</>
<Transition const { styles, attributes } = usePopper(referenceElement, popperElement, {
as={React.Fragment} placement: placement ?? "bottom-start",
enter="transition ease-out duration-100" });
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100" return (
leave="transition ease-in duration-75" <Listbox
leaveFrom="transform opacity-100 scale-100" as="div"
leaveTo="transform opacity-0 scale-95" value={value}
onChange={onChange}
className={`relative flex-shrink-0 text-left ${className}`}
disabled={disabled}
> >
<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 ${ {customButton ? (
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right" <Listbox.Button as={React.Fragment}>
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${ <button
maxHeight === "lg" ref={setReferenceElement}
? "max-h-60" type="button"
: maxHeight === "md" className={`flex items-center justify-between gap-1 w-full text-xs ${
? "max-h-48" disabled
: maxHeight === "rg" ? "cursor-not-allowed text-custom-text-200"
? "max-h-36" : "cursor-pointer hover:bg-custom-background-80"
: maxHeight === "sm" } ${customButtonClassName}`}
? "max-h-28" >
: "" {customButton}
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`} </button>
> </Listbox.Button>
<div className="space-y-1 p-2">{children}</div> ) : (
<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"
} ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Listbox.Button>
)}
</>
<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"
? "max-h-48"
: maxHeight === "rg"
? "max-h-36"
: maxHeight === "sm"
? "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> </Listbox.Options>
</Transition> </Listbox>
</Listbox> );
); };
type OptionProps = { type OptionProps = {
children: React.ReactNode; children: React.ReactNode;

View File

@ -1,4 +1,7 @@
import { Placement } from "@popperjs/core";
export type DropdownProps = { export type DropdownProps = {
customButtonClassName?: string;
buttonClassName?: string; buttonClassName?: string;
customButtonClassName?: string; customButtonClassName?: string;
className?: string; className?: string;
@ -10,8 +13,6 @@ export type DropdownProps = {
noChevron?: boolean; noChevron?: boolean;
onOpen?: () => void; onOpen?: () => void;
optionsClassName?: string; optionsClassName?: string;
position?: "right" | "left";
selfPositioned?: boolean;
verticalPosition?: "top" | "bottom";
width?: "auto" | string; width?: "auto" | string;
placement?: Placement;
}; };

View File

@ -25,6 +25,7 @@
"@nivo/line": "0.80.0", "@nivo/line": "0.80.0",
"@nivo/pie": "0.80.0", "@nivo/pie": "0.80.0",
"@nivo/scatterplot": "0.80.0", "@nivo/scatterplot": "0.80.0",
"@popperjs/core": "^2.11.8",
"@sentry/nextjs": "^7.36.0", "@sentry/nextjs": "^7.36.0",
"@tiptap/extension-code-block-lowlight": "^2.0.4", "@tiptap/extension-code-block-lowlight": "^2.0.4",
"@tiptap/extension-color": "^2.0.4", "@tiptap/extension-color": "^2.0.4",
@ -72,6 +73,7 @@
"react-hook-form": "^7.38.0", "react-hook-form": "^7.38.0",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-moveable": "^0.54.1", "react-moveable": "^0.54.1",
"react-popper": "^2.3.0",
"sharp": "^0.32.1", "sharp": "^0.32.1",
"sonner": "^0.6.2", "sonner": "^0.6.2",
"swr": "^2.1.3", "swr": "^2.1.3",

View File

@ -298,8 +298,6 @@ const Profile: NextPage = () => {
buttonClassName={errors.role ? "border-red-500 bg-red-500/10" : ""} buttonClassName={errors.role ? "border-red-500 bg-red-500/10" : ""}
width="w-full" width="w-full"
input input
verticalPosition="top"
position="right"
> >
{USER_ROLES.map((item) => ( {USER_ROLES.map((item) => (
<CustomSelect.Option key={item.value} value={item.value}> <CustomSelect.Option key={item.value} value={item.value}>
@ -362,7 +360,6 @@ const Profile: NextPage = () => {
} }
options={timeZoneOptions} options={timeZoneOptions}
onChange={onChange} onChange={onChange}
verticalPosition="top"
optionsClassName="w-full" optionsClassName="w-full"
input input
/> />

View File

@ -427,13 +427,10 @@ const SinglePage: NextPage = () => {
)} )}
<CustomSearchSelect <CustomSearchSelect
customButton={ customButton={
<button <div className="flex items-center gap-1 rounded-sm bg-custom-background-80 p-1.5 text-xs">
type="button"
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" /> <PlusIcon className="h-3.5 w-3.5" />
{pageDetails.labels.length <= 0 && <span>Add Label</span>} {pageDetails.labels.length <= 0 && <span>Add Label</span>}
</button> </div>
} }
value={pageDetails.labels} value={pageDetails.labels}
footerOption={ footerOption={

View File

@ -412,7 +412,7 @@ const MembersSettings: NextPage = () => {
)} )}
<CustomSelect <CustomSelect
customButton={ customButton={
<button className="flex item-center gap-1"> <div className="flex item-center gap-1">
<span <span
className={`flex items-center text-sm font-medium ${ className={`flex items-center text-sm font-medium ${
member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400" member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400"
@ -423,7 +423,7 @@ const MembersSettings: NextPage = () => {
{member.memberId !== user?.id && ( {member.memberId !== user?.id && (
<Icon iconName="expand_more" className="text-lg font-medium" /> <Icon iconName="expand_more" className="text-lg font-medium" />
)} )}
</button> </div>
} }
value={member.role} value={member.role}
onChange={(value: 5 | 10 | 15 | 20 | undefined) => { onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
@ -455,7 +455,6 @@ const MembersSettings: NextPage = () => {
}); });
}); });
}} }}
position="right"
disabled={ disabled={
member.memberId === user?.id || member.memberId === user?.id ||
!member.member || !member.member ||

View File

@ -269,7 +269,7 @@ const MembersSettings: NextPage = () => {
)} )}
<CustomSelect <CustomSelect
customButton={ customButton={
<button className="flex item-center gap-1"> <div className="flex item-center gap-1">
<span <span
className={`flex items-center text-sm font-medium ${ className={`flex items-center text-sm font-medium ${
member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400" member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400"
@ -280,7 +280,7 @@ const MembersSettings: NextPage = () => {
{member.memberId !== user?.id && ( {member.memberId !== user?.id && (
<Icon iconName="expand_more" className="text-lg font-medium" /> <Icon iconName="expand_more" className="text-lg font-medium" />
)} )}
</button> </div>
} }
value={member.role} value={member.role}
onChange={(value: 5 | 10 | 15 | 20 | undefined) => { onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
@ -307,7 +307,6 @@ const MembersSettings: NextPage = () => {
}); });
}); });
}} }}
position="right"
disabled={ disabled={
member.memberId === currentUser?.member.id || member.memberId === currentUser?.member.id ||
!member.status || !member.status ||

View File

@ -6628,20 +6628,13 @@ prosemirror-menu@^1.2.1:
prosemirror-history "^1.0.0" prosemirror-history "^1.0.0"
prosemirror-state "^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" version "1.18.1"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd" resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd"
integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw== integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==
dependencies: dependencies:
orderedmap "^2.0.0" 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: prosemirror-schema-basic@^1.2.0:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7" resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7"