Skip to content
This repository has been archived by the owner on Jun 20, 2022. It is now read-only.

Commit

Permalink
Merge pull request #9 from smooth-code/modals
Browse files Browse the repository at this point in the history
feat: add Modal
  • Loading branch information
gregberge authored May 29, 2018
2 parents 3935f6a + 88bc002 commit 0262a1c
Show file tree
Hide file tree
Showing 18 changed files with 551 additions and 188 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'react/forbid-prop-types': 'off',
'react/prop-types': 'off',
'react/require-default-props': 'off',
'react/sort-comp': 'off',

'import/prefer-default-export': 'off',

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
"recompact": "^3.3.0"
},
"peerDependencies": {
"react": "^16.2.0",
"react-dom": "^16.2.0",
"styled-components": "^3.1.6"
"react": ">=16.0.0",
"react-dom": ">=16.0.0",
"styled-components": "=>3.0.0"
}
}
82 changes: 54 additions & 28 deletions src/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import styled from 'styled-components'
import classNames from 'classnames'
import * as defaultTheme from './theme/defaultTheme'
import { th } from './utils'
import Transition from './Transition'

class Portal extends React.Component {
class ModalComponent extends React.Component {
constructor(props) {
super(props)
if (!this.container && typeof document !== 'undefined') {
Expand All @@ -16,41 +17,57 @@ class Portal extends React.Component {
}
}

handleKeyup = ({ keyCode }) => {
if (keyCode === 27 /* Escape */) {
this.props.onClose()
}
}

componentWillUnmount() {
document.body.removeChild(this.container)
document.removeEventListener('keyup', this.handleKeyup)
}

componentDidUpdate() {
if (this.props.opened) {
document.body.style.overflow = 'hidden'
document.addEventListener('keyup', this.handleKeyup)
} else {
document.body.style.overflow = null
document.removeEventListener('keyup', this.handleKeyup)
}
}

render() {
const { className, theme, opened, onClose, children, ...props } = this.props
if (!this.container) return null
return createPortal(this.props.children, this.container)
return createPortal(
<Transition ms={theme.modalTransitionDuration} toggle={this.props.opened}>
{({ entering, exiting }) => (
<div
role="dialog"
tabIndex="-1"
className={classNames(
'sui-modal',
{
'sui-modal-opened': opened || exiting || entering,
'sui-modal-fade-in': entering,
'sui-modal-fade-out': exiting,
},
className,
)}
{...props}
>
<div className="sui-modal-backdrop" onClick={onClose} />
{children}
</div>
)}
</Transition>,
this.container,
)
}
}

const ModalComponent = ({
className,
theme,
opened,
onClose,
children,
...props
}) => (
<Portal>
<div
role="dialog"
tabIndex="-1"
className={classNames(
'sui-modal',
{ 'sui-modal-opened': opened },
className,
)}
{...props}
>
<div className="sui-modal-backdrop" onClick={onClose} />
{children}
</div>
</Portal>
)

const Modal = styled(ModalComponent)`
position: fixed;
top: 0;
Expand All @@ -62,15 +79,22 @@ const Modal = styled(ModalComponent)`
overflow: hidden;
outline: 0;
opacity: 0;
transition: opacity 1000ms;
transition: opacity ${th('modalTransitionDuration')}ms;
&.sui-modal-opened {
display: block;
overflow-x: hidden;
overflow-y: auto;
}
&.sui-modal-fade-in {
opacity: 1;
}
&.sui-modal-fade-out {
opacity: 0;
}
.sui-modal-backdrop {
position: fixed;
top: 0;
Expand All @@ -84,6 +108,8 @@ const Modal = styled(ModalComponent)`

Modal.propTypes = {
children: PropTypes.node,
opened: PropTypes.bool,
onClose: PropTypes.func,
}

Modal.defaultProps = {
Expand Down
93 changes: 92 additions & 1 deletion src/Modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const ModalDialogExample = ModalDialog.extend`

### Live example

You can toggle a modal from a `Button` using the `Toggler`.

```js
const ModalExample = () => (
<Toggler>
Expand All @@ -55,7 +57,96 @@ const ModalExample = () => (
<ModalBody>Modal body</ModalBody>
<ModalFooter>
<Button variant="primary">Save changes</Button>
<Button variant="secondary">Close</Button>
<Button variant="secondary" onClick={() => onToggle(false)}>
Close
</Button>
</ModalFooter>
</ModalContent>
</ModalDialog>
</Modal>
</div>
)}
</Toggler>
)
;<ModalExample />
```

### Scrolling long content

When modals become too long for the user’s viewport or device, they scroll independent of the page itself. Try the demo below to see what we mean.

```js
const ModalExample = () => (
<Toggler>
{({ toggled, onToggle }) => (
<div>
<Button variant="primary" onClick={() => onToggle(true)}>
Open modal
</Button>
<Modal opened={toggled} onClose={() => onToggle(false)}>
<ModalDialog>
<ModalContent>
<ModalHeader>
<Typography variant="h5" margin={false}>
Modal title
</Typography>
</ModalHeader>
<ModalBody>
Cras mattis consectetur purus sit amet fermentum. Cras justo
odio, dapibus ac facilisis in, egestas eget quam. Morbi leo
risus, porta ac consectetur ac, vestibulum at eros. Praesent
commodo cursus magna, vel scelerisque nisl consectetur et.
Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor
auctor. Aenean lacinia bibendum nulla sed consectetur. Praesent
commodo cursus magna, vel scelerisque nisl consectetur et. Donec
sed odio dui. Donec ullamcorper nulla non metus auctor
fringilla. Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus
dolor auctor. Aenean lacinia bibendum nulla sed consectetur.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor
fringilla. Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus
dolor auctor. Aenean lacinia bibendum nulla sed consectetur.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor
fringilla. Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus
dolor auctor. Aenean lacinia bibendum nulla sed consectetur.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor
fringilla. Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus
dolor auctor. Aenean lacinia bibendum nulla sed consectetur.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor
fringilla. Cras mattis consectetur purus sit amet fermentum.
Cras justo odio, dapibus ac facilisis in, egestas eget quam.
Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Vivamus sagittis lacus vel augue laoreet rutrum faucibus
dolor auctor. Aenean lacinia bibendum nulla sed consectetur.
Praesent commodo cursus magna, vel scelerisque nisl consectetur
et. Donec sed odio dui. Donec ullamcorper nulla non metus auctor
fringilla.
</ModalBody>
<ModalFooter>
<Button variant="primary">Save changes</Button>
<Button variant="secondary" onClick={() => onToggle(false)}>
Close
</Button>
</ModalFooter>
</ModalContent>
</ModalDialog>
Expand Down
23 changes: 16 additions & 7 deletions src/ModalBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,40 @@ import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import classNames from 'classnames'
import handleRef from './internal/handleRef'
import setWithComponent from './internal/setWithComponent'
import * as defaultTheme from './theme/defaultTheme'
import { mixin } from './utils'
import { mixin, th } from './utils'

const ModalBodyComponent = ({ className, theme, ...props }) => (
<div className={classNames('sui-modal-body', className)} {...props} />
const ModalBodyComponent = ({
className,
component: Component = 'div',
theme,
...props
}) => (
<Component className={classNames('sui-modal-body', className)} {...props} />
)

const ModalBody = styled(ModalBodyComponent)`
const ModalBodyRefComponent = handleRef(ModalBodyComponent)

const ModalBody = styled(ModalBodyRefComponent)`
${mixin('base')};
position: relative;
/* Enable "flex-grow: 1" so that the body take up as much space as possible */
/* when should there be a fixed height on ModalDialog. */
flex: 1 1 auto;
padding: 1rem;
padding: ${th('modalInnerPadding')};
`

ModalBody.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
theme: PropTypes.object,
}

ModalBody.defaultProps = {
theme: defaultTheme,
}

setWithComponent(ModalBody, ModalBodyRefComponent)

/** @component */
export default ModalBody
40 changes: 31 additions & 9 deletions src/ModalContent.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,61 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import styled, { css } from 'styled-components'
import classNames from 'classnames'
import handleRef from './internal/handleRef'
import setWithComponent from './internal/setWithComponent'
import * as defaultTheme from './theme/defaultTheme'
import { mixin } from './utils'
import { mixin, th, up } from './utils'

const ModalContentComponent = ({ className, theme, ...props }) => (
<div className={classNames('sui-modal-content', className)} {...props} />
const ModalContentComponent = ({
className,
component: Component = 'div',
theme,
...props
}) => (
<Component
className={classNames('sui-modal-content', className)}
{...props}
/>
)

const ModalContentRefComponent = handleRef(ModalContentComponent)

const ModalContent = styled(ModalContentComponent)`
${mixin('base')};
position: relative;
display: flex;
flex-direction: column;
/* Ensure "ModalContent" extends the full width of the parent "ModalDialog" */
width: 100%;
/* Counteract the pointer-events: none; in the ModalDialog */
pointer-events: auto;
background-color: #fff;
background-color: ${th('modalContentBg')};
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 0.3rem;
border: ${th('modalContentBorderWidth')} solid
${th('modalContentBorderColor')};
border-radius: ${th('modalContentBorderRadius')};
box-shadow: ${th('modalContentBoxShadowXs')};
/* Remove focus outline from opened modal */
outline: 0;
${up(
'sm',
css`
box-shadow: ${th('modalContentBoxShadowSmUp')};
`,
)};
`

ModalContent.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
theme: PropTypes.object,
}

ModalContent.defaultProps = {
theme: defaultTheme,
}

setWithComponent(ModalContent, ModalContentRefComponent)

/** @component */
export default ModalContent
Loading

0 comments on commit 0262a1c

Please sign in to comment.