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
Conversation
@@ -27,6 +28,7 @@ | |||
"enzyme-to-json": "^1.5.1", | |||
"identity-obj-proxy": "^3.0.0", | |||
"jest": "^20.0.4", | |||
"pretty": "^2.0.0", |
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.
pretty
is just a dev-dependency which beautifies a string of html. This is used for testing the html of the overlay which is ported to the body.
import {observer} from 'mobx-react'; | ||
|
||
@observer | ||
export default class ClickModal extends React.PureComponent { |
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.
I think I would wait with the merge until we know how we name this.
render() { | ||
return ( | ||
<div className={this.props.className}> | ||
{React.cloneElement(this.props.clickElement, {onClick: this.handleElementClick})} |
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 do we need to clone something here?
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.
To pass an extra property
this.modalOpen = true; | ||
}; | ||
|
||
@action handleModalRequestClose = () => { |
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.
Just handleRequestClose
?
<div className={modalStyle.modal}> | ||
{this.props.children} | ||
</div> | ||
<div onClick={this.handleBackdropClick} className={modalStyle.backdrop} /> |
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.
Wasn't the idea to move that into an own Backdrop
component, so that it can also be used for the dropdowns?
$backgroundColor: $white; | ||
$modalShadow: 0 0 25px 0 rgba($black, 0.25); | ||
|
||
.container { |
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.
.modal-container
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.
Isn't the major advantage of css-modules that we can use the same classes in multiple files without caution? modal
is mentioned in the name of the file.
</ClickModal> | ||
).render(); | ||
expect(view).toMatchSnapshot(); | ||
expect(body.innerHTML).toBe(''); |
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.
Shouldn't that contain at least the button with the Open modal
text? Or am I missing something.
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.
The view is rendered in a virtual document as far as I know. Only the stuff in the portal is put in the body mentioned here.
|
||
expect(body.innerHTML).toBe(''); | ||
view.find('button').simulate('click'); | ||
expect(pretty(body.innerHTML)).toMatchSnapshot(); |
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.
Related to the question above: Don't you make a far to big snapshot this way?
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.
As mentioned, the only thing in the body is the stuff of the portal and that's exactly the things a snapshot should be taken of.
const body = document.body; | ||
const view = mount(<Modal isOpen={true}><p>Modal content</p></Modal>).render(); | ||
expect(view.html()).toBe(null); | ||
expect(pretty(body.innerHTML)).toMatchSnapshot(); |
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.
Same as above
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.
As mentioned, the only thing in the body is the stuff of the portal and that's exactly the things a snapshot should be taken of.
const backdrop = view.find('.backdrop'); | ||
expect(backdrop.length).toBe(1); | ||
|
||
expect(requestCloseSpy).toHaveBeenCalledTimes(0); |
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.
I have used toBeCalledTimes
until now, which is an alias for that. Would be nice to be consistent here.
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.
Where is this function situated? If I replace toHaveBeenCalledTimes
with toBeCalledTimes
, I get that toBeCalledTimes
is not a function.
41f9dcf
to
47ee3f7
Compare
5894bb7
to
a844fa8
Compare
@@ -8,12 +8,15 @@ type Props = { | |||
isOpen: boolean, | |||
/** When set to false the backdrop renders transparent. */ | |||
isVisible: boolean, | |||
inPortal: boolean, |
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.
I don't really get what this prop is doing.
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.
local
@@ -1,17 +1,16 @@ | |||
@import '../../containers/Application/colors.scss'; | |||
|
|||
$backdropBackground: $black; | |||
$backdropBackground: rgba($dustyGrey, .9); |
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.
Was this color decided by the designers?
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.
yes
'fa', | ||
'fa-' + this.props.name | ||
); | ||
const {className, name, ...otherProps} = this.props; |
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.
What do you need the otherProps
for?
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.
create explicit onclick
}; | ||
|
||
@observer | ||
export default class ClickModal extends React.PureComponent<Props> { |
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.
I am still not a big fan of this component... Don't you think it would make more sense to implement this as a high order component?
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.
I can't see the advantages of using a HOC here.
onRequestClose: () => void, | ||
}; | ||
|
||
const ESC_KEY = 27; |
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.
I think that should go somewhere else, or we should include a library handling this for us. I don't want to define the ESC
keycode in every component 😉
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.
If I remember correctly @chirimoya found a react library handling that using decorators.
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.
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.
react-keydown
only listens to keydowns within the component elements. Because we use portals in the modal it doesn't really work. I created an issue in react-keydown
: glortho/react-keydown#68
<Portal isOpened={this.props.isOpen || this.isOpenHasChanged}> | ||
<div | ||
className={containerClasses} | ||
onTransitionEnd={this.handleTransitionEnd} > |
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.
No space before >
<div | ||
className={containerClasses} | ||
onTransitionEnd={this.handleTransitionEnd} > | ||
<div className={modalStyles.box}> |
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 not moving that class to the ModalBox
?
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 it handles the transition. The modal box is just the element which doesn't care where its placed and how it appears.
actions={this.props.actions} | ||
onRequestClose={this.props.onRequestClose} | ||
onConfirm={this.props.onConfirm} | ||
confirmText={this.props.confirmText} > |
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.
use destructuring for all these variables.
className={modalBoxStyles.confirmButton} | ||
onClick={this.props.onConfirm}>{this.props.confirmText}</button> | ||
</div> | ||
</div> |
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.
I think we should make use of the section
, header
and footer
tags here, shouldn't we?
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.
And maybe also put them in separate components? Having a render*
method is usually a sign that it might be better to put into a separate component.
|
||
export type Action = { | ||
title: string, | ||
handleAction: () => void, |
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 not onClick
?
I think the name of the component is a bit controversial... We'll have two different kind of overlays, and both of them will be modals. But we are already occupying this term for one of these overlays... |
@danrot are we not able to manage the different kind of overlays in one component? |
@danrot The other kind of modal you talk about are "Dialogs" for me. |
57ab1c7
to
475b110
Compare
@chirimoya TBH I don't know yet... There are no designs 🙈 It's again the question if we build it using some configuration, or if the differences are so big that they justify a separate component. @mmsbrggr Both of them are modals per definitions. A modal is something that requires immediate attention and blocks the rest of the application, which is true for both components (if there will be two) |
@@ -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})} /> |
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
is undefined
in styleguidist. As the default value for isOpen is true
the Backdrop opens automatically. That's why the backdrops were open on page load all the time.
@@ -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 comment
The 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 comment
The 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 undefined
and false
make a difference.
@@ -13,6 +13,11 @@ test('The component should render in body when open', () => { | |||
expect(pretty(body.innerHTML)).toMatchSnapshot(); | |||
}); | |||
|
|||
test('The component should not render in body when property is set', () => { |
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.
when local property
<Modal | ||
isOpen={this.modalOpen} | ||
onRequestClose={this.handleRequestClose} | ||
{...modalProps} > |
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.
No space before >
(there has to be an eslint rule for that...)
className={modalBoxStyles.icon} | ||
onClick={this.props.onRequestClose} /> | ||
</header> | ||
<section> |
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.
I would have left this one a div
and the outer div
would be the section
for me, since a section
is allowed to have headers
and footers
(if I am not mistaken).
return ( | ||
<div className={modalBoxStyles.box}> | ||
<header> | ||
{this.props.title} |
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.
use destructuring
2fc28e0
to
bbc5a24
Compare
|
||
export default class Actions extends React.PureComponent<Props> { | ||
render() { | ||
if (this.props.actions.length > 0) { |
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.
use destructuring? At first I thought it doesn't make any sense, but you are using it twice, so it does for me.
actions: [], | ||
}; | ||
|
||
@keydown('esc') requestClose() { |
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.
rename to handleRequestClose
?
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.
The component doesn't handle a request close call, but really requests to be closed.
<section className={modalBoxStyles.box}> | ||
<header> | ||
{title} | ||
<Icon name={CLOSE_ICON} className={modalBoxStyles.icon} onClick={onRequestClose} /> |
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.
use requestClose
resp. handleRequestClose
directly? What if we have to do more than just calling one callback?
title="Njan Njan Njan" | ||
onRequestClose={() => setState({open: false})} | ||
actions={actions} | ||
confirmText="Apply" |
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.
Maybe also add a onConfirm
callback?
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.
I added it to both examples
clickElement={button} | ||
title="Nana Nana Nana" | ||
actions={actions} | ||
confirmText="Ok"> |
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.
Also add a onConfirm
callback?
@danrot I couldn't find a possibility to forbid spaces before ">" in the starting tag via es-lint. I created an issue jsx-eslint/eslint-plugin-react#1396 |
211a513
to
80989de
Compare
export default class Actions extends React.PureComponent<Props> { | ||
render() { | ||
const {actions} = this.props; | ||
if (actions.length > 0) { |
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.
early return
}; | ||
|
||
@observer | ||
export default class ClickModal extends React.PureComponent<Props> { |
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.
Rename to ClickOverlay
?
|
||
@observer | ||
export default class ClickModal extends React.PureComponent<Props> { | ||
@observable modalOpen = false; |
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.
Rename to isOpen
?
|
||
const CLOSE_ICON = 'times'; | ||
|
||
export default class ModalBox extends React.PureComponent<Props> { |
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.
OverlayBox
? Box
is strange somehow, because it doesn't transport a real message for me... Maybe there is a better alternative.
import Icon from '../Icon'; | ||
import type {ModalProps} from './types'; | ||
import Actions from './Actions'; | ||
import modalBoxStyles from './modalBox.scss'; |
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.
Rename to overlayBoxStyles
and overlayBox.scss
?
import Modal from './Modal'; | ||
|
||
export {ClickModal}; | ||
export default Modal; |
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.
How should these imports be handled?
- Move the
ClickModal
to its own component folder? - Have two named exports?
- Decide on which one is the more "default" one, and the other is the named export?
- Is there an actual need for both kind of modals? Could imagine that we always only open the modals when an element is clicked.
import Backdrop from '../Backdrop'; | ||
import ModalBox from './ModalBox'; | ||
import type {ModalProps} from './types'; | ||
import modalStyles from './modal.scss'; |
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.
Rename to overlayStyles
?
What's in this PR?
This PR contains a modal components which displays content above everything else.
Example Usage