Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: drawer #1708

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/devui-vue/devui/drawer/__tests__/drawer.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import { nextTick, ref } from 'vue';
import { useNamespace } from '../../shared/hooks/use-namespace';
import { useNamespace } from '@devui/shared/utils';
import { wait } from '../../shared/utils/wait';
import Drawer from '../src/drawer';
import DrawerService from '../src/drawer-service';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import '../../../styles-var/devui-var.scss';
@import '@devui/theme/styles-var/devui-var.scss';

.#{$devui-prefix}-drawer__overlay {
position: fixed;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineComponent, Transition } from 'vue';
import type { SetupContext } from 'vue';
import { drawerOverlayProps, DrawerOverlayProps } from '../drawer-types';
import { useNamespace } from '../../../shared/hooks/use-namespace';
import { useNamespace } from '@devui/shared/utils';
import './drawer-overlay.scss';

export default defineComponent({
Expand Down
1 change: 1 addition & 0 deletions packages/devui-vue/devui/drawer/src/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ZINDEX = 1;
16 changes: 8 additions & 8 deletions packages/devui-vue/devui/drawer/src/drawer-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ExtractPropTypes, PropType, Slot, Ref } from 'vue';
import type { ExtractPropTypes, PropType, Slot } from 'vue';

export const drawerProps = {
modelValue: {
Expand All @@ -9,6 +9,9 @@ export const drawerProps = {
type: Number,
default: 1040,
},
width: {
type: [Number, String]
},
showOverlay: {
type: Boolean,
default: true,
Expand All @@ -32,6 +35,10 @@ export const drawerProps = {
beforeClose: {
type: Function as PropType<(done: () => void) => void>,
},
appendToBody: {
type: Boolean,
default: true
}
};

export const drawerOverlayProps = {
Expand All @@ -53,10 +60,3 @@ export type DrawerProps = ExtractPropTypes<typeof drawerProps>;
export type DrawerOverlayProps = ExtractPropTypes<typeof drawerOverlayProps>;

export type DrawerOptions = Partial<DrawerProps> & { content?: string | Slot };

export type UseDrawerFn = {
overlayRef: Ref<HTMLElement | undefined>;
drawerRef: Ref<HTMLElement | undefined>;
drawerClasses: Ref<Record<string, boolean>>;
handleOverlayClick: () => void;
};
23 changes: 15 additions & 8 deletions packages/devui-vue/devui/drawer/src/drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineComponent, Teleport, Transition } from 'vue';
import { defineComponent, toRefs, Teleport, Transition, computed } from 'vue';
import { drawerProps, DrawerProps } from './drawer-types';
import DrawerOverlay from './components/drawer-overlay';
import { useDrawer } from './use-drawer';
Expand All @@ -10,15 +10,22 @@ export default defineComponent({
props: drawerProps,
emits: ['close', 'update:modelValue', 'open'],
setup(props: DrawerProps, { emit, slots, attrs }) {
const { overlayRef, drawerRef, drawerClasses, handleOverlayClick } = useDrawer(props, emit);
const { showOverlay, modelValue, position, appendToBody, width } = toRefs(props);
const drawerWidth = computed(() => {
if (width?.value !== undefined) {
return typeof width.value === 'number' ? `${width.value}px` : width.value;
}
return undefined;
});
const { overlayRef, drawerRef, drawerClasses, overlayZIndex, drawerZIndex, handleOverlayClick } = useDrawer(props, emit);
return () => (
<Teleport to="body">
{props.showOverlay && (
<DrawerOverlay ref={overlayRef} visible={props.modelValue} style={{ zIndex: props.zIndex - 1 }} onClick={handleOverlayClick} />
<Teleport to="body" disabled={!appendToBody.value}>
{showOverlay.value && (
<DrawerOverlay visible={modelValue.value} ref={overlayRef} style={{ zIndex: overlayZIndex.value }} onClick={handleOverlayClick} />
)}
<Transition name={`drawer-fly-${props.position}`}>
{props.modelValue && (
<div ref={drawerRef} class={drawerClasses.value} style={{ zIndex: props.zIndex }} {...attrs}>
<Transition name={`drawer-fly-${position.value}`}>
{modelValue.value && (
<div ref={drawerRef} class={drawerClasses.value} style={{ zIndex: drawerZIndex.value, width: drawerWidth.value }} {...attrs}>
{slots.default?.()}
</div>
)}
Expand Down
70 changes: 50 additions & 20 deletions packages/devui-vue/devui/drawer/src/use-drawer.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { computed, onUnmounted, ref, watch } from 'vue';
import { onClickOutside } from '@vueuse/core';
import { DrawerEmit, DrawerProps, UseDrawerFn } from './drawer-types';
import { lockScroll } from '../../shared/utils/lock-scroll';
import { useNamespace } from '../../shared/hooks/use-namespace';
import { computed, onUnmounted, ref, watch, toRefs } from 'vue';
import { onClickOutside, OnClickOutsideOptions } from '@vueuse/core';
import { DrawerEmit, DrawerProps } from './drawer-types';
import { lockScroll, } from '../../shared/utils/lock-scroll';
import { focusStack } from '../../shared/utils/focus-stack';
import type { FoucusLayer } from '../../shared/utils/focus-stack';
import { useNamespace } from '@devui/shared/utils';
import { ZINDEX } from './const';

export function useDrawer(props: DrawerProps, emit: DrawerEmit): UseDrawerFn {
export function useDrawer(props: DrawerProps, emit: DrawerEmit) {
const ns = useNamespace('drawer');
const modalNs = useNamespace('modal', true);
const { zIndex } = toRefs(props);
const drawerRef = ref<HTMLElement>();
const overlayRef = ref<HTMLElement>();
const overlayZIndex = ref(zIndex.value - 1);
const drawerZIndex = ref(zIndex.value);
const paused = ref(false);
const drawerClasses = computed(() => ({
[ns.b()]: true,
[ns.m(props.position)]: true,
}));
const layer: FoucusLayer = {
pause() {
paused.value = true;
},
resume() {
paused.value = false;
}
};
const close = () => {
emit('update:modelValue', false);
emit('close');
Expand All @@ -22,46 +36,62 @@ export function useDrawer(props: DrawerProps, emit: DrawerEmit): UseDrawerFn {
props.beforeClose ? props.beforeClose(close) : close();
};
const handleOverlayClick = () => {
props.closeOnClickOverlay && execClose();
if (props.closeOnClickOverlay && !paused.value) {
execClose();
}
};
const handleEscClose = (e: KeyboardEvent) => {
e.code === 'Escape' && execClose();
if (e.code === 'Escape' && !paused.value) {
execClose();
}
};
const handleClickOutside = (e: PointerEvent) => {
const composedPath = e.composedPath();
const modalOverlay = document.querySelectorAll(modalNs.e('overlay'));
const modal = document.querySelectorAll(modalNs.b());
const isClickModalOverlay = Array.from(modalOverlay).filter((item) => composedPath.includes(item));
const isClickModal = Array.from(modal).filter((item) => composedPath.includes(item));
if (isClickModalOverlay.length || isClickModal.length) {
const handleClickOutside = () => {
if (paused.value) {
return;
}
execClose();
};

setTimeout(() => {
onClickOutside(drawerRef, handleClickOutside, { capture: false, ignore: [overlayRef] });
onClickOutside(drawerRef, handleClickOutside, { capture: false, ignore: [overlayRef] } as OnClickOutsideOptions);
});

const calculateZIndex = () => {
const drawerDomLength = document.querySelectorAll(`.${ns.b()}`).length;
overlayZIndex.value = zIndex.value + (drawerDomLength - 1) * ZINDEX - 1;
drawerZIndex.value = zIndex.value + (drawerDomLength - 1) * ZINDEX;
};

const removeBodyAdditions = () => {
window._dvDrawerIsOpen = false;
lockScrollCb?.();
document.removeEventListener('keyup', handleEscClose);
setTimeout(() => {
focusStack.remove(layer);
});
};

watch(
() => props.modelValue,
(val) => {
(val, oldVal) => {
if (val) {
emit('open');
props.lockScroll && (lockScrollCb = lockScroll());
props.escKeyCloseable && document.addEventListener('keyup', handleEscClose);
window._dvDrawerIsOpen = true;
calculateZIndex();
focusStack.push(layer);
} else {
removeBodyAdditions();
oldVal !== undefined && removeBodyAdditions();
}
},
{
immediate: true,
flush: 'post'
}
);

onUnmounted(removeBodyAdditions);

return { overlayRef, drawerRef, drawerClasses, handleOverlayClick };
return { overlayRef, drawerRef, drawerClasses, overlayZIndex, drawerZIndex, handleOverlayClick };
}
29 changes: 29 additions & 0 deletions packages/devui-vue/devui/shared/utils/focus-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface FoucusLayer {
pause: () => void;
resume: () => void;
}
const removeFromStack = (stack: FoucusLayer[], layer: FoucusLayer) => {
const temp = [...stack];
const index = temp.findIndex((item) => item === layer);
if (index !== -1) {
temp.splice(index, 1);
}
return temp;
};
const createFocusStack = () => {
let stack: FoucusLayer[] = [];
const push = (layer: FoucusLayer) => {
const len = stack.length;
const curLayer = stack[len - 1];
if (curLayer && curLayer !== layer) {
curLayer.pause();
}
stack.push(layer);
};
const remove = (layer: FoucusLayer) => {
stack = removeFromStack(stack, layer);
stack[stack.length - 1]?.resume();
};
return { push, remove };
};
export const focusStack = createFocusStack();