Skip to content

Commit

Permalink
BREAKING CHANGE(web-react): Rename Tabs props and clean up its types …
Browse files Browse the repository at this point in the history
…#DS-1096

Rename TabItem prop `forTab` to `forTabPane`.
Rename TabPane prop `tabId` to `id`.

See the Tabs: TabItem and TabPane Props section in the web-react
package Migration Guide to version 2.
  • Loading branch information
crishpeen committed May 9, 2024
1 parent 6212b9d commit 0d3564f
Show file tree
Hide file tree
Showing 26 changed files with 217 additions and 200 deletions.
21 changes: 21 additions & 0 deletions docs/migrations/web-react/MIGRATION-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Introducing version 2 of the _spirit-web-react_ package
- [Modal: ModalDialog `isExpandedOnMobile` Prop](#modal-modaldialog-isexpandedonmobile-prop)
- [Modal: ModalDialog `isScrollable` Prop](#modal-modaldialog-isscrollable-prop)
- [Modal: ModalDialog Uniform Appearance](#modal-modaldialog-uniform-appearance)
- [Tabs: TabItem and TabPane Props](#tabs-tabitem-and-tabpane-props)
- [TextField: `label` prop](#textfield-label-prop)
- [Tooltip: `off` Placement](#tooltip-off-placement)
- [Tooltip: Refactored](#tooltip-refactored)
Expand Down Expand Up @@ -263,6 +264,26 @@ See [Codemods documentation][readme-codemods] for more details.

Or manually add `isDockedOnMobile` prop to the `ModalDialog` component.

### Tabs: TabItem and TabPane Props

TabItem `forTab` prop is renamed to `forTabPane`.
TabPane `tabId` prop is renamed to `id`.

#### Migration Guide

Use codemod to automatically update your codebase.

```sh
npx @lmc-eu/spirit-codemods -p <path> -t v2/web-react/tabs-tabitem-tabpane-props
```

See [Codemods documentation][readme-codemods] for more details.

Or manually replace the props in your project.

- `<TabItem forTab="TabPane1" … />``<TabItem forTabPane="TabPane1" … />`
- `<TabPane tabId="TabPane1" … />``<TabPane id="TabPane1" … />`

### TextField: `label` prop

The `label` prop is now required.
Expand Down
72 changes: 36 additions & 36 deletions packages/web-react/src/components/Tabs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,43 @@ groups of information in tabbable regions.
## Tab

```jsx
const [selectedTabId, setSelectedTab] = useState(1);
const [selectedId, setSelectedTab] = useState(1);

const selectTab = useCallback((tabId) => {
setSelectedTab(tabId);
const selectTab = useCallback((id) => {
setSelectedTab(id);
}, []);

<Tabs selectedTab={selectedTabId} toggle={selectTab}>
<Tabs selectedTab={selectedId} toggle={selectTab}>
<TabList>
<TabItem forTab={1}>Item Selected</TabItem>
<TabItem forTab={2}>Item</TabItem>
<TabItem forTabPane={1}>Item Selected</TabItem>
<TabItem forTabPane={2}>Item</TabItem>
</TabList>
<TabContent>
<TabPane tabId={1}>Pane 1</TabPane>
<TabPane tabId={2}>Pane 2</TabPane>
<TabPane id={1}>Pane 1</TabPane>
<TabPane id={2}>Pane 2</TabPane>
</TabContent>
</Tabs>;
```

## Tab with Links

```jsx
const [selectedTabId, setSelectedTab] = useState(1);
const [selectedId, setSelectedTab] = useState(1);

const selectTab = useCallback((tabId) => {
setSelectedTab(tabId);
const selectTab = useCallback((id) => {
setSelectedTab(id);
}, []);

<Tabs selectedTab={selectedTabId} toggle={selectTab}>
<Tabs selectedTab={selectedId} toggle={selectTab}>
<TabList>
<TabItem forTab={1}>Item Selected</TabItem>
<TabItem forTab={2}>Item</TabItem>
<TabItem forTabPane={1}>Item Selected</TabItem>
<TabItem forTabPane={2}>Item</TabItem>
<TabLink href="https://www.example.com">Item Link</TabLink>
<TabLink href="https://www.example.com">Item Link Only Desktop</TabLink>
</TabList>
<TabContent>
<TabPane tabId={1}>Pane 1</TabPane>
<TabPane tabId={2}>Pane 2</TabPane>
<TabPane id={1}>Pane 1</TabPane>
<TabPane id={2}>Pane 2</TabPane>
</TabContent>
</Tabs>;
```
Expand All @@ -52,12 +52,12 @@ const selectTab = useCallback((tabId) => {
```jsx
<UncontrolledTabs defaultSelectedTab={1}>
<TabList>
<TabItem forTab={1}>Item Selected</TabItem>
<TabItem forTab={2}>Item</TabItem>
<TabItem forTabPane={1}>Item Selected</TabItem>
<TabItem forTabPane={2}>Item</TabItem>
</TabList>
<TabContent>
<TabPane tabId={1}>Pane 1</TabPane>
<TabPane tabId={2}>Pane 2</TabPane>
<TabPane id={1}>Pane 1</TabPane>
<TabPane id={2}>Pane 2</TabPane>
</TabContent>
</UncontrolledTabs>
```
Expand All @@ -66,12 +66,12 @@ const selectTab = useCallback((tabId) => {

#### API

| Name | Type | Default | Required | Description |
| ------------------- | ------------------------ | ------- | -------- | -------------------------------------------- |
| `selectedTab` | [`string` \| `number`] ||| Identification of the selected tab |
| `toogle` | `Function` ||| Toggle function which accept tab ID as input |
| `children` | `any` ||| Child component |
| `onSelectionChange` | `(tabId: TabId) => void` ||| When the state of the selected panel changes |
| Name | Type | Default | Required | Description |
| ------------------- | ---------------------- | ------- | -------- | -------------------------------------------- |
| `selectedTab` | [`string` \| `number`] ||| Identification of the selected tab |
| `toogle` | `Function` ||| Toggle function which accept tab ID as input |
| `children` | `any` ||| Child component |
| `onSelectionChange` | `(id: TabId) => void` ||| When the state of the selected panel changes |

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
Expand All @@ -81,11 +81,11 @@ and [escape hatches][readme-escape-hatches].

#### API

| Name | Type | Default | Required | Description |
| -------------------- | ------------------------ | ------- | -------- | -------------------------------------------- |
| `defaultSelectedTab` | [`string` \| `number`] ||| Identification of default selected tab |
| `children` | `any` ||| Child component |
| `onSelectionChange` | `(tabId: TabId) => void` ||| When the state of the selected panel changes |
| Name | Type | Default | Required | Description |
| -------------------- | ---------------------- | ------- | -------- | -------------------------------------------- |
| `defaultSelectedTab` | [`string` \| `number`] ||| Identification of default selected tab |
| `children` | `any` ||| Child component |
| `onSelectionChange` | `(id: TabId) => void` ||| When the state of the selected panel changes |

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
Expand All @@ -111,10 +111,10 @@ Tab list item

#### API

| Name | Type | Default | Required | Description |
| ---------- | ---------------------- | ------- | -------- | --------------------- |
| `forTab` | [`string` \| `number`] ||| Identification of tab |
| `children` | `any` ||| Child component |
| Name | Type | Default | Required | Description |
| ------------ | ---------------------- | ------- | -------- | --------------------- |
| `forTabPane` | [`string` \| `number`] ||| Identification of tab |
| `children` | `any` ||| Child component |

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
Expand Down Expand Up @@ -158,7 +158,7 @@ Tab content item

| Name | Type | Default | Required | Description |
| ---------- | ---------------------- | ------- | -------- | --------------------- |
| `tabId` | [`string` \| `number`] ||| Identification of tab |
| `id` | [`string` \| `number`] ||| Identification of tab |
| `children` | `any` ||| Child component |

On top of the API options, the components accept [additional attributes][readme-additional-attributes].
Expand Down
4 changes: 1 addition & 3 deletions packages/web-react/src/components/Tabs/TabContent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react';
import { useStyleProps } from '../../hooks';
import { ChildrenProps, TransferProps } from '../../types';

export type TabContentProps = ChildrenProps & TransferProps;
import { TabContentProps } from '../../types';

const TabContent = ({ children, ...restProps }: TabContentProps): JSX.Element => {
const { styleProps, props: transferProps } = useStyleProps(restProps);
Expand Down
16 changes: 4 additions & 12 deletions packages/web-react/src/components/Tabs/TabContext.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import { createContext, useContext } from 'react';
import { TabId } from '../../types';

type TabsToggler = (tabId: TabId) => void;

type TabsContextType = {
selectedTabId: TabId;
selectTab: TabsToggler;
onSelectionChange?: (tabId: TabId) => void;
};
import { TabId, TabsContextType, TabsToggler } from '../../types';

const defaultContext: TabsContextType = {
selectedTabId: '',
selectedId: '',
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
selectTab: (tabId: TabId) => {},
selectTab: (id: TabId) => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onSelectionChange: (tabId: TabId) => {},
onSelectionChange: (id: TabId) => {},
};

const TabsContext = createContext<TabsContextType>(defaultContext);
Expand Down
22 changes: 9 additions & 13 deletions packages/web-react/src/components/Tabs/TabItem.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
import React from 'react';
import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { ChildrenProps, TabId, TransferProps, ClickEvents, ClickEvent } from '../../types';
import { ClickEvent, TabItemProps } from '../../types';
import { useTabContext } from './TabContext';
import { useTabsStyleProps } from './useTabsStyleProps';

export interface TabItemProps extends ChildrenProps, TransferProps, ClickEvents {
forTab: TabId;
}

const TabItem = ({ children, forTab, onClick, ...restProps }: TabItemProps): JSX.Element => {
const { selectTab, selectedTabId, onSelectionChange } = useTabContext();
const { classProps } = useTabsStyleProps({ forTab, selectedTabId });
const TabItem = ({ children, forTabPane, onClick, ...restProps }: TabItemProps): JSX.Element => {
const { selectTab, selectedId, onSelectionChange } = useTabContext();
const { classProps } = useTabsStyleProps({ forTabPane, selectedId });
const { styleProps, props: transferProps } = useStyleProps(restProps);

const handleClick = (event: ClickEvent) => {
selectTab(forTab);
selectTab(forTabPane);

if (onClick) {
onClick(event);
}

if (onSelectionChange) {
onSelectionChange(selectedTabId);
onSelectionChange(selectedId);
}
};

Expand All @@ -34,9 +30,9 @@ const TabItem = ({ children, forTab, onClick, ...restProps }: TabItemProps): JSX
type="button"
className={classNames(classProps.link, styleProps.className)}
role="tab"
aria-selected={selectedTabId === forTab}
id={`${forTab}-tab`}
aria-controls={forTab.toString()}
aria-selected={selectedId === forTabPane}
id={`${forTabPane}-tab`}
aria-controls={forTabPane.toString()}
onClick={handleClick}
>
{children}
Expand Down
4 changes: 1 addition & 3 deletions packages/web-react/src/components/Tabs/TabList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React from 'react';
import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { ChildrenProps, TransferProps } from '../../types';
import { TabListProps } from '../../types';
import { useTabsStyleProps } from './useTabsStyleProps';

export type TabListProps = ChildrenProps & TransferProps;

const TabList = ({ children, ...restProps }: TabListProps): JSX.Element => {
const { classProps } = useTabsStyleProps();
const { styleProps, props: transferProps } = useStyleProps(restProps);
Expand Down
18 changes: 7 additions & 11 deletions packages/web-react/src/components/Tabs/TabPane.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import React from 'react';
import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { ChildrenProps, TabId, TransferProps } from '../../types';
import { TabPaneProps } from '../../types';
import { useTabContext } from './TabContext';
import { useTabsStyleProps } from './useTabsStyleProps';

export interface TabPaneProps extends ChildrenProps, TransferProps {
tabId: TabId;
}

const TabPane = ({ children, tabId, ...restProps }: TabPaneProps): JSX.Element | null => {
const { selectedTabId } = useTabContext();
const { classProps } = useTabsStyleProps({ tabId, selectedTabId });
const TabPane = ({ children, id, ...restProps }: TabPaneProps): JSX.Element | null => {
const { selectedId } = useTabContext();
const { classProps } = useTabsStyleProps({ id, selectedId });
const { styleProps, props: transferProps } = useStyleProps(restProps);

return selectedTabId === tabId ? (
return selectedId === id ? (
<div
{...transferProps}
{...styleProps}
id={tabId.toString()}
id={id.toString()}
className={classNames(classProps.pane, styleProps.className)}
role="tabpanel"
aria-labelledby={`${tabId}-tab`}
aria-labelledby={`${id}-tab`}
>
{children}
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/web-react/src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TabsProvider } from './TabContext';
import { TabsProps } from '../../types';

const Tabs = ({ children, selectedTab, toggle: selectTab, onSelectionChange }: TabsProps): JSX.Element => (
<TabsProvider value={{ selectedTabId: selectedTab, selectTab, onSelectionChange }}>{children}</TabsProvider>
<TabsProvider value={{ selectedId: selectedTab, selectTab, onSelectionChange }}>{children}</TabsProvider>
);

export default Tabs;
14 changes: 4 additions & 10 deletions packages/web-react/src/components/Tabs/UncontrolledTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import React from 'react';
import { ChildrenProps, TabId, TransferProps } from '../../types';
import { UncontrolledTabsProps } from '../../types';
import { TabsProvider } from './TabContext';
import { useTab } from './useTabs';

interface TabsProps extends ChildrenProps, TransferProps {
defaultSelectedTab: TabId;
// eslint-disable-next-line react/require-default-props
onSelectionChange?: (tabId: TabId) => void;
}
const Tabs = ({ children, defaultSelectedTab, onSelectionChange }: UncontrolledTabsProps): JSX.Element => {
const { selectedId, selectTab } = useTab(defaultSelectedTab);

const Tabs = ({ children, defaultSelectedTab, onSelectionChange }: TabsProps): JSX.Element => {
const { selectedTabId, selectTab } = useTab(defaultSelectedTab);

return <TabsProvider value={{ selectedTabId, selectTab, onSelectionChange }}>{children}</TabsProvider>;
return <TabsProvider value={{ selectedId, selectTab, onSelectionChange }}>{children}</TabsProvider>;
};

export default Tabs;
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import { withTabsContext } from '../../../../tests/testUtils/withTabsContext';
import TabItem from '../TabItem';

describe('TabItem', () => {
stylePropsTest((props) => <TabItem forTab={1} data-testid="TabItemTestId" {...props} />, 'TabItemTestId');
stylePropsTest((props) => <TabItem forTabPane={1} data-testid="TabItemTestId" {...props} />, 'TabItemTestId');

classNamePrefixProviderTest(() => <TabItem forTab={0} />, 'Tabs__item');
classNamePrefixProviderTest(() => <TabItem forTabPane={0} />, 'Tabs__item');

restPropsTest((props) => <TabItem forTab={0} {...props} />, 'button');
restPropsTest((props) => <TabItem forTabPane={0} {...props} />, 'button');

it('should render button tag when there is no href prop', () => {
const dom = render(<TabItem forTab={0} />);
const dom = render(<TabItem forTabPane={0} />);

const element = dom.container.querySelector('button') as HTMLElement;
expect(element).toHaveClass('Tabs__link');
});

it('should have ARIA attributtes', () => {
const dom = render(<TabItem forTab="test" />);
const dom = render(<TabItem forTabPane="test" />);

const element = dom.container.querySelector('button') as HTMLElement;
expect(element).toHaveAttribute('id', 'test-tab');
Expand All @@ -32,7 +32,7 @@ describe('TabItem', () => {

it('should handle tab switch when clicked', async () => {
const selectTabMock = jest.fn();
const dom = render(withTabsContext(TabItem, { selectedTabId: 0, selectTab: selectTabMock })({ forTab: 'test' }));
const dom = render(withTabsContext(TabItem, { selectedId: 0, selectTab: selectTabMock })({ forTabPane: 'test' }));

fireEvent.click(dom.getByRole('tab') as HTMLElement);

Expand Down
Loading

0 comments on commit 0d3564f

Please sign in to comment.