[WEB-4050] feat: breadcrumbs revamp (#7188)

* chore: project feature enum added

* feat: revamp breadcrumb and add navigation dropdown component

* chore: custom search select component refactoring

* chore: breadcrumb stories added

* chore: switch label and breadcrumb link component refactor

* chore: project navigation helper function added

* chore: common breadcrumb component added

* chore: breadcrumb refactoring

* chore: code refactor

* chore: code refactor

* fix: build error

* fix: nprogress and button tooltip

* chore: code refactor

* chore: workspace view breadcrumb improvements

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor

---------

Co-authored-by: vamsikrishnamathala <matalav55@gmail.com>
This commit is contained in:
Anmol Singh Bhatia 2025-06-19 17:17:14 +05:30 committed by GitHub
parent 64fd0b2830
commit 2b7a17b484
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 1251 additions and 581 deletions

View file

@ -8,7 +8,6 @@ import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
// components
import { PageHead } from "@/components/core";
import { AllIssueLayoutRoot, GlobalViewsAppliedFiltersRoot } from "@/components/issues";
import { GlobalViewsHeader } from "@/components/workspace";
// constants
// hooks
import { useWorkspace } from "@/hooks/store";
@ -32,7 +31,6 @@ const GlobalViewIssuesPage = observer(() => {
<PageHead title={pageTitle} />
<div className="h-full overflow-hidden bg-custom-background-100">
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
<GlobalViewsHeader />
{globalViewId && (
<GlobalViewsAppliedFiltersRoot globalViewId={globalViewId.toString()} isLoading={isLoading} />
)}

View file

@ -5,32 +5,49 @@ import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Layers } from "lucide-react";
// plane constants
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
import {
DEFAULT_GLOBAL_VIEWS_LIST,
EIssueFilterType,
EIssuesStoreType,
ISSUE_DISPLAY_FILTERS_BY_PAGE,
EIssueLayoutTypes
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
import {
ICustomSearchSelectOption,
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
IIssueFilterOptions,
} from "@plane/types";
// ui
import { Breadcrumbs, Button, Header } from "@plane/ui";
import { Breadcrumbs, Button, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
// components
import { isIssueFilterActive } from "@plane/utils";
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, SwitcherLabel } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/components/issues";
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
import {
CreateUpdateWorkspaceViewModal,
WorkspaceViewQuickActions,
DefaultWorkspaceViewQuickActions,
} from "@/components/workspace";
// helpers
// hooks
import { useLabel, useMember, useIssues, useGlobalView } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { GlobalViewLayoutSelection } from "@/plane-web/components/views/helper";
export const GlobalIssuesHeader = observer(() => {
// states
const [createViewModal, setCreateViewModal] = useState(false);
// router
const router = useAppRouter();
const { workspaceSlug, globalViewId } = useParams();
// store hooks
const {
issuesFilter: { filters, updateFilters },
} = useIssues(EIssuesStoreType.GLOBAL);
const { getViewDetailsById } = useGlobalView();
const { getViewDetailsById, currentWorkspaceViews } = useGlobalView();
const { workspaceLabels } = useLabel();
const {
workspace: { workspaceMemberIds },
@ -113,6 +130,29 @@ export const GlobalIssuesHeader = observer(() => {
const isLocked = viewDetails?.is_locked;
const isDefaultView = DEFAULT_GLOBAL_VIEWS_LIST.find((view) => view.key === globalViewId);
const defaultViewDetails = DEFAULT_GLOBAL_VIEWS_LIST.find((view) => view.key === globalViewId);
const defaultOptions = DEFAULT_GLOBAL_VIEWS_LIST.map((view) => ({
value: view.key,
query: view.key,
content: <SwitcherLabel name={t(view.i18n_label)} LabelIcon={Layers} />,
}));
const workspaceOptions = (currentWorkspaceViews || []).map((view) => {
const _view = getViewDetailsById(view);
if (!_view) return;
return {
value: _view.id,
query: _view.name,
content: <SwitcherLabel name={_view.name} LabelIcon={Layers} />,
};
});
const switcherOptions = [...defaultOptions, ...workspaceOptions].filter(
(option) => option !== undefined
) as ICustomSearchSelectOption[];
const currentLayoutFilters = useMemo(() => {
const layout = activeLayout ?? EIssueLayoutTypes.SPREADSHEET;
return ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues[layout];
@ -124,9 +164,29 @@ export const GlobalIssuesHeader = observer(() => {
<Header>
<Header.LeftItem>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label={t("views")} icon={<Layers className="h-4 w-4 text-custom-text-300" />} />}
<Breadcrumbs.Item
component={
<BreadcrumbLink label={t("views")} icon={<Layers className="h-4 w-4 text-custom-text-300" />} />
}
/>
<Breadcrumbs.Item
component={
<BreadcrumbNavigationSearchDropdown
selectedItem={globalViewId?.toString() || ""}
navigationItems={switcherOptions}
onChange={(value: string) => {
router.push(`/${workspaceSlug}/workspace-views/${value}`);
}}
title={viewDetails?.name ?? t(defaultViewDetails?.i18n_label ?? "")}
icon={
<Breadcrumbs.Icon>
<Layers className="size-4 flex-shrink-0 text-custom-text-300" />
</Breadcrumbs.Icon>
}
isLast
/>
}
isLast
/>
</Breadcrumbs>
</Header.LeftItem>
@ -171,6 +231,12 @@ export const GlobalIssuesHeader = observer(() => {
<Button variant="primary" size="sm" onClick={() => setCreateViewModal(true)}>
{t("workspace_views.add_view")}
</Button>
<div className="hidden md:block">
{viewDetails && <WorkspaceViewQuickActions workspaceSlug={workspaceSlug?.toString()} view={viewDetails} />}
{isDefaultView && defaultViewDetails && (
<DefaultWorkspaceViewQuickActions workspaceSlug={workspaceSlug?.toString()} view={defaultViewDetails} />
)}
</div>
</Header.RightItem>
</Header>
</>