Skip to content

Commit 1ba5fd7

Browse files
authored
refactor: move features from menu of antd(#24975) (#325)
1 parent 732e368 commit 1ba5fd7

File tree

4 files changed

+560
-18
lines changed

4 files changed

+560
-18
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@babel/runtime": "^7.10.1",
4545
"classnames": "2.x",
4646
"mini-store": "^3.0.1",
47+
"omit.js": "^1.0.2",
4748
"rc-animate": "^3.1.0",
4849
"rc-trigger": "^4.2.0",
4950
"rc-util": "^5.0.1",

src/Menu.tsx

Lines changed: 183 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { Provider, create } from 'mini-store';
3+
import omit from 'omit.js';
34
import SubPopupMenu, { getActiveKey } from './SubPopupMenu';
45
import { noop } from './util';
56
import {
@@ -47,6 +48,8 @@ export interface MenuProps
4748
overflowedIndicator?: React.ReactNode;
4849
/** Menu motion define */
4950
motion?: MotionType;
51+
/** Default menu motion */
52+
defaultMotion?: MotionType;
5053

5154
/** @deprecated Please use `motion` instead */
5255
openTransitionName?: string;
@@ -55,9 +58,19 @@ export interface MenuProps
5558

5659
/** direction of menu */
5760
direction?: 'ltr' | 'rtl';
61+
62+
inlineCollapsed?: boolean;
63+
64+
/** SiderContextProps of layout in ant design */
65+
siderCollapsed?: boolean;
66+
collapsedWidth?: string | number;
67+
}
68+
69+
export interface MenuState {
70+
switchingModeFromInline: boolean;
5871
}
5972

60-
class Menu extends React.Component<MenuProps> {
73+
class Menu extends React.Component<MenuProps, MenuState> {
6174
static defaultProps = {
6275
selectable: true,
6376
onClick: noop,
@@ -96,6 +109,10 @@ class Menu extends React.Component<MenuProps> {
96109
openKeys,
97110
activeKey: { '0-menu-': getActiveKey(props, props.activeKey) },
98111
});
112+
113+
this.state = {
114+
switchingModeFromInline: false,
115+
};
99116
}
100117

101118
isRootMenu: boolean;
@@ -104,12 +121,89 @@ class Menu extends React.Component<MenuProps> {
104121

105122
innerMenu: typeof SubPopupMenu;
106123

124+
inlineOpenKeys: string[] = [];
125+
126+
prevOpenKeys: string[];
127+
107128
componentDidMount() {
108129
this.updateMiniStore();
130+
this.updateMenuDisplay();
109131
}
110132

111-
componentDidUpdate() {
133+
componentDidUpdate(prevProps: MenuProps) {
134+
this.updateOpentKeysWhenSwitchMode(prevProps);
112135
this.updateMiniStore();
136+
const { siderCollapsed, inlineCollapsed, onOpenChange } = this.props;
137+
if (
138+
(!prevProps.inlineCollapsed && inlineCollapsed) ||
139+
(!prevProps.siderCollapsed && siderCollapsed)
140+
) {
141+
onOpenChange([]);
142+
}
143+
this.updateMenuDisplay();
144+
}
145+
146+
updateOpentKeysWhenSwitchMode(prevProps: MenuProps) {
147+
const { props: nextProps, store, inlineOpenKeys } = this;
148+
const prevState = store.getState();
149+
const newState: any = {};
150+
if (prevProps.mode === 'inline' && nextProps.mode !== 'inline') {
151+
this.setState({
152+
switchingModeFromInline: true,
153+
});
154+
}
155+
156+
if (!('openKeys' in nextProps)) {
157+
// [Legacy] Old code will return after `openKeys` changed.
158+
// Not sure the reason, we should keep this logic still.
159+
if (
160+
(nextProps.inlineCollapsed && !prevProps.inlineCollapsed) ||
161+
(nextProps.siderCollapsed && !prevProps.siderCollapsed)
162+
) {
163+
this.setState({
164+
switchingModeFromInline: true,
165+
});
166+
this.inlineOpenKeys = prevState.openKeys.concat();
167+
newState.openKeys = [];
168+
}
169+
170+
if (
171+
(!nextProps.inlineCollapsed && prevProps.inlineCollapsed) ||
172+
(!nextProps.siderCollapsed && prevProps.siderCollapsed)
173+
) {
174+
newState.openKeys = inlineOpenKeys;
175+
this.inlineOpenKeys = [];
176+
}
177+
}
178+
179+
if (Object.keys(newState).length) {
180+
store.setState(newState);
181+
}
182+
}
183+
184+
updateMenuDisplay() {
185+
const {
186+
props: { collapsedWidth },
187+
store,
188+
prevOpenKeys,
189+
} = this;
190+
// https://github.com/ant-design/ant-design/issues/8587
191+
const hideMenu =
192+
this.getInlineCollapsed() &&
193+
(collapsedWidth === 0 ||
194+
collapsedWidth === '0' ||
195+
collapsedWidth === '0px');
196+
if (hideMenu) {
197+
this.prevOpenKeys = store.getState().openKeys.concat();
198+
this.store.setState({
199+
openKeys: [],
200+
});
201+
} else if (prevOpenKeys) {
202+
this.store.setState({
203+
openKeys: prevOpenKeys,
204+
});
205+
this.prevOpenKeys = null;
206+
}
113207
}
114208

115209
onSelect = (selectInfo: SelectInfo) => {
@@ -136,6 +230,19 @@ class Menu extends React.Component<MenuProps> {
136230
};
137231

138232
onClick: MenuClickEventHandler = e => {
233+
const mode = this.getRealMenuMode();
234+
const {
235+
store,
236+
props: { onOpenChange },
237+
} = this;
238+
239+
if (mode !== 'inline' && !('openKeys' in this.props)) {
240+
// closing vertical popup submenu after click it
241+
store.setState({
242+
openKeys: [],
243+
});
244+
onOpenChange([]);
245+
}
139246
this.props.onClick(e);
140247
};
141248

@@ -201,16 +308,71 @@ class Menu extends React.Component<MenuProps> {
201308
}
202309
};
203310

204-
getOpenTransitionName = () => {
205-
const { props } = this;
206-
let transitionName = props.openTransitionName;
207-
const animationName = props.openAnimation;
208-
if (!transitionName && typeof animationName === 'string') {
209-
transitionName = `${props.prefixCls}-open-${animationName}`;
311+
getRealMenuMode() {
312+
const { mode } = this.props;
313+
const { switchingModeFromInline } = this.state;
314+
const inlineCollapsed = this.getInlineCollapsed();
315+
if (switchingModeFromInline && inlineCollapsed) {
316+
return 'inline';
317+
}
318+
return inlineCollapsed ? 'vertical' : mode;
319+
}
320+
321+
getInlineCollapsed() {
322+
const { inlineCollapsed, siderCollapsed } = this.props;
323+
if (siderCollapsed !== undefined) {
324+
return siderCollapsed;
325+
}
326+
return inlineCollapsed;
327+
}
328+
329+
// Restore vertical mode when menu is collapsed responsively when mounted
330+
// https://github.com/ant-design/ant-design/issues/13104
331+
// TODO: not a perfect solution,
332+
// looking a new way to avoid setting switchingModeFromInline in this situation
333+
onMouseEnter = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
334+
this.restoreModeVerticalFromInline();
335+
const { onMouseEnter } = this.props;
336+
if (onMouseEnter) {
337+
onMouseEnter(e);
338+
}
339+
};
340+
341+
onTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
342+
// when inlineCollapsed menu width animation finished
343+
// https://github.com/ant-design/ant-design/issues/12864
344+
const widthCollapsed =
345+
e.propertyName === 'width' && e.target === e.currentTarget;
346+
347+
// Fix SVGElement e.target.className.indexOf is not a function
348+
// https://github.com/ant-design/ant-design/issues/15699
349+
const { className } = e.target as HTMLElement | SVGElement;
350+
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal,
351+
// unless during an animation.
352+
const classNameValue =
353+
Object.prototype.toString.call(className) === '[object SVGAnimatedString]'
354+
? className.animVal
355+
: className;
356+
357+
// Fix for <Menu style={{ width: '100%' }} />,
358+
// the width transition won't trigger when menu is collapsed
359+
// https://github.com/ant-design/ant-design-pro/issues/2783
360+
const iconScaled =
361+
e.propertyName === 'font-size' && classNameValue.indexOf('anticon') >= 0;
362+
if (widthCollapsed || iconScaled) {
363+
this.restoreModeVerticalFromInline();
210364
}
211-
return transitionName;
212365
};
213366

367+
restoreModeVerticalFromInline() {
368+
const { switchingModeFromInline } = this.state;
369+
if (switchingModeFromInline) {
370+
this.setState({
371+
switchingModeFromInline: false,
372+
});
373+
}
374+
}
375+
214376
setInnerMenu = node => {
215377
this.innerMenu = node;
216378
};
@@ -229,19 +391,29 @@ class Menu extends React.Component<MenuProps> {
229391
}
230392

231393
render() {
232-
let props: MenuProps & { parentMenu?: Menu } = { ...this.props };
394+
let props: MenuProps & { parentMenu?: Menu } = {
395+
...omit(this.props, [
396+
'collapsedWidth',
397+
'siderCollapsed',
398+
'defaultMotion',
399+
]),
400+
};
401+
const mode = this.getRealMenuMode();
233402
props.className += ` ${props.prefixCls}-root`;
234403
if (props.direction === 'rtl') {
235404
props.className += ` ${props.prefixCls}-rtl`;
236405
}
237406
props = {
238407
...props,
408+
mode,
239409
onClick: this.onClick,
240410
onOpenChange: this.onOpenChange,
241411
onDeselect: this.onDeselect,
242412
onSelect: this.onSelect,
413+
onMouseEnter: this.onMouseEnter,
414+
onTransitionEnd: this.onTransitionEnd,
243415
parentMenu: this,
244-
motion: getMotion(this.props),
416+
motion: getMotion(this.props, this.state),
245417
};
246418

247419
delete props.openAnimation;

src/utils/legacyUtil.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,26 @@ import { MotionType, TransitionNameType, OpenAnimation } from '../interface';
33

44
interface GetMotionProps {
55
motion?: MotionType;
6+
defaultMotion?: MotionType;
67
openAnimation?: OpenAnimation;
78
openTransitionName?: TransitionNameType;
89
prefixCls?: string;
910
}
1011

11-
export function getMotion({
12-
prefixCls,
13-
motion,
14-
openAnimation,
15-
openTransitionName,
16-
}: GetMotionProps): MotionType {
12+
interface GetMotionState {
13+
switchingModeFromInline: boolean;
14+
}
15+
16+
export function getMotion(
17+
{
18+
prefixCls,
19+
motion,
20+
defaultMotion,
21+
openAnimation,
22+
openTransitionName,
23+
}: GetMotionProps,
24+
{ switchingModeFromInline }: GetMotionState,
25+
): MotionType {
1726
if (motion) {
1827
return motion;
1928
}
@@ -35,5 +44,7 @@ export function getMotion({
3544
};
3645
}
3746

38-
return null;
47+
// When mode switch from inline
48+
// submenu should hide without animation
49+
return switchingModeFromInline ? null : defaultMotion;
3950
}

0 commit comments

Comments
 (0)