diff --git a/packages/web-react/src/components/Dialog/useDialog.ts b/packages/web-react/src/components/Dialog/useDialog.ts index 9cd4057361..654155c326 100644 --- a/packages/web-react/src/components/Dialog/useDialog.ts +++ b/packages/web-react/src/components/Dialog/useDialog.ts @@ -1,4 +1,5 @@ import { useEffect, MutableRefObject } from 'react'; +import { useScrollControl } from '../../hooks'; export const useDialog = (ref: MutableRefObject, isOpen: boolean) => { useEffect(() => { @@ -13,6 +14,8 @@ export const useDialog = (ref: MutableRefObject, isOpe } }, [isOpen, ref]); + useScrollControl(ref, isOpen); + const openDialog = () => { if (ref?.current) { ref.current.showModal(); diff --git a/packages/web-react/src/hooks/__tests__/useScrollControl.test.ts b/packages/web-react/src/hooks/__tests__/useScrollControl.test.ts new file mode 100644 index 0000000000..ed3fe7bb64 --- /dev/null +++ b/packages/web-react/src/hooks/__tests__/useScrollControl.test.ts @@ -0,0 +1,38 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { MutableRefObject } from 'react'; +import { useScrollControl } from '../useScrollControl'; + +describe('useScrollControl', () => { + const createMockRef = (isOpen: boolean) => ({ + current: { + open: isOpen, + }, + }); + + it('should disable scroll when isOpen is true', () => { + const mockRef = createMockRef(true) as MutableRefObject; + + act(() => { + renderHook(() => useScrollControl(mockRef, true)); + }); + + expect(document.body.classList.contains('is-scrolling-disabled')).toBe(true); + expect(document.body.style.top).toMatch(/-\d+px/); + expect(document.body.style.paddingRight).toMatch(/\d+px/); + }); + + it('should enable scroll when isOpen is false', () => { + const mockRef = createMockRef(false) as MutableRefObject; + + act(() => { + renderHook(() => useScrollControl(mockRef, true)); + }); + + act(() => { + renderHook(() => useScrollControl(mockRef, false)); + }); + + expect(document.body.classList.contains('is-scrolling-disabled')).toBe(false); + expect(document.body.style.top).toBe(''); + }); +}); diff --git a/packages/web-react/src/hooks/index.ts b/packages/web-react/src/hooks/index.ts index 093a19423a..545d9435c4 100644 --- a/packages/web-react/src/hooks/index.ts +++ b/packages/web-react/src/hooks/index.ts @@ -7,4 +7,5 @@ export * from './useDragAndDrop'; export * from './useIcon'; export * from './useIsomorphicLayoutEffect'; export * from './useLastActiveFocus'; +export * from './useScrollControl'; export * from './useToggle'; diff --git a/packages/web-react/src/hooks/useScrollControl.ts b/packages/web-react/src/hooks/useScrollControl.ts new file mode 100644 index 0000000000..69b45d7fc0 --- /dev/null +++ b/packages/web-react/src/hooks/useScrollControl.ts @@ -0,0 +1,32 @@ +import { useEffect, MutableRefObject } from 'react'; + +const CLASSNAME_SCROLLING_DISABLED = 'is-scrolling-disabled'; + +const disableScroll = () => { + const { body } = document; + const scrollBarWidth = window.innerWidth - body.clientWidth; + const offsetY = window.scrollY; + + body.style.paddingRight = `${scrollBarWidth}px`; + body.style.top = `-${offsetY}px`; + body.classList.add(CLASSNAME_SCROLLING_DISABLED); +}; + +const enableScroll = (offsetY: number) => { + const { body } = document; + body.style.paddingRight = ''; + body.style.top = ''; + body.classList.remove(CLASSNAME_SCROLLING_DISABLED); + window.scrollTo(0, -offsetY); +}; + +export const useScrollControl = (ref: MutableRefObject, isOpen: boolean) => { + useEffect(() => { + if (isOpen) { + disableScroll(); + } else if (ref.current && !ref.current.open) { + const offsetY = parseFloat(document.body.style.top || '0'); + enableScroll(offsetY); + } + }, [isOpen, ref]); +};