From 823885c8722ef8698e368f8dcac520f21cd4cb79 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 9 Jun 2025 17:32:05 +0530 Subject: [PATCH 01/18] chore: project feature enum added --- packages/constants/src/project.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/constants/src/project.ts b/packages/constants/src/project.ts index df22641e8d5..590dbb89ecc 100644 --- a/packages/constants/src/project.ts +++ b/packages/constants/src/project.ts @@ -149,3 +149,12 @@ export const DEFAULT_PROJECT_FORM_VALUES: Partial = { network: 2, project_lead: null, }; + +export enum EProjectFeatureKey { + WORK_ITEMS = "work_items", + CYCLES = "cycles", + MODULES = "modules", + VIEWS = "views", + PAGES = "pages", + INTAKE = "intake", +} From c669643198d4e97ad958ea0ceb313ab1628b1559 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 9 Jun 2025 17:37:18 +0530 Subject: [PATCH 02/18] feat: revamp breadcrumb and add navigation dropdown component --- packages/ui/src/breadcrumbs/breadcrumbs.tsx | 161 ++++++++++++++---- packages/ui/src/breadcrumbs/index.ts | 1 + .../src/breadcrumbs/navigation-dropdown.tsx | 82 ++++++--- .../navigation-search-dropdown.tsx | 96 +++++++++++ 4 files changed, 285 insertions(+), 55 deletions(-) create mode 100644 packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx diff --git a/packages/ui/src/breadcrumbs/breadcrumbs.tsx b/packages/ui/src/breadcrumbs/breadcrumbs.tsx index 03182569110..f667341d548 100644 --- a/packages/ui/src/breadcrumbs/breadcrumbs.tsx +++ b/packages/ui/src/breadcrumbs/breadcrumbs.tsx @@ -1,5 +1,7 @@ -import * as React from "react"; import { ChevronRight } from "lucide-react"; +import * as React from "react"; +import { cn } from "../../helpers"; +import { Tooltip } from "../tooltip"; type BreadcrumbsProps = { children: React.ReactNode; @@ -7,6 +9,15 @@ type BreadcrumbsProps = { isLoading?: boolean; }; +export const BreadcrumbItemLoader = () => ( +
+
+ + +
+
+); + const Breadcrumbs = ({ children, onBack, isLoading = false }: BreadcrumbsProps) => { const [isSmallScreen, setIsSmallScreen] = React.useState(false); @@ -22,35 +33,31 @@ const Breadcrumbs = ({ children, onBack, isLoading = false }: BreadcrumbsProps) const childrenArray = React.Children.toArray(children); - const BreadcrumbItemLoader = ( -
- - -
- ); - return ( -
+
{!isSmallScreen && ( <> - {childrenArray.map((child, index) => ( - - {index > 0 && !isSmallScreen && ( -
-
- )} -
0 ? "hidden sm:flex" : "flex"}`}> - {isLoading ? BreadcrumbItemLoader : child} -
-
- ))} + {childrenArray.map((child, index) => { + if (isLoading) { + return ( + <> + + + ); + } + if (React.isValidElement(child)) { + return React.cloneElement(child, { + isLast: index === childrenArray.length - 1, + }); + } + return child; + })} )} {isSmallScreen && childrenArray.length > 1 && ( <> -
+
{onBack && ( ... @@ -58,8 +65,8 @@ const Breadcrumbs = ({ children, onBack, isLoading = false }: BreadcrumbsProps) )}
-
- {isLoading ? BreadcrumbItemLoader : childrenArray[childrenArray.length - 1]} +
+ {isLoading ? : childrenArray[childrenArray.length - 1]}
)} @@ -68,17 +75,107 @@ const Breadcrumbs = ({ children, onBack, isLoading = false }: BreadcrumbsProps) ); }; -type Props = { - type?: "text" | "component"; +// breadcrumb item +type BreadcrumbItemProps = { component?: React.ReactNode; - link?: JSX.Element; + showSeparator?: boolean; + isLast?: boolean; }; -const BreadcrumbItem: React.FC = (props) => { - const { type = "text", component, link } = props; - return <>{type !== "text" ?
{component}
: link}; +const BreadcrumbItem: React.FC = (props) => { + const { component, showSeparator = true, isLast = false } = props; + return ( +
+ {component} + {showSeparator && !isLast && } +
+ ); +}; + +// breadcrumb icon +type BreadcrumbIconProps = { + children: React.ReactNode; + className?: string; +}; + +const BreadcrumbIcon: React.FC = (props) => { + const { children, className } = props; + return
{children}
; +}; + +// breadcrumb label +type BreadcrumbLabelProps = { + children: React.ReactNode; + className?: string; +}; + +const BreadcrumbLabel: React.FC = (props) => { + const { children, className } = props; + return ( +
+ {children} +
+ ); +}; + +// breadcrumb separator +type BreadcrumbSeparatorProps = { + className?: string; + containerClassName?: string; + iconClassName?: string; + showDivider?: boolean; +}; + +const BreadcrumbSeparator: React.FC = (props) => { + const { className, containerClassName, iconClassName, showDivider = false } = props; + return ( +
+ {showDivider && } +
+ +
+
+ ); +}; + +// breadcrumb wrapper +type BreadcrumbItemWrapperProps = { + label?: string; + disableTooltip?: boolean; + children: React.ReactNode; + className?: string; + type?: "link" | "text"; + isLast?: boolean; +}; + +const BreadcrumbItemWrapper: React.FC = (props) => { + const { label, disableTooltip = false, children, className, type = "link", isLast = false } = props; + return ( + +
+ {children} +
+
+ ); }; -Breadcrumbs.BreadcrumbItem = BreadcrumbItem; +Breadcrumbs.Item = BreadcrumbItem; +Breadcrumbs.Icon = BreadcrumbIcon; +Breadcrumbs.Label = BreadcrumbLabel; +Breadcrumbs.Separator = BreadcrumbSeparator; +Breadcrumbs.ItemWrapper = BreadcrumbItemWrapper; -export { Breadcrumbs, BreadcrumbItem }; +export { Breadcrumbs, BreadcrumbItem, BreadcrumbIcon, BreadcrumbLabel, BreadcrumbSeparator, BreadcrumbItemWrapper }; diff --git a/packages/ui/src/breadcrumbs/index.ts b/packages/ui/src/breadcrumbs/index.ts index 05a8bdbf1b6..192bd57510a 100644 --- a/packages/ui/src/breadcrumbs/index.ts +++ b/packages/ui/src/breadcrumbs/index.ts @@ -1,2 +1,3 @@ export * from "./breadcrumbs"; export * from "./navigation-dropdown"; +export * from "./navigation-search-dropdown"; diff --git a/packages/ui/src/breadcrumbs/navigation-dropdown.tsx b/packages/ui/src/breadcrumbs/navigation-dropdown.tsx index a716ca65e19..329aa5179cb 100644 --- a/packages/ui/src/breadcrumbs/navigation-dropdown.tsx +++ b/packages/ui/src/breadcrumbs/navigation-dropdown.tsx @@ -1,42 +1,54 @@ "use client"; +import { CheckIcon } from "lucide-react"; import * as React from "react"; -import { CheckIcon, ChevronDownIcon } from "lucide-react"; +import { cn } from "../../helpers"; // ui import { CustomMenu, TContextMenuItem } from "../dropdowns"; -// helpers -import { cn } from "../../helpers"; +import { Tooltip } from "../tooltip"; +import { Breadcrumbs } from "./breadcrumbs"; type TBreadcrumbNavigationDropdownProps = { selectedItemKey: string; navigationItems: TContextMenuItem[]; navigationDisabled?: boolean; + handleOnClick?: () => void; + isLast?: boolean; }; export const BreadcrumbNavigationDropdown = (props: TBreadcrumbNavigationDropdownProps) => { - const { selectedItemKey, navigationItems, navigationDisabled = false } = props; + const { selectedItemKey, navigationItems, navigationDisabled = false, handleOnClick, isLast = false } = props; + const [isOpen, setIsOpen] = React.useState(false); // derived values const selectedItem = navigationItems.find((item) => item.key === selectedItemKey); const selectedItemIcon = selectedItem?.icon ? ( - + ) : undefined; // if no selected item, return null if (!selectedItem) return null; - const NavigationButton = ({ className }: { className?: string }) => ( -
  • - {selectedItemIcon && ( -
    {selectedItemIcon}
    - )} -
    {selectedItem.title}
    -
  • + const NavigationButton = () => ( + + + ); if (navigationDisabled) { @@ -46,13 +58,37 @@ export const BreadcrumbNavigationDropdown = (props: TBreadcrumbNavigationDropdow return ( - - -
    + <> + + + } placement="bottom-start" + className="h-full rounded" + customButtonClassName={cn( + "group flex items-center gap-0.5 rounded hover:bg-custom-background-90 outline-none cursor-pointer h-full rounded", + { + "bg-custom-background-90": isOpen, + } + )} closeOnSelect + menuButtonOnClick={() => { + setIsOpen(!isOpen); + }} + onMenuClose={() => { + setIsOpen(false); + }} > {navigationItems.map((item) => { if (item.shouldRender === false) return null; @@ -74,7 +110,7 @@ export const BreadcrumbNavigationDropdown = (props: TBreadcrumbNavigationDropdow )} disabled={item.disabled} > - {item.icon && } + {item.icon && }
    {item.title}
    {item.description && ( diff --git a/packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx b/packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx new file mode 100644 index 00000000000..0439d1d3333 --- /dev/null +++ b/packages/ui/src/breadcrumbs/navigation-search-dropdown.tsx @@ -0,0 +1,96 @@ +import * as React from "react"; +import { useState } from "react"; +import { ICustomSearchSelectOption } from "@plane/types"; +import { cn } from "../../helpers"; +import { CustomSearchSelect } from "../dropdowns"; +import { Tooltip } from "../tooltip"; +import { Breadcrumbs } from "./breadcrumbs"; + +type TBreadcrumbNavigationSearchDropdownProps = { + icon?: React.JSX.Element; + title?: string; + selectedItem: string; + navigationItems: ICustomSearchSelectOption[]; + onChange?: (value: string) => void; + navigationDisabled?: boolean; + isLast?: boolean; + handleOnClick?: () => void; + disableRootHover?: boolean; +}; + +export const BreadcrumbNavigationSearchDropdown: React.FC = (props) => { + const { + icon, + title, + selectedItem, + navigationItems, + onChange, + navigationDisabled = false, + isLast = false, + handleOnClick, + } = props; + // state + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + return ( + { + setIsDropdownOpen(true); + }} + onClose={() => { + setIsDropdownOpen(false); + }} + options={navigationItems} + value={selectedItem} + onChange={(value: string) => { + if (value !== selectedItem) { + onChange?.(value); + } + }} + customButton={ + <> + + + + + + } + disabled={navigationDisabled} + className="h-full rounded" + customButtonClassName={cn( + "group flex items-center gap-0.5 rounded hover:bg-custom-background-90 outline-none cursor-pointer h-full rounded", + { + "bg-custom-background-90": isDropdownOpen, + } + )} + /> + ); +}; From 5cdf1a635185bbb08f54553ec496d3abed2c44d3 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 9 Jun 2025 17:38:10 +0530 Subject: [PATCH 03/18] chore: custom search select component refactoring --- packages/ui/src/dropdowns/custom-search-select.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/dropdowns/custom-search-select.tsx b/packages/ui/src/dropdowns/custom-search-select.tsx index e592f0dc2bc..d26163e695d 100644 --- a/packages/ui/src/dropdowns/custom-search-select.tsx +++ b/packages/ui/src/dropdowns/custom-search-select.tsx @@ -61,6 +61,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => { const openDropdown = () => { setIsOpen(true); if (referenceElement) referenceElement.focus(); + if (onOpen) onOpen(); }; const closeDropdown = () => { @@ -95,11 +96,14 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
    + } + />, + ], + }, +}; + +export const SingleItem: Story = { + args: { + children: [} />], + }, +}; + +export const WithNavigationDropdown: Story = { + args: { + children: [ + } />, + console.log("Project Alpha selected"), + }, + { + key: "project-2", + title: "Project Beta", + + action: () => console.log("Project Beta selected"), + }, + { + key: "project-3", + title: "Project Gamma", + + action: () => console.log("Project Gamma selected"), + }, + ]} + /> + } + showSeparator={false} + />, + } />, + ], + }, +}; + +export const WithNavigationDropdownAndIcons: Story = { + args: { + children: [ + } />} + />, + console.log("Project Alpha selected"), + }, + { + key: "project-2", + title: "Project Beta", + icon: Briefcase, + + // disabled: true, + action: () => console.log("Project Beta selected"), + }, + { + key: "project-3", + title: "Project Gamma", + icon: Briefcase, + + action: () => console.log("Project Gamma selected"), + }, + ]} + /> + } + showSeparator={false} + />, + console.log("Feature Alpha selected"), + }, + { + key: "feature-2", + title: "Work items", + icon: LayersIcon, + + // disabled: true, + action: () => console.log("Feature Beta selected"), + }, + { + key: "feature-3", + title: "Cycles", + icon: ContrastIcon, + + action: () => console.log("Feature Gamma selected"), + }, + { + key: "feature-3", + title: "Modules", + icon: GridIcon, + + action: () => console.log("Feature Gamma selected"), + }, + { + key: "feature-3", + title: "Views", + icon: Layers2, + + action: () => console.log("Feature Gamma selected"), + }, + { + key: "feature-3", + title: "Pages", + icon: FileIcon, + + action: () => console.log("Feature Gamma selected"), + }, + ]} + /> + } + showSeparator={false} + />, + } />} + isLast + />, + ], + }, +}; From 9cc484551e9bb76c34d27dde6815ab748f98e0e9 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 9 Jun 2025 17:53:14 +0530 Subject: [PATCH 05/18] chore: switch label and breadcrumb link component refactor --- .../components/common/breadcrumb-link.tsx | 91 ++++++++++++------- web/core/components/common/switcher-label.tsx | 34 +++++-- 2 files changed, 87 insertions(+), 38 deletions(-) diff --git a/web/core/components/common/breadcrumb-link.tsx b/web/core/components/common/breadcrumb-link.tsx index bf421a50c2d..d2a3f5fd08c 100644 --- a/web/core/components/common/breadcrumb-link.tsx +++ b/web/core/components/common/breadcrumb-link.tsx @@ -1,44 +1,73 @@ "use client"; -import { ReactNode } from "react"; +import React, { ReactNode, useMemo } from "react"; import Link from "next/link"; -import { Tooltip } from "@plane/ui"; +import { Breadcrumbs } from "@plane/ui"; import { usePlatformOS } from "@/hooks/use-platform-os"; type Props = { label?: string | ReactNode; href?: string; - icon?: React.ReactNode | undefined; + icon?: React.ReactNode; disableTooltip?: boolean; + isLast?: boolean; }; -export const BreadcrumbLink: React.FC = (props) => { - const { href, label, icon, disableTooltip = false } = props; - const { isMobile } = usePlatformOS(); +const IconWrapper = React.memo(({ icon }: { icon: React.ReactNode }) => ( +
    {icon}
    +)); + +IconWrapper.displayName = "IconWrapper"; + +const LabelWrapper = React.memo(({ label }: { label: ReactNode }) => ( +
    {label}
    +)); + +LabelWrapper.displayName = "LabelWrapper"; + +const BreadcrumbContent = React.memo(({ icon, label }: { icon?: React.ReactNode; label?: ReactNode }) => { + if (!icon && !label) return null; + return ( - -
  • -
    - {href ? ( - - {icon && ( -
    {icon}
    - )} - {label && ( -
    {label}
    - )} - - ) : ( -
    - {icon &&
    {icon}
    } -
    {label}
    -
    - )} -
    -
  • -
    + <> + {icon && } + {label && } + ); -}; +}); + +BreadcrumbContent.displayName = "BreadcrumbContent"; + +const ItemWrapper = React.memo(({ children, ...props }: React.ComponentProps) => ( + {children} +)); + +ItemWrapper.displayName = "ItemWrapper"; + +export const BreadcrumbLink = React.memo(({ href, label, icon, disableTooltip = false, isLast = false }: Props) => { + const { isMobile } = usePlatformOS(); + + const itemWrapperProps = useMemo( + () => ({ + label: label?.toString(), + disableTooltip: isMobile || disableTooltip, + type: (href && href !== "" ? "link" : "text") as "link" | "text", + isLast, + }), + [href, label, isMobile, disableTooltip, isLast] + ); + + const content = useMemo(() => , [icon, label]); + + if (href) { + return ( + + {content} + + ); + } + + return {content}; +}); + +BreadcrumbLink.displayName = "BreadcrumbLink"; diff --git a/web/core/components/common/switcher-label.tsx b/web/core/components/common/switcher-label.tsx index e1426461c98..8d267605c73 100644 --- a/web/core/components/common/switcher-label.tsx +++ b/web/core/components/common/switcher-label.tsx @@ -3,6 +3,32 @@ import { TLogoProps } from "@plane/types"; import { ISvgIcons, Logo } from "@plane/ui"; import { getFileURL } from "@plane/utils"; import { truncateText } from "@/helpers/string.helper"; + +type TSwitcherIconProps = { + logo_props?: TLogoProps; + logo_url?: string; + LabelIcon: FC; + size?: number; +}; + +export const SwitcherIcon: FC = ({ logo_props, logo_url, LabelIcon, size = 12 }) => { + if (logo_props?.in_use) { + return ; + } + + if (logo_url) { + return ( + logo + ); + } + return ; +}; + type TSwitcherLabelProps = { logo_props?: TLogoProps; logo_url?: string; @@ -14,13 +40,7 @@ export const SwitcherLabel: FC = (props) => { const { logo_props, name, LabelIcon, logo_url } = props; return (
    - {logo_props?.in_use ? ( - - ) : logo_url ? ( - logo - ) : ( - - )} + {truncateText(name ?? "", 40)}
    ); From aef81e2681e7e5018cee563b5efe0994a843db64 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 9 Jun 2025 17:53:39 +0530 Subject: [PATCH 06/18] chore: project navigation helper function added --- .../components/projects/navigation/helper.tsx | 77 +++++++++++++++++++ .../components/projects/navigation/index.ts | 1 + 2 files changed, 78 insertions(+) create mode 100644 web/ce/components/projects/navigation/helper.tsx create mode 100644 web/ce/components/projects/navigation/index.ts diff --git a/web/ce/components/projects/navigation/helper.tsx b/web/ce/components/projects/navigation/helper.tsx new file mode 100644 index 00000000000..2575f1da438 --- /dev/null +++ b/web/ce/components/projects/navigation/helper.tsx @@ -0,0 +1,77 @@ +import { FileText, Layers } from "lucide-react"; +import { EUserPermissions } from "@plane/constants"; +import { ContrastIcon, DiceIcon, Intake, LayersIcon } from "@plane/ui"; +import { TNavigationItem } from "@/components/workspace"; + +export const getProjectFeatureNavigation = ( + workspaceSlug: string, + projectId: string, + project: { + cycle_view: boolean; + module_view: boolean; + issue_views_view: boolean; + page_view: boolean; + inbox_view: boolean; + } +): TNavigationItem[] => [ + { + i18n_key: "sidebar.work_items", + key: "work_items", + name: "Work items", + href: `/${workspaceSlug}/projects/${projectId}/issues`, + icon: LayersIcon, + access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], + shouldRender: true, + sortOrder: 1, + }, + { + i18n_key: "sidebar.cycles", + key: "cycles", + name: "Cycles", + href: `/${workspaceSlug}/projects/${projectId}/cycles`, + icon: ContrastIcon, + access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], + shouldRender: project.cycle_view, + sortOrder: 2, + }, + { + i18n_key: "sidebar.modules", + key: "modules", + name: "Modules", + href: `/${workspaceSlug}/projects/${projectId}/modules`, + icon: DiceIcon, + access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], + shouldRender: project.module_view, + sortOrder: 3, + }, + { + i18n_key: "sidebar.views", + key: "views", + name: "Views", + href: `/${workspaceSlug}/projects/${projectId}/views`, + icon: Layers, + access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], + shouldRender: project.issue_views_view, + sortOrder: 4, + }, + { + i18n_key: "sidebar.pages", + key: "pages", + name: "Pages", + href: `/${workspaceSlug}/projects/${projectId}/pages`, + icon: FileText, + access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], + shouldRender: project.page_view, + sortOrder: 5, + }, + { + i18n_key: "sidebar.intake", + key: "intake", + name: "Intake", + href: `/${workspaceSlug}/projects/${projectId}/intake`, + icon: Intake, + access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], + shouldRender: project.inbox_view, + sortOrder: 6, + }, +]; diff --git a/web/ce/components/projects/navigation/index.ts b/web/ce/components/projects/navigation/index.ts new file mode 100644 index 00000000000..b9755e783ec --- /dev/null +++ b/web/ce/components/projects/navigation/index.ts @@ -0,0 +1 @@ +export * from "./helper"; From 831c255184daf56092760efee613bd1a214676cd Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 9 Jun 2025 17:56:51 +0530 Subject: [PATCH 07/18] chore: common breadcrumb component added --- web/ce/components/breadcrumbs/common.tsx | 32 ++++++++++ web/ce/components/breadcrumbs/feature.tsx | 61 ++++++++++++++++++ web/ce/components/breadcrumbs/index.ts | 2 + web/ce/components/breadcrumbs/project.tsx | 76 +++++++++++++++-------- 4 files changed, 146 insertions(+), 25 deletions(-) create mode 100644 web/ce/components/breadcrumbs/common.tsx create mode 100644 web/ce/components/breadcrumbs/feature.tsx diff --git a/web/ce/components/breadcrumbs/common.tsx b/web/ce/components/breadcrumbs/common.tsx new file mode 100644 index 00000000000..1a8a97339d5 --- /dev/null +++ b/web/ce/components/breadcrumbs/common.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { FC } from "react"; +// plane imports +import { EProjectFeatureKey } from "@plane/constants"; +// local components +import { ProjectFeatureBreadcrumb } from "./feature"; +import { ProjectBreadcrumb } from "./project"; + +type TCommonProjectBreadcrumbProps = { + workspaceSlug: string; + projectId: string; + featureKey?: EProjectFeatureKey; + isLast?: boolean; +}; + +export const CommonProjectBreadcrumbs: FC = (props) => { + const { workspaceSlug, projectId, featureKey, isLast = false } = props; + return ( + <> + + {featureKey && ( + + )} + + ); +}; diff --git a/web/ce/components/breadcrumbs/feature.tsx b/web/ce/components/breadcrumbs/feature.tsx new file mode 100644 index 00000000000..cf96e67d1e9 --- /dev/null +++ b/web/ce/components/breadcrumbs/feature.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { FC } from "react"; +import { observer } from "mobx-react"; +// ui +import { EProjectFeatureKey } from "@plane/constants"; +import { BreadcrumbNavigationDropdown, Breadcrumbs, ISvgIcons } from "@plane/ui"; +// components +import { SwitcherLabel } from "@/components/common"; +// hooks +import { useProject } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; +// local components +import { getProjectFeatureNavigation } from "../projects/navigation"; + +type TProjectFeatureBreadcrumbProps = { + workspaceSlug: string; + projectId: string; + featureKey: EProjectFeatureKey; + isLast?: boolean; +}; + +export const ProjectFeatureBreadcrumb = observer((props: TProjectFeatureBreadcrumbProps) => { + const { workspaceSlug, projectId, featureKey, isLast = false } = props; + // router + const router = useAppRouter(); + // store hooks + const { getPartialProjectById } = useProject(); + // derived values + const project = getPartialProjectById(projectId); + + if (!project) return null; + + const navigationItems = getProjectFeatureNavigation(workspaceSlug, projectId, project); + return ( + <> + item.shouldRender) + .map((item) => ({ + key: item.key, + title: item.name, + customContent: } />, + action: () => router.push(item.href), + icon: item.icon as FC, + }))} + handleOnClick={() => { + router.push(`/${workspaceSlug}/projects/${projectId}/${featureKey}/`); + }} + isLast={isLast} + /> + } + showSeparator={false} + isLast={isLast} + /> + + ); +}); diff --git a/web/ce/components/breadcrumbs/index.ts b/web/ce/components/breadcrumbs/index.ts index 9ff8c7dff40..d19c047dc2f 100644 --- a/web/ce/components/breadcrumbs/index.ts +++ b/web/ce/components/breadcrumbs/index.ts @@ -1 +1,3 @@ +export * from "./common"; +export * from "./feature"; export * from "./project"; diff --git a/web/ce/components/breadcrumbs/project.tsx b/web/ce/components/breadcrumbs/project.tsx index 3b49bb211b5..aa35b0b3236 100644 --- a/web/ce/components/breadcrumbs/project.tsx +++ b/web/ce/components/breadcrumbs/project.tsx @@ -1,39 +1,65 @@ "use client"; import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; import { Briefcase } from "lucide-react"; -// ui -import { Breadcrumbs, Logo } from "@plane/ui"; +// plane imports +import { ICustomSearchSelectOption } from "@plane/types"; +import { BreadcrumbNavigationSearchDropdown, Breadcrumbs, Logo } from "@plane/ui"; // components -import { BreadcrumbLink } from "@/components/common"; +import { SwitcherLabel } from "@/components/common"; // hooks import { useProject } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; +import { TProject } from "@/plane-web/types"; export const ProjectBreadcrumb = observer(() => { + // router + const router = useAppRouter(); + const { workspaceSlug } = useParams() as { workspaceSlug: string }; // store hooks - const { currentProjectDetails } = useProject(); + const { currentProjectDetails, joinedProjectIds, getPartialProjectById } = useProject(); + + if (!currentProjectDetails) return null; + + // derived values + const switcherOptions = joinedProjectIds + .map((projectId) => { + const project = getPartialProjectById(projectId); + return { + value: projectId, + query: project?.name, + content: , + }; + }) + .filter((option) => option !== undefined) as ICustomSearchSelectOption[]; + + // helpers + const renderIcon = (projectDetails: TProject) => ( + + + + ); return ( - - - - ) - ) : ( - - - - ) - } - /> - } - /> + <> + { + router.push(`/${workspaceSlug}/projects/${value}/issues`); + }} + title={currentProjectDetails?.name} + icon={renderIcon(currentProjectDetails)} + handleOnClick={() => { + router.push(`/${workspaceSlug}/projects/${currentProjectDetails.id}/issues`); + }} + /> + } + showSeparator={false} + /> + ); }); From 92559cc37b8261dbbe85486422bf5c39320a9771 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 9 Jun 2025 17:57:53 +0530 Subject: [PATCH 08/18] chore: breadcrumb refactoring --- admin/core/components/auth-header.tsx | 10 +- packages/ui/src/header/header.tsx | 5 +- .../(projects)/active-cycles/header.tsx | 25 +++-- .../(projects)/analytics/header.tsx | 5 +- .../(projects)/browse/[workItem]/header.tsx | 68 +++--------- .../(projects)/drafts/header.tsx | 5 +- .../[workspaceSlug]/(projects)/header.tsx | 5 +- .../(projects)/profile/[userId]/header.tsx | 5 +- .../(detail)/[projectId]/archives/header.tsx | 10 +- .../archives/issues/(detail)/header.tsx | 15 ++- .../[projectId]/cycles/(detail)/header.tsx | 86 ++++++--------- .../[projectId]/cycles/(list)/header.tsx | 24 ++--- .../[projectId]/draft-issues/header.tsx | 5 +- .../[projectId]/modules/(detail)/header.tsx | 100 +++++++----------- .../[projectId]/modules/(list)/header.tsx | 20 ++-- .../[projectId]/pages/(detail)/header.tsx | 57 ++++------ .../[projectId]/pages/(list)/header.tsx | 24 ++--- .../views/(detail)/[viewId]/header.tsx | 39 +++---- .../[projectId]/views/(list)/header.tsx | 16 +-- .../(projects)/stickies/header.tsx | 5 +- .../(projects)/workspace-views/header.tsx | 7 +- web/ce/components/issues/header.tsx | 25 ++--- .../projects/settings/intake/header.tsx | 19 ++-- web/core/components/project/header.tsx | 7 +- .../sidebar/header/root.tsx | 5 +- 25 files changed, 240 insertions(+), 352 deletions(-) diff --git a/admin/core/components/auth-header.tsx b/admin/core/components/auth-header.tsx index 5edcb611818..b97dd7c9eae 100644 --- a/admin/core/components/auth-header.tsx +++ b/admin/core/components/auth-header.tsx @@ -67,9 +67,8 @@ export const InstanceHeader: FC = observer(() => { {breadcrumbItems.length >= 0 && (
    - { {breadcrumbItems.map( (item) => item.title && ( - } + component={} /> ) )} diff --git a/packages/ui/src/header/header.tsx b/packages/ui/src/header/header.tsx index e83608a67a4..b60d41b9600 100644 --- a/packages/ui/src/header/header.tsx +++ b/packages/ui/src/header/header.tsx @@ -38,7 +38,10 @@ const Header = (props: HeaderProps) => { const LeftItem = (props: HeaderProps) => (
    {props.children}
    diff --git a/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx index 6416aee125c..92ed6b7368d 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/header.tsx @@ -14,18 +14,17 @@ export const WorkspaceActiveCycleHeader = observer(() => {
    - } - /> - } - /> - - - -
    + } + /> + } + /> +
    + + + ); }); diff --git a/web/app/(all)/[workspaceSlug]/(projects)/analytics/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/analytics/header.tsx index 2c3247bd7f6..1809d08bd52 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/analytics/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/analytics/header.tsx @@ -39,9 +39,8 @@ export const WorkspaceAnalyticsHeader = observer(() => {
    - } diff --git a/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/header.tsx index ec0d8b03a1d..2899b062129 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/header.tsx @@ -2,23 +2,22 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -import { Briefcase } from "lucide-react"; // plane imports -import { useTranslation } from "@plane/i18n"; -import { Breadcrumbs, LayersIcon, Header, Logo } from "@plane/ui"; +import { EProjectFeatureKey } from "@plane/constants"; +import { Breadcrumbs, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; import { IssueDetailQuickActions } from "@/components/issues"; // hooks import { useIssueDetail, useProject } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common"; export const ProjectIssueDetailsHeader = observer(() => { // router const router = useAppRouter(); const { workspaceSlug, workItem } = useParams(); // store hooks - const { t } = useTranslation(); const { getProjectById, loader } = useProject(); const { issue: { getIssueById, getIssueIdByIdentifier }, @@ -34,53 +33,20 @@ export const ProjectIssueDetailsHeader = observer(() => { return (
    -
    - - - - - ) - ) : ( - - - - ) - } - /> - } - /> - - } - /> - } - /> - - - } - /> - -
    + + + + } + /> +
    {projectId && issueId && ( diff --git a/web/app/(all)/[workspaceSlug]/(projects)/drafts/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/drafts/header.tsx index 5e1cc66ef60..92cdfb1c13e 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/drafts/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/drafts/header.tsx @@ -41,9 +41,8 @@ export const WorkspaceDraftHeader = observer(() => {
    - } /> } /> diff --git a/web/app/(all)/[workspaceSlug]/(projects)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/header.tsx index 16c106dad4a..ef210ec2734 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/header.tsx @@ -28,9 +28,8 @@ export const WorkspaceDashboardHeader = () => {
    - } /> } /> diff --git a/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/header.tsx index e97b4751f3a..b2b4e9d2e93 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/header.tsx @@ -51,9 +51,8 @@ export const UserProfileHeader: FC = observer((props) => {
    - = observer((props: TProps) => {
    - = observer((props: TProps) => { } /> {activeTabBreadcrumbDetail && ( - } diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx index 7b9e84a9e1f..a9f0c743aee 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx @@ -37,9 +37,8 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => { - { /> } /> - { /> } /> - { // refs @@ -171,63 +171,45 @@ export const CycleIssuesHeader: React.FC = observer(() => {
    - - - - - - ... - - - } - /> - } - /> - } + - { + console.log("value", value); router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${value}`); }} - label={ -
    - - {workItemsCount && workItemsCount > 0 ? ( - 1 ? "work items" : "work item" - } in this cycle`} - position="bottom" - > - - {workItemsCount} - - - ) : null} -
    + title={cycleDetails?.name} + icon={ + + + } + isLast /> } + isLast />
    + {workItemsCount && workItemsCount > 0 ? ( + 1 ? "work items" : "work item" + } in this cycle`} + position="bottom" + > + + {workItemsCount} + + + ) : null}
    diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx index 2339c2731c2..41dfde747cf 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx @@ -2,23 +2,25 @@ import { FC } from "react"; import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; // ui -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { Breadcrumbs, Button, ContrastIcon, Header } from "@plane/ui"; +import { Breadcrumbs, Button, Header } from "@plane/ui"; // components -import { BreadcrumbLink } from "@/components/common"; import { CyclesViewHeader } from "@/components/cycles"; // hooks import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // plane web -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common"; // constants export const CyclesListHeader: FC = observer(() => { // router const router = useAppRouter(); + const { workspaceSlug } = useParams(); + // store hooks const { toggleCreateCycleModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); @@ -35,15 +37,11 @@ export const CyclesListHeader: FC = observer(() => {
    - - } - /> - } + diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx index 2200b31f123..1dc20f42953 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx @@ -95,9 +95,8 @@ export const ProjectDraftIssueHeader: FC = observer(() => { - } diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx index 0fb2b138f49..1531986bcad 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx @@ -2,7 +2,6 @@ import { useCallback, useRef, useState } from "react"; import { observer } from "mobx-react"; -import Link from "next/link"; import { useParams } from "next/navigation"; // icons import { PanelRight } from "lucide-react"; @@ -14,6 +13,7 @@ import { ISSUE_DISPLAY_FILTERS_BY_PAGE, EUserPermissions, EUserPermissionsLevel, + EProjectFeatureKey, } from "@plane/constants"; // types import { @@ -23,10 +23,10 @@ import { IIssueFilterOptions, } from "@plane/types"; // ui -import { Breadcrumbs, Button, DiceIcon, Tooltip, Header, CustomSearchSelect } from "@plane/ui"; +import { Breadcrumbs, Button, DiceIcon, Header, BreadcrumbNavigationSearchDropdown, Tooltip } from "@plane/ui"; // components import { WorkItemsModal } from "@/components/analytics/work-items/modal"; -import { BreadcrumbLink, SwitcherLabel } from "@/components/common"; +import { SwitcherLabel } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; // helpers import { ModuleQuickActions } from "@/components/modules"; @@ -49,7 +49,7 @@ import { useIssuesActions } from "@/hooks/use-issues-actions"; import useLocalStorage from "@/hooks/use-local-storage"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs"; export const ModuleIssuesHeader: React.FC = observer(() => { // refs @@ -163,64 +163,42 @@ export const ModuleIssuesHeader: React.FC = observer(() => { />
    - - - - - - - ... - +
    + + + { + router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${value}`); + }} + title={moduleDetails?.name} + icon={} + isLast + /> + } + /> + + {workItemsCount && workItemsCount > 0 ? ( + 1 ? "work items" : "work item" + } in this module`} + position="bottom" + > + + {workItemsCount} - } - /> - } - /> - } - /> - - - {workItemsCount && workItemsCount > 0 ? ( - 1 ? "work items" : "work item" - } in this module`} - position="bottom" - > - - {workItemsCount} - - - ) : null} -
    - } - value={moduleId} - onChange={(value: string) => { - router.push(`/${workspaceSlug}/projects/${projectId}/modules/${value}`); - }} - /> - } - /> -
    + + ) : null} +
    diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx index 58194fbf30c..aded9e3619c 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx @@ -1,24 +1,25 @@ "use client"; import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; // plane imports -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // ui -import { Breadcrumbs, Button, DiceIcon, Header } from "@plane/ui"; +import { Breadcrumbs, Button, Header } from "@plane/ui"; // components -import { BreadcrumbLink } from "@/components/common"; import { ModuleViewHeader } from "@/components/modules"; // hooks import { useCommandPalette, useEventTracker, useProject, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // plane web -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs"; // constants export const ModulesListHeader: React.FC = observer(() => { // router const router = useAppRouter(); + const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; // store hooks const { toggleCreateModuleModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); @@ -39,12 +40,11 @@ export const ModulesListHeader: React.FC = observer(() => {
    - - } /> - } +
    diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx index d939c6fe5e3..741bda62c33 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx @@ -2,12 +2,13 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { FileText } from "lucide-react"; +import { EProjectFeatureKey } from "@plane/constants"; // types import { ICustomSearchSelectOption } from "@plane/types"; // ui -import { Breadcrumbs, Header, CustomSearchSelect } from "@plane/ui"; +import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui"; // components -import { BreadcrumbLink, PageAccessIcon, SwitcherLabel } from "@/components/common"; +import { PageAccessIcon, SwitcherIcon, SwitcherLabel } from "@/components/common"; import { PageHeaderActions } from "@/components/pages/header/actions"; // helpers import { getPageName } from "@/helpers/page.helper"; @@ -15,7 +16,7 @@ import { getPageName } from "@/helpers/page.helper"; import { useProject } from "@/hooks/store"; // plane web components import { useAppRouter } from "@/hooks/use-app-router"; -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common"; import { PageDetailsHeaderExtraActions } from "@/plane-web/components/pages"; // plane web hooks import { EPageStoreType, usePage, usePageStore } from "@/plane-web/hooks/store"; @@ -31,7 +32,7 @@ export const PageDetailsHeader = observer(() => { const router = useAppRouter(); const { workspaceSlug, pageId, projectId } = useParams(); // store hooks - const { currentProjectDetails, loader } = useProject(); + const { loader } = useProject(); const { getPageById, getCurrentProjectPageIds } = usePageStore(storeType); const page = usePage({ pageId: pageId?.toString() ?? "", @@ -64,45 +65,27 @@ export const PageDetailsHeader = observer(() => {
    - - - - - - - - - } + - } - /> - } - /> - - } + { router.push(`/${workspaceSlug}/projects/${projectId}/pages/${value}`); }} + title={page?.name} + icon={ + + + + } + isLast /> } /> diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx index ca15df8f5f1..af1a75f38dd 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx @@ -3,19 +3,16 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; -import { FileText } from "lucide-react"; // constants -import { EPageAccess } from "@plane/constants"; +import { EPageAccess, EProjectFeatureKey } from "@plane/constants"; // plane types import { TPage } from "@plane/types"; // plane ui import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui"; -// helpers -import { BreadcrumbLink } from "@/components/common"; // hooks import { useEventTracker, useProject } from "@/hooks/store"; // plane web -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs"; // plane web hooks import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store"; @@ -58,15 +55,14 @@ export const PagesListHeader = observer(() => { return (
    -
    - - - } />} - /> - -
    + + +
    {canCurrentUserCreatePage ? ( diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx index 3bc05747920..a3c87579fb7 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx @@ -13,6 +13,7 @@ import { EViewAccess, EUserPermissions, EUserPermissionsLevel, + EProjectFeatureKey, } from "@plane/constants"; // types import { @@ -22,9 +23,9 @@ import { IIssueFilterOptions, } from "@plane/types"; // ui -import { Breadcrumbs, Button, Tooltip, Header, CustomSearchSelect } from "@plane/ui"; +import { Breadcrumbs, Button, Tooltip, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui"; // components -import { BreadcrumbLink, SwitcherLabel } from "@/components/common"; +import { SwitcherIcon, SwitcherLabel } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; // constants import { ViewQuickActions } from "@/components/views"; @@ -44,7 +45,7 @@ import { } from "@/hooks/store"; // plane web import { useAppRouter } from "@/hooks/use-app-router"; -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs"; export const ProjectViewIssuesHeader: React.FC = observer(() => { // refs @@ -164,27 +165,27 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
    - - } - /> - } + - } + { router.push(`/${workspaceSlug}/projects/${projectId}/views/${value}`); }} + title={viewDetails?.name} + icon={ + + + + } + isLast /> } /> diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx index ea9a6ac8a0b..2d13b0d3eef 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx @@ -1,18 +1,19 @@ "use client"; import { observer } from "mobx-react"; -import { Layers } from "lucide-react"; +import { useParams } from "next/navigation"; // ui +import { EProjectFeatureKey } from "@plane/constants"; import { Breadcrumbs, Button, Header } from "@plane/ui"; // components -import { BreadcrumbLink } from "@/components/common"; import { ViewListHeader } from "@/components/views"; // hooks import { useCommandPalette, useProject } from "@/hooks/store"; // plane web -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs"; export const ProjectViewsHeader = observer(() => { + const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; // store hooks const { toggleCreateViewModal } = useCommandPalette(); const { loader } = useProject(); @@ -22,10 +23,11 @@ export const ProjectViewsHeader = observer(() => {
    - - } />} + diff --git a/web/app/(all)/[workspaceSlug]/(projects)/stickies/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/stickies/header.tsx index 9e3f1a45d5b..7f7f9c96f68 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/stickies/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/stickies/header.tsx @@ -25,9 +25,8 @@ export const WorkspaceStickyHeader = observer(() => {
    - } diff --git a/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx index 5a5ee0a6ef9..f5c197d366d 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx @@ -103,9 +103,10 @@ export const GlobalIssuesHeader = observer(() => {
    - } />} + } /> + } /> diff --git a/web/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx index abe44d506f0..334b128ef29 100644 --- a/web/ce/components/issues/header.tsx +++ b/web/ce/components/issues/header.tsx @@ -5,12 +5,12 @@ import { useParams } from "next/navigation"; // icons import { Circle, ExternalLink } from "lucide-react"; // plane constants -import { EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EIssuesStoreType, EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // ui -import { Breadcrumbs, Button, LayersIcon, Tooltip, Header } from "@plane/ui"; +import { Breadcrumbs, Button, Tooltip, Header } from "@plane/ui"; // components -import { BreadcrumbLink, CountChip } from "@/components/common"; +import { CountChip } from "@/components/common"; // constants import HeaderFilters from "@/components/issues/filters"; // helpers @@ -21,7 +21,7 @@ import { useIssues } from "@/hooks/store/use-issues"; import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "../breadcrumbs/common"; export const IssuesHeader = observer(() => { // router @@ -53,18 +53,13 @@ export const IssuesHeader = observer(() => { return (
    -
    +
    router.back()} isLoading={loader === "init-loader"}> - - - } - /> - } + {issuesCount && issuesCount > 0 ? ( diff --git a/web/ce/components/projects/settings/intake/header.tsx b/web/ce/components/projects/settings/intake/header.tsx index 32a93894f1e..9b0c994b514 100644 --- a/web/ce/components/projects/settings/intake/header.tsx +++ b/web/ce/components/projects/settings/intake/header.tsx @@ -5,16 +5,15 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { RefreshCcw } from "lucide-react"; // ui -import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { Breadcrumbs, Button, Intake, Header } from "@plane/ui"; +import { Breadcrumbs, Button, Header } from "@plane/ui"; // components -import { BreadcrumbLink } from "@/components/common"; import { InboxIssueCreateModalRoot } from "@/components/inbox"; // hooks import { useProject, useProjectInbox, useUserPermissions } from "@/hooks/store"; // plane web -import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; +import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs"; export const ProjectInboxHeader: FC = observer(() => { // states @@ -37,13 +36,13 @@ export const ProjectInboxHeader: FC = observer(() => { return (
    -
    +
    - - - } />} + diff --git a/web/core/components/project/header.tsx b/web/core/components/project/header.tsx index 17adc130ee4..e40280373c3 100644 --- a/web/core/components/project/header.tsx +++ b/web/core/components/project/header.tsx @@ -37,16 +37,15 @@ export const ProjectsBaseHeader = observer(() => {
    - } /> } /> - {isArchived && } />} + {isArchived && } />} diff --git a/web/core/components/workspace-notifications/sidebar/header/root.tsx b/web/core/components/workspace-notifications/sidebar/header/root.tsx index c641bde199c..808cad1c53d 100644 --- a/web/core/components/workspace-notifications/sidebar/header/root.tsx +++ b/web/core/components/workspace-notifications/sidebar/header/root.tsx @@ -26,9 +26,8 @@ export const NotificationSidebarHeader: FC = observe
    - } From f188542383d998f5c85134ae2a3866436d37ce96 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 10 Jun 2025 16:42:37 +0530 Subject: [PATCH 09/18] chore: code refactor --- .../(detail)/[projectId]/archives/header.tsx | 2 +- .../archives/issues/(detail)/header.tsx | 2 +- .../[projectId]/draft-issues/header.tsx | 2 +- web/ce/components/breadcrumbs/common.tsx | 2 +- web/ce/components/breadcrumbs/feature.tsx | 10 ++++++++-- web/ce/components/breadcrumbs/project.tsx | 19 ++++++++++++++----- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx index b48c6e5dd90..5a588457ac0 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx @@ -66,7 +66,7 @@ export const ProjectArchivesHeader: FC = observer((props: TProps) => {
    - + {
    - + {
    - + = (prop const { workspaceSlug, projectId, featureKey, isLast = false } = props; return ( <> - + {featureKey && ( { - const { workspaceSlug, projectId, featureKey, isLast = false } = props; + const { workspaceSlug, projectId, featureKey, isLast = false, additionalNavigationItems } = props; // router const router = useAppRouter(); // store hooks @@ -32,13 +34,17 @@ export const ProjectFeatureBreadcrumb = observer((props: TProjectFeatureBreadcru if (!project) return null; const navigationItems = getProjectFeatureNavigation(workspaceSlug, projectId, project); + + // if additional navigation items are provided, add them to the navigation items + const allNavigationItems = [...(additionalNavigationItems || []), ...navigationItems]; + return ( <> item.shouldRender) .map((item) => ({ key: item.key, diff --git a/web/ce/components/breadcrumbs/project.tsx b/web/ce/components/breadcrumbs/project.tsx index aa35b0b3236..65648a50274 100644 --- a/web/ce/components/breadcrumbs/project.tsx +++ b/web/ce/components/breadcrumbs/project.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { Briefcase } from "lucide-react"; // plane imports import { ICustomSearchSelectOption } from "@plane/types"; @@ -13,12 +12,21 @@ import { useProject } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import { TProject } from "@/plane-web/types"; -export const ProjectBreadcrumb = observer(() => { +type TProjectBreadcrumbProps = { + workspaceSlug: string; + projectId: string; + handleOnClick?: () => void; +}; + +export const ProjectBreadcrumb = observer((props: TProjectBreadcrumbProps) => { + const { workspaceSlug, projectId, handleOnClick } = props; // router const router = useAppRouter(); - const { workspaceSlug } = useParams() as { workspaceSlug: string }; // store hooks - const { currentProjectDetails, joinedProjectIds, getPartialProjectById } = useProject(); + const { joinedProjectIds, getPartialProjectById } = useProject(); + const currentProjectDetails = getPartialProjectById(projectId); + + // store hooks if (!currentProjectDetails) return null; @@ -54,7 +62,8 @@ export const ProjectBreadcrumb = observer(() => { title={currentProjectDetails?.name} icon={renderIcon(currentProjectDetails)} handleOnClick={() => { - router.push(`/${workspaceSlug}/projects/${currentProjectDetails.id}/issues`); + if (handleOnClick) handleOnClick(); + else router.push(`/${workspaceSlug}/projects/${currentProjectDetails.id}/issues`); }} /> } From dc6660db78245c9cd3b31d0d2850be98f73a467a Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Wed, 11 Jun 2025 17:22:17 +0530 Subject: [PATCH 10/18] chore: code refactor --- packages/ui/src/breadcrumbs/breadcrumbs.tsx | 5 +++-- .../(detail)/[projectId]/modules/(detail)/header.tsx | 2 +- web/ce/components/breadcrumbs/feature.tsx | 4 +++- web/ce/components/issues/header.tsx | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/breadcrumbs/breadcrumbs.tsx b/packages/ui/src/breadcrumbs/breadcrumbs.tsx index f667341d548..91322aa53fa 100644 --- a/packages/ui/src/breadcrumbs/breadcrumbs.tsx +++ b/packages/ui/src/breadcrumbs/breadcrumbs.tsx @@ -4,6 +4,7 @@ import { cn } from "../../helpers"; import { Tooltip } from "../tooltip"; type BreadcrumbsProps = { + className?: string; children: React.ReactNode; onBack?: () => void; isLoading?: boolean; @@ -18,7 +19,7 @@ export const BreadcrumbItemLoader = () => (
    ); -const Breadcrumbs = ({ children, onBack, isLoading = false }: BreadcrumbsProps) => { +const Breadcrumbs = ({ className, children, onBack, isLoading = false }: BreadcrumbsProps) => { const [isSmallScreen, setIsSmallScreen] = React.useState(false); React.useEffect(() => { @@ -34,7 +35,7 @@ const Breadcrumbs = ({ children, onBack, isLoading = false }: BreadcrumbsProps) const childrenArray = React.Children.toArray(children); return ( -
    +
    {!isSmallScreen && ( <> {childrenArray.map((child, index) => { diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx index 1531986bcad..1190f020a39 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx @@ -176,7 +176,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => { selectedItem={moduleId?.toString() ?? ""} navigationItems={switcherOptions} onChange={(value: string) => { - router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${value}`); + router.push(`/${workspaceSlug}/projects/${projectId}/modules/${value}`); }} title={moduleDetails?.name} icon={} diff --git a/web/ce/components/breadcrumbs/feature.tsx b/web/ce/components/breadcrumbs/feature.tsx index 4d9431d259f..c606a2d3f31 100644 --- a/web/ce/components/breadcrumbs/feature.tsx +++ b/web/ce/components/breadcrumbs/feature.tsx @@ -54,7 +54,9 @@ export const ProjectFeatureBreadcrumb = observer((props: TProjectFeatureBreadcru icon: item.icon as FC, }))} handleOnClick={() => { - router.push(`/${workspaceSlug}/projects/${projectId}/${featureKey}/`); + router.push( + `/${workspaceSlug}/projects/${projectId}/${featureKey === EProjectFeatureKey.WORK_ITEMS ? "issues" : featureKey}/` + ); }} isLast={isLast} /> diff --git a/web/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx index 334b128ef29..d296dddd9a1 100644 --- a/web/ce/components/issues/header.tsx +++ b/web/ce/components/issues/header.tsx @@ -54,7 +54,7 @@ export const IssuesHeader = observer(() => {
    - router.back()} isLoading={loader === "init-loader"}> + router.back()} isLoading={loader === "init-loader"} className="flex-grow-0"> Date: Mon, 16 Jun 2025 19:33:00 +0530 Subject: [PATCH 11/18] fix: build error --- web/ce/components/issues/header.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx index cfad52e5e80..3250ad4d5eb 100644 --- a/web/ce/components/issues/header.tsx +++ b/web/ce/components/issues/header.tsx @@ -4,7 +4,14 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // icons import { Circle, ExternalLink } from "lucide-react"; -import { EIssuesStoreType, EUserPermissions, EUserPermissionsLevel, SPACE_BASE_PATH, SPACE_BASE_URL } from "@plane/constants"; +import { + EIssuesStoreType, + EProjectFeatureKey, + EUserPermissions, + EUserPermissionsLevel, + SPACE_BASE_PATH, + SPACE_BASE_URL, +} from "@plane/constants"; // plane constants import { useTranslation } from "@plane/i18n"; // ui From cb658c4b98653745bd3d43a70df9fa06467487a4 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Mon, 16 Jun 2025 19:55:06 +0530 Subject: [PATCH 12/18] fix: nprogress and button tooltip --- packages/ui/src/breadcrumbs/navigation-dropdown.tsx | 2 +- web/ce/components/breadcrumbs/project.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/breadcrumbs/navigation-dropdown.tsx b/packages/ui/src/breadcrumbs/navigation-dropdown.tsx index 329aa5179cb..503e13eb2fe 100644 --- a/packages/ui/src/breadcrumbs/navigation-dropdown.tsx +++ b/packages/ui/src/breadcrumbs/navigation-dropdown.tsx @@ -29,7 +29,7 @@ export const BreadcrumbNavigationDropdown = (props: TBreadcrumbNavigationDropdow if (!selectedItem) return null; const NavigationButton = () => ( - +
    - {isLoading ? : childrenArray[childrenArray.length - 1]} + {isLoading ? ( + + ) : React.isValidElement(childrenArray[childrenArray.length - 1]) ? ( + React.cloneElement(childrenArray[childrenArray.length - 1] as React.ReactElement, { + isLast: true, + }) + ) : ( + childrenArray[childrenArray.length - 1] + )}
    )} From b3a9670225ab203c0d742286b581d8f2c8fca781 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 17 Jun 2025 16:03:41 +0530 Subject: [PATCH 14/18] chore: workspace view breadcrumb improvements --- .../[projectId]/cycles/(detail)/header.tsx | 1 - .../workspace-views/[globalViewId]/page.tsx | 2 - .../(projects)/workspace-views/header.tsx | 77 +++++++++++++++-- .../views/default-view-quick-action.tsx | 43 +--------- .../components/workspace/views/header.tsx | 15 +--- .../workspace/views/quick-action.tsx | 85 ++++++++++--------- 6 files changed, 123 insertions(+), 100 deletions(-) diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx index 00a6d5d5d9e..5844c661bd0 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx @@ -177,7 +177,6 @@ export const CycleIssuesHeader: React.FC = observer(() => { selectedItem={cycleId} navigationItems={switcherOptions} onChange={(value: string) => { - console.log("value", value); router.push(`/${workspaceSlug}/projects/${projectId}/cycles/${value}`); }} title={cycleDetails?.name} diff --git a/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx b/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx index bf7d78a6da9..0d62cd4a2c5 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx @@ -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(() => {
    - {globalViewId && ( )} diff --git a/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx index 155dba7f0ac..51d045cf0e6 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/header.tsx @@ -5,31 +5,47 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Layers } from "lucide-react"; // plane constants -import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants"; +import { + DEFAULT_GLOBAL_VIEWS_LIST, + EIssueFilterType, + EIssuesStoreType, + ISSUE_DISPLAY_FILTERS_BY_PAGE, +} 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"; 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 }, @@ -97,6 +113,30 @@ 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: , + })); + + const workspaceOptions = (currentWorkspaceViews || []).map((view) => { + const _view = getViewDetailsById(view); + if (!_view) return; + return { + value: _view.id, + query: _view.name, + content: , + }; + }); + + const switcherOptions = [...defaultOptions, ...workspaceOptions].filter( + (option) => option !== undefined + ) as ICustomSearchSelectOption[]; + return ( <> setCreateViewModal(false)} /> @@ -108,6 +148,25 @@ export const GlobalIssuesHeader = observer(() => { } /> } /> + { + router.push(`/${workspaceSlug}/workspace-views/${value}`); + }} + title={viewDetails?.name ?? t(defaultViewDetails?.i18n_label ?? "")} + icon={ + + + + } + isLast + /> + } + isLast + /> @@ -146,6 +205,12 @@ export const GlobalIssuesHeader = observer(() => { +
    + {viewDetails && } + {isDefaultView && defaultViewDetails && ( + + )} +
    diff --git a/web/core/components/workspace/views/default-view-quick-action.tsx b/web/core/components/workspace/views/default-view-quick-action.tsx index e7f0e276a3d..869c2d09656 100644 --- a/web/core/components/workspace/views/default-view-quick-action.tsx +++ b/web/core/components/workspace/views/default-view-quick-action.tsx @@ -1,19 +1,16 @@ "use client"; import { observer } from "mobx-react"; -import Link from "next/link"; import { ExternalLink, LinkIcon } from "lucide-react"; // plane imports import { useTranslation } from "@plane/i18n"; // ui import { TStaticViewTypes } from "@plane/types"; -import { ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; +import { CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; import { copyUrlToClipboard, cn } from "@plane/utils"; // helpers type Props = { - parentRef: React.RefObject; workspaceSlug: string; - globalViewId: string | undefined; view: { key: TStaticViewTypes; i18n_label: string; @@ -21,7 +18,7 @@ type Props = { }; export const DefaultWorkspaceViewQuickActions: React.FC = observer((props) => { - const { parentRef, globalViewId, view, workspaceSlug } = props; + const { workspaceSlug, view } = props; const { t } = useTranslation(); @@ -53,43 +50,11 @@ export const DefaultWorkspaceViewQuickActions: React.FC = observer((props return ( <> - - - {view.key === globalViewId ? ( - - {t(view.i18n_label)} - - ) : ( - - - {t(view.i18n_label)} - - - )} - - } + ellipsis placement="bottom-end" - menuItemsClassName="z-20" closeOnSelect + buttonClassName="flex-shrink-0 flex items-center justify-center size-[26px] bg-custom-background-80/70 rounded" > {MENU_ITEMS.map((item) => { if (item.shouldRender === false) return null; diff --git a/web/core/components/workspace/views/header.tsx b/web/core/components/workspace/views/header.tsx index f449c553b5e..e7312980062 100644 --- a/web/core/components/workspace/views/header.tsx +++ b/web/core/components/workspace/views/header.tsx @@ -37,13 +37,7 @@ const ViewTab = observer((props: { viewId: string }) => { return (
    - +
    ); }); @@ -63,12 +57,7 @@ const DefaultViewTab = (props: { if (!workspaceSlug || !globalViewId) return null; return (
    - +
    ); }; diff --git a/web/core/components/workspace/views/quick-action.tsx b/web/core/components/workspace/views/quick-action.tsx index e0950a78ac0..4db3d537cd3 100644 --- a/web/core/components/workspace/views/quick-action.tsx +++ b/web/core/components/workspace/views/quick-action.tsx @@ -2,13 +2,12 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import Link from "next/link"; -import { ExternalLink, LinkIcon, Pencil, Trash2, Lock } from "lucide-react"; +import { ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react"; // types -import { EViewAccess, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IWorkspaceView } from "@plane/types"; -import { ContextMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; +import { CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; import { copyUrlToClipboard, cn } from "@plane/utils"; // components import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "@/components/workspace"; @@ -18,15 +17,12 @@ import { CreateUpdateWorkspaceViewModal, DeleteGlobalViewModal } from "@/compone import { useUser, useUserPermissions } from "@/hooks/store"; type Props = { - parentRef: React.RefObject; workspaceSlug: string; - globalViewId: string; - viewId: string; view: IWorkspaceView; }; export const WorkspaceViewQuickActions: React.FC = observer((props) => { - const { parentRef, view, globalViewId, viewId, workspaceSlug } = props; + const { workspaceSlug, view } = props; // states const [updateViewModal, setUpdateViewModal] = useState(false); const [deleteViewModal, setDeleteViewModal] = useState(false); @@ -78,42 +74,53 @@ export const WorkspaceViewQuickActions: React.FC = observer((props) => { }, ]; - const isSelected = viewId === globalViewId; - const isPrivateView = view.access === EViewAccess.PRIVATE; - - let customButton = ( -
    - - {view.name} - - {isPrivateView && ( - - )} -
    - ); - - if (!isSelected) { - customButton = ( - - {customButton} - - ); - } - return ( <> setUpdateViewModal(false)} /> setDeleteViewModal(false)} /> - - - {customButton} + + {MENU_ITEMS.map((item) => { + if (item.shouldRender === false) return null; + return ( + { + e.preventDefault(); + e.stopPropagation(); + item.action(); + }} + className={cn( + "flex items-center gap-2", + { + "text-custom-text-400": item.disabled, + }, + item.className + )} + disabled={item.disabled} + > + {item.icon && } +
    +
    {item.title}
    + {item.description && ( +

    + {item.description} +

    + )} +
    +
    + ); + })} +
    ); }); From 08f47b55e19f35354c0462b6f368a7f1b04b4e65 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 17 Jun 2025 18:38:46 +0530 Subject: [PATCH 15/18] chore: code refactor --- web/ce/components/projects/navigation/helper.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/ce/components/projects/navigation/helper.tsx b/web/ce/components/projects/navigation/helper.tsx index 2575f1da438..1a99262c6a1 100644 --- a/web/ce/components/projects/navigation/helper.tsx +++ b/web/ce/components/projects/navigation/helper.tsx @@ -1,5 +1,5 @@ import { FileText, Layers } from "lucide-react"; -import { EUserPermissions } from "@plane/constants"; +import { EUserPermissions, EProjectFeatureKey } from "@plane/constants"; import { ContrastIcon, DiceIcon, Intake, LayersIcon } from "@plane/ui"; import { TNavigationItem } from "@/components/workspace"; @@ -16,7 +16,7 @@ export const getProjectFeatureNavigation = ( ): TNavigationItem[] => [ { i18n_key: "sidebar.work_items", - key: "work_items", + key: EProjectFeatureKey.WORK_ITEMS, name: "Work items", href: `/${workspaceSlug}/projects/${projectId}/issues`, icon: LayersIcon, @@ -26,7 +26,7 @@ export const getProjectFeatureNavigation = ( }, { i18n_key: "sidebar.cycles", - key: "cycles", + key: EProjectFeatureKey.CYCLES, name: "Cycles", href: `/${workspaceSlug}/projects/${projectId}/cycles`, icon: ContrastIcon, @@ -36,7 +36,7 @@ export const getProjectFeatureNavigation = ( }, { i18n_key: "sidebar.modules", - key: "modules", + key: EProjectFeatureKey.MODULES, name: "Modules", href: `/${workspaceSlug}/projects/${projectId}/modules`, icon: DiceIcon, @@ -46,7 +46,7 @@ export const getProjectFeatureNavigation = ( }, { i18n_key: "sidebar.views", - key: "views", + key: EProjectFeatureKey.VIEWS, name: "Views", href: `/${workspaceSlug}/projects/${projectId}/views`, icon: Layers, @@ -56,7 +56,7 @@ export const getProjectFeatureNavigation = ( }, { i18n_key: "sidebar.pages", - key: "pages", + key: EProjectFeatureKey.PAGES, name: "Pages", href: `/${workspaceSlug}/projects/${projectId}/pages`, icon: FileText, @@ -66,7 +66,7 @@ export const getProjectFeatureNavigation = ( }, { i18n_key: "sidebar.intake", - key: "intake", + key: EProjectFeatureKey.INTAKE, name: "Intake", href: `/${workspaceSlug}/projects/${projectId}/intake`, icon: Intake, From dacc9178aefb54afc74f780c32cd45a68fc5028e Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Wed, 18 Jun 2025 15:04:49 +0530 Subject: [PATCH 16/18] chore: code refactor --- web/core/components/common/breadcrumb-link.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/core/components/common/breadcrumb-link.tsx b/web/core/components/common/breadcrumb-link.tsx index d2a3f5fd08c..f8b56fed37a 100644 --- a/web/core/components/common/breadcrumb-link.tsx +++ b/web/core/components/common/breadcrumb-link.tsx @@ -1,6 +1,7 @@ "use client"; -import React, { ReactNode, useMemo } from "react"; +import React, { ReactNode, useMemo, FC } from "react"; +import { observer } from "mobx-react-lite"; import Link from "next/link"; import { Breadcrumbs } from "@plane/ui"; import { usePlatformOS } from "@/hooks/use-platform-os"; @@ -44,7 +45,8 @@ const ItemWrapper = React.memo(({ children, ...props }: React.ComponentProps { +export const BreadcrumbLink: FC = observer((props) => { + const { href, label, icon, disableTooltip = false, isLast = false } = props; const { isMobile } = usePlatformOS(); const itemWrapperProps = useMemo( From 16652a6b4cebbb94cdcac9878f65b7322128ec33 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Wed, 18 Jun 2025 15:10:04 +0530 Subject: [PATCH 17/18] chore: code refactor --- web/ce/components/breadcrumbs/common.tsx | 2 +- web/ce/components/breadcrumbs/index.ts | 2 +- .../components/breadcrumbs/{feature.tsx => project-feature.tsx} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename web/ce/components/breadcrumbs/{feature.tsx => project-feature.tsx} (100%) diff --git a/web/ce/components/breadcrumbs/common.tsx b/web/ce/components/breadcrumbs/common.tsx index c2df42db463..5b2f573cbc3 100644 --- a/web/ce/components/breadcrumbs/common.tsx +++ b/web/ce/components/breadcrumbs/common.tsx @@ -4,7 +4,7 @@ import { FC } from "react"; // plane imports import { EProjectFeatureKey } from "@plane/constants"; // local components -import { ProjectFeatureBreadcrumb } from "./feature"; +import { ProjectFeatureBreadcrumb } from "./project-feature"; import { ProjectBreadcrumb } from "./project"; type TCommonProjectBreadcrumbProps = { diff --git a/web/ce/components/breadcrumbs/index.ts b/web/ce/components/breadcrumbs/index.ts index d19c047dc2f..aad2cb35224 100644 --- a/web/ce/components/breadcrumbs/index.ts +++ b/web/ce/components/breadcrumbs/index.ts @@ -1,3 +1,3 @@ export * from "./common"; -export * from "./feature"; +export * from "./project-feature"; export * from "./project"; diff --git a/web/ce/components/breadcrumbs/feature.tsx b/web/ce/components/breadcrumbs/project-feature.tsx similarity index 100% rename from web/ce/components/breadcrumbs/feature.tsx rename to web/ce/components/breadcrumbs/project-feature.tsx From ae0867e7270be26348c007e07a7ab9cffb749dd6 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Wed, 18 Jun 2025 15:29:12 +0530 Subject: [PATCH 18/18] chore: code refactor --- web/core/components/common/breadcrumb-link.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/common/breadcrumb-link.tsx b/web/core/components/common/breadcrumb-link.tsx index f8b56fed37a..1dc85e3e2ae 100644 --- a/web/core/components/common/breadcrumb-link.tsx +++ b/web/core/components/common/breadcrumb-link.tsx @@ -7,7 +7,7 @@ import { Breadcrumbs } from "@plane/ui"; import { usePlatformOS } from "@/hooks/use-platform-os"; type Props = { - label?: string | ReactNode; + label?: string; href?: string; icon?: React.ReactNode; disableTooltip?: boolean;