Skip to content

Commit 2a86119

Browse files
authored
Merge pull request #40 from internet-development/@elijaharita/modal-upgrade
Modal upgrade
2 parents 65aa903 + c0965d0 commit 2a86119

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+567
-458
lines changed

.prettierrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"requirePragma": false,
1212
"insertPragma": false,
1313
"proseWrap": "preserve",
14-
"parser": "babel",
14+
"parser": "babel-ts",
1515
"overrides": [
1616
{
1717
"files": "*.js",

common/utilities.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ export function calculatePositionWithGutter(rect, objectWidth, viewportWidth, gu
7878
return { top, right: adjustedRight, side };
7979
}
8080

81+
export function calculatePositionWithGutterById(id, objectWidth, viewportWidth, gutter?) {
82+
let rect;
83+
if (id) {
84+
const el = document.getElementById(id);
85+
if (el) {
86+
rect = el.getBoundingClientRect();
87+
}
88+
}
89+
return calculatePositionWithGutter(rect, objectWidth, viewportWidth, gutter);
90+
}
91+
8192
export function leftPad(input, length) {
8293
const zerosNeeded = length - input.length;
8394
if (zerosNeeded <= 0) {
@@ -297,6 +308,8 @@ export async function generateNonce() {
297308

298309
export function filterUndefined(obj) {
299310
const res = {};
300-
Object.keys(obj).filter(k => obj[k] !== undefined).forEach(k => res[k] = obj[k]);
311+
Object.keys(obj)
312+
.filter((k) => obj[k] !== undefined)
313+
.forEach((k) => (res[k] = obj[k]));
301314
return res;
302-
}
315+
}

components/Page.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import * as React from 'react';
55
import SmallButton from '@system/documents/SmallButton';
66
import Head from 'next/head';
77

8-
export default function Page(props) {
8+
export interface PageProps {
9+
title: string;
10+
description: string;
11+
url: string;
12+
children?: React.ReactNode;
13+
}
14+
15+
export default function Page(props: PageProps) {
916
const source = `https://github.com/internet-development/nextjs-sass-starter/blob/main/pages${props.url.replace('https://wireframes.internet.dev', '')}.tsx`;
1017

1118
return (

components/Providers.tsx

+2-30
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,8 @@
22

33
import * as React from 'react';
44

5-
import { ModalContext } from '@system/providers/ModalContextProvider';
6-
7-
interface ModalContent {
8-
data?: any;
9-
name?: string;
10-
message?: string;
11-
parentId?: string;
12-
}
13-
14-
interface ModalContextType {
15-
modalContent: ModalContent | null;
16-
showModal: (nextContent: ModalContent | null, delay?: number) => void;
17-
}
5+
import { ModalProvider } from '@root/system/modals/ModalContext';
186

197
export default function Providers({ children }) {
20-
const [modalContent, setContent] = React.useState<ModalContent | null>(null);
21-
22-
const showModal = (nextContent) => {
23-
if (nextContent && modalContent && nextContent.name === modalContent.name) {
24-
setContent(null);
25-
return;
26-
}
27-
28-
setContent(nextContent);
29-
};
30-
31-
const modalContextValue: ModalContextType = {
32-
modalContent,
33-
showModal,
34-
};
35-
36-
return <ModalContext.Provider value={modalContextValue}>{children}</ModalContext.Provider>;
8+
return <ModalProvider>{children}</ModalProvider>;
379
}

demos/DemoSettings.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -178,20 +178,20 @@ export default function DemoSettings(props) {
178178
onSetFile={async (file) => {
179179
setUploading(true);
180180
if (Utilities.isEmpty(props.sessionKey)) {
181-
props.onSetModal({ name: 'ERROR', message: 'not authenticated' });
181+
props.onError('not authenticated');
182182
setUploading(false);
183183
}
184184

185185
const response = await Queries.onUserUploadDataS3({ domain: null, file, key: props.sessionKey });
186186
if (!response) {
187-
props.onSetModal({ name: 'ERROR', message: 'failed to upload file' });
187+
props.onError('failed to upload file');
188188
setUploading(false);
189189
return;
190190
}
191191

192192
const save = await Queries.onUserCreatePost({ id: response.id, src: response.fileURL, key: props.sessionKey, type: 'MOOD' });
193193
if (!save) {
194-
props.onSetModal({ name: 'ERROR', message: 'failed to save post' });
194+
props.onError('failed to save post');
195195
setUploading(false);
196196
return;
197197
}
@@ -206,14 +206,14 @@ export default function DemoSettings(props) {
206206
icon={`⊹`}
207207
onClick={async () => {
208208
if (Utilities.isEmpty(props.sessionKey)) {
209-
props.onSetModal({ name: 'ERROR', message: 'You must be authenticated' });
209+
props.onError('You must be authenticated');
210210
return null;
211211
}
212212

213213
for (const targetId of selected) {
214214
const response = await Queries.onUserDeletePost({ id: targetId, key: props.sessionKey });
215215
if (!response) {
216-
props.onSetModal({ name: 'ERROR', message: 'failed to delete post' });
216+
props.onError('failed to delete post');
217217
// TODO(jimmylee):
218218
// Very lazy.
219219
window.location.reload();
@@ -241,7 +241,7 @@ export default function DemoSettings(props) {
241241
onSubmit={async ({ password }) => {
242242
const response = await Queries.onUserChangePassword({ key: props.sessionKey, password });
243243
if (!response) {
244-
props.onSetModal({ name: 'ERROR', message: 'failed to change password' });
244+
props.onError('failed to change password');
245245
}
246246

247247
window.location.href = '/examples/features/settings';

demos/DemoThreads.tsx

+8-26
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,13 @@ const ThreadItem = (props) => {
1212
const onList = async (options?: Record<string, any>) => {
1313
const listing = await Queries.onUserListThreadReplies({ id: props.threadId, key: null, orderBy: { column: 'created_at', value: 'desc' } });
1414
if (!listing || listing.error) {
15-
props.setModal({
16-
name: 'ERROR',
17-
message: 'Something went wrong with listing replies to this post.',
18-
});
15+
props.onError('Something went wrong with listing replies to this post.');
1916
return;
2017
}
2118

2219
if (options && options.checkEmptyArrayError) {
2320
if (!listing.data.length) {
24-
props.setModal({
25-
name: 'ERROR',
26-
message: 'There are no replies to this thread, make one!',
27-
});
21+
props.onError('There are no replies to this thread, make one!');
2822
return;
2923
}
3024
}
@@ -34,20 +28,14 @@ const ThreadItem = (props) => {
3428

3529
const onReply = async () => {
3630
if (!props.viewer) {
37-
props.setModal({
38-
name: 'ERROR',
39-
message: 'You need to sign in first.',
40-
});
31+
props.onError('You need to sign in first.');
4132
return;
4233
}
4334

4435
const plainText = window.prompt('The easiest way to do this without building the modal. Type what words you want to share.');
4536

4637
if (Utilities.isEmpty(plainText)) {
47-
props.setModal({
48-
name: 'ERROR',
49-
message: 'You must provide words.',
50-
});
38+
props.onError('You must provide words.');
5139
return;
5240
}
5341

@@ -62,19 +50,13 @@ const ThreadItem = (props) => {
6250
type: 'GENERAL',
6351
});
6452
if (!response) {
65-
props.setModal({
66-
name: 'ERROR',
67-
message: 'Something went wrong with creating creating a thread',
68-
});
53+
props.onError('Something went wrong with creating creating a thread');
6954
return;
7055
}
7156

7257
const listing = await Queries.onUserListThreadReplies({ id: props.threadId, key: null, orderBy: { column: 'created_at', value: 'desc' } });
7358
if (!listing) {
74-
props.setModal({
75-
name: 'ERROR',
76-
message: 'Something went wrong with listing threads',
77-
});
59+
props.onError('Something went wrong with listing threads');
7860
return;
7961
}
8062

@@ -124,7 +106,7 @@ const ThreadItem = (props) => {
124106
isLast={index === list.length - 1}
125107
key={each.id}
126108
sessionKey={props.sessionKey}
127-
setModal={props.setModal}
109+
onError={props.onError}
128110
threadId={each.id}
129111
viewer={props.viewer}
130112
>
@@ -152,7 +134,7 @@ export default function DemoThreads(props) {
152134
{props.data.map((each, index) => {
153135
const author = props.viewer && props.viewer.id === each.user_id ? `You` : `Anonymous`;
154136
return (
155-
<ThreadItem isLast={index === props.data.length - 1} key={each.id} sessionKey={props.sessionKey} setModal={props.setModal} threadId={each.id} viewer={props.viewer}>
137+
<ThreadItem isLast={index === props.data.length - 1} key={each.id} sessionKey={props.sessionKey} onError={props.onError} threadId={each.id} viewer={props.viewer}>
156138
<div className={styles.byline}>
157139
{author}{Utilities.timeAgo(each.created_at)}
158140
</div>

demos/modals/ModalAuthentication.tsx

+13-5
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,22 @@ import Input from '@system/Input';
1313
import OutsideElementEvent from '@system/detectors/OutsideElementEvent';
1414

1515
import { FormHeading, FormSubHeading, FormParagraph, InputLabel } from '@system/typography/forms';
16+
import { ModalComponent } from '@root/system/modals/ModalContext';
1617

17-
export default function ModalAuthentication(props) {
18+
export interface ModalAuthenticationProps {
19+
active?: string;
20+
viewer: any | null;
21+
}
22+
23+
const ModalAuthentication: ModalComponent<ModalAuthenticationProps> = (props) => {
1824
const [email, setEmail] = React.useState<string>('');
1925
const [password, setPassword] = React.useState<string>('');
2026
const [loading, setLoading] = React.useState<boolean>(false);
2127

2228
if (props.viewer) {
2329
return (
2430
<div className={styles.wrapper}>
25-
<OutsideElementEvent onOutsideEvent={() => props.onShowModal(null)} style={{ width: '100%', maxWidth: 488, margin: `0 auto 0 auto` }}>
31+
<OutsideElementEvent onOutsideEvent={() => props.onClose()} style={{ width: '100%', maxWidth: 488, margin: `0 auto 0 auto` }}>
2632
<div className={styles.childModal} style={{ width: '100%', padding: 24 }}>
2733
<FormSubHeading>You are authenticated</FormSubHeading>
2834
<FormParagraph>To clear your session, click on the button below.</FormParagraph>
@@ -49,7 +55,7 @@ export default function ModalAuthentication(props) {
4955

5056
return (
5157
<div className={styles.wrapper}>
52-
<OutsideElementEvent onOutsideEvent={() => props.onShowModal(null)} style={{ width: '100%', maxWidth: 488, margin: `0 auto 0 auto` }}>
58+
<OutsideElementEvent onOutsideEvent={() => props.onClose()} style={{ width: '100%', maxWidth: 488, margin: `0 auto 0 auto` }}>
5359
<div className={styles.childModal} style={{ width: '100%', padding: 24 }}>
5460
<FormSubHeading>Join or sign in</FormSubHeading>
5561
<FormParagraph>Sign in or create an account to enhance your experience. Enter your e-mail and password or use an OAuth provider to get started.</FormParagraph>
@@ -96,7 +102,7 @@ export default function ModalAuthentication(props) {
96102
Cookies.set('sitekey', response.user.key, { secure: true });
97103
}
98104

99-
props.onShowModal(null);
105+
props.onClose();
100106
window.location.reload();
101107
}}
102108
style={{ marginTop: 24, width: '100%' }}
@@ -117,4 +123,6 @@ export default function ModalAuthentication(props) {
117123
</OutsideElementEvent>
118124
</div>
119125
);
120-
}
126+
};
127+
128+
export default ModalAuthentication;

demos/modals/ModalColorPicker.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ import * as Utilities from '@common/utilities';
66
import DemoColorPicker from '@demos/DemoColorPicker';
77
import OutsideElementEvent from '@system/detectors/OutsideElementEvent';
88

9-
export default function ModalColorPicker(props) {
9+
import { ModalComponent } from '@root/system/modals/ModalContext';
10+
11+
const ModalColorPicker: ModalComponent = (props) => {
1012
return (
1113
<div className={styles.wrapper}>
12-
<OutsideElementEvent onOutsideEvent={() => props.onShowModal(null)} style={{ width: '100%', maxWidth: 288, margin: `0 auto 0 auto` }}>
14+
<OutsideElementEvent onOutsideEvent={() => props.onClose()} style={{ width: '100%', maxWidth: 288, margin: `0 auto 0 auto` }}>
1315
<div className={styles.childModal} style={{ width: '100%' }}>
1416
<DemoColorPicker />
1517
</div>
1618
</OutsideElementEvent>
1719
</div>
1820
);
1921
}
22+
23+
export default ModalColorPicker;

demos/modals/ModalError.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ import * as React from 'react';
55
import Error from '@system/svg/Error';
66
import OutsideElementEvent from '@system/detectors/OutsideElementEvent';
77

8-
export default function ModalError(props) {
8+
import { ModalComponent } from '@root/system/modals/ModalContext';
9+
10+
export interface ModalErrorProps {
11+
message: any
12+
}
13+
14+
const ModalError: ModalComponent<ModalErrorProps> = (props) => {
915
return (
1016
<div className={styles.wrapper}>
11-
<OutsideElementEvent className={styles.errorModal} onOutsideEvent={() => props.onShowModal(null)}>
17+
<OutsideElementEvent className={styles.errorModal} onOutsideEvent={() => props.onClose()}>
1218
<span className={styles.errorModalLeft}>
1319
<Error height="16px" />
1420
</span>
15-
<span className={styles.errorModalRight}>{props.content.message}</span>
21+
<span className={styles.errorModalRight}>{props.message}</span>
1622
</OutsideElementEvent>
1723
</div>
1824
);
1925
}
26+
27+
export default ModalError;

demos/modals/ModalHamburgerMenu.tsx

+22-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,29 @@ import styles from '@demos/modals/Modals.module.scss';
22

33
import * as React from 'react';
44

5+
import Link from 'next/link';
6+
import OutsideElementEvent from '@root/system/detectors/OutsideElementEvent';
7+
58
import { H4 } from '@system/typography';
9+
import { CommonModalProps, ModalComponent, useModals } from '@root/system/modals/ModalContext';
610

7-
import Link from 'next/link';
11+
export interface ModalHamburgerMenuProps {
12+
content: {
13+
data: {
14+
navItems: { name: string; link: string }[];
15+
};
16+
};
17+
}
818

9-
export default function ModalHamburgerMenu(props) {
19+
const ModalHamburgerMenu: ModalComponent<ModalHamburgerMenuProps> = React.forwardRef((props, ref) => {
1020
const navItems = props.content.data.navItems;
11-
21+
22+
React.useImperativeHandle(ref, () => ({
23+
getUnmountDelayMS: () => 200,
24+
}));
25+
1226
return (
13-
<div className={`${styles.hamburgerModal} ${styles.slideIn}`}>
27+
<OutsideElementEvent className={styles.hamburgerModal} onOutsideEvent={() => props.onClose()} style={{ animationDirection: !props.isClosing ? 'normal' : 'reverse' }}>
1428
{navItems?.map((item) => (
1529
<div key={item.name} className={styles.menuContent}>
1630
{item.link ? (
@@ -22,6 +36,8 @@ export default function ModalHamburgerMenu(props) {
2236
)}
2337
</div>
2438
))}
25-
</div>
39+
</OutsideElementEvent>
2640
);
27-
}
41+
});
42+
43+
export default ModalHamburgerMenu;

0 commit comments

Comments
 (0)