From 82a162a9cf34a60d4fd64a43096d9aaec5d7b42b Mon Sep 17 00:00:00 2001 From: LuckyFBB <976060700@qq.com> Date: Thu, 22 Feb 2024 17:39:41 +0800 Subject: [PATCH 1/2] feat(modal): remove Modal.Form --- src/modal/__tests__/modalWithForm.test.tsx | 86 --------------- src/modal/components/form/index.tsx | 86 --------------- src/modal/components/modal/index.tsx | 53 --------- src/modal/demos/{basic => }/banner.tsx | 2 +- src/modal/demos/{basic => }/bannerProps.tsx | 2 +- src/modal/demos/{basic => }/basic.tsx | 0 src/modal/demos/form/advance.tsx | 115 -------------------- src/modal/demos/form/basics.tsx | 42 ------- src/modal/demos/form/classComponent.tsx | 54 --------- src/modal/demos/{basic => }/size.tsx | 2 +- src/modal/index.$tab-form.md | 34 ------ src/modal/index.md | 12 +- src/modal/{components/modal => }/index.scss | 0 src/modal/index.tsx | 67 ++++++++---- 14 files changed, 58 insertions(+), 497 deletions(-) delete mode 100644 src/modal/__tests__/modalWithForm.test.tsx delete mode 100644 src/modal/components/form/index.tsx delete mode 100644 src/modal/components/modal/index.tsx rename src/modal/demos/{basic => }/banner.tsx (93%) rename src/modal/demos/{basic => }/bannerProps.tsx (92%) rename src/modal/demos/{basic => }/basic.tsx (100%) delete mode 100644 src/modal/demos/form/advance.tsx delete mode 100644 src/modal/demos/form/basics.tsx delete mode 100644 src/modal/demos/form/classComponent.tsx rename src/modal/demos/{basic => }/size.tsx (97%) delete mode 100644 src/modal/index.$tab-form.md rename src/modal/{components/modal => }/index.scss (100%) diff --git a/src/modal/__tests__/modalWithForm.test.tsx b/src/modal/__tests__/modalWithForm.test.tsx deleted file mode 100644 index 85446d811..000000000 --- a/src/modal/__tests__/modalWithForm.test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import { cleanup, fireEvent, render, screen } from '@testing-library/react'; -import { Form, Input } from 'antd'; -import '@testing-library/jest-dom/extend-expect'; - -import Modal from '../'; - -const FormItem = Form.Item; -const EnhancedModal = Modal.Form((_props: any) => { - return ( - - - - ); -}); - -class App extends React.Component { - constructor(props: any) { - super(props); - this.state = { - visible: false, - }; - } - hideModalHandler = () => { - const { visible } = this.state; - this.setState({ visible: !visible }); - }; - render() { - return ( - <> - - - - ); - } -} - -let wrapper: any, element: any; - -beforeEach(() => { - wrapper = render( -
- -
- ); - element = wrapper.getByTestId('form'); - jest.spyOn(console, 'error').mockImplementation(() => { - return null; - }); -}); - -afterEach(cleanup); - -test('should not render modalWithForm', () => { - expect(element).toBeValid(); -}); - -test('should toggle modalWithForm when click button', () => { - fireEvent.click(wrapper.getByText('click')); - expect(screen.getByText('test-title')).toBeInTheDocument(); - // 关掉模态框 - const ele = wrapper.getByText('quit'); - fireEvent.click(ele); - expect(element).toBeValid(); -}); - -test('should trigger submit methond when form validate successful', () => { - fireEvent.click(wrapper.getByText('click')); - const eleInput = wrapper.getByTestId('test-input'); - const eleOk = wrapper.getByText('ok'); - - eleInput.onchange = jest.fn(); - fireEvent.change(eleInput, { target: { value: '1' } }); - eleOk.onclick = jest.fn(); - fireEvent.click(eleOk); - expect(eleOk.onclick).toHaveBeenCalled(); -}); diff --git a/src/modal/components/form/index.tsx b/src/modal/components/form/index.tsx deleted file mode 100644 index 4f9f8fff2..000000000 --- a/src/modal/components/form/index.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { ReactElement, useMemo } from 'react'; -import { FormProps, Modal, ModalProps } from 'antd'; - -import Form from '../../../form'; -import Utils from '../../../utils'; -import { FORM_PROPS, MODAL_PROPS } from '../../../utils/antdProps'; - -export interface IModalFormProps - extends Omit, - Omit { - /** - * modal className - * @param {string} - */ - modalClassName?: string; - /** - * 点击提交,数据验证成功后的会调事件 - * @param values - * @param record - * @returns - */ - onSubmit?: (values: Values) => void; -} - -const ModalForm = (props: IModalFormProps) => { - const { - okText = '确定', - cancelText = '取消', - layout = 'vertical', - maskClosable = false, - children, - onSubmit, - modalClassName, - } = props; - - const [formProps, modalProps] = useMemo( - () => Utils.filterAttrByArrays(props, [FORM_PROPS, MODAL_PROPS]), - [props] - ); - - const [form] = Form.useForm(); - - const okHandler = async () => { - try { - const values = await form.validateFields(); - onSubmit?.(values); - } catch (error) {} - }; - - const onCancel = (e: React.MouseEvent) => { - props.onCancel?.(e); - }; - - const afterClose = () => { - form.resetFields(); - }; - - return ( - -
- {React.cloneElement(children as ReactElement, { form, ...props })} -
-
- ); -}; - -function InternalForm

( - FormComponent: React.ComponentType & P> -): React.FC & P> { - return (props: IModalFormProps & P) => ( - - & P)} /> - - ); -} - -export default InternalForm; diff --git a/src/modal/components/modal/index.tsx b/src/modal/components/modal/index.tsx deleted file mode 100644 index 65dd6a5a0..000000000 --- a/src/modal/components/modal/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { Alert, type AlertProps, Modal, type ModalProps } from 'antd'; -import classNames from 'classnames'; -import { omit } from 'lodash'; - -import './index.scss'; - -export interface IModalProps extends ModalProps { - size?: 'small' | 'default' | 'middle' | 'large'; - banner?: AlertProps['message'] | Omit; -} - -const getWidthFromSize = (size: IModalProps['size']) => { - if (size === 'small') return 400; - if (size === 'middle') return 640; - if (size === 'large') return 900; - return 520; -}; - -const isValidBanner = (banner: IModalProps['banner']): banner is AlertProps['message'] => { - if (typeof banner === 'object') return React.isValidElement(banner); - return true; -}; - -export default function InternalModal({ - bodyStyle, - banner, - size = 'default', - children, - width, - className, - ...rest -}: IModalProps) { - const finalWidth = width ?? getWidthFromSize(size); - - return ( - - {banner && ( - - )} -

{children}
- - ); -} diff --git a/src/modal/demos/basic/banner.tsx b/src/modal/demos/banner.tsx similarity index 93% rename from src/modal/demos/basic/banner.tsx rename to src/modal/demos/banner.tsx index e6348c677..bce030366 100644 --- a/src/modal/demos/basic/banner.tsx +++ b/src/modal/demos/banner.tsx @@ -8,7 +8,7 @@ export default function Banner() { return ( <> setVisible(false)} diff --git a/src/modal/demos/basic/bannerProps.tsx b/src/modal/demos/bannerProps.tsx similarity index 92% rename from src/modal/demos/basic/bannerProps.tsx rename to src/modal/demos/bannerProps.tsx index de702ab48..602c64468 100644 --- a/src/modal/demos/basic/bannerProps.tsx +++ b/src/modal/demos/bannerProps.tsx @@ -8,7 +8,7 @@ export default function BannerProps() { return ( <> ((props) => { - return ( - <> - - - - - - - - - - - - - - ); -}); - -export default () => { - const [visible, setVisible] = useState(false); - const [index, setIndex] = useState(0); - const [dataSource, setDataSource] = useState(data); - - const columns = [ - { - title: '姓名', - dataIndex: 'name', - key: 'name', - }, - { - title: '年龄', - dataIndex: 'age', - key: 'age', - }, - { - title: '住址', - dataIndex: 'address', - key: 'address', - }, - { - title: '操作', - dataIndex: 'operate', - key: 'operate', - render: (_: void, _record: TableData, index: number) => { - return ( - - ); - }, - }, - ]; - - const onSubmit = (values: FormValues) => { - dataSource.splice(index, 0, { ...values, key: new Date() + '' }); - setDataSource([...dataSource]); - - changeVisible(); - }; - - const changeVisible = () => setVisible((v) => !v); - - return ( - <> - - - - ); -}; diff --git a/src/modal/demos/form/basics.tsx b/src/modal/demos/form/basics.tsx deleted file mode 100644 index b68882212..000000000 --- a/src/modal/demos/form/basics.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { useState } from 'react'; -import { Button, Form, Input } from 'antd'; -import { Modal } from 'dt-react-component'; - -interface FormValues { - username: string; -} - -interface CustomModalFormProps { - customAttr: string; -} - -const ModalForm = Modal.Form((props) => { - return ( - <> - - - - - - - - ); -}); - -export default () => { - const [visible, setVisible] = useState(false); - return ( - <> - - setVisible((v) => !v)} - onSubmit={(value) => { - console.log(value); - }} - customAttr={'customAttr'} - /> - - ); -}; diff --git a/src/modal/demos/form/classComponent.tsx b/src/modal/demos/form/classComponent.tsx deleted file mode 100644 index bd0d8cd02..000000000 --- a/src/modal/demos/form/classComponent.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { useState } from 'react'; -import { Button, Form, Input } from 'antd'; -import { Modal } from 'dt-react-component'; - -import { IModalFormProps } from '../form'; - -interface FormValue { - username: string; -} - -interface CustomModalFormProps { - customAttr: string; -} - -type FormItemsProps = CustomModalFormProps & IModalFormProps; - -class FormItems extends React.Component { - render(): React.ReactNode { - return ( - <> - - - - - - - - ); - } -} - -const ModalForm = Modal.Form(FormItems); - -export default () => { - const [visible, setVisible] = useState(false); - return ( - <> - - setVisible((v) => !v)} - onSubmit={(value) => { - console.log(value); - }} - customAttr={'customAttr'} - /> - - ); -}; diff --git a/src/modal/demos/basic/size.tsx b/src/modal/demos/size.tsx similarity index 97% rename from src/modal/demos/basic/size.tsx rename to src/modal/demos/size.tsx index 8035535a7..6d419e0e6 100644 --- a/src/modal/demos/basic/size.tsx +++ b/src/modal/demos/size.tsx @@ -48,7 +48,7 @@ export default function Size() { setVisible(false)} onOk={() => setVisible(false)} diff --git a/src/modal/index.$tab-form.md b/src/modal/index.$tab-form.md deleted file mode 100644 index 67464376e..000000000 --- a/src/modal/index.$tab-form.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Modal.Form ---- - -# Modal.Form 带 Form 表单的模态框 - -## 何时使用 - -当需要在模态框中收集用户的表单信息,可以在这个组件中传入自己想要的表单元素 - -## 示例 - - - - - -## API - -| 参数 | 说明 | 类型 | 默认值 | -| -------------- | ---------------------------------------------------------------------------------------------- | ----------- | ---------- | -| onSubmit | 点击确定按钮后,表单的值验证无误后的回调,接受两个参数 value:表单的值,record:其他想要提交的值 | `Function` | - | -| cancelText | 模态框取消按钮的文本 | `string` | 取消 | -| okText | 模态框确定按钮的文本 | `string` | 确定 | -| okType | 确认按钮类型 | `string` | `primary` | -| visible | 模态框显示隐藏的状态 | `boolean` | - | -| title | 标题 | `ReactNode` | - | -| modalClassName | 模态框样式类名 | `string` | - | -| children | 传入模态框的表单元素 | `ReactNode` | - | -| layout | 表单布局 | `string` | `vertical` | -| maskClosable | 点击蒙层是否允许关闭 | `boolean` | `false` | - -:::info -其余参数继承 antd4.x 的 [Form](https://ant.design/components/form-cn/#API) 和 [Modal](https://4x.ant.design/components/modal-cn/#API) -::: diff --git a/src/modal/index.md b/src/modal/index.md index f626b034d..d50924168 100644 --- a/src/modal/index.md +++ b/src/modal/index.md @@ -8,12 +8,16 @@ toc: content ## 何时使用 +- 使用模态框时,使用该组件替换 antd 的 Modal +- 支持 size 属性来快速设置宽度;限制 Modal 的高度为 600px,超出内部滚动 +- 支持 banner 属性来快速实现 Modal 内部提示 + ## 示例 - - - - + + + + ## API diff --git a/src/modal/components/modal/index.scss b/src/modal/index.scss similarity index 100% rename from src/modal/components/modal/index.scss rename to src/modal/index.scss diff --git a/src/modal/index.tsx b/src/modal/index.tsx index df9969bd5..65dd6a5a0 100644 --- a/src/modal/index.tsx +++ b/src/modal/index.tsx @@ -1,26 +1,53 @@ -import { Modal } from 'antd'; +import React from 'react'; +import { Alert, type AlertProps, Modal, type ModalProps } from 'antd'; +import classNames from 'classnames'; +import { omit } from 'lodash'; -import InternalForm from './components/form'; -import InternalModal from './components/modal'; +import './index.scss'; -type OriginalInterface = typeof Modal; +export interface IModalProps extends ModalProps { + size?: 'small' | 'default' | 'middle' | 'large'; + banner?: AlertProps['message'] | Omit; +} -const WrapperModal: any = new Proxy(InternalModal, { - get(_, key) { - if (key in Modal) { - // Skip defaultProps - if (key === 'defaultProps') return undefined; - return Modal[key as keyof OriginalInterface]; - } else if (key === 'Form') { - return InternalForm; - } - }, -}); +const getWidthFromSize = (size: IModalProps['size']) => { + if (size === 'small') return 400; + if (size === 'middle') return 640; + if (size === 'large') return 900; + return 520; +}; -type ModalStaticFunctionType = Pick; -type ModalInterface = typeof InternalModal & - ModalStaticFunctionType & { Form: typeof InternalForm }; +const isValidBanner = (banner: IModalProps['banner']): banner is AlertProps['message'] => { + if (typeof banner === 'object') return React.isValidElement(banner); + return true; +}; -export type { IModalProps } from './components/modal'; +export default function InternalModal({ + bodyStyle, + banner, + size = 'default', + children, + width, + className, + ...rest +}: IModalProps) { + const finalWidth = width ?? getWidthFromSize(size); -export default WrapperModal as ModalInterface; + return ( + + {banner && ( + + )} +
{children}
+
+ ); +} From e9485e4c89748f73c3c4bbb8835de7bccfb1b2e2 Mon Sep 17 00:00:00 2001 From: LuckyFBB <976060700@qq.com> Date: Fri, 23 Feb 2024 15:50:17 +0800 Subject: [PATCH 2/2] feat(modal): remove modal relation utils and const --- src/utils/__tests__/uttils.tests.tsx | 20 ---------- src/utils/antdProps.ts | 57 ---------------------------- src/utils/index.ts | 31 --------------- 3 files changed, 108 deletions(-) delete mode 100644 src/utils/__tests__/uttils.tests.tsx delete mode 100644 src/utils/antdProps.ts delete mode 100644 src/utils/index.ts diff --git a/src/utils/__tests__/uttils.tests.tsx b/src/utils/__tests__/uttils.tests.tsx deleted file mode 100644 index e53a85573..000000000 --- a/src/utils/__tests__/uttils.tests.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { FORM_PROPS, MODAL_PROPS } from '../antdProps'; -import Utils from '../index'; - -test('test Utils filterAttrByArrays', () => { - const [modalProps, formProps, restProps] = Utils.filterAttrByArrays( - { a: 123, b: 321, visible: false, disabled: true }, - [MODAL_PROPS, FORM_PROPS] - ); - expect(modalProps).toEqual({ - visible: false, - }); - - expect(formProps).toEqual({ - disabled: true, - }); - expect(restProps).toEqual({ - a: 123, - b: 321, - }); -}); diff --git a/src/utils/antdProps.ts b/src/utils/antdProps.ts deleted file mode 100644 index b4349b041..000000000 --- a/src/utils/antdProps.ts +++ /dev/null @@ -1,57 +0,0 @@ -export const MODAL_PROPS = [ - 'afterClose', - 'bodyStyle', - 'cancelButtonProps', - 'cancelText', - 'centered', - 'closable', - 'closeIcon', - 'confirmLoading', - 'destroyOnClose', - 'focusTriggerAfterClose', - 'footer', - 'forceRender', - 'getContainer', - 'keyboard', - 'mask', - 'maskClosable', - 'maskStyle', - 'modalRender', - 'okButtonProps', - 'okText', - 'okType', - 'style', - 'title', - 'open', - 'visible', // 兼容老版本 - 'width', - 'zIndex', - 'wrapClassName', - 'onCancel', - 'onOk', -]; - -export const FORM_PROPS = [ - 'colon', - 'disabled', - 'component', - 'fields', - 'form', - 'initialValues', - 'labelAlign', - 'labelWrap', - 'labelCol', - 'layout', - 'name', - 'preserve', - 'requiredMark', - 'scrollToFirstError', - 'size', - 'validateMessages', - 'validateTrigger', - 'wrapperCol', - 'onFieldsChange', - 'onFinish', - 'onFinishFailed', - 'onValuesChange', -]; diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index 129e90537..000000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -const utils = { - /** - * 根据 arrays 过滤当前的 attr,结果会根据 arrays 中的顺序进行排序,最后一个为未命中的集合 - * @param attr \{ a: 123, b: 321, onCancel: () => {}, disabled: true } - * @param arrays [MODAL_PROPS, FORM_PROPS] - * @returns {attr[]} [{ onCancel: () => {} }, { disabled: true }, { a: 123, b: 321 }] - */ - filterAttrByArrays(attr: { [key: string]: any }, arrays: string[][] = []) { - const result = new Array(arrays.length + 1); // 多出来一个用来存放 restProps - for (let index = 0; index < result.length; index++) { - result[index] = {}; - } - - Object.keys(attr).forEach((key: string) => { - const isFind = arrays.find((array, index) => { - if (array.includes(key)) { - result[index][key] = attr[key]; - return true; - } - return false; - }); - - if (!isFind) { - result[arrays.length][key] = attr[key]; - } - }); - return result; - }, -}; - -export default utils;