Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add api inlineMaxLevel #421

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ ReactDOM.render(
<th>24</th>
<td>Padding level multiplier. Right or left padding depends on param "direction".</td>
</tr>
<tr>
<td>inlineMaxDeep</td>
<td>Number</td>
<th></th>
<td>inline menu, specify at most a certain deep of submenu, deeper submenu will right popover</td>
</tr>
</tbody>
</table>

Expand Down
15 changes: 15 additions & 0 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,21 @@
}
}

&-inline {
.@{menuPrefixCls}-submenu-multi {
.@{menuPrefixCls}-submenu-title {
.@{menuPrefixCls}-submenu-arrow {
transform: rotate(0);
}
}
}
.@{menuPrefixCls}-submenu-multi.@{menuPrefixCls}-submenu-open {
.@{menuPrefixCls}-submenu-title {
background-color: #eaf8fe;
}
}
}

&-vertical&-sub,
&-vertical-left&-sub,
&-vertical-right&-sub {
Expand Down
5 changes: 5 additions & 0 deletions docs/demo/antd-switch-multi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## antd-switch-multi

inline Menu, up to two level menus, more submenus right popover

<code src="../examples/antd-switch-multi.tsx">
44 changes: 44 additions & 0 deletions docs/examples/antd-switch-multi.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable no-console, react/require-default-props, no-param-reassign */

import React from 'react';
import { CommonMenu, inlineMotion } from './antd';
import '../../assets/index.less';

const Demo = () => {
const [inline, setInline] = React.useState(false);
const [openKeys, setOpenKey] = React.useState(['1']);

let restProps = {};
if (inline) {
restProps = { motion: inlineMotion };
} else {
restProps = { openAnimation: 'zoom' };
}

return (
<div style={{ margin: 20, width: 200 }}>
<label>
<input
type="checkbox"
checked={inline}
onChange={() => setInline(!inline)}
/>{' '}
Inline
</label>
<CommonMenu
mode="inline"
inlineMaxDeep={2}
openKeys={openKeys}
onOpenChange={keys => {
console.error('Open Keys Changed:', keys);
setOpenKey(keys);
}}
inlineCollapsed={!inline}
{...restProps}
/>
</div>
);
};

export default Demo;
/* eslint-enable */
32 changes: 25 additions & 7 deletions src/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { parseChildren } from './utils/nodeUtil';
import MenuContextProvider from './context/MenuContext';
import useMemoCallback from './hooks/useMemoCallback';
import { warnItemProp } from './utils/warnUtil';
import { genMultiMode } from './utils/multiModeUtil';
import SubMenu from './SubMenu';
import useAccessibility from './hooks/useAccessibility';
import useUUID from './hooks/useUUID';
Expand Down Expand Up @@ -61,6 +62,7 @@ export interface MenuProps

// Mode
mode?: MenuMode;
inlineMaxDeep?: number;
inlineCollapsed?: boolean;

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

// Mode
mode = 'vertical',
inlineMaxDeep,
inlineCollapsed,

// Disabled
Expand Down Expand Up @@ -227,14 +230,17 @@ const Menu: React.FC<MenuProps> = props => {
postState: keys => keys || EMPTY_LIST,
});

const isMultiMode = genMultiMode(mergedOpenKeys, mergedMode, inlineMaxDeep);

const triggerOpenKeys = (keys: string[]) => {
setMergedOpenKeys(keys);
onOpenChange?.(keys);
};

// >>>>> Cache & Reset open keys when inlineCollapsed changed
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] =
React.useState(mergedOpenKeys);
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] = React.useState(
mergedOpenKeys,
);

const isInlineMode = mergedMode === 'inline';

Expand Down Expand Up @@ -279,10 +285,9 @@ const Menu: React.FC<MenuProps> = props => {
[registerPath, unregisterPath],
);

const pathUserContext = React.useMemo(
() => ({ isSubPathKey }),
[isSubPathKey],
);
const pathUserContext = React.useMemo(() => ({ isSubPathKey }), [
isSubPathKey,
]);

React.useEffect(() => {
refreshOverflowKeys(
Expand Down Expand Up @@ -369,6 +374,18 @@ const Menu: React.FC<MenuProps> = props => {
if (!multiple && mergedOpenKeys.length && mergedMode !== 'inline') {
triggerOpenKeys(EMPTY_LIST);
}

if (!multiple && isMultiMode.isMultiPopup) {
const inlineLevelPathKeys =
info.keyPath[info.keyPath.length - inlineMaxDeep + 1];
if (inlineLevelPathKeys) {
const subPathKeys = getSubPathKeys(inlineLevelPathKeys);
const newOpenKeys = mergedOpenKeys.filter(k => !subPathKeys.has(k));
triggerOpenKeys(newOpenKeys);
} else {
triggerOpenKeys(EMPTY_LIST);
}
}
};

// ========================= Open =========================
Expand All @@ -385,7 +402,7 @@ const Menu: React.FC<MenuProps> = props => {

if (open) {
newOpenKeys.push(key);
} else if (mergedMode !== 'inline') {
} else if (mergedMode !== 'inline' || isMultiMode.isMultiPopup) {
// We need find all related popup to close
const subPathKeys = getSubPathKeys(key);
newOpenKeys = newOpenKeys.filter(k => !subPathKeys.has(k));
Expand Down Expand Up @@ -506,6 +523,7 @@ const Menu: React.FC<MenuProps> = props => {
<MenuContextProvider
prefixCls={prefixCls}
mode={mergedMode}
inlineMaxDeep={inlineMaxDeep}
openKeys={mergedOpenKeys}
rtl={isRtl}
// Disabled
Expand Down
59 changes: 37 additions & 22 deletions src/SubMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import PopupTrigger from './PopupTrigger';
import Icon from '../Icon';
import useActive from '../hooks/useActive';
import { warnItemProp } from '../utils/warnUtil';
import { genMultiMode } from '../utils/multiModeUtil';
import useDirectionStyle from '../hooks/useDirectionStyle';
import InlineSubMenuList from './InlineSubMenuList';
import {
Expand Down Expand Up @@ -104,6 +105,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
const {
prefixCls,
mode,
inlineMaxDeep,
openKeys,

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

const { isSubPathKey } = React.useContext(PathUserContext);
const connectedPath = useFullPath();
const isMultiMode = genMultiMode(connectedPath, mode, inlineMaxDeep);

const subMenuPrefixCls = `${prefixCls}-submenu`;
const mergedDisabled = contextDisabled || disabled;
Expand Down Expand Up @@ -168,25 +171,23 @@ const InternalSubMenu = (props: SubMenuProps) => {
}
};

const onInternalMouseEnter: React.MouseEventHandler<HTMLLIElement> =
domEvent => {
triggerChildrenActive(true);
const onInternalMouseEnter: React.MouseEventHandler<HTMLLIElement> = domEvent => {
triggerChildrenActive(true);

onMouseEnter?.({
key: eventKey,
domEvent,
});
};
onMouseEnter?.({
key: eventKey,
domEvent,
});
};

const onInternalMouseLeave: React.MouseEventHandler<HTMLLIElement> =
domEvent => {
triggerChildrenActive(false);
const onInternalMouseLeave: React.MouseEventHandler<HTMLLIElement> = domEvent => {
triggerChildrenActive(false);

onMouseLeave?.({
key: eventKey,
domEvent,
});
};
onMouseLeave?.({
key: eventKey,
domEvent,
});
};

const mergedActive = React.useMemo(() => {
if (active) {
Expand Down Expand Up @@ -217,7 +218,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
});

// Trigger open by click when mode is `inline`
if (mode === 'inline') {
if (mode === 'inline' && !isMultiMode.isMultiPopup) {
onOpenChange(eventKey, !originOpen);
}
};
Expand All @@ -233,6 +234,9 @@ const InternalSubMenu = (props: SubMenuProps) => {
if (mode !== 'inline') {
onOpenChange(eventKey, newVisible);
}
if (isMultiMode.isMultiPopup) {
onOpenChange(eventKey, newVisible);
}
};

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

if (isMultiMode.isMultiPopup) {
triggerModeRef.current = 'vertical';
}

if (!overflowDisabled) {
const triggerMode = triggerModeRef.current;

Expand All @@ -296,17 +304,23 @@ const InternalSubMenu = (props: SubMenuProps) => {
<PopupTrigger
mode={triggerMode}
prefixCls={subMenuPrefixCls}
visible={!internalPopupClose && open && mode !== 'inline'}
visible={
!internalPopupClose &&
open &&
(mode !== 'inline' || isMultiMode.isMultiPopup)
}
popupClassName={popupClassName}
popupOffset={popupOffset}
popup={
<MenuContextProvider
// Special handle of horizontal mode
mode={triggerMode === 'horizontal' ? 'vertical' : triggerMode}
>
<SubMenuList id={popupId} ref={popupRef}>
{children}
</SubMenuList>
{!isMultiMode.isMulti || isMultiMode.isPopup ? (
<SubMenuList id={popupId} ref={popupRef}>
{children}
</SubMenuList>
) : null}
</MenuContextProvider>
}
disabled={mergedDisabled}
Expand Down Expand Up @@ -339,6 +353,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
[`${subMenuPrefixCls}-active`]: mergedActive,
[`${subMenuPrefixCls}-selected`]: childrenSelected,
[`${subMenuPrefixCls}-disabled`]: mergedDisabled,
[`${subMenuPrefixCls}-multi`]: isMultiMode.isMultiPopup,
},
)}
onMouseEnter={onInternalMouseEnter}
Expand All @@ -347,7 +362,7 @@ const InternalSubMenu = (props: SubMenuProps) => {
{titleNode}

{/* Inline mode */}
{!overflowDisabled && (
{!overflowDisabled && (!isMultiMode.isMulti || !isMultiMode.isPopup) && (
<InlineSubMenuList id={popupId} open={open} keyPath={connectedPath}>
{children}
</InlineSubMenuList>
Expand Down
1 change: 1 addition & 0 deletions src/context/MenuContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface MenuContextProps {

// Mode
mode: MenuMode;
inlineMaxDeep?: number;

// Disabled
disabled?: boolean;
Expand Down
31 changes: 31 additions & 0 deletions src/utils/multiModeUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { MenuMode } from '../interface';

export function genMultiMode(
keys: string[],
mode?: MenuMode,
inlineMaxDeep?: number,
): {
isMulti: boolean;
isPopup: boolean;
isMultiPopup: boolean;
} {
const multi = {
isMulti: false,
isPopup: false,
isMultiPopup: false,
};

if (mode === 'inline' && typeof inlineMaxDeep === 'number') {
multi.isMulti = true;
}

if (keys?.length >= inlineMaxDeep) {
multi.isPopup = true;
}

if (multi.isMulti && multi.isPopup) {
multi.isMultiPopup = true;
}

return multi;
}
Loading