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>
This commit is contained in:
Ezequiel Victorero
2022-07-20 06:33:46 -03:00
committed by GitHub
parent ba76be174f
commit 0633840777
16 changed files with 78 additions and 125 deletions

View File

@@ -11,7 +11,7 @@ import { getPropertiesForButtonSize } from '../Forms/commonStyles';
import { Icon } from '../Icon/Icon';
import { PopoverContent, Tooltip, TooltipPlacement } from '../Tooltip';
export type ButtonVariant = 'primary' | 'secondary' | 'destructive';
export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'success';
export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive'];
export type ButtonFill = 'solid' | 'outline' | 'text';
export const allButtonFills: ButtonFill[] = ['solid', 'outline', 'text'];
@@ -294,6 +294,9 @@ export function getPropertiesForVariant(theme: GrafanaTheme2, variant: ButtonVar
case 'destructive':
return getButtonVariantStyles(theme, theme.colors.error, fill);
case 'success':
return getButtonVariantStyles(theme, theme.colors.success, fill);
case 'primary':
default:
return getButtonVariantStyles(theme, theme.colors.primary, fill);

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useRef, useState, useEffect } from 'react';
import { Button, ButtonProps } from '../Button';
@@ -11,13 +11,40 @@ export interface Props extends ButtonProps {
onClipboardError?(copiedText: string, error: unknown): void;
}
export function ClipboardButton({ onClipboardCopy, onClipboardError, children, getText, ...buttonProps }: Props) {
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);
@@ -25,7 +52,14 @@ export function ClipboardButton({ onClipboardCopy, onClipboardError, children, g
}, [getText, onClipboardCopy, onClipboardError]);
return (
<Button onClick={copyTextCallback} {...buttonProps} ref={buttonRef}>
<Button
onClick={copyTextCallback}
icon={showCopySuccess ? 'check' : icon}
variant={showCopySuccess ? 'success' : variant}
aria-label={showCopySuccess ? 'Copied' : undefined}
{...buttonProps}
ref={buttonRef}
>
{children}
</Button>
);

View File

@@ -1,8 +1,7 @@
import { isString } from 'lodash';
import React, { useEffect, useState } from 'react';
import React from 'react';
import { ClipboardButton } from '../ClipboardButton/ClipboardButton';
import { Icon } from '../Icon/Icon';
import { Modal } from '../Modal/Modal';
import { CodeEditor } from '../Monaco/CodeEditor';
@@ -13,23 +12,6 @@ interface TableCellInspectModalProps {
}
export function TableCellInspectModal({ value, onDismiss, mode }: TableCellInspectModalProps) {
const [isInClipboard, setIsInClipboard] = useState(false);
const timeoutRef = React.useRef<number>();
useEffect(() => {
if (isInClipboard) {
timeoutRef.current = window.setTimeout(() => {
setIsInClipboard(false);
}, 2000);
}
return () => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
};
}, [isInClipboard]);
let displayValue = value;
if (isString(value)) {
try {
@@ -60,15 +42,8 @@ export function TableCellInspectModal({ value, onDismiss, mode }: TableCellInspe
<pre>{text}</pre>
)}
<Modal.ButtonRow>
<ClipboardButton getText={() => text} onClipboardCopy={() => setIsInClipboard(true)}>
{!isInClipboard ? (
'Copy to Clipboard'
) : (
<>
<Icon name="check" />
Copied to clipboard
</>
)}
<ClipboardButton icon="copy" getText={() => text}>
Copy to Clipboard
</ClipboardButton>
</Modal.ButtonRow>
</Modal>