feat: add accordian home page toolkit support

Signed-off-by: Andrew Thauer <athauer@wealthsimple.com>
Co-authored-by: Emma Indal <emma.indahl@gmail.com>
This commit is contained in:
Andrew Thauer
2022-02-15 06:57:03 -05:00
parent 1c9891e3fb
commit 651b919dbb
8 changed files with 143 additions and 18 deletions
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/plugin-home': patch
---
Add Renderer support for the HomePageToolkit component.
Previously `<HomePageToolkit Renderer={ComponentAccordion} Tools={[]} />` would
result in the error `can't access property "map", props.tools is undefined`.
This change adds a context that can pass props down to the HomePageToolkit.
Also introduced is an `expanded` prop on the `ComponentAccordion` to setting
the default expanded state. See `In Accordian` story for details.
+2
View File
@@ -24,6 +24,7 @@ export type ClockConfig = {
// @public (undocumented)
export const ComponentAccordion: ({
title,
expanded,
Content,
Actions,
Settings,
@@ -31,6 +32,7 @@ export const ComponentAccordion: ({
...childProps
}: {
title: string;
expanded?: boolean | undefined;
Content: () => JSX.Element;
Actions?: (() => JSX.Element) | undefined;
Settings?: (() => JSX.Element) | undefined;
@@ -33,10 +33,14 @@ const useStyles = makeStyles((theme: Theme) => ({
settingsIconButton: {
padding: theme.spacing(0, 1, 0, 0),
},
contentContainer: {
width: '100%',
},
}));
export const ComponentAccordion = ({
title,
expanded = false,
Content,
Actions,
Settings,
@@ -44,6 +48,7 @@ export const ComponentAccordion = ({
...childProps
}: {
title: string;
expanded?: boolean;
Content: () => JSX.Element;
Actions?: () => JSX.Element;
Settings?: () => JSX.Element;
@@ -51,7 +56,7 @@ export const ComponentAccordion = ({
}) => {
const classes = useStyles();
const [settingsIsExpanded, setSettingsIsExpanded] = React.useState(false);
const [isExpanded, setIsExpanded] = React.useState(false);
const [isExpanded, setIsExpanded] = React.useState(expanded);
const handleOpenSettings = (e: any) => {
e.stopPropagation();
@@ -71,7 +76,9 @@ export const ComponentAccordion = ({
)}
<Accordion
expanded={isExpanded}
onChange={(_e: any, expanded: boolean) => setIsExpanded(expanded)}
onChange={(_e: any, expandedValue: boolean) =>
setIsExpanded(expandedValue)
}
>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
{Settings && (
@@ -85,7 +92,7 @@ export const ComponentAccordion = ({
<Typography>{title}</Typography>
</AccordionSummary>
<AccordionDetails>
<div>
<div className={classes.contentContainer}>
<Content />
{Actions && <Actions />}
</div>
@@ -13,19 +13,32 @@
* 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';
import { ContextProvider } from './Context';
describe('<ToolkitContent>', () => {
const tools = [
{ label: 'tool', url: '/url', icon: <div>icon</div> },
{ label: 'tool 2', url: '/url-2', icon: <div>icon 2</div> },
];
test('should render list of tools', async () => {
const { getByText } = await renderInTestApp(<Content tools={tools} />);
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');
});
test('should render list of tools using context', 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> },
]}
/>,
<ContextProvider tools={tools}>
<Content tools={[]} />
</ContextProvider>,
);
expect(getByText('tool')).toBeInTheDocument();
@@ -22,6 +22,7 @@ import {
ListItemText,
} from '@material-ui/core';
import React from 'react';
import { useToolkit, Tool } from './Context';
const useStyles = makeStyles(theme => ({
toolkit: {
@@ -49,12 +50,6 @@ const useStyles = makeStyles(theme => ({
},
}));
type Tool = {
label: string;
url: string;
icon: React.ReactNode;
};
/**
* Props for Toolkit content component {@link Content}.
*
@@ -71,10 +66,12 @@ export type ToolkitContentProps = {
*/
export const Content = (props: ToolkitContentProps) => {
const classes = useStyles();
const toolkit = useToolkit();
const tools = toolkit?.tools ?? props.tools;
return (
<List className={classes.toolkit}>
{props.tools.map((tool: Tool) => (
{tools.map((tool: Tool) => (
<Link key={tool.url} to={tool.url} className={classes.tool}>
<ListItemIcon className={classes.icon}>{tool.icon}</ListItemIcon>
<ListItemText
@@ -0,0 +1,52 @@
/*
* Copyright 2022 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, { createContext } from 'react';
export type Tool = {
label: string;
url: string;
icon: React.ReactNode;
};
type ToolkitContextValue = {
tools: Tool[];
};
const Context = createContext<ToolkitContextValue | undefined>(undefined);
export const ContextProvider = ({
children,
tools,
}: {
children: JSX.Element;
tools: Tool[];
}) => {
const [toolsValue, _setTools] = React.useState(tools);
const value: ToolkitContextValue = {
tools: toolsValue,
};
return <Context.Provider value={value}>{children}</Context.Provider>;
};
export const useToolkit = () => {
const value = React.useContext(Context);
return value;
};
export default Context;
@@ -14,11 +14,13 @@
* limitations under the License.
*/
import { TemplateBackstageLogoIcon } from '../../templates';
import { HomePageToolkit } from '../../plugin';
import { InfoCard } from '@backstage/core-components';
import { wrapInTestApp } from '@backstage/test-utils';
import { Grid } from '@material-ui/core';
import React, { ComponentType } from 'react';
import { ComponentAccordion } from '../../componentRenderers';
import { HomePageToolkit } from '../../plugin';
import { TemplateBackstageLogoIcon } from '../../templates';
export default {
title: 'Plugins/Home/Components/Toolkit',
@@ -38,3 +40,43 @@ export const Default = () => {
</Grid>
);
};
export const InAccordian = () => {
const ExpandedComponentAccordion = (props: any) => (
<ComponentAccordion expanded {...props} />
);
return (
<InfoCard title="Toolkit" noPadding>
<Grid item>
<HomePageToolkit
title="Tools 1"
tools={Array(8).fill({
url: '#',
label: 'link',
icon: <TemplateBackstageLogoIcon />,
})}
Renderer={ExpandedComponentAccordion}
/>
<HomePageToolkit
title="Tools 2"
tools={Array(8).fill({
url: '#',
label: 'link',
icon: <TemplateBackstageLogoIcon />,
})}
Renderer={ComponentAccordion}
/>
<HomePageToolkit
title="Tools 3"
tools={Array(8).fill({
url: '#',
label: 'link',
icon: <TemplateBackstageLogoIcon />,
})}
Renderer={ComponentAccordion}
/>
</Grid>
</InfoCard>
);
};
@@ -16,3 +16,4 @@
export { Content } from './Content';
export type { ToolkitContentProps } from './Content';
export { ContextProvider } from './Context';