From 083ab31a05ca4770955f6ea0e3899152d5f941f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=BE=F0=9D=92=96=F0=9D=92=99=F0=9D=92=89?= Date: Thu, 5 Sep 2024 12:04:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B9=E8=BF=9B=20antd=20=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E8=8C=83=E5=9B=B4=E9=99=90=E5=88=B6=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: fix version range * feat: 改进 4.x 依赖范围限制 * test: add unit test * chore: update deps * chore: move `antd-mobile` to optionalDep * chore: update lock * chore: 改进 TS 描述 * docs: update docs --- docs/guide/typescript.md | 78 +++++++++++++++++++++++++++++++ package.json | 6 ++- pnpm-lock.yaml | 35 ++++++++++---- src/drawer/index.tsx | 33 ++++++++++++- src/hooks/useMergeOpen.ts | 29 ++++++++++++ src/modal/index.tsx | 32 ++++++++++++- tests/hooks/useMergeOpen.test.tsx | 75 +++++++++++++++++++++++++++++ 7 files changed, 273 insertions(+), 15 deletions(-) create mode 100644 docs/guide/typescript.md create mode 100644 src/hooks/useMergeOpen.ts create mode 100644 tests/hooks/useMergeOpen.test.tsx diff --git a/docs/guide/typescript.md b/docs/guide/typescript.md new file mode 100644 index 0000000..fbc0a91 --- /dev/null +++ b/docs/guide/typescript.md @@ -0,0 +1,78 @@ +--- +title: 坚持使用 visible +group: + title: Legacy + order: 99 +order: 2 +--- + +### 受控请使用 `open` + +> 该库本意是通过 `trigger` 来控制 Modal, 但是在一些场景下, 我们需要自己受控方式(但其实不推荐使用这个库,直接用 antd 原生即可) + +如果需要使用受控方式, 请使用 `open` 属性来控制 Modal 的显示与隐藏。[usage#不推荐使用](./usage#不推荐使用) + +### 坚持使用 `visible` + +但其实你可以直接用 `visible` 来控制 Modal 的显示与隐藏。 例如: + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { Button } from 'antd'; +import Modal from 'easy-antd-modal'; + +export default () => { + const [visible, setVisible] = React.useState(false); + + const handleOpen = () => { + setVisible(true); + }; + + const handleClose = () => { + setVisible(false); + }; + + return ( + <> + + + I ❤️ antd + + + ); +}; +``` + +但是这里会有 TypeScript 的类型问题,因为 `visible` 是 antd 的属性,而 `easy-antd-modal` 将 `visible` 类型 Omit 了,所以你需要自己定义类型。 + +```ts +// 这段可以直接添加到你的任何 `.ts` 文件中,例如 `antd-modal.ts` +// 也可以添加到一个 `.d.ts` 文件中。确保这个文件包含在项目的 `tsconfig.json` 中的 "file" 字段内。 +import 'easy-antd-modal'; + +declare module 'easy-antd-modal' { + interface ModalProps { + /** + * `antd` 的 `Modal` 组件的 `visible` 属性 + */ + visible?: boolean; + } + interface DrawerProps { + /** + * `antd` 的 `Drawer` 组件的 `visible` 属性 + */ + visible?: boolean; + } +} + +// 为了确保这个文件被当作一个模块,添加至少一个 `export` 声明 +export {}; +``` diff --git a/package.json b/package.json index 0833267..2a093fb 100644 --- a/package.json +++ b/package.json @@ -131,11 +131,13 @@ "@dnd-kit/core": "^6", "@dnd-kit/modifiers": "^6", "@dnd-kit/utilities": "^3", - "antd": ">=4.23.0 || >=5.3.0", - "antd-mobile": "^5", + "antd": "^4.7.0 || ^5.3.0", "react": ">=17.0.0", "react-dom": ">=17.0.0" }, + "optionalDependencies": { + "antd-mobile": "^5" + }, "packageManager": "pnpm@8.6.2", "publishConfig": { "access": "public", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8024959..cda08df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,9 @@ importers: specifier: ^3 version: 3.2.2(react@18.3.1) antd: - specifier: '>=4.23.0 || >=5.3.0' + specifier: ^4.7.0 || ^5.3.0 version: 5.20.5(react-dom@18.3.1)(react@18.3.1) + optionalDependencies: antd-mobile: specifier: ^5 version: 5.37.1(react-dom@18.3.1)(react@18.3.1) @@ -2497,7 +2498,7 @@ packages: peerDependencies: react: '>=16.3.0' dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.25.6 hoist-non-react-statics: 3.3.2 react: 18.3.1 react-is: 16.13.1 @@ -2833,7 +2834,7 @@ packages: dependencies: '@babel/runtime': 7.25.6 '@rc-component/portal': 1.1.2(react-dom@18.3.1)(react@18.3.1) - classnames: 2.3.2 + classnames: 2.5.1 rc-motion: 2.9.2(react-dom@18.3.1)(react@18.3.1) rc-resize-observer: 1.4.0(react-dom@18.3.1)(react@18.3.1) rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) @@ -2866,6 +2867,7 @@ packages: '@react-spring/types': 9.6.1 react: 18.3.1 dev: false + optional: true /@react-spring/core@9.6.1(react@18.3.1): resolution: {integrity: sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==} @@ -2878,10 +2880,12 @@ packages: '@react-spring/types': 9.6.1 react: 18.3.1 dev: false + optional: true /@react-spring/rafz@9.6.1: resolution: {integrity: sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==} dev: false + optional: true /@react-spring/shared@9.6.1(react@18.3.1): resolution: {integrity: sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==} @@ -2892,10 +2896,12 @@ packages: '@react-spring/types': 9.6.1 react: 18.3.1 dev: false + optional: true /@react-spring/types@9.6.1: resolution: {integrity: sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==} dev: false + optional: true /@react-spring/web@9.6.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==} @@ -2910,6 +2916,7 @@ packages: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) dev: false + optional: true /@rollup/rollup-android-arm-eabi@4.21.2: resolution: {integrity: sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==} @@ -4265,7 +4272,7 @@ packages: /@umijs/history@5.3.1: resolution: {integrity: sha512-/e0cEGrR2bIWQD7pRl3dl9dcyRGeC9hoW0OCvUTT/hjY0EfUrkd6G8ZanVghPMpDuY5usxq9GVcvrT8KNXLWvA==} dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.25.6 query-string: 6.14.1 dev: true @@ -4553,6 +4560,7 @@ packages: /@use-gesture/core@10.3.0: resolution: {integrity: sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==} dev: false + optional: true /@use-gesture/react@10.3.0(react@18.3.1): resolution: {integrity: sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==} @@ -4562,6 +4570,7 @@ packages: '@use-gesture/core': 10.3.0 react: 18.3.1 dev: false + optional: true /@vercel/ncc@0.33.3: resolution: {integrity: sha512-JGZ11QV+/ZcfudW2Cz2JVp54/pJNXbsuWRgSh2ZmmZdQBKXqBtIGrwI1Wyx8nlbzAiEFe7FHi4K1zX4//jxTnQ==} @@ -5005,13 +5014,16 @@ packages: /antd-mobile-icons@0.3.0: resolution: {integrity: sha512-rqINQpJWZWrva9moCd1Ye695MZYWmqLPE+bY8d2xLRy7iSQwPsinCdZYjpUPp2zL/LnKYSyXxP2ut2A+DC+whQ==} dev: false + optional: true /antd-mobile-v5-count@1.0.1: resolution: {integrity: sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==} dev: false + optional: true /antd-mobile@5.37.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-r7LXWsvgI13BCwX3k0CPhRXeWVvlpyiION4k6YjoIlkc2NCah0InhVmFSkmbhSU3zufQai5ao0oFhQtVq5dY+Q==} + requiresBuild: true peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5038,6 +5050,7 @@ packages: tslib: 2.7.0 use-sync-external-store: 1.2.2(react@18.3.1) dev: false + optional: true /antd-style@3.6.2(@types/react@18.3.5)(antd@5.20.5)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-p6tRV63+U9yp3T3DB4ope1Xs3VdkhNsPD+yiZMJnR57dclPQPtrNnrGBmdGwjWYP1HlzB1XS4JHqFAyMjvObHA==} @@ -9477,7 +9490,7 @@ packages: /history@5.3.0: resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.25.6 dev: true /hmac-drbg@1.0.1: @@ -12131,6 +12144,7 @@ packages: /nano-memoize@3.0.16: resolution: {integrity: sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA==} dev: false + optional: true /nanoid@2.1.11: resolution: {integrity: sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==} @@ -14179,7 +14193,7 @@ packages: dependencies: '@babel/runtime': 7.25.6 '@rc-component/trigger': 1.18.3(react-dom@18.3.1)(react@18.3.1) - classnames: 2.3.2 + classnames: 2.5.1 rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -14211,6 +14225,7 @@ packages: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) dev: false + optional: true /rc-field-form@1.38.2(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-O83Oi1qPyEv31Sg+Jwvsj6pXc8uQI2BtIAkURr5lvEYHVggXJhdU/nynK8wY1gbw0qR48k731sN5ON4egRCROA==} @@ -14369,7 +14384,7 @@ packages: dependencies: '@babel/runtime': 7.25.6 '@rc-component/trigger': 1.18.3(react-dom@18.3.1)(react@18.3.1) - classnames: 2.3.2 + classnames: 2.5.1 rc-motion: 2.9.2(react-dom@18.3.1)(react@18.3.1) rc-overflow: 1.3.2(react-dom@18.3.1)(react@18.3.1) rc-util: 5.43.0(react-dom@18.3.1)(react@18.3.1) @@ -14757,7 +14772,7 @@ packages: react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.25.6 - classnames: 2.3.2 + classnames: 2.5.1 rc-dropdown: 4.1.0(react-dom@18.3.1)(react@18.3.1) rc-menu: 9.12.4(react-dom@18.3.1)(react@18.3.1) rc-motion: 2.9.2(react-dom@18.3.1)(react@18.3.1) @@ -15026,7 +15041,7 @@ packages: react: ^16.6.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.25.6 invariant: 2.2.4 prop-types: 15.8.1 react: 18.3.1 @@ -15673,6 +15688,7 @@ packages: /runes2@1.1.4: resolution: {integrity: sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==} dev: false + optional: true /rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} @@ -16302,6 +16318,7 @@ packages: dependencies: react: 18.3.1 dev: false + optional: true /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} diff --git a/src/drawer/index.tsx b/src/drawer/index.tsx index f1bda72..6ab37b5 100644 --- a/src/drawer/index.tsx +++ b/src/drawer/index.tsx @@ -2,13 +2,37 @@ import type { DrawerProps as AntdDrawerProps } from 'antd'; import { Drawer as AntdDrawer } from 'antd'; import type { PropsWithModalEnhanced, UseModalEnhancedProps } from '../hooks'; import { useModalEnhanced } from '../hooks'; +import useMergeOpen from '../hooks/useMergeOpen'; import usePrefixCls from '../hooks/usePrefixCls'; import type { AnyObj } from '../types'; /** @internal */ type CloseCallback = Pick; -export type DrawerProps = Omit & UseModalEnhancedProps; +/** + * @description 方便用户自定义 `Modal` 的 `props` + * @see [easy-antd-modal/typescript](https://wxh16144.github.io/easy-antd-modal/typescript) + * @example + * ```tsx + * // 这段可以直接添加到你的任何 `.ts` 文件中,例如 `antd-modal.ts` + * // 也可以添加到一个 `.d.ts` 文件中。确保这个文件包含在项目的 `tsconfig.json` 中的 "file" 字段内。 + * import 'easy-antd-modal' + * + * declare module 'easy-antd-modal' { + * interface DrawerProps { + * // `antd` 的 `Modal` 组件的 `visible` 属性 + * visible?: boolean + * } + * } + * + * // 为了确保这个文件被当作一个模块,添加至少一个 `export` 声明 + * export {} + * ``` + */ +export interface DrawerProps + extends Omit, + UseModalEnhancedProps {} + /** * @description 方便用户自定义 `Modal` 的 `props` * @since 1.6.0 @@ -28,10 +52,15 @@ const Drawer = (props: DrawerProps) => { close('onClose', event); }; + const openProp = useMergeOpen({ + visible, + ...props, + }); + return ( <> {trigger} - + {content} diff --git a/src/hooks/useMergeOpen.ts b/src/hooks/useMergeOpen.ts new file mode 100644 index 0000000..2a6fc22 --- /dev/null +++ b/src/hooks/useMergeOpen.ts @@ -0,0 +1,29 @@ +import { version } from 'antd'; + +interface UseMergeOpenProps { + visible?: boolean; + open?: boolean; +} + +export const CAN_USE_OPEN = function canUseOpen() { + const [major, minor] = (typeof version === 'string' ? version : '0.0.0') + .split('.') + .map((v) => parseInt(v, 10)); + + return ( + major >= 5 || // antd v5 + (major === 4 && minor >= 23) // antd v4.23.0+ + ); +}; + +const useMergeOpen = (props: UseMergeOpenProps) => { + const { visible, open = visible } = props; + + const key = CAN_USE_OPEN() ? 'open' : 'visible'; + + return { + [key]: open, + }; +}; + +export default useMergeOpen; diff --git a/src/modal/index.tsx b/src/modal/index.tsx index df057a5..9e1da4e 100644 --- a/src/modal/index.tsx +++ b/src/modal/index.tsx @@ -2,13 +2,36 @@ import type { ModalProps as AntdModalProps } from 'antd'; import { Modal as AntdModal } from 'antd'; import type { PropsWithModalEnhanced, UseModalEnhancedProps } from '../hooks'; import { useModalEnhanced } from '../hooks'; +import useMergeOpen from '../hooks/useMergeOpen'; import usePrefixCls from '../hooks/usePrefixCls'; import type { AnyObj } from '../types'; /** @internal */ type CloseCallback = Pick; -export type ModalProps = Omit & UseModalEnhancedProps; +/** + * @description 方便用户自定义 `Modal` 的 `props` + * @see [easy-antd-modal/typescript](https://wxh16144.github.io/easy-antd-modal/typescript) + * @example + * ```tsx + * // 这段可以直接添加到你的任何 `.ts` 文件中,例如 `antd-modal.ts` + * // 也可以添加到一个 `.d.ts` 文件中。确保这个文件包含在项目的 `tsconfig.json` 中的 "file" 字段内。 + * import 'easy-antd-modal' + * + * declare module 'easy-antd-modal' { + * interface ModalProps { + * // `antd` 的 `Modal` 组件的 `visible` 属性 + * visible?: boolean + * } + * } + * + * // 为了确保这个文件被当作一个模块,添加至少一个 `export` 声明 + * export {} + * ``` + */ +export interface ModalProps + extends Omit, + UseModalEnhancedProps {} /** * @description 方便用户自定义 `Modal` 的 `props` @@ -35,11 +58,16 @@ const Modal = (props: ModalProps) => { close('onCancel', event); }; + const openProp = useMergeOpen({ + visible, + ...props, + }); + return ( <> {trigger} { + beforeAll(async () => { + vi.resetModules(); + useMergeOpen = await import('easy-antd-modal/hooks/useMergeOpen').then((m) => m.default); + }); + + it.each(caseMap)(`test "useMergeOpen(%j)"`, (props, expected) => { + const result = useMergeOpen(props); + expect(result).toEqual(expected); + expect(result).not.toHaveProperty('visible'); + }); +}); + +describe('v4.23.0', () => { + beforeAll(async () => { + vi.resetModules(); + vi.doMock('antd', () => ({ + version: '4.23.0', + })); + + useMergeOpen = await import('easy-antd-modal/hooks/useMergeOpen').then((m) => m.default); + + return function cleanup() { + vi.doUnmock('antd'); + }; + }); + + it.each(caseMap)(`test "useMergeOpen(%j)" for v4.23.0`, (props, expected) => { + const result = useMergeOpen(props); + expect(result).toEqual(expected); + expect(result).not.toHaveProperty('visible'); + }); +}); + +describe('v4.10.2', () => { + beforeAll(async () => { + vi.resetModules(); + vi.doMock('antd', () => ({ + version: '4.10.2', + })); + + useMergeOpen = await import('easy-antd-modal/hooks/useMergeOpen').then((m) => m.default); + + return function cleanup() { + vi.doUnmock('antd'); + }; + }); + + it.each([ + [{}, { visible: void 0 }], + [{ visible: false }, { visible: false }], + [{ open: false }, { visible: false }], + [{ visible: false, open: true }, { visible: true }], + [{ visible: true, open: false }, { visible: false }], + [{ visible: true, open: void 0 }, { visible: true }], + ])(`test 'useMergeOpen(%j)' for v4.10.2`, (props, expected) => { + const result = useMergeOpen(props); + expect(result).toEqual(expected); + expect(result).not.toHaveProperty('open'); + }); +});