Skip to content

Commit

Permalink
Adding hover message and an info icon.
Browse files Browse the repository at this point in the history
Signed-off-by: elstranack <elstranack@homeaway.com>
  • Loading branch information
elstranack authored and freben committed Jan 19, 2022
1 parent 2e903d9 commit 064e750
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .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.
28 changes: 22 additions & 6 deletions packages/core-components/src/components/ProgressBars/Gauge.tsx
Expand Up @@ -17,7 +17,8 @@
import { BackstagePalette, BackstageTheme } from '@backstage/theme';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import { Circle } from 'rc-progress';
import React from 'react';
import { useHover } from '../../hooks';
import React, { ReactNode } from 'react';

/** @public */
export type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown';
Expand All @@ -37,6 +38,15 @@ const useStyles = makeStyles<BackstageTheme>(
fontWeight: 'bold',
color: theme.palette.textContrast,
},
hoveringMessageCompliant: {
fontSize: 13,
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
position: 'absolute',
wordBreak: 'break-all',
display: 'inline-block',
},
circle: {
width: '80%',
transform: 'translate(10%, 0)',
Expand All @@ -53,6 +63,7 @@ export type GaugeProps = {
inverse?: boolean;
unit?: string;
max?: number;
hoverMessage?: ReactNode;
getColor?: GaugePropsGetColor;
};

Expand Down Expand Up @@ -104,10 +115,11 @@ export const getProgressColor: GaugePropsGetColor = ({
*/

export function Gauge(props: GaugeProps) {
const [hoverRef, isHovering] = useHover() as any;
const { getColor = getProgressColor } = props;
const classes = useStyles(props);
const { palette } = useTheme<BackstageTheme>();
const { value, fractional, inverse, unit, max } = {
const { value, fractional, inverse, unit, max, hoverMessage } = {
...defaultGaugeProps,
...props,
};
Expand All @@ -116,7 +128,7 @@ export function Gauge(props: GaugeProps) {
const asActual = max !== 100 ? Math.round(value) : asPercentage;

return (
<div className={classes.root}>
<div ref={hoverRef} className={classes.root}>
<Circle
strokeLinecap="butt"
percent={asPercentage}
Expand All @@ -125,9 +137,13 @@ export function Gauge(props: GaugeProps) {
strokeColor={getColor({ palette, value: asActual, inverse, max })}
className={classes.circle}
/>
<div className={classes.overlay}>
{isNaN(value) ? 'N/A' : `${asActual}${unit}`}
</div>
{hoverMessage && isHovering ? (
<div className={classes.hoveringMessageCompliant}>{hoverMessage}</div>
) : (
<div className={classes.overlay}>
{isNaN(value) ? 'N/A' : `${asActual}${unit}`}
</div>
)}
</div>
);
}
Expand Up @@ -118,3 +118,71 @@ export const StaticColor = () => (
</Grid>
</Wrapper>
);

export const InfoMessage = () => (
<Wrapper>
<Grid item>
<GaugeCard
title="Progress"
subheader="With a subheader"
progress={0.3}
iconInfoMessage="Info Message"
/>
</Grid>
<Grid item>
<GaugeCard
title="Progress"
subheader="With a subheader"
progress={0.57}
iconInfoMessage="Info Message"
/>
</Grid>
<Grid item>
<GaugeCard
title="Progress"
subheader="With a subheader"
progress={0.89}
iconInfoMessage="Info Message"
/>
</Grid>
<Grid item>
<GaugeCard
title="Progress"
subheader="With a subheader"
inverse
progress={0.2}
iconInfoMessage="Info Message"
/>
</Grid>
</Wrapper>
);

export const HoverMessage = () => (
<Wrapper>
<Grid item>
<GaugeCard title="Progress" progress={0.3} hoverMessage="Hover Message" />
</Grid>
<Grid item>
<GaugeCard
title="Progress"
progress={0.57}
hoverMessage="Hover Message"
/>
</Grid>
<Grid item>
<GaugeCard
title="Progress"
progress={0.89}
hoverMessage="Hover Message"
/>
</Grid>
<Grid item>
<GaugeCard
title="Progress"
inverse
progress={0.2}
hoverMessage="Hover Message"
/>
</Grid>
</Wrapper>
);
19 changes: 16 additions & 3 deletions packages/core-components/src/components/ProgressBars/GaugeCard.tsx
Expand Up @@ -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';
Expand All @@ -26,6 +26,8 @@ type Props = {
variant?: InfoCardVariants;
/** Progress in % specified as decimal, e.g. "0.23" */
progress: number;
hoverMessage?: ReactNode;
iconInfoMessage?: string;
inverse?: boolean;
deepLink?: BottomLinkProps;
getColor?: GaugePropsGetColor;
Expand All @@ -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,
hoverMessage,
iconInfoMessage,
variant,
getColor,
} = props;

const gaugeProps = {
inverse,
hoverMessage,
getColor,
value: progress,
};
Expand All @@ -68,6 +80,7 @@ export function GaugeCard(props: Props) {
subheader={subheader}
deepLink={deepLink}
variant={variant}
iconInfoMessage={iconInfoMessage}
>
<Gauge {...gaugeProps} />
</InfoCard>
Expand Down
1 change: 1 addition & 0 deletions packages/core-components/src/hooks/index.ts
Expand Up @@ -16,6 +16,7 @@

export { useQueryParamState } from './useQueryParamState';
export { useSupportConfig } from './useSupportConfig';
export { useHover } from './useHover';
export type {
SupportConfig,
SupportItem,
Expand Down
42 changes: 42 additions & 0 deletions packages/core-components/src/hooks/useHover.ts
@@ -0,0 +1,42 @@
/*
* 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 { useEffect, useRef, useState } from 'react';

export const useHover = () => {
const [value, setValue] = useState(false);

const ref = useRef(null);
const handleMouseOver = () => setValue(true);
const handleMouseOut = () => setValue(false);

useEffect(() => {
const node = ref.current as any;
if (node) {
node.addEventListener('mouseenter', handleMouseOver) as any;
node.addEventListener('mouseleave', handleMouseOut) as any;

return () => {
node.removeEventListener('mouseenter', handleMouseOver);
node.removeEventListener('mouseleave', handleMouseOut);
};
}
return () => {
setValue(false);
};
}, [ref]);

return [ref, value];
};
32 changes: 31 additions & 1 deletion packages/core-components/src/layout/InfoCard/InfoCard.tsx
Expand Up @@ -24,6 +24,8 @@ import classNames from 'classnames';
import React, { ReactNode } from 'react';
import { BottomLink, BottomLinkProps } from '../BottomLink';
import { ErrorBoundary, ErrorBoundaryProps } from '../ErrorBoundary';
import Info from '@material-ui/icons/Info';
import Tooltip from '@material-ui/core/Tooltip';

/** @public */
export type InfoCardClassKey =
Expand Down Expand Up @@ -55,6 +57,15 @@ const useStyles = makeStyles(
headerAvatar: {},
headerAction: {},
headerContent: {},
leftIcon: {
float: 'right',
},
tooltip: {
fontSize: 14,
},
subheader: {
float: 'left',
},
}),
{ name: 'BackstageInfoCard' },
);
Expand Down Expand Up @@ -134,6 +145,7 @@ type Props = {
children?: ReactNode;
headerStyle?: object;
headerProps?: CardHeaderProps;
iconInfoMessage?: ReactNode;
action?: ReactNode;
actionsClassName?: string;
actions?: ReactNode;
Expand Down Expand Up @@ -162,6 +174,7 @@ export function InfoCard(props: Props): JSX.Element {
children,
headerStyle,
headerProps,
iconInfoMessage,
action,
actionsClassName,
actions,
Expand Down Expand Up @@ -194,6 +207,23 @@ export function InfoCard(props: Props): JSX.Element {
});
}

const cardSubTitle = () => {
return (
<div className={classes.headerSubheader}>
{subheader && <div className={classes.subheader}>{subheader}</div>}
{iconInfoMessage && (
<Tooltip
title={iconInfoMessage}
arrow
classes={{ tooltip: classes.tooltip }}
>
<Info className={classes.leftIcon} />
</Tooltip>
)}
</div>
);
};

const errProps: ErrorBoundaryProps =
errorBoundaryProps || (slackChannel ? { slackChannel } : {});

Expand All @@ -211,7 +241,7 @@ export function InfoCard(props: Props): JSX.Element {
content: classes.headerContent,
}}
title={title}
subheader={subheader}
subheader={cardSubTitle()}
action={action}
style={{ ...headerStyle }}
titleTypographyProps={titleTypographyProps}
Expand Down

0 comments on commit 064e750

Please sign in to comment.