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

BREAKING CHANGE(web-react): Rename Tabs props and clean up its types #DS-1096 #1405

Merged
merged 2 commits into from
May 14, 2024
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
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 @@ -20,6 +20,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 @@ -281,6 +282,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
// @ts-ignore: No declaration -- The library is not installed; we don't need to install it for fixtures.
import { TabItem, TabPane } from '@lmc-eu/spirit-web-react';

export const MyComponent = () => (
<>
<TabItem forTab="tab1" data-test="test" />
<TabPane tabId="tab1" />
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
// @ts-ignore: No declaration -- The library is not installed; we don't need to install it for fixtures.
import { TabItem, TabPane } from '@lmc-eu/spirit-web-react';

export const MyComponent = () => (
<>
<TabItem forTabPane="tab1" data-test="test" />
<TabPane id="tab1" />
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { testTransform } from '../../../../../tests/testUtils';

testTransform(__dirname, 'tabs-tabitem-tabpane-props');
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { API, FileInfo } from 'jscodeshift';

const transform = (fileInfo: FileInfo, api: API) => {
const j = api.jscodeshift;
const root = j(fileInfo.source);

crishpeen marked this conversation as resolved.
Show resolved Hide resolved
// Find import statements for the specific module and TabItem or TabPane specifier
const importStatements = root.find(j.ImportDeclaration, {
source: {
value: (value: string) => /^@lmc-eu\/spirit-web-react(\/.*)?$/.test(value),
},
});

// Check if the module is imported
if (importStatements.length > 0) {
const tabItemComponentSpecifier = importStatements.find(j.ImportSpecifier, {
imported: {
type: 'Identifier',
name: 'TabItem',
},
});

const tabPaneComponentSpecifier = importStatements.find(j.ImportSpecifier, {
imported: {
type: 'Identifier',
name: 'TabPane',
},
});

// Check if TabItem specifier is present
if (tabItemComponentSpecifier.length > 0) {
// Find TabItem components in the module
const components = root.find(j.JSXOpeningElement, {
name: {
type: 'JSXIdentifier',
name: 'TabItem',
},
});

// Rename 'forTab' attribute to 'forTabPane'
components
.find(j.JSXAttribute, {
name: {
type: 'JSXIdentifier',
name: 'forTab',
},
})
.forEach((attributePath) => {
attributePath.node.name.name = 'forTabPane';
});
}

// Check if TabPane specifier is present
if (tabPaneComponentSpecifier.length > 0) {
// Find TabPane components in the module
const components = root.find(j.JSXOpeningElement, {
name: {
type: 'JSXIdentifier',
name: 'TabPane',
},
});

// Rename 'tabId' attribute to 'id'
components
.find(j.JSXAttribute, {
name: {
type: 'JSXIdentifier',
name: 'tabId',
},
})
.forEach((attributePath) => {
attributePath.node.name.name = 'id';
});
}
}

return root.toSource();
};

export default transform;
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
Loading
Loading