diff --git a/packages/Dialog/Dialog.constant.ts b/packages/Dialog/Dialog.constant.ts new file mode 100644 index 0000000..0e9f708 --- /dev/null +++ b/packages/Dialog/Dialog.constant.ts @@ -0,0 +1,64 @@ +// packages +import { CSSProperties, ReactElement } from 'react'; +// internal +import { OpenTypeMixin, ButtonMixin } from '../mixin'; + +// 默认值填充属性 +export interface NeutralDialogProps { + visible: boolean; + width: string; + messageAlign: 'left' | 'right' | 'center'; + zIndex: number; + transition: 'fade' | 'scale'; + showConfirmButton: boolean; + showCancelButton: boolean; + confirmButtonText: string; + cancelButtonText: string; + confirmButtonColor: string; + cancelButtonColor: string; + confirmButtonLoading: boolean; + cancelButtonLoading: boolean; + overlay: boolean; + overlayStyle: CSSProperties; +} + +export interface ExogenousDialogProps { + title?: string | ReactElement; + message?: string | ReactElement; + // 内嵌样式 + style?: CSSProperties; + // 容器类名,用以覆盖内部 + className?: string; + // 事件回调 + onClickOverlay?: () => void; + onCancel?: () => void; + onConfirm?: () => void; +} + +export type DialogProps = NeutralDialogProps & + ExogenousDialogProps & + OpenTypeMixin & + ButtonMixin; + +export const DefaultDialogManagerOptions: DialogProps = { + visible: false, + transition: 'scale', + width: '320px', + messageAlign: 'center', + zIndex: 100, + showConfirmButton: true, + showCancelButton: false, + confirmButtonText: '确认', + cancelButtonText: '取消', + confirmButtonColor: '#1989fa', + cancelButtonColor: '#333', + confirmButtonLoading: false, + cancelButtonLoading: false, + overlay: true, + overlayStyle: {}, +}; + +export interface DialogManagerAlertOptions extends DialogProps { + asyncClose?: boolean; + closeOnClickOverlay?: boolean; +} diff --git a/packages/Dialog/Dialog.css b/packages/Dialog/Dialog.css new file mode 100644 index 0000000..f1e41b3 --- /dev/null +++ b/packages/Dialog/Dialog.css @@ -0,0 +1,77 @@ +.van-dialog { + top: 45% !important; + overflow: hidden; + width: 320px; + width: var(--dialog-width, 320px); + font-size: 16px; + font-size: var(--dialog-font-size, 16px); + border-radius: 16px; + border-radius: var(--dialog-border-radius, 16px); + background-color: #fff; + background-color: var(--dialog-background-color, #fff); +} +@media (max-width: 321px) { + .van-dialog { + width: 90%; + width: var(--dialog-small-screen-width, 90%); + } +} +.van-dialog__header { + text-align: center; + padding-top: 24px; + padding-top: var(--dialog-header-padding-top, 24px); + font-weight: 500; + font-weight: var(--dialog-header-font-weight, 500); + line-height: 24px; + line-height: var(--dialog-header-line-height, 24px); +} +.van-dialog__header--isolated { + padding: 24px 0; + padding: var(--dialog-header-isolated-padding, 24px 0); +} +.van-dialog__message { + overflow-y: auto; + text-align: center; + -webkit-overflow-scrolling: touch; + font-size: 14px; + font-size: var(--dialog-message-font-size, 14px); + line-height: 20px; + line-height: var(--dialog-message-line-height, 20px); + max-height: 60vh; + max-height: var(--dialog-message-max-height, 60vh); + padding: 24px; + padding: var(--dialog-message-padding, 24px); +} +.van-dialog__message-text { + word-wrap: break-word; +} +.van-dialog__message--has-title { + padding-top: 12px; + padding-top: var(--dialog-has-title-message-padding-top, 12px); + color: #646566; + color: var(--dialog-has-title-message-text-color, #646566); +} +.van-dialog__message--left { + text-align: left; +} +.van-dialog__message--right { + text-align: right; +} +.van-dialog__footer { + display: flex; +} +.van-dialog__button { + flex: 1; +} +.van-dialog__confirm, +.van-dialog__cancel { + border: 0 !important; +} +.van-dialog-bounce-enter { + transform: translate3d(-50%, -50%, 0) scale(0.7); + opacity: 0; +} +.van-dialog-bounce-leave-active { + transform: translate3d(-50%, -50%, 0) scale(0.9); + opacity: 0; +} diff --git a/packages/Dialog/DialogBox.tsx b/packages/Dialog/DialogBox.tsx new file mode 100644 index 0000000..ee6e5c7 --- /dev/null +++ b/packages/Dialog/DialogBox.tsx @@ -0,0 +1,151 @@ +// packages +import React, { FunctionComponent, CSSProperties, isValidElement } from 'react'; +import clsx from 'clsx'; +import { View, Text } from 'remax/wechat'; +// internal +import Popup from '../Popup'; +import Button from '../Button'; +import { Select } from '../tools/Switch'; +import pickStyle from '../tools/pick-style'; +// self +import { DialogProps } from './Dialog.constant'; +import './Dialog.css'; + +// scope +const DialogBox: FunctionComponent = (props) => { + const { + className, + visible, + transition, + width, + zIndex, + overlay, + overlayStyle, + style, + title, + message, + messageAlign, + showCancelButton, + showConfirmButton, + cancelButtonText, + cancelButtonColor, + cancelButtonLoading, + confirmButtonText, + confirmButtonColor, + confirmButtonLoading, + } = props; + // event handlers + const { onClickOverlay, onCancel, onConfirm } = props; + // open type + const { + lang, + sessionFrom, + sendMessageTitle, + sendMessageImg, + sendMessagePath, + showMessageCard, + appParameter, + openType, + onGetUserInfo, + onContact, + onGetPhoneNumber, + onError, + onLaunchApp, + onOpenSetting, + } = props; + + const classnames = { + container: clsx(className, 'van-dialog'), + title: clsx('van-dialog__header', { + 'van-dialog__header--isolated': !message, + }), + message: clsx( + 'van-dialog__message', + `van-dialog__message--${messageAlign}`, + { + 'van-dialog__message--has-title': !!title, + } + ), + }; + const stylesheets: Record = { + container: { + ...(style || {}), + width, + }, + cancel: pickStyle({ + color: cancelButtonColor, + }), + confirm: pickStyle({ + color: confirmButtonColor, + }), + }; + + return ( + + + + + + + + + + + ); +}; + +export default DialogBox; diff --git a/packages/Dialog/DialogManager.ts b/packages/Dialog/DialogManager.ts new file mode 100644 index 0000000..c95a9c0 --- /dev/null +++ b/packages/Dialog/DialogManager.ts @@ -0,0 +1,102 @@ +/* eslint-disable import/prefer-default-export */ +// +import { + DialogProps, + DialogManagerAlertOptions, + DefaultDialogManagerOptions, +} from './Dialog.constant'; + +type Subscriber = (options: DialogProps) => void; + +class DialogManager { + public options: DialogProps; + + private queue: Subscriber[]; + + constructor(options: DialogProps) { + this.options = options; + this.queue = []; + } + + private pipe(options: Partial) { + if (this.queue.length === 0) { + throw new Error('DialogProvider required'); + } + + this.options = { ...this.options, ...options }; + this.queue.forEach((callback) => { + callback(this.options); + }); + } + + // 预设选项管理 + setDefaultOptions(options: Partial) { + this.options = { ...this.options, ...options }; + } + + resetDefaultOptions() { + this.options = { ...DefaultDialogManagerOptions }; + } + + // 半自动模式 + alert(options: Partial) { + const { asyncClose, closeOnClickOverlay, ...rest } = options; + + return new Promise((resolve, reject) => { + this.pipe({ + ...rest, + visible: true, + onConfirm: () => { + // close dialog within auto mode + if (!asyncClose) { + this.close(); + } + // fullfil promise + resolve(); + }, + onCancel: () => { + // close dialog within auto mode + if (!asyncClose) { + this.close(); + } + // fullfil promise + reject(); + }, + onClickOverlay: () => { + if (closeOnClickOverlay) { + // close dialog within auto mode + if (!asyncClose) { + this.close(); + } + // fullfil promise + reject(); + } + }, + }); + }); + } + + // 全手动模式 + any(options: Partial) { + this.pipe(options); + } + + close() { + // 关闭在前,重置在后 + this.queue.forEach((callback) => { + callback({ ...this.options, visible: false }); + }); + + // 重置内部参数 + this.options = { ...DefaultDialogManagerOptions }; + } + + subscribe(callback: Subscriber) { + this.queue.push(callback); + + // 释放订阅 + return () => this.queue.splice(this.queue.indexOf(callback), 1); + } +} + +export const Dialog = new DialogManager(DefaultDialogManagerOptions); diff --git a/packages/Dialog/DialogProvider.tsx b/packages/Dialog/DialogProvider.tsx new file mode 100644 index 0000000..8de39de --- /dev/null +++ b/packages/Dialog/DialogProvider.tsx @@ -0,0 +1,26 @@ +// package +import React, { useEffect, useState, FunctionComponent } from 'react'; +// self +import DialogBox from './DialogBox'; +import { Dialog } from './DialogManager'; +import { DialogProps } from './Dialog.constant'; + +// 目前仅支持单个 toast +export const DialogProvider: FunctionComponent = () => { + const [options, setOptions] = useState(Dialog.options); + + useEffect(() => { + const unsubscribe = Dialog.subscribe((_options) => { + setOptions(_options); + }); + + return () => { + unsubscribe(); + }; + }, []); + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ); +}; diff --git a/packages/Dialog/index.ts b/packages/Dialog/index.ts new file mode 100644 index 0000000..d5076e1 --- /dev/null +++ b/packages/Dialog/index.ts @@ -0,0 +1,5 @@ +/** + * @description - nothing but export component + */ +export { Dialog } from './DialogManager'; +export { DialogProvider } from './DialogProvider'; diff --git a/storyboard/pages/dialog/index.tsx b/storyboard/pages/dialog/index.tsx new file mode 100644 index 0000000..ae2b261 --- /dev/null +++ b/storyboard/pages/dialog/index.tsx @@ -0,0 +1,112 @@ +/* eslint-disable no-console */ +// packages +import React, { CSSProperties } from 'react'; +import { View, Text } from 'remax/wechat'; + +// internal +import Button from '../../../packages/Button'; +import Image from '../../../packages/Image'; +import { Dialog, DialogProvider } from '../../../packages/Dialog'; + +export default () => { + const title = 'FBI Waning'; + const message = '代码是写出来给人看的,附带能在机器上运行'; + + const onClickAlert = () => { + Dialog.alert({ + title, + message, + showCancelButton: true, + }) + .then(() => { + console.log('Alert confirmed!'); + }) + .catch(() => { + console.log('Alert rejected!'); + }); + }; + + const onClickAlertOverlay = () => { + Dialog.alert({ + title, + message, + showCancelButton: true, + closeOnClickOverlay: true, + }) + .then(() => { + console.log('Alert confirmed!'); + }) + .catch(() => { + console.log('Alert rejected!'); + }); + }; + + const onClickCustom = () => { + Dialog.alert({ + title, + message: ( + + + + ), + }); + }; + + const onClickManually = () => { + Dialog.any({ + title, + message, + visible: true, + showCancelButton: true, + onConfirm: () => { + Dialog.any({ + confirmButtonLoading: true, + }); + + setTimeout(() => { + Dialog.close(); + }, 1500); + }, + onCancel: () => { + Dialog.any({ + cancelButtonLoading: true, + }); + + setTimeout(() => { + Dialog.close(); + }, 1500); + }, + }); + }; + + const style: CSSProperties = { + marginBottom: 20, + }; + + return ( + + + 基础用法 + + + + + + + + + + ); +};