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

新增Toast和Dialog组件 #15

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ backstop_data/html_report
/.ci
/.dumi/tmp
/.vscode

project.private.config.json
119 changes: 119 additions & 0 deletions packages/bui-core/src/Dialog/Dialog.tsx
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();

Check failure on line 37 in packages/bui-core/src/Dialog/Dialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

Expected an assignment or function call and instead saw an expression
} else if (action === true) {
// TODO onCancel?.()
onConfirm && onConfirm(InputRef?.current?.value);

Check failure on line 40 in packages/bui-core/src/Dialog/Dialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

Expected an assignment or function call and instead saw an expression
}
};

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

View workflow job for this annotation

GitHub Actions / Run Lint

Anchor used as a button. Anchors are primarily expected to navigate. Use the button element instead. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md
{cancelText ? cancelText : '取消'}

Check failure on line 54 in packages/bui-core/src/Dialog/Dialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

Unnecessary use of conditional expression for default assignment
</a>
<a

Check failure on line 56 in packages/bui-core/src/Dialog/Dialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

Anchor used as a button. Anchors are primarily expected to navigate. Use the button element instead. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md
onClick={() => {
onConfirm(InputRef?.current?.value);
}}
className={`${prefixCls}-button`}
>
{confirmText ? confirmText : '确定'}

Check failure on line 62 in packages/bui-core/src/Dialog/Dialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

Unnecessary use of conditional expression for default assignment
</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 ? (

Check failure on line 97 in packages/bui-core/src/Dialog/Dialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

Unnecessary use of conditional expression for default assignment
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;
118 changes: 118 additions & 0 deletions packages/bui-core/src/Dialog/Dialog.types.ts
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;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

命名保持统一:description

/**
* 自定义按钮区域
*/
footer?: Render;
/**
* 是否显示
*/
// TODO 删除,用open
visible?: boolean;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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;
}
122 changes: 122 additions & 0 deletions packages/bui-core/src/Dialog/FunctionalDialog.tsx
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';

Check failure on line 4 in packages/bui-core/src/Dialog/FunctionalDialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

`@bifrostui/utils` import should occur before import of `./Dialog`
import {
DialogProps,
DialogPromise,
Dispatch,
DialogInstance,
PromptOptions,
ConfirmOptions,
} from './Dialog.types';

Check failure on line 12 in packages/bui-core/src/Dialog/FunctionalDialog.tsx

View workflow job for this annotation

GitHub Actions / Run Lint

Expected 1 empty line after import statement not followed by another import
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;
Loading
Loading