From a7723377dd10725f2e7e079b28b2646d6fc19b69 Mon Sep 17 00:00:00 2001 From: Christian Seidel Date: Tue, 15 Sep 2026 14:48:53 +0200 Subject: [PATCH] feat(Tabs): order tab panels independently of mount order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TabsHeader listed the tabs in the order they registered themselves, so a panel that mounted later always appeared last, no matter where it sat in the children. The new optional order prop decides the position instead. Panels without an order keep the order they mounted in, so existing consumers are unaffected. 🤖 Generated with Claude Code --- src/Tabs/TabPanel.tsx | 3 ++- src/Tabs/TabsHeader.tsx | 6 +++++- src/Tabs/index.test.tsx | 21 +++++++++++++++++++++ src/Tabs/types.ts | 4 ++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Tabs/TabPanel.tsx b/src/Tabs/TabPanel.tsx index 47517d54..34fec346 100644 --- a/src/Tabs/TabPanel.tsx +++ b/src/Tabs/TabPanel.tsx @@ -10,13 +10,14 @@ export function TabPanel(props: TabPanelProps) { context.registerTab({ id: props.id, title: props.title, + order: props.order, }); return () => { context.unregisterTab(props.id); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.id, props.title]); + }, [props.id, props.title, props.order]); return (
` `} `; +function sortedByOrder(tabs: Array): Array { + return [...tabs].sort((tab, other) => (tab.order ?? 0) - (other.order ?? 0)); +} + export function TabsHeader() { const context = useContext(TabsContext); 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..5952dfbf 100644 --- a/src/Tabs/index.test.tsx +++ b/src/Tabs/index.test.tsx @@ -50,6 +50,27 @@ 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('set active tab to last tabID', () => { const title1 = 'My tab title1'; const content1 = 'My tab content1'; 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; };