diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx index eaec264dca..aa030300fe 100644 --- a/plugins/app/src/extensions/AppNav.tsx +++ b/plugins/app/src/extensions/AppNav.tsx @@ -18,93 +18,93 @@ import { createExtension, coreExtensionData, createExtensionInput, - useRouteRef, NavItemBlueprint, - NavLogoBlueprint, + NavContentBlueprint, + NavContentComponentProps, + routeResolutionApiRef, + IconComponent, + RouteRef, + useApi, + NavContentComponent, } from '@backstage/frontend-plugin-api'; -import { makeStyles } from '@material-ui/core/styles'; -import { - Sidebar, - useSidebarOpenState, - Link, - sidebarConfig, - SidebarDivider, - SidebarItem, -} from '@backstage/core-components'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import LogoIcon from '../../../../packages/app/src/components/Root/LogoIcon'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import LogoFull from '../../../../packages/app/src/components/Root/LogoFull'; - -const useSidebarLogoStyles = makeStyles({ - root: { - width: sidebarConfig.drawerWidthClosed, - height: 3 * sidebarConfig.logoHeight, - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - marginBottom: -14, - }, - link: { - width: sidebarConfig.drawerWidthClosed, - marginLeft: 24, - }, -}); - -const SidebarLogo = ( - props: (typeof NavLogoBlueprint.dataRefs.logoElements)['T'], -) => { - const classes = useSidebarLogoStyles(); - const { isOpen } = useSidebarOpenState(); +import { Sidebar, SidebarItem } from '@backstage/core-components'; +import { useMemo } from 'react'; +function DefaultNavContent(props: NavContentComponentProps) { return ( -
- - {isOpen - ? props?.logoFull ?? - : props?.logoIcon ?? } - -
+ + {props.items.map((item, index) => ( + + ))} + ); -}; +} -const SidebarNavItem = ( - props: (typeof NavItemBlueprint.dataRefs.target)['T'], -) => { - const { icon: Icon, title, routeRef } = props; - const link = useRouteRef(routeRef); - if (!link) { - return null; - } - // TODO: Support opening modal, for example, the search one - return ; -}; +// This helps defer rendering until the app is being rendered, which is needed +// because the RouteResolutionApi can't be called until the app has been fully initialized. +function NavContentRenderer(props: { + Content: NavContentComponent; + items: Array<{ + title: string; + icon: IconComponent; + routeRef: RouteRef; + }>; +}) { + const routeResolutionApi = useApi(routeResolutionApiRef); + + const items = useMemo(() => { + return props.items.flatMap(item => { + const link = routeResolutionApi.resolve(item.routeRef); + if (!link) { + // eslint-disable-next-line no-console + console.warn( + `NavItemBlueprint: unable to resolve route ref ${item.routeRef}`, + ); + return []; + } + return [ + { + to: link(), + text: item.title, + icon: item.icon, + title: item.title, + routeRef: item.routeRef, + }, + ]; + }); + }, [props.items, routeResolutionApi]); + + return ; +} export const AppNav = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { items: createExtensionInput([NavItemBlueprint.dataRefs.target]), - logos: createExtensionInput([NavLogoBlueprint.dataRefs.logoElements], { + content: createExtensionInput([NavContentBlueprint.dataRefs.component], { singleton: true, optional: true, }), }, output: [coreExtensionData.reactElement], - factory: ({ inputs }) => [ - coreExtensionData.reactElement( - - - - {inputs.items.map((item, index) => ( - - ))} - , - ), - ], + *factory({ inputs }) { + const Content = + inputs.content?.get(NavContentBlueprint.dataRefs.component) ?? + DefaultNavContent; + + yield coreExtensionData.reactElement( + + item.get(NavItemBlueprint.dataRefs.target), + )} + Content={Content} + />, + ); + }, });