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

Add Tab and TabList components #813

Merged
merged 2 commits into from
Jan 31, 2023
Merged

Add Tab and TabList components #813

merged 2 commits into from
Jan 31, 2023

Conversation

lyzadanger
Copy link
Contributor

@lyzadanger lyzadanger commented Jan 27, 2023

This PR adds new Tab and TabList components, and pattern-library documentation for same.

Why now: I believe we may wish to use a tabbed interface in upcoming new UI in the client and before building out new tabs, I wanted to:

  • Extract the design pattern: Styling is based on the sidebar tabs (SelectionTabs) in the client, and extraction helps to better encapsulate the design pattern vs. the SelectionsTab-specific logic.
  • Ensure that we are ticking accessibility boxes. The new TabList adds arrow-key navigation per https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/
  • Fix a longstanding layout complexity to do with tabs jiggling when selected because of bold text. We've put various workarounds in place in the client over the years to try to account for it, but the new Tab component takes aim at alleviating it systematically (see documentation on textContent for that component).

As always with these new-component PRs, the lion's share of the diff is the pattern-library documentation page component. The components themselves were refreshingly simple to put together: I was able to scaffold out and concentrate on the specific problems I wanted to solve (fixing the jiggle, applying keyboard navigation) and documentation. The toolbox is getting more robust and it's starting to pay dividends!

Testing

Check out this branch, run make dev and visit the Tabs page in the pattern library

image

// Make the ::before occupy space within the button, but make it
// invisible. This ensures that the tab button is wide enough to show
// bolded text even if the visible text is not currently bold. See
// https://css-tricks.com/bold-on-hover-without-the-layout-shift/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The buttons at the top of this interface itself use this trick so that they don't move around when active:

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Clever! 😃

Comment on lines +7 to +14
const contentFn = (Component, props = {}) => {
return mount(
<div role="tablist">
<Component {...props}>This is child content</Component>
</div>
);
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here and in the tests for TabList: our automated common and accessibility tests are working well! Accessibility tests will fail if Tab isn't inside of a tablist, and TabList tests will fail if the TabList doesn't have any tab children. Sweet.

@codecov
Copy link

codecov bot commented Jan 30, 2023

Codecov Report

Merging #813 (69760ef) into main (4862843) will not change coverage.
The diff coverage is 100.00%.

❗ Current head 69760ef differs from pull request most recent head 932564a. Consider uploading reports for the commit 932564a to get more accurate results

@@            Coverage Diff            @@
##              main      #813   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           52        54    +2     
  Lines          633       649   +16     
  Branches       225       233    +8     
=========================================
+ Hits           633       649   +16     
Impacted Files Coverage Δ
src/components/navigation/Tab.tsx 100.00% <100.00%> (ø)
src/components/navigation/TabList.tsx 100.00% <100.00%> (ø)
src/components/input/Checkbox.js 100.00% <0.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@lyzadanger
Copy link
Contributor Author

I've gone ahead and pinged both @robertknight and @acelaya for review on this one. Rob more specifically for the keyboard navigation and behavior aspects.

Copy link
Contributor

@acelaya acelaya left a comment

Choose a reason for hiding this comment

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

A couple of minor comments, but looks good 👍🏼

*/
textContent?: string;
selected?: boolean;
variant?: 'basic';
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand the intention is to have more variants eventually? Maybe I would suggest not adding this property until we have other variants?

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 was on the fence about this. Tell you what: with this UI prototyping, I'm exercising these components quite a lot (it led me to add icon support to Tab and vertical support to TabList). I'm starting to lean toward the component itself doing no styling other than layout. I promise to address this in a follow-up PR.

// Make the ::before occupy space within the button, but make it
// invisible. This ensures that the tab button is wide enough to show
// bolded text even if the visible text is not currently bold. See
// https://css-tricks.com/bold-on-hover-without-the-layout-shift/
Copy link
Contributor

Choose a reason for hiding this comment

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

Clever! 😃

Copy link
Member

@robertknight robertknight left a comment

Choose a reason for hiding this comment

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

The implementation fundamentals look good. From a design perspective I think in future we may want to reconsider the lack of any borders between tabs or between tabs and tab panels, as this makes the tab-iness of the control less obvious, at least in certain contexts.

useArrowKeyNavigation(tabListRef, {
selector: 'button',
horizontal: true,
vertical: vertical,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
vertical: vertical,
vertical,

<code>aria-controls</code>
</a>{' '}
attribute to each <code>Tab</code>. This is not always feasible in
our applications.
Copy link
Member

Choose a reason for hiding this comment

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

My initial reaction to "This is not always feasible in our applications" was "why?". Are we doing something unusual or is there an implementation problem? What are the problems caused by not using this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great question! The WAI-ARIA authoring guidelines for a full tabby-widget-y interface assumes a structure like so:

<tablist>
  <tab controls="panel1">One</tab>
  <tab controls="panel2" aria-selected>Two</tab>
  <tab controls="panel3">Three</tab>
</tablist>
<tabpanel id="panel1" style="visibility:hidden">Content for panel One</tabpanel>
<tabpanel id="panel2">Content for panel Two</tabpanel>
<tabpanel id="panel3" style="visibility:hidden">Content for panel Three</tabpanel>

To wit:

  • A tabpanel for each tab
  • Non-active tabpanels are visually hidden

However, in the sidebar, we have:

<tablist>
  <tab>Annotations</tab>
  <tab aria-selected>Page Notes</tab>
  <tab>Orphans</tab>
</tablist>
<div>Page-note annotation threads</div>

To wit: All three tabs control the same "panel"; we don't have a discrete element "per panel". I don't think it would appropriate to assign the same aria-controls value to each tab (i.e. the id of the div containing threads, which, for clarity, does not currently have an id attribute). But I could be wrong.

I think this sort of situation is why WAI-ARIA guidelines indicate that there should be an aria-controls on role="tab" elements, but does not indicate that there must.

Copy link
Member

Choose a reason for hiding this comment

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

To wit: All three tabs control the same "panel"; we don't have a discrete element "per panel".

Ah, OK. That should be an implementation detail that we are re-using the DOM elements though. From a user's perspective (screen reader or not) I think it would ideally "look" the same as if we were using separate panels. I wonder if there is a blessed way of doing this kind of virtualization. How does aria-controls actually get used?

Annotations
<span className="relative bottom-[3px] left-[2px] text-[10px]">
{52}
</span>
Copy link
Member

Choose a reason for hiding this comment

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

Do you have plans to create a pattern library component for counts associated with a tab? I suspect this may be a common-enough pattern and have enough considerations around design/a11y that require thought to make it worthwhile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could do that, sure. It would simplify the acrobatics I did in re textContent. I'll give it a think.

* button will be sized to accommodate this string in bold text. This can
* prevent tab jiggle.
*/
textContent?: string;
Copy link
Member

Choose a reason for hiding this comment

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

I suppose we could try to simplify the API by determining the textContent automatically, eg. in a useLayoutEffect that reads textContent after render, although that would have a limitation of not being able to account for expected text changes (eg. if counts were not available and become available later), and be a bit more expensive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'm going to continue to hem and haw on this some. I don't plan to immediately cut a release of the package, so something intelligent might occur to me in the next few days.

@lyzadanger
Copy link
Contributor Author

The implementation fundamentals look good. From a design perspective I think in future we may want to reconsider the lack of any borders between tabs or between tabs and tab panels, as this makes the tab-iness of the control less obvious, at least in certain contexts.

For sure. My goal in the "styling" part of this was simply to capture the current pattern as-is.

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.

3 participants