Skip to content

Commit 253316e

Browse files
authored
fix: keep TabList next button visible for partial tabs (#21996)
* fix: keep TabList next button visible for partial tabs * test: update avt test
1 parent 80d7924 commit 253316e

3 files changed

Lines changed: 69 additions & 15 deletions

File tree

e2e/components/Tabs/Tabs-test.avt.e2e.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,16 +254,13 @@ test.describe('@avt Tabs', () => {
254254
state: 'visible',
255255
});
256256

257-
// Right scroll button should disappear after manually scrolling horizontally to end of tabs
257+
// Right scroll button should remain visible when the last tab is only
258+
// partially visible.
258259
const tabList = page.locator('.cds--tab--list');
259260
const lastElement = page.getByText('Settings').nth(1);
260261
const nextButton = page.getByLabel('Scroll right');
261262
await tabList.hover();
262263
await lastElement.scrollIntoViewIfNeeded();
263-
await page.waitForSelector('.cds--tab--overflow-nav-button--next', {
264-
state: 'hidden',
265-
timeout: 1000,
266-
});
267-
await expect(nextButton).toBeHidden();
264+
await expect(nextButton).toBeVisible();
268265
});
269266
});

packages/react/src/components/Tabs/Tabs.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,7 @@ function TabList({
510510
// AND SCROLL_LEFT + CLIENT_WIDTH < SCROLL_WIDTH
511511
const [isNextButtonVisible, setIsNextButtonVisible] = useState(
512512
ref.current
513-
? scrollLeft + buttonWidth + ref.current.clientWidth <
514-
ref.current.scrollWidth
513+
? scrollLeft + ref.current.clientWidth < ref.current.scrollWidth
515514
: false
516515
);
517516

@@ -632,8 +631,7 @@ function TabList({
632631
// adding 1 in calculation for firefox support
633632
setIsNextButtonVisible(
634633
ref.current
635-
? scrollLeft + buttonWidth + ref.current.clientWidth + 1 <
636-
ref.current.scrollWidth
634+
? scrollLeft + ref.current.clientWidth + 1 < ref.current.scrollWidth
637635
: false
638636
);
639637

packages/react/src/components/Tabs/__tests__/Tabs-test.js

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,52 @@ describe('Tab', () => {
607607
expect(onTabCloseRequest).toHaveBeenCalledTimes(1);
608608
});
609609

610-
it('should hide next overflow button when only 1px remains in the overflow threshold', async () => {
610+
it('should keep next overflow button visible when the last tab is partially visible', () => {
611+
jest.useFakeTimers();
612+
const clientWidthSpy = jest
613+
.spyOn(HTMLElement.prototype, 'clientWidth', 'get')
614+
.mockImplementation(function () {
615+
return this.getAttribute?.('role') === 'tablist' ? 100 : 0;
616+
});
617+
const scrollWidthSpy = jest
618+
.spyOn(HTMLElement.prototype, 'scrollWidth', 'get')
619+
.mockImplementation(function () {
620+
return this.getAttribute?.('role') === 'tablist' ? 200 : 0;
621+
});
622+
623+
try {
624+
render(
625+
<Tabs>
626+
<TabList aria-label="List of tabs" />
627+
</Tabs>
628+
);
629+
630+
const tablist = screen.getByRole('tablist');
631+
Object.defineProperty(tablist, 'scrollLeft', {
632+
configurable: true,
633+
writable: true,
634+
value: 55,
635+
});
636+
637+
fireEvent.scroll(tablist);
638+
act(() => {
639+
jest.advanceTimersByTime(250);
640+
});
641+
642+
expect(screen.getByLabelText('Scroll right')).toHaveClass(
643+
`${prefix}--tab--overflow-nav-button`,
644+
`${prefix}--tab--overflow-nav-button--next`,
645+
{ exact: true }
646+
);
647+
} finally {
648+
clientWidthSpy.mockRestore();
649+
scrollWidthSpy.mockRestore();
650+
jest.useRealTimers();
651+
}
652+
});
653+
654+
it('should hide next overflow button when only 1px remains in the overflow threshold', () => {
655+
jest.useFakeTimers();
611656
const clientWidthSpy = jest
612657
.spyOn(HTMLElement.prototype, 'clientWidth', 'get')
613658
.mockImplementation(function () {
@@ -626,14 +671,28 @@ describe('Tab', () => {
626671
</Tabs>
627672
);
628673

629-
await waitFor(() => {
630-
expect(screen.getByLabelText('Scroll right')).toHaveClass(
631-
`${prefix}--tab--overflow-nav-button--hidden`
632-
);
674+
const tablist = screen.getByRole('tablist');
675+
Object.defineProperty(tablist, 'scrollLeft', {
676+
configurable: true,
677+
writable: true,
678+
value: 44,
679+
});
680+
681+
fireEvent.scroll(tablist);
682+
act(() => {
683+
jest.advanceTimersByTime(250);
633684
});
685+
686+
expect(screen.getByLabelText('Scroll right')).toHaveClass(
687+
`${prefix}--tab--overflow-nav-button`,
688+
`${prefix}--tab--overflow-nav-button--next`,
689+
`${prefix}--tab--overflow-nav-button--hidden`,
690+
{ exact: true }
691+
);
634692
} finally {
635693
clientWidthSpy.mockRestore();
636694
scrollWidthSpy.mockRestore();
695+
jest.useRealTimers();
637696
}
638697
});
639698

0 commit comments

Comments
 (0)