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}
+ />,
+ );
+ },
});