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}