Skip to content

Commit 420ac48

Browse files
authored
Merge pull request #79 from Cardinal-Cryptography/SD-92-refactor-modal
SD-92: Refactor modal
2 parents 3ab95dc + 156c17c commit 420ac48

File tree

17 files changed

+724
-251
lines changed

17 files changed

+724
-251
lines changed

.storybook/utils/mockIndexedDb.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
// @ts-expect-error fake-indexeddb does not expose FDBFactory in package.json "exports" — using internal path
2-
import FDBFactory from 'fake-indexeddb/lib/FDBFactory';
3-
// @ts-expect-error fake-indexeddb does not expose FDBKeyRange in package.json "exports" — using internal path
4-
import FDBKeyRange from 'fake-indexeddb/lib/FDBKeyRange';
1+
import {
2+
IDBFactory,
3+
IDBKeyRange,
4+
IDBRequest,
5+
} from 'fake-indexeddb';
56

67
Object.defineProperty(window, 'indexedDB', {
7-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
8-
value: new FDBFactory(),
8+
value: new IDBFactory(),
99
});
1010

1111
Object.defineProperty(window, 'IDBKeyRange', {
12-
value: FDBKeyRange,
12+
value: IDBKeyRange,
13+
});
14+
15+
Object.defineProperty(window, 'IDBRequest', {
16+
value: IDBRequest,
1317
});

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: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { faker } from '@faker-js/faker';
2+
import type { Meta, StoryObj } from '@storybook/react';
3+
import { userEvent, waitFor, within } from '@storybook/test';
4+
import { ComponentProps, useEffect, useState } from 'react';
5+
import styled from 'styled-components';
6+
7+
import Button from 'src/domains/misc/components/Button';
8+
import Modal, { ModalProvider, useModal, useModalControls } from 'src/domains/misc/components/ModalNew';
9+
import { typography } from 'src/domains/styling/utils/tokens.ts';
10+
11+
faker.seed(0);
12+
13+
const MAX_PAGES = 10;
14+
15+
const LOREM_SENTENCES = Array.from({ length: MAX_PAGES }, () => faker.lorem.lines(4));
16+
const LOREM_TITLES = Array.from({ length: MAX_PAGES }, () => faker.lorem.words({ min: 2, max: 5 }));
17+
18+
const meta = {
19+
decorators: [
20+
Story => (
21+
<ModalProvider>
22+
<Story />
23+
</ModalProvider>
24+
),
25+
],
26+
} satisfies Meta<typeof Modal>;
27+
28+
export default meta;
29+
30+
type Story = StoryObj<ComponentProps<typeof Modal> & {
31+
pageCount: number,
32+
}>;
33+
34+
export const Basic: Story = {
35+
parameters: {
36+
chromatic: { disableSnapshot: true },
37+
},
38+
render: () => <Preview pageCount={2} />,
39+
play: async () => {
40+
const canvas = within(document.body);
41+
42+
const button = await canvas.findByRole('button');
43+
44+
await waitFor(() => userEvent.click(button));
45+
},
46+
};
47+
48+
export const Controllable: Story = {
49+
argTypes: {
50+
pageCount: {
51+
control: { type: 'range', min: 1, max: MAX_PAGES, step: 1 },
52+
description: 'Number of modal pages to show',
53+
},
54+
},
55+
parameters: {
56+
chromatic: { disableSnapshot: true },
57+
},
58+
args: {
59+
pageCount: 1,
60+
nonDismissible: false,
61+
},
62+
render: args => <Preview key={JSON.stringify(args)} {...args} />,
63+
};
64+
65+
type ModalProps = { pageCount: number } & Partial<ComponentProps<typeof Modal>>;
66+
67+
const Preview = (props: ModalProps) => {
68+
const { open, close } = useModal(<Component {...props} />);
69+
70+
useEffect(() => {
71+
return () => void close();
72+
}, [close]);
73+
74+
return (
75+
<Button onClick={open} variant="primary">
76+
Open modal
77+
</Button>
78+
);
79+
};
80+
81+
const Component = ({ pageCount, ...props }: ModalProps) => {
82+
const [page, setPage] = useState(0);
83+
84+
const config = Array.from({ length: pageCount }, (_, i) => ({
85+
title: LOREM_TITLES[i],
86+
content: (
87+
<Content
88+
page={i}
89+
pageCount={pageCount}
90+
onContinue={() => void setPage(curr => curr + 1)}
91+
/>
92+
),
93+
}));
94+
95+
return <Modal page={page} config={config} {...props} />;
96+
};
97+
98+
const Content = ({
99+
onContinue,
100+
page,
101+
pageCount,
102+
}: {
103+
onContinue?: () => void,
104+
page: number,
105+
pageCount: number,
106+
}) => {
107+
const { close } = useModalControls();
108+
const isLastPage = page === pageCount - 1;
109+
return (
110+
<Wrapper>
111+
<p>{LOREM_SENTENCES[page]}</p>
112+
<ButtonWrapper>
113+
<Button variant="primary" onClick={isLastPage ? close : onContinue}>
114+
{isLastPage ? 'Close' : 'Continue'}
115+
</Button>
116+
<p>{page + 1}/{pageCount}</p>
117+
</ButtonWrapper>
118+
</Wrapper>
119+
);
120+
};
121+
122+
const Wrapper = styled.div`
123+
display: flex;
124+
flex-direction: column;
125+
gap: 20px;
126+
${typography.web.body1};
127+
`;
128+
129+
const ButtonWrapper = styled.div`
130+
display: flex;
131+
align-items: center;
132+
justify-content: space-between;
133+
gap: 8px
134+
`;

0 commit comments

Comments
 (0)