VizTooltips: Copy to clipboard functionality (#80761)

pull/80908/head
Adela Almasan 1 year ago committed by GitHub
parent 29112f8822
commit e0d0420990
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 95
      packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx

@ -1,9 +1,10 @@
import { css, cx } from '@emotion/css';
import React, { useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { InlineToast } from '../InlineToast/InlineToast';
import { Tooltip } from '../Tooltip';
import { ColorIndicatorPosition, VizTooltipColorIndicator } from './VizTooltipColorIndicator';
@ -16,6 +17,14 @@ interface Props extends LabelValue {
isPinned: boolean;
}
enum LabelValueTypes {
label = 'label',
value = 'value',
}
const SUCCESSFULLY_COPIED_TEXT = 'Copied to clipboard';
const SHOW_SUCCESS_DURATION = 2 * 1000;
export const VizTooltipRow = ({
label,
value,
@ -32,6 +41,61 @@ export const VizTooltipRow = ({
const [showLabelTooltip, setShowLabelTooltip] = useState(false);
const [showValueTooltip, setShowValueTooltip] = useState(false);
const [copiedText, setCopiedText] = useState<Record<string, string> | null>(null);
const [showCopySuccess, setShowCopySuccess] = useState(false);
const labelRef = useRef<null | HTMLDivElement>(null);
const valueRef = useRef<null | HTMLDivElement>(null);
useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout>;
if (showCopySuccess) {
timeoutId = setTimeout(() => {
setShowCopySuccess(false);
}, SHOW_SUCCESS_DURATION);
}
return () => {
window.clearTimeout(timeoutId);
};
}, [showCopySuccess]);
const copyToClipboard = async (text: string, type: LabelValueTypes) => {
if (!(navigator?.clipboard && window.isSecureContext)) {
fallbackCopyToClipboard(text, type);
return;
}
try {
await navigator.clipboard.writeText(text);
setCopiedText({ [`${type}`]: text });
setShowCopySuccess(true);
} catch (error) {
setCopiedText(null);
}
};
const fallbackCopyToClipboard = (text: string, type: LabelValueTypes) => {
// Use a fallback method for browsers/contexts that don't support the Clipboard API.
const textarea = document.createElement('textarea');
labelRef.current?.appendChild(textarea);
textarea.value = text;
textarea.focus();
textarea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
setCopiedText({ [`${type}`]: text });
setShowCopySuccess(true);
}
} catch (err) {
console.error('Unable to copy to clipboard', err);
}
textarea.remove();
};
const onMouseEnterLabel = (event: React.MouseEvent<HTMLDivElement>) => {
if (event.currentTarget.offsetWidth < event.currentTarget.scrollWidth) {
setShowLabelTooltip(true);
@ -58,15 +122,27 @@ export const VizTooltipRow = ({
{!isPinned ? (
<div className={cx(styles.label, isActive && styles.activeSeries)}>{label}</div>
) : (
<>
<Tooltip content={label} interactive={false} show={showLabelTooltip}>
<>
{showCopySuccess && copiedText?.label && (
<InlineToast placement="top" referenceElement={labelRef.current}>
{SUCCESSFULLY_COPIED_TEXT}
</InlineToast>
)}
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<div
className={cx(styles.label, isActive && styles.activeSeries)}
className={cx(styles.label, isActive && styles.activeSeries, navigator?.clipboard && styles.copy)}
onMouseEnter={onMouseEnterLabel}
onMouseLeave={onMouseLeaveLabel}
onClick={() => copyToClipboard(label, LabelValueTypes.label)}
ref={labelRef}
>
{label}
</div>
</>
</Tooltip>
</>
)}
</div>
)}
@ -84,13 +160,23 @@ export const VizTooltipRow = ({
<div className={cx(styles.value, isActive)}>{value}</div>
) : (
<Tooltip content={value ? value.toString() : ''} interactive={false} show={showValueTooltip}>
<>
{showCopySuccess && copiedText?.value && (
<InlineToast placement="top" referenceElement={valueRef.current}>
{SUCCESSFULLY_COPIED_TEXT}
</InlineToast>
)}
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<div
className={cx(styles.value, isActive)}
className={cx(styles.value, isActive, navigator?.clipboard && styles.copy)}
onMouseEnter={onMouseEnterValue}
onMouseLeave={onMouseLeaveValue}
onClick={() => copyToClipboard(value ? value.toString() : '', LabelValueTypes.value)}
ref={valueRef}
>
{value}
</div>
</>
</Tooltip>
)}
@ -135,4 +221,7 @@ const getStyles = (theme: GrafanaTheme2, justify: string, marginRight: string) =
fontWeight: theme.typography.fontWeightBold,
color: theme.colors.text.maxContrast,
}),
copy: css({
cursor: 'pointer',
}),
});

Loading…
Cancel
Save