[Home] first homepage template to storybook + two more home components (#8434)
* add new toolkit component Signed-off-by: Emma Indal <emmai@spotify.com> * add toolkit content test Signed-off-by: Emma Indal <emmai@spotify.com> * add key to toolkit items Signed-off-by: Emma Indal <emmai@spotify.com> * add reusable company logo component Signed-off-by: Emma Indal <emmai@spotify.com> * export root ref from search plugin Signed-off-by: Emma Indal <emmai@spotify.com> * add homepage template to storybook Signed-off-by: Emma Indal <emmai@spotify.com> * export new homepage components from plugin Signed-off-by: Emma Indal <emmai@spotify.com> * fix imports Signed-off-by: Emma Indal <emmai@spotify.com> * add search plugin to dependencies Signed-off-by: Emma Indal <emmai@spotify.com> * add home plugin changeset Signed-off-by: Emma Indal <emmai@spotify.com> * add storybook changeset Signed-off-by: Emma Indal <emmai@spotify.com> * delete changeset for storybook Signed-off-by: Emma Indal <emma.indahl@gmail.com> * delete duplicated search context provider Signed-off-by: Emma Indal <emmai@spotify.com> * use list item text props Signed-off-by: Emma Indal <emmai@spotify.com> * update home plugin documentation + add customize app docs Signed-off-by: Emma Indal <emmai@spotify.com> * named exports Signed-off-by: Emma Indal <emmai@spotify.com> * add homepage components to storybook Signed-off-by: Emma Indal <emmai@spotify.com> * use app config as default for company logo Signed-off-by: Emma Indal <emmai@spotify.com> * rename homepage template Signed-off-by: Emma Indal <emmai@spotify.com> * use props instead of context for tools, update Content component of card extension to accept props Signed-off-by: Emma Indal <emmai@spotify.com> * fix api reports + docstrings + markers Signed-off-by: Emma Indal <emmai@spotify.com> * export ToolkitContentProps instead of Tool Signed-off-by: Emma Indal <emmai@spotify.com> * docs and stories fixups Signed-off-by: Emma Indal <emmai@spotify.com> * do not export rootRouteRef from search plugin Signed-off-by: Emma Indal <emmai@spotify.com> * update docs links Signed-off-by: Emma Indal <emmai@spotify.com> * change unpacking of props Signed-off-by: Emma Indal <emmai@spotify.com> * delete extra fragment Signed-off-by: Emma Indal <emmai@spotify.com> * fixup searchbar border styles Signed-off-by: Emma Indal <emmai@spotify.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-home': patch
|
||||
---
|
||||
|
||||
Adds two new home components - CompanyLogo and Toolkit.
|
||||
@@ -253,3 +253,73 @@ const LogoFull = () => {
|
||||
return <img src={MyCustomLogoFull} />;
|
||||
};
|
||||
```
|
||||
|
||||
## Custom Homepage
|
||||
|
||||
In addition to a custom theme, a custom logo, you can also customize the
|
||||
homepage of your app. To do that we need to go through a few steps.
|
||||
|
||||
### Setting up the Home Page
|
||||
|
||||
1. Create a Home Page Component that will be used for composition.
|
||||
|
||||
`packages/app/src/components/home/HomePage.tsx`
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
|
||||
export const HomePage = () => {
|
||||
return {
|
||||
/* TODO: Compose a Home Page here */
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
2. Add a route where the homepage will live, presumably `/`.
|
||||
|
||||
`packages/app/src/App.tsx`
|
||||
|
||||
```tsx
|
||||
import { HomepageCompositionRoot } from '@backstage/plugin-home';
|
||||
import { HomePage } from './components/home/HomePage';
|
||||
|
||||
// ...
|
||||
<Route path="/" element={<HomepageCompositionRoot />}>
|
||||
<HomePage />
|
||||
</Route>;
|
||||
// ...
|
||||
```
|
||||
|
||||
### Composing your Home Page
|
||||
|
||||
Composing a Home Page is no different from creating a regular React Component,
|
||||
i.e. the App Integrator is free to include whatever content they like. However,
|
||||
there are components developed with the Home Page in mind. If you are looking
|
||||
for components to use when composing your homepage, you can take a look at the
|
||||
[collection of Homepage components](https://backstage.io/?path=/story/plugins-home-components)
|
||||
in storybook. If you don't find a component that suits your needs but want to
|
||||
contribute, check the
|
||||
[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing).
|
||||
|
||||
!!! tip If you want to use one of the available homepage templates you can find
|
||||
the
|
||||
[templates](https://backstage.io/storybook/?path=/story/plugins-home-templates)
|
||||
in the storybook under the "Home" plugin. And if you would like to contribute a
|
||||
template, please see the
|
||||
[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing)
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { HomePageCompanyLogo } from '@backstage/plugin-home';
|
||||
|
||||
export const HomePage = () => {
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<HomePageCompanyLogo className={container} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ const BACKSTAGE_CORE_STORIES = [
|
||||
'packages/core-components',
|
||||
'plugins/org',
|
||||
'plugins/search',
|
||||
'plugins/home',
|
||||
];
|
||||
|
||||
module.exports = ({ args }) => {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# Home
|
||||
|
||||
Development is ongoing. You can follow the progress and contribute at the Backstage [Home Project Board](https://github.com/backstage/backstage/projects/7) or reach out to us in the [`#support` Discord channel](https://discord.com/channels/687207715902193673/687235481154617364).
|
||||
|
||||
## Overview
|
||||
|
||||
The Home plugin introduces a system for composing a Home Page for Backstage in order to surface relevant info and provide convenient shortcuts for common tasks. It's designed with composability in mind with an open ecosystem that allows anyone to contribute with any component, to be included in any Home Page.
|
||||
|
||||
For App Integrators, the system is designed to be composable to give total freedom in designing a Home Page that suits the needs of the organization. From the perspective of a Component Developer who wishes to contribute with building blocks to be included in Home Pages, there's a convenient interface for bundling the different parts and exporting them with both error boundary and lazy loading handled under the surface.
|
||||
@@ -90,6 +86,12 @@ Additionally, the App Integrator is provided an escape hatch in case the way the
|
||||
|
||||
## Contributing
|
||||
|
||||
### Homepage Components
|
||||
|
||||
We believe that people have great ideas for what makes a useful Home Page, and we want to make it easy for every to benefit from the effort you put in to create something cool for the Home Page. Therefore, a great way of contributing is by simply creating more Home Page Components, than can then be used by everyone when composing their own Home Page. If they are tightly coupled to an existing plugin, it is recommended to allow them to live within that plugin, for convenience and to limit complex dependencies. On the other hand, if there's no clear plugin that the component is based on, it's also fine to contribute them into the [home plugin](/plugins/home/src/homePageComponents)
|
||||
|
||||
Additionally, the API is at a very early state, so contributing with additional use cases may expose weaknesses in the current solution that we may iterate on, to provide more flexibility and ease of use for those who wish to develop components for the Home Page.
|
||||
|
||||
### Homepage Templates
|
||||
|
||||
We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example to create your own](/plugins/home/src/templates/DefaultTemplate.stories.tsx), and then open a PR with your suggestion.
|
||||
|
||||
+28
-22
@@ -7,6 +7,7 @@
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { Extension } from '@backstage/core-plugin-api';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
@@ -64,10 +65,9 @@ export const ComponentTabs: ({
|
||||
}[];
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ComponentRenderer" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "createCardExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
// Warning: (ae-forgotten-export) The symbol "CardExtensionProps" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function createCardExtension<T>({
|
||||
title,
|
||||
components,
|
||||
@@ -76,15 +76,7 @@ export function createCardExtension<T>({
|
||||
title: string;
|
||||
components: () => Promise<ComponentParts>;
|
||||
name?: string;
|
||||
}): Extension<
|
||||
({
|
||||
Renderer,
|
||||
title: overrideTitle,
|
||||
...childProps
|
||||
}: ComponentRenderer & {
|
||||
title?: string;
|
||||
} & T) => JSX.Element
|
||||
>;
|
||||
}): Extension<(props: CardExtensionProps<T>) => JSX.Element>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "HeaderWorldClock" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -95,6 +87,12 @@ export const HeaderWorldClock: ({
|
||||
clockConfigs: ClockConfig[];
|
||||
}) => JSX.Element | null;
|
||||
|
||||
// @public
|
||||
export const HomePageCompanyLogo: (props: {
|
||||
logo?: ReactNode;
|
||||
className?: string | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "HomepageCompositionRoot" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -103,18 +101,26 @@ export const HomepageCompositionRoot: (props: {
|
||||
children?: ReactNode;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ComponentRenderer" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "HomePageRandomJoke" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const HomePageRandomJoke: ({
|
||||
Renderer,
|
||||
title: overrideTitle,
|
||||
...childProps
|
||||
}: ComponentRenderer & {
|
||||
title?: string | undefined;
|
||||
} & {
|
||||
defaultCategory?: 'any' | 'programming' | undefined;
|
||||
}) => JSX.Element;
|
||||
export const HomePageRandomJoke: (
|
||||
props: ComponentRenderer & {
|
||||
title?: string | undefined;
|
||||
} & {
|
||||
defaultCategory?: 'any' | 'programming' | undefined;
|
||||
},
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ToolkitContentProps" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public
|
||||
export const HomePageToolkit: (
|
||||
props: ComponentRenderer & {
|
||||
title?: string | undefined;
|
||||
} & ToolkitContentProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "homePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -146,5 +152,5 @@ export const WelcomeTitle: () => JSX.Element;
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/extensions.d.ts:16:5 - (ae-forgotten-export) The symbol "ComponentParts" needs to be exported by the entry point index.d.ts
|
||||
// src/extensions.d.ts:24:5 - (ae-forgotten-export) The symbol "ComponentParts" needs to be exported by the entry point index.d.ts
|
||||
```
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@backstage/core-components": "^0.8.3",
|
||||
"@backstage/core-plugin-api": "^0.4.1",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@backstage/plugin-search": "^0.5.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
|
||||
@@ -26,7 +26,7 @@ export type ComponentRenderer = {
|
||||
};
|
||||
|
||||
type ComponentParts = {
|
||||
Content: () => JSX.Element;
|
||||
Content: (props?: any) => JSX.Element;
|
||||
Actions?: () => JSX.Element;
|
||||
Settings?: () => JSX.Element;
|
||||
ContextProvider?: (props: any) => JSX.Element;
|
||||
@@ -34,6 +34,13 @@ type ComponentParts = {
|
||||
|
||||
type RendererProps = { title: string } & ComponentParts;
|
||||
|
||||
type CardExtensionProps<T> = ComponentRenderer & { title?: string } & T;
|
||||
|
||||
/**
|
||||
* An extension creator to create card based components for the homepage
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createCardExtension<T>({
|
||||
title,
|
||||
components,
|
||||
@@ -48,11 +55,8 @@ export function createCardExtension<T>({
|
||||
component: {
|
||||
lazy: () =>
|
||||
components().then(({ Content, Actions, Settings, ContextProvider }) => {
|
||||
const CardExtension = ({
|
||||
Renderer,
|
||||
title: overrideTitle,
|
||||
...childProps
|
||||
}: ComponentRenderer & { title?: string } & T) => {
|
||||
const CardExtension = (props: CardExtensionProps<T>) => {
|
||||
const { Renderer, title: overrideTitle, ...childProps } = props;
|
||||
const app = useApp();
|
||||
const { Progress } = app.getComponents();
|
||||
const [settingsOpen, setSettingsOpen] = React.useState(false);
|
||||
@@ -103,7 +107,7 @@ export function createCardExtension<T>({
|
||||
<Settings />
|
||||
</SettingsModal>
|
||||
)}
|
||||
<Content />
|
||||
<Content {...childProps} />
|
||||
</InfoCard>
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2021 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 { TemplateBackstageLogo } from '../../templates';
|
||||
import { HomePageCompanyLogo } from '../../plugin';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { ConfigReader } from '@backstage/core-app-api';
|
||||
import { Grid, makeStyles } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Home/Components/CompanyLogo',
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[configApiRef, new ConfigReader({ app: { title: 'My App' } })],
|
||||
]}
|
||||
>
|
||||
<Story />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: { '/hello-company-logo': rootRouteRef },
|
||||
},
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
const useLogoStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
margin: theme.spacing(5, 0),
|
||||
},
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 100,
|
||||
},
|
||||
path: {
|
||||
fill: '#7df3e1',
|
||||
},
|
||||
}));
|
||||
|
||||
export const Default = () => {
|
||||
const { container } = useLogoStyles();
|
||||
|
||||
return (
|
||||
<Grid container justifyContent="center" spacing={6}>
|
||||
<HomePageCompanyLogo className={container} />
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomLogo = () => {
|
||||
const { container, svg, path } = useLogoStyles();
|
||||
|
||||
return (
|
||||
<Grid container justifyContent="center" spacing={6}>
|
||||
<HomePageCompanyLogo
|
||||
className={container}
|
||||
logo={<TemplateBackstageLogo classes={{ svg, path }} />}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 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 { CompanyLogo } from './CompanyLogo';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { ConfigReader } from '@backstage/core-app-api';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
|
||||
describe('<CompanyLogo>', () => {
|
||||
it('should have a fall back if logo is not provided', async () => {
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[[configApiRef, new ConfigReader({ app: { title: 'My App' } })]]}
|
||||
>
|
||||
<CompanyLogo />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(getByRole('heading', { name: 'My App' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show provided company logo', async () => {
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<CompanyLogo logo={<Typography variant="h1">Backstage</Typography>} />,
|
||||
);
|
||||
|
||||
expect(getByRole('heading', { name: 'Backstage' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Typography } from '@material-ui/core';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import React from 'react';
|
||||
|
||||
type CompanyLogoProps = {
|
||||
logo?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A component to display a company logo for the user.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const CompanyLogo = (props: CompanyLogoProps) => {
|
||||
const { logo, className } = props;
|
||||
const configApi = useApi(configApiRef);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{logo ? (
|
||||
<>{logo}</>
|
||||
) : (
|
||||
<Typography variant="h1">{configApi.getString('app.title')}</Typography>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
export { CompanyLogo } from './CompanyLogo';
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2021 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { Content } from './Content';
|
||||
|
||||
describe('<ToolkitContent>', () => {
|
||||
test('should render list of tools', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Content
|
||||
tools={[
|
||||
{ label: 'tool', url: '/url', icon: <div>icon</div> },
|
||||
{ label: 'tool 2', url: '/url-2', icon: <div>icon 2</div> },
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText('tool')).toBeInTheDocument();
|
||||
expect(getByText('tool 2')).toBeInTheDocument();
|
||||
expect(getByText('tool').closest('a')).toHaveAttribute('href', '/url');
|
||||
expect(getByText('tool 2').closest('a')).toHaveAttribute('href', '/url-2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Link } from '@backstage/core-components';
|
||||
import {
|
||||
makeStyles,
|
||||
List,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
toolkit: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
textAlign: 'center',
|
||||
},
|
||||
tool: {
|
||||
margin: theme.spacing(0.5, 1),
|
||||
},
|
||||
label: {
|
||||
marginTop: theme.spacing(1),
|
||||
fontSize: '0.9em',
|
||||
lineHeight: '1.25',
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
icon: {
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
borderRadius: '50px',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
boxShadow: theme.shadows[1],
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
}));
|
||||
|
||||
type Tool = {
|
||||
label: string;
|
||||
url: string;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for Toolkit content component {@link Content}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ToolkitContentProps = {
|
||||
tools: Tool[];
|
||||
};
|
||||
|
||||
/**
|
||||
* A component to display a list of tools for the user.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const Content = (props: ToolkitContentProps) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<List className={classes.toolkit}>
|
||||
{props.tools.map((tool: Tool) => (
|
||||
<Link key={tool.url} to={tool.url} className={classes.tool}>
|
||||
<ListItemIcon className={classes.icon}>{tool.icon}</ListItemIcon>
|
||||
<ListItemText
|
||||
secondaryTypographyProps={{ className: classes.label }}
|
||||
secondary={tool.label}
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 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 { TemplateBackstageLogoIcon } from '../../templates';
|
||||
import { HomePageToolkit } from '../../plugin';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Home/Components/Toolkit',
|
||||
decorators: [(Story: ComponentType<{}>) => wrapInTestApp(<Story />)],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageToolkit
|
||||
tools={Array(8).fill({
|
||||
url: '#',
|
||||
label: 'link',
|
||||
icon: <TemplateBackstageLogoIcon />,
|
||||
})}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { Content } from './Content';
|
||||
export type { ToolkitContentProps } from './Content';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export * from './CompanyLogo';
|
||||
export * from './Toolkit';
|
||||
@@ -24,6 +24,8 @@ export {
|
||||
homePlugin,
|
||||
HomepageCompositionRoot,
|
||||
HomePageRandomJoke,
|
||||
HomePageToolkit,
|
||||
HomePageCompanyLogo,
|
||||
ComponentAccordion,
|
||||
ComponentTabs,
|
||||
ComponentTab,
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { createCardExtension } from './extensions';
|
||||
import { ToolkitContentProps } from './homePageComponents';
|
||||
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
@@ -79,6 +80,21 @@ export const WelcomeTitle = homePlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* A component to display a company logo for the user.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const HomePageCompanyLogo = homePlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'CompanyLogo',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./homePageComponents/CompanyLogo').then(m => m.CompanyLogo),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const HomePageRandomJoke = homePlugin.provide(
|
||||
createCardExtension<{ defaultCategory?: 'any' | 'programming' }>({
|
||||
name: 'HomePageRandomJoke',
|
||||
@@ -86,3 +102,16 @@ export const HomePageRandomJoke = homePlugin.provide(
|
||||
components: () => import('./homePageComponents/RandomJoke'),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* A component to display a list of tools for the user.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const HomePageToolkit = homePlugin.provide(
|
||||
createCardExtension<ToolkitContentProps>({
|
||||
name: 'HomePageToolkit',
|
||||
title: 'Toolkit',
|
||||
components: () => import('./homePageComponents/Toolkit'),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2021 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 {TemplateBackstageLogo} from './TemplateBackstageLogo';
|
||||
import {TemplateBackstageLogoIcon} from './TemplateBackstageLogoIcon';
|
||||
import { HomePageToolkit, HomePageCompanyLogo } from '../plugin';
|
||||
import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { Content, Page, InfoCard } from '@backstage/core-components';
|
||||
import {
|
||||
HomePageSearchBar,
|
||||
SearchContextProvider,
|
||||
searchApiRef,
|
||||
searchPlugin
|
||||
} from '@backstage/plugin-search';
|
||||
import { Grid, makeStyles } from '@material-ui/core';
|
||||
import React, { ComponentType} from 'react';
|
||||
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Home/Templates',
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<>
|
||||
<TestApiProvider apis={[[searchApiRef, {query: () => Promise.resolve({results: []})}]]}>
|
||||
<Story />
|
||||
</TestApiProvider>
|
||||
</>,
|
||||
{
|
||||
mountedRoutes: {'/hello-company': searchPlugin.routes.root }
|
||||
}
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
search: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
boxShadow: theme.shadows[1],
|
||||
maxWidth: '60vw',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: '8px 0',
|
||||
borderColor: 'transparent',
|
||||
borderRadius: '50px',
|
||||
margin: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
const useLogoStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
margin: theme.spacing(5, 0),
|
||||
},
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 100,
|
||||
},
|
||||
path: {
|
||||
fill: '#7df3e1',
|
||||
},
|
||||
}));
|
||||
|
||||
export const DefaultTemplate = () => {
|
||||
const { search } = useStyles();
|
||||
const { svg, path, container } = useLogoStyles();
|
||||
|
||||
return (
|
||||
<SearchContextProvider>
|
||||
<Page themeId="home">
|
||||
<Content>
|
||||
<Grid container justifyContent="center" spacing={6}>
|
||||
<HomePageCompanyLogo
|
||||
className={container}
|
||||
logo={<TemplateBackstageLogo classes={{svg, path}} />}
|
||||
/>
|
||||
<Grid container item xs={12} alignItems="center" direction="row">
|
||||
<HomePageSearchBar
|
||||
className={search}
|
||||
placeholder="Search"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<InfoCard title="Composable Section">
|
||||
{/* placeholder for content */}
|
||||
<div style={{ height: 210 }} />
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<HomePageToolkit
|
||||
tools={Array(8).fill({
|
||||
url: '#',
|
||||
label: 'link',
|
||||
icon: <TemplateBackstageLogoIcon />,
|
||||
})}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<InfoCard title="Composable Section">
|
||||
{/* placeholder for content */}
|
||||
<div style={{ height: 210 }} />
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<InfoCard title="Composable Section">
|
||||
{/* placeholder for content */}
|
||||
<div style={{ height: 210 }} />
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
</SearchContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2020 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 React from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 28,
|
||||
},
|
||||
path: {
|
||||
fill: '#7df3e1',
|
||||
},
|
||||
});
|
||||
|
||||
export const TemplateBackstageLogoIcon = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={classes.svg}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 337.46 428.5"
|
||||
>
|
||||
<path
|
||||
className={classes.path}
|
||||
d="M303,166.05a80.69,80.69,0,0,0,13.45-10.37c.79-.77,1.55-1.53,2.3-2.3a83.12,83.12,0,0,0,7.93-9.38A63.69,63.69,0,0,0,333,133.23a48.58,48.58,0,0,0,4.35-16.4c1.49-19.39-10-38.67-35.62-54.22L198.56,0,78.3,115.23,0,190.25l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.69,19.16-18.36,25.52-42.12,13.7-61.87a49.22,49.22,0,0,0-6.8-8.87A89.17,89.17,0,0,0,259,178.29h.15a85.08,85.08,0,0,0,31-5.79A80.88,80.88,0,0,0,303,166.05ZM202.45,225.86c-19.32,18.51-50.4,21.23-75.7,5.9L51.61,186.15l67.45-64.64,76.41,46.38C223,184.58,221.49,207.61,202.45,225.86Zm8.93-82.22-70.65-42.89L205.14,39,274.51,81.1c25.94,15.72,29.31,37,10.55,55A60.69,60.69,0,0,1,211.38,143.64Zm29.86,190c-19.57,18.75-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,282.52v24.67L108.6,373.1a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A87.27,87.27,0,0,1,241.24,333.68Zm0-39c-19.57,18.75-46.17,29.08-74.88,29.08a123.81,123.81,0,0,1-64.1-18.19L0,243.53v24.68l108.6,65.91a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.5v-1.78A87.27,87.27,0,0,1,241.24,294.7Zm0-39c-19.57,18.76-46.17,29.09-74.88,29.09a123.81,123.81,0,0,1-64.1-18.19L0,204.55v24.68l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.68,12.88-12.35,20-27.13,19.68-41.5v-1.82A86.09,86.09,0,0,1,241.24,255.71Zm83.7,25.74a94.15,94.15,0,0,1-60.2,25.86h0V334a81.6,81.6,0,0,0,51.74-22.37c14-13.38,21.14-28.11,21-42.64v-2.19A94.92,94.92,0,0,1,324.94,281.45Zm-83.7,91.21c-19.57,18.76-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,321.5v24.68l108.6,65.9a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.8,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A86.29,86.29,0,0,1,241.24,372.66ZM327,162.45c-.68.69-1.35,1.38-2.05,2.06a94.37,94.37,0,0,1-10.64,8.65,91.35,91.35,0,0,1-11.6,7,94.53,94.53,0,0,1-26.24,8.71,97.69,97.69,0,0,1-14.16,1.57c.5,1.61.9,3.25,1.25,4.9a53.27,53.27,0,0,1,1.14,12V217h.05a84.41,84.41,0,0,0,25.35-5.55,81,81,0,0,0,26.39-16.82c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,172.17a48.55,48.55,0,0,0,4.32-16.45c.09-1.23.2-2.47.19-3.7V150q-1.08,1.54-2.25,3.09A96.73,96.73,0,0,1,327,162.45Zm0,77.92c-.69.7-1.31,1.41-2,2.1a94.2,94.2,0,0,1-60.2,25.86h0l0,26.67h0a81.6,81.6,0,0,0,51.74-22.37A73.51,73.51,0,0,0,333,250.13a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.47.19-3.71v-2.19c-.74,1.07-1.46,2.15-2.27,3.21A95.68,95.68,0,0,1,327,240.37Zm0-39c-.69.7-1.31,1.41-2,2.1a93.18,93.18,0,0,1-10.63,8.65,91.63,91.63,0,0,1-11.63,7,95.47,95.47,0,0,1-37.94,10.18h0V256h0a81.65,81.65,0,0,0,51.74-22.37c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,211.15a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.48.19-3.71v-2.2c-.74,1.08-1.46,2.16-2.27,3.22A95.68,95.68,0,0,1,327,201.39Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { TemplateBackstageLogoIcon } from './TemplateBackstageLogoIcon';
|
||||
export { TemplateBackstageLogo } from './TemplateBackstageLogo'
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2021 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 { rootRouteRef, HomePageSearchBar } from '../../plugin';
|
||||
import { searchApiRef } from '../../apis';
|
||||
import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { Grid, makeStyles } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Home/Components/SearchBar',
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[searchApiRef, { query: () => Promise.resolve({ results: [] }) }],
|
||||
]}
|
||||
>
|
||||
<Story />
|
||||
</TestApiProvider>
|
||||
</>,
|
||||
{
|
||||
mountedRoutes: { '/hello-search': rootRouteRef },
|
||||
},
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
search: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
boxShadow: theme.shadows[1],
|
||||
maxWidth: '60vw',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: '8px 0',
|
||||
borderColor: 'transparent',
|
||||
borderRadius: '50px',
|
||||
margin: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
export const Default = () => {
|
||||
const { search } = useStyles();
|
||||
|
||||
return (
|
||||
<Grid container justifyContent="center" spacing={6}>
|
||||
<Grid container item xs={12} alignItems="center" direction="row">
|
||||
<HomePageSearchBar className={search} placeholder="Search" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user