Skip to content

Commit 1cc8111

Browse files
committed
SD-92: Refactor modal
1 parent 3ab95dc commit 1cc8111

File tree

16 files changed

+576
-243
lines changed

16 files changed

+576
-243
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@cardinal-cryptography/shielder-sdk": "^0.2.0-beta.8",
2222
"@cardinal-cryptography/shielder-sdk-crypto": "^0.2.0-beta.4",
2323
"@cardinal-cryptography/shielder-sdk-crypto-wasm": "^0.2.0-beta.4",
24-
"@faker-js/faker": "^9.7.0",
24+
"@faker-js/faker": "^9.8.0",
2525
"@number-flow/react": "^0.5.7",
2626
"@radix-ui/react-dialog": "^1.1.6",
2727
"@radix-ui/react-popover": "^1.1.13",

src/domains/chains/components/ConnectModal.tsx

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,78 @@
1-
import { ComponentProps, useEffect, useRef } from 'react';
21
import styled from 'styled-components';
32

43
import { useWallet } from 'src/domains/chains/components/WalletProvider';
54
import Button from 'src/domains/misc/components/Button';
65
import CIcon from 'src/domains/misc/components/CIcon';
7-
import Modal from 'src/domains/misc/components/Modal';
8-
import { ModalRef } from 'src/domains/misc/components/Modal';
6+
import Modal, { useModalControls } from 'src/domains/misc/components/ModalNew';
97
import CheckedContainer from 'src/domains/misc/components/PatternContainer';
10-
import { PRIVACY_POLICY_LINK, TERMS_OF_CONDITIONS_LINK, TERMS_OF_SERVICE_LINK } from 'src/domains/misc/consts/consts.ts';
8+
import { PRIVACY_POLICY_LINK, TERMS_OF_CONDITIONS_LINK, TERMS_OF_SERVICE_LINK } from 'src/domains/misc/consts/consts';
119
import { typography } from 'src/domains/styling/utils/tokens';
1210
import vars from 'src/domains/styling/utils/vars';
1311

14-
type Props = Pick<ComponentProps<typeof Modal>, 'triggerElement'>;
12+
const ConnectModal = () => {
13+
const { close } = useModalControls();
14+
const { openModal } = useWallet();
1515

16-
const ConnectModal = (props: Props) => {
17-
const { openModal, isConnected } = useWallet();
18-
const ref = useRef<ModalRef | null>(null);
19-
20-
useEffect(() => {
21-
if(!ref.current || !isConnected) return;
22-
23-
ref.current.close();
24-
}, [isConnected]);
25-
26-
const handleAgreeClick = async (close: () => Promise<unknown>) => {
27-
await close();
16+
const handleAgreeClick = () => {
2817
void openModal({ view: 'Connect' });
18+
close();
2919
};
3020

3121
return (
32-
<StyledModal ref={ref} {...props} title="Welcome">
33-
{close => (
34-
<Content>
35-
<CheckedContainer>
36-
<Branding>
37-
<LogoContainer>
38-
<CIcon icon="Common" size={26} color="#406EB2" />
39-
</LogoContainer>
40-
<p>Common Web App</p>
41-
</Branding>
42-
</CheckedContainer>
43-
<Title>
44-
We value your privacy!
45-
</Title>
46-
<Text>
47-
By clicking the button "Agree and continue" below, you confirm that you have read and that you accept our:
48-
</Text>
49-
<LinksWrapper>
50-
<Link href={TERMS_OF_SERVICE_LINK} target="_blank" rel="noopener noreferrer">
51-
<CIcon icon="DocumentText" size={20} />
52-
<p>Terms of service</p>
53-
</Link>
54-
<Link href={TERMS_OF_CONDITIONS_LINK} target="_blank" rel="noopener noreferrer">
55-
<CIcon icon="DocumentText" size={20} />
56-
<p>Terms and Conditions</p>
57-
</Link>
58-
<Link href={PRIVACY_POLICY_LINK} target="_blank" rel="noopener noreferrer">
59-
<CIcon icon="DocumentText" size={20} />
60-
<p>Privacy policy</p>
61-
</Link>
62-
</LinksWrapper>
63-
<Buttons>
64-
<Button variant="primary"
65-
onClick={() => {
66-
void handleAgreeClick(close);
67-
}}
68-
>
69-
Agree and continue
70-
</Button>
71-
</Buttons>
72-
</Content>
73-
)}
74-
</StyledModal>
22+
<Modal
23+
config={
24+
{
25+
title: 'Welcome',
26+
content: (
27+
<Content>
28+
<CheckedContainer>
29+
<Branding>
30+
<LogoContainer>
31+
<CIcon icon="Common" size={26} color="#406EB2" />
32+
</LogoContainer>
33+
<p>Common Web App</p>
34+
</Branding>
35+
</CheckedContainer>
36+
<Title>
37+
We value your privacy!
38+
</Title>
39+
<Text>
40+
By clicking the button "Agree and continue" below,
41+
you confirm that you have read and that you accept our:
42+
</Text>
43+
<LinksWrapper>
44+
<Link href={TERMS_OF_SERVICE_LINK} target="_blank" rel="noopener noreferrer">
45+
<CIcon icon="DocumentText" size={20} />
46+
<p>Terms of service</p>
47+
</Link>
48+
<Link href={TERMS_OF_CONDITIONS_LINK} target="_blank" rel="noopener noreferrer">
49+
<CIcon icon="DocumentText" size={20} />
50+
<p>Terms and Conditions</p>
51+
</Link>
52+
<Link href={PRIVACY_POLICY_LINK} target="_blank" rel="noopener noreferrer">
53+
<CIcon icon="DocumentText" size={20} />
54+
<p>Privacy policy</p>
55+
</Link>
56+
</LinksWrapper>
57+
<Buttons>
58+
<Button variant="primary"
59+
onClick={() => {
60+
void handleAgreeClick();
61+
}}
62+
>
63+
Agree and continue
64+
</Button>
65+
</Buttons>
66+
</Content>
67+
),
68+
}
69+
}
70+
/>
7571
);
7672
};
7773

7874
export default ConnectModal;
7975

80-
const StyledModal = styled(Modal)`
81-
width: min(434px, 100vw);
82-
`;
83-
8476
const Content = styled.div`
8577
display: flex;
8678
flex-direction: column;

src/domains/misc/components/Layout/TopBar/TopBar.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { useMediaQuery } from '@react-hookz/web';
22
import styled from 'styled-components';
33

44
import ChainSelector from 'src/domains/chains/components/ChainSelector';
5-
import ConnectModal from 'src/domains/chains/components/ConnectModal';
5+
import ConnectModal from 'src/domains/chains/components/ConnectModal.tsx';
66
import { useWallet } from 'src/domains/chains/components/WalletProvider';
77
import Button from 'src/domains/misc/components/Button';
8+
import { useModal } from 'src/domains/misc/components/ModalNew';
89
import { BOTTOM_MENU_BREAKPOINT } from 'src/domains/misc/consts/consts';
910
import formatAddress from 'src/domains/misc/utils/formatAddress';
1011
import { typography } from 'src/domains/styling/utils/tokens';
@@ -20,6 +21,7 @@ import UserIcon from './userIcon.svg?react';
2021
const TopBar = () => {
2122
const isSmallScreen = useMediaQuery(`(max-width: ${BOTTOM_MENU_BREAKPOINT})`);
2223
const { disconnect, isConnected , address } = useWallet();
24+
const { open } = useModal(<ConnectModal />);
2325

2426
return (
2527
<NavBox.Container>
@@ -45,9 +47,14 @@ const TopBar = () => {
4547
</Button>
4648
</AccountDetails>
4749
</AccountManager>
48-
) :
49-
<ConnectModal triggerElement={<Button variant="primary">Connect</Button>} />
50-
}
50+
) : (
51+
<Button
52+
onClick={open}
53+
variant="primary"
54+
>
55+
Connect
56+
</Button>
57+
)}
5158
</NavBox.UserCanvas>
5259
</NavBox.Container>
5360
);
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { AnimatePresence, motion } from 'framer-motion';
2+
import { MouseEvent, ReactElement, useCallback, useEffect, useRef, useState } from 'react';
3+
import styled from 'styled-components';
4+
5+
import Button from 'src/domains/misc/components/Button';
6+
import DoubleBorderBox from 'src/domains/misc/components/DoubleBorderBox';
7+
import { useModalControls } from 'src/domains/misc/components/ModalNew';
8+
import Pager from 'src/domains/misc/components/Pager';
9+
import * as Title from 'src/domains/misc/components/Title';
10+
import { boxShadows } from 'src/domains/styling/utils/tokens';
11+
import vars from 'src/domains/styling/utils/vars';
12+
13+
type Config = {
14+
title?: string | ReactElement,
15+
content: ReactElement,
16+
};
17+
18+
type Props = {
19+
className?: string,
20+
page?: number,
21+
nonDismissible?: boolean,
22+
onClose?: () => void,
23+
config: Config | Config[],
24+
};
25+
26+
const Modal = ({
27+
config,
28+
nonDismissible,
29+
className,
30+
page = 0,
31+
}: Props) => {
32+
const { isLast, close, isTopModal } = useModalControls();
33+
const ref = useRef<HTMLDivElement>(null);
34+
const [isClosing, setIsClosing] = useState(false);
35+
36+
const triggerClose = useCallback(() => {
37+
if(isLast) {
38+
close();
39+
}
40+
setIsClosing(true);
41+
}, [close, isLast]);
42+
43+
const handleClickOutside = (e: MouseEvent<HTMLDivElement>) => {
44+
if (nonDismissible) return;
45+
if (!ref.current || ref.current.contains(e.target as Node)) return;
46+
const toastRoot = document.getElementById('toast-root');
47+
const walletConnectModal = document.querySelector('[class*="walletconnect"]');
48+
if (
49+
toastRoot?.contains(e.target as Node) ||
50+
walletConnectModal?.contains(e.target as Node)
51+
) {
52+
return;
53+
}
54+
triggerClose();
55+
};
56+
57+
useEffect(() => {
58+
const handleEsc = (e: KeyboardEvent) => {
59+
if (e.key === 'Escape' && !nonDismissible) {
60+
triggerClose();
61+
}
62+
};
63+
document.addEventListener('keydown', handleEsc);
64+
return () => void document.removeEventListener('keydown', handleEsc);
65+
}, [nonDismissible, triggerClose]);
66+
67+
const pages = Array.isArray(config) ? config : [config];
68+
69+
return (
70+
<Title.ProvideLeftBarAdditionalShift value={8}>
71+
<ModalWrapper style={{ pointerEvents: isTopModal ? 'auto' : 'none' }} onClick={handleClickOutside}>
72+
<AnimatePresence onExitComplete={isTopModal ? () => void close() : undefined}>
73+
{(!isClosing && isTopModal) && (
74+
<ModalContent
75+
ref={ref}
76+
variants={variants}
77+
initial="hidden"
78+
animate="visible"
79+
exit="hidden"
80+
className={className}
81+
layout
82+
>
83+
{(!!pages[page].title || !nonDismissible) && (
84+
<ModalHeader>
85+
{pages[page].title ? <StyledTitle size="medium">{pages[page].title}</StyledTitle> : <div />}
86+
{!nonDismissible && (
87+
<CloseButton
88+
size="small"
89+
variant="transparent"
90+
leftIcon="Dismiss"
91+
onClick={triggerClose}
92+
/>
93+
)}
94+
</ModalHeader>
95+
)}
96+
<Pager currentPageIndex={page}
97+
pages={
98+
pages.map(page => () => page.content)
99+
}
100+
/>
101+
</ModalContent>
102+
)}
103+
</AnimatePresence>
104+
</ModalWrapper>
105+
</Title.ProvideLeftBarAdditionalShift>
106+
);
107+
};
108+
109+
export default Modal;
110+
111+
const variants = {
112+
visible: { y: 0, opacity: 1 },
113+
hidden: { y: 40, opacity: 0 },
114+
};
115+
116+
const SLIDE_IN_CARD_BREAKPOINT = 434;
117+
118+
const ModalWrapper = styled.div`
119+
display: grid;
120+
position: fixed;
121+
inset: 0;
122+
justify-items: center;
123+
align-items: center;
124+
125+
@media (max-width: ${SLIDE_IN_CARD_BREAKPOINT}px) { /* stylelint-disable-line media-query-no-invalid */
126+
align-items: end;
127+
}
128+
`;
129+
130+
const ModalContent = styled(motion.create(DoubleBorderBox.Content))`
131+
width: min(${SLIDE_IN_CARD_BREAKPOINT}px, 100dvw);
132+
max-height: 90vh;
133+
margin: 0;
134+
overflow: clip;
135+
${boxShadows.shadow64};
136+
137+
@media (max-width: ${SLIDE_IN_CARD_BREAKPOINT}px) { /* stylelint-disable-line media-query-no-invalid */
138+
border-bottom-left-radius: ${vars('--border-radius-none')};
139+
border-bottom-right-radius: ${vars('--border-radius-none')};
140+
}
141+
`;
142+
143+
const ModalHeader = styled.div`
144+
display: grid;
145+
grid-template-columns: 1fr auto;
146+
align-items: center;
147+
margin-bottom: 16px;
148+
`;
149+
150+
const CloseButton = styled(Button)`
151+
padding-right: 0;
152+
`;
153+
154+
const StyledTitle = styled(Title.default)``;

0 commit comments

Comments
 (0)