Skip to content
This repository was archived by the owner on Aug 5, 2026. It is now read-only.

Commit fee8203

Browse files
authored
Merge branch 'main' into CAPY-2410/bpk-sheet-drawer
2 parents cd9e312 + c39a64a commit fee8203

3 files changed

Lines changed: 110 additions & 1 deletion

File tree

packages/backpack-web/src/bpk-component-card-list/src/BpkCardList.stories.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,32 @@ const GridToStackExample = () => (
286286
</PageContainer>
287287
);
288288

289+
const RowToRailShortFinalPageExample = () => (
290+
<PageContainer>
291+
<BpkCardList
292+
{...commonProps}
293+
cardList={makeList(DestinationCard, 3)}
294+
initiallyShownCardsDesktop={2}
295+
layoutDesktop={LAYOUTS.row}
296+
layoutMobile={LAYOUTS.rail}
297+
accessoryDesktop={ACCESSORY_DESKTOP_TYPES.pagination}
298+
/>
299+
</PageContainer>
300+
);
301+
302+
const RowToRailShortFinalPageWiderExample = () => (
303+
<PageContainer>
304+
<BpkCardList
305+
{...commonProps}
306+
cardList={makeList(DestinationCard, 10)}
307+
initiallyShownCardsDesktop={4}
308+
layoutDesktop={LAYOUTS.row}
309+
layoutMobile={LAYOUTS.rail}
310+
accessoryDesktop={ACCESSORY_DESKTOP_TYPES.pagination}
311+
/>
312+
</PageContainer>
313+
);
314+
289315
const RowToStackWithExpandExample = () => {
290316
const [expandText, setExpandText] = useState('Show more');
291317

@@ -433,6 +459,8 @@ export const RowToStackWithExpand = { render: () => <RowToStackWithExpandExample
433459
export const GridToStackWithExpand = { render: () => <GridToStackWithExpandExample /> };
434460
export const RowToRailForSnippets = { render: () => <RowToRailForSnippetsExample /> };
435461
export const RowToRailWithoutTitle = { render: () => <RowToRailWithoutTitleExample /> };
462+
export const RowToRailShortFinalPage = { render: () => <RowToRailShortFinalPageExample /> };
463+
export const RowToRailShortFinalPageWider = { render: () => <RowToRailShortFinalPageWiderExample /> };
436464

437465
export const MultiComponentsScrollingTest = { render: () => <MultiComponentsScrollingTestExample /> };
438466
export const VisualTest = { render: () => <BasicExample /> };

packages/backpack-web/src/bpk-component-card-list/src/BpkCardListRowRail/utils-test.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,78 @@ describe('usePageScrollSync', () => {
584584
expect(mockSetCurrentIndex).not.toHaveBeenCalled();
585585
});
586586

587+
it('should snap to the last page when the final (short) page cannot scroll its first card to the start', () => {
588+
// 3 cards, 2 shown per page => page 0 = [0,1], page 1 = [2].
589+
// The final page holds fewer cards than initiallyShownCards, so scrolling to
590+
// the end leaves card 2 alongside card 1 (firstVisibleIndex stays at 1).
591+
// Because the last card is visible, we should still land on page 1.
592+
const shortCardRefs = { current: [] as Array<HTMLDivElement | null> };
593+
Array.from({ length: 3 }).forEach((_, index) => {
594+
const { mockDiv } = makeMockDiv(index);
595+
mockDiv.scrollIntoView = jest.fn();
596+
shortCardRefs.current.push(mockDiv);
597+
});
598+
599+
const { rerender } = renderHook(
600+
({ visibilityList }) =>
601+
usePageScrollSync({
602+
cardRefs: shortCardRefs,
603+
container: mockContainer,
604+
currentIndex: 0,
605+
enabled: true,
606+
initiallyShownCards: 2,
607+
setCurrentIndex: mockSetCurrentIndex,
608+
visibilityList,
609+
}),
610+
{
611+
initialProps: {
612+
visibilityList: [1, 1, 0],
613+
},
614+
},
615+
);
616+
617+
// User initiates scroll via wheel
618+
act(() => {
619+
mockContainer.dispatchEvent(new Event('wheel'));
620+
});
621+
622+
// Scrolled to the end: cards 1 and 2 visible, card 0 out of view
623+
rerender({ visibilityList: [0, 1, 1] });
624+
625+
expect(mockSetCurrentIndex).toHaveBeenCalledWith(1);
626+
});
627+
628+
it('should NOT snap to the last page when all cards are visible in a wide viewport', () => {
629+
// 4 cards, 2 per page => page 0 = [0,1], page 1 = [2,3].
630+
// The final page is NOT short (4 % 2 === 0), so all cards fitting in the
631+
// viewport at once should not falsely advance the indicator to page 1.
632+
const { rerender } = renderHook(
633+
({ visibilityList }) =>
634+
usePageScrollSync({
635+
cardRefs: mockCardRefs,
636+
container: mockContainer,
637+
currentIndex: 0,
638+
enabled: true,
639+
initiallyShownCards: 2,
640+
setCurrentIndex: mockSetCurrentIndex,
641+
visibilityList,
642+
}),
643+
{
644+
initialProps: {
645+
visibilityList: [1, 1, 1, 1],
646+
},
647+
},
648+
);
649+
650+
act(() => {
651+
mockContainer.dispatchEvent(new Event('wheel'));
652+
});
653+
654+
rerender({ visibilityList: [1, 1, 1, 1] });
655+
656+
expect(mockSetCurrentIndex).not.toHaveBeenCalled();
657+
});
658+
587659
it('should handle partial visibility at page boundaries by using first visible card', () => {
588660
// When scrolling between pages, the first visible index determines the page.
589661
// With firstVisibleIndex=2 and initiallyShownCards=3: Math.floor(2/3) = 0

packages/backpack-web/src/bpk-component-card-list/src/BpkCardListRowRail/utils.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,16 @@ export const usePageScrollSync = ({
209209
const firstVisibleIndex = visibilityList.indexOf(1);
210210
if (firstVisibleIndex === -1) return;
211211

212-
const newPageIndex = Math.floor(firstVisibleIndex / initiallyShownCards);
212+
// A short final page can't scroll its first card to the viewport start, so
213+
// firstVisibleIndex stays on the previous page. Only treat a visible last
214+
// card as the final page when the final page is genuinely short; otherwise
215+
// (e.g. wide viewport where all cards fit) use the normal calculation to
216+
// avoid falsely snapping to the last page.
217+
const lastPageIsShort = visibilityList.length % initiallyShownCards !== 0;
218+
const newPageIndex =
219+
lastPageIsShort && visibilityList[visibilityList.length - 1] === 1
220+
? Math.ceil(visibilityList.length / initiallyShownCards) - 1
221+
: Math.floor(firstVisibleIndex / initiallyShownCards);
213222
if (newPageIndex !== currentIndex) {
214223
setCurrentIndex(newPageIndex);
215224
lastCurrentIndexRef.current = newPageIndex;

0 commit comments

Comments
 (0)