Skip to content

Commit 0c4bda3

Browse files
zombieJclaude
andcommitted
fix: reset scroll offset when stack collapses
- Add expanded parameter to useListScroll hook - Reset scroll offset to 0 when stack collapses after hover leave - Add test for scroll reset behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5f52d3c commit 0c4bda3

File tree

3 files changed

+59
-1
lines changed

3 files changed

+59
-1
lines changed

src/NotificationList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const NotificationList: React.FC<NotificationListProps> = (props) => {
9191
const [gap, setGap] = React.useState(0);
9292
const [notificationPosition, setNodeSize] = useListPosition(configList, stackPosition, gap);
9393
const { contentRef, onTouchEnd, onTouchMove, onTouchStart, onWheel, scrollOffset, viewportRef } =
94-
useListScroll(keyList, notificationPosition);
94+
useListScroll(keyList, notificationPosition, expanded);
9595

9696
React.useEffect(() => {
9797
const listNode = contentRef.current;

src/hooks/useListScroll.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function getMaxScroll(viewportNode: HTMLDivElement | null, contentNode: HTMLDivE
2727
export default function useListScroll(
2828
keyList: string[],
2929
notificationPosition: Map<string, NodePosition>,
30+
expanded = false,
3031
) {
3132
const viewportRef = React.useRef<HTMLDivElement>(null);
3233
const contentRef = React.useRef<HTMLDivElement>(null);
@@ -92,6 +93,14 @@ export default function useListScroll(
9293
};
9394
}, [syncScrollOffset]);
9495

96+
React.useEffect(() => {
97+
if (!expanded) {
98+
touchStartYRef.current = null;
99+
touchStartOffsetRef.current = 0;
100+
syncScrollOffset(0);
101+
}
102+
}, [expanded, syncScrollOffset]);
103+
95104
const onWheel = React.useCallback(
96105
(event: React.WheelEvent<HTMLDivElement>) => {
97106
const maxScroll = getMaxScroll(viewportRef.current, contentRef.current);

tests/stack.test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,53 @@ describe('stack', () => {
279279
clientHeightSpy.mockRestore();
280280
scrollHeightSpy.mockRestore();
281281
});
282+
283+
it('resets scroll offset when stack collapses after hover leave', () => {
284+
const clientHeightSpy = vi
285+
.spyOn(HTMLElement.prototype, 'clientHeight', 'get')
286+
.mockImplementation(function mockClientHeight() {
287+
if (this.classList?.contains('rc-notification-list')) {
288+
return 120;
289+
}
290+
291+
return 0;
292+
});
293+
const scrollHeightSpy = vi
294+
.spyOn(HTMLElement.prototype, 'scrollHeight', 'get')
295+
.mockImplementation(function mockScrollHeight() {
296+
if (this.classList?.contains('rc-notification-list-content')) {
297+
return 300;
298+
}
299+
300+
return 0;
301+
});
302+
303+
render(
304+
<NotificationList
305+
placement="topRight"
306+
stack={{ threshold: 3 }}
307+
configList={Array.from({ length: 5 }, (_, index) => ({
308+
key: index,
309+
description: `Notice ${index}`,
310+
duration: false,
311+
}))}
312+
/>,
313+
);
314+
315+
const list = document.querySelector<HTMLElement>('.rc-notification-list');
316+
const content = document.querySelector<HTMLElement>('.rc-notification-list-content');
317+
318+
fireEvent.mouseEnter(list!);
319+
fireEvent.wheel(list!, { deltaY: 60 });
320+
321+
expect(content?.style.transform).toBe('translate3d(0, -60px, 0)');
322+
323+
fireEvent.mouseLeave(list!);
324+
325+
expect(list).not.toHaveClass('rc-notification-stack-expanded');
326+
expect(content?.style.transform).toBe('translate3d(0, 0px, 0)');
327+
328+
clientHeightSpy.mockRestore();
329+
scrollHeightSpy.mockRestore();
330+
});
282331
});

0 commit comments

Comments
 (0)