chore: breadcrumbs ui revamp and refactor (#2634)

This commit is contained in:
Anmol Singh Bhatia 2023-11-03 18:01:49 +05:30 committed by GitHub
parent 7eeac188d7
commit 8919b724c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 672 additions and 387 deletions

View File

@ -1,58 +1,80 @@
import * as React from "react"; import * as React from "react";
// icons // icons
import { MoveLeft } from "lucide-react"; import { ChevronRight } from "lucide-react";
// components
import { Tooltip } from "../tooltip";
type BreadcrumbsProps = { type BreadcrumbsProps = {
onBack: () => void;
children: any; children: any;
}; };
const Breadcrumbs = ({ onBack, children }: BreadcrumbsProps) => ( const Breadcrumbs = ({ children }: BreadcrumbsProps) => (
<> <div className="flex items-center space-x-2">
<div className="flex w-full flex-grow items-center overflow-hidden overflow-ellipsis whitespace-nowrap"> {React.Children.map(children, (child, index) => (
<button <div key={index} className="flex items-center flex-wrap gap-2.5">
type="button" {child}
className="group grid h-7 w-7 flex-shrink-0 cursor-pointer place-items-center rounded border border-custom-sidebar-border-200 text-center text-sm hover:bg-custom-sidebar-background-90" {index !== React.Children.count(children) - 1 && (
onClick={onBack} <ChevronRight
> className="h-3.5 w-3.5 flex-shrink-0 text-custom-text-400"
<MoveLeft className="h-4 w-4 text-custom-sidebar-text-200 group-hover:text-custom-sidebar-text-100" /> aria-hidden="true"
</button> />
{children} )}
</div>
))}
</div> </div>
</>
); );
type BreadcrumbItemProps = { type Props = {
title?: string; type?: "text" | "component";
link?: JSX.Element; component?: React.ReactNode;
icon?: any; label?: string;
unshrinkTitle?: boolean; icon?: React.ReactNode;
link?: string;
}; };
const BreadcrumbItem: React.FC<Props> = (props) => {
const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({ const { type = "text", component, label, icon, link } = props;
title, return (
link,
icon,
unshrinkTitle = false,
}) => (
<> <>
{link ? ( {type != "text" ? (
link <div className="flex items-center space-x-2">{component}</div>
) : ( ) : (
<div <Tooltip tooltipContent={label} position="bottom">
className={`truncate px-3 text-sm ${ <li className="flex items-center space-x-2">
unshrinkTitle ? "flex-shrink-0" : "" <div className="flex items-center flex-wrap gap-2.5">
}`} {link ? (
<a
className="flex items-center gap-1 text-sm font-medium text-custom-text-300 hover:text-custom-text-100"
href={link}
> >
<p className={`truncate ${icon ? "flex items-center gap-2" : ""}`}> {icon && (
<div className="overflow-hidden w-5 h-5 flex justify-center items-center !text-[1rem]">
{icon} {icon}
<span className="break-words">{title}</span>
</p>
</div> </div>
)} )}
<div className="relative block overflow-hidden truncate line-clamp-1 max-w-[150px]">
{label}
</div>
</a>
) : (
<div className="flex items-center gap-1 text-sm font-medium text-custom-text-100 cursor-default">
{icon && (
<div className="overflow-hidden w-5 h-5 flex justify-center items-center">
{icon}
</div>
)}
<div className="relative block overflow-hidden truncate line-clamp-1 max-w-[150px]">
{label}
</div>
</div>
)}
</div>
</li>
</Tooltip>
)}
</> </>
); );
};
Breadcrumbs.BreadcrumbItem = BreadcrumbItem; Breadcrumbs.BreadcrumbItem = BreadcrumbItem;

View File

@ -1,6 +1,5 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
@ -10,11 +9,12 @@ import useLocalStorage from "hooks/use-local-storage";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
import { ProjectAnalyticsModal } from "components/analytics"; import { ProjectAnalyticsModal } from "components/analytics";
// ui // ui
import { Breadcrumbs, Button, CustomMenu } from "@plane/ui"; import { Breadcrumbs, Button, ContrastIcon, CustomMenu } from "@plane/ui";
// icons // icons
import { ArrowRight, ContrastIcon, Plus } from "lucide-react"; import { ArrowRight, Plus } from "lucide-react";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants // constants
@ -32,6 +32,8 @@ export const CycleIssuesHeader: React.FC = observer(() => {
cycleIssueFilter: cycleIssueFilterStore, cycleIssueFilter: cycleIssueFilterStore,
project: projectStore, project: projectStore,
} = useMobxStore(); } = useMobxStore();
const { currentProjectDetails } = projectStore;
const activeLayout = issueFilterStore.userDisplayFilters.layout; const activeLayout = issueFilterStore.userDisplayFilters.layout;
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false"); const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
@ -110,18 +112,34 @@ export const CycleIssuesHeader: React.FC = observer(() => {
/> />
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<Breadcrumbs.BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles`}> icon={
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> currentProjectDetails?.emoji ? (
<p className="truncate">{`${truncateText(cycleDetails?.project_detail.name ?? "", 32)} Cycles`}</p> renderEmoji(currentProjectDetails.emoji)
</a> ) : currentProjectDetails?.icon_prop ? (
</Link> renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="flex items-center justify-center h-4 w-4 rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/> />
</Breadcrumbs> <Breadcrumbs.BreadcrumbItem
type="text"
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
label="Cycles"
link={`/${workspaceSlug}/projects/${projectId}/cycles`}
/>
<Breadcrumbs.BreadcrumbItem
type="component"
component={
<CustomMenu <CustomMenu
placement="bottom-start"
label={ label={
<> <>
<ContrastIcon className="h-3 w-3" /> <ContrastIcon className="h-3 w-3" />
@ -140,6 +158,9 @@ export const CycleIssuesHeader: React.FC = observer(() => {
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
))} ))}
</CustomMenu> </CustomMenu>
}
/>
</Breadcrumbs>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<LayoutSelection <LayoutSelection

View File

@ -1,13 +1,13 @@
import { FC } from "react"; import { FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// ui // ui
import { Breadcrumbs, BreadcrumbItem, Button } from "@plane/ui"; import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
export interface ICyclesHeader {} export interface ICyclesHeader {}
export const CyclesHeader: FC<ICyclesHeader> = (props) => { export const CyclesHeader: FC<ICyclesHeader> = (props) => {
@ -25,17 +25,28 @@ export const CyclesHeader: FC<ICyclesHeader> = (props) => {
> >
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap"> <div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects`}> label={currentProjectDetails?.name ?? "Project"}
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> icon={
<p>Projects</p> currentProjectDetails?.emoji ? (
</a> renderEmoji(currentProjectDetails.emoji)
</Link> ) : currentProjectDetails?.icon_prop ? (
renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="flex items-center justify-center h-4 w-4 rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
label="Cycles"
/> />
<BreadcrumbItem title={`${truncateText(currentProjectDetails?.name ?? "Project Title", 32)} Cycles`} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -10,9 +10,9 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "components/issues";
import { CreateUpdateWorkspaceViewModal } from "components/workspace"; import { CreateUpdateWorkspaceViewModal } from "components/workspace";
// ui // ui
import { Button, Tooltip } from "@plane/ui"; import { Breadcrumbs, Button, LayersIcon, PhotoFilterIcon, Tooltip } from "@plane/ui";
// icons // icons
import { CheckCircle, List, PlusIcon, Sheet } from "lucide-react"; import { List, PlusIcon, Sheet } from "lucide-react";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TStaticViewTypes } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TStaticViewTypes } from "types";
// constants // constants
@ -99,9 +99,20 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
<> <>
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} /> <CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex gap-2 items-center"> <div>
{activeLayout === "spreadsheet" && <CheckCircle size={16} strokeWidth={2} />} <Breadcrumbs>
<span className="text-sm font-medium">Workspace {activeLayout === "spreadsheet" ? "Issues" : "Views"}</span> <Breadcrumbs.BreadcrumbItem
type="text"
icon={
activeLayout === "spreadsheet" ? (
<LayersIcon className="h-4 w-4 text-custom-text-300" />
) : (
<PhotoFilterIcon className="h-4 w-4 text-custom-text-300" />
)
}
label={`Workspace ${activeLayout === "spreadsheet" ? "Issues" : "Views"}`}
/>
</Breadcrumbs>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex items-center gap-1 p-1 rounded bg-custom-background-80"> <div className="flex items-center gap-1 p-1 rounded bg-custom-background-80">

View File

@ -1,6 +1,5 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
@ -10,11 +9,12 @@ import useLocalStorage from "hooks/use-local-storage";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
import { ProjectAnalyticsModal } from "components/analytics"; import { ProjectAnalyticsModal } from "components/analytics";
// ui // ui
import { Breadcrumbs, Button, CustomMenu } from "@plane/ui"; import { Breadcrumbs, Button, CustomMenu, DiceIcon } from "@plane/ui";
// icons // icons
import { ArrowRight, ContrastIcon, Plus } from "lucide-react"; import { ArrowRight, ContrastIcon, Plus } from "lucide-react";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants // constants
@ -33,6 +33,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
project: projectStore, project: projectStore,
} = useMobxStore(); } = useMobxStore();
const activeLayout = issueFilterStore.userDisplayFilters.layout; const activeLayout = issueFilterStore.userDisplayFilters.layout;
const { currentProjectDetails } = projectStore;
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false"); const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
@ -110,20 +111,32 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
/> />
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<Breadcrumbs.BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects/${projectId}/modules`}> icon={
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> currentProjectDetails?.emoji ? (
<p className="truncate">{`${truncateText( renderEmoji(currentProjectDetails.emoji)
moduleDetails?.project_detail.name ?? "", ) : currentProjectDetails?.icon_prop ? (
32 renderEmoji(currentProjectDetails.icon_prop)
)} Modules`}</p> ) : (
</a> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
</Link> {currentProjectDetails?.name.charAt(0)}
</span>
)
} }
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/> />
</Breadcrumbs> <Breadcrumbs.BreadcrumbItem
type="text"
icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />}
label="Modules"
link={`/${workspaceSlug}/projects/${projectId}/modules`}
/>
<Breadcrumbs.BreadcrumbItem
type="component"
component={
<CustomMenu <CustomMenu
label={ label={
<> <>
@ -143,6 +156,9 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
))} ))}
</CustomMenu> </CustomMenu>
}
/>
</Breadcrumbs>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<LayoutSelection <LayoutSelection

View File

@ -1,5 +1,4 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { GanttChartSquare, LayoutGrid, List, Plus } from "lucide-react"; import { GanttChartSquare, LayoutGrid, List, Plus } from "lucide-react";
// mobx store // mobx store
@ -7,9 +6,10 @@ import { useMobxStore } from "lib/mobx/store-provider";
// hooks // hooks
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
// ui // ui
import { Breadcrumbs, BreadcrumbItem, Button, Tooltip } from "@plane/ui"; import { Breadcrumbs, Button, Tooltip, DiceIcon } from "@plane/ui";
// helper // helper
import { replaceUnderscoreIfSnakeCase, truncateText } from "helpers/string.helper"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper";
const moduleViewOptions: { type: "list" | "grid" | "gantt_chart"; icon: any }[] = [ const moduleViewOptions: { type: "list" | "grid" | "gantt_chart"; icon: any }[] = [
{ {
@ -42,17 +42,28 @@ export const ModulesListHeader: React.FC = observer(() => {
> >
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap"> <div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects`}> label={currentProjectDetails?.name ?? "Project"}
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> icon={
<p>Projects</p> currentProjectDetails?.emoji ? (
</a> renderEmoji(currentProjectDetails.emoji)
</Link> ) : currentProjectDetails?.icon_prop ? (
renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />}
label="Modules"
/> />
<BreadcrumbItem title={`${truncateText(currentProjectDetails?.name ?? "Project", 32)} Modules`} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -0,0 +1,92 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { FileText, Plus } from "lucide-react";
// services
import { PageService } from "services/page.service";
// constants
import { PAGE_DETAILS } from "constants/fetch-keys";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// helper
import { renderEmoji } from "helpers/emoji.helper";
import useSWR from "swr";
export interface IPagesHeaderProps {
showButton?: boolean;
}
const pageService = new PageService();
export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
const { showButton = false } = props;
const router = useRouter();
const { workspaceSlug, pageId } = router.query;
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const { data: pageDetails } = useSWR(
workspaceSlug && currentProjectDetails?.id && pageId ? PAGE_DETAILS(pageId as string) : null,
workspaceSlug && currentProjectDetails?.id
? () => pageService.getPageDetails(workspaceSlug as string, currentProjectDetails.id, pageId as string)
: null
);
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails?.emoji ? (
renderEmoji(currentProjectDetails.emoji)
) : currentProjectDetails?.icon_prop ? (
renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
}
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<FileText className="h-4 w-4 text-custom-text-300" />}
label="Pages"
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<FileText className="h-4 w-4 text-custom-text-300" />}
label={pageDetails?.name ?? "Page"}
/>
</Breadcrumbs>
</div>
</div>
{showButton && (
<div className="flex items-center gap-2">
<Button
variant="primary"
prependIcon={<Plus />}
size="sm"
onClick={() => {
const e = new KeyboardEvent("keydown", { key: "d" });
document.dispatchEvent(e);
}}
>
Create Page
</Button>
</div>
)}
</div>
);
});

View File

@ -1,14 +1,13 @@
import { FC } from "react"; import { FC } from "react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react"; import { FileText, Plus } from "lucide-react";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { Breadcrumbs, BreadcrumbItem, Button } from "@plane/ui"; import { Breadcrumbs, Button } from "@plane/ui";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
export interface IPagesHeaderProps { export interface IPagesHeaderProps {
showButton?: boolean; showButton?: boolean;
@ -17,30 +16,37 @@ export interface IPagesHeaderProps {
export const PagesHeader: FC<IPagesHeaderProps> = observer((props) => { export const PagesHeader: FC<IPagesHeaderProps> = observer((props) => {
const { showButton = false } = props; const { showButton = false } = props;
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug } = router.query;
const { project: projectStore } = useMobxStore(); const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const projectDetails =
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
return ( return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects`}> label={currentProjectDetails?.name ?? "Project"}
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> icon={
<p>Projects</p> currentProjectDetails?.emoji ? (
</a> renderEmoji(currentProjectDetails.emoji)
</Link> ) : currentProjectDetails?.icon_prop ? (
renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<FileText className="h-4 w-4 text-custom-text-300" />}
label="Pages"
/> />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Pages`} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,21 +1,16 @@
import { useRouter } from "next/router";
// components // components
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui"; import { Breadcrumbs } from "@plane/ui";
export const ProfilePreferencesHeader = () => { export const ProfilePreferencesHeader = () => (
const router = useRouter();
return (
<div <div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`} className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
> >
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem title="My Profile Preferences" /> <Breadcrumbs.BreadcrumbItem type="text" label="My Profile Preferences" />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>
</div> </div>
); );
};

View File

@ -1,20 +1,19 @@
import { FC } from "react"; import { FC } from "react";
import useSWR from "swr"; import useSWR from "swr";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { Breadcrumbs, BreadcrumbItem } from "@plane/ui"; import { Breadcrumbs, LayersIcon } from "@plane/ui";
// helper
import { truncateText } from "helpers/string.helper";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
// constants // constants
import { ISSUE_DETAILS } from "constants/fetch-keys"; import { ISSUE_DETAILS } from "constants/fetch-keys";
// services // services
import { IssueArchiveService } from "services/issue"; import { IssueArchiveService } from "services/issue";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
const issueArchiveService = new IssueArchiveService(); const issueArchiveService = new IssueArchiveService();
@ -24,10 +23,7 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
const { project: projectStore } = useMobxStore(); const { project: projectStore } = useMobxStore();
const projectDetails = const { currentProjectDetails } = projectStore;
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
const { data: issueDetails } = useSWR<IIssue | undefined>( const { data: issueDetails } = useSWR<IIssue | undefined>(
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null, workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
@ -45,17 +41,35 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<Link href={`/${workspaceSlug}/projects/${projectId as string}/issues`}> <Breadcrumbs.BreadcrumbItem
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> type="text"
<p className="truncate">{`${truncateText( icon={
issueDetails?.project_detail.name ?? "Project", currentProjectDetails?.emoji ? (
32 renderEmoji(currentProjectDetails.emoji)
)} Issues`}</p> ) : currentProjectDetails?.icon_prop ? (
</a> renderEmoji(currentProjectDetails.icon_prop)
</Link> ) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
}
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects`}
/>
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Archived Issues`} /> <Breadcrumbs.BreadcrumbItem
type="text"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
label="Archived Issues"
link={`/${workspaceSlug}/projects/${projectId}/archived-issues`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
label={`${issueDetails?.project_detail.identifier}-${issueDetails?.sequence_id}` ?? "..."}
/>
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,37 +1,48 @@
import { FC } from "react"; import { FC } from "react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { Breadcrumbs, BreadcrumbItem } from "@plane/ui"; import { Breadcrumbs, LayersIcon } from "@plane/ui";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
export const ProjectArchivedIssuesHeader: FC = observer(() => { export const ProjectArchivedIssuesHeader: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug } = router.query;
const { project: projectStore } = useMobxStore(); const { project: projectStore } = useMobxStore();
const projectDetails = const { currentProjectDetails } = projectStore;
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
return ( return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<Link href={`/${workspaceSlug}/projects`}> <Breadcrumbs.BreadcrumbItem
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> type="text"
<p>Projects</p> icon={
</a> currentProjectDetails?.emoji ? (
</Link> renderEmoji(currentProjectDetails.emoji)
) : currentProjectDetails?.icon_prop ? (
renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
}
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects`}
/>
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Archived Issues`} /> <Breadcrumbs.BreadcrumbItem
type="text"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
label="Archived Issues"
/>
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,41 +1,47 @@
import { FC } from "react"; import { FC } from "react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { Breadcrumbs, BreadcrumbItem } from "@plane/ui"; import { Breadcrumbs, LayersIcon } from "@plane/ui";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
export const ProjectDraftIssueHeader: FC = observer(() => { export const ProjectDraftIssueHeader: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug } = router.query;
const { project: projectStore } = useMobxStore(); const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const projectDetails =
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
return ( return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects`}> icon={
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> currentProjectDetails?.emoji ? (
<p>Projects</p> renderEmoji(currentProjectDetails.emoji)
</a> ) : currentProjectDetails?.icon_prop ? (
</Link> renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects`}
/> />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Draft Issues`} /> <Breadcrumbs.BreadcrumbItem
type="text"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
label="Draft Issues"
/>
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,44 +1,52 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { Breadcrumbs, BreadcrumbItem, Button } from "@plane/ui"; import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
// components // components
import { CreateInboxIssueModal } from "components/inbox"; import { CreateInboxIssueModal } from "components/inbox";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
export const ProjectInboxHeader: FC = observer(() => { export const ProjectInboxHeader: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug } = router.query;
const [createIssueModal, setCreateIssueModal] = useState(false); const [createIssueModal, setCreateIssueModal] = useState(false);
const { project: projectStore } = useMobxStore(); const { project: projectStore } = useMobxStore();
const projectDetails = const { currentProjectDetails } = projectStore;
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
return ( return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects`}> icon={
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> currentProjectDetails?.emoji ? (
<p>Projects</p> renderEmoji(currentProjectDetails.emoji)
</a> ) : currentProjectDetails?.icon_prop ? (
</Link> renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
label="Inbox Issues"
/> />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Inbox`} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,17 +1,17 @@
import { FC } from "react"; import { FC } from "react";
import useSWR from "swr"; import useSWR from "swr";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// ui // ui
import { Breadcrumbs } from "@plane/ui"; import { Breadcrumbs, LayersIcon } from "@plane/ui";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
// services // services
import { IssueService } from "services/issue"; import { IssueService } from "services/issue";
// constants // constants
import { ISSUE_DETAILS } from "constants/fetch-keys"; import { ISSUE_DETAILS } from "constants/fetch-keys";
import { useMobxStore } from "lib/mobx/store-provider";
// services // services
const issueService = new IssueService(); const issueService = new IssueService();
@ -20,6 +20,10 @@ export const ProjectIssueDetailsHeader: FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query; const { workspaceSlug, projectId, issueId } = router.query;
const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const { data: issueDetails } = useSWR( const { data: issueDetails } = useSWR(
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null, workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
workspaceSlug && projectId && issueId workspaceSlug && projectId && issueId
@ -31,24 +35,34 @@ export const ProjectIssueDetailsHeader: FC = observer(() => {
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<Breadcrumbs.BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects/${projectId as string}/issues`}> icon={
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> currentProjectDetails?.emoji ? (
<p className="truncate">{`${truncateText( renderEmoji(currentProjectDetails.emoji)
issueDetails?.project_detail.name ?? "Project", ) : currentProjectDetails?.icon_prop ? (
32 renderEmoji(currentProjectDetails.icon_prop)
)} Issues`}</p> ) : (
</a> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
</Link> {currentProjectDetails?.name.charAt(0)}
</span>
)
} }
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects`}
/> />
<Breadcrumbs.BreadcrumbItem <Breadcrumbs.BreadcrumbItem
title={`Issue ${issueDetails?.project_detail.identifier ?? "Project"}-${ type="text"
issueDetails?.sequence_id ?? "..." icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
} Details`} label="Issues"
unshrinkTitle link={`/${workspaceSlug}/projects/${projectId}/issues`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
label={`${issueDetails?.project_detail.identifier}-${issueDetails?.sequence_id}` ?? "..."}
/> />
</Breadcrumbs> </Breadcrumbs>
</div> </div>

View File

@ -9,13 +9,13 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
import { ProjectAnalyticsModal } from "components/analytics"; import { ProjectAnalyticsModal } from "components/analytics";
// ui // ui
import { Breadcrumbs, BreadcrumbItem, Button } from "@plane/ui"; import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
export const ProjectIssuesHeader: React.FC = observer(() => { export const ProjectIssuesHeader: React.FC = observer(() => {
const [analyticsModal, setAnalyticsModal] = useState(false); const [analyticsModal, setAnalyticsModal] = useState(false);
@ -85,11 +85,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
}, },
[issueFilterStore, projectId, workspaceSlug] [issueFilterStore, projectId, workspaceSlug]
); );
const { currentProjectDetails } = projectStore;
const projectDetails =
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
const inboxDetails = projectId ? inboxStore.inboxesList?.[projectId.toString()]?.[0] : undefined; const inboxDetails = projectId ? inboxStore.inboxesList?.[projectId.toString()]?.[0] : undefined;
@ -100,7 +96,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
<ProjectAnalyticsModal <ProjectAnalyticsModal
isOpen={analyticsModal} isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)} onClose={() => setAnalyticsModal(false)}
projectDetails={projectDetails ?? undefined} projectDetails={currentProjectDetails ?? undefined}
/> />
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
@ -114,22 +110,38 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
</button> </button>
</div> </div>
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects`}> icon={
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> currentProjectDetails?.emoji ? (
<p>Projects</p> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
</a> {renderEmoji(currentProjectDetails.emoji)}
</Link> </span>
) : currentProjectDetails?.icon_prop ? (
<div className="h-7 w-7 flex-shrink-0 grid place-items-center">
{renderEmoji(currentProjectDetails.icon_prop)}
</div>
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
label={currentProjectDetails?.name ?? "Project"}
link={`/${workspaceSlug}/projects`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
label="Issues"
/> />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Issues`} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
{projectDetails?.is_deployed && deployUrl && ( {currentProjectDetails?.is_deployed && deployUrl && (
<a <a
href={`${deployUrl}/${workspaceSlug}/${projectDetails?.id}`} href={`${deployUrl}/${workspaceSlug}/${currentProjectDetails?.id}`}
className="group bg-custom-primary-100/10 text-custom-primary-100 px-2.5 py-1 text-xs flex items-center gap-1.5 rounded font-medium" className="group bg-custom-primary-100/10 text-custom-primary-100 px-2.5 py-1 text-xs flex items-center gap-1.5 rounded font-medium"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"

View File

@ -1,11 +1,10 @@
import { FC } from "react"; import { FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui"; import { Breadcrumbs } from "@plane/ui";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
@ -28,17 +27,24 @@ export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props)
> >
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}> label={currentProjectDetails?.name ?? "Project"}
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> icon={
<p className="truncate">{`${truncateText(currentProjectDetails?.name ?? "Project", 32)}`}</p> currentProjectDetails?.emoji ? (
</a> renderEmoji(currentProjectDetails.emoji)
</Link> ) : currentProjectDetails?.icon_prop ? (
renderEmoji(currentProjectDetails.icon_prop)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/> />
<BreadcrumbItem title={title} unshrinkTitle /> <Breadcrumbs.BreadcrumbItem type="text" label={title} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,5 +1,4 @@
import { useCallback } from "react"; import { useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
@ -7,9 +6,10 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
// ui // ui
import { BreadcrumbItem, Breadcrumbs, CustomMenu, PhotoFilterIcon } from "@plane/ui"; import { Breadcrumbs, CustomMenu, PhotoFilterIcon } from "@plane/ui";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
import { renderEmoji } from "helpers/emoji.helper";
// types // types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "types";
// constants // constants
@ -26,6 +26,8 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
projectViews: projectViewsStore, projectViews: projectViewsStore,
} = useMobxStore(); } = useMobxStore();
const { currentProjectDetails } = projectStore;
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined; const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
const activeLayout = issueFilterStore.userDisplayFilters.layout; const activeLayout = issueFilterStore.userDisplayFilters.layout;
@ -87,28 +89,42 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
[issueFilterStore, projectId, workspaceSlug] [issueFilterStore, projectId, workspaceSlug]
); );
const projectDetails =
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
const viewsList = projectId ? projectViewsStore.viewsList[projectId.toString()] : undefined; const viewsList = projectId ? projectViewsStore.viewsList[projectId.toString()] : undefined;
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined; const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
return ( return (
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/cycles`}> label={currentProjectDetails?.name ?? "Project"}
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> icon={
<p className="truncate">{`${projectDetails?.name ?? "Project"} Views`}</p> currentProjectDetails?.emoji ? (
</a> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
</Link> {renderEmoji(currentProjectDetails.emoji)}
</span>
) : currentProjectDetails?.icon_prop ? (
<div className="h-7 w-7 flex-shrink-0 grid place-items-center">
{renderEmoji(currentProjectDetails.icon_prop)}
</div>
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/> />
</Breadcrumbs> <Breadcrumbs.BreadcrumbItem
type="text"
icon={<PhotoFilterIcon className="h-4 w-4 text-custom-text-300" />}
label="Views"
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/views`}
/>
<Breadcrumbs.BreadcrumbItem
type="component"
component={
<CustomMenu <CustomMenu
label={ label={
<> <>
@ -128,6 +144,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
))} ))}
</CustomMenu> </CustomMenu>
}
/>
</Breadcrumbs>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<LayoutSelection <LayoutSelection

View File

@ -1,6 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// mobx store // mobx store
@ -8,9 +7,9 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { CreateUpdateProjectViewModal } from "components/views"; import { CreateUpdateProjectViewModal } from "components/views";
// components // components
import { Breadcrumbs, BreadcrumbItem, Button } from "@plane/ui"; import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
// helpers // helpers
import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper";
export const ProjectViewsHeader: React.FC = observer(() => { export const ProjectViewsHeader: React.FC = observer(() => {
// router // router
@ -20,11 +19,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
const [createViewModal, setCreateViewModal] = useState(false); const [createViewModal, setCreateViewModal] = useState(false);
const { project: projectStore } = useMobxStore(); const { project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore;
const projectDetails =
workspaceSlug && projectId
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
: undefined;
return ( return (
<> <>
@ -41,17 +36,32 @@ export const ProjectViewsHeader: React.FC = observer(() => {
> >
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}/projects`}> label={currentProjectDetails?.name ?? "Project"}
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> icon={
<p>Projects</p> currentProjectDetails?.emoji ? (
</a> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
</Link> {renderEmoji(currentProjectDetails.emoji)}
</span>
) : currentProjectDetails?.icon_prop ? (
<div className="h-7 w-7 flex-shrink-0 grid place-items-center">
{renderEmoji(currentProjectDetails.icon_prop)}
</div>
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
} }
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<PhotoFilterIcon className="h-4 w-4 text-custom-text-300" />}
label="Views"
/> />
<BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Views`} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,9 +1,7 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Search, Plus } from "lucide-react"; import { Search, Plus, Briefcase } from "lucide-react";
// ui // ui
import { BreadcrumbItem, Breadcrumbs, Button } from "@plane/ui"; import { Breadcrumbs, Button } from "@plane/ui";
// helper
import { truncateText } from "helpers/string.helper";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
@ -13,8 +11,7 @@ export const ProjectsHeader = observer(() => {
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store // store
const { project: projectStore, workspace: workspaceStore } = useMobxStore(); const { project: projectStore } = useMobxStore();
const currentWorkspace = workspaceStore.currentWorkspace;
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : []; const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : [];
@ -24,10 +21,11 @@ export const ProjectsHeader = observer(() => {
> >
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
title={`${truncateText(currentWorkspace?.name ?? "Workspace", 32)} Projects`} type="text"
unshrinkTitle={false} icon={<Briefcase className="h-4 w-4 text-custom-text-300" />}
label="Projects"
/> />
</Breadcrumbs> </Breadcrumbs>
</div> </div>

View File

@ -1,22 +1,35 @@
import { FC } from "react"; import { FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui"; import { Breadcrumbs } from "@plane/ui";
import { UserCircle2 } from "lucide-react";
// hooks // hooks
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
export const UserProfileHeader: FC = observer(() => { export interface IUserProfileHeader {
title: string;
}
export const UserProfileHeader: FC<IUserProfileHeader> = observer((props) => {
const { title } = props;
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query;
return ( return (
<div <div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`} className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
> >
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem title="User Profile" unshrinkTitle /> <Breadcrumbs.BreadcrumbItem
type="text"
label="Profile"
icon={<UserCircle2 className="h-4 w-4 text-custom-text-300" />}
link={`/${workspaceSlug}/me/profile`}
/>
<Breadcrumbs.BreadcrumbItem type="text" label={title} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft, BarChart2 } from "lucide-react";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui"; import { Breadcrumbs } from "@plane/ui";
export const WorkspaceAnalyticsHeader = () => { export const WorkspaceAnalyticsHeader = () => {
const router = useRouter(); const router = useRouter();
@ -22,8 +22,12 @@ export const WorkspaceAnalyticsHeader = () => {
</button> </button>
</div> </div>
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem title="Workspace Analytics" /> <Breadcrumbs.BreadcrumbItem
type="text"
icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />}
label="Analytics"
/>
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -1,21 +1,13 @@
import { FC } from "react"; import { FC } from "react";
import useSWR from "swr";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui"; import { Breadcrumbs } from "@plane/ui";
import { Settings } from "lucide-react";
// hooks // hooks
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// services
import { WorkspaceService } from "services/workspace.service";
// helpers
import { truncateText } from "helpers/string.helper";
// constant
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
const workspaceService = new WorkspaceService();
export interface IWorkspaceSettingHeader { export interface IWorkspaceSettingHeader {
title: string; title: string;
@ -27,27 +19,20 @@ export const WorkspaceSettingHeader: FC<IWorkspaceSettingHeader> = observer((pro
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
);
return ( return (
<div <div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`} className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
> >
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis"> <div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div> <div>
<Breadcrumbs onBack={() => router.back()}> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
link={ type="text"
<Link href={`/${workspaceSlug}`}> label="Settings"
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}> icon={<Settings className="h-4 w-4 text-custom-text-300" />}
<p className="truncate">{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}</p> link={`/${workspaceSlug}/settings`}
</a>
</Link>
}
/> />
<BreadcrumbItem title={title} unshrinkTitle /> <Breadcrumbs.BreadcrumbItem type="text" label={title} />
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>

View File

@ -65,7 +65,7 @@ const ProfileAssignedIssuesPage: NextPageWithLayout = observer(() => {
ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) { ProfileAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return ( return (
<AppLayout header={<UserProfileHeader />}> <AppLayout header={<UserProfileHeader title="Assigned" />}>
<ProfileAuthWrapper showProfileIssuesFilter>{page}</ProfileAuthWrapper> <ProfileAuthWrapper showProfileIssuesFilter>{page}</ProfileAuthWrapper>
</AppLayout> </AppLayout>
); );

View File

@ -61,7 +61,7 @@ const ProfileCreatedIssuesPage: NextPageWithLayout = () => {
ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) { ProfileCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return ( return (
<AppLayout header={<UserProfileHeader />}> <AppLayout header={<UserProfileHeader title="Created" />}>
<ProfileAuthWrapper showProfileIssuesFilter>{page}</ProfileAuthWrapper> <ProfileAuthWrapper showProfileIssuesFilter>{page}</ProfileAuthWrapper>
</AppLayout> </AppLayout>
); );

View File

@ -56,7 +56,7 @@ const ProfileOverviewPage: NextPageWithLayout = () => {
ProfileOverviewPage.getLayout = function getLayout(page: ReactElement) { ProfileOverviewPage.getLayout = function getLayout(page: ReactElement) {
return ( return (
<AppLayout header={<UserProfileHeader />}> <AppLayout header={<UserProfileHeader title="Overview" />}>
<ProfileAuthWrapper>{page}</ProfileAuthWrapper> <ProfileAuthWrapper>{page}</ProfileAuthWrapper>
</AppLayout> </AppLayout>
); );

View File

@ -61,7 +61,7 @@ const ProfileSubscribedIssuesPage: NextPageWithLayout = () => {
ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) { ProfileSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) {
return ( return (
<AppLayout header={<UserProfileHeader />}> <AppLayout header={<UserProfileHeader title="Subscribed" />}>
<ProfileAuthWrapper showProfileIssuesFilter>{page}</ProfileAuthWrapper> <ProfileAuthWrapper showProfileIssuesFilter>{page}</ProfileAuthWrapper>
</AppLayout> </AppLayout>
); );

View File

@ -19,7 +19,7 @@ import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages"; import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages";
import { CreateLabelModal } from "components/labels"; import { CreateLabelModal } from "components/labels";
import { CreateBlock } from "components/pages/create-block"; import { CreateBlock } from "components/pages/create-block";
import { PagesHeader } from "components/headers"; import { PageDetailsHeader } from "components/headers/page-details";
// ui // ui
import { EmptyState } from "components/common"; import { EmptyState } from "components/common";
import { CustomSearchSelect, TextArea, Loader, ToggleSwitch, Tooltip } from "@plane/ui"; import { CustomSearchSelect, TextArea, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
@ -626,7 +626,7 @@ const PageDetailsPage: NextPageWithLayout = () => {
PageDetailsPage.getLayout = function getLayout(page: ReactElement) { PageDetailsPage.getLayout = function getLayout(page: ReactElement) {
return ( return (
<AppLayout header={<PagesHeader />} withProjectWrapper> <AppLayout header={<PageDetailsHeader />} withProjectWrapper>
{page} {page}
</AppLayout> </AppLayout>
); );