From 09bd91e7a997acc56ce199082c904bd7d511c193 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 22 Jul 2025 23:20:04 +0200 Subject: [PATCH 1/8] frontend-plugin-api: add NavContentBlueprint Signed-off-by: Patrik Oldsberg --- .../blueprints/NavContentBlueprint.test.tsx | 112 ++++++++++++++++++ .../src/blueprints/NavContentBlueprint.ts | 74 ++++++++++++ .../src/blueprints/index.ts | 5 + 3 files changed, 191 insertions(+) create mode 100644 packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx create mode 100644 packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts diff --git a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx new file mode 100644 index 0000000000..a113a2f21d --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRouteRef } from '@backstage/frontend-plugin-api'; +import { NavContentBlueprint } from './NavContentBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; + +const routeRef = createRouteRef(); + +describe('NavContentBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const extension = NavContentBlueprint.make({ + params: { + component: () =>
Nav content
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "app/nav", + "input": "content", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "nav-content", + "name": undefined, + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should return a valid component', () => { + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: () =>
Nav content
, + }, + }); + + const tester = createExtensionTester(extension); + + expect( + tester.get(NavContentBlueprint.dataRefs.component)({ items: [] }), + ).toEqual(
Nav content
); + }); + + it('should return a valid component with items', () => { + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: ({ items }) => ( +
+ Items: + {items.map((item, index) => ( + + {item.title} + + ))} +
+ ), + }, + }); + + const tester = createExtensionTester(extension); + + expect( + tester.get(NavContentBlueprint.dataRefs.component)({ + items: [ + { + to: '/', + text: 'Home', + title: 'Home', + icon: () => null, + routeRef, + }, + ], + }), + ).toEqual( +
+ Items: + {[ + + Home + , + ]} +
, + ); + }); +}); diff --git a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts new file mode 100644 index 0000000000..a6524ea49e --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IconComponent, RouteRef } from '@backstage/frontend-plugin-api'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +/** + * The props for the {@link NavContentComponent}. + * + * @public + */ +export interface NavContentComponentProps { + /** + * The nav items available to the component. These are all the items created + * with the {@link NavItemBlueprint} in the app. + * + * In addition to the original properties from the nav items, these also + * include a resolved route path as `to`, and duplicated `title` as `text` to + * simplify rendering. + */ + items: Array<{ + // Original props from nav items + icon: IconComponent; + title: string; + routeRef: RouteRef; + + // Additional props to simplify item rendering + to: string; + text: string; + }>; +} + +/** + * A component that renders the nav bar content, to be passed to the {@link NavContentBlueprint}. + * + * @public + */ +export type NavContentComponent = ( + props: NavContentComponentProps, +) => JSX.Element | null; + +const componentDataRef = createExtensionDataRef().with({ + id: 'core.nav-content.component', +}); + +/** + * Creates an extension that replaces the entire nav bar with your own component. + * + * @public + */ +export const NavContentBlueprint = createExtensionBlueprint({ + kind: 'nav-content', + attachTo: { id: 'app/nav', input: 'content' }, + output: [componentDataRef], + dataRefs: { + component: componentDataRef, + }, + *factory(params: { component: NavContentComponent }) { + yield componentDataRef(params.component); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index 85f7a99a2c..5460e05fa3 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -18,6 +18,11 @@ export { ApiBlueprint } from './ApiBlueprint'; export { AppRootElementBlueprint } from './AppRootElementBlueprint'; export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; export { IconBundleBlueprint } from './IconBundleBlueprint'; +export { + NavContentBlueprint, + type NavContentComponent, + type NavContentComponentProps, +} from './NavContentBlueprint'; export { NavItemBlueprint } from './NavItemBlueprint'; export { NavLogoBlueprint } from './NavLogoBlueprint'; export { PageBlueprint } from './PageBlueprint'; From 69cdc24ab76e7e17cafe8a722bd792f14cacff35 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 22 Jul 2025 14:01:09 +0200 Subject: [PATCH 2/8] frontend-plugin-api: removed NavLogoBlueprint Signed-off-by: Patrik Oldsberg --- .../src/blueprints/NavLogoBlueprint.test.tsx | 72 ------------------- .../src/blueprints/NavLogoBlueprint.ts | 48 ------------- .../src/blueprints/index.ts | 1 - 3 files changed, 121 deletions(-) delete mode 100644 packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx delete mode 100644 packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx deleted file mode 100644 index bbd96ea53b..0000000000 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { NavLogoBlueprint } from './NavLogoBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; - -describe('NavLogoBlueprint', () => { - it('should create an extension with sensible defaults', () => { - const extension = NavLogoBlueprint.make({ - params: { - logoFull:
Logo Full
, - logoIcon:
Logo Icon
, - }, - }); - - expect(extension).toMatchInlineSnapshot(` - { - "$$type": "@backstage/ExtensionDefinition", - "T": undefined, - "attachTo": { - "id": "app/nav", - "input": "logos", - }, - "configSchema": undefined, - "disabled": false, - "factory": [Function], - "inputs": {}, - "kind": "nav-logo", - "name": undefined, - "output": [ - [Function], - ], - "override": [Function], - "toString": [Function], - "version": "v2", - } - `); - }); - - it('should return a valid component ref', () => { - const logoFull =
Logo Full
; - const logoIcon =
Logo Icon
; - - const extension = NavLogoBlueprint.make({ - name: 'test', - params: { - logoFull, - logoIcon, - }, - }); - - const tester = createExtensionTester(extension); - - expect(tester.get(NavLogoBlueprint.dataRefs.logoElements)).toEqual({ - logoFull, - logoIcon, - }); - }); -}); diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts deleted file mode 100644 index 4a37859975..0000000000 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; - -const logoElementsDataRef = createExtensionDataRef<{ - logoIcon?: JSX.Element; - logoFull?: JSX.Element; -}>().with({ id: 'core.nav-logo.logo-elements' }); - -/** - * Creates an extension that replaces the logo in the nav bar with your own. - * - * @public - */ -export const NavLogoBlueprint = createExtensionBlueprint({ - kind: 'nav-logo', - attachTo: { id: 'app/nav', input: 'logos' }, - output: [logoElementsDataRef], - dataRefs: { - logoElements: logoElementsDataRef, - }, - *factory({ - logoIcon, - logoFull, - }: { - logoIcon: JSX.Element; - logoFull: JSX.Element; - }) { - yield logoElementsDataRef({ - logoIcon, - logoFull, - }); - }, -}); diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index 5460e05fa3..047b007226 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -24,7 +24,6 @@ export { type NavContentComponentProps, } from './NavContentBlueprint'; export { NavItemBlueprint } from './NavItemBlueprint'; -export { NavLogoBlueprint } from './NavLogoBlueprint'; export { PageBlueprint } from './PageBlueprint'; export { RouterBlueprint } from './RouterBlueprint'; export { SignInPageBlueprint } from './SignInPageBlueprint'; From 45444b71991f13bcc073a6b98e581229b421f8ba Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 22 Jul 2025 23:23:08 +0200 Subject: [PATCH 3/8] plugins/app: simplify nav bar and integrate nav content Signed-off-by: Patrik Oldsberg --- plugins/app/src/extensions/AppNav.tsx | 144 +++++++++++++------------- 1 file changed, 72 insertions(+), 72 deletions(-) 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} + />, + ); + }, }); From 6386c513a95ddeb54c81cd73d86063d36e6f159f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Jul 2025 09:40:26 +0200 Subject: [PATCH 4/8] app-next: add example sidebar Signed-off-by: Patrik Oldsberg --- packages/app-next/app-config.yaml | 7 + packages/app-next/src/App.tsx | 2 + .../app-next/src/modules/appModuleNav.tsx | 142 ++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 packages/app-next/src/modules/appModuleNav.tsx diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index da233b0188..5665324be6 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -26,6 +26,13 @@ app: # - apis.plugin.graphiql.browse.gitlab: true # - graphiql-endpoint:graphiql/gitlab: true + - nav-item:search: false + - nav-item:user-settings: false + - nav-item:catalog + - nav-item:api-docs + - nav-item:scaffolder + - nav-item:app-visualizer + # Pages - page:catalog/entity: config: diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 43cc63d1a4..1eeedfa9be 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -44,6 +44,7 @@ import { convertLegacyPlugin } from '@backstage/core-compat-api'; import { convertLegacyPageExtension } from '@backstage/core-compat-api'; import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; import { pluginInfoResolver } from './pluginInfoResolver'; +import { appModuleNav } from './modules/appModuleNav'; /* @@ -130,6 +131,7 @@ const app = createApp({ appVisualizerPlugin, kubernetesPlugin, notFoundErrorPageModule, + appModuleNav, customHomePageModule, ...collectedLegacyPlugins, ], diff --git a/packages/app-next/src/modules/appModuleNav.tsx b/packages/app-next/src/modules/appModuleNav.tsx new file mode 100644 index 0000000000..aa7d92bd1b --- /dev/null +++ b/packages/app-next/src/modules/appModuleNav.tsx @@ -0,0 +1,142 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { compatWrapper } from '@backstage/core-compat-api'; +import { + Link, + Sidebar, + sidebarConfig, + SidebarDivider, + SidebarGroup, + SidebarItem, + SidebarScrollWrapper, + SidebarSpace, + useSidebarOpenState, +} from '@backstage/core-components'; +import SearchIcon from '@material-ui/icons/Search'; +import MenuIcon from '@material-ui/icons/Menu'; +import BuildIcon from '@material-ui/icons/Build'; +import { + createFrontendModule, + NavContentBlueprint, +} from '@backstage/frontend-plugin-api'; +import { SidebarSearchModal } from '@backstage/plugin-search'; +import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; +import { + Settings, + UserSettingsSignInAvatar, +} from '@backstage/plugin-user-settings'; +import { makeStyles } from '@material-ui/core/styles'; + +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 = () => { + const classes = useSidebarLogoStyles(); + const { isOpen } = useSidebarOpenState(); + + return ( +
+ + {isOpen ? ( + + + + ) : ( + + + + )} + +
+ ); +}; + +export const appModuleNav = createFrontendModule({ + pluginId: 'app', + extensions: [ + NavContentBlueprint.make({ + params: { + component: ({ items }) => { + return compatWrapper( + + + } to="/search"> + + + + }> + + {items.map((item, index) => ( + + ))} + + + + + + } + to="/settings" + > + + + + + , + ); + }, + }, + }), + ], +}); From 29786f6d8eeb69e4c4807785d86be56a19a0693e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Jul 2025 10:00:14 +0200 Subject: [PATCH 5/8] changesets: add changesets for nav content blueprint Signed-off-by: Patrik Oldsberg --- .changeset/five-ducks-hide.md | 31 +++++++++++++++++++++++++++++++ .changeset/full-streets-take.md | 5 +++++ 2 files changed, 36 insertions(+) create mode 100644 .changeset/five-ducks-hide.md create mode 100644 .changeset/full-streets-take.md diff --git a/.changeset/five-ducks-hide.md b/.changeset/five-ducks-hide.md new file mode 100644 index 0000000000..6ce1fec8f7 --- /dev/null +++ b/.changeset/five-ducks-hide.md @@ -0,0 +1,31 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The `NavLogoBlueprint` has been removed and replaced by `NavContentBlueprint`, which instead replaces the entire navbar. The default navbar has also been switched to a more minimal implementation. + +To use `NavContentBlueprint` to install new logos, you can use it as follows: + +```tsx +NavContentBlueprint.make({ + params: { + component: ({ items }) => { + return compatWrapper( + + + + {/* Other sidebar content */} + + + {items.map((item, index) => ( + + ))} + + + {/* Other sidebar content */} + , + ); + }, + }, +}); +``` diff --git a/.changeset/full-streets-take.md b/.changeset/full-streets-take.md new file mode 100644 index 0000000000..f04cd92c57 --- /dev/null +++ b/.changeset/full-streets-take.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': minor +--- + +Updated the `app/nav` extension to use the new `NavContentBlueprint`, and removed support for extensions created with the now removed `NavLogoBlueprint`. From 533ec2a0b54bd056fdb30d910c07ad97d4c805ed Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Jul 2025 10:08:33 +0200 Subject: [PATCH 6/8] update API reports for nav content update Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/report.api.md | 83 +++++++++++++--------- plugins/app/report.api.md | 10 ++- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 6101f3e7c9..cd8f3d02ce 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -27,6 +27,7 @@ import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { ConfigApi } from '@backstage/core-plugin-api'; import { configApiRef } from '@backstage/core-plugin-api'; +import { ConfigurableExtensionDataRef as ConfigurableExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; import { createApiFactory } from '@backstage/core-plugin-api'; import { createApiRef } from '@backstage/core-plugin-api'; import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; @@ -39,6 +40,7 @@ import { ErrorApiError } from '@backstage/core-plugin-api'; import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; import { errorApiRef } from '@backstage/core-plugin-api'; import { Expand } from '@backstage/types'; +import { ExtensionBlueprint as ExtensionBlueprint_2 } from '@backstage/frontend-plugin-api'; import { FeatureFlag } from '@backstage/core-plugin-api'; import { FeatureFlagsApi } from '@backstage/core-plugin-api'; import { featureFlagsApiRef } from '@backstage/core-plugin-api'; @@ -49,7 +51,8 @@ import { fetchApiRef } from '@backstage/core-plugin-api'; import { githubAuthApiRef } from '@backstage/core-plugin-api'; import { gitlabAuthApiRef } from '@backstage/core-plugin-api'; import { googleAuthApiRef } from '@backstage/core-plugin-api'; -import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api'; +import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; +import { IconComponent as IconComponent_3 } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { identityApiRef } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; @@ -70,6 +73,7 @@ import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; import { SessionState } from '@backstage/core-plugin-api'; import { SignInPageProps } from '@backstage/core-plugin-api'; @@ -1413,19 +1417,59 @@ export { identityApiRef }; export { microsoftAuthApiRef }; +// @public +export const NavContentBlueprint: ExtensionBlueprint_2<{ + kind: 'nav-content'; + name: undefined; + params: { + component: NavContentComponent; + }; + output: ConfigurableExtensionDataRef_2< + NavContentComponent, + 'core.nav-content.component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef_2< + NavContentComponent, + 'core.nav-content.component', + {} + >; + }; +}>; + +// @public +export type NavContentComponent = ( + props: NavContentComponentProps, +) => JSX.Element | null; + +// @public +export interface NavContentComponentProps { + items: Array<{ + icon: IconComponent_2; + title: string; + routeRef: RouteRef_2; + to: string; + text: string; + }>; +} + // @public export const NavItemBlueprint: ExtensionBlueprint<{ kind: 'nav-item'; name: undefined; params: { title: string; - icon: IconComponent_2; + icon: IconComponent_3; routeRef: RouteRef; }; output: ConfigurableExtensionDataRef< { title: string; - icon: IconComponent_2; + icon: IconComponent_3; routeRef: RouteRef; }, 'core.nav-item.target', @@ -1438,7 +1482,7 @@ export const NavItemBlueprint: ExtensionBlueprint<{ target: ConfigurableExtensionDataRef< { title: string; - icon: IconComponent_2; + icon: IconComponent_3; routeRef: RouteRef; }, 'core.nav-item.target', @@ -1447,37 +1491,6 @@ export const NavItemBlueprint: ExtensionBlueprint<{ }; }>; -// @public -export const NavLogoBlueprint: ExtensionBlueprint<{ - kind: 'nav-logo'; - name: undefined; - params: { - logoIcon: JSX.Element; - logoFull: JSX.Element; - }; - output: ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }, - 'core.nav-logo.logo-elements', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - logoElements: ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }, - 'core.nav-logo.logo-elements', - {} - >; - }; -}>; - export { OAuthApi }; export { OAuthRequestApi }; diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index 3654a3965a..027b6365b5 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -16,6 +16,7 @@ import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { NavContentComponent } from '@backstage/frontend-plugin-api'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SignInPageProps } from '@backstage/core-plugin-api'; @@ -100,13 +101,10 @@ const appPlugin: FrontendPlugin< optional: false; } >; - logos: ExtensionInput< + content: ExtensionInput< ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }, - 'core.nav-logo.logo-elements', + NavContentComponent, + 'core.nav-content.component', {} >, { From 9c3141402794b1449156c41104b9c3cfb68e2253 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Jul 2025 10:17:00 +0200 Subject: [PATCH 7/8] docs/frontend-system: update app/nav docs for nav content change Signed-off-by: Patrik Oldsberg --- .../building-apps/03-built-in-extensions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md index 4cca7cebe2..5d0898679b 100644 --- a/docs/frontend-system/building-apps/03-built-in-extensions.md +++ b/docs/frontend-system/building-apps/03-built-in-extensions.md @@ -161,10 +161,10 @@ Extension responsible for rendering the logo and items in the app's sidebar. #### Inputs -| Name | Description | Type | Optional | Default | Extension creator | -| ----- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- | -| logos | A nav logos object. | [createNavLogoExtension.logoElementsDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension.logoelementsdataref) | true | - | [createNavLogoExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension) | -| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) | +| Name | Description | Type | Optional | Default | Extension creator | +| ------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- | +| content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) | true | - | [NavContentBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) | +| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) | ### App routes From 85b6f96f64184279f9a04fe5a3d0dd2a27b9974d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Jul 2025 13:22:44 +0200 Subject: [PATCH 8/8] vocab: add "navbar" Signed-off-by: Patrik Oldsberg --- .github/vale/config/vocabularies/Backstage/accept.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 430e76a2ce..911429490c 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -288,6 +288,7 @@ namespaced namespaces Namespaces namespacing +navbar neuro newrelic nginx