Skip to content

Commit bd686c4

Browse files
authored
chore: refactor tab control (#280)
1 parent 5c88fe0 commit bd686c4

File tree

3 files changed

+111
-137
lines changed

3 files changed

+111
-137
lines changed

src/DrawerPanel.tsx

Lines changed: 37 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import * as React from 'react';
22
import classNames from 'classnames';
33
import type { Placement } from './Drawer';
4-
import KeyCode from 'rc-util/lib/KeyCode';
5-
import { composeRef } from 'rc-util/lib/ref';
64

75
export interface DrawerPanelRef {
86
focus: VoidFunction;
@@ -16,121 +14,48 @@ export interface DrawerPanelProps {
1614
height?: number | string;
1715
placement: Placement;
1816
children?: React.ReactNode;
19-
onClose?: React.KeyboardEventHandler<HTMLElement>;
2017
containerRef?: React.Ref<HTMLDivElement>;
2118
}
2219

23-
const sentinelStyle: React.CSSProperties = {
24-
width: 0,
25-
height: 0,
26-
overflow: 'hidden',
27-
outline: 'none',
28-
position: 'absolute',
20+
const DrawerPanel = (props: DrawerPanelProps) => {
21+
const {
22+
prefixCls,
23+
className,
24+
style,
25+
placement,
26+
width,
27+
height,
28+
children,
29+
containerRef,
30+
} = props;
31+
32+
// =============================== Render ===============================
33+
const panelStyle: React.CSSProperties = {};
34+
35+
if (placement === 'left' || placement === 'right') {
36+
panelStyle.width = width;
37+
} else {
38+
panelStyle.height = height;
39+
}
40+
41+
return (
42+
<>
43+
<div
44+
className={classNames(`${prefixCls}-content`, className)}
45+
style={{
46+
...panelStyle,
47+
...style,
48+
}}
49+
aria-modal="true"
50+
role="dialog"
51+
ref={containerRef}
52+
>
53+
{children}
54+
</div>
55+
</>
56+
);
2957
};
3058

31-
const DrawerPanel = React.forwardRef<DrawerPanelRef, DrawerPanelProps>(
32-
(props, ref) => {
33-
const {
34-
prefixCls,
35-
className,
36-
style,
37-
placement,
38-
width,
39-
height,
40-
children,
41-
onClose,
42-
containerRef,
43-
} = props;
44-
45-
// ================================ Refs ================================
46-
const panelRef = React.useRef<HTMLDivElement>();
47-
const sentinelStartRef = React.useRef<HTMLDivElement>();
48-
const sentinelEndRef = React.useRef<HTMLDivElement>();
49-
50-
React.useImperativeHandle(ref, () => ({
51-
focus: () => {
52-
Promise.resolve().then(() => {
53-
sentinelStartRef.current?.focus({ preventScroll: true });
54-
});
55-
},
56-
}));
57-
58-
const onPanelKeyDown: React.KeyboardEventHandler<
59-
HTMLDivElement
60-
> = event => {
61-
const { keyCode, shiftKey } = event;
62-
63-
switch (keyCode) {
64-
// Tab active
65-
case KeyCode.TAB: {
66-
if (keyCode === KeyCode.TAB) {
67-
if (
68-
!shiftKey &&
69-
document.activeElement === sentinelEndRef.current
70-
) {
71-
sentinelStartRef.current?.focus({ preventScroll: true });
72-
} else if (
73-
shiftKey &&
74-
document.activeElement === sentinelStartRef.current
75-
) {
76-
sentinelEndRef.current?.focus({ preventScroll: true });
77-
}
78-
}
79-
break;
80-
}
81-
82-
// Close
83-
case KeyCode.ESC: {
84-
onClose(event);
85-
break;
86-
}
87-
}
88-
};
89-
90-
// =============================== Render ===============================
91-
const panelStyle: React.CSSProperties = {};
92-
93-
if (placement === 'left' || placement === 'right') {
94-
panelStyle.width = width;
95-
} else {
96-
panelStyle.height = height;
97-
}
98-
99-
return (
100-
<>
101-
<div
102-
className={classNames(`${prefixCls}-content`, className)}
103-
style={{
104-
...panelStyle,
105-
...style,
106-
}}
107-
aria-modal="true"
108-
role="dialog"
109-
tabIndex={-1}
110-
ref={composeRef(panelRef, containerRef)}
111-
onKeyDown={onPanelKeyDown}
112-
>
113-
<div
114-
tabIndex={0}
115-
ref={sentinelStartRef}
116-
style={sentinelStyle}
117-
aria-hidden="true"
118-
/>
119-
120-
{children}
121-
122-
<div
123-
tabIndex={0}
124-
ref={sentinelEndRef}
125-
style={sentinelStyle}
126-
aria-hidden="true"
127-
/>
128-
</div>
129-
</>
130-
);
131-
},
132-
);
133-
13459
if (process.env.NODE_ENV !== 'production') {
13560
DrawerPanel.displayName = 'DrawerPanel';
13661
}

src/DrawerPopup.tsx

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@ import * as React from 'react';
22
import classNames from 'classnames';
33
import CSSMotion from 'rc-motion';
44
import type { CSSMotionProps } from 'rc-motion';
5-
import type { DrawerPanelRef } from './DrawerPanel';
65
import DrawerPanel from './DrawerPanel';
76
import type ScrollLocker from 'rc-util/lib/Dom/scrollLocker';
87
import DrawerContext from './context';
98
import type { DrawerContextProps } from './context';
9+
import KeyCode from 'rc-util/lib/KeyCode';
10+
11+
const sentinelStyle: React.CSSProperties = {
12+
width: 0,
13+
height: 0,
14+
overflow: 'hidden',
15+
outline: 'none',
16+
position: 'absolute',
17+
};
1018

1119
export type Placement = 'left' | 'right' | 'top' | 'bottom';
1220

@@ -97,6 +105,48 @@ export default function DrawerPopup(props: DrawerPopupProps) {
97105
onClose,
98106
} = props;
99107

108+
// ================================ Refs ================================
109+
const panelRef = React.useRef<HTMLDivElement>();
110+
const sentinelStartRef = React.useRef<HTMLDivElement>();
111+
const sentinelEndRef = React.useRef<HTMLDivElement>();
112+
113+
const onPanelKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
114+
const { keyCode, shiftKey } = event;
115+
116+
switch (keyCode) {
117+
// Tab active
118+
case KeyCode.TAB: {
119+
if (keyCode === KeyCode.TAB) {
120+
if (!shiftKey && document.activeElement === sentinelEndRef.current) {
121+
sentinelStartRef.current?.focus({ preventScroll: true });
122+
} else if (
123+
shiftKey &&
124+
document.activeElement === sentinelStartRef.current
125+
) {
126+
sentinelEndRef.current?.focus({ preventScroll: true });
127+
}
128+
}
129+
break;
130+
}
131+
132+
// Close
133+
case KeyCode.ESC: {
134+
if (onClose && keyboard) {
135+
onClose(event);
136+
}
137+
break;
138+
}
139+
}
140+
};
141+
142+
// ========================== Control ===========================
143+
// Auto Focus
144+
React.useEffect(() => {
145+
if (open && autoFocus) {
146+
panelRef.current?.focus({ preventScroll: true });
147+
}
148+
}, [open, autoFocus]);
149+
100150
// ============================ Push ============================
101151
const [pushed, setPushed] = React.useState(false);
102152

@@ -130,8 +180,6 @@ export default function DrawerPopup(props: DrawerPopupProps) {
130180
);
131181

132182
// ========================= ScrollLock =========================
133-
const panelRef = React.useRef<DrawerPanelRef>();
134-
135183
// Tell parent to push
136184
React.useEffect(() => {
137185
if (open) {
@@ -157,20 +205,6 @@ export default function DrawerPopup(props: DrawerPopupProps) {
157205
[],
158206
);
159207

160-
// ========================== Control ===========================
161-
// Auto Focus
162-
React.useEffect(() => {
163-
if (open && autoFocus) {
164-
panelRef.current?.focus();
165-
}
166-
}, [open, autoFocus]);
167-
168-
const onPanelClose: React.KeyboardEventHandler<HTMLDivElement> = event => {
169-
if (onClose && keyboard) {
170-
onClose(event);
171-
}
172-
};
173-
174208
// =========================== zIndex ===========================
175209
const zIndexStyle: React.CSSProperties = {};
176210
if (zIndex) {
@@ -252,7 +286,6 @@ export default function DrawerPopup(props: DrawerPopupProps) {
252286
{({ className: motionClassName, style: motionStyle }, motionRef) => {
253287
return (
254288
<DrawerPanel
255-
ref={panelRef}
256289
containerRef={motionRef}
257290
prefixCls={prefixCls}
258291
className={classNames(className, motionClassName)}
@@ -263,7 +296,6 @@ export default function DrawerPopup(props: DrawerPopupProps) {
263296
width={width}
264297
height={height}
265298
placement={placement}
266-
onClose={onPanelClose}
267299
>
268300
{children}
269301
</DrawerPanel>
@@ -286,9 +318,26 @@ export default function DrawerPopup(props: DrawerPopupProps) {
286318
},
287319
)}
288320
style={rootStyle}
321+
tabIndex={-1}
322+
ref={panelRef}
323+
onKeyDown={onPanelKeyDown}
289324
>
290325
{maskNode}
326+
<div
327+
tabIndex={0}
328+
ref={sentinelStartRef}
329+
style={sentinelStyle}
330+
aria-hidden="true"
331+
data-sentinel="start"
332+
/>
291333
{panelNode}
334+
<div
335+
tabIndex={0}
336+
ref={sentinelEndRef}
337+
style={sentinelStyle}
338+
aria-hidden="true"
339+
data-sentinel="end"
340+
/>
292341
</div>
293342
</DrawerContext.Provider>
294343
);

tests/index.spec.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,12 @@ describe('rc-drawer-menu', () => {
236236
</Drawer>,
237237
);
238238

239-
const list = Array.from(
240-
container.querySelector('.rc-drawer-content').children,
241-
) as HTMLElement[];
242-
243-
const firstSentinel = list[0];
244-
const lastSentinel = list[2];
239+
const firstSentinel = container.querySelector<HTMLElement>(
240+
'[data-sentinel="start"]',
241+
);
242+
const lastSentinel = container.querySelector<HTMLElement>(
243+
'[data-sentinel="end"]',
244+
);
245245

246246
// First shift to last
247247
firstSentinel.focus();

0 commit comments

Comments
 (0)