Skip to content

Commit 2562c9b

Browse files
MuxinFengzombieJMadCcc
authored
refactor: migrate @rc-component/trigger (#226)
* test: migrate rc-dropdown tests * test: skip tests about offset * chore: bump rc-trigger * test: fix part * test: clean up * chore: update ci node ver * chore: update ci node ver * chore: add jest-environment-jsdom * fix: handle ref * chore: code clean * chore: code clena * chore: add husky * test: add test cov --------- Co-authored-by: 二货机器人 <[email protected]> Co-authored-by: MadCcc <[email protected]>
1 parent b41847c commit 2562c9b

17 files changed

+846
-710
lines changed

.husky/pre-commit

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env sh
2+
. "$(dirname -- "$0")/_/husky.sh"
3+
4+
npx lint-staged

jest.config.js

-4
This file was deleted.

package.json

+40-30
Original file line numberDiff line numberDiff line change
@@ -7,65 +7,75 @@
77
"react-dropdown"
88
],
99
"homepage": "http://github.com/react-component/dropdown",
10-
"maintainers": [
11-
12-
13-
],
10+
"bugs": {
11+
"url": "http://github.com/react-component/dropdown/issues"
12+
},
1413
"repository": {
1514
"type": "git",
1615
"url": "[email protected]:react-component/dropdown.git"
1716
},
18-
"bugs": {
19-
"url": "http://github.com/react-component/dropdown/issues"
20-
},
17+
"license": "MIT",
18+
"maintainers": [
19+
20+
21+
],
22+
"main": "lib/index",
23+
"module": "./es/index",
2124
"files": [
2225
"lib",
2326
"es",
2427
"assets/*.css"
2528
],
26-
"main": "lib/index",
27-
"module": "./es/index",
28-
"license": "MIT",
2929
"scripts": {
30-
"start": "dumi dev",
3130
"build": "dumi build",
3231
"compile": "father build && lessc assets/index.less assets/index.css",
33-
"prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
32+
"coverage": "rc-test --coverage",
3433
"lint": "eslint src/ docs/examples/ --ext .tsx,.ts,.jsx,.js",
35-
"test": "father test",
36-
"coverage": "father test --coverage",
37-
"now-build": "npm run build"
34+
"now-build": "npm run build",
35+
"prepare": "husky install",
36+
"prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
37+
"start": "dumi dev",
38+
"test": "rc-test"
39+
},
40+
"lint-staged": {
41+
"**/*.{js,jsx,tsx,ts,md,json}": [
42+
"prettier --write",
43+
"git add"
44+
]
45+
},
46+
"dependencies": {
47+
"@babel/runtime": "^7.18.3",
48+
"@rc-component/trigger": "^1.7.0",
49+
"classnames": "^2.2.6",
50+
"rc-util": "^5.17.0"
3851
},
3952
"devDependencies": {
53+
"@testing-library/jest-dom": "^5.16.5",
54+
"@testing-library/react": "^14.0.0",
4055
"@types/classnames": "^2.2.6",
41-
"@types/enzyme": "^3.1.15",
42-
"@types/jest": "^26.0.12",
43-
"@types/react": "^16.8.19",
44-
"@types/react-dom": "^16.8.4",
56+
"@types/jest": "^29.0.0",
57+
"@types/react": "^18.0.0",
58+
"@types/react-dom": "^18.0.0",
4559
"@types/warning": "^3.0.0",
4660
"cross-env": "^7.0.0",
4761
"dumi": "^1.1.38",
48-
"enzyme": "^3.3.0",
49-
"enzyme-adapter-react-16": "^1.0.2",
50-
"enzyme-to-json": "^3.4.0",
5162
"father": "^2.13.2",
63+
"husky": "^8.0.3",
64+
"jest-environment-jsdom": "^29.5.0",
5265
"jquery": "^3.3.1",
5366
"less": "^3.11.1",
67+
"lint-staged": "^13.2.1",
5468
"np": "^6.0.0",
69+
"prettier": "^2.8.7",
5570
"rc-menu": "^9.5.2",
56-
"react": "^16.11.0",
57-
"react-dom": "^16.11.0",
71+
"rc-test": "^7.0.14",
72+
"react": "^18.0.0",
73+
"react-dom": "^18.0.0",
5874
"regenerator-runtime": "^0.13.9",
5975
"typescript": "^4.0.2"
6076
},
6177
"peerDependencies": {
6278
"react": ">=16.11.0",
6379
"react-dom": ">=16.11.0"
64-
},
65-
"dependencies": {
66-
"@babel/runtime": "^7.18.3",
67-
"classnames": "^2.2.6",
68-
"rc-trigger": "^5.3.1",
69-
"rc-util": "^5.17.0"
7080
}
7181
}

src/Dropdown.tsx

+27-49
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import * as React from 'react';
2-
import Trigger from 'rc-trigger';
3-
import type { TriggerProps } from 'rc-trigger';
2+
import Trigger from '@rc-component/trigger';
3+
import type { TriggerProps } from '@rc-component/trigger';
44
import classNames from 'classnames';
55
import type {
66
AnimationType,
77
AlignType,
88
BuildInPlacements,
99
ActionType,
10-
} from 'rc-trigger/lib/interface';
10+
} from '@rc-component/trigger/lib/interface';
1111
import Placements from './placements';
1212
import useAccessibility from './hooks/useAccessibility';
13+
import Overlay from './Overlay';
14+
import { composeRef, supportRef } from 'rc-util/lib/ref';
15+
import { ReactElement } from 'react';
1316

1417
export interface DropdownProps
1518
extends Pick<
@@ -60,34 +63,33 @@ function Dropdown(props: DropdownProps, ref) {
6063
visible,
6164
trigger = ['hover'],
6265
autoFocus,
66+
overlay,
67+
children,
68+
onVisibleChange,
6369
...otherProps
6470
} = props;
6571

6672
const [triggerVisible, setTriggerVisible] = React.useState<boolean>();
6773
const mergedVisible = 'visible' in props ? visible : triggerVisible;
6874

6975
const triggerRef = React.useRef(null);
76+
const overlayRef = React.useRef(null);
77+
const childRef = React.useRef(null);
7078
React.useImperativeHandle(ref, () => triggerRef.current);
7179

80+
const handleVisibleChange = (newVisible: boolean) => {
81+
setTriggerVisible(newVisible);
82+
onVisibleChange?.(newVisible);
83+
};
84+
7285
useAccessibility({
7386
visible: mergedVisible,
74-
setTriggerVisible,
75-
triggerRef,
76-
onVisibleChange: props.onVisibleChange,
87+
triggerRef: childRef,
88+
onVisibleChange: handleVisibleChange,
7789
autoFocus,
90+
overlayRef,
7891
});
7992

80-
const getOverlayElement = (): React.ReactElement => {
81-
const { overlay } = props;
82-
let overlayElement: React.ReactElement;
83-
if (typeof overlay === 'function') {
84-
overlayElement = overlay();
85-
} else {
86-
overlayElement = overlay;
87-
}
88-
return overlayElement;
89-
};
90-
9193
const onClick = (e) => {
9294
const { onOverlayClick } = props;
9395
setTriggerVisible(false);
@@ -97,27 +99,9 @@ function Dropdown(props: DropdownProps, ref) {
9799
}
98100
};
99101

100-
const onVisibleChange = (newVisible: boolean) => {
101-
const { onVisibleChange: onVisibleChangeProp } = props;
102-
setTriggerVisible(newVisible);
103-
if (typeof onVisibleChangeProp === 'function') {
104-
onVisibleChangeProp(newVisible);
105-
}
106-
};
107-
108-
const getMenuElement = () => {
109-
const overlayElement = getOverlayElement();
110-
111-
return (
112-
<>
113-
{arrow && <div className={`${prefixCls}-arrow`} />}
114-
{overlayElement}
115-
</>
116-
);
117-
};
102+
const getMenuElement = () => <Overlay ref={overlayRef} overlay={overlay} prefixCls={prefixCls} arrow={arrow} />
118103

119104
const getMenuElementOrLambda = () => {
120-
const { overlay } = props;
121105
if (typeof overlay === 'function') {
122106
return getMenuElement;
123107
}
@@ -141,16 +125,10 @@ function Dropdown(props: DropdownProps, ref) {
141125
return `${prefixCls}-open`;
142126
};
143127

144-
const renderChildren = () => {
145-
const { children } = props;
146-
const childrenProps = children.props ? children.props : {};
147-
const childClassName = classNames(childrenProps.className, getOpenClassName());
148-
return mergedVisible && children
149-
? React.cloneElement(children, {
150-
className: childClassName,
151-
})
152-
: children;
153-
};
128+
const childrenNode = React.cloneElement(children, {
129+
className: classNames(children.props?.className, mergedVisible && getOpenClassName()),
130+
ref: supportRef(children) ? composeRef(childRef, (children as ReactElement & {ref: React.Ref<HTMLElement>}).ref) : undefined,
131+
})
154132

155133
let triggerHideAction = hideAction;
156134
if (!triggerHideAction && trigger.indexOf('contextMenu') !== -1) {
@@ -169,19 +147,19 @@ function Dropdown(props: DropdownProps, ref) {
169147
popupStyle={overlayStyle}
170148
action={trigger}
171149
showAction={showAction}
172-
hideAction={triggerHideAction || []}
150+
hideAction={triggerHideAction}
173151
popupPlacement={placement}
174152
popupAlign={align}
175153
popupTransitionName={transitionName}
176154
popupAnimation={animation}
177155
popupVisible={mergedVisible}
178156
stretch={getMinOverlayWidthMatchTrigger() ? 'minWidth' : ''}
179157
popup={getMenuElementOrLambda()}
180-
onPopupVisibleChange={onVisibleChange}
158+
onPopupVisibleChange={handleVisibleChange}
181159
onPopupClick={onClick}
182160
getPopupContainer={getPopupContainer}
183161
>
184-
{renderChildren()}
162+
{childrenNode}
185163
</Trigger>
186164
);
187165
}

src/Overlay.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { forwardRef, ReactElement, useMemo } from 'react';
2+
import type { DropdownProps } from './Dropdown';
3+
import { composeRef, supportRef } from 'rc-util/lib/ref';
4+
5+
export type OverlayProps = Pick<DropdownProps, 'overlay' | 'arrow' | 'prefixCls'>
6+
7+
const Overlay = forwardRef<HTMLElement, OverlayProps>((props, ref) => {
8+
const {overlay, arrow, prefixCls} = props;
9+
10+
const overlayNode = useMemo(() => {
11+
let overlayElement: React.ReactElement;
12+
if (typeof overlay === 'function') {
13+
overlayElement = overlay();
14+
} else {
15+
overlayElement = overlay;
16+
}
17+
return overlayElement;
18+
}, [overlay]);
19+
20+
const composedRef = composeRef(ref, (overlayNode as ReactElement & {ref: React.Ref<HTMLElement>})?.ref);
21+
22+
return (
23+
<>
24+
{arrow && <div className={`${prefixCls}-arrow`} />}
25+
{React.cloneElement(overlayNode, { ref: supportRef(overlayNode) ? composedRef : undefined })}
26+
</>
27+
)
28+
});
29+
30+
export default Overlay;

src/hooks/useAccessibility.ts

+12-19
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,36 @@
1-
import * as React from 'react';
2-
import KeyCode from 'rc-util/lib/KeyCode';
3-
import raf from 'rc-util/lib/raf';
4-
import { getFocusNodeList } from 'rc-util/lib/Dom/focus';
1+
import KeyCode from "rc-util/lib/KeyCode";
2+
import raf from "rc-util/lib/raf";
3+
import * as React from "react";
54

65
const { ESC, TAB } = KeyCode;
76

87
interface UseAccessibilityProps {
98
visible: boolean;
10-
setTriggerVisible: (visible: boolean) => void;
119
triggerRef: React.RefObject<any>;
1210
onVisibleChange?: (visible: boolean) => void;
1311
autoFocus?: boolean;
12+
overlayRef?: React.RefObject<any>;
1413
}
1514

1615
export default function useAccessibility({
1716
visible,
18-
setTriggerVisible,
1917
triggerRef,
2018
onVisibleChange,
2119
autoFocus,
20+
overlayRef,
2221
}: UseAccessibilityProps) {
2322
const focusMenuRef = React.useRef<boolean>(false);
2423

2524
const handleCloseMenuAndReturnFocus = () => {
26-
if (visible && triggerRef.current) {
27-
triggerRef.current?.triggerRef?.current?.focus?.();
28-
setTriggerVisible(false);
29-
if (typeof onVisibleChange === 'function') {
30-
onVisibleChange(false);
31-
}
25+
if (visible) {
26+
triggerRef.current?.focus?.();
27+
onVisibleChange?.(false);
3228
}
3329
};
3430

3531
const focusMenu = () => {
36-
const elements = getFocusNodeList(triggerRef.current?.popupRef?.current?.getElement?.());
37-
const firstElement = elements[0];
38-
39-
if (firstElement?.focus) {
40-
firstElement.focus();
32+
if (overlayRef.current?.focus) {
33+
overlayRef.current.focus();
4134
focusMenuRef.current = true;
4235
return true;
4336
}
@@ -67,13 +60,13 @@ export default function useAccessibility({
6760

6861
React.useEffect(() => {
6962
if (visible) {
70-
window.addEventListener('keydown', handleKeyDown);
63+
window.addEventListener("keydown", handleKeyDown);
7164
if (autoFocus) {
7265
// FIXME: hack with raf
7366
raf(focusMenu, 3);
7467
}
7568
return () => {
76-
window.removeEventListener('keydown', handleKeyDown);
69+
window.removeEventListener("keydown", handleKeyDown);
7770
focusMenuRef.current = false;
7871
};
7972
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Trigger from '@rc-component/trigger/lib/mock';
2+
3+
export default Trigger;

tests/__mocks__/rc-trigger.js

-3
This file was deleted.

0 commit comments

Comments
 (0)