UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: миграция mobile header, intake actions и member filters на общий канон

This commit is contained in:
DCCONSTRUCTIONS 2026-04-22 13:32:39 +03:00
parent bc8081c0f1
commit 305357478e
9 changed files with 236 additions and 315 deletions

View File

@ -12,9 +12,10 @@ import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissi
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { YourWorkIcon, ChevronDownIcon } from "@plane/propel/icons"; import { YourWorkIcon, ChevronDownIcon } from "@plane/propel/icons";
import type { IUserProfileProjectSegregation } from "@plane/types"; import type { IUserProfileProjectSegregation } from "@plane/types";
import { Breadcrumbs, Header, CustomMenu } from "@plane/ui"; import { Breadcrumbs, Header } from "@plane/ui";
// components // components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link"; import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { SelectionDropdown } from "@/components/common/selection-dropdown";
import { ProfileIssuesFilter } from "@/components/profile/profile-issues-filter"; import { ProfileIssuesFilter } from "@/components/profile/profile-issues-filter";
// hooks // hooks
import { useAppTheme } from "@/hooks/store/use-app-theme"; import { useAppTheme } from "@/hooks/store/use-app-theme";
@ -71,30 +72,22 @@ export const UserProfileHeader = observer(function UserProfileHeader(props: TUse
<Header.RightItem> <Header.RightItem>
<div className="hidden md:flex md:items-center">{showProfileIssuesFilter && <ProfileIssuesFilter />}</div> <div className="hidden md:flex md:items-center">{showProfileIssuesFilter && <ProfileIssuesFilter />}</div>
<div className="flex gap-4 md:hidden"> <div className="flex gap-4 md:hidden">
<CustomMenu <SelectionDropdown
maxHeight={"md"}
className="flex flex-grow justify-center text-13 text-secondary"
placement="bottom-start" placement="bottom-start"
customButton={ menuButton={
<div className="flex items-center gap-2 rounded-md border border-subtle px-2 py-1.5"> <div className="flex items-center gap-2 rounded-md border border-subtle px-2 py-1.5">
<span className="flex flex-grow justify-center text-13 text-secondary">{type}</span> <span className="flex flex-grow justify-center text-13 text-secondary">{type}</span>
<ChevronDownIcon className="h-4 w-4 text-placeholder" /> <ChevronDownIcon className="h-4 w-4 text-placeholder" />
</div> </div>
} }
customButtonClassName="flex flex-grow justify-center text-secondary text-13" menuButtonWrapperClassName="flex flex-grow justify-center text-secondary text-13"
closeOnSelect options={tabsList.map((tab) => ({
> key: tab.route,
<></> title: t(tab.i18n_label),
{tabsList.map((tab) => ( isChecked: type === tab.route,
<CustomMenu.MenuItem onClick: () => router.push(`/${workspaceSlug}/profile/${userId}/${tab.route}`),
className="flex items-center gap-2" }))}
key={tab.route} />
onClick={() => router.push(`/${workspaceSlug}/profile/${userId}/${tab.route}`)}
>
<span className="w-full text-tertiary">{t(tab.i18n_label)}</span>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="shrink-0 md:hidden"> <div className="shrink-0 md:hidden">
<Button <Button
variant="ghost" variant="ghost"

View File

@ -8,7 +8,7 @@ import { useCallback } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
// plane constants // plane constants
import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants"; import { EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
// plane i18n // plane i18n
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// icons // icons
@ -18,14 +18,15 @@ import type {
IIssueDisplayFilterOptions, IIssueDisplayFilterOptions,
IIssueDisplayProperties, IIssueDisplayProperties,
TIssueLayouts, TIssueLayouts,
EIssueLayoutTypes,
} from "@plane/types"; } from "@plane/types";
import { EIssuesStoreType } from "@plane/types"; import { EIssueLayoutTypes, EIssuesStoreType } from "@plane/types";
// ui // ui
import { CustomMenu } from "@plane/ui";
// components // components
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters"; import {
import { IssueLayoutIcon } from "@/components/issues/issue-layouts/layout-icon"; DisplayFiltersSelection,
FiltersDropdown,
MobileLayoutSelection,
} from "@/components/issues/issue-layouts/filters";
// hooks // hooks
import { useIssues } from "@/hooks/store/use-issues"; import { useIssues } from "@/hooks/store/use-issues";
@ -85,35 +86,11 @@ export const ProfileIssuesMobileHeader = observer(function ProfileIssuesMobileHe
return ( return (
<div className="flex justify-evenly border-b border-subtle py-2 md:hidden"> <div className="flex justify-evenly border-b border-subtle py-2 md:hidden">
<CustomMenu <MobileLayoutSelection
maxHeight={"md"} activeLayout={activeLayout}
className="flex flex-grow justify-center text-13 text-secondary" layouts={[EIssueLayoutTypes.LIST, EIssueLayoutTypes.KANBAN]}
placement="bottom-start" onChange={handleLayoutChange}
customButton={ />
<div className="flex-center flex text-13 text-secondary">
{t("common.layout")}
<ChevronDownIcon className="my-auto ml-2 h-4 w-4 text-secondary" strokeWidth={2} />
</div>
}
customButtonClassName="flex flex-center text-secondary text-13"
closeOnSelect
>
{ISSUE_LAYOUTS.map((layout, index) => {
if (layout.key === "spreadsheet" || layout.key === "gantt_chart" || layout.key === "calendar") return;
return (
<CustomMenu.MenuItem
key={index}
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
<div className="text-tertiary">{t(layout.i18n_title)}</div>
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
<div className="flex flex-grow items-center justify-center border-l border-subtle text-13 text-secondary"> <div className="flex flex-grow items-center justify-center border-l border-subtle text-13 text-secondary">
<FiltersDropdown <FiltersDropdown
title={t("common.display")} title={t("common.display")}

View File

@ -8,27 +8,23 @@ import { useCallback, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
// plane imports // plane imports
import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants"; import { EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { CalendarLayoutIcon, BoardLayoutIcon, ListLayoutIcon, ChevronDownIcon } from "@plane/propel/icons"; import { ChevronDownIcon } from "@plane/propel/icons";
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types"; import type { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
import { EIssuesStoreType } from "@plane/types"; import { EIssueLayoutTypes, EIssuesStoreType } from "@plane/types";
import { CustomMenu } from "@plane/ui";
// components // components
import { WorkItemsModal } from "@/components/analytics/work-items/modal"; import { WorkItemsModal } from "@/components/analytics/work-items/modal";
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters"; import {
import { IssueLayoutIcon } from "@/components/issues/issue-layouts/layout-icon"; DisplayFiltersSelection,
FiltersDropdown,
MobileLayoutSelection,
} from "@/components/issues/issue-layouts/filters";
// hooks // hooks
import { useCycle } from "@/hooks/store/use-cycle"; import { useCycle } from "@/hooks/store/use-cycle";
import { useIssues } from "@/hooks/store/use-issues"; import { useIssues } from "@/hooks/store/use-issues";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
const SUPPORTED_LAYOUTS = [
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: ListLayoutIcon },
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: BoardLayoutIcon },
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: CalendarLayoutIcon },
];
export const CycleIssuesMobileHeader = observer(function CycleIssuesMobileHeader() { export const CycleIssuesMobileHeader = observer(function CycleIssuesMobileHeader() {
// router // router
const { workspaceSlug, projectId, cycleId } = useParams(); const { workspaceSlug, projectId, cycleId } = useParams();
@ -97,29 +93,11 @@ export const CycleIssuesMobileHeader = observer(function CycleIssuesMobileHeader
cycleDetails={cycleDetails ?? undefined} cycleDetails={cycleDetails ?? undefined}
/> />
<div className="flex justify-evenly border-b border-subtle bg-surface-1 py-2 md:hidden"> <div className="flex justify-evenly border-b border-subtle bg-surface-1 py-2 md:hidden">
<CustomMenu <MobileLayoutSelection
maxHeight={"md"} activeLayout={activeLayout}
className="flex flex-grow justify-center text-13 text-secondary" layouts={[EIssueLayoutTypes.LIST, EIssueLayoutTypes.KANBAN, EIssueLayoutTypes.CALENDAR]}
placement="bottom-start" onChange={handleLayoutChange}
customButton={ />
<span className="flex flex-grow justify-center text-13 text-secondary">{t("common.layout")}</span>
}
customButtonClassName="flex flex-grow justify-center text-secondary text-13"
closeOnSelect
>
{SUPPORTED_LAYOUTS.map((layout, index) => (
<CustomMenu.MenuItem
key={ISSUE_LAYOUTS[index].key}
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
<div className="text-tertiary">{t(layout.titleTranslationKey)}</div>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="flex flex-grow items-center justify-center border-l border-subtle text-13 text-secondary"> <div className="flex flex-grow items-center justify-center border-l border-subtle text-13 text-secondary">
<FiltersDropdown <FiltersDropdown
title={t("common.display")} title={t("common.display")}

View File

@ -11,7 +11,7 @@ import type { ISvgIcons } from "@plane/propel/icons";
import { TimelineLayoutIcon, GridLayoutIcon, ListLayoutIcon } from "@plane/propel/icons"; import { TimelineLayoutIcon, GridLayoutIcon, ListLayoutIcon } from "@plane/propel/icons";
// plane package imports // plane package imports
import type { TCycleLayoutOptions } from "@plane/types"; import type { TCycleLayoutOptions } from "@plane/types";
import { CustomMenu } from "@plane/ui"; import { SelectionDropdown } from "@/components/common/selection-dropdown";
// hooks // hooks
import { useCycleFilter } from "@/hooks/store/use-cycle-filter"; import { useCycleFilter } from "@/hooks/store/use-cycle-filter";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
@ -41,40 +41,29 @@ const CYCLE_VIEW_LAYOUTS: {
export const CyclesListMobileHeader = observer(function CyclesListMobileHeader() { export const CyclesListMobileHeader = observer(function CyclesListMobileHeader() {
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
// hooks // hooks
const { updateDisplayFilters } = useCycleFilter(); const { currentProjectDisplayFilters, updateDisplayFilters } = useCycleFilter();
return ( return (
<div className="flex justify-center sm:hidden"> <div className="flex justify-center sm:hidden">
<CustomMenu <SelectionDropdown
maxHeight={"md"} placement="bottom-start"
className="flex flex-grow justify-center border-b border-subtle bg-surface-1 py-2 text-13 text-secondary" menuButton={
// placement="bottom-start"
customButton={
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<ListLayoutIcon className="h-4 w-4" /> <ListLayoutIcon className="h-4 w-4" />
<span className="flex flex-grow justify-center text-13 text-secondary">Layout</span> <span className="flex flex-grow justify-center text-13 text-secondary">Layout</span>
</span> </span>
} }
customButtonClassName="flex flex-grow justify-center items-center text-secondary text-13" menuButtonWrapperClassName="flex flex-grow justify-center items-center border-b border-subtle bg-surface-1 py-2 text-secondary text-13"
closeOnSelect options={CYCLE_VIEW_LAYOUTS.filter((layout) => layout.key !== "gantt").map((layout) => ({
> key: layout.key,
{CYCLE_VIEW_LAYOUTS.map((layout) => { icon: <layout.icon className="h-3 w-3" />,
if (layout.key == "gantt") return; title: layout.title,
return ( isChecked: currentProjectDisplayFilters?.layout === layout.key,
<CustomMenu.MenuItem onClick: () =>
key={layout.key}
onClick={() => {
updateDisplayFilters(currentProjectDetails!.id, { updateDisplayFilters(currentProjectDetails!.id, {
layout: layout.key, layout: layout.key,
}); }),
}} }))}
className="flex items-center gap-2" />
>
<layout.icon className="h-3 w-3" />
<div className="text-tertiary">{layout.title}</div>
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
</div> </div>
); );
}); });

View File

@ -8,27 +8,23 @@ import { useCallback, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
// plane imports // plane imports
import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants"; import { EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { CalendarLayoutIcon, BoardLayoutIcon, ListLayoutIcon, ChevronDownIcon } from "@plane/propel/icons"; import { ChevronDownIcon } from "@plane/propel/icons";
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types"; import type { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
import { EIssuesStoreType } from "@plane/types"; import { EIssueLayoutTypes, EIssuesStoreType } from "@plane/types";
import { CustomMenu } from "@plane/ui";
// components // components
import { WorkItemsModal } from "@/components/analytics/work-items/modal"; import { WorkItemsModal } from "@/components/analytics/work-items/modal";
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters"; import {
import { IssueLayoutIcon } from "@/components/issues/issue-layouts/layout-icon"; DisplayFiltersSelection,
FiltersDropdown,
MobileLayoutSelection,
} from "@/components/issues/issue-layouts/filters";
// hooks // hooks
import { useIssues } from "@/hooks/store/use-issues"; import { useIssues } from "@/hooks/store/use-issues";
import { useModule } from "@/hooks/store/use-module"; import { useModule } from "@/hooks/store/use-module";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
const SUPPORTED_LAYOUTS = [
{ key: "list", i18n_title: "issue.layouts.list", icon: ListLayoutIcon },
{ key: "kanban", i18n_title: "issue.layouts.kanban", icon: BoardLayoutIcon },
{ key: "calendar", i18n_title: "issue.layouts.calendar", icon: CalendarLayoutIcon },
];
export const ModuleIssuesMobileHeader = observer(function ModuleIssuesMobileHeader() { export const ModuleIssuesMobileHeader = observer(function ModuleIssuesMobileHeader() {
// router // router
const { workspaceSlug, projectId, moduleId } = useParams(); const { workspaceSlug, projectId, moduleId } = useParams();
@ -79,27 +75,11 @@ export const ModuleIssuesMobileHeader = observer(function ModuleIssuesMobileHead
projectDetails={currentProjectDetails} projectDetails={currentProjectDetails}
/> />
<div className="flex justify-evenly border-b border-subtle bg-surface-1 py-2"> <div className="flex justify-evenly border-b border-subtle bg-surface-1 py-2">
<CustomMenu <MobileLayoutSelection
maxHeight={"md"} activeLayout={activeLayout}
className="flex flex-grow justify-center text-13 text-secondary" layouts={[EIssueLayoutTypes.LIST, EIssueLayoutTypes.KANBAN, EIssueLayoutTypes.CALENDAR]}
placement="bottom-start" onChange={handleLayoutChange}
customButton={<span className="flex flex-grow justify-center text-13 text-secondary">Layout</span>} />
customButtonClassName="flex flex-grow justify-center text-secondary text-13"
closeOnSelect
>
{SUPPORTED_LAYOUTS.map((layout, index) => (
<CustomMenu.MenuItem
key={layout.key}
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
<div className="text-tertiary">{t(layout.i18n_title)}</div>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="flex flex-grow items-center justify-center border-l border-subtle text-13 text-secondary"> <div className="flex flex-grow items-center justify-center border-l border-subtle text-13 text-secondary">
<FiltersDropdown <FiltersDropdown
title="Display" title="Display"

View File

@ -8,46 +8,35 @@ import { observer } from "mobx-react";
import { MODULE_VIEW_LAYOUTS } from "@plane/constants"; import { MODULE_VIEW_LAYOUTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { ChevronDownIcon } from "@plane/propel/icons"; import { ChevronDownIcon } from "@plane/propel/icons";
import { CustomMenu, Row } from "@plane/ui"; import { Row } from "@plane/ui";
import { SelectionDropdown } from "@/components/common/selection-dropdown";
import { ModuleLayoutIcon } from "@/components/modules"; import { ModuleLayoutIcon } from "@/components/modules";
import { useModuleFilter } from "@/hooks/store/use-module-filter"; import { useModuleFilter } from "@/hooks/store/use-module-filter";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
export const ModulesListMobileHeader = observer(function ModulesListMobileHeader() { export const ModulesListMobileHeader = observer(function ModulesListMobileHeader() {
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
const { updateDisplayFilters } = useModuleFilter(); const { currentProjectDisplayFilters, updateDisplayFilters } = useModuleFilter();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="flex justify-start md:hidden"> <div className="flex justify-start md:hidden">
<CustomMenu <SelectionDropdown
maxHeight={"md"} placement="bottom-start"
className="flex flex-grow justify-start border-b border-subtle bg-surface-1 py-2 text-13 text-secondary" menuButton={
// placement="bottom-start"
customButton={
<Row className="flex flex-grow justify-center gap-2 text-13 text-secondary"> <Row className="flex flex-grow justify-center gap-2 text-13 text-secondary">
<span>Layout</span> <ChevronDownIcon className="my-auto h-4 w-4 text-secondary" strokeWidth={1} /> <span>Layout</span> <ChevronDownIcon className="my-auto h-4 w-4 text-secondary" strokeWidth={1} />
</Row> </Row>
} }
customButtonClassName="flex flex-grow justify-center items-center text-secondary text-13" menuButtonWrapperClassName="flex flex-grow justify-center items-center border-b border-subtle bg-surface-1 py-2 text-secondary text-13"
closeOnSelect options={MODULE_VIEW_LAYOUTS.filter((layout) => layout.key !== "gantt").map((layout) => ({
> key: layout.key,
{MODULE_VIEW_LAYOUTS.map((layout) => { icon: <ModuleLayoutIcon layoutType={layout.key} />,
if (layout.key == "gantt") return; title: t(layout.i18n_title),
return ( isChecked: currentProjectDisplayFilters?.layout === layout.key,
<CustomMenu.MenuItem onClick: () => updateDisplayFilters(currentProjectDetails!.id.toString(), { layout: layout.key }),
key={layout.key} }))}
onClick={() => { />
updateDisplayFilters(currentProjectDetails!.id.toString(), { layout: layout.key });
}}
className="flex items-center gap-2"
>
<ModuleLayoutIcon layoutType={layout.key} />
<div className="text-tertiary">{t(layout.i18n_title)}</div>
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
</div> </div>
); );
}); });

View File

@ -25,7 +25,7 @@ import {
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TNameDescriptionLoader } from "@plane/types"; import type { TNameDescriptionLoader } from "@plane/types";
import { EInboxIssueStatus } from "@plane/types"; import { EInboxIssueStatus } from "@plane/types";
import { ControlLink, CustomMenu } from "@plane/ui"; import { ActionDropdown, ControlLink } from "@plane/ui";
import { copyUrlToClipboard, findHowManyDaysLeft, generateWorkItemLink } from "@plane/utils"; import { copyUrlToClipboard, findHowManyDaysLeft, generateWorkItemLink } from "@plane/utils";
// components // components
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal"; import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal";
@ -348,61 +348,68 @@ export const InboxIssueActionsHeader = observer(function InboxIssueActionsHeader
) : ( ) : (
<> <>
{isAllowed && ( {isAllowed && (
<CustomMenu <ActionDropdown
customButton={<MoreHorizontal className="size-4" />} button={<MoreHorizontal className="size-4" />}
customButtonClassName="nodedc-external-icon-button" buttonClassName="nodedc-external-icon-button"
placement="bottom-start" placement="bottom-start"
menuItemsClassName="z-[760]" items={[
> {
{canMarkAsAccepted && ( key: "snooze",
<CustomMenu.MenuItem shouldRender: canMarkAsAccepted,
onClick={() => action: () =>
handleActionWithPermission( handleActionWithPermission(
isProjectAdmin, isProjectAdmin,
handleIssueSnoozeAction, handleIssueSnoozeAction,
t("inbox_issue.errors.snooze_permission") t("inbox_issue.errors.snooze_permission")
) ),
} customContent: (
>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Clock size={14} strokeWidth={2} /> <Clock size={14} strokeWidth={2} />
{inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0 {inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0
? t("inbox_issue.actions.unsnooze") ? t("inbox_issue.actions.unsnooze")
: t("inbox_issue.actions.snooze")} : t("inbox_issue.actions.snooze")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
{canMarkAsDuplicate && ( {
<CustomMenu.MenuItem key: "duplicate",
onClick={() => shouldRender: canMarkAsDuplicate,
action: () =>
handleActionWithPermission( handleActionWithPermission(
isProjectAdmin, isProjectAdmin,
() => setSelectDuplicateIssue(true), () => setSelectDuplicateIssue(true),
t("inbox_issue.errors.duplicate_permission") t("inbox_issue.errors.duplicate_permission")
) ),
} customContent: (
>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FileStack size={14} strokeWidth={2} /> <FileStack size={14} strokeWidth={2} />
{t("inbox_issue.actions.mark_as_duplicate")} {t("inbox_issue.actions.mark_as_duplicate")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
<CustomMenu.MenuItem onClick={() => handleCopyIssueLink(workItemLink)}> {
key: "copy",
action: () => handleCopyIssueLink(workItemLink),
customContent: (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<CopyIcon width={14} height={14} strokeWidth={2} /> <CopyIcon width={14} height={14} strokeWidth={2} />
{t("inbox_issue.actions.copy")} {t("inbox_issue.actions.copy")}
</div> </div>
</CustomMenu.MenuItem> ),
{canDelete && ( },
<CustomMenu.MenuItem onClick={() => setDeleteIssueModal(true)}> {
key: "delete",
shouldRender: canDelete,
action: () => setDeleteIssueModal(true),
customContent: (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<TrashIcon width={14} height={14} strokeWidth={2} /> <TrashIcon width={14} height={14} strokeWidth={2} />
{t("inbox_issue.actions.delete")} {t("inbox_issue.actions.delete")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
</CustomMenu> ]}
/>
)} )}
</> </>
)} )}

View File

@ -19,7 +19,7 @@ import {
} from "@plane/propel/icons"; } from "@plane/propel/icons";
import type { TNameDescriptionLoader } from "@plane/types"; import type { TNameDescriptionLoader } from "@plane/types";
import { Header, CustomMenu, EHeaderVariant } from "@plane/ui"; import { ActionDropdown, Header, EHeaderVariant } from "@plane/ui";
import { cn, findHowManyDaysLeft, generateWorkItemLink } from "@plane/utils"; import { cn, findHowManyDaysLeft, generateWorkItemLink } from "@plane/utils";
// components // components
import { NameDescriptionUpdateStatus } from "@/components/issues/issue-update-status"; import { NameDescriptionUpdateStatus } from "@/components/issues/issue-update-status";
@ -136,102 +136,112 @@ export const InboxIssueActionsMobileHeader = observer(function InboxIssueActions
</div> </div>
</div> </div>
<div className="ml-auto"> <div className="ml-auto">
<CustomMenu <ActionDropdown
customButton={<MoreHorizontal className="size-4" />} button={<MoreHorizontal className="size-4" />}
customButtonClassName={getIconButtonStyling("secondary", "lg")} buttonClassName={getIconButtonStyling("secondary", "lg")}
placement="bottom-start" placement="bottom-start"
> items={[
{isAcceptedOrDeclined && ( {
<CustomMenu.MenuItem onClick={handleCopyIssueLink}> key: "copy",
shouldRender: !!isAcceptedOrDeclined,
action: handleCopyIssueLink,
customContent: (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<LinkIcon width={14} height={14} strokeWidth={2} /> <LinkIcon width={14} height={14} strokeWidth={2} />
{t("inbox_issue.actions.copy")} {t("inbox_issue.actions.copy")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
{isAcceptedOrDeclined && ( {
<CustomMenu.MenuItem onClick={() => router.push(workItemLink)}> key: "open",
shouldRender: !!isAcceptedOrDeclined,
action: () => router.push(workItemLink),
customContent: (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<NewTabIcon width={14} height={14} strokeWidth={2} /> <NewTabIcon width={14} height={14} strokeWidth={2} />
{t("inbox_issue.actions.open")} {t("inbox_issue.actions.open")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
{canMarkAsAccepted && !isAcceptedOrDeclined && ( {
<CustomMenu.MenuItem key: "snooze",
onClick={() => shouldRender: canMarkAsAccepted && !isAcceptedOrDeclined,
action: () =>
handleActionWithPermission( handleActionWithPermission(
isProjectAdmin, isProjectAdmin,
handleIssueSnoozeAction, handleIssueSnoozeAction,
t("inbox_issue.errors.snooze_permission") t("inbox_issue.errors.snooze_permission")
) ),
} customContent: (
>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Clock size={14} strokeWidth={2} /> <Clock size={14} strokeWidth={2} />
{inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0 {inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0
? t("inbox_issue.actions.unsnooze") ? t("inbox_issue.actions.unsnooze")
: t("inbox_issue.actions.snooze")} : t("inbox_issue.actions.snooze")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
{canMarkAsDuplicate && !isAcceptedOrDeclined && ( {
<CustomMenu.MenuItem key: "duplicate",
onClick={() => shouldRender: canMarkAsDuplicate && !isAcceptedOrDeclined,
action: () =>
handleActionWithPermission( handleActionWithPermission(
isProjectAdmin, isProjectAdmin,
() => setSelectDuplicateIssue(true), () => setSelectDuplicateIssue(true),
t("inbox_issue.errors.duplicate_permission") t("inbox_issue.errors.duplicate_permission")
) ),
} customContent: (
>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FileStack size={14} strokeWidth={2} /> <FileStack size={14} strokeWidth={2} />
{t("inbox_issue.actions.mark_as_duplicate")} {t("inbox_issue.actions.mark_as_duplicate")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
{canMarkAsAccepted && ( {
<CustomMenu.MenuItem key: "accept",
onClick={() => shouldRender: canMarkAsAccepted,
action: () =>
handleActionWithPermission( handleActionWithPermission(
isProjectAdmin, isProjectAdmin,
() => setAcceptIssueModal(true), () => setAcceptIssueModal(true),
t("inbox_issue.errors.accept_permission") t("inbox_issue.errors.accept_permission")
) ),
} customContent: (
>
<div className="flex items-center gap-2 text-success-secondary"> <div className="flex items-center gap-2 text-success-secondary">
<CheckCircleFilledIcon width={14} height={14} /> <CheckCircleFilledIcon width={14} height={14} />
{t("inbox_issue.actions.accept")} {t("inbox_issue.actions.accept")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
{canMarkAsDeclined && ( {
<CustomMenu.MenuItem key: "decline",
onClick={() => shouldRender: canMarkAsDeclined,
action: () =>
handleActionWithPermission( handleActionWithPermission(
isProjectAdmin, isProjectAdmin,
() => setDeclineIssueModal(true), () => setDeclineIssueModal(true),
t("inbox_issue.errors.decline_permission") t("inbox_issue.errors.decline_permission")
) ),
} customContent: (
>
<div className="flex items-center gap-2 text-danger-secondary"> <div className="flex items-center gap-2 text-danger-secondary">
<CloseCircleFilledIcon width={14} height={14} /> <CloseCircleFilledIcon width={14} height={14} />
{t("inbox_issue.actions.decline")} {t("inbox_issue.actions.decline")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
{canDelete && !isAcceptedOrDeclined && ( {
<CustomMenu.MenuItem onClick={() => setDeleteIssueModal(true)}> key: "delete",
shouldRender: canDelete && !isAcceptedOrDeclined,
action: () => setDeleteIssueModal(true),
customContent: (
<div className="flex items-center gap-2 text-danger-primary"> <div className="flex items-center gap-2 text-danger-primary">
<TrashIcon height={14} width={14} strokeWidth={2} /> <TrashIcon height={14} width={14} strokeWidth={2} />
{t("inbox_issue.actions.delete")} {t("inbox_issue.actions.delete")}
</div> </div>
</CustomMenu.MenuItem> ),
)} },
</CustomMenu> ]}
/>
</div> </div>
</div> </div>
</Header> </Header>

View File

@ -8,13 +8,11 @@ import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// plane imports // plane imports
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { ChevronDownIcon } from "@plane/propel/icons"; import { ChevronDownIcon } from "@plane/propel/icons";
import { EUserProjectRoles, EUserWorkspaceRoles } from "@plane/types"; import { EUserProjectRoles, EUserWorkspaceRoles } from "@plane/types";
// plane ui // plane ui
import { CustomMenu } from "@plane/ui";
// components // components
import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters"; import { FilterHeader, FilterOption, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
interface IRoleOption { interface IRoleOption {
value: string; value: string;
@ -101,13 +99,11 @@ export const MemberListFiltersDropdown = observer(function MemberListFiltersDrop
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<CustomMenu <FiltersDropdown
customButton={ menuButton={
<div className="relative"> <div className="nodedc-settings-secondary-button relative flex items-center gap-2">
<Button variant="ghost" size="lg" className="nodedc-settings-secondary-button flex items-center gap-2">
<span>{t("filters")}</span> <span>{t("filters")}</span>
<ChevronDownIcon className="h-3 w-3" /> <ChevronDownIcon className="h-3 w-3" />
</Button>
{appliedFiltersCount > 0 && ( {appliedFiltersCount > 0 && (
<div className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-accent-primary" /> <div className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-accent-primary" />
)} )}
@ -115,7 +111,9 @@ export const MemberListFiltersDropdown = observer(function MemberListFiltersDrop
} }
placement="bottom-start" placement="bottom-start"
> >
<div className="vertical-scrollbar relative h-full w-full overflow-y-auto px-2.5 py-2">
<MemberListFilters appliedFilters={appliedFilters} handleUpdate={handleUpdate} memberType={memberType} /> <MemberListFilters appliedFilters={appliedFilters} handleUpdate={handleUpdate} memberType={memberType} />
</CustomMenu> </div>
</FiltersDropdown>
); );
}); });