Skip to content

Commit

Permalink
Added Modal service shared among all components
Browse files Browse the repository at this point in the history
  • Loading branch information
rlebre committed Mar 30, 2022
1 parent 7215ba7 commit 8acfede
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 42 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.eslintcache

# local env files
.env.local
Expand Down
19 changes: 19 additions & 0 deletions components/icons/Exclamation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

interface Props {
[name: string]: string | number;
}

const Exclamation = (props: Props) => {
return (
<svg xmlns='http://www.w3.org/2000/svg' className='h-5 w-5' viewBox='0 0 20 20' fill='currentColor' {...props}>
<path
fillRule='evenodd'
d='M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z'
clipRule='evenodd'
/>
</svg>
);
};

export default Exclamation;
19 changes: 19 additions & 0 deletions components/icons/Success.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

interface Props {
[name: string]: string | number;
}

const Success = (props: Props) => {
return (
<svg xmlns='http://www.w3.org/2000/svg' className='h-5 w-5' viewBox='0 0 20 20' fill='currentColor' {...props}>
<path
fillRule='evenodd'
d='M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z'
clipRule='evenodd'
/>
</svg>
);
};

export default Success;
5 changes: 5 additions & 0 deletions components/modal/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum ModalTypes {
SUCCESS = 'success',
ERROR = 'error',
WARNING = 'warning',
}
93 changes: 93 additions & 0 deletions components/modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { MutableRefObject, useCallback, useEffect, useRef, useState } from 'react';
import ModalEvent from '../../interfaces/ModalEvent';
import { useOutsideClick } from '../click-outside/useClickOutside';
import Exclamation from '../icons/Exclamation';
import Success from '../icons/Success';
import { ModalTypes } from './config';
import styles from './modal.module.scss';
import { ModalService } from './service';

export const Modal = () => {
const [visible, setVisibility] = useState<boolean>(false);

const type = useRef<ModalTypes>();
const description = useRef<string>('');
const title = useRef<string>('');

const menuRef = useRef<HTMLDivElement>(null) as MutableRefObject<HTMLDivElement>;

useOutsideClick(menuRef, () => {
hide();
});

const show = useCallback(() => {
setVisibility(true);
}, []);

const hide = useCallback(() => {
setVisibility(false);
}, []);

useEffect(() => {
const onError = (event: CustomEvent<ModalEvent>) => {
const { title: eventTitle, description: eventDescription } = event.detail;
description.current = eventDescription;
title.current = eventTitle;
type.current = ModalTypes.ERROR;
visible && setVisibility(false);
show();
};

const onSuccess = (event: CustomEvent) => {
const { title: eventTitle, description: eventDescription } = event.detail;
description.current = eventDescription;
title.current = eventTitle;
type.current = ModalTypes.SUCCESS;
visible && setVisibility(false);
show();
};

ModalService.on(ModalTypes.SUCCESS, onSuccess);
ModalService.on(ModalTypes.ERROR, onError);

return () => {
ModalService.off(ModalTypes.SUCCESS, onSuccess);
ModalService.off(ModalTypes.ERROR, onError);
};
}, [show, visible]);

return (
<div
onKeyDown={(e) => e.key === 'Escape' && setVisibility(false)}
className={`${styles.modal} ${visible ? styles.modal__show : ''}`}
>
<div ref={menuRef} className={styles.modal__content}>
<span
role='button'
tabIndex={0}
className={styles.close}
onKeyDown={(e: React.KeyboardEvent<HTMLElement>) => e.key === 'Enter' && setVisibility(false)}
onClick={hide}
>
&times;
</span>

{title.current && <span className={styles.modal__title}>{title.current}</span>}

<div className={styles.modal__description}>
{type.current && <TypeRenderer type={type.current} />}
{description.current}
</div>
</div>
</div>
);
};

const TypeRenderer = ({ type }) => {
switch (type) {
case ModalTypes.SUCCESS:
return <Success className='h-10 w-10 fill-green-700' />;
case ModalTypes.ERROR:
return <Exclamation fill='red' className='h-10 w-10 fill-red-600' />;
}
};
11 changes: 8 additions & 3 deletions components/modal/modal.module.scss
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
.modal {
@apply fixed left-0 top-0 z-50 h-full w-full overflow-auto bg-black bg-opacity-40 pt-40;
@apply fixed left-0 -top-full z-50 h-full w-full overflow-auto bg-black bg-opacity-40 pt-40;

&__show {
@apply top-0;
}

&__title {
color: blue;
@apply pb-5 font-medium text-lg;
}

&__content {
@apply relative m-auto w-4/5 max-w-xl bg-white p-10;
@apply relative m-auto w-4/5 max-w-xl bg-white p-10 flex flex-col;
@apply rounded shadow-lg;
@apply transition-all duration-300;
}

&__description {
Expand Down
35 changes: 0 additions & 35 deletions components/modal/modal.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions components/modal/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ModalEvent from '../../interfaces/ModalEvent';

type CallbackFn = (event: CustomEvent) => void;

export class ModalService {
static on(event: string, callback: CallbackFn) {
document.addEventListener(event, callback);
}

static off(event: string, callback: CallbackFn) {
document.removeEventListener(event, callback);
}

static success(data: ModalEvent) {
document.dispatchEvent(new CustomEvent('success', { detail: data }));
}

static error(data: ModalEvent) {
document.dispatchEvent(new CustomEvent('error', { detail: data }));
}
}
4 changes: 4 additions & 0 deletions interfaces/ModalEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default interface ModalEvent {
title?: string;
description: string;
}
5 changes: 4 additions & 1 deletion pages/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Button from '../components/button/Button';
import FlatSection from '../components/flat-section/FlatSection';
import ContactSection from '../components/contact-section/ContactSection';
import LocationSection from '../components/location-section/LocationSection';
import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3';

const About = () => {
return (
Expand Down Expand Up @@ -42,7 +43,9 @@ const About = () => {
</p>
</FlatSection>

<ContactSection />
<GoogleReCaptchaProvider reCaptchaKey={`${process.env.NEXT_PUBLIC_RECAPTCHA}`}>
<ContactSection />
</GoogleReCaptchaProvider>
</div>

<LocationSection />
Expand Down
6 changes: 3 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
Expand All @@ -15,6 +15,6 @@
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/pages"],
"exclude": ["node_modules"]
}

0 comments on commit 8acfede

Please sign in to comment.