diff --git a/ui/app/components/ui/export-text-container/export-text-container.component.js b/ui/app/components/ui/export-text-container/export-text-container.component.js index 21a8179a2..1bd4d4e2f 100644 --- a/ui/app/components/ui/export-text-container/export-text-container.component.js +++ b/ui/app/components/ui/export-text-container/export-text-container.component.js @@ -1,52 +1,47 @@ -import React, { Component } from 'react' +import React from 'react' import PropTypes from 'prop-types' -import copyToClipboard from 'copy-to-clipboard' import { exportAsFile } from '../../../helpers/utils/util' import Copy from '../icon/copy-icon.component' +import { useI18nContext } from '../../../hooks/useI18nContext' +import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard' -class ExportTextContainer extends Component { - render () { - const { text = '' } = this.props - const { t } = this.context +function ExportTextContainer ({ text = '' }) { + const t = useI18nContext() + const [copied, handleCopy] = useCopyToClipboard() - return ( -
-
-
- {text} + return ( +
+
+
{text}
+
+
+
{ + handleCopy(text) + }} + > + +
+ {copied ? t('copiedExclamation') : t('copyToClipboard')}
-
-
copyToClipboard(text)} - > - -
- {t('copyToClipboard')} -
-
-
exportAsFile('', text)} - > - -
- {t('saveAsCsvFile')} -
+
exportAsFile('', text)} + > + +
+ {t('saveAsCsvFile')}
- ) - } +
+ ) } ExportTextContainer.propTypes = { text: PropTypes.string, } -ExportTextContainer.contextTypes = { - t: PropTypes.func, -} - -export default ExportTextContainer +export default React.memo(ExportTextContainer) diff --git a/ui/app/hooks/useCopyToClipboard.js b/ui/app/hooks/useCopyToClipboard.js new file mode 100644 index 000000000..8d870d1b8 --- /dev/null +++ b/ui/app/hooks/useCopyToClipboard.js @@ -0,0 +1,28 @@ +import { useState, useCallback } from 'react' +import copyToClipboard from 'copy-to-clipboard' +import { useTimeout } from './useTimeout' + +/** + * useCopyToClipboard + * + * @param {number} [delay=3000] - delay in ms + * + * @return {[boolean, Function]} + */ +const DEFAULT_DELAY = 3000 + +export function useCopyToClipboard (delay = DEFAULT_DELAY) { + const [copied, setCopied] = useState(false) + const startTimeout = useTimeout(() => setCopied(false), delay, false) + + const handleCopy = useCallback( + (text) => { + setCopied(true) + startTimeout() + copyToClipboard(text) + }, + [startTimeout], + ) + + return [copied, handleCopy] +} diff --git a/ui/app/hooks/useTimeout.js b/ui/app/hooks/useTimeout.js new file mode 100644 index 000000000..534c68216 --- /dev/null +++ b/ui/app/hooks/useTimeout.js @@ -0,0 +1,46 @@ +import { useState, useEffect, useRef, useCallback } from 'react' + +/** + * useTimeout + * + * @param {Function} cb - callback function inside setTimeout + * @param {number} delay - delay in ms + * @param {boolean} [immediate] - determines whether the timeout is invoked immediately + * + * @return {Function} + */ +export function useTimeout (cb, delay, immediate = true) { + const saveCb = useRef() + const [timeoutId, setTimeoutId] = useState(null) + + useEffect(() => { + saveCb.current = cb + }, [cb]) + + useEffect(() => { + if (timeoutId !== 'start') { + return + } + + const id = setTimeout(() => { + saveCb.current() + }, delay) + + setTimeoutId(id) + + return () => { + clearTimeout(timeoutId) + } + }, [delay, timeoutId]) + + const startTimeout = useCallback(() => { + clearTimeout(timeoutId) + setTimeoutId('start') + }, [timeoutId]) + + if (immediate) { + startTimeout() + } + + return startTimeout +} diff --git a/ui/app/pages/settings/contact-list-tab/index.scss b/ui/app/pages/settings/contact-list-tab/index.scss index 0f9c673f8..02273a66f 100644 --- a/ui/app/pages/settings/contact-list-tab/index.scss +++ b/ui/app/pages/settings/contact-list-tab/index.scss @@ -106,7 +106,8 @@ height: 20px; padding: 0; background: none; - padding-left: 10px; + padding-left: 0; + margin-left: 10px; } } diff --git a/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js b/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js index 9db3f690f..ce9312c87 100644 --- a/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/view-contact/view-contact.component.js @@ -1,85 +1,102 @@ -import React, { PureComponent } from 'react' +import React from 'react' import PropTypes from 'prop-types' import { Redirect } from 'react-router-dom' import Identicon from '../../../../components/ui/identicon' import Copy from '../../../../components/ui/icon/copy-icon.component' import Button from '../../../../components/ui/button/button.component' -import copyToClipboard from 'copy-to-clipboard' + +import Tooltip from '../../../../components/ui/tooltip-v2' +import { useI18nContext } from '../../../../hooks/useI18nContext' +import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard' function quadSplit (address) { - return '0x ' + address.slice(2).match(/.{1,4}/g).join(' ') + return ( + '0x ' + + address + .slice(2) + .match(/.{1,4}/g) + .join(' ') + ) } -export default class ViewContact extends PureComponent { +function ViewContact ({ + history, + name, + address, + checkSummedAddress, + memo, + editRoute, + listRoute, +}) { + const t = useI18nContext() + const [copied, handleCopy] = useCopyToClipboard() - static contextTypes = { - t: PropTypes.func, + if (!address) { + return } - static propTypes = { - name: PropTypes.string, - address: PropTypes.string, - history: PropTypes.object, - checkSummedAddress: PropTypes.string, - memo: PropTypes.string, - editRoute: PropTypes.string, - listRoute: PropTypes.string.isRequired, - } - - render () { - const { t } = this.context - const { history, name, address, checkSummedAddress, memo, editRoute, listRoute } = this.props - - if (!address) { - return - } - - return ( -
-
-
- -
{ name }
+ return ( +
+
+
+ +
{name}
+
+
+ +
+
+
+ {t('ethereumPublicAddress')}
-
- -
-
-
- { t('ethereumPublicAddress') } +
+
+ {quadSplit(checkSummedAddress)}
-
-
- { quadSplit(checkSummedAddress) } -
+ -
+
-
-
- { t('memo') } -
-
- { memo } -
+
+
+
+ {t('memo')} +
+
+ {memo}
- ) - } +
+ ) } + +ViewContact.propTypes = { + name: PropTypes.string, + address: PropTypes.string, + history: PropTypes.object, + checkSummedAddress: PropTypes.string, + memo: PropTypes.string, + editRoute: PropTypes.string, + listRoute: PropTypes.string.isRequired, +} + +export default React.memo(ViewContact)