АРХ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: канонизация labels, editor и ui dropdown-оберток
This commit is contained in:
parent
5cf2c2130a
commit
bc8081c0f1
|
|
@ -21,7 +21,7 @@ export type TSelectionDropdownOption = {
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
menuButton: ReactNode;
|
menuButton: ReactNode | ((props: { open: boolean }) => ReactNode);
|
||||||
menuButtonWrapperClassName?: string;
|
menuButtonWrapperClassName?: string;
|
||||||
options: TSelectionDropdownOption[];
|
options: TSelectionDropdownOption[];
|
||||||
placement?: Placement;
|
placement?: Placement;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { observer } from "mobx-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import type { TDescriptionVersion } from "@plane/types";
|
import type { TDescriptionVersion } from "@plane/types";
|
||||||
import { Avatar, CustomMenu } from "@plane/ui";
|
import { Avatar } from "@plane/ui";
|
||||||
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember } from "@/hooks/store/use-member";
|
import { useMember } from "@/hooks/store/use-member";
|
||||||
|
|
@ -28,7 +28,7 @@ export const DescriptionVersionsDropdownItem = observer(function DescriptionVers
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu.MenuItem key={version.id} className="flex items-center gap-1" onClick={() => onClick(version.id)}>
|
<button type="button" className="flex items-center gap-1" onClick={() => onClick(version.id)}>
|
||||||
<span className="flex-shrink-0">
|
<span className="flex-shrink-0">
|
||||||
<Avatar
|
<Avatar
|
||||||
name={versionCreator?.display_name ?? t("common.deactivated_user")}
|
name={versionCreator?.display_name ?? t("common.deactivated_user")}
|
||||||
|
|
@ -40,6 +40,6 @@ export const DescriptionVersionsDropdownItem = observer(function DescriptionVers
|
||||||
<span className="font-medium">{versionCreator?.display_name ?? t("common.deactivated_user")}</span>
|
<span className="font-medium">{versionCreator?.display_name ?? t("common.deactivated_user")}</span>
|
||||||
<span>{calculateTimeAgo(version.last_saved_at)}</span>
|
<span>{calculateTimeAgo(version.last_saved_at)}</span>
|
||||||
</p>
|
</p>
|
||||||
</CustomMenu.MenuItem>
|
</button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,11 @@ import { History } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import type { TDescriptionVersion } from "@plane/types";
|
import type { TDescriptionVersion } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { Avatar } from "@plane/ui";
|
||||||
import { calculateTimeAgo } from "@plane/utils";
|
import { calculateTimeAgo, cn, getFileURL } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember } from "@/hooks/store/use-member";
|
import { useMember } from "@/hooks/store/use-member";
|
||||||
// local imports
|
import { SelectionDropdown } from "@/components/common/selection-dropdown";
|
||||||
import { DescriptionVersionsDropdownItem } from "./dropdown-item";
|
|
||||||
import type { TDescriptionVersionEntityInformation } from "./root";
|
import type { TDescriptionVersionEntityInformation } from "./root";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -34,13 +33,21 @@ export const DescriptionVersionsDropdown = observer(function DescriptionVersions
|
||||||
const lastUpdatedByUserDisplayName = latestVersion?.owned_by
|
const lastUpdatedByUserDisplayName = latestVersion?.owned_by
|
||||||
? getUserDetails(latestVersion?.owned_by)?.display_name
|
? getUserDetails(latestVersion?.owned_by)?.display_name
|
||||||
: entityInformation.createdByDisplayName;
|
: entityInformation.createdByDisplayName;
|
||||||
|
const latestVersionId = latestVersion?.id;
|
||||||
// translation
|
// translation
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu
|
<SelectionDropdown
|
||||||
label={
|
disabled={disabled}
|
||||||
<div className="flex items-center gap-1 text-tertiary">
|
placement="bottom-end"
|
||||||
|
title={t("description_versions.previously_edited_by")}
|
||||||
|
menuButton={({ open }) => (
|
||||||
|
<div
|
||||||
|
className={cn("flex items-center gap-1 text-tertiary transition-colors", {
|
||||||
|
"text-primary": open,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<span className="grid size-4 flex-shrink-0 place-items-center">
|
<span className="grid size-4 flex-shrink-0 place-items-center">
|
||||||
<History className="size-3.5" />
|
<History className="size-3.5" />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -50,18 +57,32 @@ export const DescriptionVersionsDropdown = observer(function DescriptionVersions
|
||||||
{calculateTimeAgo(lastUpdatedAt)}
|
{calculateTimeAgo(lastUpdatedAt)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
menuButtonWrapperClassName="rounded-sm"
|
||||||
|
options={
|
||||||
|
versions?.map((version) => {
|
||||||
|
const versionCreator = version.owned_by ? getUserDetails(version.owned_by) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: version.id,
|
||||||
|
isChecked: version.id === latestVersionId,
|
||||||
|
onClick: () => onVersionClick(version.id),
|
||||||
|
icon: (
|
||||||
|
<Avatar
|
||||||
|
name={versionCreator?.display_name ?? t("common.deactivated_user")}
|
||||||
|
size="sm"
|
||||||
|
src={getFileURL(versionCreator?.avatar_url ?? "")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
title: (
|
||||||
|
<p className="flex items-center gap-1.5 text-11 text-secondary">
|
||||||
|
<span className="font-medium">{versionCreator?.display_name ?? t("common.deactivated_user")}</span>
|
||||||
|
<span>{calculateTimeAgo(version.last_saved_at)}</span>
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}) ?? []
|
||||||
}
|
}
|
||||||
noBorder
|
/>
|
||||||
noChevron={disabled}
|
|
||||||
placement="bottom-end"
|
|
||||||
optionsClassName="w-[300px]"
|
|
||||||
disabled={disabled}
|
|
||||||
closeOnSelect
|
|
||||||
>
|
|
||||||
<p className="mb-1 text-11 font-medium text-tertiary">{t("description_versions.previously_edited_by")}</p>
|
|
||||||
{versions?.map((version) => (
|
|
||||||
<DescriptionVersionsDropdownItem key={version.id} onClick={onVersionClick} version={version} />
|
|
||||||
))}
|
|
||||||
</CustomMenu>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ type Props = {
|
||||||
placement?: Placement;
|
placement?: Placement;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
menuButton?: React.ReactNode;
|
menuButton?: React.ReactNode | ((props: { open: boolean }) => React.ReactNode);
|
||||||
menuButtonWrapperClassName?: string;
|
menuButtonWrapperClassName?: string;
|
||||||
isFiltersApplied?: boolean;
|
isFiltersApplied?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -58,7 +58,7 @@ export function FiltersDropdown(props: Props) {
|
||||||
className={menuButtonWrapperClassName}
|
className={menuButtonWrapperClassName}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{menuButton}
|
{typeof menuButton === "function" ? menuButton({ open }) : menuButton}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div ref={setReferenceElement}>
|
<div ref={setReferenceElement}>
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { MutableRefObject } from "react";
|
import type { MutableRefObject } from "react";
|
||||||
import { useRef, useState } from "react";
|
import { useState } from "react";
|
||||||
import type { LucideIcon } from "lucide-react";
|
import type { LucideIcon } from "lucide-react";
|
||||||
// plane helpers
|
// plane helpers
|
||||||
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
|
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
|
||||||
import type { ISvgIcons } from "@plane/propel/icons";
|
import type { ISvgIcons } from "@plane/propel/icons";
|
||||||
import { CloseIcon } from "@plane/propel/icons";
|
import { CloseIcon } from "@plane/propel/icons";
|
||||||
// types
|
// types
|
||||||
import type { IIssueLabel } from "@plane/types";
|
import type { IIssueLabel } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, DragHandle } from "@plane/ui";
|
import type { TContextMenuItem } from "@plane/ui";
|
||||||
|
import { ActionDropdown, DragHandle } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
|
|
@ -52,11 +52,20 @@ export function LabelItemBlock(props: ILabelItemBlock) {
|
||||||
draggable = true,
|
draggable = true,
|
||||||
} = props;
|
} = props;
|
||||||
// states
|
// states
|
||||||
const [isMenuActive, setIsMenuActive] = useState(true);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
// refs
|
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
const actionMenuItems: TContextMenuItem[] = customMenuItems
|
||||||
|
.filter(({ isVisible }) => isVisible)
|
||||||
|
.map(({ onClick, CustomIcon, text, key }) => ({
|
||||||
|
key,
|
||||||
|
action: () => onClick(label),
|
||||||
|
customContent: (
|
||||||
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
<CustomIcon className="size-4" />
|
||||||
|
<span>{text}</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group flex items-center">
|
<div className="group flex items-center">
|
||||||
|
|
@ -74,26 +83,13 @@ export function LabelItemBlock(props: ILabelItemBlock) {
|
||||||
|
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
<div
|
<div
|
||||||
ref={actionSectionRef}
|
|
||||||
className={`absolute right-2.5 flex items-center gap-2 px-4 ${
|
className={`absolute right-2.5 flex items-center gap-2 px-4 ${
|
||||||
isMenuActive || isLabelGroup
|
isMenuActive || isLabelGroup
|
||||||
? "opacity-100"
|
? "opacity-100"
|
||||||
: "opacity-0 group-hover:pointer-events-auto group-hover:opacity-100"
|
: "opacity-0 group-hover:pointer-events-auto group-hover:opacity-100"
|
||||||
} ${isLabelGroup && "-top-0.5"}`}
|
} ${isLabelGroup && "-top-0.5"}`}
|
||||||
>
|
>
|
||||||
<CustomMenu ellipsis menuButtonOnClick={() => setIsMenuActive(!isMenuActive)} useCaptureForOutsideClick>
|
<ActionDropdown placement="bottom-end" onOpenChange={setIsMenuActive} items={actionMenuItems} />
|
||||||
{customMenuItems.map(
|
|
||||||
({ isVisible, onClick, CustomIcon, text, key }) =>
|
|
||||||
isVisible && (
|
|
||||||
<CustomMenu.MenuItem key={key} onClick={() => onClick(label)}>
|
|
||||||
<span className="flex items-center justify-start gap-2">
|
|
||||||
<CustomIcon className="size-4" />
|
|
||||||
<span>{text}</span>
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</CustomMenu>
|
|
||||||
{!isLabelGroup && (
|
{!isLabelGroup && (
|
||||||
<div className="py-0.5">
|
<div className="py-0.5">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@
|
||||||
import React, { useEffect, useState, useCallback } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import type { EditorRefApi } from "@plane/editor";
|
import type { EditorRefApi } from "@plane/editor";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { CheckIcon, ChevronDownIcon } from "@plane/propel/icons";
|
import { ChevronDownIcon } from "@plane/propel/icons";
|
||||||
import { Tooltip } from "@plane/propel/tooltip";
|
import { Tooltip } from "@plane/propel/tooltip";
|
||||||
import { CustomMenu } from "@plane/ui";
|
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// constants
|
// constants
|
||||||
import type { ToolbarMenuItem } from "@/constants/editor";
|
import type { ToolbarMenuItem } from "@/constants/editor";
|
||||||
import { TOOLBAR_ITEMS, TYPOGRAPHY_ITEMS } from "@/constants/editor";
|
import { TOOLBAR_ITEMS, TYPOGRAPHY_ITEMS } from "@/constants/editor";
|
||||||
|
import { SelectionDropdown } from "@/components/common/selection-dropdown";
|
||||||
// local imports
|
// local imports
|
||||||
import { ColorDropdown } from "./color-dropdown";
|
import { ColorDropdown } from "./color-dropdown";
|
||||||
|
|
||||||
|
|
@ -86,8 +86,6 @@ export function PageToolbar(props: Props) {
|
||||||
return initialStates;
|
return initialStates;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isTypographyMenuOpen, setIsTypographyMenuOpen] = useState(false);
|
|
||||||
|
|
||||||
const updateActiveStates = useCallback(() => {
|
const updateActiveStates = useCallback(() => {
|
||||||
const newActiveStates: Record<string, boolean> = {};
|
const newActiveStates: Record<string, boolean> = {};
|
||||||
Object.values(toolbarItems)
|
Object.values(toolbarItems)
|
||||||
|
|
@ -117,52 +115,39 @@ export function PageToolbar(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="animate-in fade-in flex items-center divide-x divide-subtle-1 overflow-x-scroll duration-200">
|
<div className="animate-in fade-in flex items-center divide-x divide-subtle-1 overflow-x-scroll duration-200">
|
||||||
<CustomMenu
|
<div className="pr-2">
|
||||||
customButton={
|
<SelectionDropdown
|
||||||
|
placement="bottom-start"
|
||||||
|
menuButton={({ open }) => (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-7 w-24 items-center justify-between gap-2 rounded-sm border-[0.5px] border-strong px-2 text-left text-13 whitespace-nowrap",
|
"flex h-7 w-24 items-center justify-between gap-2 rounded-sm border-[0.5px] border-strong px-2 text-left text-13 whitespace-nowrap",
|
||||||
{
|
{
|
||||||
"bg-layer-1-selected text-primary": isTypographyMenuOpen,
|
"bg-layer-1-selected text-primary": open,
|
||||||
"text-tertiary hover:bg-layer-1-hover": !isTypographyMenuOpen,
|
"text-tertiary hover:bg-layer-1-hover": !open,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{activeTypography?.name || "Text"}
|
{activeTypography?.name || "Text"}
|
||||||
<ChevronDownIcon className="size-3 shrink-0" />
|
<ChevronDownIcon className="size-3 shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
className="pr-2"
|
options={TYPOGRAPHY_ITEMS.map((item) => ({
|
||||||
placement="bottom-start"
|
key: item.renderKey,
|
||||||
closeOnSelect
|
isChecked: activeTypography?.itemKey === item.itemKey,
|
||||||
maxHeight="lg"
|
icon: <item.icon className="size-3" />,
|
||||||
menuButtonOnClick={() => setIsTypographyMenuOpen((prev) => !prev)}
|
title: item.name,
|
||||||
onMenuClose={() => setIsTypographyMenuOpen(false)}
|
onClick: () => {
|
||||||
>
|
|
||||||
{TYPOGRAPHY_ITEMS.map((item) => (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
key={item.renderKey}
|
|
||||||
className={cn("flex items-center justify-between gap-2", {
|
|
||||||
"bg-layer-transparent-selected text-primary": activeTypography?.itemKey === item.itemKey,
|
|
||||||
"hover:bg-layer-transparent-hover": !(activeTypography?.itemKey === item.itemKey),
|
|
||||||
})}
|
|
||||||
onClick={() => {
|
|
||||||
if (activeTypography?.itemKey !== item.itemKey) {
|
if (activeTypography?.itemKey !== item.itemKey) {
|
||||||
editorRef.executeMenuItemCommand({
|
editorRef.executeMenuItemCommand({
|
||||||
itemKey: item.itemKey,
|
itemKey: item.itemKey,
|
||||||
...item.extraProps,
|
...item.extraProps,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
},
|
||||||
>
|
}))}
|
||||||
<span className="flex items-center gap-2">
|
/>
|
||||||
<item.icon className="size-3" />
|
</div>
|
||||||
{item.name}
|
|
||||||
</span>
|
|
||||||
{activeTypography?.itemKey === item.itemKey && <CheckIcon className="size-3 shrink-0 text-tertiary" />}
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
))}
|
|
||||||
</CustomMenu>
|
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<ColorDropdown
|
<ColorDropdown
|
||||||
handleColorSelect={(key, color) =>
|
handleColorSelect={(key, color) =>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import * as React from "react";
|
||||||
// ui
|
// ui
|
||||||
import { Tooltip } from "@plane/propel/tooltip";
|
import { Tooltip } from "@plane/propel/tooltip";
|
||||||
import type { TContextMenuItem } from "../dropdowns";
|
import type { TContextMenuItem } from "../dropdowns";
|
||||||
import { CustomMenu } from "../dropdowns";
|
import { ActionDropdown } from "../dropdowns";
|
||||||
import { cn } from "../utils";
|
import { cn } from "../utils";
|
||||||
import { Breadcrumbs } from "./breadcrumbs";
|
import { Breadcrumbs } from "./breadcrumbs";
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ export function BreadcrumbNavigationDropdown(props: TBreadcrumbNavigationDropdow
|
||||||
function NavigationButton() {
|
function NavigationButton() {
|
||||||
return (
|
return (
|
||||||
<Tooltip tooltipContent={selectedItem?.title} position="bottom" disabled={isOpen}>
|
<Tooltip tooltipContent={selectedItem?.title} position="bottom" disabled={isOpen}>
|
||||||
<button
|
<div
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (!isLast) {
|
if (!isLast) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -56,7 +56,7 @@ export function BreadcrumbNavigationDropdown(props: TBreadcrumbNavigationDropdow
|
||||||
{selectedItemIcon && <Breadcrumbs.Icon>{selectedItemIcon}</Breadcrumbs.Icon>}
|
{selectedItemIcon && <Breadcrumbs.Icon>{selectedItemIcon}</Breadcrumbs.Icon>}
|
||||||
<Breadcrumbs.Label>{selectedItem?.title}</Breadcrumbs.Label>
|
<Breadcrumbs.Label>{selectedItem?.title}</Breadcrumbs.Label>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -66,8 +66,8 @@ export function BreadcrumbNavigationDropdown(props: TBreadcrumbNavigationDropdow
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
customButton={
|
button={
|
||||||
<>
|
<>
|
||||||
<NavigationButton />
|
<NavigationButton />
|
||||||
<Breadcrumbs.Separator
|
<Breadcrumbs.Separator
|
||||||
|
|
@ -86,40 +86,21 @@ export function BreadcrumbNavigationDropdown(props: TBreadcrumbNavigationDropdow
|
||||||
}
|
}
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
className="h-full rounded-sm"
|
className="h-full rounded-sm"
|
||||||
customButtonClassName={cn(
|
buttonClassName={cn(
|
||||||
"group flex h-full cursor-pointer items-center gap-0.5 rounded-sm outline-none hover:bg-surface-2",
|
"group flex h-full cursor-pointer items-center gap-0.5 rounded-sm outline-none hover:bg-surface-2",
|
||||||
{
|
{
|
||||||
"bg-surface-2": isOpen,
|
"bg-surface-2": isOpen,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
closeOnSelect
|
onOpenChange={setIsOpen}
|
||||||
menuButtonOnClick={() => {
|
items={navigationItems.map((item) => ({
|
||||||
setIsOpen(!isOpen);
|
...item,
|
||||||
}}
|
action: () => {
|
||||||
onMenuClose={() => {
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{navigationItems.map((item) => {
|
|
||||||
if (item.shouldRender === false) return null;
|
|
||||||
return (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
key={item.key}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
if (item.key === selectedItemKey) return;
|
if (item.key === selectedItemKey) return;
|
||||||
item.action();
|
item.action();
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2",
|
|
||||||
{
|
|
||||||
"text-placeholder": item.disabled,
|
|
||||||
},
|
},
|
||||||
item.className
|
customContent: (
|
||||||
)}
|
<div className="flex w-full items-center gap-2">
|
||||||
disabled={item.disabled}
|
|
||||||
>
|
|
||||||
{item.icon && <item.icon className={cn("size-4 flex-shrink-0", item.iconClassName)} />}
|
{item.icon && <item.icon className={cn("size-4 flex-shrink-0", item.iconClassName)} />}
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<h5>{item.title}</h5>
|
<h5>{item.title}</h5>
|
||||||
|
|
@ -134,9 +115,9 @@ export function BreadcrumbNavigationDropdown(props: TBreadcrumbNavigationDropdow
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{item.key === selectedItemKey && <CheckIcon className="size-3.5 flex-shrink-0" />}
|
{item.key === selectedItemKey && <CheckIcon className="size-3.5 flex-shrink-0" />}
|
||||||
</CustomMenu.MenuItem>
|
</div>
|
||||||
);
|
),
|
||||||
})}
|
}))}
|
||||||
</CustomMenu>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { MoreVertical } from "lucide-react";
|
||||||
// plane utils
|
// plane utils
|
||||||
import { calculateTimeAgo, cn, getIconForLink } from "@plane/utils";
|
import { calculateTimeAgo, cn, getIconForLink } from "@plane/utils";
|
||||||
// plane ui
|
// plane ui
|
||||||
import type { TContextMenuItem } from "../dropdowns/context-menu/root";
|
import type { TContextMenuItem } from "../dropdowns/context-menu/root";
|
||||||
import { CustomMenu } from "../dropdowns/custom-menu";
|
import { ActionDropdown } from "../dropdowns/action-dropdown";
|
||||||
|
|
||||||
export type TLinkItemBlockProps = {
|
export type TLinkItemBlockProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -38,36 +39,17 @@ export function LinkItemBlock(props: TLinkItemBlockProps) {
|
||||||
</div>
|
</div>
|
||||||
{menuItems && (
|
{menuItems && (
|
||||||
<div className="hidden group-hover:block">
|
<div className="hidden group-hover:block">
|
||||||
<CustomMenu placement="bottom-end" menuItemsClassName="z-20" closeOnSelect verticalEllipsis>
|
<ActionDropdown
|
||||||
{menuItems.map((item) => (
|
placement="bottom-end"
|
||||||
<CustomMenu.MenuItem
|
menuClassName="z-20"
|
||||||
key={item.key}
|
button={
|
||||||
onClick={(e) => {
|
<span className="grid place-items-center rounded-sm p-1 text-secondary hover:bg-white/6">
|
||||||
e.preventDefault();
|
<MoreVertical className="h-3.5 w-3.5" />
|
||||||
e.stopPropagation();
|
</span>
|
||||||
item.action();
|
}
|
||||||
}}
|
buttonClassName="grid place-items-center"
|
||||||
className={cn("flex w-full items-center gap-2", {
|
items={menuItems}
|
||||||
"text-placeholder": item.disabled,
|
/>
|
||||||
})}
|
|
||||||
disabled={item.disabled}
|
|
||||||
>
|
|
||||||
{item.icon && <item.icon className={cn("h-3 w-3", item.iconClassName)} />}
|
|
||||||
<div>
|
|
||||||
<h5>{item.title}</h5>
|
|
||||||
{item.description && (
|
|
||||||
<p
|
|
||||||
className={cn("whitespace-pre-line text-tertiary", {
|
|
||||||
"text-placeholder": item.disabled,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{item.description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
))}
|
|
||||||
</CustomMenu>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue