-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from PocketDerm/add_modals
added immersive and regular modals
- Loading branch information
Showing
22 changed files
with
925 additions
and
1,403 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
{ | ||
"extends": "curology", | ||
"globals": { | ||
"document": false, | ||
}, | ||
"overrides": [ | ||
{ | ||
"files": ["test.js"], | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# ImmersiveModal | ||
### Usage | ||
|
||
```jsx | ||
import React from 'react'; | ||
|
||
import { ImmersiveModal, Button } from 'radiance-ui'; | ||
|
||
const HeaderImage = () => ( | ||
<div> | ||
This is a placeholder for an optional header image. | ||
</div> | ||
); | ||
|
||
class DefaultImmersiveModal extends React.Component { | ||
state = { | ||
defaultIsOpen: false, | ||
headerIsOpen: false, | ||
}; | ||
|
||
onOpenDefaultModal = () => this.setState({ defaultIsOpen: true }); | ||
|
||
onOpenHeaderModal = () => this.setState({ headerIsOpen: true }); | ||
|
||
onClose = () => this.setState({ | ||
defaultIsOpen: false, | ||
headerIsOpen: false, | ||
}); | ||
|
||
render() { | ||
const { defaultIsOpen, headerIsOpen } = this.state; | ||
|
||
return ( | ||
<div> | ||
<Button onClick={this.onOpenDefaultModal}>Open ImmersiveModal</Button> | ||
{defaultIsOpen && ( | ||
<ImmersiveModal onClose={this.onClose}> | ||
<ImmersiveModal.Title>This is styled with ImmersiveModal.Title</ImmersiveModal.Title> | ||
<ImmersiveModal.Body>This is styled with ImmersiveModal.Body</ImmersiveModal.Body> | ||
<ImmersiveModal.Footer> | ||
This is styled with ImmersiveModal.Footer. It gives us a padding to separate | ||
from the body. | ||
<Button.Container> | ||
<Button onClick={this.onClose}>Close ImmersiveModal</Button> | ||
</Button.Container> | ||
</ImmersiveModal.Footer> | ||
</ImmersiveModal> | ||
)} | ||
|
||
<Button onClick={this.onOpenHeaderModal}>Open ImmersiveModal with Header</Button> | ||
{headerIsOpen && ( | ||
<ImmersiveModal | ||
onClose={this.onClose} | ||
header={<HeaderImage />} | ||
> | ||
<ImmersiveModal.Title>This is styled with ImmersiveModal.Title</ImmersiveModal.Title> | ||
<ImmersiveModal.Body>This is styled with ImmersiveModal.Body.</ImmersiveModal.Body> | ||
<ImmersiveModal.Footer> | ||
This is styled with ImmersiveModal.Footer. It gives us a padding to separate | ||
from the body. | ||
<Button.Container> | ||
<Button onClick={this.onClose}>Close ImmersiveModal</Button> | ||
</Button.Container> | ||
</ImmersiveModal.Footer> | ||
</ImmersiveModal> | ||
)} | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
|
||
<!-- STORY --> | ||
|
||
### Proptypes | ||
| prop | propType | required | default | description | | ||
|----------|--------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------| | ||
| onClose | func | no | () => {} | Function that is executed when either the close icon is clicked or any part of the page outside the modal is clicked | | ||
| canBeClosed | bool | no | true | If false, the close icon does not render and the onClose function does not execute | | ||
| children | node | yes | - | Node that will render when the modal is visible | | ||
| header | node | no | - | Node that will render at the top of the modal and without padding, most commonly used for images | | ||
|
||
### Notes | ||
|
||
Available subcomponents through dot notation: | ||
|
||
`<ImmersiveModal.Title>` | ||
`<ImmersiveModal.Body>` | ||
`<ImmersiveModal.Footer>` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Modal | ||
### Usage | ||
|
||
```jsx | ||
import React from 'react'; | ||
|
||
import { Modal, Button } from 'radiance-ui'; | ||
|
||
class DefaultModal extends React.Component { | ||
state = { | ||
isOpen: false, | ||
}; | ||
|
||
onOpenModal = () => this.setState({ isOpen: true }); | ||
|
||
onClose = () => this.setState({ isOpen: false }); | ||
|
||
render() { | ||
const { isOpen } = this.state; | ||
|
||
return ( | ||
<div> | ||
<Button onClick={this.onOpenModal}>Open Modal</Button> | ||
<Modal isOpen={isOpen} onClose={this.onClose}> | ||
<Modal.ContentContainer> | ||
<Modal.Title>This is styled with Modal.Title</Modal.Title> | ||
<Modal.Body>This is styled with Modal.Body.</Modal.Body> | ||
<Modal.Footer> | ||
This is styled with Modal.Footer. It gives us a padding to separate | ||
from the body. | ||
<Button.Container> | ||
<Button onClick={this.onClose}>Close Modal</Button> | ||
</Button.Container> | ||
</Modal.Footer> | ||
</Modal.ContentContainer> | ||
</Modal> | ||
<br /> | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
|
||
<!-- STORY --> | ||
|
||
### Proptypes | ||
| prop | propType | required | default | description | | ||
|----------|--------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------| | ||
| onClose | func | no | () => {} | Function that is executed when the close icon is clicked | | ||
| isOpen | bool | no | false | Determines if the modal is open (visible) | | ||
| canBeClosed | bool | no | true | If false, the close icon does not render | | ||
| className | string | no | '' | Class name that will be passed to the modal | | ||
| children | node | no | '' | Node that will render when the modal is open | | ||
|
||
### Notes | ||
|
||
Available subcomponents through dot notation: | ||
|
||
`<Modal.ContentContainer>` | ||
`<Modal.Title>` | ||
`<Modal.Body>` | ||
`<Modal.Footer>` |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import throwOnUndefinedProperty from '../../utils/throwOnUndefinedProperty'; | ||
|
||
const keycodes = throwOnUndefinedProperty({ | ||
escape: 27, | ||
}); | ||
|
||
export default keycodes; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import PropTypes from 'prop-types'; | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
|
||
import KEYCODES from '../../constants/keycodes'; | ||
import keyPressMatch from '../../utils/keyPressMatch'; | ||
import OffClickWrapper from '../offClickWrapper'; | ||
import CloseIcon from '../../svgs/icons/close-icon.svg'; | ||
import { | ||
ModalContainer, | ||
Overlay, | ||
CloseIconContainer, | ||
CopyContainer, | ||
Title, | ||
Body, | ||
Footer, | ||
} from './style'; | ||
|
||
class ImmersiveModal extends React.Component { | ||
static propTypes = { | ||
children: PropTypes.node.isRequired, | ||
canBeClosed: PropTypes.bool, | ||
onClose: PropTypes.func, | ||
header: PropTypes.node, | ||
}; | ||
|
||
static defaultProps = { | ||
canBeClosed: true, | ||
onClose: () => {}, | ||
}; | ||
|
||
static Title = Title; | ||
|
||
static Body = Body; | ||
|
||
static Footer = Footer; | ||
|
||
constructor(props) { | ||
super(props); | ||
|
||
this.htmlNode = document.querySelector('html'); | ||
this.domNode = | ||
document.querySelector('#reactPortalSection') || document.body; | ||
} | ||
|
||
componentDidMount() { | ||
this.htmlNode.classList.add('no-scroll'); | ||
|
||
document | ||
.getElementsByTagName('body')[0] | ||
.addEventListener('keydown', this.handleEscapeKey); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.htmlNode.classList.remove('no-scroll'); | ||
|
||
document | ||
.getElementsByTagName('body')[0] | ||
.removeEventListener('keydown', this.handleEscapeKey); | ||
} | ||
|
||
handleEscapeKey = event => { | ||
if (keyPressMatch(event, KEYCODES.escape)) { | ||
this.closeModal(); | ||
} | ||
}; | ||
|
||
closeModal = () => { | ||
const { canBeClosed, onClose } = this.props; | ||
if (canBeClosed) { | ||
onClose(); | ||
} | ||
}; | ||
|
||
render() { | ||
const { | ||
children, canBeClosed, onClose, header, | ||
} = this.props; | ||
return ReactDOM.createPortal( | ||
<Overlay> | ||
<ModalContainer> | ||
<OffClickWrapper onOffClick={this.closeModal}> | ||
{canBeClosed && ( | ||
<CloseIconContainer onClick={onClose}> | ||
<CloseIcon /> | ||
</CloseIconContainer> | ||
)} | ||
{header} | ||
<CopyContainer>{children}</CopyContainer> | ||
</OffClickWrapper> | ||
</ModalContainer> | ||
</Overlay>, | ||
this.domNode | ||
); | ||
} | ||
} | ||
|
||
export default ImmersiveModal; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import styled from 'react-emotion'; | ||
|
||
import Typography from '../typography'; | ||
import { | ||
ANIMATION, | ||
BREAKPOINTS, | ||
COLORS, | ||
MEDIA_QUERIES, | ||
SPACING, | ||
Z_SCALE, | ||
} from '../../constants'; | ||
|
||
export const Overlay = styled.div` | ||
background-color: ${COLORS.overlay}; | ||
bottom: 0; | ||
left: 0; | ||
position: fixed; | ||
right: 0; | ||
top: 0; | ||
z-index: ${Z_SCALE.modal}; | ||
overflow-y: auto; | ||
`; | ||
|
||
export const ModalContainer = styled.div` | ||
background-color: ${COLORS.white}; | ||
height: 100vh; | ||
margin: 0 auto; | ||
max-width: ${BREAKPOINTS.sm}px; | ||
overflow-y: auto; | ||
position: relative; | ||
width: 100%; | ||
${MEDIA_QUERIES.smUp} { | ||
height: auto; | ||
margin-bottom: ${SPACING.base}; | ||
margin-top: ${SPACING.base}; | ||
} | ||
`; | ||
|
||
export const CloseIconContainer = styled.div` | ||
cursor: pointer; | ||
position: absolute; | ||
right: ${SPACING.small}; | ||
top: ${SPACING.small}; | ||
transform: scale(1, 1); | ||
transition: all ${ANIMATION.defaultTiming}; | ||
z-index: 2000; | ||
padding: ${SPACING.small}; | ||
background-color: ${COLORS.white}; | ||
border-radius: 50%; | ||
&:hover { | ||
transform: scale(1.1, 1.1); | ||
} | ||
`; | ||
|
||
export const CopyContainer = styled.div` | ||
padding: ${SPACING.medium} ${SPACING.base}; | ||
${MEDIA_QUERIES.mdUp} { | ||
padding: ${SPACING.large} ${SPACING.medium}; | ||
} | ||
`; | ||
|
||
export const Title = styled(Typography.Title)` | ||
margin-bottom: ${SPACING.small}; | ||
text-align: left; | ||
`; | ||
|
||
export const Body = styled.div` | ||
color: ${COLORS.purple80}; | ||
text-align: left; | ||
&:not(:last-child) { | ||
margin-bottom: ${SPACING.base}; | ||
} | ||
p > a { | ||
text-transform: none; | ||
} | ||
`; | ||
|
||
export const Footer = styled.div` | ||
margin-bottom: ${SPACING.xsmall}; | ||
`; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
|
||
import ImmersiveModal from './index'; | ||
|
||
const childComponent = <div />; | ||
|
||
describe('<ImmersiveModal />', () => { | ||
describe('ImmersiveModal closure', () => { | ||
it('invokes the onClose prop', () => { | ||
const spy = jest.fn(); | ||
const wrapper = shallow( | ||
<ImmersiveModal onClose={spy}> | ||
{childComponent} | ||
</ImmersiveModal> | ||
); | ||
|
||
wrapper.instance().closeModal(); | ||
|
||
expect(spy).toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.