diff --git a/packages/core/src/api/app/AppBuilder.tsx b/packages/core/src/api/app/AppBuilder.tsx
index f65482b996..57c4eccf16 100644
--- a/packages/core/src/api/app/AppBuilder.tsx
+++ b/packages/core/src/api/app/AppBuilder.tsx
@@ -84,6 +84,19 @@ export default class AppBuilder {
);
break;
}
+ case 'nav-target-component': {
+ const { target, component, options = {} } = output;
+ const { exact = true } = options;
+ routes.push(
+ ,
+ );
+ break;
+ }
case 'redirect-route': {
const { path, target, options = {} } = output;
const { exact = true } = options;
diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts
index e60a7efa18..8c8972c72c 100644
--- a/packages/core/src/api/index.ts
+++ b/packages/core/src/api/index.ts
@@ -16,5 +16,6 @@
export * from './api';
export * from './apis';
+export * from './navTargets';
export { FeatureFlags } from './app/FeatureFlags';
export { useApp } from './app/AppContext';
diff --git a/packages/core/src/api/navTargets/NavTarget.ts b/packages/core/src/api/navTargets/NavTarget.ts
new file mode 100644
index 0000000000..ee964abb01
--- /dev/null
+++ b/packages/core/src/api/navTargets/NavTarget.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 type { NavTargetConfig, NavTargetOverrideConfig } from './types';
+
+export class MutableNavTarget {
+ private effectiveConfig: NavTargetConfig = this.config;
+
+ constructor(private readonly config: NavTargetConfig) {}
+
+ override(overrideConfig: NavTargetOverrideConfig) {
+ this.effectiveConfig = { ...this.config, ...overrideConfig };
+ }
+
+ get icon() {
+ return this.effectiveConfig.icon;
+ }
+
+ get path() {
+ return this.effectiveConfig.path;
+ }
+
+ get title() {
+ return this.effectiveConfig.title;
+ }
+}
+
+export function createNavTarget(config: NavTargetConfig): MutableNavTarget {
+ return new MutableNavTarget(config);
+}
diff --git a/packages/core/src/api/navTargets/index.ts b/packages/core/src/api/navTargets/index.ts
new file mode 100644
index 0000000000..6c2c899f89
--- /dev/null
+++ b/packages/core/src/api/navTargets/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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.
+ */
+
+export type { NavTarget, NavTargetConfig, NavTargetOverrideConfig } from './types';
+export { createNavTarget } from './NavTarget';
diff --git a/packages/core/src/api/navTargets/types.ts b/packages/core/src/api/navTargets/types.ts
new file mode 100644
index 0000000000..104b8203b0
--- /dev/null
+++ b/packages/core/src/api/navTargets/types.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 } from '../../icons';
+
+export type NavTarget = {
+ path: string;
+ icon: IconComponent;
+ title: string;
+};
+
+export type NavTargetConfig = {
+ path: string;
+ icon: IconComponent;
+ title: string;
+};
+
+export type NavTargetOverrideConfig = {
+ path?: string;
+ icon?: IconComponent;
+ title?: string;
+};
diff --git a/packages/core/src/api/plugin/Plugin.tsx b/packages/core/src/api/plugin/Plugin.tsx
index 3d067c66ad..d4f4aed2a6 100644
--- a/packages/core/src/api/plugin/Plugin.tsx
+++ b/packages/core/src/api/plugin/Plugin.tsx
@@ -22,6 +22,7 @@ import {
FeatureFlagName,
} from './types';
import { validateBrowserCompat, validateFlagName } from '../app/FeatureFlags';
+import { NavTarget } from '../navTargets';
export type PluginConfig = {
id: string;
@@ -34,6 +35,12 @@ export type PluginHooks = {
};
export type RouterHooks = {
+ addRoute(
+ target: NavTarget,
+ Component: ComponentType,
+ options?: RouteOptions,
+ ): void;
+
registerRoute(
path: RoutePath,
Component: ComponentType,
@@ -75,6 +82,14 @@ export default class Plugin {
this.config.register({
router: {
+ addRoute(target, component, options) {
+ outputs.push({
+ type: 'nav-target-component',
+ target,
+ component,
+ options,
+ });
+ },
registerRoute(path, component, options) {
outputs.push({ type: 'route', path, component, options });
},
diff --git a/packages/core/src/api/plugin/types.ts b/packages/core/src/api/plugin/types.ts
index c4ac426e5c..2c471f5aca 100644
--- a/packages/core/src/api/plugin/types.ts
+++ b/packages/core/src/api/plugin/types.ts
@@ -15,6 +15,7 @@
*/
import { ComponentType } from 'react';
+import { NavTarget } from '../navTargets';
export type RouteOptions = {
// Whether the route path must match exactly, defaults to true.
@@ -30,6 +31,13 @@ export type RouteOutput = {
options?: RouteOptions;
};
+export type RouteTargetOutput = {
+ type: 'nav-target-component';
+ target: NavTarget;
+ component: ComponentType<{}>;
+ options?: RouteOptions;
+};
+
export type RedirectRouteOutput = {
type: 'redirect-route';
path: RoutePath;
@@ -46,5 +54,6 @@ export type FeatureFlagOutput = {
export type PluginOutput =
| RouteOutput
+ | RouteTargetOutput
| RedirectRouteOutput
| FeatureFlagOutput;
diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx
index 87a3038a55..62f02ca58b 100644
--- a/packages/core/src/layout/Sidebar/Items.tsx
+++ b/packages/core/src/layout/Sidebar/Items.tsx
@@ -14,19 +14,13 @@
* limitations under the License.
*/
-import {
- Link,
- makeStyles,
- styled,
- SvgIcon,
- Theme,
- Typography,
-} from '@material-ui/core';
+import { Link, makeStyles, styled, Theme, Typography } from '@material-ui/core';
import clsx from 'clsx';
import React, { FC, useContext } from 'react';
import { sidebarConfig, SidebarContext } from './config';
+import { IconComponent } from '../../icons';
-const useStyles = makeStyles(theme => ({
+const useStyles = makeStyles((theme) => ({
root: {
color: '#b5b5b5',
display: 'flex',
@@ -59,7 +53,7 @@ const useStyles = makeStyles(theme => ({
}));
type SidebarItemProps = {
- icon: typeof SvgIcon;
+ icon: IconComponent;
text: string;
to?: string;
onClick?: () => void;
diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx
index 6649f1055d..e68dac4021 100644
--- a/packages/dev-utils/src/devApp/render.tsx
+++ b/packages/dev-utils/src/devApp/render.tsx
@@ -127,6 +127,18 @@ class DevAppBuilder {
);
break;
}
+ case 'nav-target-component': {
+ const { target } = output;
+ sidebarItems.push(
+ ,
+ );
+ break;
+ }
default:
break;
}
diff --git a/plugins/graphiql/src/index.ts b/plugins/graphiql/src/index.ts
index a1f5587fe1..5590a0d60f 100644
--- a/plugins/graphiql/src/index.ts
+++ b/plugins/graphiql/src/index.ts
@@ -16,3 +16,4 @@
export { plugin } from './plugin';
export * from './lib/api';
+export * from './navTargets';
diff --git a/plugins/graphiql/src/navTargets.tsx b/plugins/graphiql/src/navTargets.tsx
new file mode 100644
index 0000000000..febb6be59a
--- /dev/null
+++ b/plugins/graphiql/src/navTargets.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 React, { FC } from 'react';
+import { createNavTarget } from '@backstage/core';
+import { SvgIcon, SvgIconProps } from '@material-ui/core';
+
+const GraphiQLIcon: FC = props => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export const navTargetGraphiQL = createNavTarget({
+ icon: GraphiQLIcon,
+ path: '/graphiql',
+ title: 'GraphiQL',
+});
diff --git a/plugins/graphiql/src/plugin.ts b/plugins/graphiql/src/plugin.ts
index 6436380c27..9c73324b43 100644
--- a/plugins/graphiql/src/plugin.ts
+++ b/plugins/graphiql/src/plugin.ts
@@ -16,10 +16,11 @@
import { createPlugin } from '@backstage/core';
import { GraphiQLPage } from './components';
+import { navTargetGraphiQL } from './navTargets';
export const plugin = createPlugin({
id: 'graphiql',
register({ router }) {
- router.registerRoute('/graphiql', GraphiQLPage);
+ router.addRoute(navTargetGraphiQL, GraphiQLPage);
},
});