-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Components: Add generic panel components to build the sidebar elements #789
Conversation
@jasmussen Right! There's not so much right now. It just adds some visual components, to be used to build the different panels. I'll be adding some "non-fonctional" UI elements before merging this. And I'll work separately on the first toggle maybe. |
@jasmussen Can you take another look? I've added a generic component to open/close the panel body. |
Love it. Works exactly like I hoped it would. I'll add a few style changes at some point, and we'll still need the #450 ingredient in order for it all to come together. But that shouldn't be a blocker for this. |
I've pushed this a bit forward, adding a generic |
be861f6
to
5222e32
Compare
I'm wondering if we could simplify the component to accept a header label as a prop, so that it can be rendered as as a single component instead of three: <Panel header={ __( 'Post Settings' ) }>
<PostStatus />
</Panel> But this would require that controls are consistent across panels, or customized with a separate prop (seems fine if it's a rarer case). |
5222e32
to
3c445d5
Compare
In Calypso there's sometimes a little "summary" note when the panel is collapsed: I don't necessarily think we want this in the core editor, initially as it's simply extra complexity. Also, if you can re-sort and hide panels, as well as have their open/closed state remembered (which we want for core but which Calypso does't do), then the added value is arguably minimal. |
@aduth I've always struggled to use this component in Calypso (SectionHeader) because of this enforced "shape", that's why I prefer the flexibility of a In the current mockups we have two panels. One with icons and one without icons. But I prefer to look at these panels as panels that could be reused in other parts of WP, that's why I opted for the most flexible approach.
@jasmussen Seems like an easy addition to the |
@aduth I think we could have a compromise where if we provide a Edit: Prop added in aaf06c2 |
3c445d5
to
1c64eab
Compare
editor/sidebar/post-status/index.js
Outdated
render() { | ||
return ( | ||
<PanelBody> | ||
<PanelBodyToggle |
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'm unsure:) Do you think it'd make any more sense to render this as a component which accepts children and manages "open" state internally?
<PanelToggleSection initialOpen={ true }>
<div className="editor-sidebar-post-status__row"></div>
</PanelToggleSection>
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 makes me think we should add this behavior to the PanelBody
instead, what do you think?
<PanelBody toggle={ __( 'Status & Visibility' ) } initialOpen={ true }>
<div className="editor-sidebar-post-status__row"></div>
</PanelBody>
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.
Sure, that seems fine. It's worked for us elsewhere as a generic Accordion component with initialExpanded
, title
(and per previous note, subtitle
).
editor/sidebar/post-status/index.js
Outdated
label={ __( 'Status & Visibility' ) } | ||
/> | ||
{ this.state.opened && | ||
<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.
Can we drop the extra wrapper?
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, we'll have a lot of rows later. I've just added one for now as an example.
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, we'll have a lot of rows later. I've just added one for now as an example.
I still don't see why even if we had many rows we'd need an empty wrapper.
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.
Sorry, I've overlooked here. It was necessary when the this.state.opened
check was in this file, but it's not anymore.
editor/sidebar/post-status/index.js
Outdated
<span>{ __( 'Pending review' ) }</span> | ||
<FormToggle | ||
checked={ false } | ||
onChange={ () => {} } |
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 we ought to define a defaultProps
for this handler on FormToggle
so it doesn't throw an error when not defined.
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 this would be always defined since this is just a non-functional example. I've added this.
I'm on the fence. We're not using it anywhere and I'm not sure about the future of this feature in React.
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'm on the fence. We're not using it anywhere and I'm not sure about the future of this feature in React.
Which feature? defaultProps
? If it were to be deprecated, there'd still be some mechanism for defining defaults, likely through destructuring:
const { onChange = noop } = this.props;
I note here because it seems odd to me we'd prefer to have an error thrown when the handler isn't defined.
components/panel-body/style.scss
Outdated
@@ -0,0 +1,4 @@ | |||
.components-panel-body { | |||
padding: 15px; | |||
box-shadow: 0 0 0 1px $light-gray-500; |
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.
At a glance, could we use border here instead? I'm sensitive to become overly reliant on box-shadow as it can have miserable performance.
http://tobiasahlin.com/blog/how-to-animate-box-shadow/
https://medium.com/airbnb-engineering/css-box-shadow-can-slow-down-scrolling-d8ea47ec6867
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.
A box-shadow is more "generic" because we could use several panels one after another and having only one line (the borders collapse) while this is not the case with border.
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.
Couldn't we recreate that with .panel + .panel { margin-top: -1px; }
?
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.
yep but not as easy, I'd like the PanelBody
, PanelHeader
and Panel
to be used together (multiple headers, multiple bodies) and in the same time we could use them separately. The three components would have this border and two of them could be nested inside the Panel
component.
I think your proposal is possible but we'd probably have a lot of use-cases. I'll try it
components/form-toggle/style.scss
Outdated
} | ||
|
||
.components-form-toggle { | ||
.accessible-focus &: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.
I don't think .accessible-focus
exists.
components/form-toggle/index.js
Outdated
} | ||
} | ||
|
||
FormToggle.idNum = 1; |
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.
Elsewhere we're using instances
instead of idNum
. I don't have a strong preference either way (aside maybe avoiding the abbreviation), but we should be consistent.
https://github.com/WordPress/gutenberg/blob/3c2db1d/editor/inserter/menu.js#L287
components/form-toggle/index.js
Outdated
} | ||
|
||
render() { | ||
const id = this.props.id || 'toggle-' + this.id; |
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.
My YAGNI senses are tingling on the yet-unused id
prop.
components/form-toggle/index.js
Outdated
disabled={ this.props.disabled } | ||
/> | ||
<label className="components-form-toggle__label" htmlFor={ id } > | ||
<span className="components-form-toggle__switch" |
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.
(Acknowledging it's sourced externally) this implementation seems overly complicated to me. I think it could be simplified if we allowed the checkbox to behave normally but hidden, then simply style the label based on the current state of the checkbox.
Example: https://codepen.io/aduth/pen/wdXmwK
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 now, I'm going to leave this as is, there are some accessibility concerns behind this implementation I think. The span here is playing the role of the hidden input.
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 would we leave this as-is? Just because it's copied from an external source doesn't mean it shouldn't be subject to the same scrutiny as any other code? So far as accessibility is concerned, my proposed simplification handles this better by not even bothering to try to create a fake input, but rather letting the true underlying input
handle the intended change behaviors.
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 would we leave this as-is? Just because it's copied from an external source doesn't mean it shouldn't be subject to the same scrutiny as any other code?
I totally agree
So far as accessibility is concerned, my proposed simplification handles this better by not even bothering to try to create a fake input, but rather letting the true underlying input handle the intended change behaviors.
I'm not very familiar with accessibility concerns but I thought that since we're hiding the input, we should provide a "replacement" simulating its behaviour. I can update the code if it's not case.
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'm not very familiar with accessibility concerns but I thought that since we're hiding the input, we should provide a "replacement" simulating its behaviour.
In my demo, it's more that the input is visually hidden, but still present on the page so that it can receive and react to focus and keyboard events natively. In the Calypso implementation, this was recreated on a static element with a set of ARIA and role props, tabIndex
for focus, and keyboard/click handlers. I raise primarily because it seems to me like needless and fragile complexity.
components/form-toggle/index.js
Outdated
return; | ||
} | ||
|
||
if ( event.key === 'Enter' || event.key === ' ' ) { |
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.
key
is slightly outside acceptable browser support (only available in latest versions of Safari)
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 confess I haven't took a deep look to the FormToggle component stolen from Calypso
If we kept the separate components, could we at least consolidate them into the |
components/panel/style.scss
Outdated
background: $white; | ||
border: 1px solid $light-gray-500; | ||
|
||
> .components-panel__header, |
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 the descendant selectors 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.
Technically we could have panels inside other panels, so to avoid applying the margin to the sub-panels, I've preferred to restrict it to the direct descendant only. It's safer IMO.
Edit: Or maybe you're proposing to avoid nesting at all? This allows the header and body to be usable outside of panels and as panel descendants as well.
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 think we should optimize for reusing the panel pieces, only the panels themselves at this stage. If something is reusuable outside a panel, then it should be a separate component and get its own class for clarity.
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.
Ok! moving those styles
75a27c3
to
5c090ff
Compare
Can I have a final look here? |
components/form-toggle/style.scss
Outdated
@@ -0,0 +1,55 @@ | |||
.components-form-toggle { | |||
position: relative; |
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.
Mixed whitespace here and in the selector below.
components/panel/body-toggle.js
Outdated
|
||
function PanelBodyToggle( { opened, label, onClick } ) { | ||
return ( | ||
<button className="components-panel__body-toggle" onClick={ onClick }> |
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.
Out of curiosity, how difficult would it be to render and style this as an IconButton
and avoid the need for a specialized PanelBodyToggle
altogether?
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.
Regardless, we should do one of either use Button
as a base (for consistency) or add type="button"
to avoid this acting like a submit
if ever used in a form.
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.
Not that difficult but my thinking here is that this component could be used as standalone. But maybe I'm optimising for unnecessary features.
this.setState( { | ||
opened: ! this.state.opened, | ||
} ); | ||
} |
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: For consistency, should have a blank line between toggle
and render
.
} | ||
|
||
toggle( event ) { | ||
event.preventDefault(); |
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.
Ah, I guess this would prevent the form submission anyways. Still might be good to be intentional with the type
prop on button
.
components/panel/body.js
Outdated
const { toggle, children } = this.props; | ||
return ( | ||
<div className="components-panel__body"> | ||
{ !! toggle && |
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.
At a glance it wasn't clear to me what toggle
represented. Found later it's the label. Should we call it something more along those lines? label
, title
, toggleLabel
, heading
?
components/panel/header.js
Outdated
@@ -0,0 +1,9 @@ | |||
function PanelHeader( { children } ) { | |||
return ( | |||
<div className="components-panel__header"> |
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.
Seems odd we push responsibility of wrapping strong
to the rendering component instead of baking in consistent behavior to the PanelHeader
itself. I see we have a need to exempt icons from bold effect, but maybe this could be achieved with a separate prop instead?
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.
Good point
editor/sidebar/post-status/index.js
Outdated
<div> | ||
<div className="editor-sidebar-post-status__row"> | ||
<span>{ __( 'Pending review' ) }</span> | ||
<FormToggle /> |
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 causes a warning to be logged about button
as descendant of button
(<FormToggle>
of <PanelBodyToggle>
)
react-dom.development.js:498 Warning: validateDOMNesting(...): <button> cannot appear as a descendant of <button>.
in button (created by Button)
in Button (created by IconButton)
in IconButton (created by PanelBodyToggle)
in button (created by PanelBodyToggle)
in PanelBodyToggle (created by PanelBody)
in div (created by PanelBody)
in PanelBody (created by PostStatus)
in PostStatus (created by PostSettings)
in div (created by Panel)
in Panel (created by PostSettings)
in PostSettings (created by Connect(PostSettings))
in Connect(PostSettings) (created by Sidebar)
in div (created by Sidebar)
in Sidebar (created by Layout)
in div (created by Layout)
in Layout (created by Connect(Layout))
in Connect(Layout)
in Provider
in Provider
I'm finding buttons to be rather difficult to work with due to restrictions on the types of of elements they accept as children.
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.
Good catch, with my docker setup, I was thinking I'm in debug mode by default, but it wasn't the case.
3a53bb6
to
7633875
Compare
Tks for the review @aduth I've addressed the raised points. |
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 generally looks good 👍 Left a few minor notes.
editor/sidebar/index.js
Outdated
<PostSettings /> | ||
<Panel> | ||
<PanelHeader> | ||
<strong> |
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 we use the new label
prop, or did you have challenges with the custom className? I'd expect label={ <span ... /> }
should work well enough.
editor/sidebar/post-status/index.js
Outdated
function PostStatus() { | ||
return ( | ||
<PanelBody title={ __( 'Status & Visibility' ) }> | ||
<div className="editor-sidebar-post-status__row"> |
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.
By className conventions we'd intentionally avoid reflecting nesting in the class name to allow the component to be more easily moved if necessary. I think this ought to be simplified to editor-post-status__row
. Else maybe rename the parent directory to sidebar-post-status
.
c37c83e
to
9d33716
Compare
This PR creates generic container components to build the sidebar panels