Repository: arco-design/arco-design (ts)

Issue:
Menu手动设置selectedKeys为SubMenu时，增加SubMenu高亮效果
- [ ] I'm sure this does not appear in [the issue list of the repository](https://github.com/arco-design/arco-design/issues)

## 基本信息 

## 现有案例
https://arco.bytedance.net/react/components/menu#api

## 预期解决问题
Menu.SubMenu非弹出模式不支持selectable，
想用onClickSubMenu实现非弹出模式selectable的效果，
但是发现SubMenu选中状态没有高亮


<!-- generated by arco-issue. DO NOT REMOVE -->

Pull request description:
enhancement: SubMenu inline support selectable
<!--
  Thanks so much for your PR and contribution.

  Before submitting, please make sure to follow the Pull Request Guidelines: https://github.com/arco-design/arco-design/blob/main/CONTRIBUTING.md
-->

<!-- Put an `x` in "[ ]" to check a box) -->

## Types of changes

<!-- What types of changes does this PR introduce -->
- [ ] New feature
- [ ] Bug fix
- [ ] Documentation change
- [ ] Coding style change
- [ ] Component style change
- [ ] Refactoring
- [x] Performance improvement
- [ ] Test cases
- [ ] Continuous integration
- [ ] Typescript definition change
- [ ] Breaking change
- [ ] Others 

## Changelog

| Component | Changelog(CN) | Changelog(EN) | Related issues |
| --------- | ------------- | ------------- | -------------- |
| Menu | `Menu.SubMenu` 在非弹出模式下同样支持 `selectable` 属性。   | `Menu.SubMenu` also supports `selectable` property in non-popup mode.  | Close #345 |

## Checklist:

- [x] Test suite passes (`npm run test`)
- [x] Provide changelog for relevant changes (e.g. bug fixes and new features) if applicable.
- [x] Changes are submitted to the appropriate branch (e.g. features should be submitted to `feature` branch and others should be submitted to `main` branch)

## Other information

<!-- Please describe what other information that should be taken care of. E.g. describe the impact if introduce a breaking change -->


Patch:
diff --git a/components/ConfigProvider/index.tsx b/components/ConfigProvider/index.tsx
index f50c7a98..f97a8921 100644
--- a/components/ConfigProvider/index.tsx
+++ b/components/ConfigProvider/index.tsx
@@ -5,7 +5,7 @@ import { lighten } from './util';
 import Message from '../Message';
 import Notification from '../Notification';
 import Empty from '../Empty';
-import { setModalLocale } from '../Modal/locale';
+import { setModalLocale, setConfigProviderProps } from '../Modal/locale';
 import { IconContext } from '../../icon/react-icon/context';
 import { ConfigProviderProps } from './interface';
 import omit from '../_util/omit';
@@ -110,6 +110,9 @@ function ConfigProvider(baseProps: ConfigProviderProps) {
     ...omit(props, ['children']),
     getPrefixCls,
   };
+  useEffect(() => {
+    setConfigProviderProps(config);
+  }, []);
 
   let child = children;
 
diff --git a/components/Menu/README.en-US.md b/components/Menu/README.en-US.md
index ac0a856a..31d5511a 100644
--- a/components/Menu/README.en-US.md
+++ b/components/Menu/README.en-US.md
@@ -46,7 +46,7 @@ A component to organize, arrange, and display a list of options.
 |className|Additional css class|`string \| string[]`|`-`|-|
 |title|Title of the subMenu|`string \| ReactNode`|`-`|-|
 |key|Unique ID of the subMenu|`string` **(Required)**|`-`|-|
-|selectable|Whether to use the subMenu header as a menu item which can be selected if the subMenu is popup mode|`boolean`|`-`|-|
+|selectable|Whether to use the subMenu header as a menu item which can be selected|`boolean`|`-`|-|
 |popup|Whether to force the use of popup mode, parameter `level` indicates the level of current subMenu.|`boolean \| ((level: number) => boolean)`|`-`|2.8.0|
 |triggerProps|Pass all `Trigger` component properties|`Partial<TriggerProps>`|`-`|2.19.0|
 
diff --git a/components/Menu/README.zh-CN.md b/components/Menu/README.zh-CN.md
index b0ee930c..007573af 100644
--- a/components/Menu/README.zh-CN.md
+++ b/components/Menu/README.zh-CN.md
@@ -46,7 +46,7 @@
 |className|节点类名|`string \| string[]`|`-`|-|
 |title|子菜单的标题|`string \| ReactNode`|`-`|-|
 |key|唯一标志|`string` **(必填)**|`-`|-|
-|selectable|弹出模式下，是否将多级菜单头也作为一个菜单项，支持点击选中等状态。|`boolean`|`-`|-|
+|selectable|是否将多级菜单头也作为一个菜单项，支持点击选中等状态。|`boolean`|`-`|-|
 |popup|是否强制使用弹出模式，`level` 表示当前子菜单的层级|`boolean \| ((level: number) => boolean)`|`-`|2.8.0|
 |triggerProps|弹出模式下可接受所有 `Trigger` 的 `Props`|`Partial<TriggerProps>`|`-`|2.19.0|
 
diff --git a/components/Menu/interface.tsx b/components/Menu/interface.tsx
index f41de21b..0aa68d42 100644
--- a/components/Menu/interface.tsx
+++ b/components/Menu/interface.tsx
@@ -159,8 +159,8 @@ export interface MenuSubMenuProps {
    */
   key: string;
   /**
-   * @zh 弹出模式下，是否将多级菜单头也作为一个菜单项，支持点击选中等状态。
-   * @en Whether to use the subMenu header as a menu item which can be selected if the subMenu is popup mode
+   * @zh 是否将多级菜单头也作为一个菜单项，支持点击选中等状态。
+   * @en Whether to use the subMenu header as a menu item which can be selected
    */
   selectable?: boolean;
   /**
diff --git a/components/Menu/sub-menu/inline.tsx b/components/Menu/sub-menu/inline.tsx
index 3e5e28f5..744f1ce0 100644
--- a/components/Menu/sub-menu/inline.tsx
+++ b/components/Menu/sub-menu/inline.tsx
@@ -11,7 +11,8 @@ import { useHotkeyHandler } from '../hotkey';
 import pick from '../../_util/pick';
 
 const SubMenuInline = (props: MenuSubMenuProps & { forwardedRef }) => {
-  const { _key, children, style, className, title, level, forwardedRef, ...rest } = props;
+  const { _key, children, style, className, title, level, forwardedRef, selectable, ...rest } =
+    props;
   const {
     prefixCls,
     levelIndent,
@@ -19,17 +20,25 @@ const SubMenuInline = (props: MenuSubMenuProps & { forwardedRef }) => {
     selectedKeys = [],
     icons,
     onClickSubMenu,
+    onClickMenuItem,
     collectInlineMenuKeys,
   } = useContext(MenuContext);
 
   const baseClassName = `${prefixCls}-inline`;
   const isOpen = openKeys?.indexOf(_key) > -1;
+  const isSelected =
+    (selectable && selectedKeys.indexOf(props._key as string) > -1) ||
+    isChildrenSelected(children, selectedKeys);
+
   const [height, setHeight] = useStateWithPromise(isOpen ? 'auto' : 0);
 
-  const subMenuClickHandler = () => onClickSubMenu(_key, level, 'inline');
+  const subMenuClickHandler = (event) => {
+    onClickSubMenu(_key, level, 'inline');
+    selectable && onClickMenuItem(_key, event);
+  };
   const isActive = useHotkeyHandler(
     _key,
-    (isActive, type) => isActive && type === 'enter' && subMenuClickHandler()
+    (isActive, type) => isActive && type === 'enter' && subMenuClickHandler(null)
   );
 
   useEffect(() => {
@@ -43,15 +52,16 @@ const SubMenuInline = (props: MenuSubMenuProps & { forwardedRef }) => {
   const childrenList = processChildren(children, {
     ...pick(rest, PROPS_NEED_TO_BE_PASSED_IN_SUBMENU),
     level: level + 1,
+    selectable,
   });
 
   const header = (
     <div
       className={cs(`${baseClassName}-header`, {
         [`${prefixCls}-active`]: isActive,
-        [`${prefixCls}-selected`]: isChildrenSelected(children, selectedKeys),
+        [`${prefixCls}-selected`]: isSelected,
       })}
-      onClick={() => subMenuClickHandler()}
+      onClick={subMenuClickHandler}
     >
       <MenuIndent level={level} prefixCls={prefixCls} levelIndent={levelIndent} />
       <span>{title}</span>
diff --git a/components/Modal/confirm.tsx b/components/Modal/confirm.tsx
index 18b999ed..9a46d0fe 100644
--- a/components/Modal/confirm.tsx
+++ b/components/Modal/confirm.tsx
@@ -6,6 +6,8 @@ import IconCheckCircleFill from '../../icon/react-icon/IconCheckCircleFill';
 import IconExclamationCircleFill from '../../icon/react-icon/IconExclamationCircleFill';
 import IconCloseCircleFill from '../../icon/react-icon/IconCloseCircleFill';
 import { getModalConfig, destroyList } from './config';
+import ConfigProvider from '../ConfigProvider';
+import { getConfigProviderProps } from './locale';
 
 export interface ConfirmProps extends ModalProps {
   content?: ReactNode;
@@ -67,8 +69,15 @@ function confirm(config: ConfirmProps, renderFunc?: (props: ConfirmProps) => voi
   const div = document.createElement('div');
   document.body.appendChild(div);
 
+  const configProviderProps = getConfigProviderProps();
+
   function render(props: ConfirmProps) {
-    ReactDOM.render(<ConfirmModal {...props} onCancel={onCancel} />, div);
+    ReactDOM.render(
+      <ConfigProvider {...configProviderProps}>
+        <ConfirmModal {...props} onCancel={onCancel} />
+      </ConfigProvider>,
+      div
+    );
   }
 
   const renderFunction = renderFunc || render;
diff --git a/components/Modal/locale.tsx b/components/Modal/locale.tsx
index 0680a7df..78b166fd 100644
--- a/components/Modal/locale.tsx
+++ b/components/Modal/locale.tsx
@@ -1,10 +1,13 @@
 import defaultLocale from '../locale/default';
 import { Locale } from '../locale/interface';
+import type { ConfigProviderProps } from '../ConfigProvider';
 
 let modalLocale: Locale = {
   ...defaultLocale,
 };
 
+let configProvider = {} as ConfigProviderProps;
+
 export function setModalLocale(locale?: Locale) {
   if (locale) {
     modalLocale = {
@@ -21,3 +24,13 @@ export function setModalLocale(locale?: Locale) {
 export function getModalLocale() {
   return modalLocale;
 }
+
+export function setConfigProviderProps(configProviderProps: ConfigProviderProps) {
+  configProvider = {
+    ...configProviderProps,
+  };
+}
+
+export function getConfigProviderProps() {
+  return configProvider;
+}
diff --git a/components/Modal/modal.tsx b/components/Modal/modal.tsx
index 1a2dd223..3c63aa66 100644
--- a/components/Modal/modal.tsx
+++ b/components/Modal/modal.tsx
@@ -20,7 +20,6 @@ import Button from '../Button';
 import Portal from '../Portal';
 import confirm, { ConfirmProps } from './confirm';
 import ConfigProvider, { ConfigContext } from '../ConfigProvider';
-import { getModalLocale } from './locale';
 import IconHover from '../_class/icon-hover';
 import { setModalConfig, ModalConfigType, destroyList } from './config';
 import { isFunction, isObject } from '../_util/is';
@@ -116,7 +115,7 @@ function Modal(baseProps: PropsWithChildren<ModalProps>, ref) {
   });
 
   const prefixCls = context.getPrefixCls('modal', props.prefixCls);
-  const locale = getModalLocale();
+  const { locale } = context;
 
   // 简洁模式下默认不显示关闭按钮
   const defaultClosable = !simple;


Test patch:
diff --git a/components/Menu/__test__/index.test.tsx b/components/Menu/__test__/index.test.tsx
index 327da36a..4eb38ff3 100644
--- a/components/Menu/__test__/index.test.tsx
+++ b/components/Menu/__test__/index.test.tsx
@@ -317,4 +317,16 @@ describe('Menu', () => {
     expect(component.find('.arco-menu-item-inner')).toHaveLength(2);
     expect(component.find('.arco-menu-pop-header').text()).toBe('sub_submenu_2');
   });
+
+  it('SubMenu selectable', () => {
+    const component = mount(
+      <Menu mode="vertical" defaultSelectedKeys={['submenu']}>
+        <SubMenu selectable title="submenu" key="submenu">
+          <Menu.Item key="1">1</Menu.Item>
+        </SubMenu>
+      </Menu>
+    );
+
+    expect(component.find('.arco-menu-selected')).toHaveLength(1);
+  });
 });