mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tooltips: Support long labels (#77735)
* feat(tooltips): support long labels Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
This commit is contained in:
parent
2659409191
commit
6f0c5395ac
@ -1,95 +1,16 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { HorizontalGroup, Tooltip } from '..';
|
||||
import { useStyles2 } from '../../themes';
|
||||
|
||||
import { VizTooltipRow } from './VizTooltipRow';
|
||||
import { LabelValue } from './types';
|
||||
import { getColorIndicatorClass } from './utils';
|
||||
|
||||
interface Props {
|
||||
headerLabel: LabelValue;
|
||||
}
|
||||
|
||||
export const HeaderLabel = ({ headerLabel }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const { label, value, color, colorIndicator } = headerLabel;
|
||||
|
||||
return (
|
||||
<HorizontalGroup justify-content="space-between" spacing="lg" wrap>
|
||||
<div className={styles.wrapper}>
|
||||
<span className={styles.label}>{label}</span>
|
||||
{color && (
|
||||
<span
|
||||
style={{ backgroundColor: color }}
|
||||
className={cx(styles.colorIndicator, getColorIndicatorClass(colorIndicator!, styles))}
|
||||
/>
|
||||
)}
|
||||
<Tooltip content={value ? value.toString() : ''}>
|
||||
<span className={styles.labelValue}>{value}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</HorizontalGroup>
|
||||
<VizTooltipRow label={label} value={value} color={color} colorIndicator={colorIndicator} marginRight={'22px'} />
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
hgContainer: css({
|
||||
flexGrow: 1,
|
||||
}),
|
||||
colorIndicator: css({
|
||||
marginRight: theme.spacing(0.5),
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
paddingRight: theme.spacing(0.5),
|
||||
fontWeight: 400,
|
||||
}),
|
||||
value: css({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: theme.shape.radius.default,
|
||||
}),
|
||||
series: css({
|
||||
width: '14px',
|
||||
height: '4px',
|
||||
borderRadius: theme.shape.radius.pill,
|
||||
}),
|
||||
labelValue: css({
|
||||
fontWeight: 500,
|
||||
lineHeight: '18px',
|
||||
alignSelf: 'center',
|
||||
}),
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
width: '250px',
|
||||
maskImage: 'linear-gradient(90deg, rgba(0, 0, 0, 1) 80%, transparent)',
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
});
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, GraphSeriesValue } from '@grafana/data';
|
||||
import { GraphSeriesValue } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
|
||||
import { VizTooltipColorIndicator } from './VizTooltipColorIndicator';
|
||||
import { VizTooltipRow } from './VizTooltipRow';
|
||||
import { ColorIndicator } from './types';
|
||||
|
||||
export interface SeriesListProps {
|
||||
@ -18,13 +14,19 @@ export const SeriesList = ({ series }: SeriesListProps) => {
|
||||
return (
|
||||
<>
|
||||
{series.map((series, index) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const label = series.label as string;
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const value = series.value as string;
|
||||
return (
|
||||
<SingleSeries
|
||||
isActive={series.isActive}
|
||||
label={series.label}
|
||||
color={series.color}
|
||||
value={series.value}
|
||||
<VizTooltipRow
|
||||
key={`${series.label}-${index}`}
|
||||
label={label}
|
||||
value={value}
|
||||
color={series.color}
|
||||
colorIndicator={ColorIndicator.series}
|
||||
isActive={series.isActive}
|
||||
justify={'space-between'}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -39,31 +41,3 @@ export interface SingleSeriesProps {
|
||||
isActive?: boolean;
|
||||
colorIndicator?: ColorIndicator;
|
||||
}
|
||||
|
||||
const SingleSeries = ({ label, value, color, colorIndicator = ColorIndicator.series, isActive }: SingleSeriesProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<HorizontalGroup justify="space-between" spacing="md" className={styles.hgContainer}>
|
||||
<>
|
||||
{color && <VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} />}
|
||||
{label && <div className={cx(styles.label, isActive && styles.activeSeries)}>{label}</div>}
|
||||
</>
|
||||
{value && <div className={cx(isActive && styles.activeSeries)}>{value}</div>}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
hgContainer: css({
|
||||
flexGrow: 1,
|
||||
}),
|
||||
activeSeries: css({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
color: theme.colors.text.maxContrast,
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontWeight: 400,
|
||||
}),
|
||||
});
|
||||
|
@ -35,12 +35,14 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
width: '14px',
|
||||
height: '4px',
|
||||
borderRadius: theme.shape.radius.pill,
|
||||
minWidth: '14px',
|
||||
}),
|
||||
value: css({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: theme.shape.radius.default,
|
||||
fontWeight: 500,
|
||||
minWidth: '12px',
|
||||
}),
|
||||
hexagon: css({}),
|
||||
pie_1_4: css({}),
|
||||
@ -50,15 +52,18 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
width: '4px',
|
||||
height: '4px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
minWidth: '4px',
|
||||
}),
|
||||
marker_md: css({
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
minWidth: '8px',
|
||||
}),
|
||||
marker_lg: css({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
minWidth: '12px',
|
||||
}),
|
||||
});
|
||||
|
@ -3,15 +3,16 @@ import React, { ReactElement } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { HorizontalGroup } from '..';
|
||||
import { useStyles2 } from '../../themes';
|
||||
|
||||
import { VizTooltipRow } from './VizTooltipRow';
|
||||
import { LabelValue } from './types';
|
||||
|
||||
interface Props {
|
||||
contentLabelValue: LabelValue[];
|
||||
customContent?: ReactElement | null;
|
||||
}
|
||||
|
||||
export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@ -19,11 +20,17 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
|
||||
<div className={styles.wrapper}>
|
||||
<div>
|
||||
{contentLabelValue?.map((labelValue, i) => {
|
||||
const { label, value, color, colorIndicator } = labelValue;
|
||||
return (
|
||||
<HorizontalGroup justify="space-between" spacing="lg" key={i}>
|
||||
<div className={styles.label}>{labelValue.label}</div>
|
||||
<div className={styles.value}>{labelValue.value}</div>
|
||||
</HorizontalGroup>
|
||||
<VizTooltipRow
|
||||
key={i}
|
||||
label={label}
|
||||
value={value}
|
||||
color={color}
|
||||
colorIndicator={colorIndicator}
|
||||
colorFirst={false}
|
||||
justify={'space-between'}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@ -44,11 +51,4 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
customContentPadding: css({
|
||||
padding: `${theme.spacing(1)} 0`,
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontWeight: 400,
|
||||
}),
|
||||
value: css({
|
||||
fontWeight: 500,
|
||||
}),
|
||||
});
|
||||
|
@ -17,17 +17,10 @@ interface Props {
|
||||
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()}
|
||||
{customValueDisplay || <VizTooltipHeaderLabelValue keyValuePairs={keyValuePairs} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,46 +1,24 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { HorizontalGroup } from '..';
|
||||
import { useStyles2 } from '../../themes';
|
||||
|
||||
import { VizTooltipColorIndicator } from './VizTooltipColorIndicator';
|
||||
import { VizTooltipRow } from './VizTooltipRow';
|
||||
import { LabelValue } from './types';
|
||||
|
||||
interface Props {
|
||||
keyValuePairs?: LabelValue[];
|
||||
}
|
||||
|
||||
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>
|
||||
<>
|
||||
{keyValuePair.color && (
|
||||
<VizTooltipColorIndicator color={keyValuePair.color} colorIndicator={keyValuePair.colorIndicator!} />
|
||||
)}
|
||||
{keyValuePair.value}
|
||||
</>
|
||||
</HorizontalGroup>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
hgContainer: css({
|
||||
flexGrow: 1,
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontWeight: 400,
|
||||
}),
|
||||
});
|
||||
export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => (
|
||||
<>
|
||||
{keyValuePairs?.map((keyValuePair, i) => (
|
||||
<VizTooltipRow
|
||||
key={i}
|
||||
label={keyValuePair.label}
|
||||
value={keyValuePair.value}
|
||||
color={keyValuePair.color}
|
||||
colorIndicator={keyValuePair.colorIndicator!}
|
||||
colorFirst={false}
|
||||
justify={'space-between'}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
119
packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx
Normal file
119
packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { Tooltip } from '../Tooltip';
|
||||
|
||||
import { VizTooltipColorIndicator } from './VizTooltipColorIndicator';
|
||||
import { LabelValue } from './types';
|
||||
|
||||
interface Props extends LabelValue {
|
||||
justify?: string;
|
||||
colorFirst?: boolean;
|
||||
isActive?: boolean; // for series list
|
||||
marginRight?: string;
|
||||
}
|
||||
|
||||
export const VizTooltipRow = ({
|
||||
label,
|
||||
value,
|
||||
color,
|
||||
colorIndicator,
|
||||
justify = 'flex-start',
|
||||
colorFirst = true,
|
||||
isActive = false,
|
||||
marginRight = '0px',
|
||||
}: Props) => {
|
||||
const styles = useStyles2(getStyles, justify, marginRight);
|
||||
|
||||
const [showLabelTooltip, setShowLabelTooltip] = useState(false);
|
||||
const [showValueTooltip, setShowValueTooltip] = useState(false);
|
||||
|
||||
const onMouseEnterLabel = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (event.currentTarget.offsetWidth < event.currentTarget.scrollWidth) {
|
||||
setShowLabelTooltip(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseLeaveLabel = () => setShowLabelTooltip(false);
|
||||
|
||||
const onMouseEnterValue = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (event.currentTarget.offsetWidth < event.currentTarget.scrollWidth) {
|
||||
setShowValueTooltip(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseLeaveValue = () => setShowValueTooltip(false);
|
||||
|
||||
return (
|
||||
<div className={styles.contentWrapper}>
|
||||
{(color || label) && (
|
||||
<div className={styles.valueWrapper}>
|
||||
{color && colorFirst && <VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />}
|
||||
<Tooltip content={label} interactive={false} show={showLabelTooltip}>
|
||||
<div
|
||||
className={cx(styles.label, isActive && styles.activeSeries)}
|
||||
onMouseEnter={onMouseEnterLabel}
|
||||
onMouseLeave={onMouseLeaveLabel}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.valueWrapper}>
|
||||
{color && !colorFirst && <VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />}
|
||||
<Tooltip content={value ? value.toString() : ''} interactive={false} show={showValueTooltip}>
|
||||
<div className={cx(styles.value, isActive)} onMouseEnter={onMouseEnterValue} onMouseLeave={onMouseLeaveValue}>
|
||||
{value}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, justify: string, marginRight: string) => ({
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
gap: 4,
|
||||
borderTop: `1px solid ${theme.colors.border.medium}`,
|
||||
padding: theme.spacing(1),
|
||||
}),
|
||||
contentWrapper: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: justify,
|
||||
flexWrap: 'wrap',
|
||||
marginRight: marginRight,
|
||||
}),
|
||||
customContentPadding: css({
|
||||
padding: `${theme.spacing(1)} 0`,
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontWeight: 400,
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
marginRight: theme.spacing(0.5),
|
||||
}),
|
||||
value: css({
|
||||
fontWeight: 500,
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
valueWrapper: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
minWidth: 0,
|
||||
}),
|
||||
activeSeries: css({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
color: theme.colors.text.maxContrast,
|
||||
}),
|
||||
});
|
Loading…
Reference in New Issue
Block a user