@cloud-ru/ds-drawer 2.0.1-preview-20b4671f.0 → 2.1.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 2.1.0 (2026-10-09)
7
+
8
+ ### Features
9
+
10
+ - **FF-9241:** allow drawer to resize to full screen width ([60161a3](https://github.com/cloud-ru-tech/snack-v2/commit/60161a314fe1c4772e5445bf30f2560d60c96f60))
11
+
6
12
  # 2.0.0 (2026-10-05)
7
13
 
8
14
  ### BREAKING CHANGES
package/README.md CHANGED
@@ -448,7 +448,7 @@ export function CustomComposition() {
448
448
  | `open` | `boolean` | — | Управление состоянием показан/не показан. |
449
449
  | `position` | `"bottom"` \| `"left"` \| `"right"` \| `"top"` | — | Расположение |
450
450
  | `push` | `boolean \| PushConfig` | — | Смещение при открытии "вложенного" компонента |
451
- | `resizable` | `{ min: number; max?: number; default?: number; onResize?: ((width: number) => void) \| undefined; onResizeEnd?: ((width: number) => void) \| undefined; draggerTooltip?: string \| undefined; } \| undefined` | `'s'` | Ширина (только при position: "left" \| "right") |
451
+ | `resizable` | `ResizableMax` | `'s'` | Ширина (только при position: "left" \| "right") |
452
452
  | `rootClassName` | `string` | — | CSS-класс для корневого элемента |
453
453
  | `safeAreaBottom` | `boolean` | `true` | Нижний safe-area отступ под iOS home-indicator и Android nav-bar. Размер — токен <br/> `safeArea.bottom.minHeight`, на устройстве с большим `env(safe-area-inset-bottom)` — значение inset. |
454
454
  | `safeAreaTop` | `boolean` | `true` | Верхний safe-area отступ под iOS notch / status-bar. Размер — токен `safeArea.top.minHeight`, <br/> на устройстве с большим `env(safe-area-inset-top)` — значение inset. Применяется, только когда <br/> sheet раскрыт на полный вьюпорт: у неполного sheet'а верх и так не заходит под notch. |
@@ -477,7 +477,7 @@ export function CustomComposition() {
477
477
  | `open` | `boolean` | — | Управление состоянием показан/не показан. |
478
478
  | `position` | `"bottom"` \| `"left"` \| `"right"` \| `"top"` | — | Расположение |
479
479
  | `push` | `boolean \| PushConfig \| undefined` | — | Смещение при открытии "вложенного" компонента |
480
- | `resizable` | `{ min: number; max?: number; default?: number; onResize?: (width: number) => void; onResizeEnd?: (width: number) => void; draggerTooltip?: string; } \| undefined` | — | Ширина (только при position: "left" \| "right") |
480
+ | `resizable` | `ResizableMax` | — | Ширина (только при position: "left" \| "right") |
481
481
  | `rootClassName` | `string \| undefined` | — | CSS-класс для корневого элемента |
482
482
  | `safeAreaBottom` | `boolean \| undefined` | — | Нижний safe-area отступ под iOS home-indicator и Android nav-bar. Размер — токен <br/> `safeArea.bottom.minHeight`, на устройстве с большим `env(safe-area-inset-bottom)` — значение inset. |
483
483
  | `safeAreaTop` | `boolean \| undefined` | — | Верхний safe-area отступ под iOS notch / status-bar. Размер — токен `safeArea.top.minHeight`, <br/> на устройстве с большим `env(safe-area-inset-top)` — значение inset. Применяется, только когда <br/> sheet раскрыт на полный вьюпорт: у неполного sheet'а верх и так не заходит под notch. |
@@ -489,4 +489,6 @@ export function CustomComposition() {
489
489
 
490
490
  - `Position` = `"bottom"` \| `"left"` \| `"right"` \| `"top"`
491
491
 
492
+ - `ResizableMax` = `number | "full"`
493
+
492
494
  - `Width` = `"l"` \| `"m"` \| `"s"`
@@ -27,7 +27,7 @@ function DrawerFrame(props) {
27
27
  const { showBlackout = true, showButtonClosed = true, open, onClose, rootClassName, className, push, container, children, nestedDrawer, closeOnPopstate, position, heightAuto, width: widthProp, resizable: resizableProp, disableMotions = false, ...rest } = props;
28
28
  const heightAutoVertical = Boolean(heightAuto && (position === 'top' || position === 'bottom'));
29
29
  const resizable = heightAutoVertical ? undefined : resizableProp;
30
- const { tooltip, checkElement, width: userWidth, resizable: drawerResizable, } = (0, useDrawerResize_1.useDrawerResize)({
30
+ const { tooltip, checkElement, width: userWidth, resizable: drawerResizable, isResizing, } = (0, useDrawerResize_1.useDrawerResize)({
31
31
  resizable,
32
32
  position,
33
33
  });
@@ -59,7 +59,7 @@ function DrawerFrame(props) {
59
59
  const resolvedContainer = container ?? (portalContextRef.current ? () => portalContextRef.current : undefined);
60
60
  return ((0, jsx_runtime_1.jsx)(RcDrawer, { resizable: drawerResizable, panelRef: checkElement, mask: showBlackout, maskClosable: showBlackout, keyboard: showBlackout, "data-showblackout": showBlackout, open: open, onClose: onClose, push: push, getContainer: resolvedContainer, placement: position, destroyOnHidden: true, rootClassName: (0, classnames_1.default)(styles_module_scss_1.default.drawerRoot, rootClassName), style: drawerPanelStyle, classNames: {
61
61
  section: (0, classnames_1.default)(styles_module_scss_1.default.drawer, className),
62
- }, size: isPredefinedWidth ? 'null' : width, ...(0, utils_1.extractSupportProps)(rest), "data-content-wrapper": true, "data-position": position, "data-width": isPredefinedWidth ? width : undefined, "data-height-auto": heightAutoVertical ? true : undefined, "data-acrylic-appearance": 'neutral', "data-acrylic-level": '1Level', prefixCls: styles_module_scss_1.default.prefixCls, ...(disableMotions ? {} : constants_2.motionProps), children: (0, jsx_runtime_1.jsxs)(react_remove_scroll_1.RemoveScroll, { enabled: showBlackout || outerScrollLock, removeScrollBar: false, ref: focusTrapRef, className: styles_module_scss_1.default.scrollLock, children: [showButtonClosed && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.showButtonClosedWrapper, children: (0, jsx_runtime_1.jsx)(popup_private_1.PopupCloseButton, { className: styles_module_scss_1.default.showButtonClosed, "aria-label": 'close drawer', "data-test-id": constants_1.TEST_IDS.closeButton, onClick: onClose }) })), (0, jsx_runtime_1.jsx)(bottom_sheet_1.OverlaySurfaceProvider, { surface: bottom_sheet_1.OVERLAY_SURFACE.Drawer, bodyHeightAuto: heightAutoVertical, children: children }), tooltip, nestedDrawer] }) }));
62
+ }, size: isPredefinedWidth ? 'null' : width, ...(0, utils_1.extractSupportProps)(rest), "data-resizing": isResizing || undefined, "data-content-wrapper": true, "data-position": position, "data-width": isPredefinedWidth ? width : undefined, "data-height-auto": heightAutoVertical ? true : undefined, "data-acrylic-appearance": 'neutral', "data-acrylic-level": '1Level', prefixCls: styles_module_scss_1.default.prefixCls, ...(disableMotions ? {} : constants_2.motionProps), children: (0, jsx_runtime_1.jsxs)(react_remove_scroll_1.RemoveScroll, { enabled: showBlackout || outerScrollLock, removeScrollBar: false, ref: focusTrapRef, className: styles_module_scss_1.default.scrollLock, children: [showButtonClosed && ((0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.showButtonClosedWrapper, children: (0, jsx_runtime_1.jsx)(popup_private_1.PopupCloseButton, { className: styles_module_scss_1.default.showButtonClosed, "aria-label": 'close drawer', "data-test-id": constants_1.TEST_IDS.closeButton, onClick: onClose }) })), (0, jsx_runtime_1.jsx)(bottom_sheet_1.OverlaySurfaceProvider, { surface: bottom_sheet_1.OVERLAY_SURFACE.Drawer, bodyHeightAuto: heightAutoVertical, children: children }), tooltip, nestedDrawer] }) }));
63
63
  }
64
64
  /**
65
65
  * Адаптивный low-level `DrawerCustom`: на `mobile` — `BottomSheetCustom`, иначе — `DrawerFrame`.
@@ -10,6 +10,8 @@ type UseDrawerResizeResult = {
10
10
  resizable: DrawerProps['resizable'];
11
11
  /** Ширина ползунка в пикселях */
12
12
  width?: number;
13
+ /** Флаг, указывающий, что дровер в процессе ресайза */
14
+ isResizing: boolean;
13
15
  };
14
16
  export declare function useDrawerResize({ position, resizable: resizableProp }: UseDrawerResizeParams): UseDrawerResizeResult;
15
17
  export {};
@@ -8,8 +8,10 @@ const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const tooltip_1 = require("@cloud-ru/ds-tooltip");
9
9
  const utils_1 = require("@cloud-ru/ds-utils");
10
10
  const react_1 = require("react");
11
+ const constants_1 = require("../../../constants");
11
12
  const styles_module_scss_1 = __importDefault(require('../styles.module.css'));
12
13
  const DRAGGER_SELECTOR = `.${styles_module_scss_1.default.prefixCls}-resizable-dragger`;
14
+ const getFullMaxWidth = () => ((0, utils_1.isBrowser)() ? Math.max(0, document.documentElement.clientWidth) : Infinity);
13
15
  function useDrawerResize({ position, resizable: resizableProp }) {
14
16
  const targetRef = (0, react_1.useRef)(null);
15
17
  const targetRefCallbackRef = (0, react_1.useRef)();
@@ -30,25 +32,34 @@ function useDrawerResize({ position, resizable: resizableProp }) {
30
32
  return null;
31
33
  targetRef.current ? targetRefCallback(targetRef.current) : (targetRefCallbackRef.current = targetRefCallback);
32
34
  } })) : null;
33
- const resizable = (0, react_1.useMemo)(() => resizableProp
35
+ const { onResize, onResizeEnd, max, min } = resizableProp ?? {};
36
+ const resizeEnabled = Boolean(resizableProp);
37
+ // Сколько стартовой ширины, которую держит rc-drawer, не помещается в окно: оно могло сузиться, пока дровер
38
+ // был закрыт или открыт. CSS обрезает панель визуально, а rc-drawer считает ресайз от «виртуальной» ширины.
39
+ const excessRef = (0, react_1.useRef)(0);
40
+ const resizable = (0, react_1.useMemo)(() => resizeEnabled
34
41
  ? {
35
- onResize: value => {
36
- if (value < (resizableProp?.min || 0))
37
- return;
38
- if (value > (resizableProp?.max || Infinity))
42
+ onResize: rawValue => {
43
+ // Смещение мыши rc-drawer прибавляет к устаревшей стартовой ширине: вычитаем лишнее.
44
+ const limit = max === constants_1.RESIZABLE_MAX_FULL ? getFullMaxWidth() : max;
45
+ const value = Math.min(rawValue - excessRef.current, limit ?? Infinity);
46
+ if (value < (min || 0))
39
47
  return;
40
48
  setWidth(value);
41
- resizableProp?.onResize?.(value);
49
+ onResize?.(value);
42
50
  },
43
51
  onResizeEnd: () => {
44
52
  setOpen(false);
45
53
  setMuted(false);
46
- resizableProp?.onResizeEnd?.(width ?? 0);
54
+ onResizeEnd?.(width ?? 0);
47
55
  },
48
- onResizeStart: () => {
56
+ // rc-drawer передаёт во время выполнения реальную стартовую ширину (в типах аргумента нет).
57
+ onResizeStart: (startSize) => {
58
+ excessRef.current =
59
+ max === constants_1.RESIZABLE_MAX_FULL && startSize !== undefined ? Math.max(0, startSize - getFullMaxWidth()) : 0;
49
60
  setMuted(true);
50
61
  },
51
62
  }
52
- : undefined, [resizableProp, width]);
53
- return { tooltip, checkElement, resizable, width };
63
+ : undefined, [width, max, min, onResize, onResizeEnd, resizeEnabled]);
64
+ return { tooltip, checkElement, resizable, width, isResizing: muted };
54
65
  }
@@ -126,6 +126,7 @@
126
126
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right]{
127
127
  align-items:stretch;
128
128
  box-sizing:content-box;
129
+ max-width:calc(100% - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingLeft, var(--sn-primitive-dimension-0, 0px)) - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingRight, var(--sn-density-spacing-interval-l, var(--sn-platform-comfort-spacing-interval-l, var(--sn-primitive-dimension-32, 32px)))));
129
130
  height:100%;
130
131
  }
131
132
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left][data-width=s], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right][data-width=s]{
@@ -154,6 +155,9 @@
154
155
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=bottom][data-height-auto]{
155
156
  inset:auto 0 0;
156
157
  }
158
+ .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-resizing] *{
159
+ pointer-events:none;
160
+ }
157
161
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-enter, .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-appear{
158
162
  transform:translateX(-100%);
159
163
  }
@@ -2,7 +2,7 @@ import { BottomSheetCustomProps } from '@cloud-ru/ds-bottom-sheet';
2
2
  import { WithSupportProps } from '@cloud-ru/ds-utils';
3
3
  import { DrawerProps as RcDrawerProps } from '@rc-component/drawer';
4
4
  import { PropsWithChildren, ReactElement } from 'react';
5
- import { Position, Width } from '../../types';
5
+ import { Position, ResizableMax, Width } from '../../types';
6
6
  export type DrawerCustomProps = WithSupportProps<PropsWithChildren<{
7
7
  /** Управление состоянием показан/не показан. */
8
8
  open: boolean;
@@ -57,8 +57,11 @@ export type DrawerCustomProps = WithSupportProps<PropsWithChildren<{
57
57
  resizable?: {
58
58
  /** Минимальная доступная ширина */
59
59
  min: number;
60
- /** Максимальная доступная ширина */
61
- max?: number;
60
+ /**
61
+ * Максимальная доступная ширина: число в px либо `'full'` — вся ширина окна за вычетом 48px
62
+ * (двойной отступ обёртки).
63
+ */
64
+ max?: ResizableMax;
62
65
  /** Ширина по умолчанию */
63
66
  default?: number;
64
67
  /** Колбэк на изменение размера */
@@ -26,3 +26,7 @@ export declare const POSITION: {
26
26
  readonly Top: "top";
27
27
  readonly Bottom: "bottom";
28
28
  };
29
+ /** Значение `resizable.max`: растягивать до ширины окна за вычетом двойного отступа обёртки. */
30
+ export declare const RESIZABLE_MAX_FULL = "full";
31
+ /** Горизонтальный отступ `content-wrapper`. */
32
+ export declare const FULL_WIDTH_OFFSET = 24;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.POSITION = exports.WIDTH_AS_VALUES = exports.WIDTH = exports.TEST_IDS = void 0;
3
+ exports.FULL_WIDTH_OFFSET = exports.RESIZABLE_MAX_FULL = exports.POSITION = exports.WIDTH_AS_VALUES = exports.WIDTH = exports.TEST_IDS = void 0;
4
4
  exports.TEST_IDS = {
5
5
  closeButton: 'drawer__close-button',
6
6
  header: 'drawer__header',
@@ -29,3 +29,7 @@ exports.POSITION = {
29
29
  Top: 'top',
30
30
  Bottom: 'bottom',
31
31
  };
32
+ /** Значение `resizable.max`: растягивать до ширины окна за вычетом двойного отступа обёртки. */
33
+ exports.RESIZABLE_MAX_FULL = 'full';
34
+ /** Горизонтальный отступ `content-wrapper`. */
35
+ exports.FULL_WIDTH_OFFSET = 24;
@@ -129,6 +129,7 @@
129
129
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right]{
130
130
  align-items:stretch;
131
131
  box-sizing:content-box;
132
+ max-width:calc(100% - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingLeft, var(--sn-primitive-dimension-0, 0px)) - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingRight, var(--sn-density-spacing-interval-l, var(--sn-platform-comfort-spacing-interval-l, var(--sn-primitive-dimension-32, 32px)))));
132
133
  height:100%;
133
134
  }
134
135
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left][data-width=s], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right][data-width=s]{
@@ -157,6 +158,9 @@
157
158
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=bottom][data-height-auto]{
158
159
  inset:auto 0 0;
159
160
  }
161
+ .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-resizing] *{
162
+ pointer-events:none;
163
+ }
160
164
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-enter, .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-appear{
161
165
  transform:translateX(-100%);
162
166
  }
@@ -1,4 +1,5 @@
1
1
  import { ValueOf } from '@cloud-ru/ds-utils';
2
- import { POSITION, WIDTH } from './constants';
2
+ import { POSITION, RESIZABLE_MAX_FULL, WIDTH } from './constants';
3
3
  export type Width = ValueOf<typeof WIDTH>;
4
4
  export type Position = ValueOf<typeof POSITION>;
5
+ export type ResizableMax = number | typeof RESIZABLE_MAX_FULL;
@@ -21,7 +21,7 @@ function DrawerFrame(props) {
21
21
  const { showBlackout = true, showButtonClosed = true, open, onClose, rootClassName, className, push, container, children, nestedDrawer, closeOnPopstate, position, heightAuto, width: widthProp, resizable: resizableProp, disableMotions = false, ...rest } = props;
22
22
  const heightAutoVertical = Boolean(heightAuto && (position === 'top' || position === 'bottom'));
23
23
  const resizable = heightAutoVertical ? undefined : resizableProp;
24
- const { tooltip, checkElement, width: userWidth, resizable: drawerResizable, } = useDrawerResize({
24
+ const { tooltip, checkElement, width: userWidth, resizable: drawerResizable, isResizing, } = useDrawerResize({
25
25
  resizable,
26
26
  position,
27
27
  });
@@ -53,7 +53,7 @@ function DrawerFrame(props) {
53
53
  const resolvedContainer = container ?? (portalContextRef.current ? () => portalContextRef.current : undefined);
54
54
  return (_jsx(RcDrawer, { resizable: drawerResizable, panelRef: checkElement, mask: showBlackout, maskClosable: showBlackout, keyboard: showBlackout, "data-showblackout": showBlackout, open: open, onClose: onClose, push: push, getContainer: resolvedContainer, placement: position, destroyOnHidden: true, rootClassName: cn(styles.drawerRoot, rootClassName), style: drawerPanelStyle, classNames: {
55
55
  section: cn(styles.drawer, className),
56
- }, size: isPredefinedWidth ? 'null' : width, ...extractSupportProps(rest), "data-content-wrapper": true, "data-position": position, "data-width": isPredefinedWidth ? width : undefined, "data-height-auto": heightAutoVertical ? true : undefined, "data-acrylic-appearance": 'neutral', "data-acrylic-level": '1Level', prefixCls: styles.prefixCls, ...(disableMotions ? {} : motionProps), children: _jsxs(RemoveScroll, { enabled: showBlackout || outerScrollLock, removeScrollBar: false, ref: focusTrapRef, className: styles.scrollLock, children: [showButtonClosed && (_jsx("div", { className: styles.showButtonClosedWrapper, children: _jsx(PopupCloseButton, { className: styles.showButtonClosed, "aria-label": 'close drawer', "data-test-id": TEST_IDS.closeButton, onClick: onClose }) })), _jsx(OverlaySurfaceProvider, { surface: OVERLAY_SURFACE.Drawer, bodyHeightAuto: heightAutoVertical, children: children }), tooltip, nestedDrawer] }) }));
56
+ }, size: isPredefinedWidth ? 'null' : width, ...extractSupportProps(rest), "data-resizing": isResizing || undefined, "data-content-wrapper": true, "data-position": position, "data-width": isPredefinedWidth ? width : undefined, "data-height-auto": heightAutoVertical ? true : undefined, "data-acrylic-appearance": 'neutral', "data-acrylic-level": '1Level', prefixCls: styles.prefixCls, ...(disableMotions ? {} : motionProps), children: _jsxs(RemoveScroll, { enabled: showBlackout || outerScrollLock, removeScrollBar: false, ref: focusTrapRef, className: styles.scrollLock, children: [showButtonClosed && (_jsx("div", { className: styles.showButtonClosedWrapper, children: _jsx(PopupCloseButton, { className: styles.showButtonClosed, "aria-label": 'close drawer', "data-test-id": TEST_IDS.closeButton, onClick: onClose }) })), _jsx(OverlaySurfaceProvider, { surface: OVERLAY_SURFACE.Drawer, bodyHeightAuto: heightAutoVertical, children: children }), tooltip, nestedDrawer] }) }));
57
57
  }
58
58
  /**
59
59
  * Адаптивный low-level `DrawerCustom`: на `mobile` — `BottomSheetCustom`, иначе — `DrawerFrame`.
@@ -10,6 +10,8 @@ type UseDrawerResizeResult = {
10
10
  resizable: DrawerProps['resizable'];
11
11
  /** Ширина ползунка в пикселях */
12
12
  width?: number;
13
+ /** Флаг, указывающий, что дровер в процессе ресайза */
14
+ isResizing: boolean;
13
15
  };
14
16
  export declare function useDrawerResize({ position, resizable: resizableProp }: UseDrawerResizeParams): UseDrawerResizeResult;
15
17
  export {};
@@ -2,8 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Tooltip } from '@cloud-ru/ds-tooltip';
3
3
  import { isBrowser } from '@cloud-ru/ds-utils';
4
4
  import { useCallback, useMemo, useRef, useState } from 'react';
5
+ import { RESIZABLE_MAX_FULL } from '../../../constants.js';
5
6
  import styles from '../styles.module.css';
6
7
  const DRAGGER_SELECTOR = `.${styles.prefixCls}-resizable-dragger`;
8
+ const getFullMaxWidth = () => (isBrowser() ? Math.max(0, document.documentElement.clientWidth) : Infinity);
7
9
  export function useDrawerResize({ position, resizable: resizableProp }) {
8
10
  const targetRef = useRef(null);
9
11
  const targetRefCallbackRef = useRef();
@@ -24,25 +26,34 @@ export function useDrawerResize({ position, resizable: resizableProp }) {
24
26
  return null;
25
27
  targetRef.current ? targetRefCallback(targetRef.current) : (targetRefCallbackRef.current = targetRefCallback);
26
28
  } })) : null;
27
- const resizable = useMemo(() => resizableProp
29
+ const { onResize, onResizeEnd, max, min } = resizableProp ?? {};
30
+ const resizeEnabled = Boolean(resizableProp);
31
+ // Сколько стартовой ширины, которую держит rc-drawer, не помещается в окно: оно могло сузиться, пока дровер
32
+ // был закрыт или открыт. CSS обрезает панель визуально, а rc-drawer считает ресайз от «виртуальной» ширины.
33
+ const excessRef = useRef(0);
34
+ const resizable = useMemo(() => resizeEnabled
28
35
  ? {
29
- onResize: value => {
30
- if (value < (resizableProp?.min || 0))
31
- return;
32
- if (value > (resizableProp?.max || Infinity))
36
+ onResize: rawValue => {
37
+ // Смещение мыши rc-drawer прибавляет к устаревшей стартовой ширине: вычитаем лишнее.
38
+ const limit = max === RESIZABLE_MAX_FULL ? getFullMaxWidth() : max;
39
+ const value = Math.min(rawValue - excessRef.current, limit ?? Infinity);
40
+ if (value < (min || 0))
33
41
  return;
34
42
  setWidth(value);
35
- resizableProp?.onResize?.(value);
43
+ onResize?.(value);
36
44
  },
37
45
  onResizeEnd: () => {
38
46
  setOpen(false);
39
47
  setMuted(false);
40
- resizableProp?.onResizeEnd?.(width ?? 0);
48
+ onResizeEnd?.(width ?? 0);
41
49
  },
42
- onResizeStart: () => {
50
+ // rc-drawer передаёт во время выполнения реальную стартовую ширину (в типах аргумента нет).
51
+ onResizeStart: (startSize) => {
52
+ excessRef.current =
53
+ max === RESIZABLE_MAX_FULL && startSize !== undefined ? Math.max(0, startSize - getFullMaxWidth()) : 0;
43
54
  setMuted(true);
44
55
  },
45
56
  }
46
- : undefined, [resizableProp, width]);
47
- return { tooltip, checkElement, resizable, width };
57
+ : undefined, [width, max, min, onResize, onResizeEnd, resizeEnabled]);
58
+ return { tooltip, checkElement, resizable, width, isResizing: muted };
48
59
  }
@@ -126,6 +126,7 @@
126
126
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right]{
127
127
  align-items:stretch;
128
128
  box-sizing:content-box;
129
+ max-width:calc(100% - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingLeft, var(--sn-primitive-dimension-0, 0px)) - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingRight, var(--sn-density-spacing-interval-l, var(--sn-platform-comfort-spacing-interval-l, var(--sn-primitive-dimension-32, 32px)))));
129
130
  height:100%;
130
131
  }
131
132
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left][data-width=s], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right][data-width=s]{
@@ -154,6 +155,9 @@
154
155
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=bottom][data-height-auto]{
155
156
  inset:auto 0 0;
156
157
  }
158
+ .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-resizing] *{
159
+ pointer-events:none;
160
+ }
157
161
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-enter, .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-appear{
158
162
  transform:translateX(-100%);
159
163
  }
@@ -2,7 +2,7 @@ import { BottomSheetCustomProps } from '@cloud-ru/ds-bottom-sheet';
2
2
  import { WithSupportProps } from '@cloud-ru/ds-utils';
3
3
  import { DrawerProps as RcDrawerProps } from '@rc-component/drawer';
4
4
  import { PropsWithChildren, ReactElement } from 'react';
5
- import { Position, Width } from '../../types.js';
5
+ import { Position, ResizableMax, Width } from '../../types.js';
6
6
  export type DrawerCustomProps = WithSupportProps<PropsWithChildren<{
7
7
  /** Управление состоянием показан/не показан. */
8
8
  open: boolean;
@@ -57,8 +57,11 @@ export type DrawerCustomProps = WithSupportProps<PropsWithChildren<{
57
57
  resizable?: {
58
58
  /** Минимальная доступная ширина */
59
59
  min: number;
60
- /** Максимальная доступная ширина */
61
- max?: number;
60
+ /**
61
+ * Максимальная доступная ширина: число в px либо `'full'` — вся ширина окна за вычетом 48px
62
+ * (двойной отступ обёртки).
63
+ */
64
+ max?: ResizableMax;
62
65
  /** Ширина по умолчанию */
63
66
  default?: number;
64
67
  /** Колбэк на изменение размера */
@@ -26,3 +26,7 @@ export declare const POSITION: {
26
26
  readonly Top: "top";
27
27
  readonly Bottom: "bottom";
28
28
  };
29
+ /** Значение `resizable.max`: растягивать до ширины окна за вычетом двойного отступа обёртки. */
30
+ export declare const RESIZABLE_MAX_FULL = "full";
31
+ /** Горизонтальный отступ `content-wrapper`. */
32
+ export declare const FULL_WIDTH_OFFSET = 24;
@@ -26,3 +26,7 @@ export const POSITION = {
26
26
  Top: 'top',
27
27
  Bottom: 'bottom',
28
28
  };
29
+ /** Значение `resizable.max`: растягивать до ширины окна за вычетом двойного отступа обёртки. */
30
+ export const RESIZABLE_MAX_FULL = 'full';
31
+ /** Горизонтальный отступ `content-wrapper`. */
32
+ export const FULL_WIDTH_OFFSET = 24;
@@ -129,6 +129,7 @@
129
129
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right]{
130
130
  align-items:stretch;
131
131
  box-sizing:content-box;
132
+ max-width:calc(100% - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingLeft, var(--sn-primitive-dimension-0, 0px)) - var(--sn-drawer-anatomy-container-vertical-positionLeft-paddingRight, var(--sn-density-spacing-interval-l, var(--sn-platform-comfort-spacing-interval-l, var(--sn-primitive-dimension-32, 32px)))));
132
133
  height:100%;
133
134
  }
134
135
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=left][data-width=s], .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=right][data-width=s]{
@@ -157,6 +158,9 @@
157
158
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-position=bottom][data-height-auto]{
158
159
  inset:auto 0 0;
159
160
  }
161
+ .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper[data-resizing] *{
162
+ pointer-events:none;
163
+ }
160
164
  .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-enter, .drawerRoot:global(.cloud-ru-ds-drawer-v1) :global .cloud-ru-ds-drawer-v1-content-wrapper.cloud-ru-ds-drawer-v1-panel-motion-left-appear{
161
165
  transform:translateX(-100%);
162
166
  }
@@ -1,4 +1,5 @@
1
1
  import { ValueOf } from '@cloud-ru/ds-utils';
2
- import { POSITION, WIDTH } from './constants.js';
2
+ import { POSITION, RESIZABLE_MAX_FULL, WIDTH } from './constants.js';
3
3
  export type Width = ValueOf<typeof WIDTH>;
4
4
  export type Position = ValueOf<typeof POSITION>;
5
+ export type ResizableMax = number | typeof RESIZABLE_MAX_FULL;