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 Tabs (a composable TabPanel v2) #53960

Merged
merged 42 commits into from
Oct 6, 2023

Conversation

chad1008
Copy link
Contributor

@chad1008 chad1008 commented Aug 25, 2023

Addresses #52997

What?

Add a new Tabs component that will serve as a v2 replacement of the current TabPanel.

Why?

The new component will be more composable, allowing consumers to tailor their implementations to meet their needs more easily than can currently be accomplished. We'll also be looking to implement some previously requested features/enhancements with this new component as well.

How?

The main Tabs component will have three subcomponents, each handling their respective Ariakit counterparts: Tabs.TabList, Tabs.Tab, and Tabs.TabPanel. The Tabs component will house the TabList (which will, in turn, contain the various Tabs) and the individual TabPanels. Each Tab will correspond to one TabPanel:

<Tabs { ...props }>
	<Tabs.TabList>
		<Tabs.Tab id={ 'tab1' } title={ 'Tab 1' }>
			Tab 1
		</Tabs.Tab>
		<Tabs.Tab id={ 'tab2' } title={ 'Tab 2' }>
			Tab 2
		</Tabs.Tab>
		<Tabs.Tab id={ 'tab3' } title={ 'Tab 3' }>
			Tab 3
		</Tabs.Tab>
	</Tabs.TabList>
	<Tabs.TabPanel id={ 'tab1' }>
		<p>Selected tab: Tab 1</p>
	</Tabs.TabPanel>
	<Tabs.TabPanel id={ 'tab2' }>
		<p>Selected tab: Tab 2</p>
	</Tabs.TabPanel>
	<Tabs.TabPanel id={ 'tab3' }>
		<p>Selected tab: Tab 3</p>
	</Tabs.TabPanel>
</Tabs>

Thus far, the work on this composable Tabs component has focused on creating the subcomponents and ensuring they function in a way that maintains parity with TabPanel, and only making intentional changes. Focuses so far have been:

  • Implementing the subcomponents
  • Ensuring TabPanel's stories are replicated and functional in Storybook
  • Ensuring TabPanel's unit tests are updated and passing for Tabs. There were a few small changes needed here, one of which regarding when the onSelect callback is triggered that I'll touch on more below.

Remaining todos include:

  • Controlled Mode. Specifically, this will allow consumers to set the selected tab when necessary.
  • The ability to place the tablist and tabpanel subcomponents in arbitrary locations within the DOM.
  • tablist design flexibilty
  • Limit onSelect calls to intentional user selections. Currently in TabPanel, the consumer-provided onSelect callback is triggered when the initial tab is selected during the first render. In Tabs, we intend to only trigger this callback when the user actively selects a tab, not when the component does so automatically.
  • The usual README and CHANGELOG goodness
  • MAYBE/NICE TO HAVE: tab stop/focus behavior around tabpanels with focusable child elements. See Components: Introduce a more composable V2 of the TabPanel #52997 for a more detailed description.

Notes/Thoughts/Questions

  • Currently, we have everything in a wrapper component (Tabs) which supports slotfills. Tabs also handles providing context to the different subcomponents. Are there potential or existing implementations where a wrapper would be problematic? If so, we may want to look into alternative approaches that don't require a single wrapper component and context provider.
  • I've noticed some interesting Ariakit behavior that I wanted to get @diegohaz's feedback on, if you have a chance. It looks like the setSelectedId callback (analogous to our component's onSelect) is not called when Ariakit.useTabStore() receives a defaultSelectedId. It is called when a defaultSelectedId is not present. Does that sound like the expected behavior? I suspect it's because without that prop selectedId is undefined and needs to be updated, triggering the callback, but I wanted to double check. If that's what's happening, I might try defaulting defaultSelectedId to the first enabled tab without our implementation.

Testing Instructions

  • Unit tests pass
  • Launch Storybook
  • Confirm the tests that are shared between Tabs and TabPanel behave the same in both components. There may be some minor differences (for example, I added a third tab on some for Tabs), but the behavior should be the same, even if the tabs themselves differ slightly.
  • When testing the shared stories, confirm the behavior is consistent for both keyboard and mouse interactions.
  • On the Using SlotFill story:
    • Confirm that the tabpanel content updates properly when changing tabs, even though it's being rendered inside a different DOM element.
    • Confirm keyboard navigation (ie, it should still be possible to tab between the tablist and the tabpanel, while arrow keys navigate between the individual tabs themselves.
  • Nothing specific to test on the Insert Custom Elements story, but give it a go and try to break stuff 😉
  • On the Controlled Mode story:
    • Test mouse and keyboard interactions on the tabs, confirm they behave the same way other stories do.
    • Test the provided dropdown and ensure it allows you to change tabs
    • Test a mixed approach, switching between the tabs and the dropdown, and confirm nothing gets out of sync. For example, use the dropdown to get to a specific tab, and then manually click a new one. Can you now use the dropdown to get back to the original tab again?
  • Style/design flexibility:
    • Each of the new subcomponents has a style prop that can be used to give consumers more control over the look and feel of the tabpanel when needed (for example the 'fitted tabs' pr referenced above). Try modifying the default Tabs story by adding style props to the different components, and confirm your styles are added and rendered correctly.

Follow-ups

  • We should add the Tabs component to the package's private API (lock/unlock), and start using it around the editor. This will be a great way to test these components in a real-world scenario. We could start from those instances where folks had to be a bit hacky about adding a close button at the end of the tablist
  • Once we're happy with Tabs, we should:
    • Refactor all usages of TabPanel to Tabs in the project
    • Export the component from the package as a stable component (ie. not through private APIs)
    • Mark the TabPanel component as deprecated

@chad1008 chad1008 added [Type] Enhancement A suggestion for improvement. [Package] Components /packages/components [Feature] Component System WordPress component system labels Aug 25, 2023
@chad1008 chad1008 self-assigned this Aug 25, 2023
@diegohaz
Copy link
Member

  • I've noticed some interesting Ariakit behavior that I wanted to get @diegohaz's feedback on, if you have a chance. It looks like the setSelectedId callback (analogous to our component's onSelect) is not called when Ariakit.useTabStore() receives a defaultSelectedId. It is called when a defaultSelectedId is not present. Does that sound like the expected behavior? I suspect it's because without that prop selectedId is undefined and needs to be updated, triggering the callback, but I wanted to double check. If that's what's happening, I might try defaulting defaultSelectedId to the first enabled tab without our implementation.

Yes, I believe that's the correct behavior. If you don't supply a default selectedId state, but do provide a setSelectedId callback, you'll be informed when selectedId changes, even for the initial value (from undefined).

@github-actions
Copy link

github-actions bot commented Sep 12, 2023

Size Change: +94.9 kB (+6%) 🔍

Total Size: 1.62 MB

Filename Size Change
build/a11y/index.min.js 964 B +9 B (+1%)
build/annotations/index.min.js 2.71 kB +25 B (+1%)
build/api-fetch/index.min.js 2.29 kB +13 B (+1%)
build/autop/index.min.js 2.11 kB +7 B (0%)
build/blob/index.min.js 461 B +10 B (+2%)
build/block-directory/index.min.js 7.05 kB +40 B (+1%)
build/block-directory/style-rtl.css 1.04 kB +28 B (+3%)
build/block-directory/style.css 1.04 kB +27 B (+3%)
build/block-editor/content-rtl.css 4.28 kB +30 B (+1%)
build/block-editor/content.css 4.27 kB +30 B (+1%)
build/block-editor/default-editor-styles-rtl.css 403 B +22 B (+6%) 🔍
build/block-editor/default-editor-styles.css 403 B +22 B (+6%) 🔍
build/block-editor/index.min.js 218 kB +1.76 kB (+1%)
build/block-editor/style-rtl.css 15.6 kB +530 B (+4%)
build/block-editor/style.css 15.6 kB +530 B (+4%)
build/block-library/blocks/audio/theme-rtl.css 138 B +12 B (+10%) ⚠️
build/block-library/blocks/audio/theme.css 138 B +12 B (+10%) ⚠️
build/block-library/blocks/button/editor-rtl.css 587 B +3 B (+1%)
build/block-library/blocks/button/editor.css 587 B +5 B (+1%)
build/block-library/blocks/button/style-rtl.css 633 B +4 B (+1%)
build/block-library/blocks/button/style.css 632 B +4 B (+1%)
build/block-library/blocks/cover/style-rtl.css 1.7 kB +7 B (0%)
build/block-library/blocks/cover/style.css 1.69 kB +7 B (0%)
build/block-library/blocks/embed/theme-rtl.css 138 B +12 B (+10%) ⚠️
build/block-library/blocks/embed/theme.css 138 B +12 B (+10%) ⚠️
build/block-library/blocks/file/view.min.js 321 B +3 B (+1%)
build/block-library/blocks/freeform/editor-rtl.css 2.61 kB +1 B (0%)
build/block-library/blocks/gallery/editor-rtl.css 957 B +10 B (+1%)
build/block-library/blocks/gallery/editor.css 962 B +10 B (+1%)
build/block-library/blocks/gallery/style-rtl.css 1.55 kB +19 B (+1%)
build/block-library/blocks/gallery/style.css 1.55 kB +19 B (+1%)
build/block-library/blocks/gallery/theme-rtl.css 122 B +14 B (+13%) ⚠️
build/block-library/blocks/gallery/theme.css 122 B +14 B (+13%) ⚠️
build/block-library/blocks/heading/style-rtl.css 189 B +113 B (+149%) 🆘
build/block-library/blocks/heading/style.css 189 B +113 B (+149%) 🆘
build/block-library/blocks/html/editor-rtl.css 340 B +4 B (+1%)
build/block-library/blocks/html/editor.css 341 B +4 B (+1%)
build/block-library/blocks/image/theme-rtl.css 137 B +11 B (+9%) 🔍
build/block-library/blocks/image/theme.css 137 B +11 B (+9%) 🔍
build/block-library/blocks/image/view-interactivity.min.js 0 B -1.83 kB (removed) 🏆
build/block-library/blocks/navigation-link/editor-rtl.css 671 B +3 B (0%)
build/block-library/blocks/navigation-link/editor.css 672 B +3 B (0%)
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B +3 B (+1%)
build/block-library/blocks/navigation-submenu/editor.css 299 B +4 B (+1%)
build/block-library/blocks/navigation/editor-rtl.css 2.26 kB +4 B (0%)
build/block-library/blocks/navigation/editor.css 2.26 kB +1 B (0%)
build/block-library/blocks/navigation/style-rtl.css 2.25 kB +13 B (+1%)
build/block-library/blocks/navigation/style.css 2.23 kB +9 B (0%)
build/block-library/blocks/navigation/view.min.js 1.01 kB +23 B (+2%)
build/block-library/blocks/post-featured-image/style-rtl.css 322 B +3 B (+1%)
build/block-library/blocks/post-featured-image/style.css 322 B +3 B (+1%)
build/block-library/blocks/query-pagination/style-rtl.css 288 B -14 B (-5%)
build/block-library/blocks/query-pagination/style.css 284 B -15 B (-5%)
build/block-library/blocks/query/editor-rtl.css 486 B +36 B (+8%) 🔍
build/block-library/blocks/query/editor.css 486 B +37 B (+8%) 🔍
build/block-library/blocks/query/style-rtl.css 375 B +5 B (+1%)
build/block-library/blocks/query/style.css 372 B +4 B (+1%)
build/block-library/blocks/query/view.min.js 581 B +26 B (+5%) 🔍
build/block-library/blocks/search/style-rtl.css 594 B -13 B (-2%)
build/block-library/blocks/search/style.css 594 B -13 B (-2%)
build/block-library/blocks/search/view.min.js 471 B +3 B (+1%)
build/block-library/blocks/shortcode/editor-rtl.css 329 B +6 B (+2%)
build/block-library/blocks/shortcode/editor.css 329 B +6 B (+2%)
build/block-library/blocks/site-logo/editor-rtl.css 760 B +6 B (+1%)
build/block-library/blocks/site-logo/editor.css 760 B +6 B (+1%)
build/block-library/blocks/spacer/editor-rtl.css 359 B +11 B (+3%)
build/block-library/blocks/spacer/editor.css 359 B +11 B (+3%)
build/block-library/blocks/table/style-rtl.css 646 B +7 B (+1%)
build/block-library/blocks/table/style.css 645 B +6 B (+1%)
build/block-library/blocks/table/theme-rtl.css 157 B +11 B (+8%) 🔍
build/block-library/blocks/table/theme.css 157 B +11 B (+8%) 🔍
build/block-library/blocks/video/style-rtl.css 191 B +6 B (+3%)
build/block-library/blocks/video/style.css 191 B +6 B (+3%)
build/block-library/blocks/video/theme-rtl.css 139 B +13 B (+10%) ⚠️
build/block-library/blocks/video/theme.css 139 B +13 B (+10%) ⚠️
build/block-library/common-rtl.css 1.11 kB +12 B (+1%)
build/block-library/common.css 1.11 kB +13 B (+1%)
build/block-library/editor-rtl.css 12.2 kB +61 B (+1%)
build/block-library/editor.css 12.2 kB +62 B (+1%)
build/block-library/index.min.js 207 kB +2.83 kB (+1%)
build/block-library/style-rtl.css 14 kB +85 B (+1%)
build/block-library/style.css 14 kB +86 B (+1%)
build/block-library/theme-rtl.css 700 B +12 B (+2%)
build/block-library/theme.css 705 B +12 B (+2%)
build/block-serialization-default-parser/index.min.js 1.13 kB +8 B (+1%)
build/block-serialization-spec-parser/index.min.js 2.87 kB +2 B (0%)
build/blocks/index.min.js 51.5 kB +86 B (0%)
build/commands/index.min.js 15.5 kB +18 B (0%)
build/commands/style-rtl.css 947 B +26 B (+3%)
build/commands/style.css 942 B +24 B (+3%)
build/components/index.min.js 246 kB -8.53 kB (-3%)
build/components/style-rtl.css 11.8 kB +90 B (+1%)
build/components/style.css 11.8 kB +88 B (+1%)
build/compose/index.min.js 12.7 kB +622 B (+5%) 🔍
build/core-commands/index.min.js 2.62 kB +14 B (+1%)
build/core-data/index.min.js 17.1 kB +255 B (+2%)
build/customize-widgets/index.min.js 12 kB +12 B (0%)
build/customize-widgets/style-rtl.css 1.51 kB +26 B (+2%)
build/customize-widgets/style.css 1.5 kB +25 B (+2%)
build/data-controls/index.min.js 651 B +11 B (+2%)
build/data/index.min.js 8.87 kB +33 B (0%)
build/date/index.min.js 17.9 kB +10 B (0%)
build/deprecated/index.min.js 462 B +11 B (+2%)
build/dom-ready/index.min.js 336 B +12 B (+4%)
build/dom/index.min.js 4.68 kB +40 B (+1%)
build/edit-post/classic-rtl.css 571 B +27 B (+5%) 🔍
build/edit-post/classic.css 571 B +26 B (+5%) 🔍
build/edit-post/index.min.js 35.6 kB +251 B (+1%)
build/edit-post/style-rtl.css 7.92 kB +87 B (+1%)
build/edit-post/style.css 7.91 kB +88 B (+1%)
build/edit-site/index.min.js 185 kB +93.3 kB (+102%) 🆘
build/edit-site/style-rtl.css 14 kB +499 B (+4%)
build/edit-site/style.css 14 kB +496 B (+4%)
build/edit-widgets/index.min.js 17 kB +97 B (+1%)
build/edit-widgets/style-rtl.css 4.84 kB +49 B (+1%)
build/edit-widgets/style.css 4.84 kB +48 B (+1%)
build/editor/index.min.js 45.9 kB +400 B (+1%)
build/editor/style-rtl.css 3.58 kB +55 B (+2%)
build/editor/style.css 3.58 kB +55 B (+2%)
build/element/index.min.js 4.87 kB +50 B (+1%)
build/escape-html/index.min.js 548 B +11 B (+2%)
build/format-library/index.min.js 7.75 kB +49 B (+1%)
build/format-library/style-rtl.css 577 B +23 B (+4%)
build/format-library/style.css 577 B +24 B (+4%)
build/hooks/index.min.js 1.57 kB +23 B (+1%)
build/html-entities/index.min.js 454 B +6 B (+1%)
build/i18n/index.min.js 3.61 kB +28 B (+1%)
build/interactivity/index.min.js 11.4 kB +104 B (+1%)
build/is-shallow-equal/index.min.js 535 B +8 B (+2%)
build/keyboard-shortcuts/index.min.js 1.74 kB +16 B (+1%)
build/keycodes/index.min.js 1.9 kB +31 B (+2%)
build/list-reusable-blocks/index.min.js 2.2 kB -2 B (0%)
build/list-reusable-blocks/style-rtl.css 865 B +29 B (+3%)
build/list-reusable-blocks/style.css 865 B +29 B (+3%)
build/media-utils/index.min.js 2.92 kB +17 B (+1%)
build/notices/index.min.js 964 B +16 B (+2%)
build/nux/index.min.js 2 kB +17 B (+1%)
build/nux/style-rtl.css 775 B +40 B (+5%) 🔍
build/nux/style.css 771 B +39 B (+5%) 🔍
build/patterns/index.min.js 3.54 kB +839 B (+31%) 🚨
build/patterns/style-rtl.css 325 B +85 B (+35%) 🚨
build/patterns/style.css 325 B +85 B (+35%) 🚨
build/plugins/index.min.js 1.8 kB +14 B (+1%)
build/preferences-persistence/index.min.js 1.85 kB +10 B (+1%)
build/preferences/index.min.js 1.25 kB +14 B (+1%)
build/primitives/index.min.js 994 B +51 B (+5%) 🔍
build/priority-queue/index.min.js 1.52 kB +2 B (0%)
build/private-apis/index.min.js 972 B +14 B (+1%)
build/react-i18n/index.min.js 624 B +9 B (+1%)
build/react-refresh-entry/index.min.js 9.46 kB -6 B (0%)
build/react-refresh-runtime/index.min.js 6.78 kB -526 B (-7%)
build/redux-routine/index.min.js 2.71 kB +3 B (0%)
build/reusable-blocks/index.min.js 2.72 kB +18 B (+1%)
build/reusable-blocks/style-rtl.css 265 B +22 B (+9%) 🔍
build/reusable-blocks/style.css 265 B +22 B (+9%) 🔍
build/rich-text/index.min.js 10.2 kB +4 B (0%)
build/server-side-render/index.min.js 1.95 kB +10 B (+1%)
build/shortcode/index.min.js 1.4 kB +8 B (+1%)
build/style-engine/index.min.js 1.98 kB +4 B (0%)
build/sync/index.min.js 53.8 kB +6 B (0%)
build/token-list/index.min.js 587 B +5 B (+1%)
build/url/index.min.js 3.84 kB +106 B (+3%)
build/viewport/index.min.js 968 B +10 B (+1%)
build/warning/index.min.js 259 B +10 B (+4%)
build/widgets/index.min.js 7.17 kB +13 B (0%)
build/widgets/style-rtl.css 1.18 kB +27 B (+2%)
build/widgets/style.css 1.18 kB +26 B (+2%)
build/wordcount/index.min.js 1.03 kB +4 B (0%)
ℹ️ View Unchanged
Filename Size
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/block/editor-rtl.css 305 B
build/block-library/blocks/block/editor.css 305 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 113 B
build/block-library/blocks/categories/editor.css 112 B
build/block-library/blocks/categories/style-rtl.css 124 B
build/block-library/blocks/categories/style.css 124 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 121 B
build/block-library/blocks/code/style.css 121 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 421 B
build/block-library/blocks/columns/style.css 421 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 199 B
build/block-library/blocks/comment-template/style.css 198 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 840 B
build/block-library/blocks/comments/editor.css 839 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 636 B
build/block-library/blocks/cover/editor-rtl.css 647 B
build/block-library/blocks/cover/editor.css 650 B
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 98 B
build/block-library/blocks/details/style.css 98 B
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/file/editor-rtl.css 316 B
build/block-library/blocks/file/editor.css 316 B
build/block-library/blocks/file/style-rtl.css 311 B
build/block-library/blocks/file/style.css 312 B
build/block-library/blocks/footnotes/style-rtl.css 201 B
build/block-library/blocks/footnotes/style.css 199 B
build/block-library/blocks/freeform/editor.css 2.61 kB
build/block-library/blocks/group/editor-rtl.css 654 B
build/block-library/blocks/group/editor.css 654 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/image/editor-rtl.css 834 B
build/block-library/blocks/image/editor.css 833 B
build/block-library/blocks/image/style-rtl.css 1.42 kB
build/block-library/blocks/image/style.css 1.41 kB
build/block-library/blocks/image/view.min.js 1.83 kB
build/block-library/blocks/latest-comments/style-rtl.css 357 B
build/block-library/blocks/latest-comments/style.css 357 B
build/block-library/blocks/latest-posts/editor-rtl.css 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 478 B
build/block-library/blocks/latest-posts/style.css 478 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 505 B
build/block-library/blocks/media-text/style.css 503 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 401 B
build/block-library/blocks/page-list/editor.css 401 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 235 B
build/block-library/blocks/paragraph/editor.css 235 B
build/block-library/blocks/paragraph/style-rtl.css 335 B
build/block-library/blocks/paragraph/style.css 335 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 508 B
build/block-library/blocks/post-comments-form/style.css 508 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 141 B
build/block-library/blocks/post-excerpt/style.css 141 B
build/block-library/blocks/post-featured-image/editor-rtl.css 588 B
build/block-library/blocks/post-featured-image/editor.css 586 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 314 B
build/block-library/blocks/post-template/style.css 314 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 69 B
build/block-library/blocks/post-time-to-read/style.css 69 B
build/block-library/blocks/post-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 335 B
build/block-library/blocks/pullquote/style.css 335 B
build/block-library/blocks/pullquote/theme-rtl.css 168 B
build/block-library/blocks/pullquote/theme.css 168 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/quote/style-rtl.css 222 B
build/block-library/blocks/quote/style.css 222 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 149 B
build/block-library/blocks/rss/editor.css 149 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 178 B
build/block-library/blocks/search/editor.css 178 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 234 B
build/block-library/blocks/separator/style.css 234 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/site-logo/style-rtl.css 204 B
build/block-library/blocks/site-logo/style.css 204 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 682 B
build/block-library/blocks/social-links/editor.css 681 B
build/block-library/blocks/social-links/style-rtl.css 1.44 kB
build/block-library/blocks/social-links/style.css 1.43 kB
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 432 B
build/block-library/blocks/table/editor.css 432 B
build/block-library/blocks/tag-cloud/style-rtl.css 251 B
build/block-library/blocks/tag-cloud/style.css 253 B
build/block-library/blocks/template-part/editor-rtl.css 403 B
build/block-library/blocks/template-part/editor.css 403 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/term-description/style-rtl.css 111 B
build/block-library/blocks/term-description/style.css 111 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 99 B
build/block-library/blocks/verse/style.css 99 B
build/block-library/blocks/video/editor-rtl.css 552 B
build/block-library/blocks/video/editor.css 555 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/router/index.min.js 1.78 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 41.8 kB
build/vendors/react.min.js 4.02 kB

compressed-size-action

@chad1008
Copy link
Contributor Author

chad1008 commented Sep 12, 2023

Controlled Mode Notes/Discussion

Implementing controlled mode raised some interesting questions that @ciampo and I brainstormed a bit about regarding tab selection. The legacy TabPanel had internal logic that would fall back to a different available tab if the current selection became unavailable. There were only two cases where the legacy component (and our new one in Uncontrolled mode which largely mirrors legacy) will allow no tab to be selected (see below).

Whether or not Controlled mode should behave the same way, and try to prevent the majority of cases where no content gets rendered is worth discussing. On the one hand, perhaps the component should protect against a state where the user doesn't see any content. On the other hand if a consumer is using controlled mode, perhaps it makes sense to trust that disabling/removing tabs is being done intentionally. In that case it would make sense to leave the implementation of any fallback logic in their capable hands.

For now I've gone with the latter course. In controlled mode, the component won't fall back or otherwise automate tab selection. I've included a table below outlining the differences between the two modes in various scenarios (which are mainly those covered by the existing legacy TabPanel unit tests). At the time of this writing, uncontrolled mode mirrors the legacy component's behavior. We can certainly change that if we want... for example maybe we want uncontrolled mode to always provide a fallback tab instead of just most of the time.

legacy/uncontrolled controlled
no initialTabId provided first enabled tab render the tab associated with selectedTabId
active tab is removed, and no initialTabId was provided first enabled tab load no tab
initialTabId provided render the tab associated with initialTabId render the tab associated with selectedTabId
initialTabId is provided but invalid load no tab render the tab associated with selectedTabId
initialTabId value changes selected tab does not change selected tab does not change
active tab is removed, initialTabId was provided fall back to initialTabId load no tab
tab associated with initialTabId is removed while being the active tab load no tab load no tab
initialTabId was not provided, and the first tab in the UI is disabled first enabled tab render the tab associated with selectedTabId
initialTabId is provided, but the associated tab is disabled first enabled tab render the tab associated with selectedTabId
active tab is disabled, and initialTabId was not provided first enabled tab load no tab
tab associated with initialTabId becomes disabled while being the active tab first enabled tab load no tab
active tab is disabled, initialTabId was provided fall back to the tab associated with initialTabId if it's enabled, otherwise fall back to first enabled tab load no tab
tab associated with selectedTabId is disabled n/a load no tab
tab associated with selectedTabId is removed or otherwise can't be found n/a load no tab

I don't personally have an objection with any of the above, but am very open to discussion. I do wonder if we should push uncontrolled mode to always fall back to the first enabled tab if initialTabId isn't viable, for consistency sake.

One other wrinkle is specific to controlled mode:

  • If the currently active tab is disabled, the selected tab is cleared. If that same tab is then re-enabled before a new selection is made, it does NOT get automatically reselected, because we've already cleared the value from Ariakit's internal store by that point (necessary to prevent the component from continue to display the now-disabled tab's content).
  • Conversely, if the active tab gets removed for some reason, and then reappears, it DOES get reselected, because in that flow nothing actually modified the internal state.

I think preventing that automatic reload would be more consistent, but after a first pass it looked like it might be a little more work than expected, so I stopped to gather feedback first.

@chad1008 chad1008 marked this pull request as ready for review September 12, 2023 19:04
Copy link
Contributor

@brookewp brookewp left a comment

Choose a reason for hiding this comment

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

This is looking so good! ❤️ When I saw this, I knew this had to be a new change by you:
Screenshot 2023-09-13 at 2 33 02 PM

😄 With that said, are the changes between Tabs and TabPanel all listed under Requirements of the new component in the issue and Remaining todos mentioned in your description above? I ask because of:

Ensuring TabPanel's stories are replicated and functional in Storybook
Launch Storybook and confirm the component stories behave as expected, controls are present and functional, etc.

Regarding expected behavior, should the shared stories in Tabs and TabPanel behave exactly the same?

Are the requirements of the new component only displayed in the new stories? Are there any new features/behaviors we should test for? I've looked through ARIA requirements for Tabs, but I wanted to check if there was anything else outside of that.

@github-actions
Copy link

github-actions bot commented Sep 14, 2023

Flaky tests detected in 8d3ac35.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6433848396
📝 Reported issues:

@chad1008
Copy link
Contributor Author

Sorry for not replying to your general comment last week @brookewp! I've updated the testing instructions to make things better!

Regarding expected behavior, should the shared stories in Tabs and TabPanel behave exactly the same?

yes, for those tests (ie uncontrolled mode) things should be behaving the same way that TabPanel currently does

Are the requirements of the new component only displayed in the new stories? Are there any new features/behaviors we should test for? I've looked through ARIA requirements for Tabs, but I wanted to check if there was anything else outside of that.

We actually wound up keeping new stuff to a minimum, which was a solid suggestion from @ciampo, so the main new features are going to be design flexibility stuff like adding a custom element and flexible styling, which I've updated the testing steps to include 😄

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Didn't get to do a full round of review, but I'll start posting a few initial comments

packages/components/src/tabs/style.scss Outdated Show resolved Hide resolved
packages/components/src/tabs/README.md Outdated Show resolved Hide resolved
packages/components/src/tabs/README.md Outdated Show resolved Hide resolved
packages/components/src/tabs/README.md Outdated Show resolved Hide resolved
packages/components/src/tabs/README.md Outdated Show resolved Hide resolved
packages/components/src/tabs/index.tsx Show resolved Hide resolved
packages/components/src/tabs/test/index.tsx Outdated Show resolved Hide resolved
packages/components/src/tabs/test/index.tsx Show resolved Hide resolved
@chad1008 chad1008 force-pushed the add/composable-tabs-component branch from 198feba to 2995957 Compare October 4, 2023 13:10
@chad1008
Copy link
Contributor Author

chad1008 commented Oct 4, 2023

I've pushed some more updates today that should address the existing feedback, as well as adding in ref forwarding.

One thing that hasn't been addressed is limiting onSelect calls to user-initiated changes. @ciampo and I looked at this together while pairing today and it feels like it will be a lot of work to implement, as Ariakit itself doesn't actually work that way. Any time the selected tab changes the onSelect gets fired, even on the initial render when the component identifies the selected tab is undefined and falls back to the first available tab.

Because it looks like it would be difficult at best to get this working, we're planning to merge this PR as it is now, and begin looking into implementing Tabs in place of TabPanel to see how it fits/works/feels, then revisit this question if necessary.

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

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

Because it looks like it would be difficult at best to get this working, we're planning to merge this PR as it is now, and begin looking into implementing Tabs in place of TabPanel to see how it fits/works/feels, then revisit this question if necessary.

Sounds like a good plan to me! Large PRs like this can often get stalled and live forever, and we definitely don't want that to happen.

One thing I'd recommend before that though is to make it clear that this is still an experimental and unstable component. While it's not exported and used anywhere just yet, we want to ensure someone doesn't start using it thinking that it's stable.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Let's ship this after open comments are addressed and a CHANGELOG entry is added.

As a follow-up, I think it would be good to extend all Tabs components to accept all standard HTML props (not just className and styles), although that can be done as a follow-up.

Other follow-ups that come to mind:

  • improve focus behaviour for the tabpanel
  • export the component via private APIs and start using it in the editor. This will definitely help us to make any adjustments as needed

packages/components/src/tabs/types.ts Outdated Show resolved Hide resolved
packages/components/src/tabs/types.ts Outdated Show resolved Hide resolved
packages/components/src/tabs/types.ts Outdated Show resolved Hide resolved
packages/components/src/tabs/README.md Show resolved Hide resolved
packages/components/src/tabs/README.md Outdated Show resolved Hide resolved
packages/components/src/tabs/types.ts Outdated Show resolved Hide resolved
packages/components/src/tabs/README.md Show resolved Hide resolved
packages/components/src/tabs/stories/index.story.tsx Outdated Show resolved Hide resolved
chad1008 and others added 6 commits October 6, 2023 11:32
Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
@chad1008 chad1008 enabled auto-merge (squash) October 6, 2023 15:58
@chad1008 chad1008 merged commit 4889061 into trunk Oct 6, 2023
50 checks passed
@chad1008 chad1008 deleted the add/composable-tabs-component branch October 6, 2023 17:46
@github-actions github-actions bot added this to the Gutenberg 16.9 milestone Oct 6, 2023
@bph bph added the Needs Dev Note Requires a developer note for a major WordPress release cycle label Nov 8, 2023
@mirka
Copy link
Member

mirka commented Feb 26, 2024

Removing the "Needs Dev Note" label since this is still a locked private API.

@mirka mirka removed the Needs Dev Note Requires a developer note for a major WordPress release cycle label Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Component System WordPress component system [Package] Components /packages/components [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants