diff --git a/src/Tabs/TabPanel.tsx b/src/Tabs/TabPanel.tsx
index 47517d54..ac8a9885 100644
--- a/src/Tabs/TabPanel.tsx
+++ b/src/Tabs/TabPanel.tsx
@@ -10,13 +10,18 @@ export function TabPanel(props: TabPanelProps) {
context.registerTab({
id: props.id,
title: props.title,
+ order: props.order,
});
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [props.id, props.title, props.order]);
- return () => {
+ useEffect(
+ () => () => {
context.unregisterTab(props.id);
- };
+ },
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [props.id, props.title]);
+ [props.id],
+ );
return (
= {
@@ -33,7 +34,12 @@ function reducer(
(tab) => tab.id === action.newTab.id,
);
if (tabWasAlreadyRegistered) {
- return state;
+ return {
+ ...state,
+ tabs: state.tabs.map((tab) =>
+ tab.id === action.newTab.id ? action.newTab : tab,
+ ),
+ };
}
return {
@@ -46,14 +52,13 @@ function reducer(
const tabsWithoutUnregistered = state.tabs.filter(
(tab) => tab.id !== action.tabID,
);
- const firstTab = tabsWithoutUnregistered[0];
+ if (state.activeTabID !== action.tabID) {
+ return { ...state, tabs: tabsWithoutUnregistered };
+ }
return {
tabs: tabsWithoutUnregistered,
- activeTabID:
- state.activeTabID === action.tabID && firstTab
- ? firstTab.id
- : undefined,
+ activeTabID: sortedByOrder(tabsWithoutUnregistered)[0]?.id,
};
}
case 'onSelected': {
diff --git a/src/Tabs/TabsHeader.tsx b/src/Tabs/TabsHeader.tsx
index 7214487e..ada916bb 100644
--- a/src/Tabs/TabsHeader.tsx
+++ b/src/Tabs/TabsHeader.tsx
@@ -2,6 +2,7 @@ import React, { useContext } from 'react';
import styled, { css } from 'styled-components';
import { TabsContext } from './TabsContext';
+import { sortedByOrder } from './sortedByOrder';
import { TabPanelProps } from './types';
const TitleList = styled.ul`
@@ -43,7 +44,7 @@ export function TabsHeader() {
return (
- {context.tabs.map((tab: TabPanelProps) => {
+ {sortedByOrder(context.tabs).map((tab: TabPanelProps) => {
const isActiveTab = context.activeTabID === tab.id;
return (
diff --git a/src/Tabs/index.test.tsx b/src/Tabs/index.test.tsx
index 1bf744f5..718e5e84 100644
--- a/src/Tabs/index.test.tsx
+++ b/src/Tabs/index.test.tsx
@@ -50,6 +50,77 @@ describe('Tabs', () => {
expect(tabContent2).toBeVisible();
});
+ it('shows tabs in ascending order, no matter when they mount', () => {
+ const { rerender } = render(
+
+
+
+ ,
+ );
+
+ rerender(
+
+
+
+
+ ,
+ );
+
+ expect(
+ screen.getAllByRole('listitem').map((title) => title.textContent),
+ ).toEqual(['first', 'second', 'third']);
+ });
+
+ it('keeps the active tab when another panel changes its props', () => {
+ const { rerender } = render(
+
+
+ content of first
+
+
+ content of second
+
+ ,
+ );
+
+ rerender(
+
+
+ content of first
+
+
+ content of second
+
+ ,
+ );
+
+ expect(screen.getByText('content of first')).toBeVisible();
+ expect(
+ screen.getAllByRole('listitem').map((title) => title.textContent),
+ ).toEqual(['first', 'renamed']);
+ });
+
+ it('activates the first displayed tab when the active one unmounts', () => {
+ const { rerender } = render(
+
+
+
+
+ ,
+ );
+
+ fireEvent.click(screen.getByText('third'));
+
+ rerender(
+
+
+
+ ,
+ );
+
+ expect(screen.getByText('first')).toHaveAttribute('aria-selected', 'true');
+ });
+
it('set active tab to last tabID', () => {
const title1 = 'My tab title1';
const content1 = 'My tab content1';
diff --git a/src/Tabs/sortedByOrder.ts b/src/Tabs/sortedByOrder.ts
new file mode 100644
index 00000000..e8b957c6
--- /dev/null
+++ b/src/Tabs/sortedByOrder.ts
@@ -0,0 +1,7 @@
+import { TabPanelProps } from './types';
+
+export function sortedByOrder(
+ tabs: Array>,
+): Array> {
+ return [...tabs].sort((tab, other) => (tab.order ?? 0) - (other.order ?? 0));
+}
diff --git a/src/Tabs/types.ts b/src/Tabs/types.ts
index 8947ccdf..54b28d7f 100644
--- a/src/Tabs/types.ts
+++ b/src/Tabs/types.ts
@@ -11,5 +11,9 @@ export type TabsProps = {
export type TabPanelProps = {
id: TTabID;
title: ReactNode;
+
+ /** Ascending, panels sharing an order keep the order they mounted in. */
+ order?: number;
+
children?: ReactNode;
};