Skip to content

Commit

Permalink
feat: modal&drawer support appendToMain and zIndex (vbenjs#5092)
Browse files Browse the repository at this point in the history
* feat: modal/drawer support append to main content

* feat: modal zIndex support

* fix: drawer prop define

* chore: type

* fix: modal/drawer position fixed while append to body

* docs: typo

* chore: add full-width drawer in content area

* chore: remove unnecessary class
  • Loading branch information
mynetfan authored Dec 10, 2024
1 parent 018ddc7 commit e419b03
Show file tree
Hide file tree
Showing 22 changed files with 271 additions and 31 deletions.
8 changes: 8 additions & 0 deletions docs/src/components/common-ui/vben-drawer.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const [Drawer, drawerApi] = useVbenDrawer({

| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
Expand All @@ -95,6 +96,13 @@ const [Drawer, drawerApi] = useVbenDrawer({
| contentClass | modal内容区域的class | `string` | - |
| footerClass | modal底部区域的class | `string` | - |
| headerClass | modal顶部区域的class | `string` | - |
| zIndex | 抽屉的ZIndex层级 | `number` | `1000` |

::: info appendToMain

`appendToMain`可以指定将抽屉挂载到内容区域,打开抽屉时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,抽屉会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便抽屉能够正确计算高度。

:::

### Event

Expand Down
8 changes: 8 additions & 0 deletions docs/src/components/common-ui/vben-modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const [Modal, modalApi] = useVbenModal({

| 属性名 | 描述 | 类型 | 默认值 |
| --- | --- | --- | --- |
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
| title | 标题 | `string\|slot` | - |
| titleTooltip | 标题提示信息 | `string\|slot` | - |
| description | 描述信息 | `string\|slot` | - |
Expand All @@ -106,6 +107,13 @@ const [Modal, modalApi] = useVbenModal({
| footerClass | modal底部区域的class | `string` | - |
| headerClass | modal顶部区域的class | `string` | - |
| bordered | 是否显示border | `boolean` | `false` |
| zIndex | 弹窗的ZIndex层级 | `number` | `1000` |

::: info appendToMain

`appendToMain`可以指定将弹窗挂载到内容区域,打开带这招的弹窗时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,弹窗会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便弹窗能够正确计算高度。

:::

### Event

Expand Down
3 changes: 3 additions & 0 deletions packages/@core/base/shared/src/constants/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const CSS_VARIABLE_LAYOUT_HEADER_HEIGHT = `--vben-header-height`;
/** layout footer 组件的高度 */
export const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`;

/** 内容区域的组件ID */
export const ELEMENT_ID_MAIN_CONTENT = `__vben_main_content`;

/**
* @zh_CN 默认命名空间
*/
Expand Down
1 change: 1 addition & 0 deletions packages/@core/ui-kit/layout-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@vben-core/composables": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "catalog:",
"vue": "catalog:"
Expand Down
4 changes: 4 additions & 0 deletions packages/@core/ui-kit/layout-ui/src/vben-layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@vben-core/composables';
import { Menu } from '@vben-core/icons';
import { VbenIconButton } from '@vben-core/shadcn-ui';
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';
import { useMouse, useScroll, useThrottleFn } from '@vueuse/core';
Expand Down Expand Up @@ -457,6 +458,8 @@ function handleHeaderToggle() {
emit('toggleSidebar');
}
}
const idMainContent = ELEMENT_ID_MAIN_CONTENT;
</script>

<template>
Expand Down Expand Up @@ -553,6 +556,7 @@ function handleHeaderToggle() {

<!-- </div> -->
<LayoutContent
:id="idMainContent"
:content-compact="contentCompact"
:content-compact-width="contentCompactWidth"
:padding="contentPadding"
Expand Down
13 changes: 11 additions & 2 deletions packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import type { Component, Ref } from 'vue';
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';

export interface DrawerProps {
/**
* 是否挂载到内容区域
* @default false
*/
appendToMain?: boolean;
/**
* 取消按钮文字
*/
Expand Down Expand Up @@ -59,12 +64,12 @@ export interface DrawerProps {
* 弹窗头部样式
*/
headerClass?: ClassType;

/**
* 弹窗是否显示
* @default false
*/
loading?: boolean;

/**
* 是否显示遮罩
* @default true
Expand All @@ -74,12 +79,12 @@ export interface DrawerProps {
* 是否自动聚焦
*/
openAutoFocus?: boolean;

/**
* 抽屉位置
* @default right
*/
placement?: DrawerPlacement;

/**
* 是否显示取消按钮
* @default true
Expand All @@ -98,6 +103,10 @@ export interface DrawerProps {
* 弹窗标题提示
*/
titleTooltip?: string;
/**
* 抽屉层级
*/
zIndex?: number;
}

export interface DrawerState extends DrawerProps {
Expand Down
13 changes: 12 additions & 1 deletion packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { DrawerProps, ExtendedDrawerApi } from './drawer';
import { provide, ref, useId, watch } from 'vue';
import { computed, provide, ref, useId, watch } from 'vue';
import {
useIsMobile,
Expand All @@ -23,6 +23,7 @@ import {
VbenLoading,
VisuallyHidden,
} from '@vben-core/shadcn-ui';
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';
import { globalShareState } from '@vben-core/shared/global-state';
import { cn } from '@vben-core/shared/utils';
Expand All @@ -31,7 +32,9 @@ interface Props extends DrawerProps {
}
const props = withDefaults(defineProps<Props>(), {
appendToMain: false,
drawerApi: undefined,
zIndex: 1000,
});
const components = globalShareState.getComponents();
Expand All @@ -46,6 +49,7 @@ const { isMobile } = useIsMobile();
const state = props.drawerApi?.useStore?.();
const {
appendToMain,
cancelText,
class: drawerClass,
closable,
Expand All @@ -67,6 +71,7 @@ const {
showConfirmButton,
title,
titleTooltip,
zIndex,
} = usePriorityValues(props, state);
watch(
Expand Down Expand Up @@ -110,6 +115,10 @@ function handleFocusOutside(e: Event) {
e.preventDefault();
e.stopPropagation();
}
const getAppendTo = computed(() => {
return appendToMain.value ? `#${ELEMENT_ID_MAIN_CONTENT}` : undefined;
});
</script>
<template>
<Sheet
Expand All @@ -118,6 +127,7 @@ function handleFocusOutside(e: Event) {
@update:open="() => drawerApi?.close()"
>
<SheetContent
:append-to="getAppendTo"
:class="
cn('flex w-[520px] flex-col', drawerClass, {
'!w-full': isMobile || placement === 'bottom' || placement === 'top',
Expand All @@ -127,6 +137,7 @@ function handleFocusOutside(e: Event) {
:modal="modal"
:open="state?.isOpen"
:side="placement"
:z-index="zIndex"
@close-auto-focus="handleFocusOutside"
@escape-key-down="escapeKeyDown"
@focus-outside="handleFocusOutside"
Expand Down
11 changes: 10 additions & 1 deletion packages/@core/ui-kit/popup-ui/src/modal/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import type { ModalApi } from './modal-api';
import type { Component, Ref } from 'vue';

export interface ModalProps {
/**
* 是否要挂载到内容区域
* @default false
*/
appendToMain?: boolean;
/**
* 是否显示边框
* @default false
Expand All @@ -12,14 +17,14 @@ export interface ModalProps {
* 取消按钮文字
*/
cancelText?: string;

/**
* 是否居中
* @default false
*/
centered?: boolean;

class?: string;

/**
* 是否显示右上角的关闭按钮
* @default true
Expand Down Expand Up @@ -112,6 +117,10 @@ export interface ModalProps {
* 弹窗标题提示
*/
titleTooltip?: string;
/**
* 弹窗层级
*/
zIndex?: number;
}

export interface ModalState extends ModalProps {
Expand Down
9 changes: 9 additions & 0 deletions packages/@core/ui-kit/popup-ui/src/modal/modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
VbenLoading,
VisuallyHidden,
} from '@vben-core/shadcn-ui';
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';
import { globalShareState } from '@vben-core/shared/global-state';
import { cn } from '@vben-core/shared/utils';
Expand All @@ -32,6 +33,7 @@ interface Props extends ModalProps {
}
const props = withDefaults(defineProps<Props>(), {
appendToMain: false,
modalApi: undefined,
});
Expand All @@ -52,6 +54,7 @@ const { isMobile } = useIsMobile();
const state = props.modalApi?.useStore?.();
const {
appendToMain,
bordered,
cancelText,
centered,
Expand All @@ -78,6 +81,7 @@ const {
showConfirmButton,
title,
titleTooltip,
zIndex,
} = usePriorityValues(props, state);
const shouldFullscreen = computed(
Expand Down Expand Up @@ -161,6 +165,9 @@ function handleFocusOutside(e: Event) {
e.preventDefault();
e.stopPropagation();
}
const getAppendTo = computed(() => {
return appendToMain.value ? `#${ELEMENT_ID_MAIN_CONTENT}` : undefined;
});
</script>
<template>
<Dialog
Expand All @@ -170,6 +177,7 @@ function handleFocusOutside(e: Event) {
>
<DialogContent
ref="contentRef"
:append-to="getAppendTo"
:class="
cn(
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0 sm:rounded-[var(--radius)]',
Expand All @@ -187,6 +195,7 @@ function handleFocusOutside(e: Event) {
:modal="modal"
:open="state?.isOpen"
:show-close="closable"
:z-index="zIndex"
close-class="top-3"
@close-auto-focus="handleFocusOutside"
@closed="() => modalApi?.onClosed()"
Expand Down
27 changes: 23 additions & 4 deletions packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ import DialogOverlay from './DialogOverlay.vue';
const props = withDefaults(
defineProps<
{
appendTo?: HTMLElement | string;
class?: ClassType;
closeClass?: ClassType;
modal?: boolean;
open?: boolean;
showClose?: boolean;
zIndex?: number;
} & DialogContentProps
>(),
{ showClose: true },
{ appendTo: 'body', showClose: true, zIndex: 1000 },
);
const emits = defineEmits<
{ close: []; closed: []; opened: [] } & DialogContentEmits
Expand All @@ -45,6 +47,18 @@ const delegatedProps = computed(() => {
return delegated;
});
function isAppendToBody() {
return (
props.appendTo === 'body' ||
props.appendTo === document.body ||
!props.appendTo
);
}
const position = computed(() => {
return isAppendToBody() ? 'fixed' : 'absolute';
});
const forwarded = useForwardPropsEmits(delegatedProps, emits);
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
Expand All @@ -64,17 +78,22 @@ defineExpose({
</script>

<template>
<DialogPortal>
<DialogPortal :to="appendTo">
<Transition name="fade">
<DialogOverlay v-if="open && modal" @click="() => emits('close')" />
<DialogOverlay
v-if="open && modal"
:style="{ zIndex, position }"
@click="() => emits('close')"
/>
</Transition>
<DialogContent
ref="contentRef"
:style="{ zIndex, position }"
@animationend="onAnimationEnd"
v-bind="forwarded"
:class="
cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] fixed z-[1000] w-full p-6 shadow-lg outline-none sm:rounded-xl',
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl',
props.class,
)
"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,5 @@ useScrollLock();
const id = inject('DISMISSABLE_MODAL_ID');
</script>
<template>
<div
:data-dismissable-modal="id"
class="bg-overlay fixed inset-0 z-[1000]"
></div>
<div :data-dismissable-modal="id" class="bg-overlay inset-0"></div>
</template>
Loading

0 comments on commit e419b03

Please sign in to comment.