Skip to content

Commit 8f628d2

Browse files
authored
Merge pull request #1562 from easyops-cn/steve/drawer-elevo
Steve/drawer-elevo
2 parents 7c6ae95 + 7b60555 commit 8f628d2

File tree

5 files changed

+182
-38
lines changed

5 files changed

+182
-38
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, test, expect, jest } from "@jest/globals";
2+
import { act } from "react-dom/test-utils";
3+
import "./";
4+
import type { EoBatchAgent } from "./index.js";
5+
6+
jest.mock("@next-core/theme", () => ({}));
7+
8+
describe("eo-batch-agent", () => {
9+
test("basic usage", async () => {
10+
const element = document.createElement("eo-batch-agent") as EoBatchAgent;
11+
12+
const onTrigger = jest.fn();
13+
element.addEventListener("trigger", (e) => {
14+
onTrigger((e as CustomEvent).detail);
15+
});
16+
17+
act(() => {
18+
document.body.appendChild(element);
19+
});
20+
21+
element.trigger("foo");
22+
element.trigger("bar");
23+
element.trigger("foo");
24+
expect(onTrigger).not.toHaveBeenCalled();
25+
26+
await Promise.resolve();
27+
expect(onTrigger).toHaveBeenCalledTimes(2);
28+
expect(onTrigger).toHaveBeenNthCalledWith(1, { type: "foo" });
29+
expect(onTrigger).toHaveBeenNthCalledWith(2, { type: "bar" });
30+
31+
element.trigger("bar");
32+
expect(onTrigger).toHaveBeenCalledTimes(2);
33+
await Promise.resolve();
34+
expect(onTrigger).toHaveBeenCalledTimes(3);
35+
expect(onTrigger).toHaveBeenNthCalledWith(3, { type: "bar" });
36+
37+
act(() => {
38+
document.body.removeChild(element);
39+
});
40+
});
41+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {
2+
createDecorators,
3+
NextElement,
4+
type EventEmitter,
5+
} from "@next-core/element";
6+
import styleText from "./styles.shadow.css";
7+
8+
const { defineElement, event, method } = createDecorators();
9+
10+
/**
11+
* 批处理事件代理。
12+
*
13+
* 当多次触发同一类型事件时,只会在当前宏任务结束后触发一次该类型事件。
14+
*/
15+
export
16+
@defineElement("eo-batch-agent", {
17+
styleTexts: [styleText],
18+
})
19+
class EoBatchAgent extends NextElement {
20+
#enqueued = new Set<string>();
21+
22+
/**
23+
* 事件被触发。
24+
*/
25+
@event({ type: "trigger" })
26+
accessor #triggerEvent!: EventEmitter<{ type: string }>;
27+
28+
/**
29+
* 触发事件。
30+
*/
31+
@method()
32+
trigger(type: string): void {
33+
if (this.#enqueued.has(type)) {
34+
return;
35+
}
36+
this.#enqueued.add(type);
37+
queueMicrotask(() => {
38+
this.#enqueued.delete(type);
39+
this.#triggerEvent.emit({ type });
40+
});
41+
}
42+
43+
protected _render() {
44+
// Do nothing
45+
}
46+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:host {
2+
display: none;
3+
}

bricks/containers/src/drawer/drawer.shadow.css

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
:host {
2+
--eo-drawer-inset-gap: 0px;
3+
}
4+
5+
:host([theme-variant="elevo"]) {
6+
--eo-drawer-inset-gap: 20px;
7+
}
8+
9+
* {
10+
box-sizing: border-box;
11+
}
12+
113
.drawer {
214
position: fixed;
315
z-index: 1000;
@@ -38,7 +50,7 @@
3850
}
3951

4052
.mask,
41-
.drawer-wrapper {
53+
.drawer-content {
4254
pointer-events: auto;
4355
}
4456

@@ -75,10 +87,10 @@
7587
position: absolute;
7688
width: 100%;
7789
height: 100%;
78-
background: var(--antd-component-background);
90+
padding: var(--eo-drawer-inset-gap);
7991
}
8092

81-
.drawer.open .drawer-wrapper {
93+
.drawer.open .drawer-content {
8294
box-shadow:
8395
-6px 0 16px -8px rgb(0 0 0 / 8%),
8496
-9px 0 28px 0 rgb(0 0 0 / 5%),
@@ -130,22 +142,31 @@
130142
width: 100%;
131143
height: 100%;
132144
outline: 0;
145+
background: var(--antd-component-background);
133146
}
134147

135148
.drawer-header {
136149
display: flex;
137150
justify-content: space-between;
151+
align-items: start;
138152
padding: 12px 24px;
139153
border-bottom: 1px solid var(--palette-gray-4);
140154
}
141155

142-
.drawer-header-left,
156+
.title-wrapper,
143157
.drawer-header-right {
144158
display: flex;
145159
align-items: center;
146160
gap: 4px;
147161
}
148162

163+
.sub-title {
164+
color: #8c8c8c;
165+
font-size: 13px;
166+
line-height: 24px;
167+
margin-bottom: 5px;
168+
}
169+
149170
.close-btn {
150171
cursor: pointer;
151172
}
@@ -173,5 +194,33 @@
173194
padding: 12px 24px;
174195
border-top: 1px solid var(--palette-gray-4);
175196
background: var(--color-fill-bg-base-3);
176-
box-sizing: border-box;
197+
}
198+
199+
:host([theme-variant="elevo"]) {
200+
color: #262626;
201+
202+
.drawer.open .drawer-content {
203+
box-shadow: 0px 4px 18px 0px rgba(0, 0, 0, 0.15);
204+
border-radius: 8px;
205+
}
206+
207+
.drawer-header {
208+
padding: 20px 24px;
209+
border-bottom: none;
210+
}
211+
212+
.drawer-title {
213+
font-size: 18px;
214+
line-height: 28px;
215+
}
216+
217+
.drawer-body {
218+
padding-top: 10px;
219+
}
220+
221+
.drawer-footer {
222+
border-top-color: #e8e8e8;
223+
background: none;
224+
padding: 20px 24px;
225+
}
177226
}

bricks/containers/src/drawer/index.tsx

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import React, {
2-
useEffect,
3-
useMemo,
4-
useRef,
5-
CSSProperties,
6-
useState,
7-
} from "react";
1+
import React, { useEffect, useRef, CSSProperties, useState } from "react";
82
import { ReactNextElement, wrapBrick } from "@next-core/react-element";
93
import { createDecorators, type EventEmitter } from "@next-core/element";
104
import type {
@@ -36,6 +30,7 @@ export interface DrawerMapEvents {
3630
export interface DrawerProps {
3731
curElement?: HTMLElement;
3832
customTitle?: string;
33+
subTitle?: string;
3934
width?: number | string;
4035
height?: number;
4136
closable?: boolean;
@@ -48,6 +43,7 @@ export interface DrawerProps {
4843
stackable?: boolean;
4944
maskStyle?: CSSProperties;
5045
keyboard?: boolean;
46+
themeVariant?: "default" | "elevo";
5147
}
5248

5349
const { defineElement, property, event, method } = createDecorators();
@@ -57,8 +53,8 @@ const WrappedIcon = wrapBrick<GeneralIcon, GeneralIconProps>("eo-icon");
5753
* 通用抽屉构件
5854
* @author sailor
5955
* @slot - 抽屉内容插槽
60-
* @slot headerLeft - 头部左上角
61-
* @slot extra - 头部右上角
56+
* @slot headerLeft - 头部左上角(标题右侧)
57+
* @slot extra - 头部右上角(关闭按钮左侧)
6258
* @slot footer - 抽屉底部插槽
6359
* @category container-display
6460
*/
@@ -72,6 +68,11 @@ class Drawer extends ReactNextElement implements DrawerProps {
7268
*/
7369
@property() accessor customTitle: string | undefined;
7470

71+
/**
72+
* 副标题
73+
*/
74+
@property() accessor subTitle: string | undefined;
75+
7576
/**
7677
* 宽度(placement为left,right时生效)
7778
*/
@@ -150,6 +151,10 @@ class Drawer extends ReactNextElement implements DrawerProps {
150151
@property({ type: Boolean })
151152
accessor keyboard: boolean | undefined;
152153

154+
/** 主题变体 */
155+
@property({ render: false })
156+
accessor themeVariant: "default" | "elevo" | undefined;
157+
153158
/**
154159
* 是否可堆叠,开启后每次打开抽屉会将新的抽屉置于上层(zIndex ++)
155160
*
@@ -214,6 +219,7 @@ class Drawer extends ReactNextElement implements DrawerProps {
214219
return (
215220
<DrawerComponent
216221
customTitle={this.customTitle}
222+
subTitle={this.subTitle}
217223
width={this.width}
218224
height={this.height}
219225
closable={this.closable}
@@ -227,6 +233,7 @@ class Drawer extends ReactNextElement implements DrawerProps {
227233
scrollToTopWhenOpen={this.scrollToTopWhenOpen}
228234
curElement={this}
229235
keyboard={this.keyboard}
236+
themeVariant={this.themeVariant}
230237
stackable={this.stackable}
231238
stack={this.#stack}
232239
/>
@@ -241,6 +248,7 @@ interface DrawerComponentProps extends DrawerProps {
241248

242249
export function DrawerComponent({
243250
customTitle,
251+
subTitle,
244252
width = 500,
245253
height = 378,
246254
closable = true,
@@ -259,29 +267,6 @@ export function DrawerComponent({
259267
}: DrawerComponentProps) {
260268
const containerRef = useRef<HTMLDivElement>(null);
261269
const contentRef = useRef<HTMLDivElement>();
262-
const header = useMemo(
263-
() => (
264-
<div className="drawer-header">
265-
<div className="drawer-header-left">
266-
<span className="drawer-title">{customTitle}</span>
267-
<slot name="headerLeft"></slot>
268-
</div>
269-
<div className="drawer-header-right">
270-
<slot name="extra"></slot>
271-
{closable && (
272-
<WrappedIcon
273-
className="close-btn"
274-
lib="antd"
275-
theme="outlined"
276-
icon="close"
277-
onClick={onDrawerClose}
278-
/>
279-
)}
280-
</div>
281-
</div>
282-
),
283-
[closable, customTitle, onDrawerClose]
284-
);
285270

286271
const mergeMaskStyle = {
287272
backgroundColor: "var(--antd-modal-mask-bg)",
@@ -371,7 +356,27 @@ export function DrawerComponent({
371356
}}
372357
>
373358
<div className="drawer-content" tabIndex={-1} ref={containerRef}>
374-
{header}
359+
<div className="drawer-header">
360+
<div className="drawer-header-left">
361+
{subTitle ? <div className="sub-title">{subTitle}</div> : null}
362+
<div className="title-wrapper">
363+
<span className="drawer-title">{customTitle}</span>
364+
<slot name="headerLeft"></slot>
365+
</div>
366+
</div>
367+
<div className="drawer-header-right">
368+
<slot name="extra"></slot>
369+
{closable && (
370+
<WrappedIcon
371+
className="close-btn"
372+
lib="antd"
373+
theme="outlined"
374+
icon="close"
375+
onClick={onDrawerClose}
376+
/>
377+
)}
378+
</div>
379+
</div>
375380
<div className="drawer-body" ref={contentRef}>
376381
<slot></slot>
377382
</div>

0 commit comments

Comments
 (0)