Skip to content

Instantly share code, notes, and snippets.

@2wce
Created December 12, 2019 12:20
Show Gist options
  • Save 2wce/fbb80402a8cc514de92208bf95ae4fa6 to your computer and use it in GitHub Desktop.
Save 2wce/fbb80402a8cc514de92208bf95ae4fa6 to your computer and use it in GitHub Desktop.
Custom modal component
import { X } from 'react-feather';
import { animated } from 'react-spring';
import styled, { css } from 'styled-components';
import { color, space, width } from 'styled-system';
export const ModalWrap = styled(animated.div)`
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
z-index: 1000;
height: 100vh;
overflow: auto;
display: flex;
padding: 20px;
position: fixed;
align-items: center;
box-sizing: border-box;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
`;
type CustomModalInnerProps = {
bg?: string;
};
type ModalBodyContProps = {
hasAction?: boolean;
};
type ModalHeaderContProps = {
hasBody?: boolean;
};
export const ModalInner = styled(animated.div)<any>`
display: flex;
max-width: 100%;
border-radius: 5px;
align-items: center;
box-sizing: border-box;
flex-direction: column;
background-color: #fff;
justify-content: center;
overflow: auto;
width: ${props => (props.hasChildren ? 'auto' : '500px')};
box-shadow: 0 15px 35px rgba(50, 50, 93, 0.2), 0 5px 15px rgba(0, 0, 0, 0.17);
${space};
${color};
${width};
`;
export const CustomModalInner = styled(animated.div)<CustomModalInnerProps>`
width: auto;
display: flex;
max-width: 100%;
border-radius: 5px;
align-items: center;
box-sizing: border-box;
overflow: auto;
flex-direction: column;
justify-content: center;
background-color: ${props => props.bg || 'transparent'};
box-shadow: 0 15px 35px rgba(50, 50, 93, 0.2), 0 5px 15px rgba(0, 0, 0, 0.17);
`;
export const ModalHeaderCont = styled.div<ModalHeaderContProps>`
width: 100%;
display: flex;
padding-bottom: 10px;
margin-bottom: 20px;
align-items: center;
box-sizing: border-box;
justify-content: flex-start;
border-bottom: 1px solid #f1f7fb;
overflow: auto;
${({ hasBody }) =>
hasBody &&
css`
margin-bottom: 0;
`}
`;
export const ModalBodyCont = styled.div<ModalBodyContProps>`
width: 100%;
display: flex;
box-sizing: border-box;
align-items: flex-start;
justify-content: flex-start;
overflow: auto;
border-bottom: ${props => (props.hasAction ? 'none' : '1px solid #f1f7fb')};
`;
export const ModalFooterCont = styled.div`
width: 100%;
display: flex;
padding-top: 20px;
box-sizing: border-box;
align-items: flex-start;
justify-content: flex-end;
`;
export const CloseButton = styled(X)<any>`
right: 20px;
width: 20px;
height: 20px;
z-index: 1001;
font-size: 40px;
position: absolute;
color: rgba(0, 0, 0, 0.6);
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
&:hover {
cursor: pointer;
color: rgba(0, 0, 0, 0.9);
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
`;
import * as React from 'react';
import { Transition } from 'react-spring';
import { StyledSystemProps } from '../../../theme/StyledSystemProps';
import { H3, P } from '../../../typography';
import { CloseButton, ModalBodyCont, ModalFooterCont, ModalHeaderCont, ModalInner, ModalWrap } from './Elements';
const MODAL_ANIM_CONFIG = {
duration: 250,
tension: 100,
friction: 10,
};
type ModalProps = StyledSystemProps & {
body?: string;
open: boolean;
heading?: string;
actions?: React.ReactNode;
onClick?: () => void;
onRequestClose: () => void;
};
type ModalState = {
open: boolean;
};
class Modal extends React.Component<ModalProps, ModalState> {
static defaultProps = {
heading: 'Modal Heading',
body: 'Modal Body',
onClose: () => null,
bg: 'white',
p: 4,
};
constructor(props: ModalProps) {
super(props);
this.state = {
open: props.open,
};
}
_renderContent() {
const { children, heading, body, onRequestClose, actions } = this.props;
const hasAction = !!actions;
const hasBody = !!body;
if (children) {
return (
<React.Fragment>
<ModalHeaderCont>
<H3>{heading}</H3>
<CloseButton onClick={onRequestClose} />
</ModalHeaderCont>
{children}
</React.Fragment>
);
}
return (
<React.Fragment>
<ModalHeaderCont hasBody={hasBody}>
<H3>{heading}</H3>
<CloseButton onClick={onRequestClose} />
</ModalHeaderCont>
<ModalBodyCont hasAction={!hasAction}>
<P>{body}</P>
</ModalBodyCont>
{actions && <ModalFooterCont>{actions}</ModalFooterCont>}
</React.Fragment>
);
}
render() {
const { children, open } = this.props;
const hasChildren = !!children;
return (
<Transition
items={open}
native={true}
config={MODAL_ANIM_CONFIG}
from={{
opacity: 0,
transform: 'translateY(40px)',
}}
enter={{
opacity: 1,
transform: 'translateY(0)',
}}
leave={{
opacity: 0,
transform: 'translateY(40px)',
pointerEvents: 'none',
}}
>
{modalOpen =>
modalOpen &&
(styleProps => (
<ModalWrap style={{ opacity: styleProps.opacity }}>
<ModalInner {...this.props} haschildren={hasChildren} style={styleProps}>
{this._renderContent()}
</ModalInner>
</ModalWrap>
))
}
</Transition>
);
}
}
export default Modal;
import * as React from 'react';
import { Transition } from 'react-spring';
import { StyledSystemProps } from '../../../theme/StyledSystemProps';
import { CustomModalInner, ModalWrap } from './Elements';
const MODAL_ANIM_CONFIG = {
duration: 250,
tension: 100,
friction: 10,
};
type ModalProps = StyledSystemProps & {
onClose: () => null;
children: React.ReactNode;
open: boolean;
};
const CustomModal: React.FC<ModalProps> = ({ children, open }) => (
<Transition
items={open}
native={true}
config={MODAL_ANIM_CONFIG}
from={{
opacity: 0,
transform: 'translateY(40px)',
}}
enter={{
opacity: 1,
transform: 'translateY(0)',
}}
leave={{
opacity: 0,
transform: 'translateY(40px)',
pointerEvents: 'none',
}}
>
{modalOpen =>
modalOpen &&
(props => (
<ModalWrap style={{ opacity: props.opacity }}>
<CustomModalInner>{children}</CustomModalInner>
</ModalWrap>
))
}
</Transition>
);
export default CustomModal;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment