Adding hover message and an info icon.
Signed-off-by: elstranack <elstranack@homeaway.com>
This commit is contained in:
committed by
Fredrik Adelöw
parent
2e903d91af
commit
064e750a50
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Adding hover message to the Gauge and an info icon to the GaugeCard.
|
||||
@@ -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';
|
||||
@@ -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)',
|
||||
@@ -53,6 +63,7 @@ export type GaugeProps = {
|
||||
inverse?: boolean;
|
||||
unit?: string;
|
||||
max?: number;
|
||||
hoverMessage?: ReactNode;
|
||||
getColor?: GaugePropsGetColor;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
hoverMessage?: ReactNode;
|
||||
iconInfoMessage?: string;
|
||||
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,
|
||||
hoverMessage,
|
||||
iconInfoMessage,
|
||||
variant,
|
||||
getColor,
|
||||
} = props;
|
||||
|
||||
const gaugeProps = {
|
||||
inverse,
|
||||
hoverMessage,
|
||||
getColor,
|
||||
value: progress,
|
||||
};
|
||||
@@ -68,6 +80,7 @@ export function GaugeCard(props: Props) {
|
||||
subheader={subheader}
|
||||
deepLink={deepLink}
|
||||
variant={variant}
|
||||
iconInfoMessage={iconInfoMessage}
|
||||
>
|
||||
<Gauge {...gaugeProps} />
|
||||
</InfoCard>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
export { useQueryParamState } from './useQueryParamState';
|
||||
export { useSupportConfig } from './useSupportConfig';
|
||||
export { useHover } from './useHover';
|
||||
export type {
|
||||
SupportConfig,
|
||||
SupportItem,
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
@@ -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 =
|
||||
@@ -55,6 +57,15 @@ const useStyles = makeStyles(
|
||||
headerAvatar: {},
|
||||
headerAction: {},
|
||||
headerContent: {},
|
||||
leftIcon: {
|
||||
float: 'right',
|
||||
},
|
||||
tooltip: {
|
||||
fontSize: 14,
|
||||
},
|
||||
subheader: {
|
||||
float: 'left',
|
||||
},
|
||||
}),
|
||||
{ name: 'BackstageInfoCard' },
|
||||
);
|
||||
@@ -134,6 +145,7 @@ type Props = {
|
||||
children?: ReactNode;
|
||||
headerStyle?: object;
|
||||
headerProps?: CardHeaderProps;
|
||||
iconInfoMessage?: ReactNode;
|
||||
action?: ReactNode;
|
||||
actionsClassName?: string;
|
||||
actions?: ReactNode;
|
||||
@@ -162,6 +174,7 @@ export function InfoCard(props: Props): JSX.Element {
|
||||
children,
|
||||
headerStyle,
|
||||
headerProps,
|
||||
iconInfoMessage,
|
||||
action,
|
||||
actionsClassName,
|
||||
actions,
|
||||
@@ -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 } : {});
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user