Skip to content

Commit 7b9a13f

Browse files
feat: ConnectModal support disabled & banner props (#1434)
* chore: update * chore: update disable demo * test: add test case * docs: update changelog and doc * chore: support custom banner * feat: support design token walletListHeight * fix: demo * docs: update doc * test: update test * feat: add banner prop
1 parent bb8af81 commit 7b9a13f

File tree

14 files changed

+374
-7
lines changed

14 files changed

+374
-7
lines changed

.changeset/giant-squids-open.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ant-design/web3': minor
3+
---
4+
5+
feat: ConnectModal support new prop disabled

.changeset/smooth-boats-rest.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ant-design/web3': minor
3+
---
4+
5+
feat: ConnectModal support banner prop
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { ConfigProvider } from 'antd';
4+
import { describe, expect, it } from 'vitest';
5+
6+
import { ConnectModal } from '../index';
7+
import { walletList } from './mock';
8+
9+
describe('ConnectModal banner functionality', () => {
10+
it('should render with custom banner', async () => {
11+
const testBannerText = 'This is a custom banner for testing';
12+
13+
render(
14+
<ConfigProvider>
15+
<ConnectModal
16+
open
17+
walletList={walletList}
18+
banner={<div className="test-banner">{testBannerText}</div>}
19+
/>
20+
</ConfigProvider>,
21+
);
22+
23+
// Find the banner container
24+
const bannerContainer = document.querySelector('.ant-web3-connect-modal-banner');
25+
26+
// Assert banner container exists
27+
expect(bannerContainer).not.toBeNull();
28+
29+
// Assert our custom banner element exists
30+
const customBanner = document.querySelector('.test-banner');
31+
expect(customBanner).not.toBeNull();
32+
33+
// Assert banner text is correct
34+
expect(customBanner?.textContent).toBe(testBannerText);
35+
});
36+
37+
it('should not render banner when banner prop is not provided', async () => {
38+
render(
39+
<ConfigProvider>
40+
<ConnectModal open walletList={walletList} />
41+
</ConfigProvider>,
42+
);
43+
44+
// Banner container should not exist
45+
const bannerContainer = document.querySelector('.ant-web3-connect-modal-banner');
46+
expect(bannerContainer).toBeNull();
47+
});
48+
49+
it('should render complex banner element with interactive components', async () => {
50+
render(
51+
<ConfigProvider>
52+
<ConnectModal
53+
open
54+
walletList={walletList}
55+
banner={
56+
<div className="complex-banner">
57+
<button className="banner-button">Click me</button>
58+
<span className="banner-text">Banner with button</span>
59+
</div>
60+
}
61+
/>
62+
</ConfigProvider>,
63+
);
64+
65+
// Find the banner container
66+
const bannerContainer = document.querySelector('.ant-web3-connect-modal-banner');
67+
expect(bannerContainer).not.toBeNull();
68+
69+
// Check complex elements exist
70+
const button = document.querySelector('.banner-button');
71+
expect(button).not.toBeNull();
72+
expect(button?.textContent).toBe('Click me');
73+
74+
const text = document.querySelector('.banner-text');
75+
expect(text).not.toBeNull();
76+
expect(text?.textContent).toBe('Banner with button');
77+
});
78+
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React from 'react';
2+
import { metadata_MetaMask, metadata_WalletConnect } from '@ant-design/web3-assets';
3+
import { fireEvent, render, screen } from '@testing-library/react';
4+
import { ConfigProvider } from 'antd';
5+
import { describe, expect, it, vi } from 'vitest';
6+
7+
import { ConnectModal } from '../index';
8+
import type { Wallet } from '../interface';
9+
10+
const walletList: Wallet[] = [
11+
{
12+
...metadata_MetaMask,
13+
hasWalletReady: async () => true,
14+
hasExtensionInstalled: async () => true,
15+
},
16+
{
17+
...metadata_WalletConnect,
18+
getQrCode: () => {
19+
return Promise.resolve({
20+
uri: 'https://example.com',
21+
});
22+
},
23+
},
24+
];
25+
26+
describe('ConnectModal disabled functionality', () => {
27+
it('should render disabled wallets correctly', async () => {
28+
const onWalletSelected = vi.fn();
29+
30+
render(
31+
<ConfigProvider>
32+
<ConnectModal open walletList={walletList} onWalletSelected={onWalletSelected} disabled />
33+
</ConfigProvider>,
34+
);
35+
36+
// Find all wallet items
37+
const walletItems = document.querySelectorAll('.ant-web3-connect-modal-wallet-item');
38+
39+
expect(walletItems.length).toBe(2);
40+
41+
// Verify that the wallet items have the disabled class
42+
walletItems.forEach((item) => {
43+
expect(item.classList.contains('disabled')).toBe(true);
44+
});
45+
46+
// Attempt to click on the first wallet
47+
fireEvent.click(walletItems[0]);
48+
49+
// The onWalletSelected callback should still not have been called
50+
await vi.waitFor(() => {
51+
expect(onWalletSelected).not.toHaveBeenCalled();
52+
});
53+
});
54+
55+
it('should allow wallet selection when not disabled', async () => {
56+
const onWalletSelected = vi.fn();
57+
58+
render(
59+
<ConfigProvider>
60+
<ConnectModal
61+
open
62+
walletList={walletList}
63+
onWalletSelected={onWalletSelected}
64+
disabled={false}
65+
/>
66+
</ConfigProvider>,
67+
);
68+
69+
// Find all wallet items
70+
const walletItems = document.querySelectorAll('.ant-web3-connect-modal-wallet-item');
71+
72+
expect(walletItems.length).toBe(2);
73+
74+
// Verify that the wallet items do not have the disabled class
75+
walletItems.forEach((item) => {
76+
expect(item.classList.contains('disabled')).toBe(false);
77+
});
78+
79+
// Click on the first wallet
80+
fireEvent.click(walletItems[0]);
81+
82+
await vi.waitFor(() => {
83+
// Verify that the onWalletSelected callback was called
84+
expect(onWalletSelected).toHaveBeenCalled();
85+
});
86+
});
87+
});

packages/web3/src/connect-modal/components/ModalPanel.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const ModalPanel: React.FC<ModalPanelProps> = (props) => {
3131
defaultSelectedWallet,
3232
locale,
3333
connecting,
34+
disabled = false,
35+
banner,
3436
} = props;
3537
const intl = useIntl('ConnectModal', locale);
3638
const showQRCoodByDefault = defaultSelectedWallet?.getQrCode;
@@ -118,6 +120,7 @@ const ModalPanel: React.FC<ModalPanelProps> = (props) => {
118120
{(panelRoute === 'init' || !isSimple) && (
119121
<div className={classNames(`${prefixCls}-list-panel`)}>
120122
<div className={`${prefixCls}-header`}>{mergedTitle}</div>
123+
{banner && <div className={`${prefixCls}-banner`}>{banner}</div>}
121124
<div className={`${prefixCls}-list`}>
122125
<div className={`${prefixCls}-list-container`}>
123126
<WalletList
@@ -126,6 +129,7 @@ const ModalPanel: React.FC<ModalPanelProps> = (props) => {
126129
group={group}
127130
groupOrder={groupOrder}
128131
emptyProps={emptyProps}
132+
disabled={disabled}
129133
/>
130134
</div>
131135
<div className={`${prefixCls}-footer-container`}>

packages/web3/src/connect-modal/components/WalletItem.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface WalletItemProps {
1515
onSelect: (wallet: Wallet) => void;
1616
onQrCodeSelect: (wallet: Wallet) => void;
1717
showQrPlaceholder?: boolean;
18+
disabled?: boolean;
1819
}
1920

2021
const WalletItem: React.FC<WalletItemProps> = ({
@@ -24,6 +25,7 @@ const WalletItem: React.FC<WalletItemProps> = ({
2425
onSelect,
2526
onQrCodeSelect,
2627
showQrPlaceholder,
28+
disabled = false,
2729
}) => {
2830
const useUniversalLink: boolean = !!(mobile() && wallet.deeplink);
2931
const [showPluginTag, setShowPluginTag] = useState(!useUniversalLink);
@@ -44,8 +46,9 @@ const WalletItem: React.FC<WalletItemProps> = ({
4446
wallet.key !== undefined
4547
? selectedWallet?.key === wallet.key
4648
: selectedWallet?.name === wallet.name,
49+
disabled,
4750
})}
48-
onClick={() => onSelect(wallet)}
51+
onClick={disabled ? undefined : () => onSelect(wallet)}
4952
>
5053
<div className={`${prefixCls}-content`}>
5154
<WalletIcon wallet={wallet} />
@@ -61,8 +64,11 @@ const WalletItem: React.FC<WalletItemProps> = ({
6164
className={`${prefixCls}-qr-btn`}
6265
onClick={(e) => {
6366
e.stopPropagation();
64-
onQrCodeSelect(wallet);
67+
if (!disabled) {
68+
onQrCodeSelect(wallet);
69+
}
6570
}}
71+
disabled={disabled}
6672
>
6773
<QrcodeOutlined />
6874
</Button>

packages/web3/src/connect-modal/components/WalletList.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import WalletItem from './WalletItem';
1515

1616
export type WalletListProps = Pick<
1717
ConnectModalProps,
18-
'walletList' | 'group' | 'groupOrder' | 'emptyProps'
18+
'walletList' | 'group' | 'groupOrder' | 'emptyProps' | 'disabled'
1919
>;
2020

2121
const WalletList: ForwardRefRenderFunction<ConnectModalActionType, WalletListProps> = (
2222
props,
2323
ref,
2424
) => {
25-
const { walletList = [], group: internalGroup, groupOrder, emptyProps } = props;
25+
const { walletList = [], group: internalGroup, groupOrder, emptyProps, disabled = false } = props;
2626
const { prefixCls, updateSelectedWallet, selectedWallet, localeMessage, updatePanelRoute } =
2727
useContext(connectModalContext);
2828
const dataSource: Record<string, Wallet[]> = useMemo(() => {
@@ -134,6 +134,7 @@ const WalletList: ForwardRefRenderFunction<ConnectModalActionType, WalletListPro
134134
});
135135
}}
136136
showQrPlaceholder={walletList.some((w) => w.getQrCode && w.hasExtensionInstalled)}
137+
disabled={disabled}
137138
/>
138139
)}
139140
/>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React from 'react';
2+
import { ConnectModal, Web3ConfigProvider } from '@ant-design/web3';
3+
import {
4+
metadata_MetaMask,
5+
metadata_TokenPocket,
6+
metadata_Trust,
7+
metadata_WalletConnect,
8+
} from '@ant-design/web3-assets';
9+
import { Alert, Button, Checkbox, Space, theme, Typography } from 'antd';
10+
11+
import type { Wallet } from '../interface';
12+
13+
const walletList: Wallet[] = [
14+
metadata_MetaMask,
15+
metadata_WalletConnect,
16+
metadata_TokenPocket,
17+
metadata_Trust,
18+
];
19+
20+
// Define banner height (in pixels)
21+
const BANNER_HEIGHT = 48;
22+
23+
const App: React.FC = () => {
24+
const [open, setOpen] = React.useState(false);
25+
const [termsAgreed, setTermsAgreed] = React.useState(false);
26+
const { token } = theme.useToken();
27+
28+
return (
29+
<Web3ConfigProvider
30+
theme={{
31+
web3Components: {
32+
ConnectModal: {
33+
walletListHeight: 436 - BANNER_HEIGHT,
34+
},
35+
},
36+
}}
37+
>
38+
<Button type="primary" onClick={() => setOpen(true)}>
39+
Open with terms agreement banner
40+
</Button>
41+
<ConnectModal
42+
open={open}
43+
walletList={walletList}
44+
onCancel={() => {
45+
setOpen(false);
46+
setTermsAgreed(false);
47+
}}
48+
disabled={!termsAgreed}
49+
banner={
50+
<div
51+
style={{
52+
height: BANNER_HEIGHT - 16,
53+
backgroundColor: token.colorPrimaryBg,
54+
margin: '8px 20px',
55+
paddingInline: 8,
56+
display: 'flex',
57+
alignItems: 'center',
58+
borderRadius: token.borderRadius,
59+
borderLeft: `4px solid ${token.colorPrimary}`,
60+
boxShadow: token.boxShadowTertiary,
61+
}}
62+
>
63+
<Checkbox checked={termsAgreed} onChange={(e) => setTermsAgreed(e.target.checked)}>
64+
<Typography.Text
65+
strong
66+
style={{
67+
fontSize: token.fontSizeSM,
68+
color: token.colorTextSecondary,
69+
}}
70+
>
71+
I have read and agree to the{' '}
72+
<Typography.Link href="https://zan.top/" target="_blank" rel="noopener noreferrer">
73+
terms
74+
</Typography.Link>
75+
.
76+
</Typography.Text>
77+
</Checkbox>
78+
</div>
79+
}
80+
/>
81+
</Web3ConfigProvider>
82+
);
83+
};
84+
85+
export default App;

0 commit comments

Comments
 (0)