Skip to content

Commit 0a7279f

Browse files
authored
Merge pull request #1578 from easyops-cn/abert/eo-modal
feat(): eo-modal 支持 sidebar插槽
2 parents c5974ba + 392411a commit 0a7279f

File tree

3 files changed

+208
-3
lines changed

3 files changed

+208
-3
lines changed

bricks/containers/src/modal/index.spec.tsx

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,142 @@ describe("eo-modal", () => {
213213
document.body.removeChild(element);
214214
});
215215
});
216+
217+
test("modal without sidebar content", async () => {
218+
const element = document.createElement("eo-modal") as Modal;
219+
element.modalTitle = "Modal Title";
220+
221+
const bodyElement = document.createElement("div");
222+
bodyElement.textContent = "This is main content";
223+
224+
act(() => {
225+
element.appendChild(bodyElement);
226+
document.body.appendChild(element);
227+
});
228+
229+
await act(async () => {
230+
element.open();
231+
});
232+
233+
expect(element.visible).toBeTruthy();
234+
235+
// 没有 sidebar 内容时,不应该有 has-sidebar 类
236+
const modalElement = element.shadowRoot?.querySelector(".modal");
237+
expect(modalElement?.classList.contains("has-sidebar")).toBeFalsy();
238+
239+
// sidebar 应该存在但被隐藏
240+
const sidebarElement = element.shadowRoot?.querySelector(".modal-sidebar");
241+
expect(sidebarElement).toBeTruthy();
242+
243+
act(() => {
244+
document.body.removeChild(element);
245+
});
246+
});
247+
248+
test("modal with sidebar content", async () => {
249+
const element = document.createElement("eo-modal") as Modal;
250+
element.modalTitle = "Modal Title with Sidebar";
251+
252+
const bodyElement = document.createElement("div");
253+
bodyElement.textContent = "This is main content";
254+
255+
const sidebarElement = document.createElement("div");
256+
sidebarElement.slot = "sidebar";
257+
sidebarElement.textContent = "This is sidebar content";
258+
259+
act(() => {
260+
element.appendChild(bodyElement);
261+
element.appendChild(sidebarElement);
262+
document.body.appendChild(element);
263+
});
264+
265+
await act(async () => {
266+
element.open();
267+
});
268+
269+
// 等待 slotchange 事件触发和状态更新
270+
await act(async () => {
271+
await new Promise((resolve) => setTimeout(resolve, 10));
272+
});
273+
274+
expect(element.visible).toBeTruthy();
275+
276+
// 有 sidebar 内容时,应该有 has-sidebar 类
277+
const modalElement = element.shadowRoot?.querySelector(".modal");
278+
expect(modalElement?.classList.contains("has-sidebar")).toBeTruthy();
279+
280+
// 检查 sidebar 插槽是否正确渲染内容
281+
const sidebarSlot = element.shadowRoot?.querySelector(
282+
'slot[name="sidebar"]'
283+
) as HTMLSlotElement;
284+
expect(sidebarSlot).toBeTruthy();
285+
expect(sidebarSlot?.assignedElements().length).toBeGreaterThan(0);
286+
287+
// 验证 modal-main-content 存在
288+
const mainContent = element.shadowRoot?.querySelector(
289+
".modal-main-content"
290+
);
291+
expect(mainContent).toBeTruthy();
292+
293+
act(() => {
294+
document.body.removeChild(element);
295+
});
296+
});
297+
298+
test("sidebar content dynamically added", async () => {
299+
const element = document.createElement("eo-modal") as Modal;
300+
element.modalTitle = "Modal Title";
301+
302+
const bodyElement = document.createElement("div");
303+
bodyElement.textContent = "This is main content";
304+
305+
act(() => {
306+
element.appendChild(bodyElement);
307+
document.body.appendChild(element);
308+
});
309+
310+
await act(async () => {
311+
element.open();
312+
});
313+
314+
// 初始没有 has-sidebar 类
315+
let modalElement = element.shadowRoot?.querySelector(".modal");
316+
expect(modalElement?.classList.contains("has-sidebar")).toBeFalsy();
317+
318+
// 动态添加 sidebar 内容
319+
const sidebarElement = document.createElement("div");
320+
sidebarElement.slot = "sidebar";
321+
sidebarElement.textContent = "Dynamically added sidebar";
322+
323+
await act(async () => {
324+
element.appendChild(sidebarElement);
325+
});
326+
327+
// 等待 slotchange 事件触发
328+
await act(async () => {
329+
await new Promise((resolve) => setTimeout(resolve, 10));
330+
});
331+
332+
// 现在应该有 has-sidebar 类
333+
modalElement = element.shadowRoot?.querySelector(".modal");
334+
expect(modalElement?.classList.contains("has-sidebar")).toBeTruthy();
335+
336+
// 移除 sidebar 内容
337+
await act(async () => {
338+
element.removeChild(sidebarElement);
339+
});
340+
341+
// 等待 slotchange 事件触发
342+
await act(async () => {
343+
await new Promise((resolve) => setTimeout(resolve, 10));
344+
});
345+
346+
// 应该移除 has-sidebar 类
347+
modalElement = element.shadowRoot?.querySelector(".modal");
348+
expect(modalElement?.classList.contains("has-sidebar")).toBeFalsy();
349+
350+
act(() => {
351+
document.body.removeChild(element);
352+
});
353+
});
216354
});

bricks/containers/src/modal/index.tsx

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const { defineElement, property, event, method } = createDecorators();
7070
* @author sailor
7171
* @slot - 内容插槽
7272
* @slot footer - 底部左侧插槽
73+
* @slot sidebar - 弹窗左侧插槽
7374
* @category container-display
7475
*/
7576
@defineElement("eo-modal", {
@@ -300,6 +301,8 @@ function ModalComponent({
300301
themeVariant,
301302
}: ModalComponentProps) {
302303
const containerRef = useRef<HTMLDivElement>(null);
304+
const sidebarSlotRef = useRef<HTMLSlotElement>(null);
305+
const [hasSidebarContent, setHasSidebarContent] = useState(false);
303306

304307
const handleWrapperClick = useCallback(
305308
(e: React.MouseEvent<HTMLDivElement>): void => {
@@ -389,6 +392,40 @@ function ModalComponent({
389392
handleConfirmClick,
390393
]
391394
);
395+
const sidebar = useMemo(
396+
() => (
397+
<div className="modal-sidebar">
398+
<slot name="sidebar" ref={sidebarSlotRef}></slot>
399+
</div>
400+
),
401+
[]
402+
);
403+
404+
// 检测 sidebar 插槽是否有内容
405+
useEffect(() => {
406+
const checkSidebarContent = () => {
407+
if (sidebarSlotRef.current) {
408+
const hasContent = sidebarSlotRef.current.assignedElements().length > 0;
409+
setHasSidebarContent(hasContent);
410+
}
411+
};
412+
413+
const sidebarSlot = sidebarSlotRef.current;
414+
if (sidebarSlot) {
415+
sidebarSlot.addEventListener("slotchange", checkSidebarContent);
416+
}
417+
418+
// 当弹窗打开时,延迟检查以确保 DOM 已渲染
419+
if (open) {
420+
setTimeout(checkSidebarContent, 0);
421+
}
422+
423+
return () => {
424+
if (sidebarSlot) {
425+
sidebarSlot.removeEventListener("slotchange", checkSidebarContent);
426+
}
427+
};
428+
}, [open]);
392429

393430
const previousActiveElement = useRef<Element | null>(null);
394431

@@ -466,13 +503,17 @@ function ModalComponent({
466503
<div
467504
className={classNames("modal", {
468505
fullscreen,
506+
"has-sidebar": hasSidebarContent,
469507
})}
470508
style={{ width: width }}
471509
>
472510
<div className="modal-container" tabIndex={-1} ref={containerRef}>
473-
{header}
474-
{body}
475-
{footer}
511+
{sidebar}
512+
<div className="modal-main-content">
513+
{header}
514+
{body}
515+
{footer}
516+
</div>
476517
</div>
477518
</div>
478519
</div>

bricks/containers/src/modal/modal.shadow.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@
5454
outline: 0;
5555
}
5656

57+
.modal.has-sidebar .modal-container {
58+
display: flex;
59+
}
60+
61+
.modal-sidebar {
62+
display: none;
63+
}
64+
65+
.modal.has-sidebar .modal-sidebar {
66+
display: block;
67+
border-right: 1px solid var(--palette-gray-4);
68+
min-width: 220px;
69+
}
70+
71+
.modal-main-content {
72+
flex: 1;
73+
display: flex;
74+
flex-direction: column;
75+
min-width: 0;
76+
}
77+
5778
.modal-header {
5879
display: flex;
5980
justify-content: space-between;
@@ -82,6 +103,11 @@
82103
overflow: auto;
83104
}
84105

106+
.modal.has-sidebar .modal-body {
107+
flex: 1;
108+
overflow: auto;
109+
}
110+
85111
.modal-footer,
86112
.modal-footer-buttons {
87113
display: flex;

0 commit comments

Comments
 (0)