summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/components/ClipboardLabel.tsx
blob: 071a6991a8bf139ddb0de93001b417680590abb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import { useCallback } from 'react';
import styled from 'styled-components';
import { colors } from '../../config.json';
import { messages } from '../../shared/gettext';
import log from '../../shared/logging';
import { useScheduler } from '../../shared/scheduler';
import { useBoolean } from '../lib/utilityHooks';
import ImageView from './ImageView';

const COPIED_ICON_DURATION = 2000;

interface IProps {
  value: string;
  displayValue?: string;
  obscureValue?: boolean;
  message?: string;
  className?: string;
}

const StyledLabelContainer = styled.div({
  display: 'flex',
  flex: 1,
  height: '19px',
  alignItems: 'center',
});

const StyledLabel = styled.span({
  flex: 1,
});

const StyledButton = styled.button({
  cursor: 'default',
  padding: 0,
  marginLeft: '20px',
  backgroundColor: 'transparent',
  border: 'none',
});

const StyledCopyButton = styled(StyledButton)({
  width: '24px',
});

export default function ClipboardLabel(props: IProps) {
  const [obscured, , , toggleObscured] = useBoolean(props.obscureValue ?? true);
  const [justCopied, setJustCopied, resetJustCopied] = useBoolean(false);

  const copiedScheduler = useScheduler();

  const onCopy = useCallback(async () => {
    try {
      await navigator.clipboard.writeText(props.value);
      copiedScheduler.schedule(resetJustCopied, COPIED_ICON_DURATION);
      setJustCopied();
    } catch (e) {
      const error = e as Error;
      log.error(`Failed to copy to clipboard: ${error.message}`);
    }
  }, [props.value, copiedScheduler, setJustCopied, resetJustCopied]);

  const value = props.displayValue ?? props.value;
  return (
    <StyledLabelContainer>
      <StyledLabel className={props.className} aria-hidden={obscured}>
        {obscured ? '●●●● ●●●● ●●●● ●●●●' : value}
      </StyledLabel>
      {props.obscureValue !== false && (
        <StyledButton
          onClick={toggleObscured}
          aria-label={
            obscured
              ? // This line is here to prevent the following one to be moved up here by prettier
                // TRANSLATORS: Provided to accessibility tools such as screenreaders to describe
                // TRANSLATORS: the button which unobscures the account number.
                messages.pgettext('accessibility', 'Show account number')
              : // This line is here to prevent the following one to be moved up here by prettier
                // TRANSLATORS: Provided to accessibility tools such as screenreaders to describe
                // TRANSLATORS: the button which obscures the account number.
                messages.pgettext('accessibility', 'Hide account number')
          }>
          <ImageView
            source={obscured ? 'icon-unobscure' : 'icon-obscure'}
            tintColor={colors.white}
            tintHoverColor={colors.white80}
            width={24}
          />
        </StyledButton>
      )}
      <StyledCopyButton
        onClick={onCopy}
        aria-label={
          // TRANSLATORS: Provided to accessibility tools such as screenreaders to describe a button
          // TRANSLATORS: which copies the account number to the clipboard.
          messages.pgettext('accessibility', 'Copy account number')
        }>
        <ImageView
          source={justCopied ? 'icon-tick' : 'icon-copy'}
          tintColor={justCopied ? colors.green : colors.white}
          tintHoverColor={justCopied ? colors.green : colors.white80}
          width={justCopied ? 22 : 24}
        />
      </StyledCopyButton>
    </StyledLabelContainer>
  );
}