diff options
Diffstat (limited to 'gui/src')
5 files changed, 94 insertions, 39 deletions
diff --git a/gui/src/renderer/components/AccountTokenLabel.tsx b/gui/src/renderer/components/AccountTokenLabel.tsx index b2623a5ad2..704f41af10 100644 --- a/gui/src/renderer/components/AccountTokenLabel.tsx +++ b/gui/src/renderer/components/AccountTokenLabel.tsx @@ -3,6 +3,7 @@ import ClipboardLabel from './ClipboardLabel'; interface IAccountTokenLabelProps { accountToken: string; + obscureValue?: boolean; className?: string; } @@ -11,6 +12,7 @@ export default function AccountTokenLabel(props: IAccountTokenLabelProps) { <ClipboardLabel value={props.accountToken} displayValue={formatAccountToken(props.accountToken)} + obscureValue={props.obscureValue} className={props.className} /> ); diff --git a/gui/src/renderer/components/ClipboardLabel.tsx b/gui/src/renderer/components/ClipboardLabel.tsx index 43b0d2c764..071a6991a8 100644 --- a/gui/src/renderer/components/ClipboardLabel.tsx +++ b/gui/src/renderer/components/ClipboardLabel.tsx @@ -1,58 +1,104 @@ -import * as React from 'react'; +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 { Scheduler } from '../../shared/scheduler'; +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; - delay: number; + obscureValue?: boolean; message?: string; className?: string; } -interface IState { - showsMessage: boolean; -} - -const Label = styled.span({ - cursor: 'pointer', +const StyledLabelContainer = styled.div({ + display: 'flex', + flex: 1, + height: '19px', + alignItems: 'center', }); -export default class ClipboardLabel extends React.Component<IProps, IState> { - public static defaultProps: Partial<IProps> = { - delay: 3000, - }; +const StyledLabel = styled.span({ + flex: 1, +}); - public state: IState = { - showsMessage: false, - }; +const StyledButton = styled.button({ + cursor: 'default', + padding: 0, + marginLeft: '20px', + backgroundColor: 'transparent', + border: 'none', +}); - private scheduler = new Scheduler(); +const StyledCopyButton = styled(StyledButton)({ + width: '24px', +}); - public componentWillUnmount() { - this.scheduler.cancel(); - } +export default function ClipboardLabel(props: IProps) { + const [obscured, , , toggleObscured] = useBoolean(props.obscureValue ?? true); + const [justCopied, setJustCopied, resetJustCopied] = useBoolean(false); - public render() { - const message = this.props.message ?? messages.gettext('COPIED TO CLIPBOARD!'); - const displayValue = this.props.displayValue ?? this.props.value; - return ( - <Label className={this.props.className} onClick={this.handlePress}> - {this.state.showsMessage ? message : displayValue} - </Label> - ); - } + const copiedScheduler = useScheduler(); - private handlePress = async () => { + const onCopy = useCallback(async () => { try { - await navigator.clipboard.writeText(this.props.value); - this.scheduler.schedule(() => this.setState({ showsMessage: false }), this.props.delay); - this.setState({ showsMessage: true }); + 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> + ); } diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx index d4ac395c2c..0fedb67fdf 100644 --- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx +++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx @@ -127,7 +127,10 @@ export default class ExpiredAccountErrorView extends React.Component< <StyledAccountTokenMessage> {messages.pgettext('connect-view', 'Here’s your account number. Save it!')} <StyledAccountTokenContainer> - <StyledAccountTokenLabel accountToken={this.props.accountToken || ''} /> + <StyledAccountTokenLabel + accountToken={this.props.accountToken || ''} + obscureValue={false} + /> </StyledAccountTokenContainer> </StyledAccountTokenMessage> diff --git a/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx b/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx index 8872bbb312..5aa4e6ea63 100644 --- a/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx +++ b/gui/src/renderer/components/ExpiredAccountErrorViewStyles.tsx @@ -14,8 +14,8 @@ export const StyledHeader = styled(DefaultHeaderBar)({ export const StyledAccountTokenLabel = styled(AccountTokenLabel)({ fontFamily: 'Open Sans', - lineHeight: '24px', - fontSize: '24px', + lineHeight: '20px', + fontSize: '20px', fontWeight: 800, color: colors.white, }); @@ -80,6 +80,6 @@ export const StyledStatusIcon = styled.div({ export const StyledAccountTokenContainer = styled.div({ display: 'flex', - height: '68px', + height: '50px', alignItems: 'center', }); diff --git a/gui/src/renderer/components/WireguardKeys.tsx b/gui/src/renderer/components/WireguardKeys.tsx index 3201e0a708..be8c043458 100644 --- a/gui/src/renderer/components/WireguardKeys.tsx +++ b/gui/src/renderer/components/WireguardKeys.tsx @@ -242,7 +242,11 @@ export default class WireguardKeys extends React.Component<IProps, IState> { const publicKey = this.props.keyState.key.publicKey; return ( <StyledRowValue title={this.props.keyState.key.publicKey}> - <ClipboardLabel value={publicKey} displayValue={publicKey.substring(0, 20) + '...'} /> + <ClipboardLabel + value={publicKey} + displayValue={publicKey.substring(0, 20) + '...'} + obscureValue={false} + /> </StyledRowValue> ); } |
