-
Notifications
You must be signed in to change notification settings - Fork 5
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
新增Toast和Dialog组件 #15
base: main
Are you sure you want to change the base?
新增Toast和Dialog组件 #15
Changes from 9 commits
d3ee5e4
649d0c5
ca402b5
5a4e047
9b09f63
19f9143
28f6c77
8d6d8a0
8023b53
8d23ee7
60dbf14
e5d28fe
e8f1a43
b16ce55
e7ecba3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,3 +36,5 @@ backstop_data/html_report | |
/.ci | ||
/.dumi/tmp | ||
/.vscode | ||
|
||
project.private.config.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import React, { useRef } from 'react'; | ||
import clsx from 'clsx'; | ||
// TODO 相对路径 | ||
import { Input } from '@bifrostui/react'; | ||
import { DialogProps, Dispatch, Render } from './Dialog.types'; | ||
import Modal from '../Modal'; | ||
import './index.less'; | ||
|
||
const prefixCls = 'bui-dialog'; | ||
|
||
const Dialog = React.forwardRef<HTMLDivElement, DialogProps>((props, ref) => { | ||
const { | ||
visible, | ||
onCancel, | ||
onConfirm, | ||
dispatch, | ||
custom, | ||
header, | ||
desc, | ||
footer, | ||
type, | ||
confirmText, | ||
cancelText, | ||
placeholder, | ||
inputProps, | ||
...others | ||
} = props; | ||
|
||
const InputRef = useRef(null); | ||
|
||
// TODO 类型中val 用不到? | ||
const dialogDispatch: Dispatch = async (action) => { | ||
if (dispatch) { | ||
dispatch(action); | ||
} else if (action === false) { | ||
// TODO onCancel?.() | ||
onCancel && onCancel(); | ||
} else if (action === true) { | ||
// TODO onCancel?.() | ||
onConfirm && onConfirm(InputRef?.current?.value); | ||
} | ||
}; | ||
|
||
const renderNode = (node: Render) => | ||
typeof node === 'function' ? node(dialogDispatch) : node; | ||
|
||
const customNode = custom && renderNode(custom); | ||
const titleNode = header && renderNode(header); | ||
const descNode = desc && renderNode(desc); | ||
const footerNode = !footer ? ( | ||
<div className={`${prefixCls}-footer`}> | ||
{/* TODO 使用Button */} | ||
<a onClick={onCancel} className={`${prefixCls}-button`}> | ||
Check failure on line 53 in packages/bui-core/src/Dialog/Dialog.tsx GitHub Actions / Run Lint
|
||
{cancelText ? cancelText : '取消'} | ||
</a> | ||
<a | ||
Check failure on line 56 in packages/bui-core/src/Dialog/Dialog.tsx GitHub Actions / Run Lint
|
||
onClick={() => { | ||
onConfirm(InputRef?.current?.value); | ||
}} | ||
className={`${prefixCls}-button`} | ||
> | ||
{confirmText ? confirmText : '确定'} | ||
</a> | ||
</div> | ||
) : ( | ||
renderNode(footer) | ||
); | ||
|
||
const inputNode = type === 'prompt' && ( | ||
<Input | ||
inputProps={inputProps} | ||
inputRef={InputRef} | ||
className={`${prefixCls}-input`} | ||
placeholder={`${placeholder || '请在此处输入'}`} | ||
/> | ||
); | ||
|
||
if (!visible) return null; | ||
|
||
return ( | ||
<Modal | ||
{...others} | ||
// TODO className | ||
className={clsx(prefixCls)} | ||
open={props.visible} | ||
disablePortal | ||
onClose={onCancel} | ||
> | ||
{/* TODO 删除一层DOM */} | ||
<div | ||
ref={ref} | ||
className={clsx(`${prefixCls}-main`)} | ||
onClick={(e) => e.stopPropagation()} | ||
> | ||
<div className={`${prefixCls}-body`}> | ||
{/* TODO 语法 */} | ||
{customNode ? ( | ||
customNode | ||
) : ( | ||
<> | ||
{titleNode && ( | ||
<h1 className={`${prefixCls}-title`}>{titleNode}</h1> | ||
)} | ||
{descNode && ( | ||
<div className={`${prefixCls}-desc`}>{descNode}</div> | ||
)} | ||
{inputNode} | ||
</> | ||
)} | ||
{!!footerNode && footerNode} | ||
</div> | ||
</div> | ||
</Modal> | ||
); | ||
}); | ||
|
||
Dialog.displayName = 'BuiDialog'; | ||
|
||
export default Dialog; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { ReactNode } from 'react'; | ||
import { ModalProps } from '../Modal/Modal.types'; | ||
|
||
export type Action = boolean | string | number; | ||
export type Dispatch = (action: Action, val?: string) => void; | ||
/** | ||
* 对话框类型 | ||
*/ | ||
export type DialogType = 'confirm' | 'prompt'; | ||
export type Render = ReactNode | ((dispatch: Dispatch) => ReactNode); | ||
|
||
export interface DialogProps extends ModalProps { | ||
/** | ||
* 对话框类型 | ||
* @default confirm | ||
*/ | ||
type?: DialogType; | ||
/** | ||
* 自定义文本区域内容 | ||
*/ | ||
custom?: Render; | ||
/** | ||
* 自定义标题 | ||
*/ | ||
header?: Render; | ||
/** | ||
* 自定义内容 | ||
*/ | ||
// TODO description | ||
desc?: Render; | ||
/** | ||
* 自定义按钮区域 | ||
*/ | ||
footer?: Render; | ||
/** | ||
* 是否显示 | ||
*/ | ||
// TODO 删除,用open | ||
visible?: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 扩展了ModalProps,可以直接使用open,不需要再新增一个visible |
||
/** | ||
* 输入框占位文本 | ||
*/ | ||
placeholder?: string; | ||
/** | ||
* 内部<input>标签的标准属性 | ||
*/ | ||
// TODO React not defined | ||
// TODO InputProps | ||
inputProps?: React.InputHTMLAttributes<HTMLInputElement>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. React not defined |
||
/** | ||
* 执行操作 | ||
*/ | ||
dispatch?: Dispatch; | ||
/** | ||
* 确认文本内容 | ||
*/ | ||
confirmText?: string; | ||
/** | ||
* 取消文本内容 | ||
*/ | ||
cancelText?: string; | ||
/** | ||
* 确认回调 | ||
*/ | ||
// TODO del void | ||
onConfirm?: (val?: string) => void | Promise<void | string>; | ||
/** | ||
* 取消回调 | ||
*/ | ||
onCancel?: () => void | Promise<void>; | ||
} | ||
|
||
/** | ||
* 函数式调用配置参数 | ||
*/ | ||
export type DialogOptions = | ||
| Omit<DialogProps, 'type' | 'placeholder' | 'inputProps'> | ||
| string; | ||
|
||
/** | ||
* prompt函数式调用配置参数 | ||
*/ | ||
export type PromptOptions = DialogOptions & { | ||
/** | ||
* 输入框占位文本 | ||
*/ | ||
placeholder?: string; | ||
/** | ||
* 内部<input>标签的标准属性 | ||
*/ | ||
inputProps?: React.InputHTMLAttributes<HTMLInputElement>; | ||
}; | ||
|
||
/** | ||
* confirm函数式调用配置参数 | ||
*/ | ||
export type ConfirmOptions = DialogOptions; | ||
/** | ||
* Dialog函数式调用返回值类型 | ||
*/ | ||
export type DialogPromise = Promise<boolean | string>; | ||
export type Options = PromptOptions | ConfirmOptions; | ||
|
||
/** | ||
* Dialog Instance | ||
*/ | ||
export interface DialogInstance { | ||
// 默认ConfirmOptions | ||
(options: Options): DialogPromise; | ||
/** | ||
* 警告提示 | ||
*/ | ||
confirm?: (options: ConfirmOptions) => DialogPromise; | ||
/** | ||
* 加载提示 | ||
*/ | ||
prompt?: (options: PromptOptions, val?: string) => DialogPromise; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import type { ReactNode } from 'react'; | ||
import React, { useCallback, useEffect, useState } from 'react'; | ||
import Popup from './Dialog'; | ||
import { getRootElement, render, unmount } from '@bifrostui/utils'; | ||
import { | ||
DialogProps, | ||
DialogPromise, | ||
Dispatch, | ||
DialogInstance, | ||
PromptOptions, | ||
ConfirmOptions, | ||
} from './Dialog.types'; | ||
const { isValidElement, Component } = React; | ||
|
||
/** | ||
* 参数格式化,支持直接传文案 | ||
*/ | ||
const formatProps = (props) => { | ||
if (typeof props === 'string' || isValidElement(props)) { | ||
return { header: props }; | ||
} | ||
return props; | ||
}; | ||
|
||
const DialogGenerator = (options: DialogProps) => { | ||
const rootWrapper = document.createElement('div'); | ||
const rootElement = getRootElement(); | ||
rootElement.appendChild(rootWrapper); | ||
|
||
const DialogComponent = () => { | ||
const { custom, footer, onConfirm, onCancel, ...others } = options; | ||
const [visible, setVisible] = useState(false); | ||
|
||
const close = useCallback(() => { | ||
setVisible(false); | ||
setTimeout(() => { | ||
const unmountRes = unmount(rootWrapper); | ||
if (unmountRes && rootWrapper.parentNode) { | ||
rootWrapper.parentNode.removeChild(rootWrapper); | ||
} | ||
}, 150); | ||
}, [rootWrapper]); | ||
|
||
useEffect(() => { | ||
setVisible(true); | ||
}, []); | ||
|
||
const dispatch: Dispatch = async (action, val) => { | ||
if (action === true) { | ||
try { | ||
await onConfirm?.(val); | ||
} catch (error) { | ||
/* empty */ | ||
} | ||
} else if (action === false) { | ||
try { | ||
await onCancel?.(); | ||
} catch (error) { | ||
/* empty */ | ||
} | ||
} | ||
close(); | ||
}; | ||
|
||
const customNode: ReactNode = | ||
typeof custom === 'function' ? custom(dispatch) : custom; | ||
const footerNode: ReactNode = | ||
typeof footer === 'function' ? footer(dispatch) : footer; | ||
|
||
return ( | ||
<Popup | ||
{...others} | ||
custom={customNode} | ||
footer={footerNode} | ||
visible={visible} | ||
onConfirm={(val) => dispatch(true, val)} | ||
onCancel={() => dispatch(false)} | ||
/> | ||
); | ||
}; | ||
|
||
return render(<DialogComponent />, rootWrapper); | ||
}; | ||
|
||
const Dialog: DialogInstance = (options: DialogProps = {}): DialogPromise => { | ||
const { onConfirm, onCancel, ...rest } = options; | ||
return new Promise((resolve) => { | ||
DialogGenerator({ | ||
...rest, | ||
onConfirm: async (val) => { | ||
await onConfirm?.(val); | ||
if (rest.type === 'prompt') resolve(val); | ||
else resolve(true); | ||
}, | ||
onCancel: async () => { | ||
await onCancel?.(); | ||
resolve(false); | ||
}, | ||
}); | ||
}); | ||
}; | ||
|
||
Dialog.prototype = Component.prototype; | ||
|
||
const confirm = (options: ConfirmOptions) => { | ||
return Dialog({ | ||
type: 'confirm', | ||
...formatProps(options), | ||
}); | ||
}; | ||
|
||
const prompt = (options: PromptOptions) => { | ||
return Dialog({ | ||
type: 'prompt', | ||
...formatProps(options), | ||
}); | ||
}; | ||
|
||
Dialog.confirm = confirm; | ||
Dialog.prompt = prompt; | ||
|
||
export default Dialog; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
命名保持统一:description