-
Notifications
You must be signed in to change notification settings - Fork 326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add overlay component #3466
Add overlay component #3466
Changes from 16 commits
0c69657
9457190
57fb0f3
6bc6ed0
eb359dc
eab5d26
296e089
aa65fce
62a5097
78ec0ea
afc0b32
6a36954
ff19ff0
a5388cd
273688e
e6753cb
a391c12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
The Backdrop component serves as a simple solution to create a backdrop for modals and other kinds of overlays. | ||
The Backdrop component serves as a simple solution to create a backdrop for overlays. | ||
|
||
Here is a basic example of the component. The open state of the backdrop is controlled by the `isOpen` property. | ||
|
||
|
@@ -7,7 +7,7 @@ intialState = {open: false}; | |
|
||
<div> | ||
<button onClick={() => setState({open: true})}>Open Backdrop</button> | ||
<Backdrop isOpen={state.open} onClick={() => setState({open: false})} /> | ||
<Backdrop isOpen={!!state.open} onClick={() => setState({open: false})} /> | ||
</div> | ||
``` | ||
|
||
|
@@ -18,6 +18,6 @@ intialState = {open: false}; | |
|
||
<div> | ||
<button onClick={() => setState({open: true})}>Open Backdrop</button> | ||
<Backdrop isVisible={false} isOpen={state.open} onClick={() => setState({open: false})} /> | ||
<Backdrop isVisible={false} isOpen={!!state.open} onClick={() => setState({open: false})} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the double bang? We actually said we don't do them anymore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The make sense sometimes. Especially at the points where you really expect a boolean and not just a truthy type. At this point |
||
</div> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,16 @@ | ||
@import '../../containers/Application/colors.scss'; | ||
|
||
$backdropBackground: $black; | ||
$backdropBackground: rgba($dustyGray, .9); | ||
|
||
.backdrop { | ||
position: fixed; | ||
top: 0; | ||
left: 0; | ||
width: 100%; | ||
height: 100%; | ||
background-color: $backdropBackground; | ||
opacity: 0; | ||
background: transparent; | ||
|
||
&.is-visible { | ||
opacity: .4; | ||
background: $backdropBackground; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// @flow | ||
import React from 'react'; | ||
import type {Action} from './types'; | ||
import actionsStyles from './actions.scss'; | ||
|
||
type Props = { | ||
actions: Array<Action>, | ||
}; | ||
|
||
export default class Actions extends React.PureComponent<Props> { | ||
render() { | ||
const {actions} = this.props; | ||
if (!actions.length) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className={actionsStyles.actions}> | ||
{actions.map((action, index) => { | ||
const handleButtonClick = action.onClick; | ||
return ( | ||
<button | ||
key={index} | ||
className={actionsStyles.action} | ||
onClick={handleButtonClick}>{action.title}</button> | ||
); | ||
})} | ||
</div> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// @flow | ||
import classNames from 'classnames'; | ||
import Mousetrap from 'mousetrap'; | ||
import {observable, action} from 'mobx'; | ||
import {observer} from 'mobx-react'; | ||
import type {Node} from 'react'; | ||
import React from 'react'; | ||
import Portal from 'react-portal'; | ||
import Icon from '../Icon'; | ||
import {afterElementsRendered} from '../../services/DOM'; | ||
import Backdrop from '../Backdrop'; | ||
import type {Action} from './types'; | ||
import Actions from './Actions'; | ||
import overlayStyles from './overlay.scss'; | ||
|
||
type Props = { | ||
title: string, | ||
children: Node, | ||
actions: Array<Action>, | ||
confirmText: string, | ||
onConfirm: () => void, | ||
isOpen: boolean, | ||
onRequestClose: () => void, | ||
}; | ||
|
||
const CLOSE_ICON = 'times'; | ||
|
||
@observer | ||
export default class Overlay extends React.PureComponent<Props> { | ||
static defaultProps = { | ||
isOpen: false, | ||
actions: [], | ||
}; | ||
|
||
@observable isVisible: boolean = false; | ||
@observable isOpenHasChanged: boolean = false; | ||
|
||
@action componentWillMount() { | ||
Mousetrap.bind('esc', this.close); | ||
this.isOpenHasChanged = this.props.isOpen; | ||
} | ||
|
||
componentWillUnmount() { | ||
Mousetrap.unbind('esc', this.close); | ||
} | ||
|
||
componentDidMount() { | ||
this.toggle(); | ||
} | ||
|
||
@action componentWillReceiveProps(newProps: Props) { | ||
this.isOpenHasChanged = newProps.isOpen !== this.props.isOpen; | ||
} | ||
|
||
componentDidUpdate() { | ||
this.toggle(); | ||
} | ||
|
||
close = () => { | ||
this.props.onRequestClose(); | ||
}; | ||
|
||
@action toggle() { | ||
afterElementsRendered(action(() => { | ||
if (this.isOpenHasChanged) { | ||
this.isVisible = this.props.isOpen; | ||
} | ||
})); | ||
} | ||
|
||
@action handleTransitionEnd = () => { | ||
afterElementsRendered(action(() => { | ||
this.isOpenHasChanged = false; | ||
})); | ||
}; | ||
|
||
handleIconClick = () => { | ||
this.close(); | ||
}; | ||
|
||
render() { | ||
const containerClass = classNames({ | ||
[overlayStyles.container]: true, | ||
[overlayStyles.isDown]: this.isVisible, | ||
}); | ||
const {isOpen, title, actions, onConfirm, confirmText, children} = this.props; | ||
|
||
return ( | ||
<Portal isOpened={isOpen || this.isOpenHasChanged}> | ||
<div | ||
className={containerClass} | ||
onTransitionEnd={this.handleTransitionEnd}> | ||
<div className={overlayStyles.overlay}> | ||
<section className={overlayStyles.content}> | ||
<header> | ||
{title} | ||
<Icon | ||
name={CLOSE_ICON} | ||
className={overlayStyles.icon} | ||
onClick={this.handleIconClick} /> | ||
</header> | ||
<article>{children}</article> | ||
<footer> | ||
<Actions actions={actions} /> | ||
<button className={overlayStyles.confirmButton} onClick={onConfirm}> | ||
{confirmText} | ||
</button> | ||
</footer> | ||
</section> | ||
</div> | ||
<Backdrop local={true} onClick={this.props.onRequestClose} /> | ||
</div> | ||
</Portal> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
The overlay component let's you display some content above everything else. | ||
It renders depending on the passed property and request being closed through a callback. | ||
|
||
``` | ||
initialState = {open: false}; | ||
const actions = [ | ||
{title: 'Destroy world', onClick: () => {/* destroy world */}}, | ||
{title: 'Save world', onClick: () => {/* save world */}}, | ||
]; | ||
const onConfirm = () => { | ||
/* do confirm things */ | ||
setState({open: false}); | ||
}; | ||
|
||
<div> | ||
<button onClick={() => setState({open: true})}>Open overlay</button> | ||
<Overlay | ||
title="Njan Njan Njan" | ||
onRequestClose={() => setState({open: false})} | ||
actions={actions} | ||
confirmText="Apply" | ||
onConfirm={onConfirm} | ||
isOpen={state.open}> | ||
<div style={{width: '900px', height: '500px', display: 'flex', alignItems: 'center', justifyContent: 'center'}}> | ||
<img src="http://www.nyan.cat/cats/original.gif" /> | ||
</div> | ||
</Overlay> | ||
</div> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
@import '../../containers/Application/colors.scss'; | ||
|
||
$actionFontSize: 14px; | ||
$actionColor: $black; | ||
$height: 90px; | ||
|
||
.actions { | ||
line-height: $height; | ||
} | ||
|
||
.action { | ||
padding: 0; | ||
margin: 0 10px 0 0; | ||
border: 0; | ||
background: transparent; | ||
cursor: pointer; | ||
text-decoration: underline; | ||
font-size: $actionFontSize; | ||
color: $actionColor; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// @flow | ||
import Overlay from './Overlay'; | ||
|
||
export default Overlay; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the double bang?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because initially the
state.open
isundefined
in styleguidist. As the default value for isOpen istrue
the Backdrop opens automatically. That's why the backdrops were open on page load all the time.