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