@cloud-ru/ds-drawer 1.0.7-preview-5f569f3c.0 → 1.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +6 -0
  3. package/dist/cjs/components/Drawer/styles.module.css +0 -16
  4. package/dist/cjs/components/Drawer/types.d.ts +7 -3
  5. package/dist/cjs/components/DrawerCustom/DrawerCustom.d.ts +3 -3
  6. package/dist/cjs/components/DrawerCustom/DrawerCustom.js +1 -1
  7. package/dist/cjs/components/DrawerCustom/constants.js +2 -6
  8. package/dist/cjs/components/DrawerCustom/hooks/useDrawerResize.js +1 -5
  9. package/dist/cjs/components/DrawerCustom/styles.module.css +117 -123
  10. package/dist/cjs/constants.d.ts +1 -1
  11. package/dist/cjs/constants.js +1 -1
  12. package/dist/cjs/helperComponents/DesktopDrawer/DesktopDrawer.d.ts +1 -1
  13. package/dist/cjs/helperComponents/DesktopDrawer/DesktopDrawer.js +8 -12
  14. package/dist/cjs/helperComponents/DialogSlots.d.ts +25 -4
  15. package/dist/cjs/helperComponents/DialogSlots.js +32 -3
  16. package/dist/cjs/helperComponents/DrawerBody/DrawerBody.d.ts +1 -1
  17. package/dist/cjs/helperComponents/DrawerBody/DrawerBody.js +3 -2
  18. package/dist/cjs/helperComponents/MobileDrawer/MobileDrawer.d.ts +1 -1
  19. package/dist/cjs/helperComponents/MobileDrawer/MobileDrawer.js +4 -4
  20. package/dist/cjs/helperComponents/styles.module.css +15 -0
  21. package/dist/cjs/style.css +132 -139
  22. package/dist/esm/components/Drawer/styles.module.css +0 -16
  23. package/dist/esm/components/Drawer/types.d.ts +7 -3
  24. package/dist/esm/components/DrawerCustom/DrawerCustom.d.ts +3 -3
  25. package/dist/esm/components/DrawerCustom/DrawerCustom.js +1 -1
  26. package/dist/esm/components/DrawerCustom/constants.js +2 -3
  27. package/dist/esm/components/DrawerCustom/hooks/useDrawerResize.js +1 -2
  28. package/dist/esm/components/DrawerCustom/styles.module.css +117 -123
  29. package/dist/esm/constants.d.ts +1 -1
  30. package/dist/esm/constants.js +1 -1
  31. package/dist/esm/helperComponents/DesktopDrawer/DesktopDrawer.d.ts +1 -1
  32. package/dist/esm/helperComponents/DesktopDrawer/DesktopDrawer.js +8 -12
  33. package/dist/esm/helperComponents/DialogSlots.d.ts +25 -4
  34. package/dist/esm/helperComponents/DialogSlots.js +29 -4
  35. package/dist/esm/helperComponents/DrawerBody/DrawerBody.d.ts +1 -1
  36. package/dist/esm/helperComponents/DrawerBody/DrawerBody.js +4 -3
  37. package/dist/esm/helperComponents/MobileDrawer/MobileDrawer.d.ts +1 -1
  38. package/dist/esm/helperComponents/MobileDrawer/MobileDrawer.js +4 -4
  39. package/dist/esm/helperComponents/styles.module.css +15 -0
  40. package/dist/esm/style.css +132 -139
  41. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  42. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  43. package/package.json +9 -9
  44. package/src/components/Drawer/styles.module.scss +0 -19
  45. package/src/components/Drawer/types.ts +7 -3
  46. package/src/components/DrawerCustom/DrawerCustom.tsx +1 -1
  47. package/src/components/DrawerCustom/constants.ts +2 -4
  48. package/src/components/DrawerCustom/hooks/useDrawerResize.tsx +1 -2
  49. package/src/components/DrawerCustom/styles.module.scss +42 -60
  50. package/src/constants.ts +1 -1
  51. package/src/helperComponents/DesktopDrawer/DesktopDrawer.tsx +18 -27
  52. package/src/helperComponents/DialogSlots.tsx +74 -3
  53. package/src/helperComponents/DrawerBody/DrawerBody.tsx +5 -2
  54. package/src/helperComponents/MobileDrawer/MobileDrawer.tsx +5 -2
  55. package/src/helperComponents/styles.module.scss +22 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-ru/ds-drawer",
3
- "version": "1.0.7-preview-5f569f3c.0",
3
+ "version": "1.1.0",
4
4
  "description": "Пакет выезжающих панелей — компоненты Drawer и DrawerCustom с едиными токенами позиции и ширины.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,13 +40,13 @@
40
40
  "classnames": "^2.5.1",
41
41
  "react-remove-scroll": "^2.7.2",
42
42
  "tabbable": "^6.5.0",
43
- "@cloud-ru/ds-adaptive": "1.0.1",
44
- "@cloud-ru/ds-bottom-sheet": "1.0.6-preview-5f569f3c.0",
45
- "@cloud-ru/ds-materials": "1.0.1",
46
- "@cloud-ru/ds-popup-private": "1.0.5-preview-5f569f3c.0",
47
- "@cloud-ru/ds-portal-context": "1.0.1",
48
- "@cloud-ru/ds-scroll": "1.0.2",
49
- "@cloud-ru/ds-tooltip": "1.0.4",
50
- "@cloud-ru/ds-utils": "1.1.0"
43
+ "@cloud-ru/ds-adaptive": "^1.0.1",
44
+ "@cloud-ru/ds-bottom-sheet": "^1.1.0",
45
+ "@cloud-ru/ds-materials": "^1.0.1",
46
+ "@cloud-ru/ds-popup-private": "^1.1.0",
47
+ "@cloud-ru/ds-portal-context": "^1.0.1",
48
+ "@cloud-ru/ds-scroll": "^1.0.2",
49
+ "@cloud-ru/ds-tooltip": "^1.0.4",
50
+ "@cloud-ru/ds-utils": "^1.1.0"
51
51
  }
52
52
  }
@@ -1,26 +1,7 @@
1
1
  @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
- @use '@cloud-ru/ds-figma-variables/build/scss/components/drawer.module' as drawer;
3
2
 
4
3
  .drawer {
5
4
  box-sizing: border-box;
6
5
  }
7
6
 
8
- .safeAreaTop {
9
- display: flex;
10
- flex-direction: column;
11
- align-items: flex-start;
12
- align-self: stretch;
13
-
14
- padding-top: base.simple-var(drawer.$drawer, 'anatomy', 'container', 'safeArea', 'padding-top');
15
- }
16
-
17
- .safeAreaBottom {
18
- display: flex;
19
- flex-direction: column;
20
- align-items: flex-start;
21
- align-self: stretch;
22
-
23
- padding-bottom: base.simple-var(drawer.$drawer, 'anatomy', 'container', 'safeArea', 'padding-bottom');
24
- }
25
-
26
7
  // Desktop-футер: ряд кнопок прижат вправо.
@@ -1,6 +1,6 @@
1
1
  import { BottomSheetActionButton, BottomSheetProps, FooterActionsOrientation } from '@cloud-ru/ds-bottom-sheet';
2
2
  import { WithSupportProps } from '@cloud-ru/ds-utils';
3
- import { ReactElement, ReactNode } from 'react';
3
+ import { ReactElement, ReactNode, Ref } from 'react';
4
4
 
5
5
  import { DialogBodyProps, DialogHeaderProps } from '../../helperComponents';
6
6
  import { DrawerCustomProps } from '../DrawerCustom';
@@ -9,14 +9,18 @@ import { DrawerCustomProps } from '../DrawerCustom';
9
9
  * Адаптивный Drawer: desktop — боковая/верхняя/нижняя панель, `mobile` — `BottomSheet`.
10
10
  *
11
11
  * Только desktop: `position`, `width`, `heightAuto`, `nestedDrawer` (на mobile игнорируются).
12
- * Только mobile: `swipeEnabled`, `snapPoints`, `snapIndex`, `onSnapIndexChange`, `safeArea`
12
+ * Только mobile: `swipeEnabled`, `snapPoints`, `snapIndex`, `onSnapIndexChange`, `safeArea`, `withDividers`
13
13
  * (на desktop-панели игнорируются — это анатомия `BottomSheet`).
14
14
  */
15
15
  export type DrawerProps = WithSupportProps<
16
16
  Omit<DrawerCustomProps, 'nestedDrawer' | 'push' | 'resizable'> &
17
17
  Pick<BottomSheetProps, 'swipeEnabled' | 'snapPoints' | 'snapIndex' | 'onSnapIndexChange' | 'safeArea'> &
18
- Pick<DialogHeaderProps, 'title' | 'slotAfterTitle' | 'onBackButtonClick'> &
18
+ Pick<DialogHeaderProps, 'title' | 'slotAfterTitle' | 'slotSecondTitle' | 'onBackButtonClick'> &
19
19
  Pick<DialogBodyProps, 'content'> & {
20
+ /** Ссылка на скроллируемый контейнер контента (например, для дозагрузки по скроллу). */
21
+ contentRef?: Ref<HTMLElement>;
22
+ /** Только mobile: разделители между шапкой/контентом/футером sheet'а. @default false */
23
+ withDividers?: boolean;
20
24
  /** Текстовая строка-подзаголовок под title. */
21
25
  subtitle?: ReactNode;
22
26
  /** CSS-класс */
@@ -118,7 +118,7 @@ function DrawerFrame(props: DrawerCustomProps) {
118
118
  data-height-auto={heightAutoVertical ? true : undefined}
119
119
  data-acrylic-appearance='neutral'
120
120
  data-acrylic-level='1Level'
121
- prefixCls={styles.prefixCls}
121
+ prefixCls='snack-rc-drawer'
122
122
  {...(disableMotions ? {} : motionProps)}
123
123
  >
124
124
  {/*
@@ -1,16 +1,14 @@
1
1
  import { DrawerProps } from '@rc-component/drawer';
2
2
 
3
- import styles from './styles.module.scss';
4
-
5
3
  export const maskMotion: DrawerProps['maskMotion'] = {
6
4
  motionAppear: true,
7
- motionName: styles.maskMotionName,
5
+ motionName: 'maskMotion',
8
6
  };
9
7
 
10
8
  export const motion: DrawerProps['motion'] = placement => ({
11
9
  motionAppear: true,
12
10
  motionDeadline: 300,
13
- motionName: `${styles.panelMotionName}-${placement}`,
11
+ motionName: `panelMotion-${placement}`,
14
12
  });
15
13
 
16
14
  export const motionProps: Partial<DrawerProps> = {
@@ -3,10 +3,9 @@ import { isBrowser } from '@cloud-ru/ds-utils';
3
3
  import { DrawerProps } from '@rc-component/drawer';
4
4
  import { useCallback, useMemo, useRef, useState } from 'react';
5
5
 
6
- import styles from '../styles.module.scss';
7
6
  import { DrawerCustomProps } from '../types';
8
7
 
9
- const DRAGGER_SELECTOR = `.${styles.prefixCls}-resizable-dragger`;
8
+ const DRAGGER_SELECTOR = '.snack-rc-drawer-resizable-dragger';
10
9
 
11
10
  type UseDrawerResizeParams = Pick<DrawerCustomProps, 'position' | 'resizable'>;
12
11
 
@@ -10,20 +10,6 @@ $draggerWidth: base.$sn-primitive-dimension-4;
10
10
  $draggerPosition: calc(drawer.$sn-drawer-anatomy-container-vertical-positionLeft-paddingRight - $draggerWidth - base.$sn-primitive-dimension-6);
11
11
  $activeDraggerHeight: calc(base.$sn-primitive-dimension-32 + base.$sn-primitive-dimension-24);
12
12
 
13
- // Классы rc-drawer/rc-motion глобальные (css-модуль их не хеширует), поэтому свои — с namespace и
14
- // версией: оболочка консоли на snack v1 объявляет те же `.maskMotion-*`/`.panelMotion-*` и смещает
15
- // панель своими правилами. В JS имена отдаёт `:export`. Версию повышать при несовместимом изменении.
16
- $classVersion: 'v1';
17
- $prefixCls: 'cloud-ru-ds-drawer-#{$classVersion}';
18
- $maskMotionName: '#{$prefixCls}-mask-motion';
19
- $panelMotionName: '#{$prefixCls}-panel-motion';
20
-
21
- :export {
22
- maskMotionName: #{$maskMotionName};
23
- panelMotionName: #{$panelMotionName};
24
- prefixCls: #{$prefixCls};
25
- }
26
-
27
13
  // placement → offscreen transform для panelMotion-*
28
14
  $panelMotions: (
29
15
  'left': translateX(-100%),
@@ -32,18 +18,14 @@ $panelMotions: (
32
18
  'bottom': translateY(100%),
33
19
  );
34
20
 
35
- .drawerRoot:global(.#{$prefixCls}) {
21
+ .drawerRoot:global(.snack-rc-drawer) {
36
22
  pointer-events: none;
37
23
 
38
24
  position: fixed;
39
25
  z-index: initial;
40
26
  inset: 0;
41
27
 
42
- // 🔴 Не обрезаем панель корнем: пока панель едет трансформом, обрезка заставляет браузер
43
- // пересчитывать её в главном потоке каждый кадр — на тяжёлом кадре движение дёргается.
44
- // Корень — `position: fixed`, а фиксированные элементы не участвуют в прокрутке документа,
45
- // поэтому выехавшая за экран панель полос прокрутки не создаёт.
46
- overflow: visible;
28
+ overflow: hidden;
47
29
 
48
30
  transition: width 0s ease 0.3s, height 0s ease 0.3s, transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
49
31
 
@@ -51,12 +33,12 @@ $panelMotions: (
51
33
  transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), opacity 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), box-shadow 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
52
34
  }
53
35
 
54
- &:global(.#{$prefixCls}-open) {
36
+ &:global(.snack-rc-drawer-open) {
55
37
  transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
56
38
  }
57
39
 
58
40
  :global {
59
- .#{$prefixCls}-mask {
41
+ .snack-rc-drawer-mask {
60
42
  pointer-events: auto;
61
43
 
62
44
  position: absolute;
@@ -70,27 +52,27 @@ $panelMotions: (
70
52
 
71
53
  transition: opacity 0.2s;
72
54
 
73
- &.#{$maskMotionName}-enter,
74
- &.#{$maskMotionName}-appear {
55
+ &.maskMotion-enter,
56
+ &.maskMotion-appear {
75
57
  opacity: 0;
76
58
  }
77
59
 
78
- &.#{$maskMotionName}-enter-active,
79
- &.#{$maskMotionName}-appear-active {
60
+ &.maskMotion-enter-active,
61
+ &.maskMotion-appear-active {
80
62
  opacity: 1;
81
63
  }
82
64
 
83
- &.#{$maskMotionName}-leave {
65
+ &.maskMotion-leave {
84
66
  opacity: 1;
85
67
  transition: opacity 0.1s;
86
68
  }
87
69
 
88
- &.#{$maskMotionName}-leave-active {
70
+ &.maskMotion-leave-active {
89
71
  opacity: 0;
90
72
  }
91
73
  }
92
74
 
93
- .#{$prefixCls}-section {
75
+ .snack-rc-drawer-section {
94
76
  pointer-events: auto;
95
77
 
96
78
  position: relative;
@@ -108,10 +90,10 @@ $panelMotions: (
108
90
  box-shadow: base.$sn-boxShadow-elevation-level5;
109
91
  }
110
92
 
111
- .#{$prefixCls}-content-wrapper {
93
+ .snack-rc-drawer-content-wrapper {
112
94
  pointer-events: none;
113
95
  will-change: transform;
114
-
96
+
115
97
  position: absolute;
116
98
  z-index: initial;
117
99
 
@@ -130,7 +112,7 @@ $panelMotions: (
130
112
  &[data-position='top'] {
131
113
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionTop');
132
114
 
133
- .#{$prefixCls}-section {
115
+ .snack-rc-drawer-section {
134
116
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionTop', 'container');
135
117
  }
136
118
  }
@@ -140,7 +122,7 @@ $panelMotions: (
140
122
 
141
123
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionBottom');
142
124
 
143
- .#{$prefixCls}-section {
125
+ .snack-rc-drawer-section {
144
126
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionBottom', 'container');
145
127
  }
146
128
  }
@@ -152,7 +134,7 @@ $panelMotions: (
152
134
 
153
135
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionLeft');
154
136
 
155
- .#{$prefixCls}-section {
137
+ .snack-rc-drawer-section {
156
138
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionLeft', 'container');
157
139
  }
158
140
  }
@@ -164,7 +146,7 @@ $panelMotions: (
164
146
 
165
147
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionRight');
166
148
 
167
- .#{$prefixCls}-section {
149
+ .snack-rc-drawer-section {
168
150
  @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionRight', 'container');
169
151
  }
170
152
  }
@@ -207,32 +189,32 @@ $panelMotions: (
207
189
  }
208
190
 
209
191
  @each $placement, $offscreen in $panelMotions {
210
- &.#{$panelMotionName}-#{$placement}-enter,
211
- &.#{$panelMotionName}-#{$placement}-appear {
192
+ &.panelMotion-#{$placement}-enter,
193
+ &.panelMotion-#{$placement}-appear {
212
194
  transform: $offscreen;
213
195
  }
214
196
 
215
- &.#{$panelMotionName}-#{$placement}-enter-active,
216
- &.#{$panelMotionName}-#{$placement}-appear-active {
197
+ &.panelMotion-#{$placement}-enter-active,
198
+ &.panelMotion-#{$placement}-appear-active {
217
199
  transform: translate(0);
218
200
  }
219
201
 
220
- &.#{$panelMotionName}-#{$placement}-leave {
202
+ &.panelMotion-#{$placement}-leave {
221
203
  transform: translate(0);
222
204
  }
223
205
 
224
- &.#{$panelMotionName}-#{$placement}-leave-active {
206
+ &.panelMotion-#{$placement}-leave-active {
225
207
  transform: $offscreen;
226
208
  transition: transform 0.1s ease-out;
227
209
  }
228
210
  }
229
211
  }
230
212
 
231
- .#{$prefixCls}-content-wrapper-hidden {
213
+ .snack-rc-drawer-content-wrapper-hidden {
232
214
  display: none;
233
215
  }
234
216
 
235
- .#{$prefixCls}-resizable-dragger {
217
+ .snack-rc-drawer-resizable-dragger {
236
218
  pointer-events: auto;
237
219
  cursor: ew-resize;
238
220
 
@@ -266,66 +248,66 @@ $panelMotions: (
266
248
  }
267
249
  }
268
250
 
269
- .#{$prefixCls}-resizable-dragger-left {
251
+ .snack-rc-drawer-resizable-dragger-left {
270
252
  right: $draggerPosition;
271
253
  }
272
254
 
273
- .#{$prefixCls}-resizable-dragger-right {
255
+ .snack-rc-drawer-resizable-dragger-right {
274
256
  left: $draggerPosition;
275
257
  }
276
258
  }
277
259
 
278
- &:global(.#{$prefixCls}-left),
279
- &:global(.#{$prefixCls}-right) {
260
+ &:global(.snack-rc-drawer-left),
261
+ &:global(.snack-rc-drawer-right) {
280
262
  top: 0;
281
263
  width: 0%;
282
264
  height: 100%;
283
265
  }
284
266
 
285
- &:global(.#{$prefixCls}-left) {
267
+ &:global(.snack-rc-drawer-left) {
286
268
  left: 0;
287
269
  }
288
270
 
289
- &:global(.#{$prefixCls}-right) {
271
+ &:global(.snack-rc-drawer-right) {
290
272
  right: 0;
291
273
  }
292
274
 
293
- &:global(.#{$prefixCls}-top),
294
- &:global(.#{$prefixCls}-bottom) {
275
+ &:global(.snack-rc-drawer-top),
276
+ &:global(.snack-rc-drawer-bottom) {
295
277
  left: 0;
296
278
  width: 100%;
297
279
  height: 0%;
298
280
  }
299
281
 
300
- &:global(.#{$prefixCls}-top) {
282
+ &:global(.snack-rc-drawer-top) {
301
283
  top: 0;
302
284
  }
303
285
 
304
- &:global(.#{$prefixCls}-bottom) {
286
+ &:global(.snack-rc-drawer-bottom) {
305
287
  bottom: 0;
306
288
  }
307
289
 
308
- &:has(:global(.#{$prefixCls}-resizable-dragger-dragging)) {
290
+ &:has(:global(.snack-rc-drawer-resizable-dragger-dragging)) {
309
291
  cursor: ew-resize;
310
292
 
311
- :global(.#{$prefixCls}-resizable-dragger) {
293
+ :global(.snack-rc-drawer-resizable-dragger) {
312
294
  height: $activeDraggerHeight;
313
295
  }
314
296
  }
315
297
 
316
298
 
317
- &:global(.#{$prefixCls}-left.#{$prefixCls}-open),
318
- &:global(.#{$prefixCls}-right.#{$prefixCls}-open) {
299
+ &:global(.snack-rc-drawer-left.snack-rc-drawer-open),
300
+ &:global(.snack-rc-drawer-right.snack-rc-drawer-open) {
319
301
  width: 100%;
320
302
  }
321
303
 
322
- &:global(.#{$prefixCls}-top.#{$prefixCls}-open),
323
- &:global(.#{$prefixCls}-bottom.#{$prefixCls}-open) {
304
+ &:global(.snack-rc-drawer-top.snack-rc-drawer-open),
305
+ &:global(.snack-rc-drawer-bottom.snack-rc-drawer-open) {
324
306
  height: 100%;
325
307
  }
326
308
  }
327
309
 
328
- .drawerRoot:global(.#{$prefixCls} .#{$prefixCls}-content-wrapper) {
310
+ .drawerRoot:global(.snack-rc-drawer .snack-rc-drawer-content-wrapper) {
329
311
  @include m.with-material('acrylic', #{drawer});
330
312
  }
331
313
 
package/src/constants.ts CHANGED
@@ -11,8 +11,8 @@ export const TEST_IDS = {
11
11
  footerCancel: 'drawer__footer-cancel',
12
12
  footerAdditional: 'drawer__footer-additional',
13
13
  nestedDrawer: 'drawer__nested',
14
- /** Зарезервирован: rc-drawer отдаёт для маски только `classNames`/`styles`, атрибут навесить нечем. */
15
14
  overlay: 'drawer__overlay',
15
+ contentWrapper: 'drawer__content-wrapper',
16
16
  } as const;
17
17
 
18
18
  export const WIDTH = {
@@ -1,4 +1,3 @@
1
- import { FooterActions } from '@cloud-ru/ds-bottom-sheet';
2
1
  import cn from 'classnames';
3
2
 
4
3
  import { NESTED_DRAWER_PUSH_DISTANCE } from '../../components/Drawer/constants';
@@ -13,6 +12,7 @@ export function DesktopDrawer({
13
12
  media,
14
13
  title,
15
14
  slotAfterTitle,
15
+ slotSecondTitle,
16
16
  subtitle,
17
17
  onBackButtonClick,
18
18
  approveButton,
@@ -21,28 +21,14 @@ export function DesktopDrawer({
21
21
  footerActionsOrientation,
22
22
  footer,
23
23
  nestedDrawer,
24
+ contentRef,
25
+ // `withDividers` — mobile-only (мастер bottomSheet): молча уходит в `rest` и отбрасывается фреймом.
24
26
  className,
25
27
  ...rest
26
28
  }: DrawerProps) {
27
- const showHeader = Boolean(title || subtitle || slotAfterTitle);
28
- // Футер: произвольный `footer` (приоритет) либо сборка из слотов через общий `FooterActions`.
29
- const footerContent =
30
- footer ??
31
- (approveButton || cancelButton || additionalButton ? (
32
- <FooterActions
33
- surface='window'
34
- size='l'
35
- approveButton={approveButton}
36
- cancelButton={cancelButton}
37
- additionalButton={additionalButton}
38
- footerActionsOrientation={footerActionsOrientation}
39
- testIds={{
40
- approve: TEST_IDS.footerApprove,
41
- cancel: TEST_IDS.footerCancel,
42
- additional: TEST_IDS.footerAdditional,
43
- }}
44
- />
45
- ) : null);
29
+ const showHeader = Boolean(title || subtitle || slotAfterTitle || slotSecondTitle);
30
+ // Произвольный `footer` приоритетнее кнопок-слотов; раскладку слотов делает сам `DrawerCustom.Footer`.
31
+ const hasFooter = Boolean(footer || approveButton || cancelButton || additionalButton);
46
32
 
47
33
  return (
48
34
  <DrawerCustom
@@ -53,12 +39,11 @@ export function DesktopDrawer({
53
39
  >
54
40
  {media}
55
41
 
56
- <div className={styles.safeAreaTop} />
57
-
58
42
  {showHeader && (
59
43
  <DrawerCustom.Header
60
44
  title={title}
61
45
  slotAfterTitle={slotAfterTitle}
46
+ slotSecondTitle={slotSecondTitle}
62
47
  subtitle={subtitle}
63
48
  onBackButtonClick={onBackButtonClick}
64
49
  // Сохраняем публичный контракт test-id'ов drawer'а поверх общего `PopupHeader`.
@@ -71,12 +56,18 @@ export function DesktopDrawer({
71
56
  />
72
57
  )}
73
58
 
74
- <DrawerCustom.Body data-test-id={TEST_IDS.body} content={content} />
75
-
76
- <div className={styles.safeAreaBottom} />
59
+ <DrawerCustom.Body data-test-id={TEST_IDS.body} innerRef={contentRef} content={content} />
77
60
 
78
- {footerContent != null && (
79
- <DrawerCustom.Footer data-test-id={TEST_IDS.footer}>{footerContent}</DrawerCustom.Footer>
61
+ {hasFooter && (
62
+ <DrawerCustom.Footer
63
+ data-test-id={TEST_IDS.footer}
64
+ approveButton={footer ? undefined : approveButton}
65
+ cancelButton={footer ? undefined : cancelButton}
66
+ additionalButton={footer ? undefined : additionalButton}
67
+ footerActionsOrientation={footerActionsOrientation}
68
+ >
69
+ {footer}
70
+ </DrawerCustom.Footer>
80
71
  )}
81
72
 
82
73
  {nestedDrawer}
@@ -1,4 +1,7 @@
1
1
  import {
2
+ BottomSheetActionButton,
3
+ FooterActions,
4
+ FooterActionsOrientation,
2
5
  OVERLAY_SURFACE,
3
6
  SheetBody,
4
7
  SheetFooter,
@@ -8,17 +11,85 @@ import {
8
11
  useOverlaySurface,
9
12
  } from '@cloud-ru/ds-bottom-sheet';
10
13
 
14
+ import { TEST_IDS } from '../constants';
11
15
  import { DrawerBody, DrawerBodyProps } from './DrawerBody';
16
+ import styles from './styles.module.scss';
12
17
 
13
18
  // Header и Footer surface-aware реализует сам `PopupHeader`/`PopupFooter` (`SheetHeader`/`SheetFooter`):
14
19
  // он читает `OverlaySurfaceProvider` и рисует window-раскладку на drawer, bottomSheet — на sheet.
15
20
  // Свап остаётся только для Body (desktop-frame vs sheet).
16
21
  export type DialogHeaderProps = SheetHeaderProps;
17
22
  export type DialogBodyProps = DrawerBodyProps;
18
- export type DialogFooterProps = SheetFooterProps;
23
+ export type DialogFooterProps = SheetFooterProps & {
24
+ /** Основная кнопка. Задаёт штатную раскладку футера — вместо `children`. */
25
+ approveButton?: BottomSheetActionButton;
26
+ /** Кнопка отмены. */
27
+ cancelButton?: BottomSheetActionButton;
28
+ /** Дополнительная (третья) кнопка. */
29
+ additionalButton?: BottomSheetActionButton;
30
+ /** Ориентация кнопок при ровно двух действиях. */
31
+ footerActionsOrientation?: FooterActionsOrientation;
32
+ };
19
33
 
20
- export const DialogHeader = SheetHeader;
21
- export const DialogFooter = SheetFooter;
34
+ /**
35
+ * Шапка панели вместе с верхней safe-area.
36
+ *
37
+ * Отступ лежит здесь, а не во фрейме: иначе его теряет ручная композиция из
38
+ * `DrawerCustom.Header/Body/Footer`. На `sheet` не нужен — там свой `padding-top` в токенах.
39
+ */
40
+ export function DialogHeader(props: DialogHeaderProps) {
41
+ const isSheet = useOverlaySurface() === OVERLAY_SURFACE.Sheet;
42
+
43
+ return (
44
+ <>
45
+ {!isSheet && <div className={styles.safeAreaTop} />}
46
+ <SheetHeader {...props} />
47
+ </>
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Футер панели вместе с зазором от `Body` (нижняя safe-area; на `sheet` его даёт сам мастер).
53
+ *
54
+ * Кнопки-слоты раскладывает `FooterActions`. `children` — для нестандартного содержимого:
55
+ * раскладку в этом случае обеспечивает потребитель.
56
+ */
57
+ export function DialogFooter({
58
+ approveButton,
59
+ cancelButton,
60
+ additionalButton,
61
+ footerActionsOrientation,
62
+ children,
63
+ ...rest
64
+ }: DialogFooterProps) {
65
+ const isSheet = useOverlaySurface() === OVERLAY_SURFACE.Sheet;
66
+ const hasActions = Boolean(approveButton || cancelButton || additionalButton);
67
+
68
+ return (
69
+ <>
70
+ {!isSheet && <div className={styles.safeAreaBottom} />}
71
+ <SheetFooter {...rest}>
72
+ {hasActions ? (
73
+ <FooterActions
74
+ surface={isSheet ? 'bottomSheet' : 'window'}
75
+ size='l'
76
+ approveButton={approveButton}
77
+ cancelButton={cancelButton}
78
+ additionalButton={additionalButton}
79
+ footerActionsOrientation={footerActionsOrientation}
80
+ testIds={{
81
+ approve: TEST_IDS.footerApprove,
82
+ cancel: TEST_IDS.footerCancel,
83
+ additional: TEST_IDS.footerAdditional,
84
+ }}
85
+ />
86
+ ) : (
87
+ children
88
+ )}
89
+ </SheetFooter>
90
+ </>
91
+ );
92
+ }
22
93
 
23
94
  export function DialogBody(props: DialogBodyProps) {
24
95
  return useOverlaySurface() === OVERLAY_SURFACE.Sheet ? <SheetBody {...props} /> : <DrawerBody {...props} />;
@@ -1,6 +1,6 @@
1
1
  import { SheetBodyProps, useOverlayBodyHeightAuto } from '@cloud-ru/ds-bottom-sheet';
2
2
  import { Scroll } from '@cloud-ru/ds-scroll';
3
- import { extractSupportProps } from '@cloud-ru/ds-utils';
3
+ import { extractSupportProps, withInnerRefSupport } from '@cloud-ru/ds-utils';
4
4
  import cn from 'classnames';
5
5
 
6
6
  import styles from './styles.module.scss';
@@ -8,11 +8,12 @@ import styles from './styles.module.scss';
8
8
  export type DrawerBodyProps = SheetBodyProps;
9
9
 
10
10
  /** Body дровера (desktop): `Scroll`; высота-по-контенту — из overlay-контекста. */
11
- export function DrawerBody({ content, children, bodyPadding = true, className, ...rest }: DrawerBodyProps) {
11
+ export function DrawerBody({ content, children, bodyPadding = true, innerRef, className, ...rest }: DrawerBodyProps) {
12
12
  const heightAutoVertical = useOverlayBodyHeightAuto();
13
13
 
14
14
  return (
15
15
  <Scroll
16
+ ref={innerRef}
16
17
  size='m'
17
18
  barHideStrategy='never'
18
19
  className={cn(styles.body, heightAutoVertical && styles.bodyHeightAuto, className)}
@@ -23,3 +24,5 @@ export function DrawerBody({ content, children, bodyPadding = true, className, .
23
24
  </Scroll>
24
25
  );
25
26
  }
27
+
28
+ withInnerRefSupport(DrawerBody);
@@ -14,6 +14,7 @@ export function MobileDrawer({
14
14
  media,
15
15
  title,
16
16
  slotAfterTitle,
17
+ slotSecondTitle,
17
18
  subtitle,
18
19
  onBackButtonClick,
19
20
  approveButton,
@@ -33,19 +34,21 @@ export function MobileDrawer({
33
34
  snapIndex,
34
35
  onSnapIndexChange,
35
36
  safeArea,
37
+ withDividers = false,
36
38
  ...rest
37
39
  }: DrawerProps) {
38
40
  // Футер собирает сам `BottomSheet`.
39
41
  return (
40
42
  <BottomSheet
41
- // Дефолт `withDividers` сменился на `true` — здесь вид сохраняем прежним.
42
- withDividers={false}
43
+ // Дефолт `withDividers` в sheet'е — `true`; у Drawer'а вид исторически без разделителей.
44
+ withDividers={withDividers}
43
45
  open={open}
44
46
  onClose={onClose}
45
47
  showBackdrop={showBlackout}
46
48
  title={title}
47
49
  subtitle={subtitle}
48
50
  slotAfterTitle={slotAfterTitle}
51
+ slotSecondTitle={slotSecondTitle}
49
52
  onBackButtonClick={onBackButtonClick}
50
53
  media={media}
51
54
  content={content}
@@ -0,0 +1,22 @@
1
+ @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
+ @use '@cloud-ru/ds-figma-variables/build/scss/components/drawer.module' as drawer;
3
+
4
+ // Safe-area окна описана в слотах, а не во фрейме: иначе её теряет ручная композиция из
5
+ // `DrawerCustom.Header/Body/Footer`, и заголовок прилипает к верхней кромке панели.
6
+ .safeAreaTop {
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: flex-start;
10
+ align-self: stretch;
11
+
12
+ padding-top: base.simple-var(drawer.$drawer, 'anatomy', 'container', 'safeArea', 'padding-top');
13
+ }
14
+
15
+ .safeAreaBottom {
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: flex-start;
19
+ align-self: stretch;
20
+
21
+ padding-bottom: base.simple-var(drawer.$drawer, 'anatomy', 'container', 'safeArea', 'padding-bottom');
22
+ }