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';