feat(@vben-core/popup-ui): add extra header slot to modal (#8310)

Allow custom content to be rendered in the modal header, positioned to the
left of the fullscreen toggle button, via a new `extra` slot. Mirrors the
existing `extra` slot in the drawer component.

Co-authored-by: EvoX <evox@evomap.ai>
This commit is contained in:
俞可 · YU KE
2026-08-28 15:49:13 +08:00
committed by GitHub
co-authored by EvoX
parent 390efe9f60
commit 0b8750eafb
3 changed files with 55 additions and 7 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@vben-core/popup-ui': minor
---
feat(@vben-core/popup-ui): support custom content in modal header via extra slot
@@ -106,6 +106,43 @@ describe('vben modal', () => {
document.dispatchEvent(new MouseEvent('mouseup'));
});
it('renders custom content in the extra header slot', async () => {
const mainContent = document.createElement('main');
mainContent.id = ELEMENT_ID_MAIN_CONTENT;
mainContent.innerHTML = '<div><div></div></div>';
document.body.append(mainContent);
const Consumer = defineComponent(() => {
const [Modal, modalApi] = useVbenModal({
appendToMain: true,
fullscreenButton: true,
title: 'Slot modal',
});
onMounted(() => {
modalApi.open();
});
return () =>
h(Modal, null, {
extra: () => h('span', { class: 'extra-slot-marker' }, 'Custom'),
});
});
const host = document.createElement('div');
document.body.append(host);
activeApp = createApp(() => h(Consumer));
activeApp.mount(host);
await nextTick();
await nextTick();
const marker = document.querySelector('.extra-slot-marker');
expect(marker).toBeInstanceOf(HTMLElement);
if (!(marker instanceof HTMLElement)) return;
expect(marker.textContent?.trim()).toBe('Custom');
// extra slot 应渲染在 fullscreen 按钮容器内,与全屏按钮同排
const container = marker.closest('.absolute');
expect(container).toBeInstanceOf(HTMLElement);
expect(mainContent.contains(container)).toBe(true);
});
it('fires onClosed via the fallback when no animation event arrives', async () => {
vi.useFakeTimers({
toFake: [
@@ -319,14 +319,20 @@ function handleClosed() {
<slot></slot>
</div>
<VbenLoading v-if="showLoading || submitting" spinning />
<VbenIconButton
v-if="fullscreenButton"
class="absolute top-3 right-10 flex-center size-6 rounded-full px-1 text-lg text-foreground/80 opacity-70 transition-opacity hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-hidden disabled:pointer-events-none"
@click="handleFullscreen"
<div
v-if="fullscreenButton || $slots.extra"
class="absolute top-3 right-10 flex items-center gap-1"
>
<Shrink v-if="fullscreen" class="size-3.5" />
<Expand v-else class="size-3.5" />
</VbenIconButton>
<slot name="extra"></slot>
<VbenIconButton
v-if="fullscreenButton"
class="flex-center size-6 rounded-full px-1 text-lg text-foreground/80 opacity-70 transition-opacity hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-hidden disabled:pointer-events-none"
@click="handleFullscreen"
>
<Shrink v-if="fullscreen" class="size-3.5" />
<Expand v-else class="size-3.5" />
</VbenIconButton>
</div>
<DialogFooter
ref="footerRef"