Tooltip: VizTooltip components (#75794)

This commit is contained in:
Adela Almasan 2023-10-02 22:10:22 -05:00 committed by GitHub
parent a7cedee7eb
commit 0c404a4cd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 346 additions and 0 deletions

View File

@ -0,0 +1,50 @@
import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { HorizontalGroup, Tooltip } from '..';
import { useStyles2 } from '../../themes';
import { LabelValue } from './types';
interface Props {
headerLabel: LabelValue;
}
export const HeaderLabel = ({ headerLabel }: Props) => {
const styles = useStyles2(getStyles);
return (
<HorizontalGroup justify-content="space-between" spacing="lg" wrap>
<div className={styles.wrapper}>
<span className={styles.label}>{headerLabel.label}</span>
<Tooltip content={headerLabel.value ? headerLabel.value.toString() : ''}>
<span className={styles.value}>{headerLabel.value}</span>
</Tooltip>
</div>
</HorizontalGroup>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
label: css({
color: theme.colors.text.secondary,
paddingRight: theme.spacing(0.5),
fontWeight: 400,
}),
value: css({
fontWeight: 500,
lineHeight: '18px',
alignSelf: 'center',
}),
wrapper: css({
display: 'flex',
flexDirection: 'row',
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
width: '250px',
maskImage: 'linear-gradient(90deg, rgba(0, 0, 0, 1) 80%, transparent)',
}),
});

View File

@ -0,0 +1,54 @@
import { css } from '@emotion/css';
import React, { ReactElement } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { HorizontalGroup } from '..';
import { useStyles2 } from '../../themes';
import { LabelValue } from './types';
interface Props {
contentLabelValue: LabelValue[];
customContent?: ReactElement | null;
}
export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) => {
const styles = useStyles2(getStyles);
return (
<div className={styles.wrapper}>
<div>
{contentLabelValue?.map((labelValue, i) => {
return (
<HorizontalGroup justify="space-between" spacing="lg" key={i}>
<div className={styles.label}>{labelValue.label}</div>
<div className={styles.value}>{labelValue.value}</div>
</HorizontalGroup>
);
})}
</div>
{customContent && <div className={styles.customContentPadding}>{customContent}</div>}
</div>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
wrapper: css({
display: 'flex',
flexDirection: 'column',
flex: 1,
gap: 4,
borderTop: `1px solid ${theme.colors.border.medium}`,
padding: `${theme.spacing(1)} 0`,
}),
customContentPadding: css({
padding: `${theme.spacing(1)} 0`,
}),
label: css({
color: theme.colors.text.secondary,
fontWeight: 400,
}),
value: css({
fontWeight: 500,
}),
});

View File

@ -0,0 +1,65 @@
import { css } from '@emotion/css';
import React from 'react';
import { Field, GrafanaTheme2, LinkModel } from '@grafana/data';
import { Button, ButtonProps, DataLinkButton, HorizontalGroup } from '..';
import { useStyles2 } from '../../themes';
interface Props {
dataLinks: Array<LinkModel<Field>>;
canAnnotate: boolean;
}
export const ADD_ANNOTATION_ID = 'add-annotation-button';
export const VizTooltipFooter = ({ dataLinks, canAnnotate }: Props) => {
const styles = useStyles2(getStyles);
const renderDataLinks = () => {
const buttonProps: ButtonProps = {
variant: 'secondary',
};
return (
<HorizontalGroup>
{dataLinks.map((link, i) => (
<DataLinkButton key={i} link={link} buttonProps={buttonProps} />
))}
</HorizontalGroup>
);
};
return (
<div className={styles.wrapper}>
{dataLinks.length > 0 && <div className={styles.dataLinks}>{renderDataLinks()}</div>}
{canAnnotate && (
<div className={styles.addAnnotations}>
<Button icon="comment-alt" variant="secondary" size="sm" id={ADD_ANNOTATION_ID}>
Add annotation
</Button>
</div>
)}
</div>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
wrapper: css({
display: 'flex',
flexDirection: 'column',
flex: 1,
}),
dataLinks: css({
height: 40,
overflowX: 'auto',
overflowY: 'hidden',
whiteSpace: 'nowrap',
borderTop: `1px solid ${theme.colors.border.medium}`,
maskImage: 'linear-gradient(90deg, rgba(0, 0, 0, 1) 80%, transparent)',
}),
addAnnotations: css({
borderTop: `1px solid ${theme.colors.border.medium}`,
paddingTop: theme.spacing(1),
}),
});

View File

@ -0,0 +1,42 @@
import { css } from '@emotion/css';
import React, { ReactElement } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { HeaderLabel } from './HeaderLabel';
import { VizTooltipHeaderLabelValue } from './VizTooltipHeaderLabelValue';
import { LabelValue } from './types';
interface Props {
headerLabel: LabelValue;
keyValuePairs?: LabelValue[];
customValueDisplay?: ReactElement | null;
}
export const VizTooltipHeader = ({ headerLabel, keyValuePairs, customValueDisplay }: Props) => {
const styles = useStyles2(getStyles);
const renderValue = () => {
if (customValueDisplay) {
return customValueDisplay;
}
return <VizTooltipHeaderLabelValue keyValuePairs={keyValuePairs} />;
};
return (
<div className={styles.wrapper}>
<HeaderLabel headerLabel={headerLabel} />
{renderValue()}
</div>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
wrapper: css({
display: 'flex',
flexDirection: 'column',
flex: 1,
paddingBottom: theme.spacing(1),
}),
});

View File

@ -0,0 +1,83 @@
import { css, cx } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { HorizontalGroup } from '..';
import { useStyles2 } from '../../themes';
import { LabelValue } from './types';
import { getColorIndicatorClass } from './utils';
interface Props {
keyValuePairs?: LabelValue[];
}
export type HeaderLabelValueStyles = ReturnType<typeof getStyles>;
export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => {
const styles = useStyles2(getStyles);
return (
<>
{keyValuePairs?.map((keyValuePair, i) => {
return (
<HorizontalGroup justify="space-between" spacing="md" className={styles.hgContainer} key={i}>
<div className={styles.label}>{keyValuePair.label}</div>
<>
<span
style={{ backgroundColor: keyValuePair.color }}
className={cx(styles.colorIndicator, getColorIndicatorClass(keyValuePair.colorIndicator!, styles))}
/>
{keyValuePair.value}
</>
</HorizontalGroup>
);
})}
</>
);
};
// @TODO Update classes/add svgs?
const getStyles = (theme: GrafanaTheme2) => ({
hgContainer: css({
flexGrow: 1,
}),
colorIndicator: css({
marginRight: theme.spacing(0.5),
}),
label: css({
color: theme.colors.text.secondary,
fontWeight: 400,
}),
series: css({
width: '14px',
height: '4px',
borderRadius: theme.shape.radius.pill,
}),
value: css({
width: '12px',
height: '12px',
borderRadius: theme.shape.radius.default,
fontWeight: 500,
}),
hexagon: css({}),
pie_1_4: css({}),
pie_2_4: css({}),
pie_3_4: css({}),
marker_sm: css({
width: '4px',
height: '4px',
borderRadius: theme.shape.radius.circle,
}),
marker_md: css({
width: '8px',
height: '8px',
borderRadius: theme.shape.radius.circle,
}),
marker_lg: css({
width: '12px',
height: '12px',
borderRadius: theme.shape.radius.circle,
}),
});

View File

@ -0,0 +1,24 @@
export enum ColorIndicator {
series = 'series',
value = 'value',
hexagon = 'hexagon',
pie_1_4 = 'pie_1_4',
pie_2_4 = 'pie_2_4',
pie_3_4 = 'pie_3_4',
marker_sm = 'marker_sm',
marker_md = 'marker_md',
marker_lg = 'marker_lg',
}
export enum LabelValuePlacement {
hidden = 'hidden',
leading = 'leading',
trailing = 'trailing',
}
export interface LabelValue {
label: string;
value: string | number | null;
color?: string;
colorIndicator?: ColorIndicator;
}

View File

@ -1,3 +1,6 @@
import { HeaderLabelValueStyles } from './VizTooltipHeaderLabelValue';
import { ColorIndicator } from './types';
export const calculateTooltipPosition = (
xPos = 0,
yPos = 0,
@ -38,3 +41,28 @@ export const calculateTooltipPosition = (
}
return { x, y };
};
export const getColorIndicatorClass = (colorIndicator: string, styles: HeaderLabelValueStyles) => {
switch (colorIndicator) {
case ColorIndicator.value:
return styles.value;
case ColorIndicator.series:
return styles.series;
case ColorIndicator.hexagon:
return styles.hexagon;
case ColorIndicator.pie_1_4:
return styles.pie_1_4;
case ColorIndicator.pie_2_4:
return styles.pie_2_4;
case ColorIndicator.pie_3_4:
return styles.pie_3_4;
case ColorIndicator.marker_sm:
return styles.marker_sm;
case ColorIndicator.marker_md:
return styles.marker_md;
case ColorIndicator.marker_lg:
return styles.marker_lg;
default:
return styles.value;
}
};