Created
January 31, 2020 12:38
-
-
Save ajitid/339caefb318100fb3dffd363b7b91b91 to your computer and use it in GitHub Desktop.
React Modal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useRef, useEffect } from 'react' | |
export enum ModalCloseReason { | |
ClickedCloseButton, | |
PressedKeyboardEscape, | |
ClickedOutside, | |
TouchedOutside, | |
OtherEvent, | |
} | |
interface HandleCloseShape { | |
(reason: ModalCloseReason): void | |
} | |
interface ModalProps { | |
handleClose: HandleCloseShape | |
} | |
const Modal: React.FC<ModalProps> = ({ children, handleClose }) => { | |
const containerRef = useRef<HTMLDivElement | null>(null) | |
// bring back focus to the originally focused element on unmount | |
useEffect(() => { | |
const focusedElement = document.activeElement as HTMLElement | |
return () => { | |
focusedElement.focus() | |
} | |
}, []) | |
// focus to modal on mount | |
useEffect(() => { | |
if (!containerRef.current) return | |
containerRef.current.focus() | |
}, []) | |
const handleMouseUp = (e: React.MouseEvent) => { | |
e.stopPropagation() | |
e.nativeEvent.stopImmediatePropagation() | |
} | |
useEffect(() => { | |
const handleOutsideClick = () => { | |
handleClose(ModalCloseReason.ClickedOutside) | |
} | |
document.addEventListener('mouseup', handleOutsideClick) | |
return () => { | |
document.removeEventListener('mouseup', handleOutsideClick) | |
} | |
}, [handleClose]) | |
const handleTouchEnd = (e: React.TouchEvent) => { | |
e.stopPropagation() | |
e.nativeEvent.stopImmediatePropagation() | |
} | |
useEffect(() => { | |
const handleOutsideTouch = () => { | |
handleClose(ModalCloseReason.TouchedOutside) | |
} | |
document.addEventListener('touchend', handleOutsideTouch) | |
return () => { | |
document.addEventListener('touchend', handleOutsideTouch) | |
} | |
}, [handleClose]) | |
useEffect(() => { | |
const handleOutsideClick = (e: KeyboardEvent) => { | |
if (e.key === 'Escape') { | |
e.stopPropagation() | |
/* | |
needed if e is a react synthetic event -> | |
e.nativeEvent.stopImmediatePropagation() | |
*/ | |
handleClose(ModalCloseReason.PressedKeyboardEscape) | |
} | |
} | |
document.addEventListener('keydown', handleOutsideClick) | |
return () => { | |
document.removeEventListener('keydown', handleOutsideClick) | |
} | |
}, [handleClose]) | |
return ( | |
<div | |
ref={containerRef} | |
tabIndex={0} | |
onMouseUp={handleMouseUp} | |
onTouchEnd={handleTouchEnd} | |
style={{ backgroundColor: '#4e3' }} | |
> | |
<button onClick={() => handleClose(ModalCloseReason.ClickedCloseButton)}>close</button> | |
<button>cfjnv</button> | |
{children} | |
</div> | |
) | |
} | |
export default Modal |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useRef } from 'react' | |
import ReactDOM from 'react-dom' | |
const portalRoot = document.getElementById('portal') | |
const Portal: React.FC = ({ children }) => { | |
const el = useRef(document.createElement('div')) | |
useEffect(() => { | |
const element = el.current | |
if (!portalRoot) { | |
throw new Error('Element with id `portal` does not exists in DOM.') | |
} | |
portalRoot.appendChild(element) | |
return () => { | |
portalRoot.removeChild(element) | |
} | |
}, []) | |
return ReactDOM.createPortal(children, el.current) | |
} | |
export default Portal |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
div
below app root with idportal