UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: миграция mobile header, intake actions и member filters на общий канон
This commit is contained in:
parent
bc8081c0f1
commit
305357478e
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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")}
|
||||||
|
|
|
||||||
|
|
@ -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")}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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>
|
]}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue