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:
@@ -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.
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user