Line data Source code
1 : import * as React from 'react';
2 :
3 : import { CheckIcon, CopyIcon } from './symbol_icon.js';
4 : import type { Icon } from './symbol_icon.js';
5 :
6 : import { useLogger } from '../../platform/logger/logger_provider.js';
7 : import { stringifyError } from '../../platform/logger/logger.js';
8 : import { ButtonSize, ButtonVariant, IconButton } from './button.js';
9 :
10 1 : const DEFAULT_COPY_TIMEOUT = 2000;
11 :
12 : interface Props {
13 : variant: ButtonVariant;
14 : size: ButtonSize;
15 : className?: string;
16 : value?: string;
17 : getValue?: () => string;
18 : timeoutMs?: number;
19 : logContext: string;
20 : disabled?: boolean;
21 : icon?: Icon;
22 : ['aria-label']: string;
23 : ['aria-labelledby']: string;
24 : ['data-testid']?: string;
25 : }
26 : /// The icon to render inside the button
27 1 : export function CopyToClipboardButton(props: Props): React.ReactElement {
28 1 : const logger = useLogger();
29 1 : const [lastCopied, setLastCopied] = React.useState<number | null>(null);
30 1 : const [wasRecentlyCopied, setWasRecentlyCopied] = React.useState<boolean>(false);
31 1 : const timeoutMs = props.timeoutMs ?? DEFAULT_COPY_TIMEOUT;
32 :
33 1 : const copy = React.useCallback(async () => {
34 : // Read the value when copying, getValue may return text that changes without a re-render
35 1 : const value = props.value ?? (props.getValue ? props.getValue() : "");
36 1 : try {
37 1 : await navigator.clipboard.writeText(value);
38 1 : logger.info("Copied to clipboard", { "chars": value.length.toString() }, props.logContext);
39 1 : setLastCopied(Date.now());
40 1 : } catch (e: any) {
41 0 : logger.error("Copying failed", { "error": stringifyError(e) }, props.logContext);
42 0 : }
43 1 : }, [setLastCopied, props.value, props.getValue]);
44 :
45 1 : React.useEffect(() => {
46 1 : if (lastCopied == null) {
47 1 : return;
48 : }
49 1 : setWasRecentlyCopied(true);
50 1 : const timeoutId = setTimeout(() => {
51 0 : setWasRecentlyCopied(false);
52 1 : }, timeoutMs);
53 1 : return () => clearTimeout(timeoutId);
54 1 : }, [lastCopied]);
55 :
56 1 : let InnerIcon: Icon;
57 1 : if (wasRecentlyCopied) {
58 1 : InnerIcon = CheckIcon;
59 : } else {
60 1 : InnerIcon = props.icon ?? CopyIcon;
61 : }
62 :
63 1 : const ariaLabel = props['aria-label'];
64 1 : const ariaLabelledBy = props['aria-labelledby'];
65 1 : return (
66 1 : <IconButton
67 1 : className={props.className}
68 1 : variant={props.variant}
69 1 : size={props.size}
70 1 : onClick={props.disabled ? undefined : copy}
71 1 : aria-label={ariaLabel}
72 1 : aria-labelledby={ariaLabelledBy}
73 1 : data-testid={props['data-testid']}
74 1 : disabled={props.disabled}
75 : >
76 1 : <InnerIcon />
77 1 : </IconButton>
78 : );
79 1 : }
|