Skip to content

Commit a318fc5

Browse files
authoredNov 6, 2019
refactor: upgrade rc trigger (#87)
* refactor: upgrade rc-trigger * refactor test suite * use getRootDomNode replace findDomNode achieve minOverlayWidthMatchTrigger * merge visible state and use trigger stretch prop replace afterPopupVisibleChange * remove extra code * test: use rc-util tool and remove extra test code * fix: typescript type * update package.json for any branch * v3.0.0-alpha.0
1 parent 993fad0 commit a318fc5

33 files changed

+701
-712
lines changed
 

‎.eslintrc.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const base = require('@umijs/fabric/dist/eslint');
2+
3+
module.exports = {
4+
...base,
5+
rules: {
6+
...base.rules,
7+
'no-template-curly-in-string': 0,
8+
'prefer-promise-reject-errors': 0,
9+
'react/no-array-index-key': 0,
10+
'react/sort-comp': 0,
11+
'@typescript-eslint/no-explicit-any': 0,
12+
'jsx-a11y/label-has-associated-control': 0,
13+
'jsx-a11y/label-has-for': 0,
14+
'no-shadow': 0
15+
},
16+
};

‎.fatherrc.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default {
2+
cjs: 'babel',
3+
esm: { type: 'babel', importLibToEs: true },
4+
preCommit: {
5+
eslint: true,
6+
prettier: true,
7+
},
8+
};

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.storybook
12
*.iml
23
*.log
34
.idea/

‎.travis.yml

+6-26
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,17 @@
11
language: node_js
22

3-
sudo: false
4-
5-
notifications:
6-
email:
7-
- yiminghe@gmail.com
8-
93
node_js:
10-
- 6.0.0
4+
- 10
115

12-
before_install:
13-
- |
14-
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/'
15-
then
16-
echo "Only docs were updated, stopping build process."
17-
exit
18-
fi
19-
npm install npm@3.x -g
20-
phantomjs --version
216
script:
22-
- |
7+
- |
238
if [ "$TEST_TYPE" = test ]; then
24-
npm test
9+
npm run coverage && \
10+
bash <(curl -s https://codecov.io/bash)
2511
else
2612
npm run $TEST_TYPE
2713
fi
2814
env:
2915
matrix:
30-
- TEST_TYPE=lint
31-
- TEST_TYPE=test
32-
- TEST_TYPE=coverage
33-
34-
35-
matrix:
36-
allow_failures:
37-
- env: "TEST_TYPE=saucelabs"
16+
- TEST_TYPE=lint
17+
- TEST_TYPE=test

‎LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The MIT License (MIT)
2+
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in
12+
all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

‎LICENSE.md

-9
This file was deleted.

‎examples/context-menu.html

-1
This file was deleted.

‎examples/context-menu.js

+30-35
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,36 @@
11
/* eslint-disable no-console */
2-
import Dropdown from 'rc-dropdown';
3-
import Menu, { Item as MenuItem } from 'rc-menu';
4-
import 'rc-dropdown/assets/index.less';
5-
import React, { Component } from 'react';
6-
import ReactDOM from 'react-dom';
2+
import Menu, { Item as MenuItem } from 'rc-menu'
3+
import '../assets/index.less'
4+
import React from 'react'
5+
import Dropdown from '../src'
76

8-
class Demo extends Component {
9-
render() {
10-
const menu = (
11-
<Menu
12-
style={{ width: 140 }}
13-
>
14-
<MenuItem key="1">one</MenuItem>
15-
<MenuItem key="2">two</MenuItem>
16-
</Menu>
17-
);
7+
function ContextMenu() {
8+
const menu = (
9+
<Menu style={{ width: 140 }}>
10+
<MenuItem key="1">one</MenuItem>
11+
<MenuItem key="2">two</MenuItem>
12+
</Menu>
13+
)
1814

19-
return (
20-
<Dropdown
21-
trigger={['contextMenu']}
22-
overlay={menu}
23-
animation="slide-up"
24-
alignPoint
15+
return (
16+
<Dropdown
17+
trigger={['contextMenu']}
18+
overlay={menu}
19+
animation="slide-up"
20+
alignPoint
21+
>
22+
<div
23+
role="button"
24+
style={{
25+
border: '1px solid #000',
26+
padding: '100px 0',
27+
textAlign: 'center',
28+
}}
2529
>
26-
<div
27-
role="button"
28-
style={{
29-
border: '1px solid #000',
30-
padding: '100px 0',
31-
textAlign: 'center',
32-
}}
33-
>
34-
Right click me!
35-
</div>
36-
</Dropdown>
37-
);
38-
}
30+
Right click me!
31+
</div>
32+
</Dropdown>
33+
)
3934
}
4035

41-
ReactDOM.render(<Demo />, document.getElementById('__react-content'));
36+
export default ContextMenu

‎examples/dropdown-menu-width.html

Whitespace-only changes.

‎examples/dropdown-menu-width.js

+17-24
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,40 @@
1-
/* eslint-disable no-console */
2-
import Dropdown from 'rc-dropdown';
3-
import Menu, { Item as MenuItem } from 'rc-menu';
4-
import 'rc-dropdown/assets/index.less';
5-
import React, { PureComponent } from 'react';
6-
import ReactDOM from 'react-dom';
1+
/* eslint-disable no-console,react/button-has-type */
2+
import Menu, { Item as MenuItem } from 'rc-menu'
3+
import '../assets/index.less'
4+
import React, { PureComponent } from 'react'
5+
import Dropdown from '../src'
76

87
class Example extends PureComponent {
9-
state = { longList: false };
8+
state = { longList: false }
9+
1010
short = () => {
11-
this.setState({ longList: false });
11+
this.setState({ longList: false })
1212
}
13+
1314
long = () => {
14-
this.setState({ longList: true });
15+
this.setState({ longList: true })
1516
}
17+
1618
render() {
1719
const menuItems = [
1820
<MenuItem key="1">1st item</MenuItem>,
1921
<MenuItem key="2">2nd item</MenuItem>,
20-
];
22+
]
2123

2224
if (this.state.longList) {
23-
menuItems.push(<MenuItem key="3">3rd LONG SUPER LONG item</MenuItem>);
25+
menuItems.push(<MenuItem key="3">3rd LONG SUPER LONG item</MenuItem>)
2426
}
25-
const menu = (
26-
<Menu>
27-
{menuItems}
28-
</Menu>
29-
);
27+
const menu = <Menu>{menuItems}</Menu>
3028
return (
3129
<div>
3230
<Dropdown overlay={menu}>
33-
<button>
34-
Actions
35-
</button>
31+
<button>Actions</button>
3632
</Dropdown>
3733
<button onClick={this.long}>Long List</button>
3834
<button onClick={this.short}>Short List</button>
3935
</div>
40-
);
36+
)
4137
}
4238
}
4339

44-
ReactDOM.render(
45-
<Example />,
46-
document.getElementById('__react-content')
47-
);
40+
export default Example

‎examples/multiple.html

Whitespace-only changes.

‎examples/multiple.js

+18-21
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
1-
/* eslint-disable no-console */
2-
import Dropdown from 'rc-dropdown';
3-
import Menu, { Item as MenuItem, Divider } from 'rc-menu';
4-
import 'rc-dropdown/assets/index.less';
5-
import React, { Component } from 'react';
6-
import ReactDOM from 'react-dom';
1+
/* eslint-disable no-console,react/button-has-type */
2+
import Menu, { Item as MenuItem, Divider } from 'rc-menu'
3+
import '../assets/index.less'
4+
import React, { Component } from 'react'
5+
import Dropdown from '../src'
76

87
class Test extends Component {
98
state = {
109
visible: false,
11-
};
10+
}
1211

13-
onVisibleChange = (visible) => {
12+
onVisibleChange = visible => {
13+
console.log('visible', visible)
1414
this.setState({
1515
visible,
16-
});
16+
})
1717
}
1818

19-
selected = [];
19+
selected = []
2020

2121
saveSelected = ({ selectedKeys }) => {
22-
this.selected = selectedKeys;
22+
this.selected = selectedKeys
2323
}
2424

2525
confirm = () => {
26-
console.log(this.selected);
26+
console.log(this.selected)
2727
this.setState({
2828
visible: false,
29-
});
29+
})
3030
}
3131

3232
render() {
@@ -48,11 +48,12 @@ class Test extends Component {
4848
pointerEvents: 'visible',
4949
}}
5050
onClick={this.confirm}
51-
>确定
51+
>
52+
确定
5253
</button>
5354
</MenuItem>
5455
</Menu>
55-
);
56+
)
5657

5758
return (
5859
<Dropdown
@@ -65,12 +66,8 @@ class Test extends Component {
6566
>
6667
<button>open</button>
6768
</Dropdown>
68-
);
69+
)
6970
}
7071
}
7172

72-
ReactDOM.render(
73-
<div style={{ margin: 20 }}>
74-
<Test />
75-
</div>
76-
, document.getElementById('__react-content'));
73+
export default Test

‎examples/overlay-callback.html

Whitespace-only changes.

‎examples/overlay-callback.js

+24-23
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
/* eslint-disable no-console */
2-
import Dropdown from 'rc-dropdown';
3-
import Menu, { Item as MenuItem, Divider } from 'rc-menu';
4-
import 'rc-dropdown/assets/index.less';
5-
import React from 'react';
6-
import ReactDOM from 'react-dom';
1+
/* eslint-disable no-console,react/button-has-type */
2+
import Menu, { Item as MenuItem, Divider } from 'rc-menu'
3+
import '../assets/index.less'
4+
import React from 'react'
5+
import Dropdown from '../src'
76

87
function onSelect({ key }) {
9-
console.log(`${key} selected`);
8+
console.log(`${key} selected`)
109
}
1110

1211
function onVisibleChange(visible) {
13-
console.log(visible);
12+
console.log(visible)
1413
}
1514

1615
const menuCallback = () => (
@@ -20,20 +19,22 @@ const menuCallback = () => (
2019
<Divider />
2120
<MenuItem key="2">two</MenuItem>
2221
</Menu>
23-
);
22+
)
2423

25-
ReactDOM.render(
26-
<div style={{ margin: 20 }}>
27-
<div style={{ height: 100 }}/>
28-
<div>
29-
<Dropdown
30-
trigger={['click']}
31-
overlay={menuCallback}
32-
animation="slide-up"
33-
onVisibleChange={onVisibleChange}
34-
>
35-
<button style={{ width: 100 }}>open</button>
36-
</Dropdown>
24+
export default function OverlayCallback() {
25+
return (
26+
<div style={{ margin: 20 }}>
27+
<div style={{ height: 100 }} />
28+
<div>
29+
<Dropdown
30+
trigger={['click']}
31+
overlay={menuCallback}
32+
animation="slide-up"
33+
onVisibleChange={onVisibleChange}
34+
>
35+
<button style={{ width: 100 }}>open</button>
36+
</Dropdown>
37+
</div>
3738
</div>
38-
</div>
39-
, document.getElementById('__react-content'));
39+
)
40+
}

‎examples/simple.html

Whitespace-only changes.

‎examples/simple.js

+24-23
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
/* eslint-disable no-console */
2-
import Dropdown from 'rc-dropdown';
3-
import Menu, { Item as MenuItem, Divider } from 'rc-menu';
4-
import 'rc-dropdown/assets/index.less';
5-
import React from 'react';
6-
import ReactDOM from 'react-dom';
1+
/* eslint-disable no-console,react/button-has-type */
2+
import Menu, { Item as MenuItem, Divider } from 'rc-menu'
3+
import '../assets/index.less'
4+
import React from 'react'
5+
import Dropdown from '../src'
76

87
function onSelect({ key }) {
9-
console.log(`${key} selected`);
8+
console.log(`${key} selected`)
109
}
1110

1211
function onVisibleChange(visible) {
13-
console.log(visible);
12+
console.log(visible)
1413
}
1514

1615
const menu = (
@@ -20,20 +19,22 @@ const menu = (
2019
<Divider />
2120
<MenuItem key="2">two</MenuItem>
2221
</Menu>
23-
);
22+
)
2423

25-
ReactDOM.render(
26-
<div style={{ margin: 20 }}>
27-
<div style={{ height: 100 }}/>
28-
<div>
29-
<Dropdown
30-
trigger={['click']}
31-
overlay={menu}
32-
animation="slide-up"
33-
onVisibleChange={onVisibleChange}
34-
>
35-
<button style={{ width: 100 }}>open</button>
36-
</Dropdown>
24+
export default function Simple() {
25+
return (
26+
<div style={{ margin: 20 }}>
27+
<div style={{ height: 100 }} />
28+
<div>
29+
<Dropdown
30+
trigger={['click']}
31+
overlay={menu}
32+
animation="slide-up"
33+
onVisibleChange={onVisibleChange}
34+
>
35+
<button style={{ width: 100 }}>open</button>
36+
</Dropdown>
37+
</div>
3738
</div>
38-
</div>
39-
, document.getElementById('__react-content'));
39+
)
40+
}

‎index.d.ts

-26
This file was deleted.

‎index.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1+
'use strict';
12

2-
var Dropdown = require('./src/');
3-
4-
module.exports = Dropdown;
3+
module.exports = require('./src');

‎now.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"version": 2,
3+
"name": "rc-dropdown",
4+
"builds": [
5+
{
6+
"src": "package.json",
7+
"use": "@now/static-build",
8+
"config": { "distDir": ".doc" }
9+
}
10+
]
11+
}

‎package.json

+30-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rc-dropdown",
3-
"version": "2.4.1",
3+
"version": "3.0.0-alpha.0",
44
"description": "dropdown ui component for react",
55
"keywords": [
66
"react",
@@ -26,43 +26,42 @@
2626
"main": "lib/index",
2727
"module": "./es/index",
2828
"license": "MIT",
29-
"config": {
30-
"port": 8006
31-
},
3229
"scripts": {
33-
"build": "rc-tools run build",
34-
"compile": "rc-tools run compile --babel-runtime",
35-
"gh-pages": "rc-tools run gh-pages",
36-
"start": "rc-tools run server",
37-
"pub": "rc-tools run pub",
38-
"lint": "rc-tools run lint",
39-
"karma": "rc-test run karma",
40-
"saucelabs": "rc-test run saucelabs",
41-
"test": "rc-test run test",
42-
"chrome-test": "rc-test run chrome-test",
43-
"coverage": "rc-test run coverage"
30+
"start": "cross-env NODE_ENV=development father doc dev --storybook",
31+
"build": "father doc build --storybook",
32+
"compile": "father build",
33+
"prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish --any-branch",
34+
"lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
35+
"test": "father test",
36+
"coverage": "father test --coverage",
37+
"now-build": "npm run build"
4438
},
4539
"devDependencies": {
46-
"async": "~2.6.1",
47-
"bootstrap": "~4.3.1",
48-
"core-js": "^2.5.1",
49-
"expect.js": "~0.3.1",
40+
"@types/classnames": "^2.2.6",
41+
"@types/enzyme": "^3.1.15",
42+
"@types/jest": "^24.0.18",
43+
"@types/react": "^16.8.19",
44+
"@types/react-dom": "^16.8.4",
45+
"@types/warning": "^3.0.0",
46+
"cross-env": "^6.0.0",
47+
"enzyme": "^3.3.0",
48+
"enzyme-adapter-react-16": "^1.0.2",
49+
"father": "^2.13.2",
5050
"jquery": "^3.3.1",
51-
"pre-commit": "1.x",
52-
"rc-menu": "^7.4.20",
53-
"rc-test": "^6.0.8",
54-
"rc-tools": "^7.0.2",
55-
"react": "^16.0.0",
56-
"react-dom": "^16.0.0"
51+
"np": "^5.0.3",
52+
"rc-menu": "^8.0.0-alpha.2",
53+
"rc-util": "^4.14.2",
54+
"react": "^16.11.0",
55+
"react-dom": "^16.0.0",
56+
"typescript": "^3.5.2"
57+
},
58+
"peerDependencies": {
59+
"react": "*",
60+
"react-dom": "*"
5761
},
58-
"pre-commit": [
59-
"lint"
60-
],
6162
"dependencies": {
6263
"babel-runtime": "^6.26.0",
6364
"classnames": "^2.2.6",
64-
"prop-types": "^15.5.8",
65-
"rc-trigger": "^2.5.1",
66-
"react-lifecycles-compat": "^3.0.2"
65+
"rc-trigger": "^4.0.0-alpha.4"
6766
}
6867
}

‎src/Dropdown.jsx

-221
This file was deleted.

‎src/Dropdown.tsx

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import * as React from 'react';
2+
import Trigger, { TriggerProps } from 'rc-trigger';
3+
import classNames from 'classnames';
4+
import { AnimationType, AlignType, BuildInPlacements, ActionType } from 'rc-trigger/lib/interface';
5+
import Placements from './placements';
6+
7+
export interface DropdownProps extends Pick<TriggerProps, 'getPopupContainer' | 'children'> {
8+
minOverlayWidthMatchTrigger?: boolean;
9+
onVisibleChange?: (visible: boolean) => void;
10+
onOverlayClick?: (e: Event) => void;
11+
prefixCls?: string;
12+
transitionName?: string;
13+
overlayClassName?: string;
14+
openClassName?: string;
15+
animation?: AnimationType;
16+
align?: AlignType;
17+
overlayStyle?: React.CSSProperties;
18+
placement?: string;
19+
placements?: BuildInPlacements;
20+
overlay?: (() => React.ReactElement) | React.ReactElement;
21+
trigger?: ActionType | ActionType[];
22+
alignPoint?: boolean;
23+
showAction?: ActionType[];
24+
hideAction?: ActionType[];
25+
visible?: boolean;
26+
}
27+
28+
function Dropdown(props: DropdownProps, ref) {
29+
const {
30+
prefixCls = 'rc-dropdown',
31+
transitionName,
32+
animation,
33+
align,
34+
placement = 'bottomLeft',
35+
placements = Placements,
36+
getPopupContainer,
37+
showAction,
38+
hideAction,
39+
overlayClassName,
40+
overlayStyle,
41+
visible,
42+
trigger = ['hover'],
43+
...otherProps
44+
} = props;
45+
46+
const [triggerVisible, setTriggerVisible] = React.useState<boolean>();
47+
const mergedVisible = 'visible' in props ? visible : triggerVisible;
48+
49+
const triggerRef = React.useRef(null);
50+
React.useImperativeHandle(ref, () => triggerRef.current);
51+
52+
const getOverlayElement = (): React.ReactElement => {
53+
const { overlay } = props;
54+
let overlayElement: React.ReactElement;
55+
if (typeof overlay === 'function') {
56+
overlayElement = overlay();
57+
} else {
58+
overlayElement = overlay;
59+
}
60+
return overlayElement;
61+
};
62+
63+
const onClick = e => {
64+
const { onOverlayClick } = props;
65+
const overlayProps = getOverlayElement().props;
66+
setTriggerVisible(false);
67+
68+
if (onOverlayClick) {
69+
onOverlayClick(e);
70+
}
71+
if (overlayProps.onClick) {
72+
overlayProps.onClick(e);
73+
}
74+
};
75+
76+
const onVisibleChange = (visible: boolean) => {
77+
const { onVisibleChange } = props;
78+
setTriggerVisible(visible);
79+
if (typeof onVisibleChange === 'function') {
80+
onVisibleChange(visible);
81+
}
82+
};
83+
84+
const getMenuElement = () => {
85+
const overlayElement = getOverlayElement();
86+
const extraOverlayProps = {
87+
prefixCls: `${prefixCls}-menu`,
88+
onClick,
89+
};
90+
if (typeof overlayElement.type === 'string') {
91+
delete extraOverlayProps.prefixCls;
92+
}
93+
return React.cloneElement(overlayElement, extraOverlayProps);
94+
};
95+
96+
const getMenuElementOrLambda = () => {
97+
const { overlay } = props;
98+
if (typeof overlay === 'function') {
99+
return getMenuElement;
100+
}
101+
return getMenuElement();
102+
};
103+
104+
const getMinOverlayWidthMatchTrigger = () => {
105+
const { minOverlayWidthMatchTrigger, alignPoint } = props;
106+
if ('minOverlayWidthMatchTrigger' in props) {
107+
return minOverlayWidthMatchTrigger;
108+
}
109+
110+
return !alignPoint;
111+
};
112+
113+
const getOpenClassName = () => {
114+
const { openClassName } = props;
115+
if (openClassName !== undefined) {
116+
return openClassName;
117+
}
118+
return `${prefixCls}-open`;
119+
};
120+
121+
const renderChildren = () => {
122+
const { children } = props;
123+
const childrenProps = children.props ? children.props : {};
124+
const childClassName = classNames(childrenProps.className, getOpenClassName());
125+
return triggerVisible && children
126+
? React.cloneElement(children, {
127+
className: childClassName,
128+
})
129+
: children;
130+
};
131+
132+
let triggerHideAction = hideAction;
133+
if (!triggerHideAction && trigger.indexOf('contextMenu') !== -1) {
134+
triggerHideAction = ['click'];
135+
}
136+
137+
return (
138+
<Trigger
139+
{...otherProps}
140+
prefixCls={prefixCls}
141+
ref={triggerRef}
142+
popupClassName={overlayClassName}
143+
popupStyle={overlayStyle}
144+
builtinPlacements={placements}
145+
action={trigger}
146+
showAction={showAction}
147+
hideAction={triggerHideAction || []}
148+
popupPlacement={placement}
149+
popupAlign={align}
150+
popupTransitionName={transitionName}
151+
popupAnimation={animation}
152+
popupVisible={mergedVisible}
153+
stretch={getMinOverlayWidthMatchTrigger() ? 'minWidth' : ''}
154+
popup={getMenuElementOrLambda()}
155+
onPopupVisibleChange={onVisibleChange}
156+
getPopupContainer={getPopupContainer}
157+
>
158+
{renderChildren()}
159+
</Trigger>
160+
);
161+
}
162+
163+
export default React.forwardRef(Dropdown);

‎src/index.js

-2
This file was deleted.

‎src/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Dropdown from './Dropdown'
2+
3+
export default Dropdown

‎src/placements.js ‎src/placements.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
const autoAdjustOverflow = {
22
adjustX: 1,
33
adjustY: 1,
4-
};
4+
}
55

6-
const targetOffset = [0, 0];
6+
const targetOffset = [0, 0]
77

8-
export const placements = {
8+
const placements = {
99
topLeft: {
1010
points: ['bl', 'tl'],
1111
overflow: autoAdjustOverflow,
@@ -42,6 +42,6 @@ export const placements = {
4242
offset: [0, 4],
4343
targetOffset,
4444
},
45-
};
45+
}
4646

47-
export default placements;
47+
export default placements

‎tests/__mocks__/rc-trigger.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Trigger from 'rc-trigger/lib/mock';
2+
3+
export default Trigger;

‎tests/basic.js

-207
This file was deleted.

‎tests/basic.test.js

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/* eslint-disable react/button-has-type,react/no-find-dom-node,react/no-render-return-value,object-shorthand,func-names,max-len */
2+
import React from 'react';
3+
import { mount } from 'enzyme';
4+
import Menu, { Item as MenuItem, Divider } from 'rc-menu';
5+
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
6+
import { sleep, getPopupDomNode } from './utils';
7+
import Dropdown from '../src';
8+
import placements from '../src/placements';
9+
import '../assets/index.less';
10+
11+
spyElementPrototypes(HTMLElement, {
12+
offsetLeft: {
13+
get: function() {
14+
return parseFloat(window.getComputedStyle(this).marginLeft) || 0;
15+
},
16+
},
17+
offsetTop: {
18+
get: function() {
19+
return parseFloat(window.getComputedStyle(this).marginTop) || 0;
20+
},
21+
},
22+
offsetHeight: {
23+
get: function() {
24+
return parseFloat(window.getComputedStyle(this).height) || 0;
25+
},
26+
},
27+
offsetWidth: {
28+
get: function() {
29+
return parseFloat(window.getComputedStyle(this).width) || 0;
30+
},
31+
},
32+
});
33+
34+
describe('dropdown', () => {
35+
it('default visible', () => {
36+
const dropdown = mount(
37+
<Dropdown overlay={<div className="check-for-visible">Test</div>} visible>
38+
<button className="my-button">open</button>
39+
</Dropdown>,
40+
);
41+
expect(getPopupDomNode(dropdown) instanceof HTMLDivElement).toBeTruthy();
42+
});
43+
44+
it('simply works', async () => {
45+
let clicked;
46+
let overlayClicked;
47+
48+
function onClick({ key }) {
49+
clicked = key;
50+
}
51+
52+
function onOverlayClick({ key }) {
53+
overlayClicked = key;
54+
}
55+
56+
const menu = (
57+
<Menu style={{ width: 140 }} onClick={onClick}>
58+
<MenuItem key="1">
59+
<span className="my-menuitem">one</span>
60+
</MenuItem>
61+
<Divider />
62+
<MenuItem key="2">two</MenuItem>
63+
</Menu>
64+
);
65+
const dropdown = mount(
66+
<Dropdown trigger={['click']} overlay={menu} onOverlayClick={onOverlayClick}>
67+
<button className="my-button">open</button>
68+
</Dropdown>,
69+
);
70+
expect(dropdown.find('.my-button')).toBeTruthy();
71+
expect(dropdown.find('.rc-dropdown')).toBeTruthy();
72+
73+
dropdown.find('.my-button').simulate('click');
74+
expect(clicked).toBeUndefined();
75+
expect(getPopupDomNode(dropdown).classList.contains('rc-dropdown-hidden')).toBe(false);
76+
77+
dropdown.find('.my-menuitem').simulate('click');
78+
expect(clicked).toBe('1');
79+
expect(overlayClicked).toBe('1');
80+
expect(getPopupDomNode(dropdown).classList.contains('rc-dropdown-hidden')).toBe(true);
81+
});
82+
83+
it('re-align works', async () => {
84+
const buttonStyle = { width: 600, height: 20, marginLeft: 100 };
85+
const menu = (
86+
<Menu>
87+
<MenuItem key="1">one</MenuItem>
88+
</Menu>
89+
);
90+
const dropdown = mount(
91+
<Dropdown trigger={['click']} placement="bottomRight" overlay={menu}>
92+
<button className="my-btn" style={buttonStyle}>
93+
open
94+
</button>
95+
</Dropdown>,
96+
);
97+
98+
dropdown.find('.my-btn').simulate('click');
99+
await sleep(500);
100+
expect(getPopupDomNode(dropdown).getAttribute('style')).toEqual(
101+
expect.stringContaining(
102+
`left: -${999 - buttonStyle.width - placements.bottomLeft.offset[0]}px; top: -${999 -
103+
buttonStyle.height -
104+
placements.bottomLeft.offset[1]}px;`,
105+
),
106+
);
107+
});
108+
109+
// https://github.com/ant-design/ant-design/issues/9559
110+
it('should have correct menu width when switch from shorter menu to longer', async () => {
111+
class Example extends React.Component {
112+
state = { longList: true };
113+
114+
getPopupDomNode() {
115+
return this.trigger.getPopupDomNode();
116+
}
117+
118+
short = () => {
119+
this.setState({ longList: false });
120+
};
121+
122+
long = () => {
123+
this.setState({ longList: true });
124+
};
125+
126+
render() {
127+
const menuItems = [
128+
<MenuItem key="1">1st item</MenuItem>,
129+
<MenuItem key="2">2nd item</MenuItem>,
130+
];
131+
if (this.state.longList) {
132+
menuItems.push(<MenuItem key="3">3rd LONG SUPER LONG item</MenuItem>);
133+
}
134+
return (
135+
<Dropdown
136+
trigger={['click']}
137+
ref={node => {
138+
this.trigger = node;
139+
}}
140+
overlay={<Menu>{menuItems}</Menu>}
141+
>
142+
<button>Actions 111</button>
143+
</Dropdown>
144+
);
145+
}
146+
}
147+
const dropdown = mount(<Example />);
148+
dropdown.find('button').simulate('click');
149+
await sleep(500);
150+
expect(getPopupDomNode(dropdown).getAttribute('style')).toEqual(
151+
expect.stringContaining(
152+
`left: -${999 - placements.bottomLeft.offset[0]}px; top: -${999 -
153+
placements.bottomLeft.offset[1]}px;`,
154+
),
155+
);
156+
157+
// Todo - offsetwidth
158+
});
159+
160+
it('Test default minOverlayWidthMatchTrigger', async () => {
161+
const overlayWidth = 50;
162+
const overlay = <div style={{ width: overlayWidth }}>Test</div>;
163+
164+
const dropdown = mount(
165+
<Dropdown trigger={['click']} overlay={overlay}>
166+
<button style={{ width: 100 }} className="my-button">
167+
open
168+
</button>
169+
</Dropdown>,
170+
);
171+
172+
dropdown.find('.my-button').simulate('click');
173+
await sleep(500);
174+
expect(getPopupDomNode(dropdown).getAttribute('style')).toEqual(
175+
expect.stringContaining('min-width: 100px'),
176+
);
177+
});
178+
179+
it('user pass minOverlayWidthMatchTrigger', async () => {
180+
const overlayWidth = 50;
181+
const overlay = <div style={{ width: overlayWidth }}>Test</div>;
182+
183+
const dropdown = mount(
184+
<Dropdown trigger={['click']} overlay={overlay} minOverlayWidthMatchTrigger={false}>
185+
<button style={{ width: 100 }} className="my-button">
186+
open
187+
</button>
188+
</Dropdown>,
189+
);
190+
191+
dropdown.find('.my-button').simulate('click');
192+
await sleep(500);
193+
expect(getPopupDomNode(dropdown).getAttribute('style')).not.toEqual(
194+
expect.stringContaining('min-width: 100px'),
195+
);
196+
});
197+
198+
it('should support default openClassName', () => {
199+
const overlay = <div style={{ width: 50 }}>Test</div>;
200+
const dropdown = mount(
201+
<Dropdown trigger={['click']} overlay={overlay} minOverlayWidthMatchTrigger={false}>
202+
<button style={{ width: 100 }} className="my-button">
203+
open
204+
</button>
205+
</Dropdown>,
206+
);
207+
dropdown.find('.my-button').simulate('click');
208+
expect(dropdown.find('.my-button').prop('className')).toBe('my-button rc-dropdown-open');
209+
dropdown.find('.my-button').simulate('click');
210+
expect(dropdown.find('.my-button').prop('className')).toBe('my-button');
211+
});
212+
213+
it('should support custom openClassName', async () => {
214+
const overlay = <div style={{ width: 50 }}>Test</div>;
215+
const dropdown = mount(
216+
<Dropdown
217+
trigger={['click']}
218+
overlay={overlay}
219+
minOverlayWidthMatchTrigger={false}
220+
openClassName="opened"
221+
>
222+
<button style={{ width: 100 }} className="my-button">
223+
open
224+
</button>
225+
</Dropdown>,
226+
);
227+
228+
dropdown.find('.my-button').simulate('click');
229+
expect(dropdown.find('.my-button').prop('className')).toBe('my-button opened');
230+
dropdown.find('.my-button').simulate('click');
231+
expect(dropdown.find('.my-button').prop('className')).toBe('my-button');
232+
});
233+
234+
it('overlay callback', async () => {
235+
const overlay = <div style={{ width: 50 }}>Test</div>;
236+
const dropdown = mount(
237+
<Dropdown trigger={['click']} overlay={() => overlay}>
238+
<button className="my-button">open</button>
239+
</Dropdown>,
240+
);
241+
242+
dropdown.find('.my-button').simulate('click');
243+
expect(getPopupDomNode(dropdown).classList.contains('rc-dropdown-hidden')).toBe(false);
244+
});
245+
});

‎tests/index.js

-5
This file was deleted.

‎tests/point.js

-50
This file was deleted.

‎tests/point.test.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* eslint-disable react/button-has-type,react/no-render-return-value */
2+
import React from 'react';
3+
import { mount } from 'enzyme';
4+
import Dropdown from '../src';
5+
import placements from '../src/placements';
6+
import { sleep, getPopupDomNode } from './utils';
7+
8+
describe('point', () => {
9+
it('click show', async () => {
10+
const overlay = (
11+
<div
12+
className="check-for-visible"
13+
style={{
14+
width: 10,
15+
}}
16+
>
17+
Test
18+
</div>
19+
);
20+
21+
const dropdown = mount(
22+
<Dropdown
23+
trigger={['contextMenu']}
24+
overlay={overlay}
25+
alignPoint
26+
align={{
27+
points: ['tl'],
28+
overflow: {},
29+
}}
30+
>
31+
<button className="my-button">open</button>
32+
</Dropdown>,
33+
);
34+
35+
const pageStyle = {
36+
pageX: 9,
37+
pageY: 3,
38+
};
39+
40+
dropdown.find('.my-button').simulate('contextmenu', pageStyle);
41+
42+
await sleep(500);
43+
44+
expect(getPopupDomNode(dropdown).getAttribute('style')).toEqual(
45+
expect.stringContaining(
46+
`left: -${999 - pageStyle.pageX - placements.bottomLeft.offset[0]}px; top: -${999 -
47+
pageStyle.pageY -
48+
placements.bottomLeft.offset[1]}px;`,
49+
),
50+
);
51+
});
52+
});

‎tests/setup.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
global.requestAnimationFrame = global.requestAnimationFrame || function requestAnimationFrame(cb) {
2+
return setTimeout(cb, 0);
3+
};
4+
5+
const Enzyme = require('enzyme');
6+
const Adapter = require('enzyme-adapter-react-16');
7+
8+
Enzyme.configure({ adapter: new Adapter() });

‎tests/utils.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* eslint-disable no-param-reassign */
2+
export function sleep(timeout = 0) {
3+
return new Promise(resolve => {
4+
setTimeout(() => {
5+
resolve();
6+
}, timeout);
7+
});
8+
}
9+
10+
export function getPopupDomNode(wrapper) {
11+
return wrapper
12+
.find('Trigger')
13+
.instance()
14+
.getPopupDomNode();
15+
}

1 commit comments

Comments
 (1)
Please sign in to comment.