From c5222c3da9bca2ea51ecb734aef5fff6bc3eafde Mon Sep 17 00:00:00 2001 From: afc163 Date: Fri, 20 Jun 2025 11:12:31 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(ScrollBar):=20=E2=9C=A8=20support=20da?= =?UTF-8?q?rk=20mode=20by=20js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ScrollBar.tsx | 21 +++++++++++++++++++-- tests/dark.test.js | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/dark.test.js diff --git a/src/ScrollBar.tsx b/src/ScrollBar.tsx index 15a9db25..832eadd1 100644 --- a/src/ScrollBar.tsx +++ b/src/ScrollBar.tsx @@ -45,6 +45,7 @@ const ScrollBar = React.forwardRef((props, ref) => const [dragging, setDragging] = React.useState(false); const [pageXY, setPageXY] = React.useState(null); const [startTop, setStartTop] = React.useState(null); + const [dark, setDark] = React.useState(false); const isLTR = !rtl; @@ -187,6 +188,21 @@ const ScrollBar = React.forwardRef((props, ref) => } }, [dragging]); + React.useEffect(() => { + const media = window.matchMedia?.('(prefers-color-scheme: dark)'); + setDark(media.matches); + + const listener = (e: MediaQueryListEvent) => { + setDark(e.matches); + }; + + media?.addEventListener('change', listener); + + return () => { + media?.removeEventListener('change', listener); + }; + }, []); + React.useEffect(() => { delayHidden(); return () => { @@ -209,10 +225,11 @@ const ScrollBar = React.forwardRef((props, ref) => const thumbStyle: React.CSSProperties = { position: 'absolute', - background: 'rgba(0, 0, 0, 0.5)', + background: dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)', borderRadius: 99, cursor: 'pointer', userSelect: 'none', + ...propsThumbStyle, }; if (horizontal) { @@ -266,7 +283,7 @@ const ScrollBar = React.forwardRef((props, ref) => className={classNames(`${scrollbarPrefixCls}-thumb`, { [`${scrollbarPrefixCls}-thumb-moving`]: dragging, })} - style={{ ...thumbStyle, ...propsThumbStyle }} + style={{ ...thumbStyle }} onMouseDown={onThumbMouseDown} /> diff --git a/tests/dark.test.js b/tests/dark.test.js new file mode 100644 index 00000000..820651fb --- /dev/null +++ b/tests/dark.test.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import List from '../src'; + +describe('List.dark', () => { + const mockMatchMedia = (matches) => { + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + }; + + it('should render dark scrollbar', () => { + mockMatchMedia(true); + + const wrapper = mount( + item}> + {(item) =>
{item}
} +
+ ); + + const thumb = wrapper.find('.rc-virtual-list-scrollbar-thumb'); + expect(thumb.props().style.background).toBe('rgba(255, 255, 255, 0.5)'); + }); + + it('should render light scrollbar', () => { + mockMatchMedia(false); + + const wrapper = mount( + item}> + {(item) =>
{item}
} +
+ ); + + const thumb = wrapper.find('.rc-virtual-list-scrollbar-thumb'); + expect(thumb.props().style.background).toBe('rgba(0, 0, 0, 0.5)'); + }); +}); \ No newline at end of file From 679d1f32c02518180647bf4c46db8b04f78a5a60 Mon Sep 17 00:00:00 2001 From: afc163 Date: Fri, 20 Jun 2025 11:35:44 +0800 Subject: [PATCH 2/2] fix test case --- jest.config.js | 4 ++++ tests/dark.test.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ tests/setup.js | 13 +++++++++++ 3 files changed, 71 insertions(+) create mode 100644 jest.config.js create mode 100644 tests/setup.js diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..0c6601ae --- /dev/null +++ b/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + setupFiles: ['./tests/setup.js'], + snapshotSerializers: [require.resolve('enzyme-to-json/serializer')], +}; \ No newline at end of file diff --git a/tests/dark.test.js b/tests/dark.test.js index 820651fb..6e8454ae 100644 --- a/tests/dark.test.js +++ b/tests/dark.test.js @@ -1,8 +1,15 @@ import React from 'react'; import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; import List from '../src'; describe('List.dark', () => { + const originalMatchMedia = window.matchMedia; + + afterEach(() => { + window.matchMedia = originalMatchMedia; + }); + const mockMatchMedia = (matches) => { Object.defineProperty(window, 'matchMedia', { writable: true, @@ -44,4 +51,51 @@ describe('List.dark', () => { const thumb = wrapper.find('.rc-virtual-list-scrollbar-thumb'); expect(thumb.props().style.background).toBe('rgba(0, 0, 0, 0.5)'); }); + + it('should update on theme change', () => { + let listener; + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(() => ({ + matches: false, + addEventListener: (type, cb) => { + if (type === 'change') { + listener = cb; + } + }, + removeEventListener: () => {}, + })), + }); + + const wrapper = mount( + item}> + {(item) =>
{item}
} +
+ ); + + // Initial: light + expect(wrapper.find('.rc-virtual-list-scrollbar-thumb').props().style.background).toBe( + 'rgba(0, 0, 0, 0.5)', + ); + + // Change to dark + act(() => { + listener({ matches: true }); + }); + wrapper.update(); + + expect(wrapper.find('.rc-virtual-list-scrollbar-thumb').props().style.background).toBe( + 'rgba(255, 255, 255, 0.5)', + ); + + // Change back to light + act(() => { + listener({ matches: false }); + }); + wrapper.update(); + + expect(wrapper.find('.rc-virtual-list-scrollbar-thumb').props().style.background).toBe( + 'rgba(0, 0, 0, 0.5)', + ); + }); }); \ No newline at end of file diff --git a/tests/setup.js b/tests/setup.js new file mode 100644 index 00000000..87237082 --- /dev/null +++ b/tests/setup.js @@ -0,0 +1,13 @@ +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); \ No newline at end of file