@cloud-ru/ds-drawer 1.0.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 (140) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +480 -0
  4. package/dist/cjs/components/Drawer/Drawer.d.ts +6 -0
  5. package/dist/cjs/components/Drawer/Drawer.js +15 -0
  6. package/dist/cjs/components/Drawer/constants.d.ts +1 -0
  7. package/dist/cjs/components/Drawer/constants.js +4 -0
  8. package/dist/cjs/components/Drawer/index.d.ts +2 -0
  9. package/dist/cjs/components/Drawer/index.js +18 -0
  10. package/dist/cjs/components/Drawer/styles.module.css +19 -0
  11. package/dist/cjs/components/Drawer/types.d.ts +35 -0
  12. package/dist/cjs/components/Drawer/types.js +2 -0
  13. package/dist/cjs/components/DrawerCustom/DrawerCustom.d.ts +15 -0
  14. package/dist/cjs/components/DrawerCustom/DrawerCustom.js +76 -0
  15. package/dist/cjs/components/DrawerCustom/constants.d.ts +4 -0
  16. package/dist/cjs/components/DrawerCustom/constants.js +17 -0
  17. package/dist/cjs/components/DrawerCustom/hooks/index.d.ts +1 -0
  18. package/dist/cjs/components/DrawerCustom/hooks/index.js +17 -0
  19. package/dist/cjs/components/DrawerCustom/hooks/useDrawerFocusTrap.d.ts +9 -0
  20. package/dist/cjs/components/DrawerCustom/hooks/useDrawerFocusTrap.js +83 -0
  21. package/dist/cjs/components/DrawerCustom/hooks/useDrawerResize.d.ts +15 -0
  22. package/dist/cjs/components/DrawerCustom/hooks/useDrawerResize.js +50 -0
  23. package/dist/cjs/components/DrawerCustom/index.d.ts +2 -0
  24. package/dist/cjs/components/DrawerCustom/index.js +17 -0
  25. package/dist/cjs/components/DrawerCustom/motion.css +93 -0
  26. package/dist/cjs/components/DrawerCustom/styles.module.css +491 -0
  27. package/dist/cjs/components/DrawerCustom/types.d.ts +71 -0
  28. package/dist/cjs/components/DrawerCustom/types.js +2 -0
  29. package/dist/cjs/components/index.d.ts +2 -0
  30. package/dist/cjs/components/index.js +18 -0
  31. package/dist/cjs/constants.d.ts +28 -0
  32. package/dist/cjs/constants.js +31 -0
  33. package/dist/cjs/helperComponents/DesktopDrawer/DesktopDrawer.d.ts +3 -0
  34. package/dist/cjs/helperComponents/DesktopDrawer/DesktopDrawer.js +32 -0
  35. package/dist/cjs/helperComponents/DesktopDrawer/index.d.ts +1 -0
  36. package/dist/cjs/helperComponents/DesktopDrawer/index.js +17 -0
  37. package/dist/cjs/helperComponents/DialogSlots.d.ts +8 -0
  38. package/dist/cjs/helperComponents/DialogSlots.js +12 -0
  39. package/dist/cjs/helperComponents/DrawerBody/DrawerBody.d.ts +4 -0
  40. package/dist/cjs/helperComponents/DrawerBody/DrawerBody.js +17 -0
  41. package/dist/cjs/helperComponents/DrawerBody/index.d.ts +1 -0
  42. package/dist/cjs/helperComponents/DrawerBody/index.js +17 -0
  43. package/dist/cjs/helperComponents/DrawerBody/styles.module.css +30 -0
  44. package/dist/cjs/helperComponents/MobileDrawer/MobileDrawer.d.ts +7 -0
  45. package/dist/cjs/helperComponents/MobileDrawer/MobileDrawer.js +26 -0
  46. package/dist/cjs/helperComponents/MobileDrawer/index.d.ts +1 -0
  47. package/dist/cjs/helperComponents/MobileDrawer/index.js +17 -0
  48. package/dist/cjs/helperComponents/index.d.ts +8 -0
  49. package/dist/cjs/helperComponents/index.js +30 -0
  50. package/dist/cjs/index.d.ts +5 -0
  51. package/dist/cjs/index.js +23 -0
  52. package/dist/cjs/package.json +3 -0
  53. package/dist/cjs/style.css +633 -0
  54. package/dist/cjs/types.d.ts +4 -0
  55. package/dist/cjs/types.js +2 -0
  56. package/dist/cjs/utils/interopDefault.d.ts +7 -0
  57. package/dist/cjs/utils/interopDefault.js +10 -0
  58. package/dist/esm/components/Drawer/Drawer.d.ts +6 -0
  59. package/dist/esm/components/Drawer/Drawer.js +12 -0
  60. package/dist/esm/components/Drawer/constants.d.ts +1 -0
  61. package/dist/esm/components/Drawer/constants.js +1 -0
  62. package/dist/esm/components/Drawer/index.d.ts +2 -0
  63. package/dist/esm/components/Drawer/index.js +2 -0
  64. package/dist/esm/components/Drawer/styles.module.css +19 -0
  65. package/dist/esm/components/Drawer/types.d.ts +35 -0
  66. package/dist/esm/components/Drawer/types.js +1 -0
  67. package/dist/esm/components/DrawerCustom/DrawerCustom.d.ts +15 -0
  68. package/dist/esm/components/DrawerCustom/DrawerCustom.js +70 -0
  69. package/dist/esm/components/DrawerCustom/constants.d.ts +4 -0
  70. package/dist/esm/components/DrawerCustom/constants.js +13 -0
  71. package/dist/esm/components/DrawerCustom/hooks/index.d.ts +1 -0
  72. package/dist/esm/components/DrawerCustom/hooks/index.js +1 -0
  73. package/dist/esm/components/DrawerCustom/hooks/useDrawerFocusTrap.d.ts +9 -0
  74. package/dist/esm/components/DrawerCustom/hooks/useDrawerFocusTrap.js +80 -0
  75. package/dist/esm/components/DrawerCustom/hooks/useDrawerResize.d.ts +15 -0
  76. package/dist/esm/components/DrawerCustom/hooks/useDrawerResize.js +47 -0
  77. package/dist/esm/components/DrawerCustom/index.d.ts +2 -0
  78. package/dist/esm/components/DrawerCustom/index.js +1 -0
  79. package/dist/esm/components/DrawerCustom/motion.css +93 -0
  80. package/dist/esm/components/DrawerCustom/styles.module.css +491 -0
  81. package/dist/esm/components/DrawerCustom/types.d.ts +71 -0
  82. package/dist/esm/components/DrawerCustom/types.js +1 -0
  83. package/dist/esm/components/index.d.ts +2 -0
  84. package/dist/esm/components/index.js +2 -0
  85. package/dist/esm/constants.d.ts +28 -0
  86. package/dist/esm/constants.js +28 -0
  87. package/dist/esm/helperComponents/DesktopDrawer/DesktopDrawer.d.ts +3 -0
  88. package/dist/esm/helperComponents/DesktopDrawer/DesktopDrawer.js +26 -0
  89. package/dist/esm/helperComponents/DesktopDrawer/index.d.ts +1 -0
  90. package/dist/esm/helperComponents/DesktopDrawer/index.js +1 -0
  91. package/dist/esm/helperComponents/DialogSlots.d.ts +8 -0
  92. package/dist/esm/helperComponents/DialogSlots.js +8 -0
  93. package/dist/esm/helperComponents/DrawerBody/DrawerBody.d.ts +4 -0
  94. package/dist/esm/helperComponents/DrawerBody/DrawerBody.js +11 -0
  95. package/dist/esm/helperComponents/DrawerBody/index.d.ts +1 -0
  96. package/dist/esm/helperComponents/DrawerBody/index.js +1 -0
  97. package/dist/esm/helperComponents/DrawerBody/styles.module.css +30 -0
  98. package/dist/esm/helperComponents/MobileDrawer/MobileDrawer.d.ts +7 -0
  99. package/dist/esm/helperComponents/MobileDrawer/MobileDrawer.js +23 -0
  100. package/dist/esm/helperComponents/MobileDrawer/index.d.ts +1 -0
  101. package/dist/esm/helperComponents/MobileDrawer/index.js +1 -0
  102. package/dist/esm/helperComponents/index.d.ts +8 -0
  103. package/dist/esm/helperComponents/index.js +8 -0
  104. package/dist/esm/index.d.ts +5 -0
  105. package/dist/esm/index.js +5 -0
  106. package/dist/esm/style.css +633 -0
  107. package/dist/esm/types.d.ts +4 -0
  108. package/dist/esm/types.js +1 -0
  109. package/dist/esm/utils/interopDefault.d.ts +7 -0
  110. package/dist/esm/utils/interopDefault.js +7 -0
  111. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  112. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  113. package/package.json +51 -0
  114. package/src/components/Drawer/Drawer.tsx +15 -0
  115. package/src/components/Drawer/constants.ts +1 -0
  116. package/src/components/Drawer/index.ts +2 -0
  117. package/src/components/Drawer/styles.module.scss +26 -0
  118. package/src/components/Drawer/types.ts +42 -0
  119. package/src/components/DrawerCustom/DrawerCustom.tsx +197 -0
  120. package/src/components/DrawerCustom/constants.ts +17 -0
  121. package/src/components/DrawerCustom/hooks/index.ts +1 -0
  122. package/src/components/DrawerCustom/hooks/useDrawerFocusTrap.ts +97 -0
  123. package/src/components/DrawerCustom/hooks/useDrawerResize.tsx +78 -0
  124. package/src/components/DrawerCustom/index.ts +2 -0
  125. package/src/components/DrawerCustom/styles.module.scss +330 -0
  126. package/src/components/DrawerCustom/types.ts +77 -0
  127. package/src/components/index.ts +2 -0
  128. package/src/constants.ts +31 -0
  129. package/src/helperComponents/DesktopDrawer/DesktopDrawer.tsx +85 -0
  130. package/src/helperComponents/DesktopDrawer/index.ts +1 -0
  131. package/src/helperComponents/DialogSlots.tsx +25 -0
  132. package/src/helperComponents/DrawerBody/DrawerBody.tsx +25 -0
  133. package/src/helperComponents/DrawerBody/index.ts +1 -0
  134. package/src/helperComponents/DrawerBody/styles.module.scss +32 -0
  135. package/src/helperComponents/MobileDrawer/MobileDrawer.tsx +75 -0
  136. package/src/helperComponents/MobileDrawer/index.ts +1 -0
  137. package/src/helperComponents/index.ts +11 -0
  138. package/src/index.ts +6 -0
  139. package/src/types.ts +6 -0
  140. package/src/utils/interopDefault.ts +7 -0
@@ -0,0 +1,330 @@
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
+ // Отступ close-кнопки — общий токен «window»-поверхности из коллекции popupPrivate
4
+ // (она несёт и padding ×4, и min-size/border-radius кнопки).
5
+ @use '@cloud-ru/ds-figma-variables/build/scss/components/popupPrivate.module' as popupPrivate;
6
+ @use '@cloud-ru/ds-materials' as m;
7
+
8
+ $widthSizes: 's', 'm', 'l';
9
+ $draggerWidth: base.$sn-primitive-dimension-4;
10
+ $draggerPosition: calc(drawer.$sn-drawer-anatomy-container-vertical-positionLeft-paddingRight - $draggerWidth - base.$sn-primitive-dimension-6);
11
+ $activeDraggerHeight: calc(base.$sn-primitive-dimension-32 + base.$sn-primitive-dimension-24);
12
+
13
+ // placement → offscreen transform для panelMotion-*
14
+ $panelMotions: (
15
+ 'left': translateX(-100%),
16
+ 'right': translateX(100%),
17
+ 'top': translateY(-100%),
18
+ 'bottom': translateY(100%),
19
+ );
20
+
21
+ .drawerRoot:global(.snack-rc-drawer) {
22
+ pointer-events: none;
23
+
24
+ position: fixed;
25
+ z-index: initial;
26
+ inset: 0;
27
+
28
+ overflow: hidden;
29
+
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);
31
+
32
+ &:global(> *) {
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);
34
+ }
35
+
36
+ &:global(.snack-rc-drawer-open) {
37
+ transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
38
+ }
39
+
40
+ :global {
41
+ .snack-rc-drawer-mask {
42
+ pointer-events: auto;
43
+
44
+ position: absolute;
45
+ z-index: initial;
46
+ inset: 0;
47
+
48
+ opacity: 1;
49
+ background-color: base.$sn-theme-color-blackout;
50
+
51
+ transition: opacity 0.2s;
52
+
53
+ &.maskMotion-enter,
54
+ &.maskMotion-appear {
55
+ opacity: 0;
56
+ }
57
+
58
+ &.maskMotion-enter-active,
59
+ &.maskMotion-appear-active {
60
+ opacity: 1;
61
+ }
62
+
63
+ &.maskMotion-leave {
64
+ opacity: 1;
65
+ transition: opacity 0.1s;
66
+ }
67
+
68
+ &.maskMotion-leave-active {
69
+ opacity: 0;
70
+ }
71
+ }
72
+
73
+ .snack-rc-drawer-section {
74
+ pointer-events: auto;
75
+
76
+ position: relative;
77
+
78
+ display: flex;
79
+ flex-direction: column;
80
+ flex-grow: 1;
81
+
82
+ box-sizing: border-box;
83
+ width: 100%;
84
+ // Тень панели из макета (`elevation-level5`). Раньше правило висело на `&:global(...)`,
85
+ // то есть требовало оба класса на одном узле, и не срабатывало никогда.
86
+ box-shadow: base.$sn-boxShadow-elevation-level5;
87
+ }
88
+
89
+ .snack-rc-drawer-content-wrapper {
90
+ pointer-events: none;
91
+ will-change: transform;
92
+
93
+ position: absolute;
94
+ z-index: initial;
95
+
96
+ overflow: unset;
97
+ display: flex;
98
+ flex-direction: column;
99
+ align-items: flex-start;
100
+
101
+ max-width: 100%;
102
+
103
+ background: none;
104
+ box-shadow: none;
105
+
106
+ transition: transform 0.2s ease-in-out;
107
+
108
+ &[data-position='top'] {
109
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionTop');
110
+
111
+ .snack-rc-drawer-section {
112
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionTop', 'container');
113
+ }
114
+ }
115
+
116
+ &[data-position='bottom'] {
117
+ bottom: 0;
118
+
119
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionBottom');
120
+
121
+ .snack-rc-drawer-section {
122
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'horizontal', 'positionBottom', 'container');
123
+ }
124
+ }
125
+
126
+ &[data-position='left'] {
127
+ top: 0;
128
+ bottom: 0;
129
+ left: 0;
130
+
131
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionLeft');
132
+
133
+ .snack-rc-drawer-section {
134
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionLeft', 'container');
135
+ }
136
+ }
137
+
138
+ &[data-position='right'] {
139
+ top: 0;
140
+ right: 0;
141
+ bottom: 0;
142
+
143
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionRight');
144
+
145
+ .snack-rc-drawer-section {
146
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'positionRight', 'container');
147
+ }
148
+ }
149
+
150
+ &[data-position='left'],
151
+ &[data-position='right'] {
152
+ align-items: stretch;
153
+ // Ширина в макете — это ширина самой панели; горизонтальный паддинг обёртки
154
+ // (зона перетаскивания) лежит снаружи и не должен её сужать.
155
+ box-sizing: content-box;
156
+ height: 100%;
157
+
158
+ @each $width in $widthSizes {
159
+ &[data-width='#{$width}'] {
160
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'width', 'container', $width);
161
+
162
+ width: min(100%, base.simple-var(drawer.$drawer, 'anatomy', 'container', 'vertical', 'width', 'container', $width, 'max-width'));
163
+ }
164
+ }
165
+ }
166
+
167
+ &[data-position='top'],
168
+ &[data-position='bottom'] {
169
+ width: 100%;
170
+ height: 100%;
171
+ }
172
+
173
+ &[data-position='top'][data-height-auto],
174
+ &[data-position='bottom'][data-height-auto] {
175
+ height: auto;
176
+ max-height: 100%;
177
+ }
178
+
179
+ &[data-position='top'][data-height-auto] {
180
+ inset: 0 0 auto;
181
+ }
182
+
183
+ &[data-position='bottom'][data-height-auto] {
184
+ inset: auto 0 0;
185
+ }
186
+
187
+ @each $placement, $offscreen in $panelMotions {
188
+ &.panelMotion-#{$placement}-enter,
189
+ &.panelMotion-#{$placement}-appear {
190
+ transform: $offscreen;
191
+ }
192
+
193
+ &.panelMotion-#{$placement}-enter-active,
194
+ &.panelMotion-#{$placement}-appear-active {
195
+ transform: translate(0);
196
+ }
197
+
198
+ &.panelMotion-#{$placement}-leave {
199
+ transform: translate(0);
200
+ }
201
+
202
+ &.panelMotion-#{$placement}-leave-active {
203
+ transform: $offscreen;
204
+ transition: transform 0.1s ease-out;
205
+ }
206
+ }
207
+ }
208
+
209
+ .snack-rc-drawer-content-wrapper-hidden {
210
+ display: none;
211
+ }
212
+
213
+ .snack-rc-drawer-resizable-dragger {
214
+ pointer-events: auto;
215
+ cursor: ew-resize;
216
+
217
+ position: absolute;
218
+ z-index: 2;
219
+ top: 50%;
220
+ transform: translateY(-50%);
221
+
222
+ width: $draggerWidth;
223
+ height: base.$sn-primitive-dimension-32;
224
+ border-radius: base.$sn-density-radius-2xs;
225
+
226
+ background-color: base.$sn-theme-color-neutral-decor;
227
+
228
+ transition: height ease-in-out 0.2s;
229
+
230
+ &::after {
231
+ content: '';
232
+
233
+ position: absolute;
234
+ top: 50%;
235
+ left: 50%;
236
+ transform: translate(-50%, -50%);
237
+
238
+ width: calc(100% + base.$sn-primitive-dimension-12);
239
+ height: 100vh;
240
+ }
241
+
242
+ &:hover {
243
+ height: $activeDraggerHeight;
244
+ }
245
+ }
246
+
247
+ .snack-rc-drawer-resizable-dragger-left {
248
+ right: $draggerPosition;
249
+ }
250
+
251
+ .snack-rc-drawer-resizable-dragger-right {
252
+ left: $draggerPosition;
253
+ }
254
+ }
255
+
256
+ &:global(.snack-rc-drawer-left),
257
+ &:global(.snack-rc-drawer-right) {
258
+ top: 0;
259
+ width: 0%;
260
+ height: 100%;
261
+ }
262
+
263
+ &:global(.snack-rc-drawer-left) {
264
+ left: 0;
265
+ }
266
+
267
+ &:global(.snack-rc-drawer-right) {
268
+ right: 0;
269
+ }
270
+
271
+ &:global(.snack-rc-drawer-top),
272
+ &:global(.snack-rc-drawer-bottom) {
273
+ left: 0;
274
+ width: 100%;
275
+ height: 0%;
276
+ }
277
+
278
+ &:global(.snack-rc-drawer-top) {
279
+ top: 0;
280
+ }
281
+
282
+ &:global(.snack-rc-drawer-bottom) {
283
+ bottom: 0;
284
+ }
285
+
286
+ &:has(:global(.snack-rc-drawer-resizable-dragger-dragging)) {
287
+ cursor: ew-resize;
288
+
289
+ :global(.snack-rc-drawer-resizable-dragger) {
290
+ height: $activeDraggerHeight;
291
+ }
292
+ }
293
+
294
+
295
+ &:global(.snack-rc-drawer-left.snack-rc-drawer-open),
296
+ &:global(.snack-rc-drawer-right.snack-rc-drawer-open) {
297
+ width: 100%;
298
+ }
299
+
300
+ &:global(.snack-rc-drawer-top.snack-rc-drawer-open),
301
+ &:global(.snack-rc-drawer-bottom.snack-rc-drawer-open) {
302
+ height: 100%;
303
+ }
304
+ }
305
+
306
+ .drawerRoot:global(.snack-rc-drawer .snack-rc-drawer-content-wrapper) {
307
+ @include m.with-material('acrylic', #{drawer});
308
+ }
309
+
310
+ .focusScope {
311
+ display: contents;
312
+ }
313
+
314
+ .showButtonClosed {
315
+ margin: base.simple-var(popupPrivate.$popupPrivate, 'anatomy', 'window', 'badgeButtonClosed', 'padding-top') base.simple-var(popupPrivate.$popupPrivate, 'anatomy', 'window', 'badgeButtonClosed', 'padding-right') base.simple-var(popupPrivate.$popupPrivate, 'anatomy', 'window', 'badgeButtonClosed', 'padding-bottom') base.simple-var(popupPrivate.$popupPrivate, 'anatomy', 'window', 'badgeButtonClosed', 'padding-left');
316
+ }
317
+
318
+ .showButtonClosedWrapper {
319
+ position: absolute;
320
+ top: 0;
321
+ right: 0;
322
+
323
+ display: flex;
324
+ flex-direction: column;
325
+ align-items: center;
326
+ justify-content: center;
327
+
328
+ width: 0;
329
+ height: 0;
330
+ }
@@ -0,0 +1,77 @@
1
+ import { BottomSheetCustomProps } from '@cloud-ru/ds-bottom-sheet';
2
+ import { WithSupportProps } from '@cloud-ru/ds-utils';
3
+ import { DrawerProps as RcDrawerProps } from '@rc-component/drawer';
4
+ import { PropsWithChildren, ReactElement } from 'react';
5
+
6
+ import { Position, Width } from '../../types';
7
+
8
+ export type DrawerCustomProps = WithSupportProps<
9
+ PropsWithChildren<{
10
+ /** Управление состоянием показан/не показан. */
11
+ open: boolean;
12
+ /** Колбэк закрытия */
13
+ onClose(): void;
14
+ /**
15
+ * Отображение темной подложки
16
+ * @default true
17
+ */
18
+ showBlackout?: boolean;
19
+ /**
20
+ * Отображение кнопки закрытия в шапке дровера
21
+ * @default true
22
+ */
23
+ showButtonClosed?: boolean;
24
+ /**
25
+ * Расположение
26
+ */
27
+ position: Position;
28
+ /**
29
+ * Ширина (только при position: "left" | "right")
30
+ * @default 's'
31
+ */
32
+ width?: Width | string | number;
33
+ /**
34
+ * Высота панели по контенту (только при `position: "top" | "bottom"`).
35
+ * @default false
36
+ */
37
+ heightAuto?: boolean;
38
+ /** CSS-класс для элемента с контентом */
39
+ className?: string;
40
+ /** CSS-класс для корневого элемента */
41
+ rootClassName?: string;
42
+ /** Смещение при открытии "вложенного" компонента */
43
+ push?: RcDrawerProps['push'];
44
+ /** Контейнер в котором будет рендерится Drawer. По-умолчанию - body */
45
+ container?: string | HTMLElement;
46
+ /** Вложенный Drawer */
47
+ nestedDrawer?: ReactElement<Omit<DrawerCustomProps, 'resizable'>>;
48
+ /** Закрывать дровер при перемещении по истории браузера */
49
+ closeOnPopstate?: boolean;
50
+ /** Футер */
51
+ footer?: ReactElement;
52
+ /** Отключить анимации
53
+ * @default false
54
+ */
55
+ disableMotions?: boolean;
56
+ /**
57
+ * Ширина (только при position: "left" | "right")
58
+ * @default 's'
59
+ */
60
+ resizable?: {
61
+ /** Минимальная доступная ширина */
62
+ min: number;
63
+ /** Максимальная доступная ширина */
64
+ max?: number;
65
+ /** Ширина по умолчанию */
66
+ default?: number;
67
+ /** Колбэк на изменение размера */
68
+ onResize?: (width: number) => void;
69
+ /** Колбэк на окончание изменения размера */
70
+ onResizeEnd?: (width: number) => void;
71
+ /** Tooltip для элемента dragger */
72
+ draggerTooltip?: string;
73
+ };
74
+ }>
75
+ > &
76
+ // Только mobile: управляют sheet-поверхностью (на desktop игнорируются).
77
+ Pick<BottomSheetCustomProps, 'snapPoints' | 'swipeEnabled' | 'safeArea'>;
@@ -0,0 +1,2 @@
1
+ export * from './Drawer';
2
+ export * from './DrawerCustom';
@@ -0,0 +1,31 @@
1
+ export const TEST_IDS = {
2
+ closeButton: 'drawer__close-button',
3
+ header: 'drawer__header',
4
+ title: 'drawer__title',
5
+ image: 'drawer__image',
6
+ tooltip: 'drawer__title-tooltip',
7
+ subtitle: 'drawer__subtitle',
8
+ body: 'drawer__body',
9
+ footer: 'drawer__footer',
10
+ footerApprove: 'drawer__footer-approve',
11
+ footerCancel: 'drawer__footer-cancel',
12
+ footerAdditional: 'drawer__footer-additional',
13
+ nestedDrawer: 'drawer__nested',
14
+ overlay: 'drawer__overlay',
15
+ contentWrapper: 'drawer__content-wrapper',
16
+ } as const;
17
+
18
+ export const WIDTH = {
19
+ S: 's',
20
+ M: 'm',
21
+ L: 'l',
22
+ } as const;
23
+
24
+ export const WIDTH_AS_VALUES: string[] = Object.values(WIDTH);
25
+
26
+ export const POSITION = {
27
+ Left: 'left',
28
+ Right: 'right',
29
+ Top: 'top',
30
+ Bottom: 'bottom',
31
+ } as const;
@@ -0,0 +1,85 @@
1
+ import { FooterActions } from '@cloud-ru/ds-bottom-sheet';
2
+ import cn from 'classnames';
3
+
4
+ import { NESTED_DRAWER_PUSH_DISTANCE } from '../../components/Drawer/constants';
5
+ import styles from '../../components/Drawer/styles.module.scss';
6
+ import { DrawerProps } from '../../components/Drawer/types';
7
+ import { DrawerCustom } from '../../components/DrawerCustom';
8
+ import { TEST_IDS } from '../../constants';
9
+
10
+ /** Desktop-поверхность Drawer'а: панель (`DrawerCustom`) с пресетной разметкой. Internal. */
11
+ export function DesktopDrawer({
12
+ content,
13
+ media,
14
+ title,
15
+ slotAfterTitle,
16
+ subtitle,
17
+ onBackButtonClick,
18
+ approveButton,
19
+ cancelButton,
20
+ additionalButton,
21
+ footerActionsOrientation,
22
+ footer,
23
+ nestedDrawer,
24
+ className,
25
+ ...rest
26
+ }: 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);
46
+
47
+ return (
48
+ <DrawerCustom
49
+ {...rest}
50
+ resizable={undefined}
51
+ className={cn(styles.drawer, className)}
52
+ push={Boolean(nestedDrawer) && { distance: NESTED_DRAWER_PUSH_DISTANCE }}
53
+ >
54
+ {media}
55
+
56
+ <div className={styles.safeAreaTop} />
57
+
58
+ {showHeader && (
59
+ <DrawerCustom.Header
60
+ title={title}
61
+ slotAfterTitle={slotAfterTitle}
62
+ subtitle={subtitle}
63
+ onBackButtonClick={onBackButtonClick}
64
+ // Сохраняем публичный контракт test-id'ов drawer'а поверх общего `PopupHeader`.
65
+ // slotAfterTitle НЕ переопределяем: id тултипа ставит сам потребитель на своём span.
66
+ testIds={{
67
+ header: TEST_IDS.header,
68
+ title: TEST_IDS.title,
69
+ subtitle: TEST_IDS.subtitle,
70
+ }}
71
+ />
72
+ )}
73
+
74
+ <DrawerCustom.Body data-test-id={TEST_IDS.body} content={content} />
75
+
76
+ <div className={styles.safeAreaBottom} />
77
+
78
+ {footerContent != null && (
79
+ <DrawerCustom.Footer data-test-id={TEST_IDS.footer}>{footerContent}</DrawerCustom.Footer>
80
+ )}
81
+
82
+ {nestedDrawer}
83
+ </DrawerCustom>
84
+ );
85
+ }
@@ -0,0 +1 @@
1
+ export * from './DesktopDrawer';
@@ -0,0 +1,25 @@
1
+ import {
2
+ OVERLAY_SURFACE,
3
+ SheetBody,
4
+ SheetFooter,
5
+ SheetFooterProps,
6
+ SheetHeader,
7
+ SheetHeaderProps,
8
+ useOverlaySurface,
9
+ } from '@cloud-ru/ds-bottom-sheet';
10
+
11
+ import { DrawerBody, DrawerBodyProps } from './DrawerBody';
12
+
13
+ // Header и Footer surface-aware реализует сам `PopupHeader`/`PopupFooter` (`SheetHeader`/`SheetFooter`):
14
+ // он читает `OverlaySurfaceProvider` и рисует window-раскладку на drawer, bottomSheet — на sheet.
15
+ // Свап остаётся только для Body (desktop-frame vs sheet).
16
+ export type DialogHeaderProps = SheetHeaderProps;
17
+ export type DialogBodyProps = DrawerBodyProps;
18
+ export type DialogFooterProps = SheetFooterProps;
19
+
20
+ export const DialogHeader = SheetHeader;
21
+ export const DialogFooter = SheetFooter;
22
+
23
+ export function DialogBody(props: DialogBodyProps) {
24
+ return useOverlaySurface() === OVERLAY_SURFACE.Sheet ? <SheetBody {...props} /> : <DrawerBody {...props} />;
25
+ }
@@ -0,0 +1,25 @@
1
+ import { SheetBodyProps, useOverlayBodyHeightAuto } from '@cloud-ru/ds-bottom-sheet';
2
+ import { Scroll } from '@cloud-ru/ds-scroll';
3
+ import { extractSupportProps } from '@cloud-ru/ds-utils';
4
+ import cn from 'classnames';
5
+
6
+ import styles from './styles.module.scss';
7
+
8
+ export type DrawerBodyProps = SheetBodyProps;
9
+
10
+ /** Body дровера (desktop): `Scroll`; высота-по-контенту — из overlay-контекста. */
11
+ export function DrawerBody({ content, children, bodyPadding = true, className, ...rest }: DrawerBodyProps) {
12
+ const heightAutoVertical = useOverlayBodyHeightAuto();
13
+
14
+ return (
15
+ <Scroll
16
+ size='m'
17
+ barHideStrategy='never'
18
+ className={cn(styles.body, heightAutoVertical && styles.bodyHeightAuto, className)}
19
+ data-no-padding={bodyPadding === false || undefined}
20
+ {...extractSupportProps(rest)}
21
+ >
22
+ {children ?? content}
23
+ </Scroll>
24
+ );
25
+ }
@@ -0,0 +1 @@
1
+ export * from './DrawerBody';
@@ -0,0 +1,32 @@
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
+ // Ветка surface='drawer' (1:1 из `@cloud-ru/ds-drawer` Body).
5
+ .body {
6
+ @include base.composite-var(drawer.$drawer, 'anatomy', 'container', 'bodyWrapper');
7
+
8
+ display: flex;
9
+ flex: 1 0 0;
10
+ flex-direction: column;
11
+ align-items: flex-start;
12
+ align-self: stretch;
13
+
14
+ box-sizing: border-box;
15
+
16
+ color: base.$sn-theme-color-available-version-textMain;
17
+
18
+ @include base.composite-var(base.$sn-regular-body-m);
19
+ }
20
+
21
+ .body[data-no-padding] {
22
+ padding-right: 0;
23
+ padding-left: 0;
24
+ }
25
+
26
+ /* heightAuto + top/bottom: высота по контенту, при переполнении — flex-скролл. */
27
+ .bodyHeightAuto {
28
+ flex: 0 1 auto;
29
+ height: auto !important; /* stylelint-disable-line declaration-no-important */
30
+ min-height: 0;
31
+ max-height: 100%;
32
+ }
@@ -0,0 +1,75 @@
1
+ import { BottomSheet } from '@cloud-ru/ds-bottom-sheet';
2
+ import { extractSupportProps } from '@cloud-ru/ds-utils';
3
+
4
+ import { DrawerProps } from '../../components/Drawer/types';
5
+ import { TEST_IDS } from '../../constants';
6
+
7
+ /**
8
+ * Mobile-поверхность Drawer'а: контент в `BottomSheet`, слоты маппятся на API sheet'а
9
+ * (`subtitle` 1:1, `showBlackout→showBackdrop`, остальное 1:1). Internal. Desktop-only пропы
10
+ * уходят в `...rest`.
11
+ */
12
+ export function MobileDrawer({
13
+ content,
14
+ media,
15
+ title,
16
+ slotAfterTitle,
17
+ subtitle,
18
+ onBackButtonClick,
19
+ approveButton,
20
+ cancelButton,
21
+ additionalButton,
22
+ footerActionsOrientation,
23
+ footer,
24
+ className,
25
+ rootClassName,
26
+ open,
27
+ onClose,
28
+ showBlackout,
29
+ container,
30
+ closeOnPopstate,
31
+ swipeEnabled,
32
+ snapPoints,
33
+ snapIndex,
34
+ onSnapIndexChange,
35
+ safeArea,
36
+ ...rest
37
+ }: DrawerProps) {
38
+ // Футер собирает сам `BottomSheet`.
39
+ return (
40
+ <BottomSheet
41
+ // Дефолт `withDividers` сменился на `true` — здесь вид сохраняем прежним.
42
+ withDividers={false}
43
+ open={open}
44
+ onClose={onClose}
45
+ showBackdrop={showBlackout}
46
+ title={title}
47
+ subtitle={subtitle}
48
+ slotAfterTitle={slotAfterTitle}
49
+ onBackButtonClick={onBackButtonClick}
50
+ media={media}
51
+ content={content}
52
+ approveButton={approveButton}
53
+ cancelButton={cancelButton}
54
+ additionalButton={additionalButton}
55
+ footerActionsOrientation={footerActionsOrientation}
56
+ footer={footer}
57
+ // Те же id футера, что у desktop-поверхности — стабильный селектор при свапе surface.
58
+ footerTestIds={{
59
+ approve: TEST_IDS.footerApprove,
60
+ cancel: TEST_IDS.footerCancel,
61
+ additional: TEST_IDS.footerAdditional,
62
+ }}
63
+ className={className}
64
+ rootClassName={rootClassName}
65
+ container={container}
66
+ closeOnPopstate={closeOnPopstate}
67
+ swipeEnabled={swipeEnabled}
68
+ snapPoints={snapPoints}
69
+ snapIndex={snapIndex}
70
+ onSnapIndexChange={onSnapIndexChange}
71
+ safeArea={safeArea}
72
+ {...extractSupportProps(rest)}
73
+ />
74
+ );
75
+ }
@@ -0,0 +1 @@
1
+ export * from './MobileDrawer';