diff --git a/packages/extension-ui/package.json b/packages/extension-ui/package.json index ea7145c8..d7b9dc3a 100644 --- a/packages/extension-ui/package.json +++ b/packages/extension-ui/package.json @@ -22,6 +22,7 @@ "@types/bn.js": "^4.11.6", "@types/enzyme": "^3.10.5", "@types/enzyme-adapter-react-16": "^1.0.6", + "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-dom": "^16.9.5", "@types/react-router": "^5.1.4", "@types/react-router-dom": "^5.1.3", diff --git a/packages/extension-ui/src/Popup/CreateAccount/Mnemonic.tsx b/packages/extension-ui/src/Popup/CreateAccount/Mnemonic.tsx index 5f2dc3fc..32279712 100644 --- a/packages/extension-ui/src/Popup/CreateAccount/Mnemonic.tsx +++ b/packages/extension-ui/src/Popup/CreateAccount/Mnemonic.tsx @@ -2,8 +2,9 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Button, ButtonArea, Checkbox, MnemonicSeed, VerticalSpace, Warning } from '../../components'; +import useToast from '../../hooks/useToast'; interface Props { onNextStep: () => void; @@ -23,13 +24,18 @@ const onCopy = (): void => { function Mnemonic ({ onNextStep, seed }: Props): React.ReactElement { const [isMnemonicSaved, setIsMnemonicSaved] = useState(false); + const { show } = useToast(); + const _onCopy = useCallback((): void => { + onCopy(); + show('Copied'); + }, [show]); return ( <> Please write down your wallet’s mnemonic seed and keep it in a safe place.
Mnemonic seed is used to restore your wallet. Keep it carefully in case you lose your assets.
diff --git a/packages/extension-ui/src/Popup/index.tsx b/packages/extension-ui/src/Popup/index.tsx index 1895ba11..eddfc1bd 100644 --- a/packages/extension-ui/src/Popup/index.tsx +++ b/packages/extension-ui/src/Popup/index.tsx @@ -11,6 +11,7 @@ import { setSS58Format } from '@polkadot/util-crypto'; import { Loading } from '../components'; import { AccountContext, ActionContext, AuthorizeReqContext, MediaContext, MetadataReqContext, SigningReqContext } from '../components/contexts'; +import ToastProvider from '../components/Toast/ToastProvider'; import { subscribeAccounts, subscribeAuthorizeRequests, subscribeMetadataRequests, subscribeSigningRequests } from '../messaging'; import Accounts from './Accounts'; import Authorize from './Authorize'; @@ -100,20 +101,22 @@ export default function Popup (): React.ReactElement<{}> { - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/packages/extension-ui/src/components/Address.tsx b/packages/extension-ui/src/components/Address.tsx index ecfa211f..171284dc 100644 --- a/packages/extension-ui/src/components/Address.tsx +++ b/packages/extension-ui/src/components/Address.tsx @@ -5,7 +5,8 @@ import { AccountJson } from '@polkadot/extension-base/background/types'; import { Chain } from '@polkadot/extension-chains/types'; -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import CopyToClipboard from 'react-copy-to-clipboard'; import styled from 'styled-components'; import settings from '@polkadot/ui-settings'; import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; @@ -13,11 +14,13 @@ import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; import { AccountContext } from './contexts'; import Identicon from './Identicon'; import Svg from './Svg'; -import Menu from './Menu'; +import Menu from './Menu'; +import CopyImg from '../assets/copy.svg'; import DetailsImg from '../assets/details.svg'; import useOutsideClick from '../hooks/useOutsideClick'; import useMetadata from '../hooks/useMetadata'; +import useToast from '../hooks/useToast'; interface Props { address?: string | null; @@ -69,6 +72,7 @@ function Address ({ actions, address, children, className, genesisHash, name }: const [showActionsMenu, setShowActionsMenu] = useState(false); const [moveMenuUp, setIsMovedMenu] = useState(false); const actionsRef = useRef(null); + const { show } = useToast(); useOutsideClick(actionsRef, () => (showActionsMenu && setShowActionsMenu(!showActionsMenu))); @@ -91,7 +95,8 @@ function Address ({ actions, address, children, className, genesisHash, name }: }, [showActionsMenu]); const theme = ((chain && chain.icon) || 'polkadot') as 'polkadot'; - const _onClick = (): void => setShowActionsMenu(!showActionsMenu); + const _onClick = useCallback((): void => setShowActionsMenu(!showActionsMenu), [showActionsMenu]); + const _onCopy = useCallback((): void => show('Copied'), [show]); return (
@@ -104,10 +109,18 @@ function Address ({ actions, address, children, className, genesisHash, name }: /> {name || (account && account.name) || ''} - {formatted || ''} - {chain?.genesisHash && ( - {chain.name} - )} + + {formatted || ''} + {chain?.genesisHash && ( + {chain.name} + )} + + + + {actions && ( <> @@ -134,6 +147,22 @@ function Address ({ actions, address, children, className, genesisHash, name }: ); } +const CopyAddress = styled.div` + display: flex; + justify-content: space-between; + + & ${Svg} { + width: 14px; + height: 14px; + margin-right: 10px; + background: ${({ theme }): string => theme.accountDotsIconColor}; + &:hover { + background: ${({ theme }): string => theme.labelColor}; + cursor: pointer; + } + } +`; + const AccountInfoRow = styled.div` display: flex; flex-direction: row; @@ -158,7 +187,7 @@ const Name = styled.div` `; const FullAddress = styled.div` - width: 300px; + width: 270px; overflow: hidden; text-overflow: ellipsis; color: ${({ theme }): string => theme.labelColor}; diff --git a/packages/extension-ui/src/components/Toast/Toast.tsx b/packages/extension-ui/src/components/Toast/Toast.tsx new file mode 100644 index 00000000..8c53a34c --- /dev/null +++ b/packages/extension-ui/src/components/Toast/Toast.tsx @@ -0,0 +1,35 @@ +// Copyright 2019 @polkadot/extension-ui authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import React from 'react'; +import styled from 'styled-components'; + +interface Props { + content: React.ReactChild; + className?: string; +} + +function Toast ({ className, content }: Props): React.ReactElement { + return ( +
+

{content}

+
+ ); +} + +export default styled(Toast)<{visible: boolean}>` + position: fixed; + display: ${({ visible }): string => visible ? 'block' : 'none'}; + height: 40px; + text-align: center; + vertical-align: middle; + line-height: 7px; + top: 460px; + left: calc(50% - 50px); + && { + margin: auto; + border-radius: 25px; + background: ${({ theme }): string => theme.highlightedAreaBackground}; + } +`; diff --git a/packages/extension-ui/src/components/Toast/ToastProvider.tsx b/packages/extension-ui/src/components/Toast/ToastProvider.tsx new file mode 100644 index 00000000..a41c1727 --- /dev/null +++ b/packages/extension-ui/src/components/Toast/ToastProvider.tsx @@ -0,0 +1,41 @@ +// Copyright 2019 @polkadot/extension-ui authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import React, { useCallback, useState } from 'react'; +import { ToastContext } from '..'; +import Toast from './Toast'; + +interface ToastProviderProps { + children?: React.ReactNode; +} + +const TOAST_TIMEOUT = 1500; + +const ToastProvider = ({ children }: ToastProviderProps): React.ReactElement => { + const [content, setContent] = useState(''); + const [visible, setVisible] = useState(false); + + const show = useCallback((message: string): () => void => { + const timerId = setTimeout(() => setVisible(false), TOAST_TIMEOUT); + + setContent(message); + setVisible(true); + + return (): void => clearTimeout(timerId); + }, []); + + return ( + + {children} + + + ); +}; + +export default ToastProvider; + +ToastProvider.displayName = 'Toast'; diff --git a/packages/extension-ui/src/components/contexts.tsx b/packages/extension-ui/src/components/contexts.tsx index 13a77653..428f00ed 100644 --- a/packages/extension-ui/src/components/contexts.tsx +++ b/packages/extension-ui/src/components/contexts.tsx @@ -17,6 +17,7 @@ const MediaContext = React.createContext(false); const MetadataReqContext = React.createContext([]); const SigningReqContext = React.createContext([]); const ThemeSwitchContext = React.createContext<(theme: AvailableThemes) => void>(noop); +const ToastContext = React.createContext<({show: (message: string) => void})>({ show: noop }); export { AccountContext, @@ -25,5 +26,6 @@ export { MediaContext, MetadataReqContext, SigningReqContext, - ThemeSwitchContext + ThemeSwitchContext, + ToastContext }; diff --git a/packages/extension-ui/src/hooks/useToast.tsx b/packages/extension-ui/src/hooks/useToast.tsx new file mode 100644 index 00000000..d82541c2 --- /dev/null +++ b/packages/extension-ui/src/hooks/useToast.tsx @@ -0,0 +1,10 @@ +// Copyright 2019 @polkadot/extension-ui authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { useContext } from 'react'; +import { ToastContext } from '../components/contexts'; + +export default function useToast (): {show: (message: string) => void} { + return useContext(ToastContext); +} diff --git a/yarn.lock b/yarn.lock index 900a0dc7..f39c045f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2917,6 +2917,7 @@ __metadata: "@types/bn.js": ^4.11.6 "@types/enzyme": ^3.10.5 "@types/enzyme-adapter-react-16": ^1.0.6 + "@types/react-copy-to-clipboard": ^4.3.0 "@types/react-dom": ^16.9.5 "@types/react-router": ^5.1.4 "@types/react-router-dom": ^5.1.3 @@ -3503,6 +3504,15 @@ __metadata: languageName: node linkType: hard +"@types/react-copy-to-clipboard@npm:^4.3.0": + version: 4.3.0 + resolution: "@types/react-copy-to-clipboard@npm:4.3.0" + dependencies: + "@types/react": "*" + checksum: 2/09816ba8a92fefbcc62abd4eea27881804c3ad0b70b52dcaf6717087ed95533d2afdbda3c78d5e3c5c41ebce13b19ca99aa710f3db258db393c2eada59d19c83 + languageName: node + linkType: hard + "@types/react-dom@npm:^16.9.5": version: 16.9.6 resolution: "@types/react-dom@npm:16.9.6"