diff --git a/.changeset/swift-carpets-yawn.md b/.changeset/swift-carpets-yawn.md new file mode 100644 index 0000000000..d242b3cf81 --- /dev/null +++ b/.changeset/swift-carpets-yawn.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Adding hover message to the Gauge and an info icon to the GaugeCard. diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 47ca3d3cf8..6365b4f9d7 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -384,7 +384,12 @@ export function GaugeCard(props: Props_10): JSX.Element; export type GaugeCardClassKey = 'root'; // @public (undocumented) -export type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown'; +export type GaugeClassKey = + | 'root' + | 'overlay' + | 'description' + | 'circle' + | 'colorUnknown'; // @public (undocumented) export type GaugeProps = { @@ -393,6 +398,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + description?: ReactNode; getColor?: GaugePropsGetColor; }; diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 0336ebc990..11f977b387 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -17,10 +17,15 @@ import { BackstagePalette, BackstageTheme } from '@backstage/theme'; import { makeStyles, useTheme } from '@material-ui/core/styles'; import { Circle } from 'rc-progress'; -import React from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; /** @public */ -export type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown'; +export type GaugeClassKey = + | 'root' + | 'overlay' + | 'description' + | 'circle' + | 'colorUnknown'; const useStyles = makeStyles( theme => ({ @@ -37,6 +42,15 @@ const useStyles = makeStyles( fontWeight: 'bold', color: theme.palette.textContrast, }, + description: { + fontSize: '100%', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + position: 'absolute', + wordBreak: 'break-all', + display: 'inline-block', + }, circle: { width: '80%', transform: 'translate(10%, 0)', @@ -53,6 +67,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + description?: ReactNode; getColor?: GaugePropsGetColor; }; @@ -104,10 +119,11 @@ export const getProgressColor: GaugePropsGetColor = ({ */ export function Gauge(props: GaugeProps) { + const [hoverRef, setHoverRef] = useState(null); const { getColor = getProgressColor } = props; const classes = useStyles(props); const { palette } = useTheme(); - const { value, fractional, inverse, unit, max } = { + const { value, fractional, inverse, unit, max, description } = { ...defaultGaugeProps, ...props, }; @@ -115,8 +131,28 @@ export function Gauge(props: GaugeProps) { const asPercentage = fractional ? Math.round(value * max) : value; const asActual = max !== 100 ? Math.round(value) : asPercentage; + const [isHovering, setIsHovering] = useState(false); + + useEffect(() => { + const node = hoverRef; + const handleMouseOver = () => setIsHovering(true); + const handleMouseOut = () => setIsHovering(false); + if (node && description) { + node.addEventListener('mouseenter', handleMouseOver); + node.addEventListener('mouseleave', handleMouseOut); + + return () => { + node.removeEventListener('mouseenter', handleMouseOver); + node.removeEventListener('mouseleave', handleMouseOut); + }; + } + return () => { + setIsHovering(false); + }; + }, [description, hoverRef]); + return ( -
+
-
- {isNaN(value) ? 'N/A' : `${asActual}${unit}`} -
+ {description && isHovering ? ( +
{description}
+ ) : ( +
+ {isNaN(value) ? 'N/A' : `${asActual}${unit}`} +
+ )}
); } diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 64a6cc1b16..08c7813643 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -18,6 +18,8 @@ import React, { PropsWithChildren } from 'react'; import { GaugeCard } from './GaugeCard'; import Grid from '@material-ui/core/Grid'; import { MemoryRouter } from 'react-router'; +import Tooltip from '@material-ui/core/Tooltip'; +import Info from '@material-ui/icons/Info'; const linkInfo = { title: 'Go to XYZ Location', link: '#' }; @@ -118,3 +120,79 @@ export const StaticColor = () => ( ); + +export const InfoMessage = () => ( + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + +); + +export const HoverMessage = () => ( + + + + + + + + + + + + + + +); diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 9e6c19ebfe..1114890523 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -15,7 +15,7 @@ */ import { makeStyles } from '@material-ui/core/styles'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { BottomLinkProps } from '../../layout/BottomLink'; import { InfoCard, InfoCardVariants } from '../../layout/InfoCard'; import { Gauge, GaugePropsGetColor } from './Gauge'; @@ -26,6 +26,8 @@ type Props = { variant?: InfoCardVariants; /** Progress in % specified as decimal, e.g. "0.23" */ progress: number; + description?: ReactNode; + icon?: ReactNode; inverse?: boolean; deepLink?: BottomLinkProps; getColor?: GaugePropsGetColor; @@ -52,11 +54,21 @@ const useStyles = makeStyles( */ export function GaugeCard(props: Props) { const classes = useStyles(props); - const { title, subheader, progress, inverse, deepLink, variant, getColor } = - props; + const { + title, + subheader, + progress, + inverse, + deepLink, + description, + icon, + variant, + getColor, + } = props; const gaugeProps = { inverse, + description, getColor, value: progress, }; @@ -68,6 +80,7 @@ export function GaugeCard(props: Props) { subheader={subheader} deepLink={deepLink} variant={variant} + icon={icon} > diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index f1e67904f1..256d60cfa2 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -55,6 +55,9 @@ const useStyles = makeStyles( headerAvatar: {}, headerAction: {}, headerContent: {}, + subheader: { + display: 'flex', + }, }), { name: 'BackstageInfoCard' }, ); @@ -134,6 +137,7 @@ type Props = { children?: ReactNode; headerStyle?: object; headerProps?: CardHeaderProps; + icon?: ReactNode; action?: ReactNode; actionsClassName?: string; actions?: ReactNode; @@ -162,6 +166,7 @@ export function InfoCard(props: Props): JSX.Element { children, headerStyle, headerProps, + icon, action, actionsClassName, actions, @@ -194,6 +199,15 @@ export function InfoCard(props: Props): JSX.Element { }); } + const cardSubTitle = () => { + return ( +
+ {subheader &&
{subheader}
} + {icon} +
+ ); + }; + const errProps: ErrorBoundaryProps = errorBoundaryProps || (slackChannel ? { slackChannel } : {}); @@ -211,7 +225,7 @@ export function InfoCard(props: Props): JSX.Element { content: classes.headerContent, }} title={title} - subheader={subheader} + subheader={cardSubTitle()} action={action} style={{ ...headerStyle }} titleTypographyProps={titleTypographyProps} diff --git a/plugins/git-release-manager/src/features/Features.test.tsx b/plugins/git-release-manager/src/features/Features.test.tsx index 28bd9b6310..8f9e3544f2 100644 --- a/plugins/git-release-manager/src/features/Features.test.tsx +++ b/plugins/git-release-manager/src/features/Features.test.tsx @@ -55,7 +55,7 @@ describe('Features', () => { expect(getByTestId(TEST_IDS.info.info)).toMatchInlineSnapshot(`