UNPKG

vui-design

Version:

A high quality UI Toolkit based on Vue.js

419 lines (371 loc) 11.5 kB
import VuiLazyRender from "../lazy-render"; import VuiIcon from "../icon"; import VuiButton from "../button"; import Portal from "../../directives/portal"; import Locale from "../../mixins/locale"; import Popup from "../../libs/popup"; import PropTypes from "../../utils/prop-types"; import is from "../../utils/is"; import merge from "../../utils/merge"; import styleToObject from "../../utils/styleToObject"; import addScrollbarEffect from "../../utils/addScrollbarEffect"; import getStyle from "../../utils/getStyle"; import setStyle from "../../utils/setStyle"; import getElementByEvent from "../../utils/getElementByEvent"; import getClassNamePrefix from "../../utils/getClassNamePrefix"; export const createProps = () => { return { classNamePrefix: PropTypes.string, visible: PropTypes.bool.def(false), title: PropTypes.string, showFooter: PropTypes.bool.def(true), showCancelButton: PropTypes.bool.def(true), cancelButtonProps: PropTypes.object, cancelText: PropTypes.string, cancelAsync: PropTypes.bool.def(false), showOkButton: PropTypes.bool.def(true), okButtonProps: PropTypes.object, okText: PropTypes.string, okAsync: PropTypes.bool.def(false), autofocusButton: PropTypes.oneOf(["ok", "cancel"]), closable: PropTypes.bool.def(true), placement: PropTypes.oneOf(["top", "bottom", "left", "right"]).def("right"), width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(480), height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(480), className: PropTypes.string, headerStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), bodyStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), footerStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), backdrop: PropTypes.bool.def(true), backdropClassName: PropTypes.string, backdropStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), clickBackdropToClose: PropTypes.bool.def(true), destroyOnClose: PropTypes.bool.def(false), animations: PropTypes.array.def(["vui-drawer-backdrop-fade", "vui-drawer-slide"]), getPopupContainer: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.element, PropTypes.func]).def(() => document.body) }; }; export default { name: "vui-drawer", provide() { return { vuiDrawer: this }; }, inject: { vuiDrawer: { default: undefined } }, components: { VuiLazyRender, VuiIcon, VuiButton }, directives: { Portal }, mixins: [ Locale ], model: { prop: "visible", event: "input" }, props: createProps(), data() { const { $props: props } = this; const state = { visible: props.visible, closed: props.visible ? false : true, cancelLoading: false, okLoading: false, zIndex: Popup.nextZIndex() }; return { state }; }, watch: { visible(value) { if (this.state.visible === value) { return; } this.state.visible = value; if (!value) { return; } this.state.zIndex = Popup.nextZIndex(); } }, methods: { toggle(visible) { this.state.visible = visible; this.$emit("input", visible); this.$emit("change", visible); }, open() { this.toggle(true); }, close() { this.toggle(false); }, push() { const { vuiDrawer, $refs: references, $props: props } = this; const drawer = references.drawer; const placement = props.placement; const distance = parseInt(getStyle(drawer, placement)) + 240; setStyle(drawer, placement, distance + "px"); if (vuiDrawer) { vuiDrawer.push(); } }, pull() { const { vuiDrawer, $refs: references, $props: props } = this; const drawer = references.drawer; const placement = props.placement; const distance = parseInt(getStyle(drawer, placement)) - 240; setStyle(drawer, placement, distance + "px"); if (vuiDrawer) { vuiDrawer.pull(); } }, handleBackdropClick() { const { $props: props } = this; if (!props.backdrop || !props.clickBackdropToClose) { return; } this.handleCancel(); }, handleWrapperClick(e) { const { $refs: references } = this; const target = getElementByEvent(e); if (!target || !references.wrapper || target !== references.wrapper) { return; } this.handleBackdropClick(); }, handleCancel() { const { $props: props } = this; if (props.cancelAsync) { new Promise(resolve => { this.state.cancelLoading = true; this.$emit("cancel", resolve); }).then(value => { this.state.cancelLoading = false; if (value === false) { return; } this.close(); }); } else { this.close(); this.$emit("cancel"); } }, handleOk() { const { $props: props } = this; if (props.okAsync) { new Promise(resolve => { this.state.okLoading = true; this.$emit("ok", resolve); }).then(value => { this.state.okLoading = false; if (value === false) { return; } this.close(); }); } else { this.close(); this.$emit("ok"); } }, handleBeforeEnter() { this.state.closed = false; this.$emit("beforeOpen"); }, handleEnter() { if (this.vuiDrawer) { this.vuiDrawer.push(); } else { this.scrollbarEffect = addScrollbarEffect(); } this.$emit("open"); }, handleAfterEnter() { this.$emit("afterOpen"); }, handleBeforeLeave() { this.$emit("beforeClose"); }, handleLeave() { if (this.vuiDrawer) { this.vuiDrawer.pull(); } this.$emit("close"); }, handleAfterLeave() { this.state.closed = true; if (this.scrollbarEffect) { this.scrollbarEffect.remove(); } this.$emit("afterClose"); } }, beforeDestroy() { if (this.scrollbarEffect) { this.scrollbarEffect.remove(); } }, render() { const { $slots: slots, $props: props, state, t: translate } = this; const { handleBackdropClick, handleWrapperClick, handleCancel, handleOk, handleBeforeEnter, handleEnter, handleAfterEnter, handleBeforeLeave, handleLeave, handleAfterLeave } = this; const showHeader = slots.title || props.title; // class const classNamePrefix = getClassNamePrefix(props.classNamePrefix, "drawer"); let classes = {}; classes.elBackdrop = { [`${classNamePrefix}-backdrop`]: true, [`${props.backdropClassName}`]: props.backdropClassName }; classes.elWrapper = `${classNamePrefix}-wrapper`; classes.el = { [`${classNamePrefix}`]: true, [`${classNamePrefix}-${props.placement}`]: props.placement, [`${classNamePrefix}-with-header`]: showHeader, [`${classNamePrefix}-with-footer`]: props.showFooter, [`${props.className}`]: props.className }; classes.elHeader = `${classNamePrefix}-header`; classes.elTitle = `${classNamePrefix}-title`; classes.elBody = `${classNamePrefix}-body`; classes.elFooter = `${classNamePrefix}-footer`; classes.elBtnClose = `${classNamePrefix}-btn-close`; // styles let styles = {}; styles.elBackdrop = { zIndex: state.zIndex }; styles.elWrapper = { zIndex: state.zIndex }; styles.el = {}; if (["left", "right"].indexOf(props.placement) > -1) { styles.el.width = is.string(props.width) ? props.width : `${props.width}px`; } else { styles.el.height = is.string(props.height) ? props.height : `${props.height}px`; } // render let children = []; if (props.backdrop) { let backdropProps = { class: classes.elBackdrop, style: [ styles.elBackdrop, is.string(props.backdropStyle) ? styleToObject(props.backdropStyle) : props.backdropStyle ] }; if (props.clickBackdropToClose) { backdropProps.on = { click: handleBackdropClick }; } children.push( <transition appear name={props.animations[0]}> <div ref="backdrop" v-show={state.visible} {...backdropProps}></div> </transition> ); } let header; if (showHeader) { header = ( <div class={classes.elHeader} style={props.headerStyle}> <div class={classes.elTitle}>{slots.title || props.title}</div> </div> ); } let body; body = ( <div class={classes.elBody} style={props.bodyStyle}>{props.destroyOnClose && state.closed ? null : slots.default}</div> ); let footer; if (props.showFooter) { if (slots.footer) { footer = ( <div class={classes.elFooter} style={props.footerStyle}>{slots.footer}</div> ); } else { let buttons = []; if (props.showCancelButton) { const cancelButtonProps = { props: { loading: state.cancelLoading, autofocus: props.autofocusButton === "cancel" }, on: { click: handleCancel } }; const mergedCancelButtonProps = merge(true, cancelButtonProps, props.cancelButtonProps); buttons.push( <VuiButton {...mergedCancelButtonProps}>{props.cancelText || translate("vui.drawer.cancelText")}</VuiButton> ); } if (props.showOkButton) { const okButtonProps = { props: { type: "primary", loading: state.okLoading, autofocus: props.autofocusButton === "ok" }, on: { click: handleOk } }; const mergedOkButtonProps = merge(true, okButtonProps, props.okButtonProps); buttons.push( <VuiButton {...mergedOkButtonProps}>{props.okText || translate("vui.drawer.okText")}</VuiButton> ); } footer = ( <div class={classes.elFooter} style={props.footerStyle}>{buttons}</div> ); } } let btnClose; if (props.closable) { btnClose = ( <div class={classes.elBtnClose} onClick={handleCancel}> <VuiIcon type="crossmark" /> </div> ); } children.push( <transition appear name={props.animations[0]}> <div ref="wrapper" v-show={state.visible} class={classes.elWrapper} style={styles.elWrapper} onClick={handleWrapperClick}> <transition appear name={props.animations[1]} onBeforeEnter={handleBeforeEnter} onEnter={handleEnter} onAfterEnter={handleAfterEnter} onBeforeLeave={handleBeforeLeave} onLeave={handleLeave} onAfterLeave={handleAfterLeave}> <div ref="drawer" v-show={state.visible} class={classes.el} style={styles.el}> {header} {body} {footer} {btnClose} </div> </transition> </div> </transition> ); return ( <VuiLazyRender render={state.visible}> <div v-portal={props.getPopupContainer}> {children} </div> </VuiLazyRender> ); } };