Skip to content

Commit 6981b5a

Browse files
dsmmckenCopilot
andauthored
feat: adds a render slot to NavTabList after content (#2565)
To adopt NavTabList in enterprise, we need to be able to attach a popper to a navtab. This PR creates a slot that goes after the title, that takes a render function, passing in the tab. Enterprise has a diverged from Core and is lacking bug fixes, so I would like to get NavTabList de-duplicated and used in Enterprise. Testing: Tested by manually creating popper in styleguide, worked as expected. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: dsmmcken <[email protected]>
1 parent 590def3 commit 6981b5a

File tree

3 files changed

+62
-0
lines changed

3 files changed

+62
-0
lines changed

packages/components/src/navigation/NavTab.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ interface NavTabProps {
1818
index: number;
1919
isDraggable: boolean;
2020
contextActions?: ResolvableContextAction | ResolvableContextAction[];
21+
/**
22+
* Optional render function to render content after the tab title.
23+
*
24+
* @param tab The tab to render content for
25+
* @returns The content to render after the tab title
26+
*/
27+
renderAfterTabContent?: (tab: NavTabItem) => React.ReactNode;
2128
}
2229

2330
const NavTab = memo(
@@ -30,6 +37,7 @@ const NavTab = memo(
3037
index,
3138
isDraggable,
3239
contextActions,
40+
renderAfterTabContent,
3341
}: NavTabProps) => {
3442
const { key, isClosable = onClose != null, title, icon } = tab;
3543

@@ -98,6 +106,7 @@ const NavTab = memo(
98106
{title}
99107
<Tooltip>{title}</Tooltip>
100108
</span>
109+
{renderAfterTabContent?.(tab)}
101110
{isClosable && (
102111
<Button
103112
kind="ghost"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import NavTabList, { type NavTabItem } from './NavTabList';
5+
6+
// Helper to build tabs
7+
function makeTabs(count = 3): NavTabItem[] {
8+
return Array.from({ length: count }, (_, i) => ({
9+
key: `TAB_${i + 1}`,
10+
title: `Tab ${i + 1}`,
11+
isClosable: false,
12+
}));
13+
}
14+
15+
// JSDOM doesn't implement scrollIntoView; stub to avoid errors triggered by effect
16+
window.HTMLElement.prototype.scrollIntoView = jest.fn();
17+
18+
describe('NavTabList renderAfterTabContent', () => {
19+
it('renders content after tab title when renderAfterTabContent provided', async () => {
20+
const tabs = makeTabs(3);
21+
const user = userEvent.setup();
22+
const onSelect = jest.fn();
23+
24+
render(
25+
<NavTabList
26+
activeKey={tabs[0].key}
27+
tabs={tabs}
28+
onSelect={onSelect}
29+
renderAfterTabContent={tab => <span>{`${tab.title}-slot`}</span>}
30+
/>
31+
);
32+
33+
// Assert each tab's content is rendered
34+
tabs.forEach(tab => {
35+
expect(screen.getByText(`${tab.title}-slot`)).toBeInTheDocument();
36+
});
37+
38+
// Selecting a tab still works with content present
39+
await user.click(screen.getByText('Tab 2'));
40+
expect(onSelect).toHaveBeenCalledWith('TAB_2');
41+
});
42+
});

packages/components/src/navigation/NavTabList.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ type NavTabListProps<T extends NavTabItem = NavTabItem> = {
106106
* @returns Additional context items for the tab
107107
*/
108108
makeContextActions?: (tab: T) => ContextAction | ContextAction[];
109+
110+
/**
111+
* Optional render function to render content after each tab's title.
112+
* Should be wrapped in useCallback to avoid unnecessary re-renders.
113+
*
114+
* @param tab The tab to render content for
115+
* @returns The content to render after the tab title
116+
*/
117+
renderAfterTabContent?: (tab: T) => React.ReactNode;
109118
};
110119

111120
function isScrolledLeft(element: HTMLElement): boolean {
@@ -181,6 +190,7 @@ function NavTabList({
181190
onReorder,
182191
onClose,
183192
makeContextActions,
193+
renderAfterTabContent,
184194
}: NavTabListProps): React.ReactElement {
185195
const containerRef = useRef<HTMLDivElement>();
186196
const [isOverflowing, setIsOverflowing] = useState(true);
@@ -431,6 +441,7 @@ function NavTabList({
431441
onClose={onClose}
432442
isDraggable={onReorder != null}
433443
contextActions={tabContextActionMap.get(key)}
444+
renderAfterTabContent={renderAfterTabContent}
434445
/>
435446
);
436447
});

0 commit comments

Comments
 (0)