-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Modal component #6261
Modal component #6261
Conversation
components/modal/modal-content.js
Outdated
import withFocusReturn from '../higher-order/with-focus-return'; | ||
import withFocusContain from '../higher-order/with-focus-contain'; | ||
|
||
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.
There is a utility inside @wordpress/utils
named keycodes
for this, withkeycodes.ESCAPE
being the one you want.
} | ||
|
||
handleTabBehaviour( event ) { | ||
if ( event.keyCode === 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.
There is a utility inside @wordpress/utils
named keycodes
which will allow you to use a readable version of the keycode instead of using the number.
Rather than relying on a bunch of inline styles it would make sense for such component to have at least some basic styling in place, as otherwise once plugins start to adopt this component we'd be left with a bunch of different looking modals rather than having consistency. If the concern is that the component would then be too limited in how it looks, then maybe a prop could be introduced where it would remove the CSS class which is applying those styles. I have personally already built my own Modal component for a project which has a custom Map block with the ability to plot markers: For the most part this modal pretty much matches all the modals already in WordPress Core. The overlay background colour, the shadow on the modal itself, etc. I think a modal component should at the very minimum have a title prop whilst all of the body of the modal itself is left up to the plugin developer. Here is the inner markup I've used for my modal:
|
@paulwilde Thank you for your review. I agree it needs some styling, but because in gutenberg it should not overlay the WordPress-menu I wanted to create a specific gutenberg implementation in It also accepts classNames for both the content and the overlay. The style prop is mostly to keep it consistent with |
} | ||
|
||
componentWillUnmount() { | ||
this.focusContainRef.current.addEventListener( 'keydown', this.handleTabBehaviour ); |
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.
removeEventListener
?
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.
See next comment.
} | ||
|
||
componentDidMount() { | ||
this.focusContainRef.current.addEventListener( 'keydown', this.handleTabBehaviour ); |
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 bind this on the node directly instead of a prop onKeyDown
on the rendered div
?
Should we include a code comment informing future maintainers?
} | ||
|
||
handleTabBehaviour( event ) { | ||
if ( event.keyCode === 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.
Minor: Early return lets you avoid indenting the rest of the function, and I find to be generally more readable:
if ( event.keyCode !== 9 ) {
return;
}
components/modal/README.md
Outdated
@@ -0,0 +1,117 @@ | |||
RangeControl |
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.
Copy-pasta. 🍝
components/modal/modal-content.js
Outdated
|
||
componentDidMount() { | ||
// Focus on mount | ||
if ( this.props.focusOnMount ) { |
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.
Documentation claims this is default to true
but I'm not seeing where that takes effect.
components/modal/style.scss
Outdated
@@ -0,0 +1,9 @@ | |||
.components-modal { | |||
&__screen-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.
Minor: My recommendation would be to avoid the nesting and just create separate rules for them. This is a bit too clever.
.components-modal__screen-overlay {
components/modal/index.js
Outdated
} | ||
|
||
Modal.defaultProps = { | ||
className: null, |
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 value is providing a default here doing?
components/modal/index.js
Outdated
}; | ||
|
||
function setElements() { | ||
const wpwrapEl = document.getElementById( 'wpwrap' ); |
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.
This binds usage to a specific WordPress context, eliminating reusability of the component. The component shouldn't have any awareness of its ancestry. Can we pass this data via context instead?
|
||
if ( event.shiftKey && event.target === firstTabbable ) { | ||
event.preventDefault(); | ||
return lastTabbable.focus(); |
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/why are we return
-ing here?
components/modal/index.js
Outdated
...otherProps | ||
} = this.props; | ||
|
||
if ( ! this.node ) { |
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.
This should be in the constructor. A render
should have no side effects.
Updated instructions to see this modal in action (readme and instructions in the first comment need to be updated): In First, import what is needed: Then, after the
|
} | ||
|
||
handleTabBehaviour( event ) { | ||
if ( ! event.keyCode === TAB ) { |
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.
This is false for any key pressed, please double check. It should be if ( event.keyCode !== TAB )
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 clarify what's happening here, the negation occurs before the equality check, so this is effectively:
if ( false === TAB ) {
Makes me wish this ESLint rule were expanded to cover this case: https://eslint.org/docs/rules/no-unsafe-negation
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 idea how I made this mistake... :/
components/modal/frame.js
Outdated
style={ style } | ||
ref={ this.containerRef } | ||
role="dialog" | ||
aria-modal={ true } |
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.
For some reason, aria-modal="true"
prevents Safari + VoiceOver to read out the content of the modal when using Control-Option-Arrows. This doesn't happen on react-modal
because it (incorrectlysets it on the overlay.
aria-modalis new in ARIA 1.1 and apparently not well supported. We should investigate this, since its effect is supposed to be the same we get with
aria-hidden` on the content outside the modal, maybe we should just not use it for now.
components/modal/header.js
Outdated
className={ 'components-modal__header' } | ||
> | ||
<div> | ||
<span aria-hidden="true"> |
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.
we should avoid to render an empty span if an icon prop is not passed
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.
Looks good, thanks for working through this one. I pushed a couple revisions in 1a0bf75 and 8778706 .
I also observed an unexpected behavior where multiple div
were created in the parent element. In a strange coincidence, I found this tracked back to another issue I discovered today with StrictMode
where apparently two copies of the component are being constructed, thus the extra node. This will only apply to development environments, not the plugin distributable. It's an issue we should sort through, but outside the scope of these immediate changes.
}; | ||
} | ||
|
||
return forwardRef( ( props, ref ) => { |
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.
Should this be built-in to
createHigherOrderComponent
?See similar mention at #6480 (comment)
This comment has yet to be addressed or responded.
For future readers, see related effort at #7557 .
components/modal/index.js
Outdated
* | ||
* @param {Object} nextProps The component's next props. | ||
*/ | ||
componentWillReceiveProps( nextProps ) { |
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.
componentWillReceiveProps
is effectively deprecated.
https://reactjs.org/docs/react-component.html#the-component-lifecycle
For this type of usage, you may consider static getDerivedStateFromProps
instead:
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
However, in this case I don't think this requires state. I think we can just use the withInstanceId
higher-order component.
I will make these revisions in an upcoming commit.
🎉 thank you for landing this one! @xyfi @atimmer @omarreiss, what are next steps planned? We still need to provide Plugins API integration with pinning support for easier consumption for plugin developers. I see that #6241 contains some logic in that area. Do you plan to continue working on it or should I put up some PR next week? |
@gziolo Tested and should the |
@xyfi @jasmussen any thoughts on this one? I didn't follow closely design decisions made for this PR. |
It's meant to be white, it's more coherent with the design of the editor. |
This should be discussed during a dev chat and preferably with @helen |
These modals will also work differently in how they are invoked. |
@mtias I see, then why does the overlay color is using darker version on the screenshots above? Thanks! |
Dev chat isn't really the appropriate venue for discussing Gutenberg design issues, that's what the design meetings are for, as well as the editor meetings. There's certainly a discussion to be had over ensuring consistency, but I'll leave it to @karmatosed as the Gutenberg Design Lead to expend on her thoughts here. Given that Gutenberg introduces a whole pile of new design elements to WordPress, many of which are different to existing elements, it seems to me there's a reasonable argument that Gutenberg is introducing a new design language, so there will be some inconsistencies for a while, as the scope of Gutenberg expands beyond the editor page, to site customisation, and beyond. |
As I see it this is not just a "Gutenberg design" issue. It's about integration in WordPress and introduction of new patterns in WordPress so, in my opinion, dev chat is the appropriate place to discuss it. |
I agree with @pento there is no need to have this discussed in a dev chat. This issue works for discussion. If it were to happen in any chat this would be for a design chat, not dev one also. This is talking about inconsistencies in design and would be something the design team (who meet every week) could discuss there. I still think that's the wrong approach and standby it should be within this issue. In many patterns new design has been created within Gutenberg that doesn't exist in core. Some does, some doesn't. We don't and shouldn't limit everything to what came before. We can respect it but maybe we have a better option and that's to have an open mind about. On merge perhaps over time core will adopt the new modals. Regarding the modal, ideally we do have one treatment but we don't have to be limited by what exists today in core. Therefore, I standby the work done here so far. |
We agree we disagree 🙂Introducing new patterns without considering the overall impact and consistency with the existing ones in core is not necessarily good. I'm not saying which one is better. I'd just like to see consistency. In my opinion, it's a responsibility of the Gutenberg team to make sure consistency is respected. In this specific case, if the team thinks a lighter overlay is better, then it's a team responsibility to make a proposal to change the overlays in core. |
* Add mode to directive processing * Add #59886 wp-develop fix * Add #6251 fix * Fix linter * Include #6261 wp-develop * Fix spacing Co-authored-by: c4rl0sbr4v0 <[email protected]> Co-authored-by: SantosGuillamot <[email protected]> Co-authored-by: sirreal <[email protected]>
* Add mode to directive processing * Add WordPress#59886 wp-develop fix * Add WordPress#6251 fix * Fix linter * Include WordPress#6261 wp-develop * Fix spacing Co-authored-by: c4rl0sbr4v0 <[email protected]> Co-authored-by: SantosGuillamot <[email protected]> Co-authored-by: sirreal <[email protected]>
Description
Adds a modal component. A more feature rich version of the one used in #6244.
Note: I've attempted to mimic the API from
react-modal
as much as possible.Controlling whether the modal shows or not is the responsibility of the developer that implements the modal, using the
isOpen
props.The modal mounts itself in the document body using
createPortal
.For accessibility reasons, when opening a modal all other elements in the body get an
aria-hidden="true"
attribute, except for script tags and elements that already have anaria-hidden="true"
element.How has this been tested?
To see an overview of its features go to
components/modal/README.md
.To test create a
modal.js
file in the./edit-post
folder with tthe following contents and addimport './modal';
somewhere inedit-post/index.js
.Screenshots
Ouput of the code above:
Types of changes
New feature
Checklist:
Note: Tests are failing due to enzyme not recognizing the React forwardRef function.
Forward ref enzymejs/enzyme#1592