Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support tooltip #97

Merged
merged 7 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 83 additions & 2 deletions packages/web3/src/connect-button/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,90 @@
import { ConnectButton } from '..';
import { render } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { fireEvent, render } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mockClipboard } from '../../utils/test-utils';

describe('ConnectButton', () => {
let resetMockClipboard: () => void;
beforeEach(() => {
resetMockClipboard = mockClipboard();
});
afterEach(() => {
resetMockClipboard();
});
it('mount correctly', () => {
expect(() => render(<ConnectButton />)).not.toThrow();
});
it('display tooltip', () => {
const { baseElement } = render(
<ConnectButton address="3ea2cfd153b8d8505097b81c87c11f5d05097c18" tooltip={{ open: true }} />,
);
expect(baseElement.querySelector('.ant-tooltip')).not.toBeNull();
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(
<ConnectButton
address="3ea2cfd153b8d8505097b81c87c11f5d05097c18"
tooltip={{ open: true, copyable: false }}
/>,
);
expect(baseElement.querySelector('.ant-tooltip')).not.toBeNull();
expect(baseElement.querySelector('.ant-tooltip-inner')?.textContent?.trim()).toBe(
'0x3ea2cf...097c18',
);
expect(baseElement.querySelector('.anticon-copy')).toBeNull();
});
it('custom title in tooltip', () => {
const { baseElement } = render(
<ConnectButton
address="3ea2cfd153b8d8505097b81c87c11f5d05097c18"
tooltip={{ open: true, title: 'aaaaaabbbbbbcccccc' }}
/>,
);
expect(baseElement.querySelector('.ant-tooltip')).not.toBeNull();
expect(baseElement.querySelector('.ant-tooltip-inner')?.textContent?.trim()).toBe(
'aaaaaabbbbbbcccccc',
);
});
it('should not display tooltip when not custom title and without address in tooltip', () => {
const { baseElement } = render(<ConnectButton tooltip />);
expect(baseElement.querySelector('.ant-tooltip')).toBeNull();
});
it('should copy text after click copy icon', async () => {
const { baseElement } = render(
<ConnectButton address="3ea2cfd153b8d8505097b81c87c11f5d05097c18" tooltip={{ open: true }} />,
);
expect(baseElement.querySelector('.ant-tooltip')).not.toBeNull();
expect(baseElement.querySelector('.ant-tooltip-inner')?.textContent).toBe('0x3ea2cf...097c18');
expect(baseElement.querySelector('.anticon-copy')).not.toBeNull();
fireEvent.click(baseElement.querySelector('.anticon-copy')!);
await vi.waitFor(() => {
expect(baseElement.querySelector('.ant-message')).not.toBeNull();
expect(baseElement.querySelector('.ant-message-notice-content')?.textContent).toBe(
'Address Copied!',
);
expect(navigator.clipboard.readText()).resolves.toBe(
'0x3ea2cfd153b8d8505097b81c87c11f5d05097c18',
);
});
});
it('should copy text after click copy icon in custom title mode', async () => {
const { baseElement } = render(
<ConnectButton tooltip={{ open: true, title: 'aaaaaabbbbbbcccccc' }} />,
);
expect(baseElement.querySelector('.ant-tooltip')).not.toBeNull();
expect(baseElement.querySelector('.ant-tooltip-inner')?.textContent?.trim()).toBe(
'aaaaaabbbbbbcccccc',
);
expect(baseElement.querySelector('.anticon-copy')).not.toBeNull();
fireEvent.click(baseElement.querySelector('.anticon-copy')!);
await vi.waitFor(() => {
expect(baseElement.querySelector('.ant-message')).not.toBeNull();
expect(baseElement.querySelector('.ant-message-notice-content')?.textContent).toBe(
'Address Copied!',
);
expect(navigator.clipboard.readText()).resolves.toBe('aaaaaabbbbbbcccccc');
});
});
});
42 changes: 38 additions & 4 deletions packages/web3/src/connect-button/connect-button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { Button, Dropdown } from 'antd';
import { Address } from '../address';
import type { ConnectButtonProps } from './interface';
import type { ConnectButtonProps, ConnectButtonTooltipProps } from './interface';
import { ConnectButtonTooltip } from './tooltip';

export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
const {
Expand All @@ -12,6 +13,8 @@ export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
chains,
currentChain,
onSwitchChain,
tooltip,
...restProps
} = props;

const buttonProps = {
Expand All @@ -28,11 +31,13 @@ export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
}
},
children: connected ? <Address ellipsis address={address} /> : 'Connect Wallet',
...props,
...restProps,
};

let content = <Button {...buttonProps} />;

if (chains && chains.length > 1) {
return (
content = (
<Dropdown.Button
icon={currentChain?.icon}
menu={{
Expand All @@ -52,7 +57,36 @@ export const ConnectButton: React.FC<ConnectButtonProps> = (props) => {
);
}

return <Button {...buttonProps} />;
const mergedTooltipCopyable: ConnectButtonTooltipProps['copyable'] =
typeof tooltip === 'object' ? tooltip.copyable !== false : !!tooltip;

const customTooltipTitle = typeof tooltip === 'object' && tooltip.title !== undefined;

const tooltipTitle = customTooltipTitle ? (
tooltip.title
) : (
<Address
ellipsis={{
headClip: 8,
tailClip: 6,
}}
copyable={mergedTooltipCopyable}
tooltip={false}
address={address}
/>
);

return tooltip || (!customTooltipTitle && !!address) ? (
<ConnectButtonTooltip
copyable={customTooltipTitle && mergedTooltipCopyable}
title={tooltipTitle}
{...(typeof tooltip === 'object' ? tooltip : {})}
>
{content}
</ConnectButtonTooltip>
) : (
content
);
};

ConnectButton.displayName = 'ConnectButton';
25 changes: 25 additions & 0 deletions packages/web3/src/connect-button/demos/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ConnectButton } from '@ant-design/web3';
import { Space } from 'antd';

const App: React.FC = () => {
return (
<Space>
<ConnectButton address="3ea2cfd153b8d8505097b81c87c11f5d05097c18" tooltip />
<ConnectButton
address="3ea2cfd153b8d8505097b81c87c11f5d05097c18"
tooltip={{
title: 'aaaaaabbbbbbcccccc',
}}
/>
<ConnectButton
address="3ea2cfd153b8d8505097b81c87c11f5d05097c18"
tooltip={{
title: 'aaaaaabbbbbbcccccc',
copyable: false,
}}
/>
</Space>
);
};

export default App;
21 changes: 21 additions & 0 deletions packages/web3/src/connect-button/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,24 @@ A Button for connect chain quickly.
## Simple Usage

<code src="./demos/simple.tsx"></code>

## Show Tooltip

<code src="./demos/tooltip.tsx"></code>

## API

| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| address | Address | `string` | - | - |
| tooltip | Show tooltip when mouse enter address | `boolean \|` [ConnectButtonTooltipProps](#connectbuttontooltipprops) | `true`, will display address by default | - |

### ConnectButtonTooltipProps

```ts
// TooltipProps: https://ant.design/components/tooltip-cn#api
export type ConnectButtonTooltipProps = TooltipProps & {
copyable?: boolean; // Whether to copy
title?: boolean | string | React.ReactNode; // Show content
};
```
21 changes: 21 additions & 0 deletions packages/web3/src/connect-button/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ group: 组件
## 基本使用

<code src="./demos/simple.tsx"></code>

## 展示提示

<code src="./demos/tooltip.tsx"></code>

## API

| 属性 | 描述 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| address | 地址 | `string` | - | - |
| tooltip | 鼠标移入地址时展示提示 | `boolean \|` [ConnectButtonTooltipProps](#connectbuttontooltipprops) | `true`,默认显示 address 信息 | - |

### ConnectButtonTooltipProps

```ts
// TooltipProps: https://ant.design/components/tooltip-cn#api
export type ConnectButtonTooltipProps = TooltipProps & {
copyable?: boolean; // 是否可复制
title?: boolean | string | React.ReactNode; // 展示内容
};
```
2 changes: 1 addition & 1 deletion packages/web3/src/connect-button/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ConnectorTriggerProps } from '@ant-design/web3-common';

export type ConnectButtonTooltipProps = TooltipProps & {
copyable?: boolean;
title?: boolean | string;
title?: boolean | string | React.ReactNode;
};

export type ConnectButtonProps = ButtonProps &
Expand Down
36 changes: 34 additions & 2 deletions packages/web3/src/connect-button/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
import { Tooltip, message } from 'antd';
import { CopyOutlined } from '@ant-design/icons';
import type { ConnectButtonTooltipProps } from './interface';
import type { PropsWithChildren } from 'react';

export const ConnectButtonTooltip: React.FC<ConnectButtonTooltipProps> = () => {
return <div>TODO</div>;
export const ConnectButtonTooltip: React.FC<PropsWithChildren<ConnectButtonTooltipProps>> = ({
title,
copyable,
children,
...restProps
}) => {
const [messageApi, contextHolder] = message.useMessage();
if (!title) return null;
const content = copyable ? (
<>
{title}{' '}
kiner-tang marked this conversation as resolved.
Show resolved Hide resolved
<CopyOutlined
title="Copy Address"
onClick={() => {
navigator.clipboard.writeText(String(title)).then(() => {
messageApi.success('Address Copied!');
});
}}
/>
</>
) : (
title
);
return (
<>
{contextHolder}
<Tooltip title={content} {...restProps}>
{children}
</Tooltip>
</>
);
};
27 changes: 27 additions & 0 deletions packages/web3/src/utils/test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const oriClipboard = window.navigator.clipboard;
// 代理 navigator.clipboard 方法用于测试复制文本到粘贴板的功能
export const mockClipboard = () => {
const clipboard = {
text: '',
writeText: (text: string) => {
clipboard.text = text;
return Promise.resolve();
},
readText: () => {
return Promise.resolve(clipboard.text);
},
};
Object.defineProperty(window, 'navigator', {
value: {
clipboard,
},
});

return () => {
Object.defineProperty(window, 'navigator', {
value: {
clipboard: oriClipboard,
},
});
};
};