@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.
- package/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +480 -0
- package/dist/cjs/components/Drawer/Drawer.d.ts +6 -0
- package/dist/cjs/components/Drawer/Drawer.js +15 -0
- package/dist/cjs/components/Drawer/constants.d.ts +1 -0
- package/dist/cjs/components/Drawer/constants.js +4 -0
- package/dist/cjs/components/Drawer/index.d.ts +2 -0
- package/dist/cjs/components/Drawer/index.js +18 -0
- package/dist/cjs/components/Drawer/styles.module.css +19 -0
- package/dist/cjs/components/Drawer/types.d.ts +35 -0
- package/dist/cjs/components/Drawer/types.js +2 -0
- package/dist/cjs/components/DrawerCustom/DrawerCustom.d.ts +15 -0
- package/dist/cjs/components/DrawerCustom/DrawerCustom.js +76 -0
- package/dist/cjs/components/DrawerCustom/constants.d.ts +4 -0
- package/dist/cjs/components/DrawerCustom/constants.js +17 -0
- package/dist/cjs/components/DrawerCustom/hooks/index.d.ts +1 -0
- package/dist/cjs/components/DrawerCustom/hooks/index.js +17 -0
- package/dist/cjs/components/DrawerCustom/hooks/useDrawerFocusTrap.d.ts +9 -0
- package/dist/cjs/components/DrawerCustom/hooks/useDrawerFocusTrap.js +83 -0
- package/dist/cjs/components/DrawerCustom/hooks/useDrawerResize.d.ts +15 -0
- package/dist/cjs/components/DrawerCustom/hooks/useDrawerResize.js +50 -0
- package/dist/cjs/components/DrawerCustom/index.d.ts +2 -0
- package/dist/cjs/components/DrawerCustom/index.js +17 -0
- package/dist/cjs/components/DrawerCustom/motion.css +93 -0
- package/dist/cjs/components/DrawerCustom/styles.module.css +491 -0
- package/dist/cjs/components/DrawerCustom/types.d.ts +71 -0
- package/dist/cjs/components/DrawerCustom/types.js +2 -0
- package/dist/cjs/components/index.d.ts +2 -0
- package/dist/cjs/components/index.js +18 -0
- package/dist/cjs/constants.d.ts +28 -0
- package/dist/cjs/constants.js +31 -0
- package/dist/cjs/helperComponents/DesktopDrawer/DesktopDrawer.d.ts +3 -0
- package/dist/cjs/helperComponents/DesktopDrawer/DesktopDrawer.js +32 -0
- package/dist/cjs/helperComponents/DesktopDrawer/index.d.ts +1 -0
- package/dist/cjs/helperComponents/DesktopDrawer/index.js +17 -0
- package/dist/cjs/helperComponents/DialogSlots.d.ts +8 -0
- package/dist/cjs/helperComponents/DialogSlots.js +12 -0
- package/dist/cjs/helperComponents/DrawerBody/DrawerBody.d.ts +4 -0
- package/dist/cjs/helperComponents/DrawerBody/DrawerBody.js +17 -0
- package/dist/cjs/helperComponents/DrawerBody/index.d.ts +1 -0
- package/dist/cjs/helperComponents/DrawerBody/index.js +17 -0
- package/dist/cjs/helperComponents/DrawerBody/styles.module.css +30 -0
- package/dist/cjs/helperComponents/MobileDrawer/MobileDrawer.d.ts +7 -0
- package/dist/cjs/helperComponents/MobileDrawer/MobileDrawer.js +26 -0
- package/dist/cjs/helperComponents/MobileDrawer/index.d.ts +1 -0
- package/dist/cjs/helperComponents/MobileDrawer/index.js +17 -0
- package/dist/cjs/helperComponents/index.d.ts +8 -0
- package/dist/cjs/helperComponents/index.js +30 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/style.css +633 -0
- package/dist/cjs/types.d.ts +4 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils/interopDefault.d.ts +7 -0
- package/dist/cjs/utils/interopDefault.js +10 -0
- package/dist/esm/components/Drawer/Drawer.d.ts +6 -0
- package/dist/esm/components/Drawer/Drawer.js +12 -0
- package/dist/esm/components/Drawer/constants.d.ts +1 -0
- package/dist/esm/components/Drawer/constants.js +1 -0
- package/dist/esm/components/Drawer/index.d.ts +2 -0
- package/dist/esm/components/Drawer/index.js +2 -0
- package/dist/esm/components/Drawer/styles.module.css +19 -0
- package/dist/esm/components/Drawer/types.d.ts +35 -0
- package/dist/esm/components/Drawer/types.js +1 -0
- package/dist/esm/components/DrawerCustom/DrawerCustom.d.ts +15 -0
- package/dist/esm/components/DrawerCustom/DrawerCustom.js +70 -0
- package/dist/esm/components/DrawerCustom/constants.d.ts +4 -0
- package/dist/esm/components/DrawerCustom/constants.js +13 -0
- package/dist/esm/components/DrawerCustom/hooks/index.d.ts +1 -0
- package/dist/esm/components/DrawerCustom/hooks/index.js +1 -0
- package/dist/esm/components/DrawerCustom/hooks/useDrawerFocusTrap.d.ts +9 -0
- package/dist/esm/components/DrawerCustom/hooks/useDrawerFocusTrap.js +80 -0
- package/dist/esm/components/DrawerCustom/hooks/useDrawerResize.d.ts +15 -0
- package/dist/esm/components/DrawerCustom/hooks/useDrawerResize.js +47 -0
- package/dist/esm/components/DrawerCustom/index.d.ts +2 -0
- package/dist/esm/components/DrawerCustom/index.js +1 -0
- package/dist/esm/components/DrawerCustom/motion.css +93 -0
- package/dist/esm/components/DrawerCustom/styles.module.css +491 -0
- package/dist/esm/components/DrawerCustom/types.d.ts +71 -0
- package/dist/esm/components/DrawerCustom/types.js +1 -0
- package/dist/esm/components/index.d.ts +2 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/constants.d.ts +28 -0
- package/dist/esm/constants.js +28 -0
- package/dist/esm/helperComponents/DesktopDrawer/DesktopDrawer.d.ts +3 -0
- package/dist/esm/helperComponents/DesktopDrawer/DesktopDrawer.js +26 -0
- package/dist/esm/helperComponents/DesktopDrawer/index.d.ts +1 -0
- package/dist/esm/helperComponents/DesktopDrawer/index.js +1 -0
- package/dist/esm/helperComponents/DialogSlots.d.ts +8 -0
- package/dist/esm/helperComponents/DialogSlots.js +8 -0
- package/dist/esm/helperComponents/DrawerBody/DrawerBody.d.ts +4 -0
- package/dist/esm/helperComponents/DrawerBody/DrawerBody.js +11 -0
- package/dist/esm/helperComponents/DrawerBody/index.d.ts +1 -0
- package/dist/esm/helperComponents/DrawerBody/index.js +1 -0
- package/dist/esm/helperComponents/DrawerBody/styles.module.css +30 -0
- package/dist/esm/helperComponents/MobileDrawer/MobileDrawer.d.ts +7 -0
- package/dist/esm/helperComponents/MobileDrawer/MobileDrawer.js +23 -0
- package/dist/esm/helperComponents/MobileDrawer/index.d.ts +1 -0
- package/dist/esm/helperComponents/MobileDrawer/index.js +1 -0
- package/dist/esm/helperComponents/index.d.ts +8 -0
- package/dist/esm/helperComponents/index.js +8 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/style.css +633 -0
- package/dist/esm/types.d.ts +4 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils/interopDefault.d.ts +7 -0
- package/dist/esm/utils/interopDefault.js +7 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +51 -0
- package/src/components/Drawer/Drawer.tsx +15 -0
- package/src/components/Drawer/constants.ts +1 -0
- package/src/components/Drawer/index.ts +2 -0
- package/src/components/Drawer/styles.module.scss +26 -0
- package/src/components/Drawer/types.ts +42 -0
- package/src/components/DrawerCustom/DrawerCustom.tsx +197 -0
- package/src/components/DrawerCustom/constants.ts +17 -0
- package/src/components/DrawerCustom/hooks/index.ts +1 -0
- package/src/components/DrawerCustom/hooks/useDrawerFocusTrap.ts +97 -0
- package/src/components/DrawerCustom/hooks/useDrawerResize.tsx +78 -0
- package/src/components/DrawerCustom/index.ts +2 -0
- package/src/components/DrawerCustom/styles.module.scss +330 -0
- package/src/components/DrawerCustom/types.ts +77 -0
- package/src/components/index.ts +2 -0
- package/src/constants.ts +31 -0
- package/src/helperComponents/DesktopDrawer/DesktopDrawer.tsx +85 -0
- package/src/helperComponents/DesktopDrawer/index.ts +1 -0
- package/src/helperComponents/DialogSlots.tsx +25 -0
- package/src/helperComponents/DrawerBody/DrawerBody.tsx +25 -0
- package/src/helperComponents/DrawerBody/index.ts +1 -0
- package/src/helperComponents/DrawerBody/styles.module.scss +32 -0
- package/src/helperComponents/MobileDrawer/MobileDrawer.tsx +75 -0
- package/src/helperComponents/MobileDrawer/index.ts +1 -0
- package/src/helperComponents/index.ts +11 -0
- package/src/index.ts +6 -0
- package/src/types.ts +6 -0
- package/src/utils/interopDefault.ts +7 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { isMobileLayout, useAdaptiveLayout } from '@cloud-ru/ds-adaptive';
|
|
2
|
+
import { BottomSheetCustom, OVERLAY_SURFACE, OverlaySurfaceProvider } from '@cloud-ru/ds-bottom-sheet';
|
|
3
|
+
import { PopupCloseButton } from '@cloud-ru/ds-popup-private';
|
|
4
|
+
import { usePortalContext } from '@cloud-ru/ds-portal-context';
|
|
5
|
+
import { extractSupportProps, useModalOpenState } from '@cloud-ru/ds-utils';
|
|
6
|
+
import RcDrawerImport, { DrawerProps as RcDrawerBaseProps } from '@rc-component/drawer';
|
|
7
|
+
import cn from 'classnames';
|
|
8
|
+
import { ComponentType, CSSProperties, useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
import { TEST_IDS, WIDTH_AS_VALUES } from '../../constants';
|
|
11
|
+
import {
|
|
12
|
+
DialogBody,
|
|
13
|
+
DialogBodyProps,
|
|
14
|
+
DialogFooter,
|
|
15
|
+
DialogFooterProps,
|
|
16
|
+
DialogHeader,
|
|
17
|
+
DialogHeaderProps,
|
|
18
|
+
} from '../../helperComponents';
|
|
19
|
+
import { interopDefault } from '../../utils/interopDefault';
|
|
20
|
+
import { motionProps } from './constants';
|
|
21
|
+
import { useDrawerFocusTrap } from './hooks';
|
|
22
|
+
import { useDrawerResize } from './hooks/useDrawerResize';
|
|
23
|
+
import styles from './styles.module.scss';
|
|
24
|
+
import { DrawerCustomProps } from './types';
|
|
25
|
+
|
|
26
|
+
const RcDrawer = interopDefault<ComponentType<RcDrawerBaseProps>>(RcDrawerImport);
|
|
27
|
+
|
|
28
|
+
/** Desktop-frame дровера: rc-drawer + position/width/push/nestedDrawer + close-button. Surface='drawer'. */
|
|
29
|
+
function DrawerFrame(props: DrawerCustomProps) {
|
|
30
|
+
const {
|
|
31
|
+
showBlackout = true,
|
|
32
|
+
showButtonClosed = true,
|
|
33
|
+
open,
|
|
34
|
+
onClose,
|
|
35
|
+
rootClassName,
|
|
36
|
+
className,
|
|
37
|
+
push,
|
|
38
|
+
container,
|
|
39
|
+
children,
|
|
40
|
+
nestedDrawer,
|
|
41
|
+
closeOnPopstate,
|
|
42
|
+
position,
|
|
43
|
+
heightAuto,
|
|
44
|
+
width: widthProp,
|
|
45
|
+
resizable: resizableProp,
|
|
46
|
+
disableMotions = false,
|
|
47
|
+
...rest
|
|
48
|
+
} = props;
|
|
49
|
+
const heightAutoVertical = Boolean(heightAuto && (position === 'top' || position === 'bottom'));
|
|
50
|
+
const resizable = heightAutoVertical ? undefined : resizableProp;
|
|
51
|
+
const {
|
|
52
|
+
tooltip,
|
|
53
|
+
checkElement,
|
|
54
|
+
width: userWidth,
|
|
55
|
+
resizable: drawerResizable,
|
|
56
|
+
} = useDrawerResize({
|
|
57
|
+
resizable,
|
|
58
|
+
position,
|
|
59
|
+
});
|
|
60
|
+
const width = resizable ? (userWidth ?? widthProp) : widthProp;
|
|
61
|
+
const isPredefinedWidth = typeof width === 'string' && WIDTH_AS_VALUES.includes(width);
|
|
62
|
+
|
|
63
|
+
// Wrapper height/inset задаёт CSS по `[data-height-auto]`; здесь только стили секции.
|
|
64
|
+
const drawerPanelStyle = useMemo((): CSSProperties | undefined => {
|
|
65
|
+
if (!heightAutoVertical) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
height: 'auto',
|
|
71
|
+
maxHeight: '100%',
|
|
72
|
+
minHeight: 0,
|
|
73
|
+
overflow: 'hidden',
|
|
74
|
+
};
|
|
75
|
+
}, [heightAutoVertical]);
|
|
76
|
+
|
|
77
|
+
// Esc обрабатывает rc-drawer (`keyboard={showBlackout}`) — он уважает стек порталов и закрывает
|
|
78
|
+
// только верхний дровер. `CloseWatcher` подключаем лишь там, где rc-drawer Esc не слушает
|
|
79
|
+
// (`showBlackout=false`): иначе два независимых обработчика закрывают и вложенный, и родительский.
|
|
80
|
+
useModalOpenState(open, onClose, { closeOnPopstate, closeByCloseWatcher: !showBlackout });
|
|
81
|
+
|
|
82
|
+
const focusTrapRef = useDrawerFocusTrap(Boolean(open));
|
|
83
|
+
|
|
84
|
+
// Без явного container портал идёт через PortalContextProvider (как у Modal): скоуп к локальному
|
|
85
|
+
// элементу и отказ от body-level scroll lock, который rc-drawer ставит при рендере в document.body.
|
|
86
|
+
const portalContextRef = usePortalContext();
|
|
87
|
+
const resolvedContainer =
|
|
88
|
+
container ?? (portalContextRef.current ? () => portalContextRef.current as HTMLElement : undefined);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<RcDrawer
|
|
92
|
+
resizable={drawerResizable}
|
|
93
|
+
panelRef={checkElement}
|
|
94
|
+
mask={showBlackout}
|
|
95
|
+
maskClosable={showBlackout}
|
|
96
|
+
keyboard={showBlackout}
|
|
97
|
+
data-showblackout={showBlackout}
|
|
98
|
+
open={open}
|
|
99
|
+
onClose={onClose}
|
|
100
|
+
push={push}
|
|
101
|
+
getContainer={resolvedContainer}
|
|
102
|
+
placement={position}
|
|
103
|
+
destroyOnHidden
|
|
104
|
+
rootClassName={cn(styles.drawerRoot, rootClassName)}
|
|
105
|
+
style={drawerPanelStyle}
|
|
106
|
+
classNames={{
|
|
107
|
+
section: cn(styles.drawer, className),
|
|
108
|
+
}}
|
|
109
|
+
size={isPredefinedWidth ? 'null' : width}
|
|
110
|
+
{...extractSupportProps(rest)}
|
|
111
|
+
data-content-wrapper
|
|
112
|
+
data-position={position}
|
|
113
|
+
data-width={isPredefinedWidth ? width : undefined}
|
|
114
|
+
data-height-auto={heightAutoVertical ? true : undefined}
|
|
115
|
+
data-acrylic-appearance='neutral'
|
|
116
|
+
data-acrylic-level='1Level'
|
|
117
|
+
prefixCls='snack-rc-drawer'
|
|
118
|
+
{...(disableMotions ? {} : motionProps)}
|
|
119
|
+
>
|
|
120
|
+
<div ref={focusTrapRef} className={styles.focusScope}>
|
|
121
|
+
{showButtonClosed && (
|
|
122
|
+
<div className={styles.showButtonClosedWrapper}>
|
|
123
|
+
<PopupCloseButton
|
|
124
|
+
className={styles.showButtonClosed}
|
|
125
|
+
aria-label='close drawer'
|
|
126
|
+
data-test-id={TEST_IDS.closeButton}
|
|
127
|
+
onClick={onClose}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
<OverlaySurfaceProvider surface={OVERLAY_SURFACE.Drawer} bodyHeightAuto={heightAutoVertical}>
|
|
133
|
+
{children}
|
|
134
|
+
</OverlaySurfaceProvider>
|
|
135
|
+
|
|
136
|
+
{tooltip}
|
|
137
|
+
|
|
138
|
+
{nestedDrawer}
|
|
139
|
+
</div>
|
|
140
|
+
</RcDrawer>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Адаптивный low-level `DrawerCustom`: на `mobile` — `BottomSheetCustom`, иначе — `DrawerFrame`.
|
|
146
|
+
* Композиция одна (surface-aware слоты `.Header/.Body/.Footer`). Форс — `withLayoutType`.
|
|
147
|
+
*/
|
|
148
|
+
export function DrawerCustom(props: DrawerCustomProps) {
|
|
149
|
+
const { layoutType } = useAdaptiveLayout();
|
|
150
|
+
|
|
151
|
+
if (isMobileLayout(layoutType)) {
|
|
152
|
+
const {
|
|
153
|
+
open,
|
|
154
|
+
onClose,
|
|
155
|
+
children,
|
|
156
|
+
className,
|
|
157
|
+
rootClassName,
|
|
158
|
+
container,
|
|
159
|
+
closeOnPopstate,
|
|
160
|
+
showBlackout,
|
|
161
|
+
snapPoints,
|
|
162
|
+
swipeEnabled,
|
|
163
|
+
safeArea,
|
|
164
|
+
disableMotions,
|
|
165
|
+
...rest
|
|
166
|
+
} = props;
|
|
167
|
+
return (
|
|
168
|
+
<BottomSheetCustom
|
|
169
|
+
open={open}
|
|
170
|
+
onClose={onClose}
|
|
171
|
+
container={container}
|
|
172
|
+
className={className}
|
|
173
|
+
rootClassName={rootClassName}
|
|
174
|
+
closeOnPopstate={closeOnPopstate}
|
|
175
|
+
showBackdrop={showBlackout}
|
|
176
|
+
snapPoints={snapPoints}
|
|
177
|
+
swipeEnabled={swipeEnabled}
|
|
178
|
+
safeArea={safeArea}
|
|
179
|
+
disableMotions={disableMotions}
|
|
180
|
+
{...extractSupportProps(rest)}
|
|
181
|
+
>
|
|
182
|
+
{children}
|
|
183
|
+
</BottomSheetCustom>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return <DrawerFrame {...props} />;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export namespace DrawerCustom {
|
|
191
|
+
export type HeaderProps = DialogHeaderProps;
|
|
192
|
+
export type BodyProps = DialogBodyProps;
|
|
193
|
+
export type FooterProps = DialogFooterProps;
|
|
194
|
+
export const Header = DialogHeader;
|
|
195
|
+
export const Body = DialogBody;
|
|
196
|
+
export const Footer = DialogFooter;
|
|
197
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DrawerProps } from '@rc-component/drawer';
|
|
2
|
+
|
|
3
|
+
export const maskMotion: DrawerProps['maskMotion'] = {
|
|
4
|
+
motionAppear: true,
|
|
5
|
+
motionName: 'maskMotion',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const motion: DrawerProps['motion'] = placement => ({
|
|
9
|
+
motionAppear: true,
|
|
10
|
+
motionDeadline: 300,
|
|
11
|
+
motionName: `panelMotion-${placement}`,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const motionProps: Partial<DrawerProps> = {
|
|
15
|
+
maskMotion,
|
|
16
|
+
motion,
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useDrawerFocusTrap';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useLayoutEffect } from '@cloud-ru/ds-utils';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { tabbable } from 'tabbable';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Focus scope для Drawer-панели:
|
|
7
|
+
* - запоминает activeElement до открытия и возвращает фокус после закрытия;
|
|
8
|
+
* - устанавливает фокус на контейнер при открытии (без прыжка на первый focusable);
|
|
9
|
+
* - цикличный Tab / Shift+Tab внутри контейнера.
|
|
10
|
+
*
|
|
11
|
+
* Возвращает ref, который нужно повесить на корневой DOM-узел панели.
|
|
12
|
+
*/
|
|
13
|
+
export function useDrawerFocusTrap(active: boolean) {
|
|
14
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
15
|
+
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
if (!active) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Панель rc-drawer монтируется отдельным коммитом, поэтому `containerRef` здесь ещё пуст —
|
|
22
|
+
// возврат фокуса от него не зависит и на его отсутствие завязываться нельзя.
|
|
23
|
+
// Начальный фокус ставит сам rc-drawer (autoFocus на panelRef).
|
|
24
|
+
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
if (
|
|
28
|
+
previous &&
|
|
29
|
+
previous !== document.body &&
|
|
30
|
+
previous !== document.documentElement &&
|
|
31
|
+
document.documentElement.contains(previous)
|
|
32
|
+
) {
|
|
33
|
+
previous.focus({ preventScroll: true });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, [active]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!active) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
44
|
+
if (event.key !== 'Tab') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const container = containerRef.current;
|
|
49
|
+
if (!container) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const activeElement = document.activeElement;
|
|
54
|
+
if (!(activeElement instanceof HTMLElement)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const focusInsideDialog = container.contains(activeElement);
|
|
59
|
+
const nodes = tabbable(container);
|
|
60
|
+
|
|
61
|
+
if (nodes.length === 0) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const first = nodes[0];
|
|
67
|
+
const last = nodes[nodes.length - 1];
|
|
68
|
+
|
|
69
|
+
if (!focusInsideDialog) {
|
|
70
|
+
// Фокус ушёл наружу (rc-drawer sentinel / body) — вернём внутрь.
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
if (event.shiftKey) {
|
|
73
|
+
last?.focus();
|
|
74
|
+
} else {
|
|
75
|
+
first?.focus();
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (event.shiftKey && activeElement === first) {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
last?.focus();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!event.shiftKey && activeElement === last) {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
first?.focus();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
document.addEventListener('keydown', handleKeyDown, true);
|
|
93
|
+
return () => document.removeEventListener('keydown', handleKeyDown, true);
|
|
94
|
+
}, [active]);
|
|
95
|
+
|
|
96
|
+
return containerRef;
|
|
97
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Tooltip } from '@cloud-ru/ds-tooltip';
|
|
2
|
+
import { isBrowser } from '@cloud-ru/ds-utils';
|
|
3
|
+
import { DrawerProps } from '@rc-component/drawer';
|
|
4
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import { DrawerCustomProps } from '../types';
|
|
7
|
+
|
|
8
|
+
const DRAGGER_SELECTOR = '.rc-drawer-resizable-dragger';
|
|
9
|
+
|
|
10
|
+
type UseDrawerResizeParams = Pick<DrawerCustomProps, 'position' | 'resizable'>;
|
|
11
|
+
|
|
12
|
+
type UseDrawerResizeResult = {
|
|
13
|
+
/** JSX Тултипа для ползунка изменения ширины */
|
|
14
|
+
tooltip: React.ReactNode;
|
|
15
|
+
/** Функция поиска ползунка в DOM-дереве. Стоит вызывать тогда, когда он отрендерен. */
|
|
16
|
+
checkElement: () => void;
|
|
17
|
+
/** Ресайз-хендлер для rc-drawer */
|
|
18
|
+
resizable: DrawerProps['resizable'];
|
|
19
|
+
/** Ширина ползунка в пикселях */
|
|
20
|
+
width?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function useDrawerResize({ position, resizable: resizableProp }: UseDrawerResizeParams): UseDrawerResizeResult {
|
|
24
|
+
const targetRef = useRef<HTMLElement | null>(null);
|
|
25
|
+
const targetRefCallbackRef = useRef<(node: HTMLElement) => void | undefined>();
|
|
26
|
+
const [width, setWidth] = useState<number | undefined>(resizableProp?.default);
|
|
27
|
+
const [muted, setMuted] = useState(false);
|
|
28
|
+
const [open, setOpen] = useState(false);
|
|
29
|
+
const draggerTooltip = resizableProp?.draggerTooltip;
|
|
30
|
+
|
|
31
|
+
const checkElement = useCallback(() => {
|
|
32
|
+
if (isBrowser() && draggerTooltip) {
|
|
33
|
+
const element = document.querySelector(DRAGGER_SELECTOR) as HTMLElement;
|
|
34
|
+
|
|
35
|
+
if (!element) return;
|
|
36
|
+
|
|
37
|
+
targetRefCallbackRef.current ? targetRefCallbackRef.current(element) : (targetRef.current = element);
|
|
38
|
+
}
|
|
39
|
+
}, [draggerTooltip]);
|
|
40
|
+
|
|
41
|
+
const tooltip = draggerTooltip ? (
|
|
42
|
+
<Tooltip placement={position} offset={4} tip={draggerTooltip} open={open && !muted} onOpenChange={setOpen}>
|
|
43
|
+
{({ ref: targetRefCallback }) => {
|
|
44
|
+
if (!targetRefCallback) return null;
|
|
45
|
+
|
|
46
|
+
targetRef.current ? targetRefCallback(targetRef.current) : (targetRefCallbackRef.current = targetRefCallback);
|
|
47
|
+
}}
|
|
48
|
+
</Tooltip>
|
|
49
|
+
) : null;
|
|
50
|
+
|
|
51
|
+
const resizable = useMemo(
|
|
52
|
+
() =>
|
|
53
|
+
resizableProp
|
|
54
|
+
? ({
|
|
55
|
+
onResize: value => {
|
|
56
|
+
if (value < (resizableProp?.min || 0)) return;
|
|
57
|
+
if (value > (resizableProp?.max || Infinity)) return;
|
|
58
|
+
|
|
59
|
+
setWidth(value);
|
|
60
|
+
|
|
61
|
+
resizableProp?.onResize?.(value);
|
|
62
|
+
},
|
|
63
|
+
onResizeEnd: () => {
|
|
64
|
+
setOpen(false);
|
|
65
|
+
setMuted(false);
|
|
66
|
+
|
|
67
|
+
resizableProp?.onResizeEnd?.(width ?? 0);
|
|
68
|
+
},
|
|
69
|
+
onResizeStart: () => {
|
|
70
|
+
setMuted(true);
|
|
71
|
+
},
|
|
72
|
+
} satisfies DrawerProps['resizable'])
|
|
73
|
+
: undefined,
|
|
74
|
+
[resizableProp, width],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return { tooltip, checkElement, resizable, width };
|
|
78
|
+
}
|