diff --git a/.changeset/long-dragons-kneel.md b/.changeset/long-dragons-kneel.md new file mode 100644 index 0000000000..9380d95b55 --- /dev/null +++ b/.changeset/long-dragons-kneel.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-home': patch +--- + +Add Renderer support for the HomePageToolkit component. + +Previously `` 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. diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 264189ee07..6fae50151c 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -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; diff --git a/plugins/home/src/componentRenderers/ComponentAccordion.tsx b/plugins/home/src/componentRenderers/ComponentAccordion.tsx index 5cf54d7a0c..ef44f7f292 100644 --- a/plugins/home/src/componentRenderers/ComponentAccordion.tsx +++ b/plugins/home/src/componentRenderers/ComponentAccordion.tsx @@ -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 = ({ )} setIsExpanded(expanded)} + onChange={(_e: any, expandedValue: boolean) => + setIsExpanded(expandedValue) + } > }> {Settings && ( @@ -85,7 +92,7 @@ export const ComponentAccordion = ({ {title} -
+
{Actions && }
diff --git a/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx b/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx index 7fbaff08f9..1955019a4a 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx @@ -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('', () => { + const tools = [ + { label: 'tool', url: '/url', icon:
icon
}, + { label: 'tool 2', url: '/url-2', icon:
icon 2
}, + ]; + test('should render list of tools', async () => { + const { getByText } = await renderInTestApp(); + + 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( - icon
}, - { label: 'tool 2', url: '/url-2', icon:
icon 2
}, - ]} - />, + + + , ); expect(getByText('tool')).toBeInTheDocument(); diff --git a/plugins/home/src/homePageComponents/Toolkit/Content.tsx b/plugins/home/src/homePageComponents/Toolkit/Content.tsx index 4ee1884bd0..21affc57f1 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Content.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Content.tsx @@ -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 ( - {props.tools.map((tool: Tool) => ( + {tools.map((tool: Tool) => ( {tool.icon} (undefined); + +export const ContextProvider = ({ + children, + tools, +}: { + children: JSX.Element; + tools: Tool[]; +}) => { + const [toolsValue, _setTools] = React.useState(tools); + + const value: ToolkitContextValue = { + tools: toolsValue, + }; + + return {children}; +}; + +export const useToolkit = () => { + const value = React.useContext(Context); + return value; +}; + +export default Context; diff --git a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx index 0a8e6fffbe..cad188c5d6 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx @@ -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 = () => { ); }; + +export const InAccordian = () => { + const ExpandedComponentAccordion = (props: any) => ( + + ); + + return ( + + + , + })} + Renderer={ExpandedComponentAccordion} + /> + , + })} + Renderer={ComponentAccordion} + /> + , + })} + Renderer={ComponentAccordion} + /> + + + ); +}; diff --git a/plugins/home/src/homePageComponents/Toolkit/index.ts b/plugins/home/src/homePageComponents/Toolkit/index.ts index 98e0869825..67ea1f3c84 100644 --- a/plugins/home/src/homePageComponents/Toolkit/index.ts +++ b/plugins/home/src/homePageComponents/Toolkit/index.ts @@ -16,3 +16,4 @@ export { Content } from './Content'; export type { ToolkitContentProps } from './Content'; +export { ContextProvider } from './Context';