АРХ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: канонизация labels, editor и ui dropdown-оберток
This commit is contained in:
@@ -21,7 +21,7 @@ export type TSelectionDropdownOption = {
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean;
|
||||
menuButton: ReactNode;
|
||||
menuButton: ReactNode | ((props: { open: boolean }) => ReactNode);
|
||||
menuButtonWrapperClassName?: string;
|
||||
options: TSelectionDropdownOption[];
|
||||
placement?: Placement;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TDescriptionVersion } from "@plane/types";
|
||||
import { Avatar, CustomMenu } from "@plane/ui";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { calculateTimeAgo, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
@@ -28,7 +28,7 @@ export const DescriptionVersionsDropdownItem = observer(function DescriptionVers
|
||||
const { t } = useTranslation();
|
||||
|
||||
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">
|
||||
<Avatar
|
||||
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>{calculateTimeAgo(version.last_saved_at)}</span>
|
||||
</p>
|
||||
</CustomMenu.MenuItem>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,12 +9,11 @@ import { History } from "lucide-react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TDescriptionVersion } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { calculateTimeAgo } from "@plane/utils";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { calculateTimeAgo, cn, getFileURL } from "@plane/utils";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
// local imports
|
||||
import { DescriptionVersionsDropdownItem } from "./dropdown-item";
|
||||
import { SelectionDropdown } from "@/components/common/selection-dropdown";
|
||||
import type { TDescriptionVersionEntityInformation } from "./root";
|
||||
|
||||
type Props = {
|
||||
@@ -34,13 +33,21 @@ export const DescriptionVersionsDropdown = observer(function DescriptionVersions
|
||||
const lastUpdatedByUserDisplayName = latestVersion?.owned_by
|
||||
? getUserDetails(latestVersion?.owned_by)?.display_name
|
||||
: entityInformation.createdByDisplayName;
|
||||
const latestVersionId = latestVersion?.id;
|
||||
// translation
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<CustomMenu
|
||||
label={
|
||||
<div className="flex items-center gap-1 text-tertiary">
|
||||
<SelectionDropdown
|
||||
disabled={disabled}
|
||||
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">
|
||||
<History className="size-3.5" />
|
||||
</span>
|
||||
@@ -50,18 +57,32 @@ export const DescriptionVersionsDropdown = observer(function DescriptionVersions
|
||||
{calculateTimeAgo(lastUpdatedAt)}
|
||||
</p>
|
||||
</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>
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ type Props = {
|
||||
placement?: Placement;
|
||||
disabled?: boolean;
|
||||
tabIndex?: number;
|
||||
menuButton?: React.ReactNode;
|
||||
menuButton?: React.ReactNode | ((props: { open: boolean }) => React.ReactNode);
|
||||
menuButtonWrapperClassName?: string;
|
||||
isFiltersApplied?: boolean;
|
||||
};
|
||||
@@ -58,7 +58,7 @@ export function FiltersDropdown(props: Props) {
|
||||
className={menuButtonWrapperClassName}
|
||||
disabled={disabled}
|
||||
>
|
||||
{menuButton}
|
||||
{typeof menuButton === "function" ? menuButton({ open }) : menuButton}
|
||||
</button>
|
||||
) : (
|
||||
<div ref={setReferenceElement}>
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
*/
|
||||
|
||||
import type { MutableRefObject } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
// plane helpers
|
||||
import { PROJECT_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
// types
|
||||
import type { IIssueLabel } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu, DragHandle } from "@plane/ui";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ActionDropdown, DragHandle } from "@plane/ui";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
@@ -52,11 +52,20 @@ export function LabelItemBlock(props: ILabelItemBlock) {
|
||||
draggable = true,
|
||||
} = props;
|
||||
// states
|
||||
const [isMenuActive, setIsMenuActive] = useState(true);
|
||||
// refs
|
||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
|
||||
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 (
|
||||
<div className="group flex items-center">
|
||||
@@ -74,26 +83,13 @@ export function LabelItemBlock(props: ILabelItemBlock) {
|
||||
|
||||
{!disabled && (
|
||||
<div
|
||||
ref={actionSectionRef}
|
||||
className={`absolute right-2.5 flex items-center gap-2 px-4 ${
|
||||
isMenuActive || isLabelGroup
|
||||
? "opacity-100"
|
||||
: "opacity-0 group-hover:pointer-events-auto group-hover:opacity-100"
|
||||
} ${isLabelGroup && "-top-0.5"}`}
|
||||
>
|
||||
<CustomMenu ellipsis menuButtonOnClick={() => setIsMenuActive(!isMenuActive)} useCaptureForOutsideClick>
|
||||
{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>
|
||||
<ActionDropdown placement="bottom-end" onOpenChange={setIsMenuActive} items={actionMenuItems} />
|
||||
{!isLabelGroup && (
|
||||
<div className="py-0.5">
|
||||
<button
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import type { EditorRefApi } from "@plane/editor";
|
||||
// plane imports
|
||||
import { CheckIcon, ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// constants
|
||||
import type { ToolbarMenuItem } from "@/constants/editor";
|
||||
import { TOOLBAR_ITEMS, TYPOGRAPHY_ITEMS } from "@/constants/editor";
|
||||
import { SelectionDropdown } from "@/components/common/selection-dropdown";
|
||||
// local imports
|
||||
import { ColorDropdown } from "./color-dropdown";
|
||||
|
||||
@@ -86,8 +86,6 @@ export function PageToolbar(props: Props) {
|
||||
return initialStates;
|
||||
});
|
||||
|
||||
const [isTypographyMenuOpen, setIsTypographyMenuOpen] = useState(false);
|
||||
|
||||
const updateActiveStates = useCallback(() => {
|
||||
const newActiveStates: Record<string, boolean> = {};
|
||||
Object.values(toolbarItems)
|
||||
@@ -117,52 +115,39 @@ export function PageToolbar(props: Props) {
|
||||
|
||||
return (
|
||||
<div className="animate-in fade-in flex items-center divide-x divide-subtle-1 overflow-x-scroll duration-200">
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<span
|
||||
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",
|
||||
{
|
||||
"bg-layer-1-selected text-primary": isTypographyMenuOpen,
|
||||
"text-tertiary hover:bg-layer-1-hover": !isTypographyMenuOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{activeTypography?.name || "Text"}
|
||||
<ChevronDownIcon className="size-3 shrink-0" />
|
||||
</span>
|
||||
}
|
||||
className="pr-2"
|
||||
placement="bottom-start"
|
||||
closeOnSelect
|
||||
maxHeight="lg"
|
||||
menuButtonOnClick={() => setIsTypographyMenuOpen((prev) => !prev)}
|
||||
onMenuClose={() => setIsTypographyMenuOpen(false)}
|
||||
>
|
||||
{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={() => {
|
||||
<div className="pr-2">
|
||||
<SelectionDropdown
|
||||
placement="bottom-start"
|
||||
menuButton={({ open }) => (
|
||||
<span
|
||||
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",
|
||||
{
|
||||
"bg-layer-1-selected text-primary": open,
|
||||
"text-tertiary hover:bg-layer-1-hover": !open,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{activeTypography?.name || "Text"}
|
||||
<ChevronDownIcon className="size-3 shrink-0" />
|
||||
</span>
|
||||
)}
|
||||
options={TYPOGRAPHY_ITEMS.map((item) => ({
|
||||
key: item.renderKey,
|
||||
isChecked: activeTypography?.itemKey === item.itemKey,
|
||||
icon: <item.icon className="size-3" />,
|
||||
title: item.name,
|
||||
onClick: () => {
|
||||
if (activeTypography?.itemKey !== item.itemKey) {
|
||||
editorRef.executeMenuItemCommand({
|
||||
itemKey: item.itemKey,
|
||||
...item.extraProps,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<item.icon className="size-3" />
|
||||
{item.name}
|
||||
</span>
|
||||
{activeTypography?.itemKey === item.itemKey && <CheckIcon className="size-3 shrink-0 text-tertiary" />}
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<ColorDropdown
|
||||
handleColorSelect={(key, color) =>
|
||||
|
||||
Reference in New Issue
Block a user