Skip to content

Commit 2bd3a32

Browse files
author
z1399
committed
feat: add api inlineMaxLevel
inline menu, specify at most a certain level of submenu, deeper submenu will right popover.
1 parent f468451 commit 2bd3a32

File tree

8 files changed

+165
-27
lines changed

8 files changed

+165
-27
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ ReactDOM.render(
219219
<th>24</th>
220220
<td>Padding level multiplier. Right or left padding depends on param "direction".</td>
221221
</tr>
222+
<tr>
223+
<td>inlineMaxLevel</td>
224+
<td>Number</td>
225+
<th></th>
226+
<td>inline menu, specify at most a certain level of submenu, deeper submenu will right popover</td>
227+
</tr>
222228
</tbody>
223229
</table>
224230

assets/index.less

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,21 @@
227227
}
228228
}
229229

230+
&-inline {
231+
.@{menuPrefixCls}-submenu-multi {
232+
.@{menuPrefixCls}-submenu-title {
233+
.@{menuPrefixCls}-submenu-arrow {
234+
transform: rotate(0);
235+
}
236+
}
237+
}
238+
.@{menuPrefixCls}-submenu-multi.@{menuPrefixCls}-submenu-open {
239+
.@{menuPrefixCls}-submenu-title {
240+
background-color: #eaf8fe;
241+
}
242+
}
243+
}
244+
230245
&-vertical&-sub,
231246
&-vertical-left&-sub,
232247
&-vertical-right&-sub {

docs/demo/antd-switch-multi.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## antd-switch-multi
2+
3+
inline Menu, up to two level menus, more submenus right popover
4+
5+
<code src="../examples/antd-switch-multi.tsx">

docs/examples/antd-switch-multi.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* eslint-disable no-console, react/require-default-props, no-param-reassign */
2+
3+
import React from 'react';
4+
import { CommonMenu, inlineMotion } from './antd';
5+
import '../../assets/index.less';
6+
7+
const Demo = () => {
8+
const [inline, setInline] = React.useState(false);
9+
const [openKeys, setOpenKey] = React.useState(['1']);
10+
11+
let restProps = {};
12+
if (inline) {
13+
restProps = { motion: inlineMotion };
14+
} else {
15+
restProps = { openAnimation: 'zoom' };
16+
}
17+
18+
return (
19+
<div style={{ margin: 20, width: 200 }}>
20+
<label>
21+
<input
22+
type="checkbox"
23+
checked={inline}
24+
onChange={() => setInline(!inline)}
25+
/>{' '}
26+
Inline
27+
</label>
28+
<CommonMenu
29+
mode="inline"
30+
inlineMaxLevel={2}
31+
openKeys={openKeys}
32+
onOpenChange={keys => {
33+
console.error('Open Keys Changed:', keys);
34+
setOpenKey(keys);
35+
}}
36+
inlineCollapsed={!inline}
37+
{...restProps}
38+
/>
39+
</div>
40+
);
41+
};
42+
43+
export default Demo;
44+
/* eslint-enable */

src/Menu.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { parseChildren } from './utils/nodeUtil';
2020
import MenuContextProvider from './context/MenuContext';
2121
import useMemoCallback from './hooks/useMemoCallback';
2222
import { warnItemProp } from './utils/warnUtil';
23+
import { genMultiMode } from './utils/multiModeUtil';
2324
import SubMenu from './SubMenu';
2425
import useAccessibility from './hooks/useAccessibility';
2526
import useUUID from './hooks/useUUID';
@@ -61,6 +62,7 @@ export interface MenuProps
6162

6263
// Mode
6364
mode?: MenuMode;
65+
inlineMaxLevel?: number;
6466
inlineCollapsed?: boolean;
6567

6668
// Open control
@@ -130,6 +132,7 @@ const Menu: React.FC<MenuProps> = props => {
130132

131133
// Mode
132134
mode = 'vertical',
135+
inlineMaxLevel,
133136
inlineCollapsed,
134137

135138
// Disabled
@@ -233,8 +236,9 @@ const Menu: React.FC<MenuProps> = props => {
233236
};
234237

235238
// >>>>> Cache & Reset open keys when inlineCollapsed changed
236-
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] =
237-
React.useState(mergedOpenKeys);
239+
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] = React.useState(
240+
mergedOpenKeys,
241+
);
238242

239243
const isInlineMode = mergedMode === 'inline';
240244

@@ -279,10 +283,9 @@ const Menu: React.FC<MenuProps> = props => {
279283
[registerPath, unregisterPath],
280284
);
281285

282-
const pathUserContext = React.useMemo(
283-
() => ({ isSubPathKey }),
284-
[isSubPathKey],
285-
);
286+
const pathUserContext = React.useMemo(() => ({ isSubPathKey }), [
287+
isSubPathKey,
288+
]);
286289

287290
React.useEffect(() => {
288291
refreshOverflowKeys(
@@ -369,6 +372,23 @@ const Menu: React.FC<MenuProps> = props => {
369372
if (!multiple && mergedOpenKeys.length && mergedMode !== 'inline') {
370373
triggerOpenKeys(EMPTY_LIST);
371374
}
375+
376+
const isMultiMode = genMultiMode(
377+
mergedOpenKeys,
378+
mergedMode,
379+
inlineMaxLevel,
380+
);
381+
if (!multiple && isMultiMode.isMultiPopup) {
382+
const inlineLevelPathKeys =
383+
info.keyPath[info.keyPath.length - inlineMaxLevel + 1];
384+
if (inlineLevelPathKeys) {
385+
const subPathKeys = getSubPathKeys(inlineLevelPathKeys);
386+
const newOpenKeys = mergedOpenKeys.filter(k => !subPathKeys.has(k));
387+
triggerOpenKeys(newOpenKeys);
388+
} else {
389+
triggerOpenKeys(EMPTY_LIST);
390+
}
391+
}
372392
};
373393

374394
// ========================= Open =========================
@@ -506,6 +526,7 @@ const Menu: React.FC<MenuProps> = props => {
506526
<MenuContextProvider
507527
prefixCls={prefixCls}
508528
mode={mergedMode}
529+
inlineMaxLevel={inlineMaxLevel}
509530
openKeys={mergedOpenKeys}
510531
rtl={isRtl}
511532
// Disabled

src/SubMenu/index.tsx

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import PopupTrigger from './PopupTrigger';
1717
import Icon from '../Icon';
1818
import useActive from '../hooks/useActive';
1919
import { warnItemProp } from '../utils/warnUtil';
20+
import { genMultiMode } from '../utils/multiModeUtil';
2021
import useDirectionStyle from '../hooks/useDirectionStyle';
2122
import InlineSubMenuList from './InlineSubMenuList';
2223
import {
@@ -104,6 +105,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
104105
const {
105106
prefixCls,
106107
mode,
108+
inlineMaxLevel,
107109
openKeys,
108110

109111
// Disabled
@@ -129,6 +131,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
129131

130132
const { isSubPathKey } = React.useContext(PathUserContext);
131133
const connectedPath = useFullPath();
134+
const isMultiMode = genMultiMode(connectedPath, mode, inlineMaxLevel);
132135

133136
const subMenuPrefixCls = `${prefixCls}-submenu`;
134137
const mergedDisabled = contextDisabled || disabled;
@@ -168,25 +171,23 @@ const InternalSubMenu = (props: SubMenuProps) => {
168171
}
169172
};
170173

171-
const onInternalMouseEnter: React.MouseEventHandler<HTMLLIElement> =
172-
domEvent => {
173-
triggerChildrenActive(true);
174+
const onInternalMouseEnter: React.MouseEventHandler<HTMLLIElement> = domEvent => {
175+
triggerChildrenActive(true);
174176

175-
onMouseEnter?.({
176-
key: eventKey,
177-
domEvent,
178-
});
179-
};
177+
onMouseEnter?.({
178+
key: eventKey,
179+
domEvent,
180+
});
181+
};
180182

181-
const onInternalMouseLeave: React.MouseEventHandler<HTMLLIElement> =
182-
domEvent => {
183-
triggerChildrenActive(false);
183+
const onInternalMouseLeave: React.MouseEventHandler<HTMLLIElement> = domEvent => {
184+
triggerChildrenActive(false);
184185

185-
onMouseLeave?.({
186-
key: eventKey,
187-
domEvent,
188-
});
189-
};
186+
onMouseLeave?.({
187+
key: eventKey,
188+
domEvent,
189+
});
190+
};
190191

191192
const mergedActive = React.useMemo(() => {
192193
if (active) {
@@ -233,6 +234,9 @@ const InternalSubMenu = (props: SubMenuProps) => {
233234
if (mode !== 'inline') {
234235
onOpenChange(eventKey, newVisible);
235236
}
237+
if (isMultiMode.isMultiPopup) {
238+
onOpenChange(eventKey, newVisible);
239+
}
236240
};
237241

238242
/**
@@ -287,6 +291,10 @@ const InternalSubMenu = (props: SubMenuProps) => {
287291
triggerModeRef.current = connectedPath.length > 1 ? 'vertical' : mode;
288292
}
289293

294+
if (isMultiMode.isMultiPopup) {
295+
triggerModeRef.current = 'vertical';
296+
}
297+
290298
if (!overflowDisabled) {
291299
const triggerMode = triggerModeRef.current;
292300

@@ -296,17 +304,23 @@ const InternalSubMenu = (props: SubMenuProps) => {
296304
<PopupTrigger
297305
mode={triggerMode}
298306
prefixCls={subMenuPrefixCls}
299-
visible={!internalPopupClose && open && mode !== 'inline'}
307+
visible={
308+
!internalPopupClose &&
309+
open &&
310+
(mode !== 'inline' || isMultiMode.isMultiPopup)
311+
}
300312
popupClassName={popupClassName}
301313
popupOffset={popupOffset}
302314
popup={
303315
<MenuContextProvider
304316
// Special handle of horizontal mode
305317
mode={triggerMode === 'horizontal' ? 'vertical' : triggerMode}
306318
>
307-
<SubMenuList id={popupId} ref={popupRef}>
308-
{children}
309-
</SubMenuList>
319+
{!isMultiMode.isMulti || isMultiMode.isPopup ? (
320+
<SubMenuList id={popupId} ref={popupRef}>
321+
{children}
322+
</SubMenuList>
323+
) : null}
310324
</MenuContextProvider>
311325
}
312326
disabled={mergedDisabled}
@@ -339,6 +353,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
339353
[`${subMenuPrefixCls}-active`]: mergedActive,
340354
[`${subMenuPrefixCls}-selected`]: childrenSelected,
341355
[`${subMenuPrefixCls}-disabled`]: mergedDisabled,
356+
[`${subMenuPrefixCls}-multi`]: isMultiMode.isMultiPopup,
342357
},
343358
)}
344359
onMouseEnter={onInternalMouseEnter}
@@ -347,7 +362,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
347362
{titleNode}
348363

349364
{/* Inline mode */}
350-
{!overflowDisabled && (
365+
{!overflowDisabled && (!isMultiMode.isMulti || !isMultiMode.isPopup) && (
351366
<InlineSubMenuList id={popupId} open={open} keyPath={connectedPath}>
352367
{children}
353368
</InlineSubMenuList>

src/context/MenuContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface MenuContextProps {
1717

1818
// Mode
1919
mode: MenuMode;
20+
inlineMaxLevel?: number;
2021

2122
// Disabled
2223
disabled?: boolean;

src/utils/multiModeUtil.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { MenuMode } from '../interface';
2+
3+
export function genMultiMode(
4+
keys: string[],
5+
mode?: MenuMode,
6+
inlineMaxLevel?: number,
7+
): {
8+
isMulti: boolean;
9+
isPopup: boolean;
10+
isMultiPopup: boolean;
11+
} {
12+
const multi = {
13+
isMulti: false,
14+
isPopup: false,
15+
isMultiPopup: false,
16+
};
17+
18+
if (mode === 'inline' && typeof inlineMaxLevel === 'number') {
19+
multi.isMulti = true;
20+
}
21+
22+
if (keys?.length >= inlineMaxLevel) {
23+
multi.isPopup = true;
24+
}
25+
26+
if (multi.isMulti && multi.isPopup) {
27+
multi.isMultiPopup = true;
28+
}
29+
30+
return multi;
31+
}

0 commit comments

Comments
 (0)