perf: improve modal and drawer component documentation and fix known problems (#4264)

* feat: improve modal and drawer component documentation and fix known problems

* chore: update ci
This commit is contained in:
Vben
2024-08-28 22:26:35 +08:00
committed by GitHub
parent 84816ef769
commit 36e7ca19a1
54 changed files with 882 additions and 176 deletions

View File

@@ -38,7 +38,6 @@ export class DrawerApi {
isOpen: false,
loading: false,
modal: true,
sharedData: {},
title: '',
};
@@ -93,7 +92,11 @@ export class DrawerApi {
* 取消操作
*/
onCancel() {
this.api.onCancel?.();
if (this.api.onCancel) {
this.api.onCancel?.();
} else {
this.close();
}
}
/**

View File

@@ -1,6 +1,8 @@
<script lang="ts" setup>
import type { DrawerProps, ExtendedDrawerApi } from './drawer';
import { ref, watch } from 'vue';
import { useIsMobile, usePriorityValue } from '@vben-core/composables';
import { Info, X } from '@vben-core/icons';
import {
@@ -31,6 +33,8 @@ const props = withDefaults(defineProps<Props>(), {
drawerApi: undefined,
});
const wrapperRef = ref<HTMLElement>();
const { isMobile } = useIsMobile();
const state = props.drawerApi?.useStore?.();
@@ -47,6 +51,18 @@ const confirmText = usePriorityValue('confirmText', props, state);
const closeOnClickModal = usePriorityValue('closeOnClickModal', props, state);
const closeOnPressEscape = usePriorityValue('closeOnPressEscape', props, state);
watch(
() => showLoading.value,
(v) => {
if (v && wrapperRef.value) {
wrapperRef.value.scrollTo({
// behavior: 'smooth',
top: 0,
});
}
},
);
function interactOutside(e: Event) {
if (!closeOnClickModal.value) {
e.preventDefault();
@@ -129,9 +145,10 @@ function pointerDownOutside(e: Event) {
</SheetHeader>
<div
ref="wrapperRef"
:class="
cn('relative flex-1 p-3', contentClass, {
'overflow-y-auto': !showLoading,
cn('relative flex-1 overflow-y-auto p-3', contentClass, {
'overflow-hidden': showLoading,
})
"
>

View File

@@ -38,10 +38,10 @@ export class ModalApi {
footer: true,
fullscreen: false,
fullscreenButton: true,
header: true,
isOpen: false,
loading: false,
modal: true,
sharedData: {},
title: '',
};

View File

@@ -60,12 +60,16 @@ export interface ModalProps {
* @default true
*/
fullscreenButton?: boolean;
/**
* 是否显示顶栏
* @default true
*/
header?: boolean;
/**
* 弹窗是否显示
* @default false
*/
loading?: boolean;
/**
* 是否显示遮罩
* @default true

View File

@@ -12,7 +12,6 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
VbenButton,
VbenIconButton,
VbenLoading,
@@ -21,8 +20,6 @@ import {
} from '@vben-core/shadcn-ui';
import { cn } from '@vben-core/shared';
// import { useElementSize } from '@vueuse/core';
import { useModalDraggable } from './use-modal-draggable';
interface Props extends ModalProps {
@@ -42,15 +39,15 @@ const props = withDefaults(defineProps<Props>(), {
});
const contentRef = ref();
const wrapperRef = ref<HTMLElement>();
const dialogRef = ref();
const headerRef = ref();
const footerRef = ref();
const { isMobile } = useIsMobile();
// const { height: headerHeight } = useElementSize(headerRef);
// const { height: footerHeight } = useElementSize(footerRef);
const state = props.modalApi?.useStore?.();
const header = usePriorityValue('header', props, state);
const title = usePriorityValue('title', props, state);
const fullscreen = usePriorityValue('fullscreen', props, state);
const description = usePriorityValue('description', props, state);
@@ -68,9 +65,12 @@ const fullscreenButton = usePriorityValue('fullscreenButton', props, state);
const closeOnClickModal = usePriorityValue('closeOnClickModal', props, state);
const closeOnPressEscape = usePriorityValue('closeOnPressEscape', props, state);
const shouldFullscreen = computed(() => fullscreen.value || isMobile.value);
const shouldFullscreen = computed(
() => (fullscreen.value && header.value) || isMobile.value,
);
const shouldDraggable = computed(
() => draggable.value && !shouldFullscreen.value,
() => draggable.value && !shouldFullscreen.value && header.value,
);
const { dragging, transform } = useModalDraggable(
@@ -79,32 +79,29 @@ const { dragging, transform } = useModalDraggable(
shouldDraggable,
);
// const loadingStyle = computed(() => {
// // py-5 4px*5*2
// const headerPadding = 40;
// // p-2 4px*2*2
// const footerPadding = 16;
// return {
// bottom: `${footerHeight.value + footerPadding}px`,
// height: `calc(100% - ${footerHeight.value + headerHeight.value + headerPadding + footerPadding}px)`,
// top: `${headerHeight.value + headerPadding}px`,
// };
// });
watch(
() => state?.value?.isOpen,
async (v) => {
if (v) {
await nextTick();
if (contentRef.value) {
const innerContentRef = contentRef.value.getContentRef();
dialogRef.value = innerContentRef.$el;
if (!contentRef.value) return;
const innerContentRef = contentRef.value.getContentRef();
dialogRef.value = innerContentRef.$el;
// reopen modal reassign value
const { offsetX, offsetY } = transform;
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
}
},
);
// reopen modal reassign value
const { offsetX, offsetY } = transform;
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
}
watch(
() => showLoading.value,
(v) => {
if (v && wrapperRef.value) {
wrapperRef.value.scrollTo({
// behavior: 'smooth',
top: 0,
});
}
},
);
@@ -142,10 +139,6 @@ function pointerDownOutside(e: Event) {
:open="state?.isOpen"
@update:open="() => modalApi?.close()"
>
<DialogTrigger v-if="$slots.trigger" as-child>
<slot name="trigger"> </slot>
</DialogTrigger>
<DialogContent
ref="contentRef"
:class="
@@ -170,8 +163,9 @@ function pointerDownOutside(e: Event) {
ref="headerRef"
:class="
cn(
'border-b px-6 py-5',
'border-b px-5 py-4',
{
hidden: !header,
'cursor-move select-none': shouldDraggable,
},
props.headerClass,
@@ -182,12 +176,14 @@ function pointerDownOutside(e: Event) {
<slot name="title">
{{ title }}
<VbenTooltip v-if="titleTooltip" side="right">
<template #trigger>
<Info class="inline-flex size-5 cursor-pointer pb-1" />
</template>
{{ titleTooltip }}
</VbenTooltip>
<slot v-if="titleTooltip" name="titleTooltip">
<VbenTooltip side="right">
<template #trigger>
<Info class="inline-flex size-5 cursor-pointer pb-1" />
</template>
{{ titleTooltip }}
</VbenTooltip>
</slot>
</slot>
</DialogTitle>
<DialogDescription v-if="description">
@@ -201,13 +197,18 @@ function pointerDownOutside(e: Event) {
</VisuallyHidden>
</DialogHeader>
<div
ref="wrapperRef"
:class="
cn('relative min-h-40 flex-1 p-3', contentClass, {
'overflow-y-auto': !showLoading,
cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {
'overflow-hidden': showLoading,
})
"
>
<VbenLoading v-if="showLoading" class="size-full" spinning />
<VbenLoading
v-if="showLoading"
class="size-full h-auto min-h-full"
spinning
/>
<slot></slot>
</div>

View File

@@ -20,9 +20,6 @@ export function useModalDraggable(
const dragging = ref(false);
// let isFirstDrag = true;
// let initialX = 0;
// let initialY = 0;
const onMousedown = (e: MouseEvent) => {
const downX = e.clientX;
const downY = e.clientY;
@@ -31,12 +28,6 @@ export function useModalDraggable(
return;
}
// if (isFirstDrag) {
// const { x, y } = getInitialTransform(targetRef.value);
// initialX = x;
// initialY = y;
// }
const targetRect = targetRef.value.getBoundingClientRect();
const { offsetX, offsetY } = transform;
@@ -56,12 +47,9 @@ export function useModalDraggable(
const onMousemove = (e: MouseEvent) => {
let moveX = offsetX + e.clientX - downX;
let moveY = offsetY + e.clientY - downY;
// const x = isFirstDrag ? initialX : 0;
// const y = isFirstDrag ? initialY : 0;
moveX = Math.min(Math.max(moveX, minLeft), maxLeft);
// + x;
moveY = Math.min(Math.max(moveY, minTop), maxTop);
// + y;
transform.offsetX = moveX;
transform.offsetY = moveY;
@@ -73,7 +61,6 @@ export function useModalDraggable(
};
const onMouseup = () => {
// isFirstDrag = false;
dragging.value = false;
document.removeEventListener('mousemove', onMousemove);
document.removeEventListener('mouseup', onMouseup);
@@ -127,20 +114,3 @@ export function useModalDraggable(
transform,
};
}
// function getInitialTransform(target: HTMLElement) {
// let x = 0;
// let y = 0;
// const transformValue = window.getComputedStyle(target)?.transform;
// if (transformValue) {
// const match = transformValue.match(/matrix\(([^)]+)\)/);
// if (match) {
// const values = match[1]?.split(', ') ?? [];
// // 获取 translateX 值
// x = Number.parseFloat(`${values[4]}`);
// // 获取 translateY 值
// y = Number.parseFloat(`${values[5]}`);
// }
// }
// return { x, y };
// }

View File

@@ -69,7 +69,7 @@ function onTransitionEnd() {
<div
:class="
cn(
'bg-overlay z-100 pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center backdrop-blur-sm transition-all duration-500',
'bg-overlay z-100 pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center transition-all duration-500',
{
'invisible opacity-0': !showSpinner,
},

View File

@@ -44,7 +44,7 @@ defineExpose({
<template>
<DialogPortal>
<DialogOverlay
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-overlay fixed inset-0 z-[1000] backdrop-blur-sm"
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-overlay fixed inset-0 z-[1000]"
data-dismissable-modal="true"
@click="() => emits('close')"
/>