Files
Ezequiel Victorero 0633840777 GrafanaUI: Add success state to ClipboardButton (#52069)
* User Experience: apply the same pattern feedback for all copy to clipboard buttons

* add copy icon to all ClipboardButton use cases

* Change primary color for copy to clipboard in create token

* Add success button variant

* Remove copy confirmation from TableCellInspectModal because it's in the base component now

* Design tweaks to copy confirmation

 - Only change the icon to tick to avoid the button changing size
 - Change button to success green
 - Only show copy confirmation state for 2 seconds

* revert TabelCellInspectModal text button back

* revert accidental change to ShareLink

Co-authored-by: joshhunt <josh@trtr.co>
2022-07-20 10:33:46 +01:00

86 lines
2.5 KiB
TypeScript

import React, { useCallback, useRef, useState, useEffect } from 'react';
import { Button, ButtonProps } from '../Button';
export interface Props extends ButtonProps {
/** A function that returns text to be copied */
getText(): string;
/** Callback when the text has been successfully copied */
onClipboardCopy?(copiedText: string): void;
/** Callback when there was an error copying the text */
onClipboardError?(copiedText: string, error: unknown): void;
}
const SHOW_SUCCESS_DURATION = 2 * 1000;
export function ClipboardButton({
onClipboardCopy,
onClipboardError,
children,
getText,
icon,
variant,
...buttonProps
}: Props) {
const [showCopySuccess, setShowCopySuccess] = useState(false);
useEffect(() => {
let timeoutId: NodeJS.Timeout;
if (showCopySuccess) {
timeoutId = setTimeout(() => {
setShowCopySuccess(false);
}, SHOW_SUCCESS_DURATION);
}
return () => {
window.clearTimeout(timeoutId);
};
}, [showCopySuccess]);
const buttonRef = useRef<null | HTMLButtonElement>(null);
const copyTextCallback = useCallback(async () => {
const textToCopy = getText();
try {
await copyText(textToCopy, buttonRef);
setShowCopySuccess(true);
onClipboardCopy?.(textToCopy);
} catch (e) {
onClipboardError?.(textToCopy, e);
}
}, [getText, onClipboardCopy, onClipboardError]);
return (
<Button
onClick={copyTextCallback}
icon={showCopySuccess ? 'check' : icon}
variant={showCopySuccess ? 'success' : variant}
aria-label={showCopySuccess ? 'Copied' : undefined}
{...buttonProps}
ref={buttonRef}
>
{children}
</Button>
);
}
const copyText = async (text: string, buttonRef: React.MutableRefObject<HTMLButtonElement | null>) => {
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
} else {
// Use a fallback method for browsers/contexts that don't support the Clipboard API.
// See https://web.dev/async-clipboard/#feature-detection.
const input = document.createElement('input');
// Normally we'd append this to the body. However if we're inside a focus manager
// from react-aria, we can't focus anything outside of the managed area.
// Instead, let's append it to the button. Then we're guaranteed to be able to focus + copy.
buttonRef.current?.appendChild(input);
input.value = text;
input.focus();
input.select();
document.execCommand('copy');
input.remove();
}
};