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

Tabs: Auto-generate README #68209

Merged
merged 15 commits into from
Jan 2, 2025
336 changes: 149 additions & 187 deletions packages/components/src/tabs/README.md

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions packages/components/src/tabs/docs-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "../../schemas/docs-manifest.json",
"displayName": "Tabs",
"filePath": "./index.tsx",
"subcomponents": [
{
"displayName": "TabList",
"preferredDisplayName": "Tabs.TabList",
"filePath": "./tablist.tsx"
},
{
"displayName": "Tab",
"preferredDisplayName": "Tabs.Tab",
"filePath": "./tab.tsx"
},
{
"displayName": "TabPanel",
"preferredDisplayName": "Tabs.TabPanel",
"filePath": "./tabpanel.tsx"
}
]
}
25 changes: 21 additions & 4 deletions packages/components/src/tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ function internalToExternalTabId(
}

/**
* Display one panel of content at a time with a tabbed interface, based on the
* WAI-ARIA Tabs Pattern⁠.
* 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/).
*
* @see https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
* ```
* 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.
*
* `Tabs` itself is a wrapper component and context provider.
* It is responsible for managing the state of the tabs, and rendering the `TabList` and `TabPanels`.
*/
export const Tabs = Object.assign(
function Tabs( {
Expand Down Expand Up @@ -121,12 +124,26 @@ export const Tabs = Object.assign(
);
},
{
/**
* Renders a single tab.
*
* The currently active tab receives default styling that can be
* overridden with CSS targeting `[aria-selected="true"]`.
*/
Tab: Object.assign( Tab, {
displayName: 'Tabs.Tab',
} ),
/**
* A wrapper component for the `Tab` components.
*
* It is responsible for rendering the list of tabs.
*/
TabList: Object.assign( TabList, {
displayName: 'Tabs.TabList',
} ),
/**
* Renders the content to display for a single tab once that tab is selected.
*/
TabPanel: Object.assign( TabPanel, {
displayName: 'Tabs.TabPanel',
} ),
Expand Down
92 changes: 92 additions & 0 deletions packages/components/src/tabs/stories/best-practices.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Meta } from '@storybook/blocks';

import * as TabsStories from './index.story';

<Meta of={ TabsStories } name="Best Practices" />

# Tabs

## Usage

### Uncontrolled Mode

Tabs can be used in an uncontrolled mode, where the component manages its own state. In this mode, the `defaultTabId` 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 } defaultTabId="tab2">
<Tabs.TabList>
<Tabs.Tab tabId="tab1" title="Tab 1">
Tab 1
</Tabs.Tab>
<Tabs.Tab tabId="tab2" title="Tab 2">
Tab 2
</Tabs.Tab>
<Tabs.Tab tabId="tab3" title="Tab 3">
Tab 3
</Tabs.Tab>
</Tabs.TabList>
<Tabs.TabPanel tabId="tab1">
<p>Selected tab: Tab 1</p>
</Tabs.TabPanel>
<Tabs.TabPanel tabId="tab2">
<p>Selected tab: Tab 2</p>
</Tabs.TabPanel>
<Tabs.TabPanel tabId="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 `defaultTabId` 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.

```tsx
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 tabId="tab1" title="Tab 1">
Tab 1
</Tabs.Tab>
<Tabs.Tab tabId="tab2" title="Tab 2">
Tab 2
</Tabs.Tab>
<Tabs.Tab tabId="tab3" title="Tab 3">
Tab 3
</Tabs.Tab>
</Tabs.TabList>
<Tabs.TabPanel tabId="tab1">
<p>Selected tab: Tab 1</p>
</Tabs.TabPanel>
<Tabs.TabPanel tabId="tab2">
<p>Selected tab: Tab 2</p>
</Tabs.TabPanel>
<Tabs.TabPanel tabId="tab3">
<p>Selected tab: Tab 3</p>
</Tabs.TabPanel>
</Tabs>
);
```
6 changes: 6 additions & 0 deletions packages/components/src/tabs/tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import {
import type { WordPressComponentProps } from '../context';
import { chevronRight } from '@wordpress/icons';

/**
* Renders a single tab.
*
* The currently active tab receives default styling that can be
* overridden with CSS targeting `[aria-selected="true"]`.
*/
export const Tab = forwardRef<
HTMLButtonElement,
Omit< WordPressComponentProps< TabProps, 'button', false >, 'id' >
Expand Down
5 changes: 5 additions & 0 deletions packages/components/src/tabs/tablist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ function useScrollRectIntoView(
}, [ margin, parent, rect ] );
}

/**
* A wrapper component for the `Tab` components.
*
* It is responsible for rendering the list of tabs.
*/
export const TabList = forwardRef<
HTMLDivElement,
WordPressComponentProps< TabListProps, 'div', false >
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/tabs/tabpanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import warning from '@wordpress/warning';
import { useTabsContext } from './context';
import type { WordPressComponentProps } from '../context';

/**
* Renders the content to display for a single tab once that tab is selected.
*/
export const TabPanel = forwardRef<
HTMLDivElement,
Omit< WordPressComponentProps< TabPanelProps, 'div', false >, 'id' >
Expand Down
6 changes: 3 additions & 3 deletions packages/components/src/tabs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ export type TabsProps = {
/**
* Determines if the tab should be selected when it receives focus. If set to
* `false`, the tab will only be selected upon clicking, not when using arrow
* keys to shift focus (manual tab activation). See the official W3C docs
* keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/)
* for more info.
*
* @default true
*
* @see https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/
Copy link
Member Author

Choose a reason for hiding this comment

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

We should avoid @see tags as they are suppressed in Storybook props tables, and don't make sense when displayed unformatted in the auto-generated README.

*/
selectOnMove?: Ariakit.TabStoreProps[ 'selectOnMove' ];
/**
Expand Down Expand Up @@ -63,6 +61,7 @@ export type TabsProps = {
/**
* The current active tab `id`. The active tab is the tab element within the
* tablist widget that has DOM focus.
*
* - `null` represents the tablist (ie. the base composite element). Users
* will be able to navigate out of it using arrow keys.
* - If `activeTabId` is initially set to `null`, the base composite element
Expand All @@ -87,6 +86,7 @@ export type TabsProps = {
/**
* Defines the orientation of the tablist and determines which arrow keys
* can be used to move focus:
*
* - `both`: all arrow keys work.
* - `horizontal`: only left and right arrow keys work.
* - `vertical`: only up and down arrow keys work.
Expand Down
Loading