-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Components: add Tabs (a composable TabPanel v2)
#53960
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
Merged
Merged
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
978bec5
conjure components
chad1008 7ffc88a
summon stories
chad1008 04e0269
unleash unit tests
chad1008 ca47818
create controlled mode
chad1008 00db04b
remove monolithic flow and tabs array
chad1008 298defe
specify styles
chad1008 ecc55bd
render README
chad1008 aabbf40
incorporate initial doc feedback
chad1008 59b1387
update for Tootip/Popover.Slot changes
chad1008 00b9e42
docs manifest
chad1008 ab09233
create individual files for each sub-component
chad1008 1c2f9e5
update index to use individual subcomponent files
chad1008 0b95527
fix mystyped class name on TabList
chad1008 c8b92d6
clean up unit tests
chad1008 1ff0685
shore up unit tests by adding additional `toHaveBeenLastCalledWith` a…
chad1008 e53b792
README improvements
chad1008 ed776b3
transition to emotion
chad1008 8451783
remove old scss import
chad1008 800b56f
unit test cleanup
chad1008 41053ef
remove excess initialTabId + controlled mode tests & reorganize
chad1008 fb90eb0
add handling for active tab becoming disabled when `initialTabId` was…
chad1008 3af61fe
add tab disabled/removed stories
chad1008 187de6e
controlled mode: do not restore selection if active tab is removed an…
chad1008 ce09452
more explicit ternary
chad1008 598230e
remove `active-class` prop
chad1008 9240134
Update packages/components/src/tabs/types.ts
chad1008 9615e69
removing superfluos test
chad1008 f2c2012
handle fallback when no enabled tabs are available
chad1008 31e6fa3
go away unneeded optional chaining
chad1008 d3145dc
replace includes with proper imports in styles
chad1008 a8b5f8a
remove @ts-expect-error
chad1008 f1e38de
add eslint-disable for ariakit imports
chad1008 dfe7384
introduce `render` prop for `Tabs.Tab`
chad1008 2995957
update path to space utils
chad1008 b12db10
add test for uncontrolled mode removing tab when no other tabs are en…
chad1008 2d37e60
add ref forwarding
chad1008 6831880
typo
chad1008 2fe4332
update `selectedTabId` description
chad1008 4025488
misc feedback updates
chad1008 8367af6
mark as experimental
chad1008 e24d31a
update `onSelect` JSDoc description
chad1008 8d3ac35
update CHANGELOG
chad1008 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,242 @@ | ||
| # Tabs | ||
|
|
||
| <div class="callout callout-alert"> | ||
| This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. | ||
| </div> | ||
|
|
||
| Tabs is a collection of React components that combine to render an [ARIA-compliant tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/). | ||
chad1008 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Tabs organizes content across different screens, data sets, and interactions. It has two sections: a list of tabs, and the view to show when tabs are chosen. | ||
|
|
||
| ## Development guidelines | ||
|
|
||
| ### Usage | ||
|
|
||
| #### Uncontrolled Mode | ||
|
|
||
| Tabs can be used in an uncontrolled mode, where the component manages its own state. In this mode, the `initialTabId` prop can be used to set the initially selected tab. If this prop is not set, the first tab will be selected by default. In addition, in most cases where the currently active tab becomes disabled or otherwise unavailable, uncontrolled mode will automatically fall back to selecting the first available tab. | ||
|
|
||
| ```jsx | ||
| import { Tabs } from '@wordpress/components'; | ||
|
|
||
| const onSelect = ( tabName ) => { | ||
| console.log( 'Selecting tab', tabName ); | ||
| }; | ||
|
|
||
| const MyUncontrolledTabs = () => ( | ||
| <Tabs onSelect={onSelect} initialTab="tab2"> | ||
| <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> | ||
| ); | ||
| ``` | ||
|
|
||
| #### Controlled Mode | ||
|
|
||
| Tabs can also be used in a controlled mode, where the parent component specifies the `selectedTabId` and the `onSelect` props to control tab selection. In this mode, the `initialTabId` prop will be ignored if it is provided. If the `selectedTabId` is `null`, no tab is selected. In this mode, if the currently selected tab becomes disabled or otherwise unavailable, the component will _not_ fall back to another available tab, leaving the controlling component in charge of implementing the desired logic. | ||
|
|
||
| ```jsx | ||
| import { Tabs } from '@wordpress/components'; | ||
| const [ selectedTabId, setSelectedTabId ] = useState< | ||
| string | undefined | null | ||
| >(); | ||
|
|
||
| const onSelect = ( tabName ) => { | ||
| console.log( 'Selecting tab', tabName ); | ||
| }; | ||
|
|
||
| const MyControlledTabs = () => ( | ||
| <Tabs | ||
| selectedTabId={ selectedTabId } | ||
| onSelect={ ( selectedId ) => { | ||
| setSelectedTabId( selectedId ); | ||
| onSelect( selectedId ); | ||
| } } | ||
| > | ||
| <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> | ||
| ); | ||
| ``` | ||
|
|
||
| ### Components and Sub-components | ||
|
|
||
| Tabs is comprised of four individual components: | ||
| - `Tabs`: a wrapper component and context provider. It is responsible for managing the state of the tabs and rendering the `TabList` and `TabPanels`. | ||
| - `TabList`: a wrapper component for the `Tab` components. It is responsible for rendering the list of tabs. | ||
| - `Tab`: renders a single tab. The currently active tab receives default styling that can be overridden with CSS targeting [aria-selected="true"]. | ||
| - `TabPanel`: renders the content to display for a single tab once that tab is selected. | ||
|
|
||
| #### Tabs | ||
|
|
||
| ##### Props | ||
|
|
||
| ###### `children`: `React.ReactNode` | ||
|
|
||
| The children elements, which should be at least a `Tabs.Tablist` component and a series of `Tabs.TabPanel` components. | ||
|
|
||
| - Required: Yes | ||
|
|
||
| ###### `selectOnMove`: `boolean` | ||
|
|
||
| When `true`, the tab will be selected when receiving focus (automatic tab activation). When `false`, the tab will be selected only when clicked (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) for more info. | ||
|
|
||
| - Required: No | ||
| - Default: `true` | ||
|
|
||
| ###### `initialTabId`: `string` | ||
|
|
||
| The id of the tab to be selected upon mounting of component. If this prop is not set, the first tab will be selected by default. The id provided will be internally prefixed with a unique instance ID to avoid collisions. | ||
|
|
||
| _Note: this prop will be overridden by the `selectedTabId` prop if it is provided. (Controlled Mode)_ | ||
|
|
||
| - Required: No | ||
|
|
||
| ###### `onSelect`: `( ( selectedId: string | null | undefined ) => void )` | ||
|
|
||
| The function called when a tab has been selected. It is passed the selected tab's ID as an argument. | ||
|
|
||
| - Required: No | ||
| - Default: `noop` | ||
|
|
||
| ###### `orientation`: `horizontal | vertical` | ||
|
|
||
| The orientation of the `tablist` (`vertical` or `horizontal`) | ||
|
|
||
| - Required: No | ||
| - Default: `horizontal` | ||
|
|
||
| ###### `selectedTabId`: `string | null` | ||
|
|
||
| The ID of the tab to display. This id is prepended with the `Tabs` instanceId internally. | ||
| If left `undefined`, the component assumes it is being used in uncontrolled mode. Consequently, any value different than `undefined` will set the component in `controlled` mode. When in controlled mode, the `null` value will result in no tab being selected. | ||
|
|
||
| - Required: No | ||
chad1008 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #### TabList | ||
|
|
||
| ##### Props | ||
|
|
||
| ###### `children`: `React.ReactNode` | ||
|
|
||
| The children elements, which should be a series of `Tabs.TabPanel` components. | ||
|
|
||
| - Required: No | ||
|
|
||
| ###### `className`: `string` | ||
|
|
||
| The class name to apply to the tablist. | ||
|
|
||
| - Required: No | ||
| - Default: '' | ||
|
|
||
| ###### `style`: `React.CSSProperties` | ||
|
|
||
| Custom CSS styles for the tablist. | ||
|
|
||
| - Required: No | ||
|
|
||
| #### Tab | ||
|
|
||
| ##### Props | ||
|
|
||
| ###### `id`: `string` | ||
|
|
||
| The id of the tab, which is prepended with the `Tabs` instance ID. | ||
|
|
||
| - Required: Yes | ||
|
|
||
| ###### `style`: `React.CSSProperties` | ||
|
|
||
| Custom CSS styles for the tab. | ||
|
|
||
| - Required: No | ||
|
|
||
| ###### `children`: `React.ReactNode` | ||
|
|
||
| The children elements, generally the text to display on the tab. | ||
|
|
||
| - Required: No | ||
|
|
||
| ###### `className`: `string` | ||
|
|
||
| The class name to apply to the tab. | ||
|
|
||
| - Required: No | ||
|
|
||
| ###### `disabled`: `boolean` | ||
|
|
||
| Determines if the tab button should be disabled. | ||
|
|
||
| - Required: No | ||
| - Default: `false` | ||
|
|
||
| ###### `render`: `React.ReactNode` | ||
|
|
||
| The type of component to render the tab button as. If this prop is not provided, the tab button will be rendered as a `button` element. | ||
|
|
||
| - Required: No | ||
|
|
||
| #### TabPanel | ||
|
|
||
| ##### Props | ||
|
|
||
| ###### `children`: `React.ReactNode` | ||
|
|
||
| The children elements, generally the content to display on the tabpanel. | ||
|
|
||
| - Required: No | ||
|
|
||
| ###### `id`: `string` | ||
chad1008 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| The id of the tabpanel, which is combined with the `Tabs` instance ID and the suffix `-view` | ||
|
|
||
| - Required: Yes | ||
|
|
||
| ###### `className`: `string` | ||
|
|
||
| The class name to apply to the tabpanel. | ||
|
|
||
| - Required: No | ||
|
|
||
| ###### `style`: `React.CSSProperties` | ||
|
|
||
| Custom CSS styles for the tab. | ||
|
|
||
| - Required: No | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { createContext, useContext } from '@wordpress/element'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import type { TabsContextProps } from './types'; | ||
|
|
||
| export const TabsContext = createContext< TabsContextProps >( undefined ); | ||
|
|
||
| export const useTabsContext = () => useContext( TabsContext ); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.