mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
committed by
GitHub
parent
ba76be174f
commit
0633840777
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user