@gravity-ui/navigation 3.3.9 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,6 +49,8 @@ export interface DrawerItemProps {
49
49
  * @default false
50
50
  * */
51
51
  keepMounted?: boolean;
52
+ /** Optional inline styles to be applied to the DrawerItem component. */
53
+ style?: React.CSSProperties;
52
54
  }
53
55
  export declare const DrawerItem: React.ForwardRefExoticComponent<DrawerItemProps & React.RefAttributes<HTMLDivElement>>;
54
56
  type DrawerChild = React.ReactElement<DrawerItemProps>;
@@ -63,17 +65,23 @@ export interface DrawerProps {
63
65
  veilClassName?: string;
64
66
  /** Optional callback function that is called when the veil (overlay) is clicked. */
65
67
  onVeilClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
66
- /** Optional callback function that is called when the escape key is pressed, if the drawer is open. */
68
+ /** Optional callback function that is called when the escape key is pressed if the drawer is open. */
67
69
  onEscape?: (event: KeyboardEvent) => void;
68
70
  /** Optional flag to hide the background darkening */
69
71
  hideVeil?: boolean;
70
- /** Optional flag to not use `Portal` for drawer */
72
+ /** Optional flag to doesn't use `Portal` for drawer */
71
73
  disablePortal?: boolean;
72
74
  /**
73
75
  * Keep child components mounted when closed
74
76
  * @default false
75
77
  * */
76
78
  keepMounted?: boolean;
79
+ /**
80
+ * Whether to lock page scroll when drawer is open.
81
+ * Applied only when hideVeil=true and disablePortal=false.
82
+ * @default false
83
+ */
84
+ scrollLock?: boolean;
77
85
  }
78
86
  export declare const Drawer: React.FC<DrawerProps>;
79
87
  export {};
@@ -5,4 +5,5 @@ export declare const Showcase: import("@storybook/csf").AnnotatedStoryFn<import(
5
5
  export declare const ResizableItem: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, import("@storybook/csf").Args>;
6
6
  export declare const DisablePortal: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, import("@storybook/csf").Args>;
7
7
  export declare const HideVeil: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, import("@storybook/csf").Args>;
8
+ export declare const ScrollLock: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, import("@storybook/csf").Args>;
8
9
  export declare const UsePortal: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, import("@storybook/csf").Args>;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import './ScrollLock.scss';
3
+ export declare function ScrollLockShowcase(): React.JSX.Element;
@@ -5,6 +5,7 @@ export declare const DRAWER_ITEM_INITIAL_RESIZE_WIDTH = 400;
5
5
  export type DrawerDirection = 'right' | 'left' | 'top' | 'bottom';
6
6
  export type OnResizeHandler = (width: number, event: MouseEvent | TouchEvent) => void;
7
7
  export type OnResizeContinueHandler = (width: number) => void;
8
+ export declare function useScrollLock(enabled: boolean): void;
8
9
  export interface UseResizeHandlersParams {
9
10
  onStart: () => void;
10
11
  onMove: (delta: number) => void;
@@ -141,7 +141,7 @@ function styleInject(css, ref) {
141
141
  var css_248z$u = ".g-root{--gn-aside-top-panel-height:0px}.gn-aside-header{--gn-aside-header-min-width:56px;--_--item-icon-background-size:38px;--_--background-color:var(--g-color-base-background);--_--decoration-collapsed-background-color:var(--g-color-base-warning-light);--_--decoration-expanded-background-color:var(--g-color-base-warning-light);--_--vertical-divider-line-color:var(--g-color-line-generic);--_--horizontal-divider-line-color:var(--g-color-line-generic);background-color:var(--g-color-base-background);height:100%;position:relative;width:100%}.gn-aside-header__aside{background-color:var(--gn-aside-header-expanded-background-color,var(--gn-aside-header-background-color,var(--_--background-color)));box-sizing:border-box;display:flex;flex-direction:column;height:100vh;left:0;margin-top:var(--gn-top-alert-height,0);max-height:calc(100vh - var(--gn-top-alert-height, 0));position:sticky;top:var(--gn-top-alert-height,0);width:inherit;z-index:var(--gn-aside-header-z-index,100)}.gn-aside-header__aside:after{background-color:var(--gn-aside-header-divider-vertical-color,var(--_--vertical-divider-line-color));content:\"\";height:100%;position:absolute;right:0;top:0;width:1px;z-index:2}.gn-aside-header__aside-popup-anchor{inset:0;position:absolute;z-index:1}.gn-aside-header__aside-content{--gradient-height:334px;display:flex;flex-direction:column;height:inherit;overflow-x:hidden;padding-top:var(--gn-aside-header-padding-top);position:relative;user-select:none;width:inherit;z-index:2}.gn-aside-header__aside-content>.gn-aside-header-logo{margin:8px 0}.gn-aside-header__aside-content_with-decoration{background:linear-gradient(180deg,var(--gn-aside-header-decoration-expanded-background-color,var(--_--decoration-expanded-background-color)) calc(var(--gradient-height)*.33),transparent calc(var(--gradient-height)*.88))}.gn-aside-header__aside-custom-background{bottom:0;display:flex;position:absolute;top:0;width:var(--gn-aside-header-size);z-index:-1}.gn-aside-header_compact .gn-aside-header__aside{background-color:var(--gn-aside-header-collapsed-background-color,var(--gn-aside-header-background-color,var(--_--background-color)))}.gn-aside-header_compact .gn-aside-header__aside-content{background:transparent}.gn-aside-header__header{--gn-aside-header-header-divider-height:29px;box-sizing:border-box;flex:none;padding-bottom:22px;padding-top:8px;position:relative;width:100%;z-index:1}.gn-aside-header__header .gn-aside-header__header-divider{bottom:0;color:var(--gn-aside-header-decoration-collapsed-background-color,var(--_--decoration-collapsed-background-color));display:none;left:0;position:absolute;z-index:-2}.gn-aside-header__header_with-decoration:before{background-color:var(--gn-aside-header-decoration-collapsed-background-color,var(--_--decoration-collapsed-background-color));content:\"\";display:none;height:calc(100% - var(--gn-aside-header-header-divider-height));left:0;position:absolute;top:0;width:100%;z-index:-2}.gn-aside-header__header:after{background-color:var(--gn-aside-header-divider-horizontal-color,var(--_--horizontal-divider-line-color));bottom:12px;content:\"\";height:1px;left:0;position:absolute;width:100%;z-index:-2}.gn-aside-header_compact .gn-aside-header__header:before,.gn-aside-header_compact .gn-aside-header__header_with-decoration .gn-aside-header__header-divider{display:block}.gn-aside-header_compact .gn-aside-header__header_with-decoration:after{display:none}.gn-aside-header__logo-button .gn-aside-header__logo-icon-place{height:var(--gn-aside-header-item-icon-background-size,var(--_--item-icon-background-size));width:var(--gn-aside-header-min-width)}.gn-aside-header__menu-items{flex-grow:1}.gn-aside-header__footer{display:flex;flex-direction:column;flex-shrink:0;margin:8px 0;width:100%}.gn-aside-header__panels{inset:var(--gn-top-alert-height,0) 0 0;max-height:calc(100vh - var(--gn-top-alert-height, 0));overflow:auto;position:fixed;z-index:var(--gn-aside-header-panel-z-index,98)}.gn-aside-header__panel{height:100%}.gn-aside-header__pane-container{display:flex;flex-direction:row;outline:none;overflow:visible;user-select:text}.gn-aside-header__top-alert{background:var(--g-color-base-background);position:fixed;top:0;width:100%;z-index:var(--gn-aside-header-pane-top-z-index,98)}.gn-aside-header__content{margin-top:var(--gn-top-alert-height,0);width:calc(100% - var(--gn-aside-header-size));z-index:var(--gn-aside-header-content-z-index,95)}";
142
142
  styleInject(css_248z$u);
143
143
 
144
- const TopAlert$1 = React.lazy(() => Promise.resolve().then(function () { return require('./index-C751PFJy.js'); }).then((module) => ({ default: module.TopAlert })));
144
+ const TopAlert$1 = React.lazy(() => Promise.resolve().then(function () { return require('./index-ClUpN5oD.js'); }).then((module) => ({ default: module.TopAlert })));
145
145
  const Layout = ({ compact, className, children, topAlert }) => {
146
146
  const size = compact ? ASIDE_HEADER_COMPACT_WIDTH : ASIDE_HEADER_EXPANDED_WIDTH;
147
147
  const asideHeaderContextValue = React.useMemo(() => ({ size, compact }), [compact, size]);
@@ -4393,6 +4393,17 @@ function getEventClientPosition(e, direction) {
4393
4393
  }
4394
4394
  return direction === 'horizontal' ? e.clientX : e.clientY;
4395
4395
  }
4396
+ function useScrollLock(enabled) {
4397
+ React__namespace.useEffect(() => {
4398
+ if (!enabled)
4399
+ return;
4400
+ const originalStyle = window.getComputedStyle(document.body).overflow;
4401
+ document.body.style.overflow = 'hidden';
4402
+ return () => {
4403
+ document.body.style.overflow = originalStyle;
4404
+ };
4405
+ }, [enabled]);
4406
+ }
4396
4407
  function useResizeHandlers({ onStart, onMove, onEnd, direction = 'horizontal', }) {
4397
4408
  const initialPosition = React__namespace.useRef(0);
4398
4409
  const currentPosition = React__namespace.useRef(0);
@@ -4430,7 +4441,7 @@ function useResizeHandlers({ onStart, onMove, onEnd, direction = 'horizontal', }
4430
4441
  window.addEventListener('selectstart', disableSelect, { passive: false });
4431
4442
  document.body.style.setProperty('cursor', direction === 'horizontal' ? 'col-resize' : 'row-resize');
4432
4443
  onStart();
4433
- }, [handleEnd, handleMove, onStart, direction]);
4444
+ }, [handleEnd, handleMove, onStart, direction, disableSelect]);
4434
4445
  return {
4435
4446
  onMouseDown: handleStart,
4436
4447
  onTouchStart: handleStart,
@@ -4480,7 +4491,7 @@ styleInject(css_248z$l);
4480
4491
  const b$m = block('drawer');
4481
4492
  const TIMEOUT = 300;
4482
4493
  const DrawerItem = React.forwardRef(function DrawerItem(props, ref) {
4483
- const { visible, content, children, direction = 'left', className, resizable, width, minResizeWidth, maxResizeWidth, onResizeStart, onResizeContinue, onResize, keepMounted = false, } = props;
4494
+ const { visible, content, children, direction = 'left', className, resizable, width, minResizeWidth, maxResizeWidth, onResizeStart, onResizeContinue, onResize, keepMounted = false, style = {}, } = props;
4484
4495
  const [isInitialRender, setInitialRender] = React.useState(true);
4485
4496
  const itemRef = React.useRef(null);
4486
4497
  const handleRef = uikit.useForkRef(ref, itemRef);
@@ -4494,15 +4505,18 @@ const DrawerItem = React.forwardRef(function DrawerItem(props, ref) {
4494
4505
  onResize,
4495
4506
  onResizeContinue,
4496
4507
  });
4497
- const style = {};
4498
- if (resizable) {
4499
- if (['left', 'right'].includes(direction)) {
4500
- style.width = `${resizedWidth}px`;
4501
- }
4502
- else {
4503
- style.height = `${resizedWidth}px`;
4508
+ const innerStyle = React.useMemo(() => {
4509
+ const css = Object.assign({}, style);
4510
+ if (resizable) {
4511
+ if (['left', 'right'].includes(direction)) {
4512
+ css.width = `${resizedWidth}px`;
4513
+ }
4514
+ else {
4515
+ css.height = `${resizedWidth}px`;
4516
+ }
4504
4517
  }
4505
- }
4518
+ return css;
4519
+ }, [direction, resizable, resizedWidth, style]);
4506
4520
  React.useEffect(() => {
4507
4521
  setInitialRender(true);
4508
4522
  }, [direction]);
@@ -4513,10 +4527,10 @@ const DrawerItem = React.forwardRef(function DrawerItem(props, ref) {
4513
4527
  direction: cssDirection,
4514
4528
  hidden: isInitialRender && !visible,
4515
4529
  resize: isResizing,
4516
- }, [className]), style: style },
4530
+ }, [className]), style: innerStyle },
4517
4531
  resizerElement, children !== null && children !== undefined ? children : content)));
4518
4532
  });
4519
- const Drawer = ({ className, veilClassName, children, style, onVeilClick, onEscape, hideVeil, disablePortal = true, keepMounted = false, }) => {
4533
+ const Drawer = ({ className, veilClassName, children, style, onVeilClick, onEscape, hideVeil, disablePortal = true, keepMounted = false, scrollLock = false, }) => {
4520
4534
  let someItemVisible = false;
4521
4535
  React.Children.forEach(children, (child) => {
4522
4536
  if (React.isValidElement(child) && child.type === DrawerItem) {
@@ -4541,9 +4555,11 @@ const Drawer = ({ className, veilClassName, children, style, onVeilClick, onEsca
4541
4555
  }, [onEscape, someItemVisible]);
4542
4556
  const containerRef = React.useRef(null);
4543
4557
  const veilRef = React.useRef(null);
4544
- const drawer = (React.createElement(Transition, { in: someItemVisible, timeout: { enter: 0, exit: TIMEOUT }, mountOnEnter: !keepMounted, unmountOnExit: !keepMounted, nodeRef: containerRef }, (state) => {
4558
+ const shouldApplyScrollLock = scrollLock && someItemVisible && hideVeil && !disablePortal;
4559
+ useScrollLock(shouldApplyScrollLock);
4560
+ return (React.createElement(Transition, { in: someItemVisible, timeout: { enter: 0, exit: TIMEOUT }, mountOnEnter: !keepMounted, unmountOnExit: !keepMounted, nodeRef: containerRef }, (state) => {
4545
4561
  const childrenVisible = someItemVisible && state === 'entered';
4546
- return (React.createElement("div", { ref: containerRef, className: b$m({ hideVeil }, className), style: style },
4562
+ const content = (React.createElement("div", { ref: containerRef, className: b$m({ hideVeil }, className), style: style },
4547
4563
  React.createElement(CSSTransition, { in: childrenVisible, timeout: TIMEOUT, unmountOnExit: true, classNames: b$m('veil-transition'), nodeRef: veilRef },
4548
4564
  React.createElement("div", { ref: veilRef, className: b$m('veil', { hidden: hideVeil }, veilClassName), onClick: onVeilClick })),
4549
4565
  React.Children.map(children, (child) => {
@@ -4554,11 +4570,16 @@ const Drawer = ({ className, veilClassName, children, style, onVeilClick, onEsca
4554
4570
  }
4555
4571
  return child;
4556
4572
  })));
4573
+ if (disablePortal) {
4574
+ return content;
4575
+ }
4576
+ // When hideVeil=true, we don't use FloatingOverlay to avoid blocking mouse events
4577
+ if (hideVeil) {
4578
+ return React.createElement(uikit.Portal, null, content);
4579
+ }
4580
+ return (React.createElement(uikit.Portal, null,
4581
+ React.createElement(FloatingOverlay, { lockScroll: true }, content)));
4557
4582
  }));
4558
- if (disablePortal) {
4559
- return drawer;
4560
- }
4561
- return (React.createElement(uikit.Portal, null, someItemVisible ? React.createElement(FloatingOverlay, { lockScroll: true }, drawer) : drawer));
4562
4583
  };
4563
4584
 
4564
4585
  const Panels = () => {
@@ -5629,10 +5650,23 @@ styleInject(css_248z$5);
5629
5650
 
5630
5651
  const b$5 = block('mobile-overlap-panel');
5631
5652
  const OverlapPanel = ({ title, renderContent, className, onClose, action, closeTitle = i18n('overlap_button_close'), visible, topOffset, }) => {
5632
- return (React.createElement(Drawer, { className: b$5('', { action: Boolean(action) }, className), onVeilClick: onClose, onEscape: onClose, style: {
5633
- top: topOffset,
5634
- } },
5635
- React.createElement(DrawerItem, { id: "overlap", visible: visible, className: b$5('drawer-item') },
5653
+ const topOffsetValue = typeof topOffset === 'number' ? `${topOffset}px` : topOffset;
5654
+ const [itemPosition, setItemPosition] = React.useState();
5655
+ const drawerStyle = React.useMemo(() => ({ top: `calc(${topOffsetValue} + var(--gn-top-alert-height, 0px))` }), [topOffsetValue]);
5656
+ const drawerItemStyle = React.useMemo(() => itemPosition === 'absolute'
5657
+ ? {}
5658
+ : { top: `calc(${topOffsetValue} + var(--gn-top-alert-height, 0px))` }, [topOffsetValue, itemPosition]);
5659
+ const itemRef = React.useRef(null);
5660
+ // It is necessary to determine the position of the DrawerItem in order to correctly set the top offset
5661
+ React.useLayoutEffect(() => {
5662
+ if (itemRef.current) {
5663
+ const style = getComputedStyle(itemRef.current);
5664
+ const position = style.position;
5665
+ setItemPosition(position);
5666
+ }
5667
+ }, []);
5668
+ return (React.createElement(Drawer, { className: b$5('', { action: Boolean(action) }, className), onVeilClick: onClose, onEscape: onClose, style: drawerStyle },
5669
+ React.createElement(DrawerItem, { id: "overlap", ref: itemRef, visible: visible, className: b$5('drawer-item'), style: drawerItemStyle },
5636
5670
  React.createElement("div", { className: b$5('header') },
5637
5671
  React.createElement(uikit.Button, { size: "l", view: "flat", className: b$5('close'), onClick: onClose, "aria-label": closeTitle },
5638
5672
  React.createElement(uikit.Icon, { className: b$5('icon'), data: icons.ArrowLeft, size: MOBILE_HEADER_ICON_SIZE })),
@@ -5645,7 +5679,7 @@ const OverlapPanel = ({ title, renderContent, className, onClose, action, closeT
5645
5679
  var css_248z$4 = ".gn-mobile-header{--mobile-header-min-heigth:50px;--mobile-header-icon-size:20px;background-color:var(--g-color-base-background)}.gn-mobile-header__header-container{background-color:var(--g-color-base-background);position:sticky;top:0;z-index:var(--gn-mobile-header-z-index,100)}.gn-mobile-header__header{align-items:center;border-bottom:1px solid var(--g-color-line-generic);box-sizing:border-box;display:flex;justify-content:space-between;padding:0 10px}.gn-mobile-header__burger{padding:12px}.gn-mobile-header__panel-item{--gn-drawer-item-position:var(--gn-mobile-header-panel-position,absolute)}.gn-mobile-header__burger-menu,.gn-mobile-header__panel-item{background-color:var(--g-color-base-background);max-height:100%;max-width:90vw;width:320px}.gn-mobile-header__user-menu{overflow-y:auto}.gn-mobile-header__overlap-panel,.gn-mobile-header__panels{z-index:var(--gn-mobile-header-panel-z-index,98)}.gn-mobile-header__panels{inset:var(--mobile-header-min-heigth) 0 0;overflow:hidden;position:fixed}.gn-mobile-header__panel-item{top:unset}.gn-mobile-header__content{overflow:auto}";
5646
5680
  styleInject(css_248z$4);
5647
5681
 
5648
- const TopAlert = React.lazy(() => Promise.resolve().then(function () { return require('./index-C751PFJy.js'); }).then((module) => ({ default: module.TopAlert })));
5682
+ const TopAlert = React.lazy(() => Promise.resolve().then(function () { return require('./index-ClUpN5oD.js'); }).then((module) => ({ default: module.TopAlert })));
5649
5683
  const b$4 = block('mobile-header');
5650
5684
  const MobileHeader = React.forwardRef(({ logo, burgerMenu, burgerCloseTitle = i18n('burger_button_close'), burgerOpenTitle = i18n('burger_button_open'), panelItems = [], renderContent, sideItemRenderContent, onClosePanel, onEvent, className, contentClassName, overlapPanel, topAlert, }, ref) => {
5651
5685
  const targetRef = useForwardRef(ref);
@@ -5961,4 +5995,4 @@ exports.styleInject = styleInject;
5961
5995
  exports.useAsideHeaderContext = useAsideHeaderContext;
5962
5996
  exports.useSettingsContext = useSettingsContext;
5963
5997
  exports.useSettingsSelectionContext = useSettingsSelectionContext;
5964
- //# sourceMappingURL=index-wmJ_X61I.js.map
5998
+ //# sourceMappingURL=index-CLs1L-sq.js.map