Merge pull request #4580 from johnnyiller/techradar/issue-4493-description-popup

tech-radar: add description dialog box
This commit is contained in:
Fredrik Adelöw
2021-02-19 16:45:07 +01:00
committed by GitHub
10 changed files with 271 additions and 11 deletions
@@ -68,6 +68,7 @@ const RadarComponent = (props: TechRadarComponentProps): JSX.Element => {
};
}),
moved: entry.timeline[0].moved,
description: entry.timeline[0].description,
url: entry.url,
};
});
@@ -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 React from 'react';
import { render, screen } from '@testing-library/react';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { Props, RadarDescription } from './RadarDescription';
const minProps: Props = {
open: true,
title: 'example-title',
description: 'example-description',
onClose: () => {},
};
describe('RadarDescription', () => {
it('should render', () => {
render(
<ThemeProvider theme={lightTheme}>
<RadarDescription {...minProps} />
</ThemeProvider>,
);
const radarDescription = screen.getByTestId('radar-description');
expect(radarDescription).toBeInTheDocument();
expect(screen.getByText(String(minProps.description))).toBeInTheDocument();
});
});
@@ -0,0 +1,63 @@
/*
* 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 from 'react';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Button, DialogActions, DialogContent } from '@material-ui/core';
import LinkIcon from '@material-ui/icons/Link';
export type Props = {
open: boolean;
onClose: () => void;
title: string;
description: string;
url?: string;
};
const RadarDescription = (props: Props): JSX.Element => {
const { open, onClose, title, description, url } = props;
const handleClick = () => {
onClose();
if (url) {
window.location.href = url;
}
};
return (
<Dialog data-testid="radar-description" open={open} onClose={onClose}>
<DialogTitle data-testid="radar-description-dialog-title">
{title}
</DialogTitle>
<DialogContent dividers>{description}</DialogContent>
{url && (
<DialogActions>
<Button
onClick={handleClick}
color="primary"
startIcon={<LinkIcon />}
href={url}
>
LEARN MORE
</Button>
</DialogActions>
)}
</Dialog>
);
};
export { RadarDescription };
@@ -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 { RadarDescription } from './RadarDescription';
export type { Props } from './RadarDescription';
@@ -15,7 +15,8 @@
*/
import React from 'react';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import GetBBoxPolyfill from '../../utils/polyfills/getBBox';
@@ -29,6 +30,12 @@ const minProps: Props = {
color: 'red',
};
const optionalProps: Props = {
...minProps,
title: 'example-title',
description: 'example-description',
};
describe('RadarEntry', () => {
beforeAll(() => {
GetBBoxPolyfill.create(0, 0, 1000, 500);
@@ -38,8 +45,8 @@ describe('RadarEntry', () => {
GetBBoxPolyfill.remove();
});
it('should render', () => {
const rendered = render(
it('should render link only', () => {
render(
<ThemeProvider theme={lightTheme}>
<svg>
<RadarEntry {...minProps} />
@@ -47,10 +54,29 @@ describe('RadarEntry', () => {
</ThemeProvider>,
);
const radarEntry = rendered.getByTestId('radar-entry');
const radarEntry = screen.getByTestId('radar-entry');
const { x, y } = minProps;
expect(radarEntry).toBeInTheDocument();
expect(radarEntry.getAttribute('transform')).toBe(`translate(${x}, ${y})`);
expect(rendered.getByText(String(minProps.value))).toBeInTheDocument();
expect(screen.getByText(String(minProps.value))).toBeInTheDocument();
});
it('should render with description', () => {
render(
<ThemeProvider theme={lightTheme}>
<svg>
<RadarEntry {...optionalProps} />
</svg>
</ThemeProvider>,
);
userEvent.click(screen.getByRole('button'));
const radarEntry = screen.getByTestId('radar-entry');
expect(radarEntry).toBeInTheDocument();
const radarDescription = screen.getByTestId('radar-description');
expect(radarDescription).toBeInTheDocument();
expect(screen.getByText(String(minProps.value))).toBeInTheDocument();
});
});
@@ -17,6 +17,7 @@
import React from 'react';
import { makeStyles, Theme } from '@material-ui/core';
import { WithLink } from '../../utils/components';
import { RadarDescription } from '../RadarDescription';
export type Props = {
x: number;
@@ -25,6 +26,8 @@ export type Props = {
color: string;
url?: string;
moved?: number;
description?: string;
title?: string;
onMouseEnter?: (event: React.MouseEvent<SVGGElement, MouseEvent>) => void;
onMouseLeave?: (event: React.MouseEvent<SVGGElement, MouseEvent>) => void;
onClick?: (event: React.MouseEvent<SVGGElement, MouseEvent>) => void;
@@ -59,9 +62,12 @@ const makeBlip = (color: string, moved?: number) => {
const RadarEntry = (props: Props): JSX.Element => {
const classes = useStyles(props);
const [open, setOpen] = React.useState(false);
const {
moved,
description,
title,
color,
url,
value,
@@ -74,6 +80,18 @@ const RadarEntry = (props: Props): JSX.Element => {
const blip = makeBlip(color, moved);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const toggle = () => {
setOpen(!open);
};
return (
<g
transform={`translate(${x}, ${y})`}
@@ -82,9 +100,32 @@ const RadarEntry = (props: Props): JSX.Element => {
onClick={onClick}
data-testid="radar-entry"
>
<WithLink url={url} className={classes.link}>
{blip}
</WithLink>
{' '}
{open && (
<RadarDescription
open={open}
onClose={handleClose}
title={title ? title : 'no title'}
description={description ? description : 'no description'}
url={url}
/>
)}
{description ? (
<a
className={classes.link}
onClick={handleClickOpen}
href={url ? url : '#'}
role="button"
tabIndex={0}
onKeyPress={toggle}
>
{blip}
</a>
) : (
<WithLink url={url} className={classes.link}>
{blip}
</WithLink>
)}
<text y={3} className={classes.text}>
{value}
</text>
@@ -17,6 +17,7 @@ import React from 'react';
import { makeStyles, Theme } from '@material-ui/core';
import type { Quadrant, Ring, Entry } from '../../utils/types';
import { WithLink } from '../../utils/components';
import { RadarDescription } from '../RadarDescription';
type Segments = {
[k: number]: { [k: number]: Entry[] };
@@ -105,6 +106,62 @@ const RadarLegend = (props: Props): JSX.Element => {
onEntryMouseLeave?: Props['onEntryMouseEnter'];
};
type RadarLegendLinkProps = {
url?: string;
description?: string;
title?: string;
};
const RadarLegendLink = ({
url,
description,
title,
}: RadarLegendLinkProps) => {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const toggle = () => {
setOpen(!open);
};
if (description) {
return (
<>
<span
className={classes.entryLink}
onClick={handleClickOpen}
role="button"
tabIndex={0}
onKeyPress={toggle}
>
<span className={classes.entry}>{title}</span>
</span>
{open && (
<RadarDescription
open={open}
onClose={handleClose}
title={title ? title : 'no title'}
url={url}
description={description}
/>
)}
</>
);
}
return (
<WithLink url={url} className={classes.entryLink}>
<span className={classes.entry}>{title}</span>
</WithLink>
);
};
const RadarLegendRing = ({
ring,
entries,
@@ -129,9 +186,11 @@ const RadarLegend = (props: Props): JSX.Element => {
onEntryMouseLeave && (() => onEntryMouseLeave(entry))
}
>
<WithLink url={entry.url} className={classes.entryLink}>
<span className={classes.entry}>{entry.title}</span>
</WithLink>
<RadarLegendLink
url={entry.url}
title={entry.title}
description={entry.description}
/>
</li>
))}
</ol>
@@ -73,7 +73,9 @@ const RadarPlot = (props: Props): JSX.Element => {
color={entry.color || ''}
value={(entry?.index || 0) + 1}
url={entry.url}
description={entry.description}
moved={entry.moved}
title={entry.title}
onMouseEnter={onEntryMouseEnter && (() => onEntryMouseEnter(entry))}
onMouseLeave={onEntryMouseLeave && (() => onEntryMouseLeave(entry))}
/>
+2
View File
@@ -68,6 +68,8 @@ export type Entry = {
url?: string;
// How this entry has recently moved; -1 for "down", +1 for "up", 0 for not moved
moved?: MovedState;
// Most recent description to display in the UI
description?: string;
active?: boolean;
timeline?: Array<EntrySnapshot>;
};