app-react: new API for consuming nav items that detects page extensions
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AppNode } from '@backstage/frontend-plugin-api';
|
||||
import { AppTheme } from '@backstage/frontend-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
@@ -99,6 +100,7 @@ export type NavContentComponent = (
|
||||
|
||||
// @public
|
||||
export interface NavContentComponentProps {
|
||||
// @deprecated
|
||||
items: Array<{
|
||||
icon: IconComponent;
|
||||
title: string;
|
||||
@@ -106,6 +108,23 @@ export interface NavContentComponentProps {
|
||||
to: string;
|
||||
text: string;
|
||||
}>;
|
||||
navItems: NavItems;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface NavItem {
|
||||
href: string;
|
||||
icon: IconElement;
|
||||
node: AppNode;
|
||||
routeRef: RouteRef;
|
||||
title: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface NavItems {
|
||||
clone(): NavItems;
|
||||
rest(): NavItem[];
|
||||
take(id: string): NavItem | undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -14,12 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createRouteRef } from '@backstage/frontend-plugin-api';
|
||||
import { NavContentBlueprint } from './NavContentBlueprint';
|
||||
import { AppNode, createRouteRef } from '@backstage/frontend-plugin-api';
|
||||
import { NavContentBlueprint, NavItem, NavItems } from './NavContentBlueprint';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
|
||||
const routeRef = createRouteRef();
|
||||
|
||||
function mockNode(id: string): AppNode {
|
||||
return { spec: { id } } as AppNode;
|
||||
}
|
||||
|
||||
function mockNavItems(items: NavItem[]): NavItems {
|
||||
return {
|
||||
take: () => undefined,
|
||||
rest: () => items,
|
||||
clone() {
|
||||
return mockNavItems(items);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('NavContentBlueprint', () => {
|
||||
it('should create an extension with sensible defaults', () => {
|
||||
const extension = NavContentBlueprint.make({
|
||||
@@ -52,22 +66,7 @@ describe('NavContentBlueprint', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return a valid component', () => {
|
||||
const extension = NavContentBlueprint.make({
|
||||
name: 'test',
|
||||
params: {
|
||||
component: () => <div>Nav content</div>,
|
||||
},
|
||||
});
|
||||
|
||||
const tester = createExtensionTester(extension);
|
||||
|
||||
expect(
|
||||
tester.get(NavContentBlueprint.dataRefs.component)({ items: [] }),
|
||||
).toEqual(<div>Nav content</div>);
|
||||
});
|
||||
|
||||
it('should return a valid component with items', () => {
|
||||
it('should return a valid component with legacy items', () => {
|
||||
const extension = NavContentBlueprint.make({
|
||||
name: 'test',
|
||||
params: {
|
||||
@@ -88,6 +87,7 @@ describe('NavContentBlueprint', () => {
|
||||
|
||||
expect(
|
||||
tester.get(NavContentBlueprint.dataRefs.component)({
|
||||
navItems: mockNavItems([]),
|
||||
items: [
|
||||
{
|
||||
to: '/',
|
||||
@@ -109,4 +109,68 @@ describe('NavContentBlueprint', () => {
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return a valid component with navItems', () => {
|
||||
const items: NavItem[] = [
|
||||
{
|
||||
node: mockNode('page:home'),
|
||||
href: '/',
|
||||
title: 'Home',
|
||||
icon: <span>home</span>,
|
||||
routeRef,
|
||||
},
|
||||
{
|
||||
node: mockNode('page:catalog'),
|
||||
href: '/catalog',
|
||||
title: 'Catalog',
|
||||
icon: <span>catalog</span>,
|
||||
routeRef,
|
||||
},
|
||||
{
|
||||
node: mockNode('page:docs'),
|
||||
href: '/docs',
|
||||
title: 'Docs',
|
||||
icon: <span>docs</span>,
|
||||
routeRef,
|
||||
},
|
||||
];
|
||||
|
||||
const extension = NavContentBlueprint.make({
|
||||
name: 'test',
|
||||
params: {
|
||||
component: ({ navItems }) => (
|
||||
<div>
|
||||
{navItems.rest().map(item => (
|
||||
<a key={item.node.spec.id} href={item.href}>
|
||||
{item.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const tester = createExtensionTester(extension);
|
||||
|
||||
expect(
|
||||
tester.get(NavContentBlueprint.dataRefs.component)({
|
||||
navItems: mockNavItems(items),
|
||||
items: [],
|
||||
}),
|
||||
).toEqual(
|
||||
<div>
|
||||
{[
|
||||
<a key="page:home" href="/">
|
||||
Home
|
||||
</a>,
|
||||
<a key="page:catalog" href="/catalog">
|
||||
Catalog
|
||||
</a>,
|
||||
<a key="page:docs" href="/docs">
|
||||
Docs
|
||||
</a>,
|
||||
]}
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,12 +14,50 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IconComponent, RouteRef } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
AppNode,
|
||||
IconComponent,
|
||||
IconElement,
|
||||
RouteRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
createExtensionBlueprint,
|
||||
createExtensionDataRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
/**
|
||||
* A navigation item auto-discovered from a page extension in the app.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface NavItem {
|
||||
/** The app node of the page extension that this nav item points to */
|
||||
node: AppNode;
|
||||
/** The resolved route path */
|
||||
href: string;
|
||||
/** The display title */
|
||||
title: string;
|
||||
/** The display icon */
|
||||
icon: IconElement;
|
||||
/** The route ref of the source page */
|
||||
routeRef: RouteRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of nav items that supports picking specific items by ID
|
||||
* and retrieving whatever remains. Created fresh for each render.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface NavItems {
|
||||
/** Take an item by extension ID, removing it from the collection. */
|
||||
take(id: string): NavItem | undefined;
|
||||
/** All items not yet taken. */
|
||||
rest(): NavItem[];
|
||||
/** Create a copy of the collection preserving the current taken state. */
|
||||
clone(): NavItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* The props for the {@link NavContentComponent}.
|
||||
*
|
||||
@@ -27,20 +65,21 @@ import {
|
||||
*/
|
||||
export interface NavContentComponentProps {
|
||||
/**
|
||||
* The nav items available to the component. These are all the items created
|
||||
* with the {@link @backstage/frontend-plugin-api#NavItemBlueprint} in the app.
|
||||
* Nav items auto-discovered from page extensions, with take/rest semantics
|
||||
* for placing specific items in specific positions.
|
||||
*/
|
||||
navItems: NavItems;
|
||||
|
||||
/**
|
||||
* Flat list of nav items for simple rendering. Use `navItems` for more
|
||||
* control over item placement.
|
||||
*
|
||||
* 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.
|
||||
* @deprecated Use `navItems` instead.
|
||||
*/
|
||||
items: Array<{
|
||||
// Original props from nav items
|
||||
icon: IconComponent;
|
||||
title: string;
|
||||
routeRef: RouteRef<undefined>;
|
||||
|
||||
// Additional props to simplify item rendering
|
||||
to: string;
|
||||
text: string;
|
||||
}>;
|
||||
|
||||
@@ -20,6 +20,8 @@ export { NavContentBlueprint } from './NavContentBlueprint';
|
||||
export type {
|
||||
NavContentComponent,
|
||||
NavContentComponentProps,
|
||||
NavItem,
|
||||
NavItems,
|
||||
} from './NavContentBlueprint';
|
||||
export { RouterBlueprint } from './RouterBlueprint';
|
||||
export { SignInPageBlueprint } from './SignInPageBlueprint';
|
||||
|
||||
Reference in New Issue
Block a user