Skip to content

Commit

Permalink
Add Toast component
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan-Mahda committed Dec 11, 2024
1 parent 3373c0f commit 4b237ac
Show file tree
Hide file tree
Showing 19 changed files with 200 additions and 15 deletions.
3 changes: 3 additions & 0 deletions packages/browser-wallet/src/assets/svgX/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { Outlet } from 'react-router-dom';
import Toast from '@popup/shared/Toast/Toast';
import Toast from '@popup/popupX/shared/Toast';
import clsx from 'clsx';
import { Connection } from '@popup/popupX/page-layouts/MainLayout/Header/components';
import FullscreenPromptLayout from '@popup/popupX/page-layouts/FullscreenPromptLayout';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
import clsx from 'clsx';
import Text from '@popup/popupX/shared/Text';
import { AccountInfoType } from '@concordium/web-sdk';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import { useCopyAddress } from '@popup/popupX/shared/utils/hooks';

function shortNumber(number: number | string): string {
return number.toLocaleString('en-US', {
Expand Down Expand Up @@ -77,6 +77,7 @@ export default function AccountSelector({ showAccountSelector, onUpdateSelectedA
const { t } = useTranslation('x', { keyPrefix: 'header.accountSelector' });
const credentialsLoading = useAtomValue(credentialsAtomWithLoading);
const [selectedAccount, setSelectedAccount] = useAtom(selectedAccountAtom);
const copyAddressToClipboard = useCopyAddress();
const [search, setSearch] = useState('');
const [ascSort, setAscSort] = useState(true);
const credentials = credentialsLoading.value ?? [];
Expand All @@ -97,7 +98,7 @@ export default function AccountSelector({ showAccountSelector, onUpdateSelectedA

const copyAddress = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, address: string) => {
event.stopPropagation();
copyToClipboard(address);
copyAddressToClipboard(address);
};

if (!showAccountSelector) return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import clsx from 'clsx';
import Header from '@popup/popupX/page-layouts/MainLayout/Header';
import Toast from '@popup/popupX/shared/Toast';
import { AccountButton, NavButton } from '@popup/popupX/page-layouts/MainLayout/Header/components';
import { relativeRoutes, RoutePath } from '@popup/popupX/constants/routes';

Expand Down Expand Up @@ -54,6 +55,7 @@ export default function MainLayout() {
</div>
<Outlet />
</main>
<Toast />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import Card from '@popup/popupX/shared/Card';
import Text from '@popup/popupX/shared/Text';
import { generatePath, useNavigate } from 'react-router-dom';
import { absoluteRoutes } from '@popup/popupX/constants/routes';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import { useAtomValue } from 'jotai';
import { credentialsAtom } from '@popup/store/account';
import { WalletCredential } from '@shared/storage/types';
import { displaySplitAddress, useIdentityName, useWritableSelectedAccount } from '@popup/shared/utils/account-helpers';
import { useAccountInfo } from '@popup/shared/AccountInfoListenerContext';
import { displayAsCcd } from 'wallet-common-helpers';
import useEditableValue from '@popup/popupX/shared/EditableValue';
import { useCopyAddress } from '@popup/popupX/shared/utils/hooks';

function compareAsc(left: WalletCredential, right: WalletCredential): number {
if (left.credName === '' && right.credName !== '') {
Expand All @@ -43,6 +43,7 @@ type AccountListItemProps = {
function AccountListItem({ credential }: AccountListItemProps) {
const { t } = useTranslation('x', { keyPrefix: 'accounts' });
const nav = useNavigate();
const copyAddressToClipboard = useCopyAddress();
const navToPrivateKey = () =>
nav(generatePath(absoluteRoutes.settings.accounts.privateKey.path, { account: credential.address }));
const navToConnectedSites = () =>
Expand Down Expand Up @@ -82,7 +83,7 @@ function AccountListItem({ credential }: AccountListItemProps) {
</Card.Row>
<Card.Row>
<Text.Capture className="wrap-anywhere">{address}</Text.Capture>
<Button.Icon className="transparent" onClick={() => copyToClipboard(address)} icon={<Copy />} />
<Button.Icon className="transparent" onClick={() => copyAddressToClipboard(address)} icon={<Copy />} />
</Card.Row>
<Card.Row>
<Text.MainRegular>{t('ccdBalance')}</Text.MainRegular>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { withSelectedCredential } from '@popup/popupX/shared/utils/hoc';
import { useTranslation } from 'react-i18next';
import Button from '@popup/popupX/shared/Button';
import Copy from '@assets/svgX/copy.svg';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import Card from '@popup/popupX/shared/Card';
import { useCopyToClipboard } from '@popup/popupX/shared/utils/hooks';

type Params = {
contractIndex: string;
Expand All @@ -24,6 +24,7 @@ function useSelectedToken(credential: WalletCredential) {
function NftRaw({ credential }: { credential: WalletCredential }) {
const { t } = useTranslation('x', { keyPrefix: 'nft' });
const token = useSelectedToken(credential);
const copyToClipboard = useCopyToClipboard();
const metadata = token?.metadata || {};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { networkConfigurationAtom } from '@popup/store/settings';
import { saveData } from '@popup/shared/utils/file-helpers';
import { NetworkConfiguration } from '@shared/storage/types';
import { getNet } from '@shared/utils/network-helpers';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import { Navigate, useParams } from 'react-router-dom';
import { withPasswordProtected } from '@popup/popupX/shared/utils/hoc';
import { useCopyToClipboard } from '@popup/popupX/shared/utils/hooks';

type CredentialKeys = {
threshold: number;
Expand Down Expand Up @@ -100,6 +100,7 @@ type Props = {
function PrivateKey({ address }: Props) {
const { t } = useTranslation('x', { keyPrefix: 'privateKey' });
const { privateKey, handleExport } = usePrivateKeyData(address);
const copyToClipboard = useCopyToClipboard();

return (
<Page className="account-private-key-x">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import Card from '@popup/popupX/shared/Card';
import { useAtomValue } from 'jotai';
import { selectedCredentialAtom } from '@popup/store/account';
import { DisplayAsQR } from 'wallet-common-helpers';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import { displayNameAndSplitAddress } from '@popup/shared/utils/account-helpers';
import { useCopyAddress } from '@popup/popupX/shared/utils/hooks';

export default function ReceiveFunds() {
const { t } = useTranslation('x', { keyPrefix: 'receiveFunds' });
const credential = useAtomValue(selectedCredentialAtom);
const copyAddressToClipboard = useCopyAddress();

if (credential === undefined) {
return null;
Expand All @@ -27,7 +28,10 @@ export default function ReceiveFunds() {
<Card type="gradient">
<DisplayAsQR value={credential.address} bgColor="transparent" className="qr-card" />
<Text.Main>{credential.address}</Text.Main>
<Button.Secondary label={t('copyAddress')} onClick={() => copyToClipboard(credential.address)} />
<Button.Secondary
label={t('copyAddress')}
onClick={() => copyAddressToClipboard(credential.address)}
/>
</Card>
</Page.Main>
</Page>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import { useAsyncMemo } from 'wallet-common-helpers';
import { decrypt } from '@shared/utils/crypto';
import { useAtomValue } from 'jotai';
import { encryptedSeedPhraseAtom, sessionPasscodeAtom } from '@popup/store/settings';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import { withPasswordProtected } from '@popup/popupX/shared/utils/hoc';
import { useCopyToClipboard } from '@popup/popupX/shared/utils/hooks';

function SeedPhrase() {
const { t } = useTranslation('x', { keyPrefix: 'seedPhrase' });
const passcode = useAtomValue(sessionPasscodeAtom);
const encryptedSeed = useAtomValue(encryptedSeedPhraseAtom);
const copyToClipboard = useCopyToClipboard();

const seedPhrase = useAsyncMemo(
async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { withSelectedCredential } from '@popup/popupX/shared/utils/hoc';
import { useTranslation } from 'react-i18next';
import Button from '@popup/popupX/shared/Button';
import Copy from '@assets/svgX/copy.svg';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import Card from '@popup/popupX/shared/Card';
import { useCopyToClipboard } from '@popup/popupX/shared/utils/hooks';

type Params = {
contractIndex: string;
Expand All @@ -23,6 +23,7 @@ function useSelectedToken(credential: WalletCredential) {
function TokenRaw({ credential }: { credential: WalletCredential }) {
const { t } = useTranslation('x', { keyPrefix: 'tokenDetails' });
const token = useSelectedToken(credential);
const copyToClipboard = useCopyToClipboard();
const metadata = token?.metadata || {};

return (
Expand Down
16 changes: 16 additions & 0 deletions packages/browser-wallet/src/popup/popupX/shared/Toast/Messages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import Check from '@assets/svgX/check.svg';
import { displaySplitAddressShort } from '@popup/shared/utils/account-helpers';
import Text from '@popup/popupX/shared/Text';

export function CopyAddress({ address, message }: { address: string; message: string }) {
return (
<div className="copy-address-x">
<Check />
<div className="copy-message">
<Text.Label>{message}</Text.Label>
<Text.Capture>{displaySplitAddressShort(address)}</Text.Capture>
</div>
</div>
);
}
74 changes: 74 additions & 0 deletions packages/browser-wallet/src/popup/popupX/shared/Toast/Toast.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.toast-x {
visibility: hidden;
width: rem(175px);
left: 0;
right: 0;
margin: 0 auto;
background-color: $color-white;
color: $color-black;
text-align: center;
border-radius: rem(16px);
padding: rem(10px) rem(15px);
position: absolute;
z-index: 2;
bottom: rem(16px);
backdrop-filter: blur(5px);
box-shadow: 0 -6px 15.3px 0 rgba($color-black, 0.25);

&__show {
visibility: visible;
animation: fadein-toast 0.5s;
}

&__fadeout {
visibility: visible;
animation: fadeout-toast 0.5s;
}

@keyframes fadein-toast {
from {
bottom: 0;
opacity: 0;
}

to {
bottom: rem(16px);
opacity: 1;
}
}

@keyframes fadeout-toast {
from {
bottom: rem(16px);
opacity: 1;
}

to {
bottom: rem(16px);
opacity: 0;
}
}
}

.copy-address-x {
display: flex;
flex-direction: row;
align-items: center;

.copy-message {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: rem(4px);
margin-left: rem(10px);

.label__main {
color: $color-black;
white-space: nowrap;
}

.capture__main_small {
color: $color-black;
}
}
}
49 changes: 49 additions & 0 deletions packages/browser-wallet/src/popup/popupX/shared/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { toastsAtom } from '@popup/state';
import clsx from 'clsx';
import { useAtom } from 'jotai';
import React, { ReactNode, useEffect, useState } from 'react';
import { noOp } from 'wallet-common-helpers';

// The fadeout timeout has to be aligned with the animation in the corresponding CSS. This is
// done by manually tweaking the values until the animation looks decent. Currently the value
// is 100ms less than the corresponding value in CSS.
const fadeoutTimeoutMs = 400;

// Determines how long we display the toast.
const toastTimeoutMs = 5000;

export default function Toast() {
const [toasts, setToasts] = useAtom(toastsAtom);
const [toastText, setToastText] = useState<string | ReactNode>();
const [fadeout, setFadeout] = useState<boolean>(false);
useEffect(() => {
if (!toastText && toasts.length > 0) {
const [nextToast, ...remainderToasts] = toasts;
setToastText(nextToast);
setToasts(remainderToasts);
}
}, [toasts, toastText]);

useEffect(() => {
if (toastText) {
const fadeoutTimer = setTimeout(() => {
setFadeout(true);
setTimeout(() => setFadeout(false), fadeoutTimeoutMs);
}, toastTimeoutMs - fadeoutTimeoutMs);

const timeout = setTimeout(() => {
setToastText(undefined);
}, toastTimeoutMs);

return () => {
clearTimeout(timeout);
clearTimeout(fadeoutTimer);
};
}
return noOp;
}, [toastText]);

return (
<div className={clsx('toast-x', toastText && 'toast-x__show', fadeout && 'toast-x__fadeout')}>{toastText}</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Toast';
4 changes: 4 additions & 0 deletions packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ const t = {
reject: 'Reject',
error: 'Error',
},
messages: {
addressCopied: 'Address copied',
copied: 'Copied',
},
};

export default t;
24 changes: 24 additions & 0 deletions packages/browser-wallet/src/popup/popupX/shared/utils/hooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSetAtom } from 'jotai';
import { addToastAtom } from '@popup/state';
import { copyToClipboard } from '@popup/popupX/shared/utils/helpers';
import { CopyAddress } from '@popup/popupX/shared/Toast/Messages';

export function useCopyAddress() {
const { t } = useTranslation('x', { keyPrefix: 'sharedX.messages' });
const addToast = useSetAtom(addToastAtom);

return (address: string) => {
copyToClipboard(address).then(() => addToast(<CopyAddress address={address} message={t('addressCopied')} />));
};
}

export function useCopyToClipboard() {
const { t } = useTranslation('x', { keyPrefix: 'sharedX.messages' });
const addToast = useSetAtom(addToastAtom);

return (text: string) => {
copyToClipboard(text).then(() => addToast(t('copied')));
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
@import '../shared/Text/Text';
@import '../shared/Tooltip/Tooltip';
@import '../shared/Loader/Loader';
@import '../shared/Toast/Toast';
@import '../shared/IdCard/IdCard';
@import '../shared/PasswordProtect/PasswordProtect';
@import '../shared/Web3IdCard/Web3IdCard';
Expand Down
4 changes: 2 additions & 2 deletions packages/browser-wallet/src/popup/shared/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { toastsAtom } from '@popup/state';
import clsx from 'clsx';
import { useAtom } from 'jotai';
import React, { useEffect, useState } from 'react';
import React, { ReactNode, useEffect, useState } from 'react';
import { noOp } from 'wallet-common-helpers';

// The fadeout timeout has to be aligned with the animation in the corresponding CSS. This is
Expand All @@ -14,7 +14,7 @@ const toastTimeoutMs = 5000;

export default function Toast() {
const [toasts, setToasts] = useAtom(toastsAtom);
const [toastText, setToastText] = useState<string>();
const [toastText, setToastText] = useState<string | ReactNode>();
const [fadeout, setFadeout] = useState<boolean>(false);

useEffect(() => {
Expand Down
Loading

0 comments on commit 4b237ac

Please sign in to comment.