diff --git a/packages/web3/src/address/index.tsx b/packages/web3/src/address/index.tsx index 7495bd22c..7a66d2874 100644 --- a/packages/web3/src/address/index.tsx +++ b/packages/web3/src/address/index.tsx @@ -1,7 +1,10 @@ import { CopyOutlined } from '@ant-design/icons'; import type { TooltipProps } from 'antd'; -import { Space, Tooltip, message } from 'antd'; -import React from 'react'; +import { Space, Tooltip, message, ConfigProvider } from 'antd'; +import React, { useContext } from 'react'; +import { useStyle } from './style'; +import classNames from 'classnames'; +import { writeCopyText } from '../utils'; export interface AddressProps { ellipsis?: @@ -17,6 +20,9 @@ export interface AddressProps { export const Address: React.FC = (props) => { const { ellipsis, address, copyable, tooltip } = props; + const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); + const prefixCls = getPrefixCls('web3-address'); + const { wrapSSR, hashId } = useStyle(prefixCls); const isEllipsis = !!ellipsis; const { headClip = 6, tailClip = 4 } = @@ -34,8 +40,8 @@ export const Address: React.FC = (props) => { const filledAddress = address.startsWith('0x') ? address : `0x${address}`; const displayTooltip = tooltip === undefined || tooltip === true ? filledAddress : tooltip; - return ( - + return wrapSSR( + {isEllipsis ? `${filledAddress.slice(0, headClip)}...${filledAddress.slice(-tailClip)}` @@ -45,12 +51,12 @@ export const Address: React.FC = (props) => { { - navigator.clipboard.writeText(filledAddress).then(() => { + writeCopyText(filledAddress).then(() => { message.success('Address Copied!'); }); }} /> )} - + , ); }; diff --git a/packages/web3/src/address/style/index.ts b/packages/web3/src/address/style/index.ts new file mode 100644 index 000000000..51241e9e9 --- /dev/null +++ b/packages/web3/src/address/style/index.ts @@ -0,0 +1,28 @@ +import type React from 'react'; +import { + useStyle as useAntdStyle, + type GenerateStyle, + type Web3AliasToken, +} from '../../theme/useStyle'; + +export interface AddressToken extends Web3AliasToken { + componentCls: string; +} + +const genAddressStyle: GenerateStyle = (token) => { + return { + [token.componentCls]: { + wordBreak: 'break-all', + }, + }; +}; + +export function useStyle(prefixCls: string) { + return useAntdStyle('Address', (token) => { + const proListToken: AddressToken = { + ...token, + componentCls: `.${prefixCls}`, + }; + return [genAddressStyle(proListToken)]; + }); +} diff --git a/packages/web3/src/connect-button/__tests__/__snapshots__/profile-modal.test.tsx.snap b/packages/web3/src/connect-button/__tests__/__snapshots__/profile-modal.test.tsx.snap new file mode 100644 index 000000000..edd5509c5 --- /dev/null +++ b/packages/web3/src/connect-button/__tests__/__snapshots__/profile-modal.test.tsx.snap @@ -0,0 +1,150 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ProfileModal > match snapshot 1`] = ` + +
+
+
+
+
+ + +`; diff --git a/packages/web3/src/connect-button/__tests__/index.test.tsx b/packages/web3/src/connect-button/__tests__/index.test.tsx index d193f48f9..18b6c2bb8 100644 --- a/packages/web3/src/connect-button/__tests__/index.test.tsx +++ b/packages/web3/src/connect-button/__tests__/index.test.tsx @@ -2,6 +2,7 @@ import { ConnectButton } from '..'; import { fireEvent, render } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { mockClipboard } from '../../utils/test-utils'; +import { readCopyText } from '../../utils'; describe('ConnectButton', () => { let resetMockClipboard: () => void; @@ -62,6 +63,7 @@ describe('ConnectButton', () => { expect(baseElement.querySelector('.ant-tooltip-inner')?.textContent).toBe('0x3ea2cf...097c18'); expect(baseElement.querySelector('.anticon-copy')).not.toBeNull(); }); + it('disabled copyable in tooltip', () => { const { baseElement } = render( { ); expect(baseElement.querySelector('.anticon-copy')).toBeNull(); }); + it('custom title in tooltip', () => { const { baseElement } = render( { 'aaaaaabbbbbbcccccc', ); }); + it('should not display tooltip when not custom title and without address in tooltip', () => { const { baseElement } = render(); expect(baseElement.querySelector('.ant-tooltip')).toBeNull(); }); + it('should copy text after click copy icon', async () => { const { baseElement } = render( , @@ -104,11 +109,10 @@ describe('ConnectButton', () => { expect(baseElement.querySelector('.ant-message-notice-content')?.textContent).toBe( 'Address Copied!', ); - expect(navigator.clipboard.readText()).resolves.toBe( - '0x3ea2cfd153b8d8505097b81c87c11f5d05097c18', - ); + expect(readCopyText()).resolves.toBe('0x3ea2cfd153b8d8505097b81c87c11f5d05097c18'); }); }); + it('should copy text after click copy icon in custom title mode', async () => { const { baseElement } = render( , @@ -124,7 +128,7 @@ describe('ConnectButton', () => { expect(baseElement.querySelector('.ant-message-notice-content')?.textContent).toBe( 'Address Copied!', ); - expect(navigator.clipboard.readText()).resolves.toBe('aaaaaabbbbbbcccccc'); + expect(readCopyText()).resolves.toBe('aaaaaabbbbbbcccccc'); }); }); }); diff --git a/packages/web3/src/connect-button/__tests__/profile-modal.test.tsx b/packages/web3/src/connect-button/__tests__/profile-modal.test.tsx new file mode 100644 index 000000000..9c81a3fdd --- /dev/null +++ b/packages/web3/src/connect-button/__tests__/profile-modal.test.tsx @@ -0,0 +1,68 @@ +import { render, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { mockClipboard } from '../../utils/test-utils'; +import { ProfileModal } from '../profile-modal'; +import { ConnectButton } from '..'; +import { readCopyText } from '../../utils'; + +describe('ProfileModal', () => { + let resetMockClipboard: () => void; + beforeEach(() => { + resetMockClipboard = mockClipboard(); + }); + afterEach(() => { + resetMockClipboard(); + }); + + it('match snapshot', () => { + const App = () => ( + + ); + const { baseElement } = render(); + expect(baseElement).toMatchSnapshot(); + }); + + it('open profile modal in ConnectButton', async () => { + const App = () => ( + + ); + const { baseElement } = render(); + expect(baseElement.querySelector('.ant-web3-connect-button-text')).not.toBeNull(); + + fireEvent.click(baseElement.querySelector('.ant-web3-connect-button-text')!); + + await vi.waitFor(() => { + expect( + baseElement.querySelector('.ant-web3-connect-button-profile-modal .ant-web3-address') + ?.textContent, + ).toBe('0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B'); + }); + }); + + it('Disconnect & Copy Address Button', () => { + const disconnectTestFn = vi.fn(); + const App = () => ( + + ); + const { baseElement } = render(); + const btns = baseElement.querySelectorAll('.ant-web3-connect-button-profile-modal .ant-btn'); + expect(btns.length).toBe(2); + fireEvent.click(btns[1]); + expect(disconnectTestFn).toBeCalled(); + fireEvent.click(btns[0]); + expect(readCopyText()).resolves.toBe('0x21CDf0974d53a6e96eF05d7B324a9803735fFd3B'); + }); +}); diff --git a/packages/web3/src/connect-button/connect-button.tsx b/packages/web3/src/connect-button/connect-button.tsx index 0b0da1243..fdf755997 100644 --- a/packages/web3/src/connect-button/connect-button.tsx +++ b/packages/web3/src/connect-button/connect-button.tsx @@ -1,10 +1,11 @@ -import React, { useContext } from 'react'; +import React, { useContext, useState } from 'react'; import { Button, ConfigProvider } from 'antd'; import classNames from 'classnames'; import { Address } from '../address'; import type { ConnectButtonProps, ConnectButtonTooltipProps } from './interface'; import { ConnectButtonTooltip } from './tooltip'; import { ChainSelect } from './chain-select'; +import { ProfileModal } from './profile-modal'; import { useStyle } from './style'; export const ConnectButton: React.FC = (props) => { @@ -18,10 +19,12 @@ export const ConnectButton: React.FC = (props) => { tooltip, currentChain, name, + avatar, ...restProps } = props; const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('web3-connect-button'); + const [profileOpen, setProfileOpen] = useState(false); const { wrapSSR, hashId } = useStyle(prefixCls); let buttonText: React.ReactNode = 'Connect Wallet'; if (connected) { @@ -53,11 +56,29 @@ export const ConnectButton: React.FC = (props) => { const content = ( + ) : null} + + + + + ); +}; +ProfileModal.displayName = 'ProfileModal'; diff --git a/packages/web3/src/connect-button/tooltip.tsx b/packages/web3/src/connect-button/tooltip.tsx index 926bbfa0a..74382deaa 100644 --- a/packages/web3/src/connect-button/tooltip.tsx +++ b/packages/web3/src/connect-button/tooltip.tsx @@ -2,6 +2,7 @@ import { Tooltip, message } from 'antd'; import { CopyOutlined } from '@ant-design/icons'; import type { ConnectButtonTooltipProps } from './interface'; import type { PropsWithChildren } from 'react'; +import { writeCopyText } from '../utils'; export const ConnectButtonTooltip: React.FC> = ({ title, @@ -17,7 +18,7 @@ export const ConnectButtonTooltip: React.FC { - navigator.clipboard.writeText(String(title)).then(() => { + writeCopyText(String(title)).then(() => { messageApi.success('Address Copied!'); }); }} diff --git a/packages/web3/src/utils/browser.ts b/packages/web3/src/utils/browser.ts new file mode 100644 index 000000000..809a5d11d --- /dev/null +++ b/packages/web3/src/utils/browser.ts @@ -0,0 +1,22 @@ +export const getPlatform = () => { + const userAgent = navigator.userAgent.toLowerCase(); + if (userAgent.includes('chrome')) { + return 'Chrome'; + } else if (userAgent.includes('firefox')) { + return 'Firefox'; + } else if (userAgent.includes('edge')) { + return 'Edge'; + } else if (userAgent.includes('safari')) { + return 'Safari'; + } else { + return 'Other'; + } +}; + +export const writeCopyText = (text: string): Promise => { + return navigator.clipboard.writeText(text); +}; + +export const readCopyText = (): Promise => { + return navigator.clipboard.readText(); +}; diff --git a/packages/web3/src/utils/index.ts b/packages/web3/src/utils/index.ts index 892c80ed1..6346d4753 100644 --- a/packages/web3/src/utils/index.ts +++ b/packages/web3/src/utils/index.ts @@ -1,15 +1 @@ -export const getPlatform = () => { - const userAgent = navigator.userAgent.toLowerCase(); - - if (userAgent.includes('chrome')) { - return 'Chrome'; - } else if (userAgent.includes('firefox')) { - return 'Firefox'; - } else if (userAgent.includes('edge')) { - return 'Edge'; - } else if (userAgent.includes('safari')) { - return 'Safari'; - } else { - return 'Other'; - } -}; +export * from './browser';