mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: estimate dropdown handled in issues
This commit is contained in:
parent
78db7fe03f
commit
2adb18f636
2
packages/types/src/issues/issue.d.ts
vendored
2
packages/types/src/issues/issue.d.ts
vendored
@ -15,7 +15,7 @@ export type TIssue = {
|
||||
priority: TIssuePriorities;
|
||||
label_ids: string[];
|
||||
assignee_ids: string[];
|
||||
estimate_point: number | null;
|
||||
estimate_point: string | null;
|
||||
|
||||
sub_issues_count: number;
|
||||
attachment_count: number;
|
||||
|
@ -5,7 +5,7 @@ import { Controller, useForm } from "react-hook-form"; // services
|
||||
import { usePopper } from "react-popper";
|
||||
// ui
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { Popover, PopoverButton, PopoverPanel, Transition } from "@headlessui/react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { RichTextReadOnlyEditor } from "@/components/editor/rich-text-editor/rich-text-read-only-editor";
|
||||
// icons
|
||||
@ -178,9 +178,9 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<Popover as="div" className={`relative w-min text-left`}>
|
||||
<PopoverButton as={Fragment}>
|
||||
<Popover.Button as={Fragment}>
|
||||
<button ref={setReferenceElement}>{button}</button>
|
||||
</PopoverButton>
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
show={isOpen}
|
||||
as={React.Fragment}
|
||||
@ -191,7 +191,7 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<PopoverPanel
|
||||
<Popover.Panel
|
||||
as="div"
|
||||
className={`fixed z-10 flex w-full min-w-[50rem] max-w-full flex-col space-y-4 overflow-hidden rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 shadow ${className}`}
|
||||
ref={setPopperElement as Ref<HTMLDivElement>}
|
||||
@ -261,7 +261,7 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Fragment, ReactNode, useRef, useState } from "react";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Check, ChevronDown, Search, Triangle } from "lucide-react";
|
||||
@ -9,6 +8,8 @@ import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import {
|
||||
useAppRouter,
|
||||
useEstimate,
|
||||
useProjectEstimates,
|
||||
// useEstimate
|
||||
} from "@/hooks/store";
|
||||
import { useDropdown } from "@/hooks/use-dropdown";
|
||||
@ -22,15 +23,15 @@ type Props = TDropdownProps & {
|
||||
button?: ReactNode;
|
||||
dropdownArrow?: boolean;
|
||||
dropdownArrowClassName?: string;
|
||||
onChange: (val: number | null) => void;
|
||||
onChange: (val: string | undefined) => void;
|
||||
onClose?: () => void;
|
||||
projectId: string;
|
||||
value: number | null;
|
||||
value: string | undefined;
|
||||
};
|
||||
|
||||
type DropdownOptions =
|
||||
| {
|
||||
value: number | null;
|
||||
value: string | null;
|
||||
query: string;
|
||||
content: JSX.Element;
|
||||
}[]
|
||||
@ -79,23 +80,29 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
});
|
||||
// store hooks
|
||||
const { workspaceSlug } = useAppRouter();
|
||||
console.log("workspaceSlug", workspaceSlug);
|
||||
console.log("projectId", projectId);
|
||||
|
||||
// const { fetchProjectEstimates, getProjectActiveEstimateDetails, getEstimatePointValue } = useEstimate();
|
||||
// const activeEstimate = getProjectActiveEstimateDetails(projectId);
|
||||
const activeEstimate: any = undefined;
|
||||
const { currentActiveEstimateId, getProjectEstimates } = useProjectEstimates();
|
||||
const { estimatePointIds, estimatePointById } = useEstimate(
|
||||
currentActiveEstimateId ? currentActiveEstimateId : undefined
|
||||
);
|
||||
|
||||
const options: DropdownOptions = sortBy(activeEstimate?.points ?? [], "key")?.map((point) => ({
|
||||
value: point.key,
|
||||
query: `${point?.value}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Triangle className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="flex-grow truncate">{point.value}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
const options: DropdownOptions = (estimatePointIds ?? [])
|
||||
?.map((estimatePoint) => {
|
||||
const currentEstimatePoint = estimatePointById(estimatePoint);
|
||||
if (currentEstimatePoint)
|
||||
return {
|
||||
value: currentEstimatePoint.id,
|
||||
query: `${currentEstimatePoint?.value}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Triangle className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="flex-grow truncate">{currentEstimatePoint.value}</span>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
else undefined;
|
||||
})
|
||||
.filter((estimatePointDropdownOption) => estimatePointDropdownOption != undefined) as DropdownOptions;
|
||||
options?.unshift({
|
||||
value: null,
|
||||
query: "No estimate",
|
||||
@ -110,14 +117,10 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
const filteredOptions =
|
||||
query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
|
||||
|
||||
const selectedEstimate =
|
||||
value !== null
|
||||
? // getEstimatePointValue(value, projectId)
|
||||
null
|
||||
: null;
|
||||
const selectedEstimate = value && estimatePointById ? estimatePointById(value) : undefined;
|
||||
|
||||
const onOpen = async () => {
|
||||
// if (!activeEstimate && workspaceSlug) await fetchProjectEstimates(workspaceSlug, projectId);
|
||||
if (!currentActiveEstimateId && workspaceSlug) await getProjectEstimates(workspaceSlug, projectId);
|
||||
};
|
||||
|
||||
const { handleClose, handleKeyDown, handleOnClick, searchInputKeyDown } = useDropdown({
|
||||
@ -131,7 +134,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
setQuery,
|
||||
});
|
||||
|
||||
const dropdownOnChange = (val: number | null) => {
|
||||
const dropdownOnChange = (val: string | undefined) => {
|
||||
onChange(val);
|
||||
handleClose();
|
||||
};
|
||||
@ -175,13 +178,13 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
className={buttonClassName}
|
||||
isActive={isOpen}
|
||||
tooltipHeading="Estimate"
|
||||
tooltipContent={selectedEstimate !== null ? selectedEstimate : placeholder}
|
||||
tooltipContent={selectedEstimate ? selectedEstimate?.value : placeholder}
|
||||
showTooltip={showTooltip}
|
||||
variant={buttonVariant}
|
||||
>
|
||||
{!hideIcon && <Triangle className="h-3 w-3 flex-shrink-0" />}
|
||||
{(selectedEstimate || placeholder) && BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||
<span className="flex-grow truncate">{selectedEstimate !== null ? selectedEstimate : placeholder}</span>
|
||||
<span className="flex-grow truncate">{selectedEstimate ? selectedEstimate?.value : placeholder}</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
||||
@ -215,20 +218,14 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option) => (
|
||||
<Combobox.Option
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={({ active, selected }) =>
|
||||
`flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 ${
|
||||
active ? "bg-custom-background-80" : ""
|
||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<Combobox.Option key={option.value} value={option.value}>
|
||||
{({ active, selected }) => (
|
||||
<div
|
||||
className={`flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 ${active ? `!hover:bg-custom-background-80` : ``} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`}
|
||||
>
|
||||
<span className="flex-grow truncate">{option.content}</span>
|
||||
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
))
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Ref, useState } from "react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { Popover } from "@headlessui/react";
|
||||
// popper
|
||||
// helper
|
||||
import { getButtonStyling } from "@plane/ui";
|
||||
@ -42,7 +42,7 @@ export const ComicBoxButton: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverButton ref={setReferenceElement} onClick={onClick} disabled={disabled}>
|
||||
<Popover.Button ref={setReferenceElement} onClick={onClick} disabled={disabled}>
|
||||
<div className={`flex items-center gap-2.5 ${getButtonStyling("primary", "lg", disabled)}`}>
|
||||
{icon}
|
||||
<span className="leading-4">{label}</span>
|
||||
@ -55,9 +55,9 @@ export const ComicBoxButton: React.FC<Props> = (props) => {
|
||||
<div className={`absolute bg-blue-400/40 right-0 h-1.5 w-1.5 mt-0.5 mr-0.5 rounded-full`} />
|
||||
</span>
|
||||
</div>
|
||||
</PopoverButton>
|
||||
</Popover.Button>
|
||||
{isHovered && (
|
||||
<PopoverPanel
|
||||
<Popover.Panel
|
||||
as="div"
|
||||
className="flex flex-col rounded border border-custom-border-200 bg-custom-background-100 p-5 relative min-w-80"
|
||||
ref={setPopperElement as Ref<HTMLDivElement>}
|
||||
@ -68,7 +68,7 @@ export const ComicBoxButton: React.FC<Props> = (props) => {
|
||||
<div className="absolute w-2 h-2 bg-custom-background-100 border rounded-lb-sm border-custom-border-200 border-r-0 border-t-0 transform rotate-45 bottom-2 -left-[5px]" />
|
||||
<h3 className="text-lg font-semibold w-full">{title}</h3>
|
||||
<h4 className="mt-1 text-sm">{description}</h4>
|
||||
</PopoverPanel>
|
||||
</Popover.Panel>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC, useRef, Fragment, useState } from "react";
|
||||
import { Info, Check, ChevronDown } from "lucide-react";
|
||||
import { Listbox, ListboxButton, ListboxOptions, Transition, ListboxOption } from "@headlessui/react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { TEstimatePointsObject } from "@plane/types";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
@ -51,7 +51,7 @@ export const EstimatePointDropdown: FC<TEstimatePointDropdown> = (props) => {
|
||||
}}
|
||||
className="w-full flex-shrink-0 text-left"
|
||||
>
|
||||
<ListboxButton
|
||||
<Listbox.Button
|
||||
type="button"
|
||||
ref={buttonRef}
|
||||
onClick={() => setIsDropdownOpen((prev) => !prev)}
|
||||
@ -75,7 +75,7 @@ export const EstimatePointDropdown: FC<TEstimatePointDropdown> = (props) => {
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</ListboxButton>
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
show={isDropdownOpen}
|
||||
as={Fragment}
|
||||
@ -86,12 +86,12 @@ export const EstimatePointDropdown: FC<TEstimatePointDropdown> = (props) => {
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<ListboxOptions
|
||||
<Listbox.Options
|
||||
ref={dropdownRef}
|
||||
className="fixed z-10 mt-1 h-fit w-48 sm:w-60 overflow-y-auto rounded-md border border-custom-border-200 bg-custom-background-100 shadow-sm focus:outline-none"
|
||||
>
|
||||
<div className="p-1.5">
|
||||
<ListboxOption
|
||||
<Listbox.Option
|
||||
value={"none"}
|
||||
className={cn(
|
||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 hover:bg-custom-background-90`,
|
||||
@ -102,9 +102,9 @@ export const EstimatePointDropdown: FC<TEstimatePointDropdown> = (props) => {
|
||||
<div className="text-sm font-medium w-full line-clamp-1">None</div>
|
||||
{selectedOption === "none" && <Check size={12} />}
|
||||
</div>
|
||||
</ListboxOption>
|
||||
</Listbox.Option>
|
||||
{options.map((option) => (
|
||||
<ListboxOption
|
||||
<Listbox.Option
|
||||
key={option?.key}
|
||||
value={option?.id}
|
||||
className={cn(
|
||||
@ -116,10 +116,10 @@ export const EstimatePointDropdown: FC<TEstimatePointDropdown> = (props) => {
|
||||
<div className="text-sm font-medium w-full line-clamp-1">{option.value}</div>
|
||||
{selectedOption === option?.id && <Check size={12} />}
|
||||
</div>
|
||||
</ListboxOption>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</div>
|
||||
</ListboxOptions>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</Listbox>
|
||||
</div>
|
||||
|
@ -69,7 +69,7 @@ export const EstimatePointUpdate: FC<TEstimatePointUpdate> = observer((props) =>
|
||||
let isEstimateValid = false;
|
||||
|
||||
const currentEstimatePointValues = estimatePoints
|
||||
.map((point) => point?.value || undefined)
|
||||
.map((point) => (point?.id != estimatePoint?.id ? point?.value : undefined))
|
||||
.filter((value) => value != undefined) as string[];
|
||||
const isRepeated =
|
||||
(estimateType && isEstimatePointValuesRepeated(currentEstimatePointValues, estimateType, estimateInputValue)) ||
|
||||
|
@ -142,10 +142,10 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
||||
)}
|
||||
|
||||
{/* estimate */}
|
||||
{isVisible && areEstimateEnabledByProjectId(projectId) && (
|
||||
{isVisible && projectId && areEstimateEnabledByProjectId(projectId) && (
|
||||
<div className="h-7">
|
||||
<EstimateDropdown
|
||||
value={data?.estimate_point || null}
|
||||
value={data?.estimate_point || undefined}
|
||||
onChange={(estimatePoint) => handleData("estimate_point", estimatePoint)}
|
||||
projectId={projectId}
|
||||
buttonVariant="border-with-text"
|
||||
|
@ -54,13 +54,7 @@ import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"
|
||||
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
// types
|
||||
import {
|
||||
// useEstimate,
|
||||
useIssueDetail,
|
||||
useProject,
|
||||
useProjectState,
|
||||
useUser,
|
||||
} from "@/hooks/store";
|
||||
import { useProjectEstimates, useIssueDetail, useProject, useProjectState, useUser } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// components
|
||||
import type { TIssueOperations } from "./root";
|
||||
@ -88,8 +82,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { data: currentUser } = useUser();
|
||||
// const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
||||
const areEstimatesEnabledForCurrentProject = false;
|
||||
const { areEstimateEnabledByProjectId } = useProjectEstimates();
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
@ -319,15 +312,17 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{areEstimatesEnabledForCurrentProject && (
|
||||
{projectId && areEstimateEnabledByProjectId(projectId) && (
|
||||
<div className="flex h-8 items-center gap-2">
|
||||
<div className="flex w-2/5 flex-shrink-0 items-center gap-1 text-sm text-custom-text-300">
|
||||
<Triangle className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Estimate</span>
|
||||
</div>
|
||||
<EstimateDropdown
|
||||
value={issue?.estimate_point !== null ? issue.estimate_point : null}
|
||||
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })}
|
||||
value={issue?.estimate_point ?? undefined}
|
||||
onChange={(val: string | undefined) =>
|
||||
issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })
|
||||
}
|
||||
projectId={projectId}
|
||||
disabled={!isEditable}
|
||||
buttonVariant="transparent-with-text"
|
||||
|
@ -220,7 +220,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleEstimate = (value: number | null) => {
|
||||
const handleEstimate = (value: string | undefined) => {
|
||||
updateIssue &&
|
||||
updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => {
|
||||
captureIssueEvent({
|
||||
@ -398,7 +398,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="estimate">
|
||||
<div className="h-5" onClick={handleEventPropagation}>
|
||||
<EstimateDropdown
|
||||
value={issue.estimate_point}
|
||||
value={issue.estimate_point ?? undefined}
|
||||
onChange={handleEstimate}
|
||||
projectId={issue.project_id}
|
||||
disabled={isReadOnly}
|
||||
|
@ -17,7 +17,7 @@ export const SpreadsheetEstimateColumn: React.FC<Props> = observer((props: Props
|
||||
return (
|
||||
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
||||
<EstimateDropdown
|
||||
value={issue.estimate_point}
|
||||
value={issue.estimate_point || undefined}
|
||||
onChange={(data) =>
|
||||
onChange(issue, { estimate_point: data }, { changed_property: "estimate_point", change_details: data })
|
||||
}
|
||||
|
@ -673,7 +673,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="h-7">
|
||||
<EstimateDropdown
|
||||
value={value}
|
||||
value={value || undefined}
|
||||
onChange={(estimatePoint) => {
|
||||
onChange(estimatePoint);
|
||||
handleFormChange();
|
||||
|
@ -197,14 +197,14 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
<span>Estimate</span>
|
||||
</div>
|
||||
<EstimateDropdown
|
||||
value={issue?.estimate_point !== null ? issue.estimate_point : null}
|
||||
value={issue.estimate_point ?? undefined}
|
||||
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })}
|
||||
projectId={projectId}
|
||||
disabled={disabled}
|
||||
buttonVariant="transparent-with-text"
|
||||
className="w-3/4 flex-grow group"
|
||||
buttonContainerClassName="w-full text-left"
|
||||
buttonClassName={`text-sm ${issue?.estimate_point !== null ? "" : "text-custom-text-400"}`}
|
||||
buttonClassName={`text-sm ${issue?.estimate_point !== undefined ? "" : "text-custom-text-400"}`}
|
||||
placeholder="None"
|
||||
hideIcon
|
||||
dropdownArrow
|
||||
|
@ -1,64 +0,0 @@
|
||||
import React from "react";
|
||||
import { Field, Label, Radio, RadioGroup } from "@headlessui/react";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
type RadioInputProps = {
|
||||
label: string | React.ReactNode | undefined;
|
||||
labelClassName?: string;
|
||||
ariaLabel?: string;
|
||||
options: { label: string; value: string; disabled?: boolean }[];
|
||||
vertical?: boolean;
|
||||
selected: string;
|
||||
onChange: (value: string) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const RadioInput = ({
|
||||
label: inputLabel,
|
||||
labelClassName: inputLabelClassName,
|
||||
options,
|
||||
vertical,
|
||||
selected,
|
||||
ariaLabel,
|
||||
onChange,
|
||||
className,
|
||||
}: RadioInputProps) => {
|
||||
const wrapperClass = vertical ? "flex flex-col gap-1" : "flex gap-2";
|
||||
|
||||
const setSelected = (value: string) => {
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
let aria = ariaLabel ? ariaLabel.toLowerCase().replace(" ", "-") : "";
|
||||
if (!aria && typeof inputLabel === "string") {
|
||||
aria = inputLabel.toLowerCase().replace(" ", "-");
|
||||
} else {
|
||||
aria = "radio-input";
|
||||
}
|
||||
|
||||
return (
|
||||
<RadioGroup value={selected} onChange={setSelected} aria-label={aria} className={className}>
|
||||
<Label className={cn(`mb-2`, inputLabelClassName)}>{inputLabel}</Label>
|
||||
<div className={`${wrapperClass}`}>
|
||||
{options.map(({ value, label, disabled }) => (
|
||||
<Field key={label} className="flex items-center gap-2">
|
||||
<Radio
|
||||
value={value}
|
||||
className="group flex size-5 items-center justify-center rounded-full border border-custom-border-400 bg-custom-background-500 data-[checked]:bg-custom-primary-200 data-[checked]:border-custom-primary-100 cursor-pointer
|
||||
data-[disabled]:bg-custom-background-200
|
||||
data-[disabled]:border-custom-border-200
|
||||
data-[disabled]:cursor-not-allowed"
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className="invisible size-2 rounded-full bg-white group-data-[checked]:visible" />
|
||||
</Radio>
|
||||
<Label className="text-base cursor-pointer">{label}</Label>
|
||||
</Field>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export { RadioInput };
|
@ -6,7 +6,7 @@ import { usePopper } from "react-popper";
|
||||
// icons
|
||||
import { Activity, Check, ChevronDown, LogOut, Mails, PlusSquare, Settings } from "lucide-react";
|
||||
// ui
|
||||
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from "@headlessui/react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// types
|
||||
import { IWorkspace } from "@plane/types";
|
||||
// plane ui
|
||||
@ -104,7 +104,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
<Menu as="div" className="relative h-full flex-grow truncate text-left">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<MenuButton className="group/menu-button h-full w-full truncate rounded-md text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:outline-none">
|
||||
<Menu.Button className="group/menu-button h-full w-full truncate rounded-md text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:outline-none">
|
||||
<div
|
||||
className={`flex items-center gap-x-2 truncate rounded p-1 ${
|
||||
sidebarCollapsed ? "justify-center" : "justify-between"
|
||||
@ -126,7 +126,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</MenuButton>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
@ -136,7 +136,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems as={Fragment}>
|
||||
<Menu.Items as={Fragment}>
|
||||
<div className="fixed left-4 z-20 mt-1 flex w-full max-w-[19rem] origin-top-left flex-col divide-y divide-custom-border-100 rounded-md border-[0.5px] border-custom-sidebar-border-300 bg-custom-sidebar-background-100 shadow-custom-shadow-rg outline-none">
|
||||
<div className="vertical-scrollbar scrollbar-sm mb-2 flex max-h-96 flex-col items-start justify-start gap-2 overflow-y-scroll px-4">
|
||||
<h6 className="sticky top-0 z-10 h-full w-full bg-custom-sidebar-background-100 pb-1 pt-3 text-sm font-medium text-custom-sidebar-text-400">
|
||||
@ -155,7 +155,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<MenuItem
|
||||
<Menu.Item
|
||||
as="div"
|
||||
className="flex items-center justify-between gap-1 rounded p-1 text-sm text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80"
|
||||
>
|
||||
@ -188,7 +188,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
<Check className="h-5 w-5 text-custom-sidebar-text-100" />
|
||||
</span>
|
||||
)}
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
@ -203,13 +203,13 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2 px-4 py-2 text-sm">
|
||||
<Link href="/create-workspace" className="w-full">
|
||||
<MenuItem
|
||||
<Menu.Item
|
||||
as="div"
|
||||
className="flex items-center gap-2 rounded px-2 py-1 text-sm font-medium text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80"
|
||||
>
|
||||
<PlusSquare strokeWidth={1.75} className="h-4 w-4 flex-shrink-0" />
|
||||
Create workspace
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
{userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
|
||||
<Link
|
||||
@ -220,18 +220,18 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
if (index > 0) handleItemClick();
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
<Menu.Item
|
||||
as="div"
|
||||
className="flex items-center gap-2 rounded px-2 py-1 text-sm font-medium text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||
>
|
||||
<link.icon className="h-4 w-4 flex-shrink-0" />
|
||||
{link.name}
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-full px-4 py-2">
|
||||
<MenuItem
|
||||
<Menu.Item
|
||||
as="button"
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 rounded px-2 py-1 text-sm font-medium text-red-600 hover:bg-custom-sidebar-background-80"
|
||||
@ -239,17 +239,17 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
>
|
||||
<LogOut className="h-4 w-4 flex-shrink-0" />
|
||||
Sign out
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
{!sidebarCollapsed && (
|
||||
<Menu as="div" className="relative flex-shrink-0">
|
||||
<MenuButton className="grid place-items-center outline-none" ref={setReferenceElement}>
|
||||
<Menu.Button className="grid place-items-center outline-none" ref={setReferenceElement}>
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={currentUser?.avatar || undefined}
|
||||
@ -257,7 +257,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
shape="square"
|
||||
className="!text-base"
|
||||
/>
|
||||
</MenuButton>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
@ -267,7 +267,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<MenuItems
|
||||
<Menu.Items
|
||||
className="absolute left-0 z-20 mt-1 flex w-52 origin-top-left flex-col divide-y
|
||||
divide-custom-sidebar-border-200 rounded-md border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 text-xs shadow-lg outline-none"
|
||||
ref={setPopperElement as Ref<HTMLDivElement>}
|
||||
@ -284,17 +284,17 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
if (index == 0) handleItemClick();
|
||||
}}
|
||||
>
|
||||
<MenuItem key={index} as="div">
|
||||
<Menu.Item key={index} as="div">
|
||||
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
|
||||
<link.icon className="h-4 w-4 stroke-[1.5]" />
|
||||
{link.name}
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className={`pt-2 ${isUserInstanceAdmin || false ? "pb-2" : ""}`}>
|
||||
<MenuItem
|
||||
<Menu.Item
|
||||
as="button"
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80"
|
||||
@ -302,20 +302,20 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
>
|
||||
<LogOut className="h-4 w-4 stroke-[1.5]" />
|
||||
Sign out
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
{isUserInstanceAdmin && (
|
||||
<div className="p-2 pb-0">
|
||||
<Link href={GOD_MODE_URL}>
|
||||
<MenuItem as="button" type="button" className="w-full">
|
||||
<Menu.Item as="button" type="button" className="w-full">
|
||||
<span className="flex w-full items-center justify-center rounded bg-custom-primary-100/20 px-2 py-1 text-sm font-medium text-custom-primary-100 hover:bg-custom-primary-100/30 hover:text-custom-primary-200">
|
||||
Enter God Mode
|
||||
</span>
|
||||
</MenuItem>
|
||||
</Menu.Item>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</MenuItems>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
)}
|
||||
|
@ -16,7 +16,7 @@
|
||||
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^1.3.0",
|
||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
||||
"@blueprintjs/popover2": "^1.13.3",
|
||||
"@headlessui/react": "^2.0.4",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@nivo/bar": "0.80.0",
|
||||
"@nivo/calendar": "0.80.0",
|
||||
"@nivo/core": "0.80.0",
|
||||
|
@ -1592,7 +1592,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5"
|
||||
integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==
|
||||
|
||||
"@headlessui/react@^1.7.13", "@headlessui/react@^1.7.19":
|
||||
"@headlessui/react@^1.7.13", "@headlessui/react@^1.7.19", "@headlessui/react@^1.7.3":
|
||||
version "1.7.19"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.19.tgz#91c78cf5fcb254f4a0ebe96936d48421caf75f40"
|
||||
integrity sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==
|
||||
@ -1600,7 +1600,7 @@
|
||||
"@tanstack/react-virtual" "^3.0.0-beta.60"
|
||||
client-only "^0.0.1"
|
||||
|
||||
"@headlessui/react@^2.0.3", "@headlessui/react@^2.0.4":
|
||||
"@headlessui/react@^2.0.3":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.0.4.tgz#46cb39ca9dde3c2d15f4706c81dad78405b608f0"
|
||||
integrity sha512-16d/rOLeYsFsmPlRmXGu8DCBzrWD0zV1Ccx3n73wN87yFu8Y9+X04zflv8EJEt9TAYRyLKOmQXUnOnqQl6NgpA==
|
||||
|
Loading…
Reference in New Issue
Block a user