Skip to content
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

Merged
merged 18 commits into from
May 17, 2017

Conversation

youknowriad
Copy link
Contributor

This PR creates generic container components to build the sidebar panels

<Panel>
	<PanelHeader>
		// Panel Header content
	</PanelHeader>
	<PanelBody>
		// Panel Body content
	</PanelBody>
</Panel>

@youknowriad youknowriad self-assigned this May 15, 2017
@youknowriad youknowriad requested a review from jasmussen May 15, 2017 09:24
@jasmussen
Copy link
Contributor

I love it! I love that it's something you're working on. Thank you!

There's not a ton to see yet, correct?

screen shot 2017-05-15 at 11 54 49

Markup looks good. Only one panel migth use different markup as far as I can imagine right now, revisions:

screen shot 2017-05-15 at 11 55 38

And even that panel can launch with less.

@youknowriad
Copy link
Contributor Author

@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.

@youknowriad
Copy link
Contributor Author

@jasmussen Can you take another look? I've added a generic component to open/close the panel body.

@jasmussen
Copy link
Contributor

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.

@youknowriad
Copy link
Contributor Author

youknowriad commented May 15, 2017

I've pushed this a bit forward, adding a generic FormToggle component (shamelessly copied from Calypso). Still not functional, but this is another UI building block.

@youknowriad youknowriad force-pushed the add/panel-components branch from be861f6 to 5222e32 Compare May 15, 2017 11:52
@aduth
Copy link
Member

aduth commented May 15, 2017

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).

@youknowriad youknowriad force-pushed the add/panel-components branch from 5222e32 to 3c445d5 Compare May 15, 2017 13:07
@jasmussen
Copy link
Contributor

In Calypso there's sometimes a little "summary" note when the panel is collapsed:

screen shot 2017-05-15 at 15 07 23

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.

@youknowriad
Copy link
Contributor Author

youknowriad commented May 15, 2017

I'm wondering if we could simplify the component to accept a header label as a prop

@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 PanelHeader container component.

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.

In Calypso there's sometimes a little "summary" note when the panel is collapsed

@jasmussen Seems like an easy addition to the PostBodyToggle component when necessary.

@youknowriad
Copy link
Contributor Author

youknowriad commented May 15, 2017

@aduth I think we could have a compromise where if we provide a header prop to the Panel component, the Panel component could include the PanelHeader and in the same time, having the possibility to avoid this prop and provide a PanelHeader explicitely.

Edit: Prop added in aaf06c2

@youknowriad youknowriad force-pushed the add/panel-components branch from 3c445d5 to 1c64eab Compare May 15, 2017 13:23
render() {
return (
<PanelBody>
<PanelBodyToggle
Copy link
Member

@aduth aduth May 15, 2017

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>

Copy link
Contributor Author

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>

Copy link
Member

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).

https://github.com/Automattic/wp-calypso/blob/f19b119/client/components/accordion/index.jsx#L16-L22

label={ __( 'Status & Visibility' ) }
/>
{ this.state.opened &&
<div>
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

<span>{ __( 'Pending review' ) }</span>
<FormToggle
checked={ false }
onChange={ () => {} }
Copy link
Member

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.

Copy link
Contributor Author

@youknowriad youknowriad May 15, 2017

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.

Copy link
Member

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.

@@ -0,0 +1,4 @@
.components-panel-body {
padding: 15px;
box-shadow: 0 0 0 1px $light-gray-500;
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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; } ?

Copy link
Contributor Author

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 {
.accessible-focus &:focus {
Copy link
Member

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.

}
}

FormToggle.idNum = 1;
Copy link
Member

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

}

render() {
const id = this.props.id || 'toggle-' + this.id;
Copy link
Member

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.

disabled={ this.props.disabled }
/>
<label className="components-form-toggle__label" htmlFor={ id } >
<span className="components-form-toggle__switch"
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

return;
}

if ( event.key === 'Enter' || event.key === ' ' ) {
Copy link
Member

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)

http://caniuse.com/#feat=keyboardevent-key

Copy link
Contributor Author

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

@aduth
Copy link
Member

aduth commented May 15, 2017

If we kept the separate components, could we at least consolidate them into the components/panel directory instead of three separate directories? PanelHeader and PanelBody don't seem to serve a standalone purpose justifying the separation.

background: $white;
border: 1px solid $light-gray-500;

> .components-panel__header,
Copy link
Member

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?

Copy link
Contributor Author

@youknowriad youknowriad May 16, 2017

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.

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! moving those styles

@youknowriad youknowriad force-pushed the add/panel-components branch from 75a27c3 to 5c090ff Compare May 16, 2017 08:40
@youknowriad
Copy link
Contributor Author

Can I have a final look here?

@@ -0,0 +1,55 @@
.components-form-toggle {
position: relative;
Copy link
Member

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.


function PanelBodyToggle( { opened, label, onClick } ) {
return (
<button className="components-panel__body-toggle" onClick={ onClick }>
Copy link
Member

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?

Copy link
Member

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.

Copy link
Contributor Author

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,
} );
}
Copy link
Member

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();
Copy link
Member

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.

const { toggle, children } = this.props;
return (
<div className="components-panel__body">
{ !! toggle &&
Copy link
Member

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?

@@ -0,0 +1,9 @@
function PanelHeader( { children } ) {
return (
<div className="components-panel__header">
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

<div>
<div className="editor-sidebar-post-status__row">
<span>{ __( 'Pending review' ) }</span>
<FormToggle />
Copy link
Member

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.

Copy link
Contributor Author

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.

@youknowriad youknowriad force-pushed the add/panel-components branch from 3a53bb6 to 7633875 Compare May 16, 2017 16:28
@youknowriad
Copy link
Contributor Author

Tks for the review @aduth I've addressed the raised points.

Copy link
Member

@aduth aduth left a 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.

<PostSettings />
<Panel>
<PanelHeader>
<strong>
Copy link
Member

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.

function PostStatus() {
return (
<PanelBody title={ __( 'Status & Visibility' ) }>
<div className="editor-sidebar-post-status__row">
Copy link
Member

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.

@youknowriad youknowriad force-pushed the add/panel-components branch from c37c83e to 9d33716 Compare May 17, 2017 08:05
@youknowriad youknowriad merged commit a67c9bb into master May 17, 2017
@youknowriad youknowriad deleted the add/panel-components branch May 17, 2017 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants