Skip to content

Commit

Permalink
feat(Labels): Add tooltips on compare actions
Browse files Browse the repository at this point in the history
  • Loading branch information
grafakus committed Aug 23, 2024
1 parent 5470a93 commit 2b21e14
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export class SceneLabelValuePanel extends SceneObjectBase<SceneLabelValuesStatAn
static Component({ model }: SceneComponentProps<SceneLabelValuePanel>) {
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 (
<div className={cx(styles.container, isSelected && 'selected')}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type ItemStats = {
interface SceneStatsPanelState extends SceneObjectState {
item: GridItemData;
itemStats?: ItemStats;
actionChecks: boolean[];
compareActionChecks: boolean[];
}

export class SceneStatsPanel extends SceneObjectBase<SceneStatsPanelState> {
Expand All @@ -25,7 +25,7 @@ export class SceneStatsPanel extends SceneObjectBase<SceneStatsPanelState> {
super({
item,
itemStats: undefined,
actionChecks: [false, false],
compareActionChecks: [false, false],
});

this.addActivationHandler(this.onActivate.bind(this));
Expand All @@ -41,7 +41,7 @@ export class SceneStatsPanel extends SceneObjectBase<SceneStatsPanelState> {
const { item } = this.state;

this.setState({
actionChecks: [baselineItem?.value === item.value, comparisonItem?.value === item.value],
compareActionChecks: [baselineItem?.value === item.value, comparisonItem?.value === item.value],
});
}

Expand All @@ -64,13 +64,13 @@ export class SceneStatsPanel extends SceneObjectBase<SceneStatsPanelState> {
}

static Component({ model }: SceneComponentProps<SceneStatsPanel>) {
const { item, itemStats, actionChecks } = model.useState();
const { item, itemStats, compareActionChecks } = model.useState();

return (
<StatsPanel
item={item}
itemStats={itemStats}
actionChecks={actionChecks}
compareActionChecks={compareActionChecks}
onChangeCompareTarget={model.onChangeCompareTarget}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>(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 (
<>
<Tooltip content={option.description} show={!checked && showTooltip} placement="top">
<span className={styles.tooltipContent} />
</Tooltip>
<Checkbox
ref={checkboxRef}
className={cx(styles.checkbox, checked && 'checked')}
checked={checked}
label={option.label}
onChange={() => 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};
}
`,
});
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 (
Expand All @@ -55,15 +56,13 @@ export function StatsPanel({ item, itemStats, actionChecks, onChangeCompareTarge
{total}
</h1>

<div className={styles.controls}>
<div className={styles.compareActions}>
{options.map((option, i) => (
<Checkbox
<CompareAction
key={option.value}
className={cx(styles.checkbox, actionChecks[i] && 'checked')}
checked={actionChecks[i]}
label={option.label}
title={option.description}
onChange={() => onChangeCompareTarget(option.value)}
option={option}
checked={compareActionChecks[i]}
onChange={onChangeCompareTarget}
/>
))}
</div>
Expand All @@ -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};
}
`,
});

0 comments on commit 2b21e14

Please sign in to comment.