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/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..6e8454ae --- /dev/null +++ b/tests/dark.test.js @@ -0,0 +1,101 @@ +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, + 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)'); + }); + + 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