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: Update subcomponents to accept full HTML element props #55860

Merged
merged 5 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
### Experimental

- `Tabs`: Add `focusable` prop to the `Tabs.TabPanel` sub-component ([#55287](https://github.com/WordPress/gutenberg/pull/55287))
- `Tabs`: Update sub-components to accept relevant HTML element props ([#55860](https://github.com/WordPress/gutenberg/pull/55860))

### Enhancements

Expand Down
37 changes: 0 additions & 37 deletions packages/components/src/tabs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,6 @@ 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
Expand All @@ -182,24 +169,12 @@ 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.
Expand Down Expand Up @@ -229,18 +204,6 @@ The id of the tabpanel, which is combined with the `Tabs` instance ID and the su

- 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

###### `focusable`: `boolean`

Determines whether or not the tabpanel element should be focusable. If `false`, pressing the tab key will skip over the tabpanel, and instead focus on the first focusable element in the panel (if there is one).
Expand Down
12 changes: 6 additions & 6 deletions packages/components/src/tabs/tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import type { TabProps } from './types';
import warning from '@wordpress/warning';
import { TabsContext } from './context';
import { Tab as StyledTab } from './styles';
import type { WordPressComponentProps } from '../context';

export const Tab = forwardRef< HTMLButtonElement, TabProps >( function Tab(
{ children, id, className, disabled, render, style },
ref
) {
export const Tab = forwardRef<
HTMLButtonElement,
WordPressComponentProps< TabProps, 'button', false >
>( function Tab( { children, id, disabled, render, ...otherProps }, ref ) {
const context = useContext( TabsContext );
if ( ! context ) {
warning( '`Tabs.TabList` must be wrapped in a `Tabs` component.' );
Expand All @@ -28,10 +29,9 @@ export const Tab = forwardRef< HTMLButtonElement, TabProps >( function Tab(
ref={ ref }
store={ store }
id={ instancedTabId }
className={ className }
style={ style }
disabled={ disabled }
render={ render }
{ ...otherProps }
>
{ children }
</StyledTab>
Expand Down
41 changes: 21 additions & 20 deletions packages/components/src/tabs/tablist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,26 @@ import { forwardRef } from '@wordpress/element';
import type { TabListProps } from './types';
import { useTabsContext } from './context';
import { TabListWrapper } from './styles';
import type { WordPressComponentProps } from '../context';

export const TabList = forwardRef< HTMLDivElement, TabListProps >(
function TabList( { children, className, style }, ref ) {
const context = useTabsContext();
if ( ! context ) {
warning( '`Tabs.TabList` must be wrapped in a `Tabs` component.' );
return null;
}
const { store } = context;
return (
<Ariakit.TabList
ref={ ref }
style={ style }
store={ store }
className={ className }
render={ <TabListWrapper /> }
>
{ children }
</Ariakit.TabList>
);
export const TabList = forwardRef<
HTMLDivElement,
WordPressComponentProps< TabListProps, 'div', false >
>( function TabList( { children, ...otherProps }, ref ) {
const context = useTabsContext();
if ( ! context ) {
warning( '`Tabs.TabList` must be wrapped in a `Tabs` component.' );
return null;
}
);
const { store } = context;
return (
<Ariakit.TabList
ref={ ref }
store={ store }
render={ <TabListWrapper /> }
{ ...otherProps }
>
{ children }
</Ariakit.TabList>
);
} );
48 changes: 23 additions & 25 deletions packages/components/src/tabs/tabpanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,28 @@ import { TabPanel as StyledTabPanel } from './styles';

import warning from '@wordpress/warning';
import { TabsContext } from './context';
import type { WordPressComponentProps } from '../context';

export const TabPanel = forwardRef< HTMLDivElement, TabPanelProps >(
function TabPanel(
{ children, id, className, style, focusable = true },
ref
) {
const context = useContext( TabsContext );
if ( ! context ) {
warning( '`Tabs.TabPanel` must be wrapped in a `Tabs` component.' );
return null;
}
const { store, instanceId } = context;

return (
<StyledTabPanel
focusable={ focusable }
ref={ ref }
style={ style }
store={ store }
id={ `${ instanceId }-${ id }-view` }
className={ className }
>
{ children }
</StyledTabPanel>
);
export const TabPanel = forwardRef<
HTMLDivElement,
WordPressComponentProps< TabPanelProps, 'div', false >
>( function TabPanel( { children, id, focusable = true, ...otherProps }, ref ) {
const context = useContext( TabsContext );
if ( ! context ) {
warning( '`Tabs.TabPanel` must be wrapped in a `Tabs` component.' );
return null;
}
);
const { store, instanceId } = context;

return (
<StyledTabPanel
ref={ ref }
store={ store }
id={ `${ instanceId }-${ id }-view` }
focusable={ focusable }
{ ...otherProps }
>
{ children }
</StyledTabPanel>
);
} );
21 changes: 6 additions & 15 deletions packages/components/src/tabs/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,20 @@ import userEvent from '@testing-library/user-event';
/**
* WordPress dependencies
*/
import { wordpress, category, media } from '@wordpress/icons';
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import Tabs from '..';
import type { TabsProps } from '../types';
import type { IconType } from '../../icon';

type Tab = {
id: string;
title: string;
content: React.ReactNode;
tab: {
className?: string;
icon?: IconType;
disabled?: boolean;
};
tabpanel?: {
Expand All @@ -36,19 +33,19 @@ const TABS: Tab[] = [
id: 'alpha',
title: 'Alpha',
content: 'Selected tab: Alpha',
tab: { className: 'alpha-class', icon: wordpress },
tab: { className: 'alpha-class' },
},
{
id: 'beta',
title: 'Beta',
content: 'Selected tab: Beta',
tab: { className: 'beta-class', icon: category },
tab: { className: 'beta-class' },
},
{
id: 'gamma',
title: 'Gamma',
content: 'Selected tab: Gamma',
tab: { className: 'gamma-class', icon: media },
tab: { className: 'gamma-class' },
},
];

Expand All @@ -58,17 +55,15 @@ const TABS_WITH_DELTA: Tab[] = [
id: 'delta',
title: 'Delta',
content: 'Selected tab: Delta',
tab: { className: 'delta-class', icon: media },
tab: { className: 'delta-class' },
},
];

const UncontrolledTabs = ( {
tabs,
showTabIcons = false,
...props
}: Omit< TabsProps, 'children' > & {
tabs: Tab[];
showTabIcons?: boolean;
} ) => {
return (
<Tabs { ...props }>
Expand All @@ -79,9 +74,8 @@ const UncontrolledTabs = ( {
id={ tabObj.id }
className={ tabObj.tab.className }
disabled={ tabObj.tab.disabled }
icon={ showTabIcons ? tabObj.tab.icon : undefined }
>
{ showTabIcons ? null : tabObj.title }
{ tabObj.title }
</Tabs.Tab>
) ) }
</Tabs.TabList>
Expand All @@ -100,11 +94,9 @@ const UncontrolledTabs = ( {

const ControlledTabs = ( {
tabs,
showTabIcons = false,
...props
}: Omit< TabsProps, 'children' > & {
tabs: Tab[];
showTabIcons?: boolean;
} ) => {
const [ selectedTabId, setSelectedTabId ] = useState<
string | undefined | null
Expand All @@ -126,9 +118,8 @@ const ControlledTabs = ( {
id={ tabObj.id }
className={ tabObj.tab.className }
disabled={ tabObj.tab.disabled }
icon={ showTabIcons ? tabObj.tab.icon : undefined }
>
{ showTabIcons ? null : tabObj.title }
{ tabObj.title }
</Tabs.Tab>
) ) }
</Tabs.TabList>
Expand Down
33 changes: 0 additions & 33 deletions packages/components/src/tabs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
// eslint-disable-next-line no-restricted-imports
import type * as Ariakit from '@ariakit/react';

/**
* Internal dependencies
*/
import type { IconType } from '../icon';

export type TabsContextProps =
| {
/**
Expand Down Expand Up @@ -78,37 +73,17 @@ export type TabListProps = {
* The children elements, which should be a series of `Tabs.TabPanel` components.
*/
children?: React.ReactNode;
/**
* The class name to apply to the tablist.
*/
className?: string;
/**
* Custom CSS styles for the rendered tablist.
*/
style?: React.CSSProperties;
};

export type TabProps = {
/**
* The id of the tab, which is prepended with the `Tabs` instanceId.
*/
id: string;
/**
* Custom CSS styles for the tab.
*/
style?: React.CSSProperties;
/**
* The children elements, generally the text to display on the tab.
*/
children?: React.ReactNode;
/**
* The class name to apply to the tab button.
*/
className?: string;
/**
* The icon used for the tab button.
*/
icon?: IconType;
/**
* Determines if the tab button should be disabled.
*
Expand All @@ -131,14 +106,6 @@ export type TabPanelProps = {
* A unique identifier for the tabpanel, which is used to generate a unique `id` for the underlying element.
*/
id: string;
/**
* The class name to apply to the tabpanel.
*/
className?: string;
/**
* Custom CSS styles for the rendered `TabPanel` component.
*/
style?: React.CSSProperties;
/**
* Determines whether or not the tabpanel element should be focusable.
* If `false`, pressing the tab key will skip over the tabpanel, and instead
Expand Down
Loading