Skip to content

Commit ee866bf

Browse files
authored
Merge pull request #1560 from easyops-cn/steve/actions-opened-keys
feat(actions): support activeKeys
2 parents fc77eb9 + 6e322f0 commit ee866bf

File tree

3 files changed

+165
-6
lines changed

3 files changed

+165
-6
lines changed

bricks/basic/src/actions/index.spec.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,100 @@ describe("eo-actions", () => {
196196
document.body.removeChild(element);
197197
});
198198
});
199+
200+
test("activeKeys", async () => {
201+
const element = document.createElement("eo-actions") as EoActions;
202+
element.actions = [
203+
{
204+
text: "a",
205+
key: "a",
206+
items: [
207+
{
208+
text: "a-1",
209+
key: "a-1",
210+
},
211+
{
212+
text: "a-2",
213+
key: "a-2",
214+
},
215+
],
216+
},
217+
{
218+
text: "b",
219+
key: "b",
220+
items: [
221+
{
222+
text: "b-1",
223+
key: "b-1",
224+
},
225+
{
226+
text: "b-2",
227+
key: "b-2",
228+
},
229+
],
230+
},
231+
];
232+
233+
act(() => {
234+
document.body.appendChild(element);
235+
});
236+
237+
const popovers = element.shadowRoot?.querySelectorAll(".popover");
238+
const firstSubMenuItem = popovers?.[0]?.querySelector(
239+
"eo-menu-item"
240+
) as HTMLElement;
241+
const firstSubMenuItemChild =
242+
firstSubMenuItem?.nextElementSibling?.querySelector(
243+
"eo-menu-item"
244+
) as HTMLElement;
245+
const secondSubMenuItem = popovers?.[1]?.querySelector(
246+
"eo-menu-item"
247+
) as HTMLElement;
248+
249+
expect(firstSubMenuItem.classList.contains("menu-item-active")).toBe(false);
250+
expect(firstSubMenuItemChild.classList.contains("menu-item-active")).toBe(
251+
false
252+
);
253+
expect(secondSubMenuItem.classList.contains("menu-item-active")).toBe(
254+
false
255+
);
256+
257+
element.activeKeys = ["a"];
258+
await act(async () => {
259+
await (global as any).flushPromises();
260+
});
261+
expect(firstSubMenuItem.classList.contains("menu-item-active")).toBe(true);
262+
expect(firstSubMenuItemChild.classList.contains("menu-item-active")).toBe(
263+
false
264+
);
265+
expect(secondSubMenuItem.classList.contains("menu-item-active")).toBe(
266+
false
267+
);
268+
269+
element.activeKeys = ["a", "a-1"];
270+
await act(async () => {
271+
await (global as any).flushPromises();
272+
});
273+
expect(firstSubMenuItem.classList.contains("menu-item-active")).toBe(true);
274+
expect(firstSubMenuItemChild.classList.contains("menu-item-active")).toBe(
275+
true
276+
);
277+
expect(secondSubMenuItem.classList.contains("menu-item-active")).toBe(
278+
false
279+
);
280+
281+
element.activeKeys = ["b"];
282+
await act(async () => {
283+
await (global as any).flushPromises();
284+
});
285+
expect(firstSubMenuItem.classList.contains("menu-item-active")).toBe(false);
286+
expect(firstSubMenuItemChild.classList.contains("menu-item-active")).toBe(
287+
false
288+
);
289+
expect(secondSubMenuItem.classList.contains("menu-item-active")).toBe(true);
290+
291+
act(() => {
292+
document.body.removeChild(element);
293+
});
294+
});
199295
});

bricks/basic/src/actions/index.tsx

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from "react";
1+
import React, { useEffect, useMemo, useRef } from "react";
22
import { EventEmitter, createDecorators } from "@next-core/element";
33
import { ReactNextElement, wrapBrick } from "@next-core/react-element";
44
import "@next-core/theme";
@@ -39,15 +39,43 @@ interface SubMenuItemComProps {
3939
index: number;
4040
action: SubMenuAction;
4141
checkedKeys: (string | number)[] | undefined;
42+
activeKeys: (string | number)[] | undefined;
4243
onSubMenuClick: (action: SimpleAction) => void;
4344
}
4445

4546
function SubMenuItemCom({
4647
index,
4748
action,
4849
checkedKeys,
50+
activeKeys,
4951
onSubMenuClick,
5052
}: SubMenuItemComProps) {
53+
const popoverRef = useRef<Popover>(null);
54+
const [checked, opened] = useMemo(() => {
55+
let keyIndex = -1;
56+
if (activeKeys && action.key) {
57+
for (let i = 0; i < activeKeys.length; i++) {
58+
if (activeKeys[i] === action.key) {
59+
keyIndex = i;
60+
break;
61+
}
62+
}
63+
}
64+
if (keyIndex === -1) {
65+
return [false, false];
66+
}
67+
if (keyIndex === activeKeys!.length - 1) {
68+
return [true, false];
69+
}
70+
return [true, true];
71+
}, [action.key, activeKeys]);
72+
73+
useEffect(() => {
74+
if (popoverRef.current) {
75+
popoverRef.current.active = opened;
76+
}
77+
}, [opened]);
78+
5179
return (
5280
<WrappedPopover
5381
data-index={index}
@@ -58,27 +86,46 @@ function SubMenuItemCom({
5886
distance={4}
5987
anchorDisplay="block"
6088
strategy="fixed"
89+
ref={popoverRef}
6190
>
6291
<WrappedMenuItem
6392
slot="anchor"
6493
className={classnames("sub-menu-item-label", {
6594
"menu-item-danger": action.danger,
95+
"menu-item-active": checked,
6696
})}
6797
icon={action.icon}
6898
disabled={action.disabled}
6999
>
70100
{action.text}
71101
</WrappedMenuItem>
72102
<div className="sub-menu-wrapper">
73-
{action?.items.map((innerItem: SimpleAction, innerIndex: number) => {
74-
const menuItem = (
103+
{action?.items.map((innerItem, innerIndex: number, list) => {
104+
if (isDivider(innerItem)) {
105+
if (innerIndex === 0 || innerIndex === list.length - 1) {
106+
return null;
107+
}
108+
return <div key={innerIndex} className="menu-item-divider" />;
109+
}
110+
const menuItem = (innerItem as SubMenuAction)?.items?.length ? (
111+
<SubMenuItemCom
112+
index={innerIndex}
113+
action={innerItem as SubMenuAction}
114+
checkedKeys={checkedKeys}
115+
activeKeys={activeKeys}
116+
onSubMenuClick={onSubMenuClick}
117+
/>
118+
) : (
75119
<React.Fragment>
76120
<WrappedMenuItem
77121
className={classnames({
78122
"menu-item-danger": innerItem.danger,
79123
"menu-item-selected":
80124
!isNil(innerItem.key) &&
81125
checkedKeys?.includes(innerItem.key),
126+
"menu-item-active":
127+
!isNil(innerItem.key) &&
128+
activeKeys?.includes(innerItem.key),
82129
})}
83130
icon={innerItem.icon}
84131
disabled={innerItem.disabled}
@@ -141,7 +188,7 @@ export interface SimpleAction {
141188
}
142189

143190
export interface SubMenuAction extends SimpleAction {
144-
items: SimpleAction[];
191+
items: Action[];
145192
placement?: Placement;
146193
}
147194

@@ -155,6 +202,7 @@ export interface ActionsProps {
155202
actions?: Action[];
156203
itemDraggable?: boolean;
157204
checkedKeys?: (string | number)[];
205+
activeKeys?: (string | number)[];
158206
themeVariant?: "default" | "elevo";
159207
}
160208

@@ -189,13 +237,21 @@ class EoActions extends ReactNextElement implements ActionsProps {
189237
accessor actions: Action[] | undefined;
190238

191239
/**
192-
* actions选中项配置
240+
* actions 选中项配置
193241
*/
194242
@property({
195243
attribute: false,
196244
})
197245
accessor checkedKeys: (string | number)[] = [];
198246

247+
/**
248+
* actions 激活项配置,用于菜单项的选择和展开,因按菜单层级顺序依次列出当前激活的菜单项
249+
*/
250+
@property({
251+
attribute: false,
252+
})
253+
accessor activeKeys: (string | number)[] = [];
254+
199255
/**
200256
* action中的菜单项是否可拖拽
201257
*/
@@ -253,6 +309,7 @@ class EoActions extends ReactNextElement implements ActionsProps {
253309
onItemDragStart={this.#handleItemDragStart}
254310
onItemDragEnd={this.#handleItemDragEnd}
255311
checkedKeys={this.checkedKeys}
312+
activeKeys={this.activeKeys}
256313
/>
257314
);
258315
}
@@ -268,6 +325,7 @@ export interface ActionsComponentProps extends ActionsProps {
268325
export function EoActionsComponent({
269326
actions,
270327
checkedKeys,
328+
activeKeys,
271329
onActionClick,
272330
itemDraggable,
273331
onItemDragStart,
@@ -293,16 +351,19 @@ export function EoActionsComponent({
293351
index={index}
294352
action={action as SubMenuAction}
295353
checkedKeys={checkedKeys}
354+
activeKeys={activeKeys}
296355
onSubMenuClick={(action: SimpleAction) => {
297356
onActionClick?.(action);
298357
}}
299-
></SubMenuItemCom>
358+
/>
300359
) : (
301360
<WrappedMenuItem
302361
className={classnames({
303362
"menu-item-danger": action.danger,
304363
"menu-item-selected":
305364
!isNil(action.key) && checkedKeys?.includes(action.key),
365+
"menu-item-active":
366+
!isNil(action.key) && activeKeys?.includes(action.key),
306367
})}
307368
draggable={itemDraggable}
308369
icon={action.icon}

bricks/basic/src/actions/styles.shadow.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ eo-link {
5959
.menu-item-danger:not([disabled])::part(menu-item) {
6060
color: var(--theme-red-color);
6161
}
62+
63+
.menu-item-active::part(menu-item),
6264
.menu-item-selected::part(menu-item) {
6365
font-weight: 600;
6466
background-color: var(--app-bar-dropdown-menu-item-hover-bg);

0 commit comments

Comments
 (0)