From 0c404a4cd9a7d1186b33c6265cbc6bba232eb569 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:10:22 -0500 Subject: [PATCH] Tooltip: VizTooltip components (#75794) --- .../src/components/VizTooltip/HeaderLabel.tsx | 50 +++++++++++ .../VizTooltip/VizTooltipContent.tsx | 54 ++++++++++++ .../VizTooltip/VizTooltipFooter.tsx | 65 +++++++++++++++ .../VizTooltip/VizTooltipHeader.tsx | 42 ++++++++++ .../VizTooltip/VizTooltipHeaderLabelValue.tsx | 83 +++++++++++++++++++ .../src/components/VizTooltip/types.ts | 24 ++++++ .../src/components/VizTooltip/utils.ts | 28 +++++++ 7 files changed, 346 insertions(+) create mode 100644 packages/grafana-ui/src/components/VizTooltip/HeaderLabel.tsx create mode 100644 packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx create mode 100644 packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx create mode 100644 packages/grafana-ui/src/components/VizTooltip/VizTooltipHeader.tsx create mode 100644 packages/grafana-ui/src/components/VizTooltip/VizTooltipHeaderLabelValue.tsx create mode 100644 packages/grafana-ui/src/components/VizTooltip/types.ts diff --git a/packages/grafana-ui/src/components/VizTooltip/HeaderLabel.tsx b/packages/grafana-ui/src/components/VizTooltip/HeaderLabel.tsx new file mode 100644 index 00000000000..9303073f222 --- /dev/null +++ b/packages/grafana-ui/src/components/VizTooltip/HeaderLabel.tsx @@ -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 ( + +
+ {headerLabel.label} + + {headerLabel.value} + +
+
+ ); +}; + +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)', + }), +}); diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx new file mode 100644 index 00000000000..6cedcfa9744 --- /dev/null +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx @@ -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 ( +
+
+ {contentLabelValue?.map((labelValue, i) => { + return ( + +
{labelValue.label}
+
{labelValue.value}
+
+ ); + })} +
+ {customContent &&
{customContent}
} +
+ ); +}; + +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, + }), +}); diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx new file mode 100644 index 00000000000..490d62bf582 --- /dev/null +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx @@ -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>; + 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 ( + + {dataLinks.map((link, i) => ( + + ))} + + ); + }; + + return ( +
+ {dataLinks.length > 0 &&
{renderDataLinks()}
} + {canAnnotate && ( +
+ +
+ )} +
+ ); +}; + +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), + }), +}); diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipHeader.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipHeader.tsx new file mode 100644 index 00000000000..fe7233f551d --- /dev/null +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipHeader.tsx @@ -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 ; + }; + return ( +
+ + {renderValue()} +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + wrapper: css({ + display: 'flex', + flexDirection: 'column', + flex: 1, + paddingBottom: theme.spacing(1), + }), +}); diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipHeaderLabelValue.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipHeaderLabelValue.tsx new file mode 100644 index 00000000000..13d8db31a8c --- /dev/null +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipHeaderLabelValue.tsx @@ -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; + +export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => { + const styles = useStyles2(getStyles); + + return ( + <> + {keyValuePairs?.map((keyValuePair, i) => { + return ( + +
{keyValuePair.label}
+ <> + + {keyValuePair.value} + +
+ ); + })} + + ); +}; + +// @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, + }), +}); diff --git a/packages/grafana-ui/src/components/VizTooltip/types.ts b/packages/grafana-ui/src/components/VizTooltip/types.ts new file mode 100644 index 00000000000..615ad81b331 --- /dev/null +++ b/packages/grafana-ui/src/components/VizTooltip/types.ts @@ -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; +} diff --git a/packages/grafana-ui/src/components/VizTooltip/utils.ts b/packages/grafana-ui/src/components/VizTooltip/utils.ts index 6517962fc07..44f65d28ffa 100644 --- a/packages/grafana-ui/src/components/VizTooltip/utils.ts +++ b/packages/grafana-ui/src/components/VizTooltip/utils.ts @@ -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; + } +};