From a49bd8248831434c0cedf238758aad80d0a268f1 Mon Sep 17 00:00:00 2001 From: Roman Barlos Date: Tue, 1 Jul 2025 16:59:26 +0300 Subject: [PATCH 1/2] feat(Drawer): add scrollLock props --- src/components/Drawer/Drawer.tsx | 42 +++++++++++----- .../Drawer/__stories__/Drawer.stories.tsx | 4 ++ .../Drawer/__stories__/ScrollLock.scss | 42 ++++++++++++++++ .../Drawer/__stories__/ScrollLock.tsx | 48 +++++++++++++++++++ src/components/Drawer/utils.ts | 15 +++++- 5 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 src/components/Drawer/__stories__/ScrollLock.scss create mode 100644 src/components/Drawer/__stories__/ScrollLock.tsx diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx index 58205177..e0a2d7fa 100644 --- a/src/components/Drawer/Drawer.tsx +++ b/src/components/Drawer/Drawer.tsx @@ -12,6 +12,7 @@ import { type OnResizeContinueHandler, type OnResizeHandler, useResizableDrawerItem, + useScrollLock, } from './utils'; import './Drawer.scss'; @@ -199,6 +200,13 @@ export interface DrawerProps { * @default false * */ keepMounted?: boolean; + + /** + * Whether to lock page scroll when drawer is open. + * Applied only when hideVeil=true and disablePortal=false. + * @default false + */ + scrollLock?: boolean; } export const Drawer: React.FC = ({ @@ -211,6 +219,7 @@ export const Drawer: React.FC = ({ hideVeil, disablePortal = true, keepMounted = false, + scrollLock = false, }) => { let someItemVisible = false; React.Children.forEach(children, (child) => { @@ -239,7 +248,10 @@ export const Drawer: React.FC = ({ const containerRef = React.useRef(null); const veilRef = React.useRef(null); - const drawer = ( + const shouldApplyScrollLock = scrollLock && someItemVisible && !disablePortal; + useScrollLock(shouldApplyScrollLock); + + return ( = ({ > {(state) => { const childrenVisible = someItemVisible && state === 'entered'; - return ( + + const content = (
= ({ })}
); - }} -
- ); - if (disablePortal) { - return drawer; - } + if (disablePortal) { + return content; + } - return ( - - {someItemVisible ? {drawer} : drawer} - + // When hideVeil=true, we don't use FloatingOverlay to avoid blocking mouse events + if (hideVeil) { + return {content}; + } + + return ( + + {content} + + ); + }} + ); }; diff --git a/src/components/Drawer/__stories__/Drawer.stories.tsx b/src/components/Drawer/__stories__/Drawer.stories.tsx index c4f3fdd5..072f4d80 100644 --- a/src/components/Drawer/__stories__/Drawer.stories.tsx +++ b/src/components/Drawer/__stories__/Drawer.stories.tsx @@ -6,6 +6,7 @@ import {DisablePortalShowcase} from './DisablePortal'; import {DrawerShowcase} from './DrawerShowcase'; import {HideVeilShowcase} from './HideVeil'; import {ResizableItemShowcase} from './ResizableItem'; +import {ScrollLockShowcase} from './ScrollLock'; import {UsePortalShowcase} from './UsePortal'; export default { @@ -25,5 +26,8 @@ export const DisablePortal = DisablePortalTemplate.bind({}); const HideVeilTemplate: StoryFn = () => ; export const HideVeil = HideVeilTemplate.bind({}); +const ScrollLockTemplate: StoryFn = () => ; +export const ScrollLock = ScrollLockTemplate.bind({}); + const UsePortalTemplate: StoryFn = () => ; export const UsePortal = UsePortalTemplate.bind({}); diff --git a/src/components/Drawer/__stories__/ScrollLock.scss b/src/components/Drawer/__stories__/ScrollLock.scss new file mode 100644 index 00000000..ad0a7a3a --- /dev/null +++ b/src/components/Drawer/__stories__/ScrollLock.scss @@ -0,0 +1,42 @@ +.scroll-lock { + --gn-drawer-item-position: absolute; + + width: 100dvw; + height: 100dvh; + + display: flex; + flex-direction: column; + + &__header { + padding: 20px; + border-bottom: 1px solid var(--g-color-line-generic); + display: flex; + align-items: center; + gap: 8px; + } + + &__container { + position: relative; + flex-grow: 1; + padding: 16px; + } + + &__drawer { + position: absolute; + inset: 0; + overflow: hidden; + } + + &__item { + width: 400px; + height: 100%; + overflow-x: hidden; + } + + &__item-content { + box-sizing: border-box; + padding: 8px 16px; + height: 100%; + overflow-y: scroll; + } +} diff --git a/src/components/Drawer/__stories__/ScrollLock.tsx b/src/components/Drawer/__stories__/ScrollLock.tsx new file mode 100644 index 00000000..38e7a8da --- /dev/null +++ b/src/components/Drawer/__stories__/ScrollLock.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import {Button, Checkbox} from '@gravity-ui/uikit'; + +import {cn} from '../../utils/cn'; +import {Drawer, DrawerItem} from '../Drawer'; + +import {PlaceholderText} from './moc'; + +import './ScrollLock.scss'; + +const b = cn('scroll-lock'); + +export function ScrollLockShowcase() { + const [visible, setVisible] = React.useState(true); + const [scrollLock, setScrollLock] = React.useState(true); + + return ( +
+
+ + +
+
+ {Array.from({length: 50}, (_, index) => ( +

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro, quos! +

+ ))} + setVisible(false)} + > + +
+ +
+
+
+
+
+ ); +} diff --git a/src/components/Drawer/utils.ts b/src/components/Drawer/utils.ts index 40624bf7..62d9be1f 100644 --- a/src/components/Drawer/utils.ts +++ b/src/components/Drawer/utils.ts @@ -21,6 +21,19 @@ function getEventClientPosition( return direction === 'horizontal' ? e.clientX : e.clientY; } +export function useScrollLock(enabled: boolean) { + React.useEffect(() => { + if (!enabled) return; + + const originalStyle = window.getComputedStyle(document.body).overflow; + document.body.style.overflow = 'hidden'; + + return () => { + document.body.style.overflow = originalStyle; + }; + }, [enabled]); +} + export interface UseResizeHandlersParams { onStart: () => void; onMove: (delta: number) => void; @@ -99,7 +112,7 @@ export function useResizeHandlers({ onStart(); }, - [handleEnd, handleMove, onStart, direction], + [handleEnd, handleMove, onStart, direction, disableSelect], ); return { From a3130ce0152558461962d5c96c4d70aad236e108 Mon Sep 17 00:00:00 2001 From: Roman Barlos Date: Wed, 2 Jul 2025 12:10:51 +0300 Subject: [PATCH 2/2] fix: fix shouldApplyScrollLock --- src/components/Drawer/Drawer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx index e0a2d7fa..12927799 100644 --- a/src/components/Drawer/Drawer.tsx +++ b/src/components/Drawer/Drawer.tsx @@ -248,7 +248,7 @@ export const Drawer: React.FC = ({ const containerRef = React.useRef(null); const veilRef = React.useRef(null); - const shouldApplyScrollLock = scrollLock && someItemVisible && !disablePortal; + const shouldApplyScrollLock = scrollLock && someItemVisible && hideVeil && !disablePortal; useScrollLock(shouldApplyScrollLock); return (