UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: миграция workspace sidebar и views action-menu на общий канон
This commit is contained in:
parent
6d35fc7bee
commit
5cf2c2130a
|
|
@ -4,14 +4,15 @@
|
||||||
* See the LICENSE file for details.
|
* See the LICENSE file for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useRef } from "react";
|
import { useMemo } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { LogOut, MoreHorizontal, Settings, Share2, ArchiveIcon } from "lucide-react";
|
import { LogOut, MoreHorizontal, Settings, Share2, ArchiveIcon } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
import { MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { LinkIcon } from "@plane/propel/icons";
|
import { LinkIcon } from "@plane/propel/icons";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import type { TContextMenuItem } from "@plane/ui";
|
||||||
|
import { ActionDropdown } from "@plane/ui";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
|
@ -34,85 +35,81 @@ export function ProjectActionsMenu({
|
||||||
onLeaveProject,
|
onLeaveProject,
|
||||||
onPublishModal,
|
onPublishModal,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
// states
|
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
|
||||||
// translation
|
// translation
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// refs
|
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
// router
|
// router
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
const menuItems = useMemo<TContextMenuItem[]>(
|
||||||
<CustomMenu
|
() => [
|
||||||
customButton={
|
...(isAdmin
|
||||||
<span
|
? [
|
||||||
ref={actionSectionRef}
|
{
|
||||||
className="grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1"
|
key: "publish",
|
||||||
onClick={() => setIsMenuActive(!isMenuActive)}
|
title: t("publish_project"),
|
||||||
|
icon: Share2,
|
||||||
|
action: onPublishModal,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
key: "copy-link",
|
||||||
|
title: t("copy_link"),
|
||||||
|
icon: LinkIcon,
|
||||||
|
action: onCopyText,
|
||||||
|
},
|
||||||
|
...(isAuthorized
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "archives",
|
||||||
|
title: t("archives"),
|
||||||
|
icon: ArchiveIcon,
|
||||||
|
action: () => {
|
||||||
|
navigate(`/${workspaceSlug}/projects/${project?.id}/archives/issues`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
key: "settings",
|
||||||
|
title: t("settings"),
|
||||||
|
icon: Settings,
|
||||||
|
action: () => {
|
||||||
|
navigate(`/${workspaceSlug}/settings/projects/${project?.id}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...(!isAuthorized
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "leave-project",
|
||||||
|
action: onLeaveProject,
|
||||||
|
customContent: (
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-start gap-2"
|
||||||
|
data-ph-element={MEMBER_TRACKER_ELEMENTS.SIDEBAR_PROJECT_QUICK_ACTIONS}
|
||||||
>
|
>
|
||||||
|
<LogOut className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||||
|
<span>{t("leave_project")}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
[isAdmin, isAuthorized, navigate, onCopyText, onLeaveProject, onPublishModal, project?.id, t, workspaceSlug]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionDropdown
|
||||||
|
button={
|
||||||
|
<span className="grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1">
|
||||||
<MoreHorizontal className="size-4" />
|
<MoreHorizontal className="size-4" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
customButtonClassName="grid place-items-center"
|
buttonClassName="grid place-items-center"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")}
|
items={menuItems}
|
||||||
useCaptureForOutsideClick
|
/>
|
||||||
closeOnSelect
|
|
||||||
onMenuClose={() => setIsMenuActive(false)}
|
|
||||||
>
|
|
||||||
{/* Publish project settings */}
|
|
||||||
{isAdmin && (
|
|
||||||
<CustomMenu.MenuItem onClick={onPublishModal}>
|
|
||||||
<div className="relative flex flex-shrink-0 items-center justify-start gap-2">
|
|
||||||
<div className="flex h-4 w-4 cursor-pointer items-center justify-center rounded-sm text-secondary transition-all duration-300 hover:bg-layer-1">
|
|
||||||
<Share2 className="h-3.5 w-3.5 stroke-[1.5]" />
|
|
||||||
</div>
|
|
||||||
<div>{t("publish_project")}</div>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
<CustomMenu.MenuItem onClick={onCopyText}>
|
|
||||||
<span className="flex items-center justify-start gap-2">
|
|
||||||
<LinkIcon className="h-3.5 w-3.5 stroke-[1.5]" />
|
|
||||||
<span>{t("copy_link")}</span>
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
{isAuthorized && (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/${workspaceSlug}/projects/${project?.id}/archives/issues`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex cursor-pointer items-center justify-start gap-2">
|
|
||||||
<ArchiveIcon className="h-3.5 w-3.5 stroke-[1.5]" />
|
|
||||||
<span>{t("archives")}</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/${workspaceSlug}/settings/projects/${project?.id}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex cursor-pointer items-center justify-start gap-2">
|
|
||||||
<Settings className="h-3.5 w-3.5 stroke-[1.5]" />
|
|
||||||
<span>{t("settings")}</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
{/* Leave project */}
|
|
||||||
{!isAuthorized && (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={onLeaveProject}
|
|
||||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.SIDEBAR_PROJECT_QUICK_ACTIONS}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-start gap-2">
|
|
||||||
<LogOut className="h-3.5 w-3.5 stroke-[1.5]" />
|
|
||||||
<span>{t("leave_project")}</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
</CustomMenu>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { Disclosure } from "@headlessui/react";
|
||||||
import { ROLE, EUserPermissions, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
import { ROLE, EUserPermissions, MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||||
import type { EUserProjectRoles, IUser, IWorkspaceMember, TProjectMembership } from "@plane/types";
|
import type { EUserProjectRoles, IUser, IWorkspaceMember, TProjectMembership } from "@plane/types";
|
||||||
import { CustomMenu, CustomSelect } from "@plane/ui";
|
import { ActionDropdown, CustomSelect } from "@plane/ui";
|
||||||
import { getFileURL } from "@plane/utils";
|
import { getFileURL } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMember } from "@/hooks/store/use-member";
|
import { useMember } from "@/hooks/store/use-member";
|
||||||
|
|
@ -69,23 +69,25 @@ export function NameColumn(props: NameProps) {
|
||||||
{first_name} {last_name}
|
{first_name} {last_name}
|
||||||
</div>
|
</div>
|
||||||
{(isAdmin || id === currentUser?.id) && (
|
{(isAdmin || id === currentUser?.id) && (
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
ellipsis
|
|
||||||
buttonClassName="p-0.5 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
optionsClassName="p-1.5"
|
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
>
|
buttonClassName="p-0.5 opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
<CustomMenu.MenuItem>
|
items={[
|
||||||
|
{
|
||||||
|
key: "remove-member",
|
||||||
|
action: () => setRemoveMemberModal(rowData),
|
||||||
|
customContent: (
|
||||||
<div
|
<div
|
||||||
className="flex cursor-pointer items-center gap-x-1 font-medium text-danger-primary"
|
className="flex cursor-pointer items-center gap-x-1 font-medium text-danger-primary"
|
||||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.PROJECT_MEMBER_TABLE_CONTEXT_MENU}
|
data-ph-element={MEMBER_TRACKER_ELEMENTS.PROJECT_MEMBER_TABLE_CONTEXT_MENU}
|
||||||
onClick={() => setRemoveMemberModal(rowData)}
|
|
||||||
>
|
>
|
||||||
<CircleMinus className="size-3.5 flex-shrink-0" />
|
<CircleMinus className="size-3.5 flex-shrink-0" />
|
||||||
{rowData.member?.id === currentUser?.id ? "Leave " : "Remove "}
|
{rowData.member?.id === currentUser?.id ? "Leave " : "Remove "}
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
),
|
||||||
</CustomMenu>
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import { useTranslation } from "@plane/i18n";
|
||||||
import { LinkIcon, TrashIcon, ChevronDownIcon } from "@plane/propel/icons";
|
import { LinkIcon, TrashIcon, ChevronDownIcon } from "@plane/propel/icons";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||||
import type { TContextMenuItem } from "@plane/ui";
|
import type { TContextMenuItem } from "@plane/ui";
|
||||||
import { CustomSelect, CustomMenu } from "@plane/ui";
|
import { ActionDropdown, CustomSelect } from "@plane/ui";
|
||||||
import { cn, copyTextToClipboard } from "@plane/utils";
|
import { copyTextToClipboard } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace/confirm-workspace-member-remove";
|
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace/confirm-workspace-member-remove";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -185,41 +185,7 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
|
||||||
})}
|
})}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect>
|
<ActionDropdown placement="bottom-end" items={MENU_ITEMS} />
|
||||||
{MENU_ITEMS.map((item) => {
|
|
||||||
if (item.shouldRender === false) return null;
|
|
||||||
return (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
key={item.key}
|
|
||||||
onClick={() => {
|
|
||||||
item.action();
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2",
|
|
||||||
{
|
|
||||||
"text-placeholder": item.disabled,
|
|
||||||
},
|
|
||||||
item.className
|
|
||||||
)}
|
|
||||||
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>
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,11 @@ import { createRoot } from "react-dom/client";
|
||||||
import { Star, MoreHorizontal, GripVertical } from "lucide-react";
|
import { Star, MoreHorizontal, GripVertical } from "lucide-react";
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { DraftIcon, FavoriteFolderIcon, ChevronRightIcon } from "@plane/propel/icons";
|
import { DraftIcon, FavoriteFolderIcon, ChevronRightIcon } from "@plane/propel/icons";
|
||||||
import { Tooltip } from "@plane/propel/tooltip";
|
import { Tooltip } from "@plane/propel/tooltip";
|
||||||
import type { IFavorite, InstructionType } from "@plane/types";
|
import type { IFavorite, InstructionType } from "@plane/types";
|
||||||
import { CustomMenu, DropIndicator, DragHandle } from "@plane/ui";
|
import { ActionDropdown, DropIndicator, DragHandle } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -58,7 +57,6 @@ export function FavoriteFolder(props: Props) {
|
||||||
const [folderToRename, setFolderToRename] = useState<string | boolean | null>(null);
|
const [folderToRename, setFolderToRename] = useState<string | boolean | null>(null);
|
||||||
const [instruction, setInstruction] = useState<InstructionType | undefined>(undefined);
|
const [instruction, setInstruction] = useState<InstructionType | undefined>(undefined);
|
||||||
// refs
|
// refs
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
const elementRef = useRef<HTMLDivElement | null>(null);
|
||||||
// translation
|
// translation
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -135,9 +133,6 @@ export function FavoriteFolder(props: Props) {
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isDragging, favorite.id, isLastChild, favorite.id]);
|
}, [isDragging, favorite.id, isLastChild, favorite.id]);
|
||||||
|
|
||||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
|
||||||
|
|
||||||
return folderToRename ? (
|
return folderToRename ? (
|
||||||
<NewFavoriteFolder
|
<NewFavoriteFolder
|
||||||
setCreateNewFolder={setFolderToRename}
|
setCreateNewFolder={setFolderToRename}
|
||||||
|
|
@ -208,39 +203,44 @@ export function FavoriteFolder(props: Props) {
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
customButton={
|
button={
|
||||||
<span
|
<span className="grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1">
|
||||||
ref={actionSectionRef}
|
|
||||||
className="grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1"
|
|
||||||
>
|
|
||||||
<MoreHorizontal className="size-3" />
|
<MoreHorizontal className="size-3" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
menuButtonOnClick={() => setIsMenuActive(!isMenuActive)}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none flex-shrink-0 opacity-0 group-hover/project-item:pointer-events-auto group-hover/project-item:opacity-100",
|
"pointer-events-none flex-shrink-0 opacity-0 group-hover/project-item:pointer-events-auto group-hover/project-item:opacity-100",
|
||||||
{
|
{
|
||||||
"pointer-events-auto opacity-100": isMenuActive,
|
"pointer-events-auto opacity-100": isMenuActive,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
customButtonClassName="grid place-items-center"
|
buttonClassName="grid place-items-center"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")}
|
onOpenChange={setIsMenuActive}
|
||||||
>
|
items={[
|
||||||
<CustomMenu.MenuItem onClick={() => handleRemoveFromFavorites(favorite)}>
|
{
|
||||||
|
key: "remove-favorite-folder",
|
||||||
|
action: () => handleRemoveFromFavorites(favorite),
|
||||||
|
customContent: (
|
||||||
<span className="flex items-center justify-start gap-2">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<Star className="fill-yellow-500 stroke-yellow-500 h-3.5 w-3.5" />
|
<Star className="h-3.5 w-3.5 fill-yellow-500 stroke-yellow-500" />
|
||||||
<span>Remove from favorites</span>
|
<span>Remove from favorites</span>
|
||||||
</span>
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
),
|
||||||
<CustomMenu.MenuItem onClick={() => setFolderToRename(favorite.id)}>
|
},
|
||||||
|
{
|
||||||
|
key: "rename-favorite-folder",
|
||||||
|
action: () => setFolderToRename(favorite.id),
|
||||||
|
customContent: (
|
||||||
<div className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
<DraftIcon className="h-3.5 w-3.5 stroke-[1.5] text-tertiary" />
|
<DraftIcon className="h-3.5 w-3.5 stroke-[1.5] text-tertiary" />
|
||||||
<span>Rename Folder</span>
|
<span>Rename Folder</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
),
|
||||||
</CustomMenu>
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
as="button"
|
as="button"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -8,49 +8,49 @@ import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { MoreHorizontal, Star } from "lucide-react";
|
import { MoreHorizontal, Star } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
|
||||||
import type { IFavorite } from "@plane/types";
|
import type { IFavorite } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { ActionDropdown } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ref: React.MutableRefObject<HTMLDivElement | null>;
|
|
||||||
isMenuActive: boolean;
|
isMenuActive: boolean;
|
||||||
favorite: IFavorite;
|
favorite: IFavorite;
|
||||||
onChange: (value: boolean) => void;
|
onOpenChange: (value: boolean) => void;
|
||||||
handleRemoveFromFavorites: (favorite: IFavorite) => void;
|
handleRemoveFromFavorites: (favorite: IFavorite) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FavoriteItemQuickAction = observer(function FavoriteItemQuickAction(props: Props) {
|
export const FavoriteItemQuickAction = observer(function FavoriteItemQuickAction(props: Props) {
|
||||||
const { ref, isMenuActive, onChange, handleRemoveFromFavorites, favorite } = props;
|
const { isMenuActive, onOpenChange, handleRemoveFromFavorites, favorite } = props;
|
||||||
// translation
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
customButton={
|
button={
|
||||||
<span ref={ref} className="grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1">
|
<span className="grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1">
|
||||||
<MoreHorizontal className="size-4" />
|
<MoreHorizontal className="size-4" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
menuButtonOnClick={() => onChange(!isMenuActive)}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none flex-shrink-0 opacity-0 group-hover/project-item:pointer-events-auto group-hover/project-item:opacity-100",
|
"pointer-events-none flex-shrink-0 opacity-0 group-hover/project-item:pointer-events-auto group-hover/project-item:opacity-100",
|
||||||
{
|
{
|
||||||
"pointer-events-auto opacity-100": isMenuActive,
|
"pointer-events-auto opacity-100": isMenuActive,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
customButtonClassName="grid place-items-center"
|
buttonClassName="grid place-items-center"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")}
|
onOpenChange={onOpenChange}
|
||||||
>
|
items={[
|
||||||
<CustomMenu.MenuItem onClick={() => handleRemoveFromFavorites(favorite)}>
|
{
|
||||||
|
key: "remove-favorite",
|
||||||
|
action: () => handleRemoveFromFavorites(favorite),
|
||||||
|
customContent: (
|
||||||
<span className="flex items-center justify-start gap-2">
|
<span className="flex items-center justify-start gap-2">
|
||||||
<Star className="fill-yellow-500 stroke-yellow-500 h-3.5 w-3.5 flex-shrink-0" />
|
<Star className="h-3.5 w-3.5 flex-shrink-0 fill-yellow-500 stroke-yellow-500" />
|
||||||
<span>Remove from favorites</span>
|
<span>Remove from favorites</span>
|
||||||
</span>
|
</span>
|
||||||
</CustomMenu.MenuItem>
|
),
|
||||||
</CustomMenu>
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import { attachInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
|
||||||
import type { IFavorite, InstructionType } from "@plane/types";
|
import type { IFavorite, InstructionType } from "@plane/types";
|
||||||
import { DropIndicator } from "@plane/ui";
|
import { DropIndicator } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -48,9 +47,6 @@ export const FavoriteRoot = observer(function FavoriteRoot(props: Props) {
|
||||||
|
|
||||||
//ref
|
//ref
|
||||||
const elementRef = useRef<HTMLDivElement>(null);
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
const handleQuickAction = (value: boolean) => setIsMenuActive(value);
|
|
||||||
|
|
||||||
// drag and drop
|
// drag and drop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -121,9 +117,6 @@ export const FavoriteRoot = observer(function FavoriteRoot(props: Props) {
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [elementRef?.current, isDragging, isLastChild, favorite.id]);
|
}, [elementRef?.current, isDragging, isLastChild, favorite.id]);
|
||||||
|
|
||||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isDragging && <DropIndicator isVisible={instruction === "reorder-above"} />}
|
{isDragging && <DropIndicator isVisible={instruction === "reorder-above"} />}
|
||||||
|
|
@ -132,9 +125,8 @@ export const FavoriteRoot = observer(function FavoriteRoot(props: Props) {
|
||||||
<FavoriteItemTitle href={itemLink} icon={itemIcon} title={itemTitle} />
|
<FavoriteItemTitle href={itemLink} icon={itemIcon} title={itemTitle} />
|
||||||
<FavoriteItemQuickAction
|
<FavoriteItemQuickAction
|
||||||
favorite={favorite}
|
favorite={favorite}
|
||||||
ref={actionSectionRef}
|
|
||||||
isMenuActive={isMenuActive}
|
isMenuActive={isMenuActive}
|
||||||
onChange={handleQuickAction}
|
onOpenChange={setIsMenuActive}
|
||||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||||
/>
|
/>
|
||||||
</FavoriteItemWrapper>
|
</FavoriteItemWrapper>
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,9 @@ import { HelpCircle, MessagesSquare, User } from "lucide-react";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { PageIcon } from "@plane/propel/icons";
|
import { PageIcon } from "@plane/propel/icons";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { ActionDropdown } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProductUpdatesModal } from "@/components/global";
|
import { ProductUpdatesModal } from "@/components/global";
|
||||||
import { AppSidebarItem } from "@/components/sidebar/sidebar-item";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { usePowerK } from "@/hooks/store/use-power-k";
|
import { usePowerK } from "@/hooks/store/use-power-k";
|
||||||
import { useChatSupport } from "@/hooks/use-chat-support";
|
import { useChatSupport } from "@/hooks/use-chat-support";
|
||||||
|
|
@ -33,75 +32,99 @@ export const HelpMenuRoot = observer(function HelpMenuRoot() {
|
||||||
<>
|
<>
|
||||||
<ProductUpdatesModal isOpen={isProductUpdatesModalOpen} handleClose={() => setProductUpdatesModalOpen(false)} />
|
<ProductUpdatesModal isOpen={isProductUpdatesModalOpen} handleClose={() => setProductUpdatesModalOpen(false)} />
|
||||||
|
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
customButton={
|
buttonAsChild
|
||||||
<AppSidebarItem
|
button={
|
||||||
variant="button"
|
<button type="button" className="group flex flex-col items-center justify-center gap-0.5 text-tertiary">
|
||||||
item={{
|
<div
|
||||||
icon: <HelpCircle className="size-5" />,
|
className={`flex size-8 items-center justify-center gap-2 rounded-md ${
|
||||||
isActive: isNeedHelpOpen,
|
isNeedHelpOpen
|
||||||
}}
|
? "bg-layer-transparent-selected text-secondary !text-icon-primary"
|
||||||
/>
|
: "group-hover:text-icon-secondary group-hover:bg-layer-transparent-hover !text-icon-tertiary"
|
||||||
}
|
}`}
|
||||||
// customButtonClassName="relative grid place-items-center rounded-md p-1.5 outline-none"
|
|
||||||
menuButtonOnClick={() => !isNeedHelpOpen && setIsNeedHelpOpen(true)}
|
|
||||||
onMenuClose={() => setIsNeedHelpOpen(false)}
|
|
||||||
placement="bottom-end"
|
|
||||||
maxHeight="lg"
|
|
||||||
closeOnSelect
|
|
||||||
>
|
>
|
||||||
<CustomMenu.MenuItem onClick={() => window.open("https://go.plane.so/p-docs", "_blank")}>
|
<HelpCircle className="size-5" />
|
||||||
<div className="flex items-center gap-x-2 rounded-sm text-11">
|
|
||||||
<PageIcon className="h-3.5 w-3.5 text-secondary" height={14} width={14} />
|
|
||||||
<span className="text-11">{t("documentation")}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</button>
|
||||||
{isChatSupportEnabled && (
|
}
|
||||||
<CustomMenu.MenuItem>
|
placement="bottom-end"
|
||||||
|
menuClassName="w-64"
|
||||||
|
onOpenChange={setIsNeedHelpOpen}
|
||||||
|
items={[]}
|
||||||
|
menuContent={({ closeDropdown }) => (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={openChatSupport}
|
onClick={() => {
|
||||||
className="flex w-full items-center gap-x-2 rounded-sm text-11 hover:bg-layer-1"
|
window.open("https://go.plane.so/p-docs", "_blank");
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-x-2 rounded-[0.9rem] px-2 py-2 text-left text-11 text-secondary transition-colors hover:bg-white/6"
|
||||||
|
>
|
||||||
|
<PageIcon className="h-3.5 w-3.5 text-secondary" height={14} width={14} />
|
||||||
|
<span>{t("documentation")}</span>
|
||||||
|
</button>
|
||||||
|
{isChatSupportEnabled && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
openChatSupport();
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-x-2 rounded-[0.9rem] px-2 py-2 text-left text-11 text-secondary transition-colors hover:bg-white/6"
|
||||||
>
|
>
|
||||||
<MessagesSquare className="h-3.5 w-3.5 text-secondary" />
|
<MessagesSquare className="h-3.5 w-3.5 text-secondary" />
|
||||||
<span className="text-11">{t("message_support")}</span>
|
<span>{t("message_support")}</span>
|
||||||
</button>
|
</button>
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
)}
|
||||||
<CustomMenu.MenuItem onClick={() => window.open("mailto:sales@plane.so", "_blank")}>
|
<button
|
||||||
<div className="flex items-center gap-x-2 rounded-sm text-11">
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
window.open("mailto:sales@plane.so", "_blank");
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-x-2 rounded-[0.9rem] px-2 py-2 text-left text-11 text-secondary transition-colors hover:bg-white/6"
|
||||||
|
>
|
||||||
<User className="h-3.5 w-3.5 text-secondary" size={14} />
|
<User className="h-3.5 w-3.5 text-secondary" size={14} />
|
||||||
<span className="text-11">{t("contact_sales")}</span>
|
<span>{t("contact_sales")}</span>
|
||||||
</div>
|
</button>
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<div className="my-1 border-t border-subtle" />
|
<div className="my-1 border-t border-subtle" />
|
||||||
<CustomMenu.MenuItem>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleShortcutsListModal(true)}
|
onClick={() => {
|
||||||
className="justify-sbg-layer-211 flex w-full items-center hover:bg-layer-1"
|
toggleShortcutsListModal(true);
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center rounded-[0.9rem] px-2 py-2 text-left text-11 text-secondary transition-colors hover:bg-white/6"
|
||||||
>
|
>
|
||||||
<span className="text-11">{t("keyboard_shortcuts")}</span>
|
<span>{t("keyboard_shortcuts")}</span>
|
||||||
</button>
|
</button>
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setProductUpdatesModalOpen(true)}
|
onClick={() => {
|
||||||
className="justify-sbg-layer-211 flex w-full items-center hover:bg-layer-1"
|
setProductUpdatesModalOpen(true);
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center rounded-[0.9rem] px-2 py-2 text-left text-11 text-secondary transition-colors hover:bg-white/6"
|
||||||
>
|
>
|
||||||
<span className="text-11">{t("whats_new")}</span>
|
<span>{t("whats_new")}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
window.open("https://forum.plane.so", "_blank", "noopener,noreferrer");
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center rounded-[0.9rem] px-2 py-2 text-left text-11 text-secondary transition-colors hover:bg-white/6"
|
||||||
|
>
|
||||||
|
<span>Forum</span>
|
||||||
</button>
|
</button>
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem onClick={() => window.open("https://forum.plane.so", "_blank", "noopener,noreferrer")}>
|
|
||||||
<div className="flex items-center gap-x-2 rounded-sm text-11">
|
|
||||||
<span className="text-11">Forum</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<div className="mt-1 border-t border-subtle px-1 pt-2 text-11 text-secondary">
|
<div className="mt-1 border-t border-subtle px-1 pt-2 text-11 text-secondary">
|
||||||
<PlaneVersionNumber />
|
<PlaneVersionNumber />
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu>
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* See the LICENSE file for details.
|
* See the LICENSE file for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
||||||
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||||
import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview";
|
import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview";
|
||||||
|
|
@ -24,7 +24,8 @@ import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||||
import { LinkIcon, ArchiveIcon, ChevronRightIcon, WorkItemsIcon } from "@plane/propel/icons";
|
import { LinkIcon, ArchiveIcon, ChevronRightIcon, WorkItemsIcon } from "@plane/propel/icons";
|
||||||
import { IconButton } from "@plane/propel/icon-button";
|
import { IconButton } from "@plane/propel/icon-button";
|
||||||
import { Tooltip } from "@plane/propel/tooltip";
|
import { Tooltip } from "@plane/propel/tooltip";
|
||||||
import { CustomMenu, DropIndicator, DragHandle, ControlLink } from "@plane/ui";
|
import type { TContextMenuItem } from "@plane/ui";
|
||||||
|
import { ActionDropdown, DropIndicator, DragHandle, ControlLink } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { DEFAULT_TAB_KEY, getTabUrl } from "@/components/navigation/tab-navigation-utils";
|
import { DEFAULT_TAB_KEY, getTabUrl } from "@/components/navigation/tab-navigation-utils";
|
||||||
|
|
@ -188,6 +189,21 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
|
||||||
: null,
|
: null,
|
||||||
].filter((item): item is TProjectActionItem => Boolean(item));
|
].filter((item): item is TProjectActionItem => Boolean(item));
|
||||||
|
|
||||||
|
const projectActionMenuItems = useMemo<TContextMenuItem[]>(
|
||||||
|
() =>
|
||||||
|
projectActionItems.map((item) => ({
|
||||||
|
key: item.key,
|
||||||
|
action: item.onClick,
|
||||||
|
customContent: (
|
||||||
|
<div className="flex items-center justify-start gap-2" data-ph-element={item.analytics}>
|
||||||
|
{item.icon}
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
[projectActionItems]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = projectRef.current;
|
const element = projectRef.current;
|
||||||
const dragHandleElement = dragHandleRef.current;
|
const dragHandleElement = dragHandleRef.current;
|
||||||
|
|
@ -405,8 +421,8 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
|
||||||
</ControlLink>
|
</ControlLink>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{!renderInToolbarMenu && (
|
{!renderInToolbarMenu && (
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
customButton={
|
button={
|
||||||
<span className="grid place-items-center">
|
<span className="grid place-items-center">
|
||||||
<MoreHorizontal className="h-3.5 w-3.5 text-placeholder" />
|
<MoreHorizontal className="h-3.5 w-3.5 text-placeholder" />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -417,34 +433,17 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
|
||||||
"pointer-events-auto opacity-100": isMenuActive,
|
"pointer-events-auto opacity-100": isMenuActive,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
customButtonClassName={cn(
|
buttonClassName={cn(
|
||||||
"grid size-7 place-items-center rounded-full text-placeholder transition-colors hover:bg-layer-transparent-hover",
|
"grid size-7 place-items-center rounded-full text-placeholder transition-colors hover:bg-layer-transparent-hover",
|
||||||
{
|
{
|
||||||
"bg-layer-transparent-hover": isMenuActive,
|
"bg-layer-transparent-hover": isMenuActive,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
menuItemsClassName={renderInToolbarMenu ? "z-[220]" : ""}
|
|
||||||
portalElement={renderInToolbarMenu && typeof document !== "undefined" ? document.body : undefined}
|
portalElement={renderInToolbarMenu && typeof document !== "undefined" ? document.body : undefined}
|
||||||
ariaLabel={t("aria_labels.projects_sidebar.toggle_quick_actions_menu")}
|
onOpenChange={setIsMenuActive}
|
||||||
useCaptureForOutsideClick
|
items={projectActionMenuItems}
|
||||||
closeOnSelect
|
/>
|
||||||
menuButtonOnClick={() => setIsMenuActive((state) => !state)}
|
|
||||||
onMenuClose={() => setIsMenuActive(false)}
|
|
||||||
>
|
|
||||||
{projectActionItems.map((item) => (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
key={item.key}
|
|
||||||
onClick={item.onClick}
|
|
||||||
data-ph-element={item.analytics}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-start gap-2">
|
|
||||||
{item.icon}
|
|
||||||
<span>{item.label}</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
))}
|
|
||||||
</CustomMenu>
|
|
||||||
)}
|
)}
|
||||||
{isAccordionMode && (
|
{isAccordionMode && (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Menu } from "@headlessui/react";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { LogOut, Settings, Settings2 } from "lucide-react";
|
import { LogOut, Settings, Settings2 } from "lucide-react";
|
||||||
|
|
@ -13,11 +12,10 @@ import { LogOut, Settings, Settings2 } from "lucide-react";
|
||||||
import { GOD_MODE_URL } from "@plane/constants";
|
import { GOD_MODE_URL } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||||
import { Avatar, CustomMenu } from "@plane/ui";
|
import { ActionDropdown, Avatar } from "@plane/ui";
|
||||||
import { getFileURL } from "@plane/utils";
|
import { getFileURL } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { CoverImage } from "@/components/common/cover-image";
|
import { CoverImage } from "@/components/common/cover-image";
|
||||||
import { AppSidebarItem } from "@/components/sidebar/sidebar-item";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
||||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||||
|
|
@ -62,8 +60,8 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: TUserMenuRootP
|
||||||
else toggleAnySidebarDropdown(false);
|
else toggleAnySidebarDropdown(false);
|
||||||
}, [isUserMenuOpen, toggleAnySidebarDropdown]);
|
}, [isUserMenuOpen, toggleAnySidebarDropdown]);
|
||||||
|
|
||||||
const menuContent = (
|
const renderMenuContent = (closeDropdown: () => void) => (
|
||||||
<>
|
<div className="flex flex-col gap-y-3">
|
||||||
<div className="relative h-29 w-full rounded-lg">
|
<div className="relative h-29 w-full rounded-lg">
|
||||||
<CoverImage
|
<CoverImage
|
||||||
src={currentUser?.cover_image_url ?? undefined}
|
src={currentUser?.cover_image_url ?? undefined}
|
||||||
|
|
@ -93,50 +91,68 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: TUserMenuRootP
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CustomMenu.MenuItem
|
<button
|
||||||
onClick={() =>
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
toggleProfileSettingsModal({
|
toggleProfileSettingsModal({
|
||||||
activeTab: "general",
|
activeTab: "general",
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
})
|
});
|
||||||
}
|
closeDropdown();
|
||||||
className="flex items-center gap-2"
|
}}
|
||||||
|
className="flex w-full items-center gap-2 rounded-[0.9rem] px-2 py-2 text-left text-secondary transition-colors hover:bg-white/6"
|
||||||
>
|
>
|
||||||
<Settings className="size-3.5 shrink-0" />
|
<Settings className="size-3.5 shrink-0" />
|
||||||
{t("settings")}
|
{t("settings")}
|
||||||
</CustomMenu.MenuItem>
|
</button>
|
||||||
<CustomMenu.MenuItem
|
<button
|
||||||
onClick={() =>
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
toggleProfileSettingsModal({
|
toggleProfileSettingsModal({
|
||||||
activeTab: "preferences",
|
activeTab: "preferences",
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
})
|
});
|
||||||
}
|
closeDropdown();
|
||||||
className="flex items-center gap-2"
|
}}
|
||||||
|
className="flex w-full items-center gap-2 rounded-[0.9rem] px-2 py-2 text-left text-secondary transition-colors hover:bg-white/6"
|
||||||
>
|
>
|
||||||
<Settings2 className="size-3.5 shrink-0" />
|
<Settings2 className="size-3.5 shrink-0" />
|
||||||
{t("preferences")}
|
{t("preferences")}
|
||||||
</CustomMenu.MenuItem>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<CustomMenu.MenuItem onClick={handleSignOut} className="flex items-center gap-2">
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
handleSignOut();
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-2 rounded-[0.9rem] px-2 py-2 text-left text-secondary transition-colors hover:bg-white/6"
|
||||||
|
>
|
||||||
<LogOut className="size-3.5 shrink-0" />
|
<LogOut className="size-3.5 shrink-0" />
|
||||||
{t("sign_out")}
|
{t("sign_out")}
|
||||||
</CustomMenu.MenuItem>
|
</button>
|
||||||
{isUserInstanceAdmin && (
|
{isUserInstanceAdmin && (
|
||||||
<CustomMenu.MenuItem
|
<button
|
||||||
onClick={() => router.push(GOD_MODE_URL)}
|
type="button"
|
||||||
className="bg-accent-primary/20 text-accent-primary hover:bg-accent-primary/30 hover:text-accent-secondary"
|
onClick={() => {
|
||||||
|
router.push(GOD_MODE_URL);
|
||||||
|
closeDropdown();
|
||||||
|
}}
|
||||||
|
className="rounded-[0.9rem] bg-accent-primary/20 px-2 py-2 text-left text-accent-primary transition-colors hover:bg-accent-primary/30 hover:text-accent-secondary"
|
||||||
>
|
>
|
||||||
{t("enter_god_mode")}
|
{t("enter_god_mode")}
|
||||||
</CustomMenu.MenuItem>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isToolbarVariant) {
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className="relative">
|
<ActionDropdown
|
||||||
<Menu.Button
|
className="flex items-center"
|
||||||
|
buttonAsChild
|
||||||
|
button={
|
||||||
|
isToolbarVariant ? (
|
||||||
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={t("profile")}
|
aria-label={t("profile")}
|
||||||
className="flex size-8 items-center justify-center rounded-full border-0 bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07]"
|
className="flex size-8 items-center justify-center rounded-full border-0 bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07]"
|
||||||
|
|
@ -147,60 +163,44 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: TUserMenuRootP
|
||||||
size={18}
|
size={18}
|
||||||
shape="circle"
|
shape="circle"
|
||||||
/>
|
/>
|
||||||
</Menu.Button>
|
</button>
|
||||||
|
) : isSidebarUtilityVariant ? (
|
||||||
<Menu.Items className="absolute top-full left-0 z-[170] mt-2 origin-top-left">
|
<button
|
||||||
<div className="nodedc-glass-modal nodedc-glass-popup-surface flex w-72 flex-col gap-y-3 rounded-[1.25rem] border-0 p-3 shadow-none outline-none">
|
type="button"
|
||||||
{menuContent}
|
aria-label={t("profile")}
|
||||||
</div>
|
className="flex size-8 items-center justify-center rounded-full border-0 bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07]"
|
||||||
</Menu.Items>
|
>
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomMenu
|
|
||||||
className="flex items-center"
|
|
||||||
customButtonClassName={
|
|
||||||
isSidebarUtilityVariant
|
|
||||||
? "flex size-8 items-center justify-center rounded-full border-0 bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07]"
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
customButton={
|
|
||||||
isSidebarUtilityVariant ? (
|
|
||||||
<span className="pointer-events-none flex size-8 items-center justify-center">
|
|
||||||
<Avatar
|
<Avatar
|
||||||
name={currentUser?.display_name}
|
name={currentUser?.display_name}
|
||||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||||
size={18}
|
size={18}
|
||||||
shape="circle"
|
shape="circle"
|
||||||
/>
|
/>
|
||||||
</span>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<AppSidebarItem
|
<button type="button" className="group flex flex-col items-center justify-center gap-0.5 text-tertiary">
|
||||||
variant="button"
|
<div
|
||||||
item={{
|
className={`flex size-8 items-center justify-center gap-2 rounded-md ${
|
||||||
icon: (
|
isUserMenuOpen
|
||||||
|
? "bg-layer-transparent-selected text-secondary !text-icon-primary"
|
||||||
|
: "group-hover:text-icon-secondary group-hover:bg-layer-transparent-hover !text-icon-tertiary"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
name={currentUser?.display_name}
|
name={currentUser?.display_name}
|
||||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||||
size={20}
|
size={20}
|
||||||
shape="circle"
|
shape="circle"
|
||||||
/>
|
/>
|
||||||
),
|
</div>
|
||||||
isActive: isUserMenuOpen,
|
</button>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
menuButtonOnClick={() => !isUserMenuOpen && setIsUserMenuOpen(true)}
|
|
||||||
onMenuClose={() => setIsUserMenuOpen(false)}
|
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
maxHeight="2xl"
|
menuClassName="w-72 p-3"
|
||||||
optionsClassName="w-72 p-3 flex flex-col gap-y-3"
|
onOpenChange={setIsUserMenuOpen}
|
||||||
closeOnSelect
|
items={[]}
|
||||||
>
|
menuContent={({ closeDropdown }) => renderMenuContent(closeDropdown)}
|
||||||
{menuContent}
|
/>
|
||||||
</CustomMenu>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,17 @@
|
||||||
* See the LICENSE file for details.
|
* See the LICENSE file for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useRef } from "react";
|
import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { MoreHorizontal, ArchiveIcon, Settings } from "lucide-react";
|
import { MoreHorizontal, ArchiveIcon, Settings } from "lucide-react";
|
||||||
import { Disclosure } from "@headlessui/react";
|
import { Disclosure } from "@headlessui/react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { ChevronRightIcon } from "@plane/propel/icons";
|
import { ChevronRightIcon } from "@plane/propel/icons";
|
||||||
import { EUserWorkspaceRoles } from "@plane/types";
|
import { EUserWorkspaceRoles } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { ActionDropdown } from "@plane/ui";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// store hooks
|
// store hooks
|
||||||
import { useUserPermissions } from "@/hooks/store/user";
|
import { useUserPermissions } from "@/hooks/store/user";
|
||||||
|
|
@ -31,16 +30,11 @@ export const SidebarWorkspaceMenuHeader = observer(function SidebarWorkspaceMenu
|
||||||
const { isWorkspaceMenuOpen, toggleWorkspaceMenu } = props;
|
const { isWorkspaceMenuOpen, toggleWorkspaceMenu } = props;
|
||||||
// state
|
// state
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
// refs
|
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
// hooks
|
// hooks
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { allowPermissions } = useUserPermissions();
|
const { allowPermissions } = useUserPermissions();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
|
||||||
|
|
||||||
// TODO: fix types
|
// TODO: fix types
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const isAdmin = allowPermissions([EUserWorkspaceRoles.ADMIN] as any, EUserPermissionsLevel.WORKSPACE);
|
const isAdmin = allowPermissions([EUserWorkspaceRoles.ADMIN] as any, EUserPermissionsLevel.WORKSPACE);
|
||||||
|
|
@ -54,15 +48,9 @@ export const SidebarWorkspaceMenuHeader = observer(function SidebarWorkspaceMenu
|
||||||
>
|
>
|
||||||
<span>{t("workspace")}</span>
|
<span>{t("workspace")}</span>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
customButton={
|
button={
|
||||||
<span
|
<span className="my-auto grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1">
|
||||||
ref={actionSectionRef}
|
|
||||||
className="my-auto grid place-items-center rounded-sm p-0.5 text-placeholder hover:bg-layer-1"
|
|
||||||
onClick={() => {
|
|
||||||
setIsMenuActive(!isMenuActive);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MoreHorizontal className="size-4" />
|
<MoreHorizontal className="size-4" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
@ -72,25 +60,25 @@ export const SidebarWorkspaceMenuHeader = observer(function SidebarWorkspaceMenu
|
||||||
"pointer-events-auto opacity-100": isMenuActive,
|
"pointer-events-auto opacity-100": isMenuActive,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
customButtonClassName="grid place-items-center"
|
buttonClassName="grid place-items-center"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
>
|
onOpenChange={setIsMenuActive}
|
||||||
<CustomMenu.MenuItem onClick={() => router.push(`/${workspaceSlug}/projects/archives`)}>
|
items={[
|
||||||
<div className="flex items-center justify-start gap-2">
|
{
|
||||||
<ArchiveIcon className="h-3.5 w-3.5 stroke-[1.5]" />
|
key: "archives",
|
||||||
<span>{t("archives")}</span>
|
title: t("archives"),
|
||||||
</div>
|
icon: ArchiveIcon,
|
||||||
</CustomMenu.MenuItem>
|
action: () => router.push(`/${workspaceSlug}/projects/archives`),
|
||||||
|
},
|
||||||
{isAdmin && (
|
{
|
||||||
<CustomMenu.MenuItem onClick={() => router.push(`/${workspaceSlug}/settings`)}>
|
key: "settings",
|
||||||
<div className="flex items-center justify-start gap-2">
|
title: t("settings"),
|
||||||
<Settings className="h-3.5 w-3.5 stroke-[1.5]" />
|
icon: Settings,
|
||||||
<span>{t("settings")}</span>
|
action: () => router.push(`/${workspaceSlug}/settings`),
|
||||||
</div>
|
shouldRender: isAdmin,
|
||||||
</CustomMenu.MenuItem>
|
},
|
||||||
)}
|
]}
|
||||||
</CustomMenu>
|
/>
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
as="button"
|
as="button"
|
||||||
className="group/workspace-button sticky top-0 z-10 flex items-center justify-between gap-1 rounded-sm px-0.5 py-1.5 text-11 font-semibold text-placeholder hover:bg-surface-2"
|
className="group/workspace-button sticky top-0 z-10 flex items-center justify-between gap-1 rounded-sm px-0.5 py-1.5 text-11 font-semibold text-placeholder hover:bg-surface-2"
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||||
// ui
|
// ui
|
||||||
import type { TStaticViewTypes } from "@plane/types";
|
import type { TStaticViewTypes } from "@plane/types";
|
||||||
import type { TContextMenuItem } from "@plane/ui";
|
import type { TContextMenuItem } from "@plane/ui";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { ActionDropdown } from "@plane/ui";
|
||||||
import { copyUrlToClipboard, cn } from "@plane/utils";
|
import { copyUrlToClipboard } from "@plane/utils";
|
||||||
// helpers
|
// helpers
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
|
@ -57,46 +57,12 @@ export const DefaultWorkspaceViewQuickActions = observer(function DefaultWorkspa
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
ellipsis
|
className="flex-shrink-0"
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
closeOnSelect
|
buttonClassName="flex size-[26px] items-center justify-center rounded-sm bg-layer-1/70"
|
||||||
buttonClassName="flex-shrink-0 flex items-center justify-center size-[26px] bg-layer-1/70 rounded-sm"
|
items={MENU_ITEMS}
|
||||||
>
|
/>
|
||||||
{MENU_ITEMS.map((item) => {
|
|
||||||
if (item.shouldRender === false) return null;
|
|
||||||
return (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
key={item.key}
|
|
||||||
onClick={() => {
|
|
||||||
item.action();
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2",
|
|
||||||
{
|
|
||||||
"text-placeholder": item.disabled,
|
|
||||||
},
|
|
||||||
item.className
|
|
||||||
)}
|
|
||||||
disabled={item.disabled}
|
|
||||||
>
|
|
||||||
{item.icon && <item.icon className={cn("h-3 w-3", item.iconClassName)} />}
|
|
||||||
<div>
|
|
||||||
<h5>{t(item.title || "")}</h5>
|
|
||||||
{item.description && (
|
|
||||||
<p
|
|
||||||
className={cn("whitespace-pre-line text-tertiary", {
|
|
||||||
"text-placeholder": item.disabled,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{item.description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CustomMenu>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import { observer } from "mobx-react";
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||||
import type { IWorkspaceView } from "@plane/types";
|
import type { IWorkspaceView } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { ActionDropdown } from "@plane/ui";
|
||||||
import { copyUrlToClipboard, cn } from "@plane/utils";
|
import { copyUrlToClipboard } from "@plane/utils";
|
||||||
// helpers
|
// helpers
|
||||||
import { useViewMenuItems } from "@/components/common/quick-actions-helper";
|
import { useViewMenuItems } from "@/components/common/quick-actions-helper";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
@ -64,46 +64,12 @@ export const WorkspaceViewQuickActions = observer(function WorkspaceViewQuickAct
|
||||||
<>
|
<>
|
||||||
<CreateUpdateWorkspaceViewModal data={view} isOpen={updateViewModal} onClose={() => setUpdateViewModal(false)} />
|
<CreateUpdateWorkspaceViewModal data={view} isOpen={updateViewModal} onClose={() => setUpdateViewModal(false)} />
|
||||||
<DeleteGlobalViewModal data={view} isOpen={deleteViewModal} onClose={() => setDeleteViewModal(false)} />
|
<DeleteGlobalViewModal data={view} isOpen={deleteViewModal} onClose={() => setDeleteViewModal(false)} />
|
||||||
<CustomMenu
|
<ActionDropdown
|
||||||
ellipsis
|
className="flex-shrink-0"
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
closeOnSelect
|
buttonClassName="flex size-[26px] items-center justify-center rounded-sm bg-layer-1/70"
|
||||||
buttonClassName="flex-shrink-0 flex items-center justify-center size-[26px] bg-layer-1/70 rounded-sm"
|
items={MENU_ITEMS.items}
|
||||||
>
|
/>
|
||||||
{MENU_ITEMS.items.map((item) => {
|
|
||||||
if (item.shouldRender === false) return null;
|
|
||||||
return (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
key={item.key}
|
|
||||||
onClick={() => {
|
|
||||||
item.action();
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2",
|
|
||||||
{
|
|
||||||
"text-placeholder": item.disabled,
|
|
||||||
},
|
|
||||||
item.className
|
|
||||||
)}
|
|
||||||
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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import { useParams } from "next/navigation";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { EditIcon, TrashIcon } from "@plane/propel/icons";
|
import { EditIcon, TrashIcon } from "@plane/propel/icons";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import type { TContextMenuItem } from "@plane/ui";
|
||||||
|
import { ActionDropdown } from "@plane/ui";
|
||||||
import { truncateText } from "@plane/utils";
|
import { truncateText } from "@plane/utils";
|
||||||
// hooks
|
// hooks
|
||||||
import { useGlobalView } from "@/hooks/store/use-global-view";
|
import { useGlobalView } from "@/hooks/store/use-global-view";
|
||||||
|
|
@ -36,6 +37,25 @@ export const GlobalViewListItem = observer(function GlobalViewListItem(props: Pr
|
||||||
|
|
||||||
if (!view) return null;
|
if (!view) return null;
|
||||||
|
|
||||||
|
const menuItems: TContextMenuItem[] = [
|
||||||
|
{
|
||||||
|
key: "edit",
|
||||||
|
title: t("common.actions.edit"),
|
||||||
|
icon: EditIcon,
|
||||||
|
action: () => {
|
||||||
|
setUpdateViewModal(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "delete",
|
||||||
|
title: t("common.actions.delete"),
|
||||||
|
icon: TrashIcon,
|
||||||
|
action: () => {
|
||||||
|
setDeleteViewModal(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUpdateWorkspaceViewModal data={view} isOpen={updateViewModal} onClose={() => setUpdateViewModal(false)} />
|
<CreateUpdateWorkspaceViewModal data={view} isOpen={updateViewModal} onClose={() => setUpdateViewModal(false)} />
|
||||||
|
|
@ -52,28 +72,11 @@ export const GlobalViewListItem = observer(function GlobalViewListItem(props: Pr
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2 flex flex-shrink-0">
|
<div className="ml-2 flex flex-shrink-0">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<CustomMenu ellipsis>
|
<ActionDropdown
|
||||||
<CustomMenu.MenuItem
|
placement="bottom-end"
|
||||||
onClick={() => {
|
buttonClassName="grid size-7 place-items-center rounded-sm text-secondary transition-colors hover:bg-layer-transparent-hover"
|
||||||
setUpdateViewModal(true);
|
items={menuItems}
|
||||||
}}
|
/>
|
||||||
>
|
|
||||||
<span className="flex items-center justify-start gap-2">
|
|
||||||
<EditIcon width={14} height={14} strokeWidth={2} />
|
|
||||||
<span>{t("common.actions.edit")}</span>
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
setDeleteViewModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="flex items-center justify-start gap-2">
|
|
||||||
<TrashIcon width={14} height={14} strokeWidth={2} />
|
|
||||||
<span>{t("common.actions.delete")}</span>
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
</CustomMenu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue