From 2b21e1432e315fd7fcf97a23f731e5a1e82444a1 Mon Sep 17 00:00:00 2001 From: Marc Mignonsin Date: Fri, 23 Aug 2024 12:42:36 +0200 Subject: [PATCH] feat(Labels): Add tooltips on compare actions --- .../components/SceneLabelValuePanel.tsx | 4 +- .../SceneStatsPanel/SceneStatsPanel.tsx | 10 +- .../SceneStatsPanel/ui/CompareAction.tsx | 92 +++++++++++++++++++ .../SceneStatsPanel/ui/StatsPanel.tsx | 52 +++-------- 4 files changed, 113 insertions(+), 45 deletions(-) create mode 100644 src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/CompareAction.tsx diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneLabelValuePanel.tsx b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneLabelValuePanel.tsx index d2a4bf1d..a2344f01 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneLabelValuePanel.tsx +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneLabelValuePanel.tsx @@ -66,8 +66,8 @@ export class SceneLabelValuePanel extends SceneObjectBase) { const styles = useStyles2(getStyles); // eslint-disable-line react-hooks/rules-of-hooks const { statsPanel, timeseriesPanel } = model.useState(); - const { actionChecks } = statsPanel.useState(); - const isSelected = actionChecks[0] || actionChecks[1]; + const { compareActionChecks } = statsPanel.useState(); + const isSelected = compareActionChecks[0] || compareActionChecks[1]; return (
diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/SceneStatsPanel.tsx b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/SceneStatsPanel.tsx index b0f744e2..26215ed3 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/SceneStatsPanel.tsx +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/SceneStatsPanel.tsx @@ -15,7 +15,7 @@ export type ItemStats = { interface SceneStatsPanelState extends SceneObjectState { item: GridItemData; itemStats?: ItemStats; - actionChecks: boolean[]; + compareActionChecks: boolean[]; } export class SceneStatsPanel extends SceneObjectBase { @@ -25,7 +25,7 @@ export class SceneStatsPanel extends SceneObjectBase { super({ item, itemStats: undefined, - actionChecks: [false, false], + compareActionChecks: [false, false], }); this.addActivationHandler(this.onActivate.bind(this)); @@ -41,7 +41,7 @@ export class SceneStatsPanel extends SceneObjectBase { const { item } = this.state; this.setState({ - actionChecks: [baselineItem?.value === item.value, comparisonItem?.value === item.value], + compareActionChecks: [baselineItem?.value === item.value, comparisonItem?.value === item.value], }); } @@ -64,13 +64,13 @@ export class SceneStatsPanel extends SceneObjectBase { } static Component({ model }: SceneComponentProps) { - const { item, itemStats, actionChecks } = model.useState(); + const { item, itemStats, compareActionChecks } = model.useState(); return ( ); diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/CompareAction.tsx b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/CompareAction.tsx new file mode 100644 index 00000000..00f00fac --- /dev/null +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/CompareAction.tsx @@ -0,0 +1,92 @@ +import { css, cx } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Checkbox, Tooltip, useStyles2 } from '@grafana/ui'; +import React, { useEffect, useRef, useState } from 'react'; + +import { CompareTarget } from '../../../domain/types'; + +type CompareActionProps = { + option: { + label: string; + value: CompareTarget; + description: string; + }; + checked: boolean; + onChange: (compareTarget: CompareTarget) => void; +}; + +export function CompareAction({ option, checked, onChange }: CompareActionProps) { + const styles = useStyles2(getStyles); + + const [showTooltip, setShowTooltip] = useState(false); + const checkboxRef = useRef(null); + const label = (checkboxRef.current as HTMLInputElement)?.closest('label'); + + useEffect(() => { + if (!label || checked) { + setShowTooltip(false); + return; + } + + const onMouseEnter = () => { + setShowTooltip(true); + }; + + const onMouseLeave = () => { + setShowTooltip(false); + }; + + label.addEventListener('mouseenter', onMouseEnter); + label.addEventListener('mouseleave', onMouseLeave); + + return () => { + label.removeEventListener('mouseleave', onMouseLeave); + label.removeEventListener('mouseenter', onMouseEnter); + }; + }, [checked, label]); + + return ( + <> + + + + onChange(option.value)} + /> + + ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + tooltipContent: css` + position: relative; + left: 42px; + `, + checkbox: css` + column-gap: 4px; + + &:last-child { + & :nth-child(1) { + grid-column-start: 2; + } + & :nth-child(2) { + grid-column-start: 1; + } + } + + span { + color: ${theme.colors.text.secondary}; + } + span:hover { + color: ${theme.colors.text.primary}; + } + + &.checked span { + color: ${theme.colors.text.primary}; + } + `, +}); diff --git a/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/StatsPanel.tsx b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/StatsPanel.tsx index 3fd8743b..f0393ad6 100644 --- a/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/StatsPanel.tsx +++ b/src/pages/ProfilesExplorerView/components/SceneExploreServiceLabels/components/SceneGroupByLabels/components/SceneLabelValuesGrid/components/SceneStatsPanel/ui/StatsPanel.tsx @@ -1,21 +1,22 @@ -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { getValueFormat, GrafanaTheme2 } from '@grafana/data'; -import { Checkbox, Spinner, useStyles2 } from '@grafana/ui'; +import { Spinner, useStyles2 } from '@grafana/ui'; import React, { useMemo } from 'react'; import { GridItemData } from '../../../../../../../../../components/SceneByVariableRepeaterGrid/types/GridItemData'; import { getColorByIndex } from '../../../../../../../../../helpers/getColorByIndex'; import { CompareTarget } from '../../../domain/types'; import { ItemStats } from '../SceneStatsPanel'; +import { CompareAction } from './CompareAction'; export type StatsPanelProps = { item: GridItemData; itemStats?: ItemStats; - actionChecks: boolean[]; + compareActionChecks: boolean[]; onChangeCompareTarget: (compareTarget: CompareTarget) => void; }; -export function StatsPanel({ item, itemStats, actionChecks, onChangeCompareTarget }: StatsPanelProps) { +export function StatsPanel({ item, itemStats, compareActionChecks, onChangeCompareTarget }: StatsPanelProps) { const styles = useStyles2(getStyles); const { index, value } = item; @@ -38,15 +39,15 @@ export function StatsPanel({ item, itemStats, actionChecks, onChangeCompareTarge { label: 'Baseline', value: CompareTarget.BASELINE, - description: !actionChecks[0] ? `Click to select "${value}" as baseline for comparison` : '', + description: !compareActionChecks[0] ? `Click to select "${value}" as baseline for comparison` : '', }, { label: 'Comparison', value: CompareTarget.COMPARISON, - description: !actionChecks[1] ? `Click to select "${value}" as target for comparison` : '', + description: !compareActionChecks[1] ? `Click to select "${value}" as target for comparison` : '', }, ], - [actionChecks, value] + [compareActionChecks, value] ); return ( @@ -55,15 +56,13 @@ export function StatsPanel({ item, itemStats, actionChecks, onChangeCompareTarge {total} -
+
{options.map((option, i) => ( - onChangeCompareTarget(option.value)} + option={option} + checked={compareActionChecks[i]} + onChange={onChangeCompareTarget} /> ))}
@@ -89,32 +88,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ text-align: center; margin-top: ${theme.spacing(5)}; `, - controls: css` + compareActions: css` display: flex; justify-content: space-between; font-size: 11px; `, - checkbox: css` - column-gap: 4px; - - &:nth-child(2) { - & :nth-child(1) { - grid-column-start: 2; - } - & :nth-child(2) { - grid-column-start: 1; - } - } - - span { - color: ${theme.colors.text.secondary}; - } - span:hover { - color: ${theme.colors.text.primary}; - } - - &.checked span { - color: ${theme.colors.text.primary}; - } - `, });