mirror of
https://github.com/makeplane/plane.git
synced 2025-12-16 11:57:56 +01:00
chore: replaced listbox with our custom implementation
This commit is contained in:
@@ -3,9 +3,8 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Link2, MoveRight } from "lucide-react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { CenterPanelIcon, FullScreenPanelIcon, setToast, SidePanelIcon, TOAST_TYPE } from "@plane/ui";
|
||||
import { CenterPanelIcon, CustomSelect, FullScreenPanelIcon, setToast, SidePanelIcon, TOAST_TYPE } from "@plane/ui";
|
||||
// helpers
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
@@ -66,49 +65,29 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
||||
<MoveRight className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
<Listbox
|
||||
as="div"
|
||||
<CustomSelect
|
||||
value={peekMode}
|
||||
onChange={(val) => setPeekMode(val)}
|
||||
onChange={(val: any) => setPeekMode(val)}
|
||||
className="relative flex-shrink-0 text-left"
|
||||
customButtonClassName={`grid place-items-center text-custom-text-300 hover:text-custom-text-200 ${peekMode === "full" ? "rotate-45" : ""}`}
|
||||
customButton={<Icon className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />}
|
||||
optionsClassName="absolute left-0 z-10 mt-1 min-w-[12rem] origin-top-left overflow-y-auto whitespace-nowrap rounded-md border border-custom-border-300 bg-custom-background-90 text-xs shadow-lg focus:outline-none"
|
||||
>
|
||||
<Listbox.Button
|
||||
className={`grid place-items-center text-custom-text-300 hover:text-custom-text-200 ${peekMode === "full" ? "rotate-45" : ""}`}
|
||||
>
|
||||
<Icon className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />
|
||||
</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 left-0 z-10 mt-1 min-w-[12rem] origin-top-left overflow-y-auto whitespace-nowrap rounded-md border border-custom-border-300 bg-custom-background-90 text-xs shadow-lg focus:outline-none">
|
||||
<div className="space-y-1 p-2">
|
||||
{PEEK_MODES.map((mode) => (
|
||||
<Listbox.Option
|
||||
key={mode.key}
|
||||
value={mode.key}
|
||||
className={({ active, selected }) =>
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||
active ? "bg-custom-background-80" : ""
|
||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<mode.icon className="-my-1 h-4 w-4 flex-shrink-0" />
|
||||
{mode.label}
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</Listbox>
|
||||
<div className="space-y-1 p-2">
|
||||
{PEEK_MODES.map((mode) => (
|
||||
<CustomSelect.Option
|
||||
key={mode.key}
|
||||
value={mode.key}
|
||||
className="cursor-pointer select-none truncate rounded px-1 py-1.5"
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<mode.icon className="-my-1 h-4 w-4 flex-shrink-0" />
|
||||
{mode.label}
|
||||
</div>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</div>
|
||||
</CustomSelect>
|
||||
</div>
|
||||
{isClipboardWriteAllowed && (peekMode === "side" || peekMode === "modal") && (
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
|
||||
@@ -15,15 +15,14 @@ import {
|
||||
} from "react-hook-form";
|
||||
// icons
|
||||
import { usePopper } from "react-popper";
|
||||
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
import { ChevronDown, Plus, XCircle } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IUser, IWorkspace } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
@@ -175,69 +174,49 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = observer((props) => {
|
||||
name={`emails.${index}.role`}
|
||||
rules={{ required: true }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Listbox
|
||||
as="div"
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
onChange={(val: any) => {
|
||||
onChange(val);
|
||||
setValue(`emails.${index}.role_active`, true);
|
||||
}}
|
||||
className="w-full flex-shrink-0 text-left"
|
||||
optionsClassName="p-2 absolute space-y-1 z-10 mt-1 h-fit w-48 sm:w-60 rounded-md border border-custom-border-300 bg-custom-background-100 shadow-sm focus:outline-none"
|
||||
customButtonClassName="flex w-full items-center justify-between gap-1 rounded-md px-2.5 py-2 text-sm border-[0.5px] border-custom-border-300"
|
||||
customButton={
|
||||
<>
|
||||
<span
|
||||
className={`text-sm ${
|
||||
!getValues(`emails.${index}.role_active`) ? "text-custom-text-400" : "text-custom-text-100"
|
||||
} sm:text-sm`}
|
||||
>
|
||||
{ROLE[value]}
|
||||
</span>
|
||||
<ChevronDown
|
||||
className={`size-3 ${
|
||||
!getValues(`emails.${index}.role_active`)
|
||||
? "stroke-onboarding-text-400"
|
||||
: "stroke-onboarding-text-100"
|
||||
}`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Listbox.Button
|
||||
type="button"
|
||||
ref={setReferenceElement}
|
||||
className="flex w-full items-center justify-between gap-1 rounded-md px-2.5 py-2 text-sm border-[0.5px] border-custom-border-300"
|
||||
>
|
||||
<span
|
||||
className={`text-sm ${
|
||||
!getValues(`emails.${index}.role_active`) ? "text-custom-text-400" : "text-custom-text-100"
|
||||
} sm:text-sm`}
|
||||
{Object.entries(ROLE_DETAILS).map(([key, value]) => (
|
||||
<CustomSelect.Option
|
||||
key={key}
|
||||
value={parseInt(key)}
|
||||
className={"cursor-pointer select-none truncate rounded px-1 py-1.5"}
|
||||
>
|
||||
{ROLE[value]}
|
||||
</span>
|
||||
|
||||
<ChevronDown
|
||||
className={`size-3 ${
|
||||
!getValues(`emails.${index}.role_active`)
|
||||
? "stroke-onboarding-text-400"
|
||||
: "stroke-onboarding-text-100"
|
||||
}`}
|
||||
/>
|
||||
</Listbox.Button>
|
||||
|
||||
<Listbox.Options as="div">
|
||||
<div
|
||||
className="p-2 absolute space-y-1 z-10 mt-1 h-fit w-48 sm:w-60 rounded-md border border-custom-border-300 bg-custom-background-100 shadow-sm focus:outline-none"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
{Object.entries(ROLE_DETAILS).map(([key, value]) => (
|
||||
<Listbox.Option
|
||||
as="div"
|
||||
key={key}
|
||||
value={parseInt(key)}
|
||||
className={({ active, selected }) =>
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||
active || selected ? "bg-onboarding-background-400/40" : ""
|
||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<div className="flex items-center text-wrap gap-2 p-1">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">{t(value.i18n_title)}</div>
|
||||
<div className="flex text-xs text-custom-text-300">{t(value.i18n_description)}</div>
|
||||
</div>
|
||||
{selected && <Check className="h-4 w-4 shrink-0" />}
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Listbox>
|
||||
<div className="flex items-center text-wrap gap-2 p-1">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">{t(value.i18n_title)}</div>
|
||||
<div className="flex text-xs text-custom-text-300">{t(value.i18n_description)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -15,15 +15,14 @@ import {
|
||||
} from "react-hook-form";
|
||||
// icons
|
||||
import { usePopper } from "react-popper";
|
||||
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
import { ChevronDown, Plus, XCircle } from "lucide-react";
|
||||
// plane imports
|
||||
import { ROLE, ROLE_DETAILS, EUserPermissions, MEMBER_TRACKER_EVENTS, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { EOnboardingSteps, IWorkspace } from "@plane/types";
|
||||
// ui
|
||||
import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
// helpers
|
||||
|
||||
@@ -174,69 +173,50 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = observer((props) => {
|
||||
name={`emails.${index}.role`}
|
||||
rules={{ required: true }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Listbox
|
||||
as="div"
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
onChange={(val: any) => {
|
||||
onChange(val);
|
||||
setValue(`emails.${index}.role_active`, true);
|
||||
}}
|
||||
className="w-full flex-shrink-0 text-left"
|
||||
customButtonClassName="flex w-full items-center justify-between gap-1 rounded-md px-2.5 py-2 text-sm border-[0.5px] border-custom-border-300"
|
||||
optionsClassName="p-2 absolute space-y-1 z-10 mt-1 h-fit w-48 sm:w-60 rounded-md border border-custom-border-300 bg-custom-background-100 shadow-sm focus:outline-none"
|
||||
customButton={
|
||||
<>
|
||||
<span
|
||||
className={`text-sm ${
|
||||
!getValues(`emails.${index}.role_active`) ? "text-custom-text-400" : "text-custom-text-100"
|
||||
} sm:text-sm`}
|
||||
>
|
||||
{ROLE[value]}
|
||||
</span>
|
||||
|
||||
<ChevronDown
|
||||
className={`size-3 ${
|
||||
!getValues(`emails.${index}.role_active`)
|
||||
? "stroke-onboarding-text-400"
|
||||
: "stroke-onboarding-text-100"
|
||||
}`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Listbox.Button
|
||||
type="button"
|
||||
ref={setReferenceElement}
|
||||
className="flex w-full items-center justify-between gap-1 rounded-md px-2.5 py-2 text-sm border-[0.5px] border-custom-border-300"
|
||||
>
|
||||
<span
|
||||
className={`text-sm ${
|
||||
!getValues(`emails.${index}.role_active`) ? "text-custom-text-400" : "text-custom-text-100"
|
||||
} sm:text-sm`}
|
||||
{Object.entries(ROLE_DETAILS).map(([key, value]) => (
|
||||
<CustomSelect.Option
|
||||
key={key}
|
||||
value={parseInt(key)}
|
||||
className={"cursor-pointer select-none truncate rounded px-1 py-1.5"}
|
||||
>
|
||||
{ROLE[value]}
|
||||
</span>
|
||||
|
||||
<ChevronDown
|
||||
className={`size-3 ${
|
||||
!getValues(`emails.${index}.role_active`)
|
||||
? "stroke-onboarding-text-400"
|
||||
: "stroke-onboarding-text-100"
|
||||
}`}
|
||||
/>
|
||||
</Listbox.Button>
|
||||
|
||||
<Listbox.Options as="div">
|
||||
<div
|
||||
className="p-2 absolute space-y-1 z-10 mt-1 h-fit w-48 sm:w-60 rounded-md border border-custom-border-300 bg-custom-background-100 shadow-sm focus:outline-none"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
{Object.entries(ROLE_DETAILS).map(([key, value]) => (
|
||||
<Listbox.Option
|
||||
as="div"
|
||||
key={key}
|
||||
value={parseInt(key)}
|
||||
className={({ active, selected }) =>
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||
active || selected ? "bg-onboarding-background-400/40" : ""
|
||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<div className="flex items-center text-wrap gap-2 p-1">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">{t(value.i18n_title)}</div>
|
||||
<div className="flex text-xs text-custom-text-300">{t(value.i18n_description)}</div>
|
||||
</div>
|
||||
{selected && <Check className="h-4 w-4 shrink-0" />}
|
||||
</div>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Listbox>
|
||||
<div className="flex items-center text-wrap gap-2 p-1">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">{t(value.i18n_title)}</div>
|
||||
<div className="flex text-xs text-custom-text-300">{t(value.i18n_description)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -124,7 +124,6 @@ export const LanguageTimezone = observer(() => {
|
||||
value={profile?.language}
|
||||
label={profile?.language ? getLanguageLabel(profile?.language) : "Select a language"}
|
||||
onChange={handleLanguageChange}
|
||||
buttonClassName={"border-none"}
|
||||
className="rounded-md border !border-custom-border-200"
|
||||
optionsClassName="w-full"
|
||||
input
|
||||
|
||||
Reference in New Issue
Block a user