diff --git a/framework/PageFramework.tsx b/framework/PageFramework.tsx
index 2bfe2a9665..47ba364382 100644
--- a/framework/PageFramework.tsx
+++ b/framework/PageFramework.tsx
@@ -7,6 +7,7 @@ import { PageNavSideBarProvider } from './PageNavigation/PageNavSidebar';
import { PageNotificationsProvider } from './PageNotifications/PageNotificationsProvider';
import { SettingsProvider } from './Settings';
import { FrameworkTranslationsProvider } from './useFrameworkTranslations';
+import { PageBreadcrumbsProvider } from './PageTabs/PageBreadcrumbs';
/**
* The `PageFramework` component bundles up all the context providers in the Ansible UI framework in a convienent component for framework consumers. Examples of internal context providers are translations, navigation, settings, alerts, and dialogs.
@@ -21,7 +22,9 @@ export function PageFramework(props: { children: ReactNode }) {
- {props.children}
+
+ {props.children}
+
diff --git a/framework/PageHeader.tsx b/framework/PageHeader.tsx
index 2494a6850f..10de219633 100644
--- a/framework/PageHeader.tsx
+++ b/framework/PageHeader.tsx
@@ -14,11 +14,12 @@ import {
Truncate,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon, OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
-import { CSSProperties, Fragment, ReactNode } from 'react';
+import { CSSProperties, Fragment, ReactNode, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import './PageFramework.css';
import { useBreakpoint } from './components/useBreakPoint';
import { useFrameworkTranslations } from './useFrameworkTranslations';
+import { usePageBreadcrumbs } from './PageTabs/PageBreadcrumbs';
export interface ICatalogBreadcrumb {
id?: string;
@@ -26,9 +27,10 @@ export interface ICatalogBreadcrumb {
to?: string;
target?: string;
component?: React.ElementType;
+ isLoading?: boolean;
}
-function Breadcrumbs(props: { breadcrumbs: ICatalogBreadcrumb[]; style?: CSSProperties }) {
+function Breadcrumbs(props: { breadcrumbs?: ICatalogBreadcrumb[]; style?: CSSProperties }) {
const navigate = useNavigate();
if (!props.breadcrumbs) return ;
return (
@@ -102,25 +104,38 @@ export interface PageHeaderProps {
*
*/
export function PageHeader(props: PageHeaderProps) {
- const { breadcrumbs, title, description, controls, headerActions, footer } = props;
+ const { title, description, controls, headerActions, footer } = props;
const isLg = useBreakpoint('lg');
const isXl = useBreakpoint('xl');
const isMdOrLarger = useBreakpoint('md');
const [translations] = useFrameworkTranslations();
+
+ const { tabBreadcrumb } = usePageBreadcrumbs();
+
+ const pageBreadcrumbs = useMemo(() => {
+ const pageBreadcrumbs = [];
+ if (props.breadcrumbs) pageBreadcrumbs.push(...props.breadcrumbs);
+ if (tabBreadcrumb) pageBreadcrumbs.push(tabBreadcrumb);
+ return pageBreadcrumbs;
+ }, [props.breadcrumbs, tabBreadcrumb]);
+
return (
- {breadcrumbs && (
-
+ {pageBreadcrumbs && (
+
)}
{title ? (
props.titleHelp ? (
diff --git a/framework/PageTabs/PageBreadcrumbs.tsx b/framework/PageTabs/PageBreadcrumbs.tsx
new file mode 100644
index 0000000000..a055a4871d
--- /dev/null
+++ b/framework/PageTabs/PageBreadcrumbs.tsx
@@ -0,0 +1,25 @@
+import { createContext, Dispatch, ReactNode, SetStateAction, useContext, useState } from 'react';
+import { ICatalogBreadcrumb } from '../PageHeader';
+
+export type PageBreadcrumbsContext = {
+ /** The tab breadcrumb is used to display the current active tab
+ * in the page breadcrumbs (displayed by the PageHeader component) */
+ tabBreadcrumb?: ICatalogBreadcrumb;
+ setTabBreadcrumb: Dispatch>;
+};
+
+export const PageBreadcrumbsContext = createContext({
+ tabBreadcrumb: {},
+ setTabBreadcrumb: () => {},
+});
+
+export function PageBreadcrumbsProvider(props: { children: ReactNode }) {
+ const [tabBreadcrumb, setTabBreadcrumb] = useState({});
+ return (
+
+ {props.children}
+
+ );
+}
+
+export const usePageBreadcrumbs = (): PageBreadcrumbsContext => useContext(PageBreadcrumbsContext);
diff --git a/framework/PageTabs/PageRoutedTabs.tsx b/framework/PageTabs/PageRoutedTabs.tsx
index 6ea27ade0e..0bfc576a83 100644
--- a/framework/PageTabs/PageRoutedTabs.tsx
+++ b/framework/PageTabs/PageRoutedTabs.tsx
@@ -4,6 +4,8 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { PageLayout, useGetPageUrl, usePageNavigate } from '..';
import { getPersistentFilters } from '../../frontend/common/PersistentFilters';
import { useSearchParams } from 'react-router-dom';
+import { usePageBreadcrumbs } from './PageBreadcrumbs';
+import { useEffect } from 'react';
export function PageRoutedTabs(props: {
backTab?: { label: string; page: string; persistentFilterKey: string };
@@ -19,10 +21,22 @@ export function PageRoutedTabs(props: {
const navigate = useNavigate();
const getPageUrl = useGetPageUrl();
const location = useLocation();
+ const { setTabBreadcrumb } = usePageBreadcrumbs();
+
const activeTab = props.tabs.find(
(tab) => tab && getPageUrl(tab.page, { params: props.params }) === location.pathname
);
+ // Set current active tab to tabBreadcrumb in the PageBreadcrumbContext
+ useEffect(() => {
+ if (activeTab) {
+ setTabBreadcrumb({ label: activeTab.label });
+ return () => setTabBreadcrumb(undefined);
+ } else {
+ setTabBreadcrumb(undefined);
+ }
+ }, [activeTab, setTabBreadcrumb]);
+
const [searchParams] = useSearchParams();
const sharedQueryKeysObj: Record = {};
diff --git a/framework/components/LoadingPage.tsx b/framework/components/LoadingPage.tsx
index 8c91d9ead6..dc78dd1bf2 100644
--- a/framework/components/LoadingPage.tsx
+++ b/framework/components/LoadingPage.tsx
@@ -9,7 +9,7 @@ export function LoadingPage(props: { breadcrumbs?: boolean; tabs?: boolean }) {
) as unknown as string }]
+ ? [{ label: () as unknown as string, isLoading: true }]
: undefined
}
title={() as unknown as string}