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

Use TabPanel in edit settings sidebar header (#13587) #16663

Closed
wants to merge 7 commits into from

Conversation

jffng
Copy link
Contributor

@jffng jffng commented Jul 18, 2019

Description

This PR replaces the sidebar settings header with TabPanel from the components package.

No visual change, but a slight behavioral change in the keyboard navigation: only the active tab is placed in the tab order, and the user can navigate between tabs using the arrows.

This follows the WAI-ARIA recommendation on keyboard interaction for tab panel components:

Tab: When focus moves into the tab list, places focus on the active tab element . When the tab list contains the focus, moves focus to the next element in the page tab sequence outside the tablist, which is typically either the first focusable element inside the tab panel or the tab panel itself.

How has this been tested?

Tested in the playground and editor on:

macOS 10.14.5
Chrome 75.0.3770.100
Safari 12.1.1
Firefox 67.0.4

Types of changes

Addresses #13587

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.

* Add styles to the component itself

* Add aria-label prop to the TabButton

* Replace markup with TabPanel component in edit settings sidebar header
@jffng
Copy link
Contributor Author

jffng commented Jul 18, 2019

Discovered two issues to address:

  1. Rework some of the tests as the class selectors have changed.
  2. Active tab class needs to change when the edit-post/block event fires

I ended up refactoring TabPanel with hooks to solve an issue where the initial state is set by a prop, but each re-render caused the component's active tab to lose focus.

@jffng jffng requested review from nerrad and ntwb as code owners July 23, 2019 02:02
@youknowriad youknowriad requested review from jasmussen and mapk July 23, 2019 11:30
onSelect={ onSelect }
controlledTabName={ selectedTabName }
>
{ () => { } }
Copy link

@ItsJonQ ItsJonQ Jul 23, 2019

Choose a reason for hiding this comment

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

@jffng Hallooo! This PR looks great! I was a little confused at this one (at first glance).

What do you think about adjusted the original TabPanel component's children() render to do something like...

children && children(selectedTab)

That way, you won't have to pass it in a children render function that returns null/empty.

P.S. I'm not sure what the current conventions are for handling this flow 😊. The flow I suggested feels little cleaner + less ambiguous

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @ItsJonQ! I agree. We could also set children's default value to noop.

Copy link

Choose a reason for hiding this comment

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

Aww yiss! noop it up! 🎉

Modify e2e tests to use arrow keys to switch between tabs and select using
aria-label

Allow settings header to control the active tab
@jffng jffng force-pushed the try/sidebar-settings-tab-panel branch from b4542b8 to bcf46a3 Compare July 23, 2019 14:05
@mapk
Copy link
Contributor

mapk commented Jul 24, 2019

Thanks @jffng for a fantastic PR, and thanks @ItsJonQ for the follow-up code review. I just tested this and love jumping around with the arrow keys. I did notice some keyboard nav weirdness though. When I was in the tabPanel, and "Document" was focused, hitting tab brought me to an in-between state where clicking Enter didn't do anything and nothing was focused. If I hit tab again, I was taken to the "Close" icon. Is this expected behavior?

I feel like that in-between tab position wants to rest on the "Block" tab, but isn't so it just leaves me hanging.

Would love an accessibility review here too!

@mapk mapk added Needs Accessibility Feedback Need input from accessibility [Feature] UI Components Impacts or related to the UI component system labels Jul 24, 2019
@@ -133,9 +133,9 @@ The class to add to the active tab
- Required: No
- Default: `is-active`

#### initialTabName
#### controlledTabName
Copy link
Member

Choose a reason for hiding this comment

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

This is a breaking change and can't be landed as is. It needs to provide a way which ensures that this code is backward compatible with a solid deprecation strategy which informs developers that there is a new way to handle this prop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review @gziolo, noted.

I added support for the initialTabName for backwards compatibility, as well deprecation notes in the README. However, I lack context as to the timing / version to officially deprecate this prop.

This is also assuming that having a prop to control the active tab from a parent component is a good idea / not an anti-pattern.

Copy link
Member

Choose a reason for hiding this comment

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

If we don't want to keep this param, we should rather remove it from docs and ensure that when used it works as close as possible as the new one.

@@ -0,0 +1,46 @@
.components-tab-panel__tabs {
Copy link
Member

Choose a reason for hiding this comment

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

@mapk are we fine with these styles to be the default look outside of the context of sidebar and WordPress in general?

@gziolo gziolo added the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label Jul 25, 2019
@jffng
Copy link
Contributor Author

jffng commented Jul 25, 2019

When I was in the tabPanel, and "Document" was focused, hitting tab brought me to an in-between state where clicking Enter didn't do anything and nothing was focused. If I hit tab again, I was taken to the "Close" icon. Is this expected behavior?

No, nice catch @mapk. The component was rendering an empty container where the tab panel content would normally go (we're not actually using it in this case), and this empty container was receiving the focus in that in-between state. I pushed a fix that should resolve it, making the tab panel content optional.

Would love an accessibility review here too!

Yes!

}
};

useEffect(
Copy link
Member

Choose a reason for hiding this comment

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

Did you consider adding onSelect to the objects passed to tabs instead? It might simplify the solution as you could leave the whole logic which reasons which tab is selected to TabPanel component.

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 had considered it, but avoided that route as I was originally trying to minimize changes to TabPanel. I agree adding an onSelect method to the tab objects would simplify the solution. (We would still need to support the onSelect component handler for backwards compatibility)

The issue that led me to refactor TabPanel was the need to control the selected tab from the parent component without triggering a re-render of the entire component, causing the active tab to lose focus.

Copy link
Member

Choose a reason for hiding this comment

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

The issue that led me to refactor TabPanel was the need to control the selected tab from the parent component without triggering a re-render of the entire component, causing the active tab to lose focus.

Shouldn't the component ensure that there is never focus loss? 3rd party developers shouldn't care about it, it should be baked in into such component.

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 will think about that, thank you!

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 believe I was able to simplify the logic, please let me know if these changes address your feedback @gziolo, thanks.

Adjust test to three tab presses to navigate the block settings instead of four
@jffng jffng force-pushed the try/sidebar-settings-tab-panel branch from 796cb66 to 8733d7d Compare July 25, 2019 16:03
Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

@jffng, the changes applied to PR look great. This is exactly what I would see as someone who consumes this component. Everything is handled behind the scenes and you don't need to worry about all the logic which handles navigation between tabs.

</ul>
<TabPanel
tabs={ tabs }
controlledTabName={ sidebarName + '__panel-tab' }
Copy link
Member

Choose a reason for hiding this comment

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

Does it need to be explicitly set from outside or is it possible to append __panel-tab part internally in the TabPanel implementation?

// translators: ARIA label for the Settings Sidebar tab, selected.
[ __( 'Block (selected)' ), 'is-active' ] :
__( 'Block (selected)' ) :
Copy link
Member

Choose a reason for hiding this comment

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

Maybe, the part which informs about the selected tab could be constructed inside the TabPanel from now on. It seems like we only append (selected) when a tab is selected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice suggestion, this simplifies the settings header logic even further.

@jffng jffng force-pushed the try/sidebar-settings-tab-panel branch from 15ca57e to 84ab486 Compare July 29, 2019 18:00
@gziolo
Copy link
Member

gziolo commented Jul 31, 2019

@drw158 - how do you feel about the changes proposed in the context of the audit that you performed and documented in #16710?

As far as I see, TabPanel is used for the content of the tab in other libraries. I checked myself these two libs:

@davewhitley
Copy link
Contributor

davewhitley commented Aug 1, 2019

@gziolo I'm leaning towards either structure as shown in Reakit or Reach UI, especially if we rename our current Panel component.

<TabList {...tab} aria-label="My tabs">
    <Tab {...tab} stopId="tab1">
        Tab 1
    </Tab>
    <Tab {...tab} stopId="tab2" disabled>
        Tab 2
    </Tab>
    <Tab {...tab} stopId="tab3">
        Tab 3
    </Tab>
</TabList>
<TabPanel {...tab} stopId="tab1">
    Tab 1
</TabPanel>
<TabPanel {...tab} stopId="tab2">
    Tab 2
</TabPanel>
<TabPanel {...tab} stopId="tab3">
    Tab 3
</TabPanel>

@gziolo gziolo removed the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label Jan 5, 2021
Base automatically changed from master to trunk March 1, 2021 15:42
@annezazu
Copy link
Contributor

annezazu commented Jul 27, 2022

It seems the original issue was closed #13587 and this problem was addressed in another PR: #20872 Going to close this out as a result :) Feel free to re-open/ask for it to be if I am missing something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system Needs Accessibility Feedback Need input from accessibility
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants