From 826f7c7b8bc1c2d780e69081ac519273bfa95a90 Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Tue, 11 Feb 2025 14:21:16 +0800 Subject: [PATCH 1/8] feat(float): add new component Float --- pnpm-lock.yaml | 102 +++++++++++++----------------------- src/float/index.tsx | 6 +-- src/float/useMergeOption.ts | 25 +++++++++ 3 files changed, 64 insertions(+), 69 deletions(-) create mode 100644 src/float/useMergeOption.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f05053e5..7af128dfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,8 +46,6 @@ specifiers: react: ^18.0.0 react-dom: ^18.0.0 react-draggable: ~4.4.6 - react-markdown: ~8.0.6 - react-syntax-highlighter: ~15.5.0 react-test-renderer: ^18.2.0 remark-gfm: ~3.0.1 resize-observer-polyfill: ^1.5.1 @@ -71,9 +69,6 @@ dependencies: rc-drawer: 5.1.0_react-dom@18.2.0+react@18.2.0 rc-virtual-list: 3.11.2_react-dom@18.2.0+react@18.2.0 react-draggable: 4.4.6_react-dom@18.2.0+react@18.2.0 - react-markdown: 8.0.7_d51bdd6a322172e118eec6adc1172a28 - react-syntax-highlighter: 15.5.0_react@18.2.0 - remark-gfm: 3.0.1 shortid: 2.2.16 showdown: 1.9.1 @@ -902,6 +897,13 @@ packages: chalk: 4.1.2 dev: true + /@commitlint/types/17.8.1: + resolution: {integrity: sha512-PXDQXkAmiMEG162Bqdh9ChML/GJZo6vU+7F03ALKDK8zYc6SuAr47LjG7hGYRqUOz+WK0dU7bQ0xzuqFMdxzeQ==} + engines: {node: '>=v14'} + dependencies: + chalk: 4.1.2 + dev: true + /@cspotcode/source-map-support/0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -2818,12 +2820,6 @@ packages: '@types/react': 18.2.25 dev: true - /@types/react-syntax-highlighter/15.5.13: - resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} - dependencies: - '@types/react': 18.2.25 - dev: true - /@types/react/18.2.25: resolution: {integrity: sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==} dependencies: @@ -2876,9 +2872,6 @@ packages: /@types/unist/2.0.8: resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==} - - /@types/yargs-parser/21.0.1: - resolution: {integrity: sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==} dev: true /@types/yargs/16.0.6: @@ -5021,6 +5014,7 @@ packages: /comma-separated-tokens/2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: true /commander/11.0.0: resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} @@ -8290,16 +8284,7 @@ packages: /hast-util-whitespace/2.0.1: resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} - - /hastscript/6.0.0: - resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} - dependencies: - '@types/hast': 2.3.6 - comma-separated-tokens: 1.0.8 - hast-util-parse-selector: 2.2.5 - property-information: 5.6.0 - space-separated-tokens: 1.1.5 - dev: false + dev: true /hastscript/7.2.0: resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} @@ -10476,6 +10461,12 @@ packages: /markdown-table/3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + dev: true + + /math-intrinsics/1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + dev: true /mathml-tag-names/2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} @@ -10901,6 +10892,7 @@ packages: /micromark-util-encode/1.1.0: resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} + dev: true /micromark-util-html-tag-name/1.2.0: resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} @@ -10932,9 +10924,11 @@ packages: /micromark-util-symbol/1.1.0: resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + dev: true /micromark-util-types/1.1.0: resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + dev: true /micromark/3.2.0: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} @@ -11119,6 +11113,7 @@ packages: /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true /ms/2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -11331,10 +11326,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - /object-inspect/1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: true - /object-is/1.1.5: resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} engines: {node: '>= 0.4'} @@ -11934,6 +11925,11 @@ packages: resolution: {integrity: sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==} dev: true + /possible-typed-array-names/1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + dev: true + /postcss-attribute-case-insensitive/5.0.2: resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==} engines: {node: ^12 || ^14 || >=16} @@ -12793,11 +12789,6 @@ packages: resolution: {integrity: sha512-tX2AYsehKDw1EORwBps+WhBFKc2kxfoFpQAjxBndbZKr4fRmMkv47XN0BghC/K1qwodB1otbe4oF23vUTFDokw==} dev: true - /prismjs/1.27.0: - resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} - engines: {node: '>=6'} - dev: false - /prismjs/1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} @@ -12848,14 +12839,9 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 - /property-information/5.6.0: - resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} - dependencies: - xtend: 4.0.2 - dev: false - /property-information/6.3.0: resolution: {integrity: sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==} + dev: true /protoduck/4.0.0: resolution: {integrity: sha512-9sxuz0YTU/68O98xuDn8NBxTVH9EuMhrBTxZdiBL0/qxRmWhB/5a8MagAebDa+98vluAZTs8kMZibCdezbRCeQ==} @@ -13723,33 +13709,7 @@ packages: /react-is/18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - - /react-markdown/8.0.7_d51bdd6a322172e118eec6adc1172a28: - resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==} - peerDependencies: - '@types/react': '>=16' - react: '>=16' - dependencies: - '@types/hast': 2.3.6 - '@types/prop-types': 15.7.8 - '@types/react': 18.2.25 - '@types/unist': 2.0.8 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 2.0.1 - prop-types: 15.8.1 - property-information: 6.3.0 - react: 18.2.0 - react-is: 18.2.0 - remark-parse: 10.0.2 - remark-rehype: 10.1.0 - space-separated-tokens: 2.0.2 - style-to-object: 0.4.2 - unified: 10.1.2 - unist-util-visit: 4.1.2 - vfile: 5.3.7 - transitivePeerDependencies: - - supports-color - dev: false + dev: true /react-merge-refs/1.1.0: resolution: {integrity: sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==} @@ -14659,6 +14619,7 @@ packages: /space-separated-tokens/2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: true /spdx-correct/3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -15912,6 +15873,14 @@ packages: qs: 6.11.2 dev: true + /use-clippy/1.0.9_react@18.2.0: + resolution: {integrity: sha512-B/zuIpRwonntyMsTz01hY4c2GF291tl+zN+qK1+A2xlAVxP27I9segLx6GquWtA3vI6FELaxBBmS5F2xUvu8uw==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + /use-isomorphic-layout-effect/1.1.2_f5a8ee5e28435b2ac8313cc823937062: resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} peerDependencies: @@ -16413,3 +16382,4 @@ packages: /zwitch/2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: true diff --git a/src/float/index.tsx b/src/float/index.tsx index d6df6e039..b35648817 100644 --- a/src/float/index.tsx +++ b/src/float/index.tsx @@ -1,14 +1,14 @@ import React, { useState } from 'react'; -import Draggable, { type DraggableEventHandler, type DraggableProps } from 'react-draggable'; +import Draggable, { DraggableEventHandler, type DraggableProps } from 'react-draggable'; import classNames from 'classnames'; -import useMergeOption, { type MergeOption } from '../useMergeOption'; +import useMergeOption from './useMergeOption'; import './index.scss'; export interface IFloatProps { className?: string; style?: React.CSSProperties; - draggable?: MergeOption>>; + draggable?: boolean | Partial>; position?: DraggableProps['position']; onChange?: DraggableProps['onStop']; } diff --git a/src/float/useMergeOption.ts b/src/float/useMergeOption.ts new file mode 100644 index 000000000..d1d3a1d58 --- /dev/null +++ b/src/float/useMergeOption.ts @@ -0,0 +1,25 @@ +import { useMemo } from 'react'; + +export type MergeOption> = boolean | T; + +export type ReturnMergeOption> = { + disabled: boolean; + options: T; +}; + +export default function useMergeOption>( + opt: MergeOption +): ReturnMergeOption { + return useMemo(() => { + if (typeof opt === 'object') { + return { + disabled: false, + options: opt, + }; + } + return { + disabled: !opt, + options: {}, + }; + }, [opt]); +} From 7fbff3a6765721c0126a8c4c2f4b5b681bb51212 Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Tue, 11 Feb 2025 14:21:43 +0800 Subject: [PATCH 2/8] feat(modal): modal support draggable --- src/modal/demos/draggable.tsx | 43 +++++++++++++++++++++++++++++++++++ src/modal/index.md | 1 + src/modal/index.scss | 5 ++++ src/modal/modal.tsx | 34 ++++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/modal/demos/draggable.tsx diff --git a/src/modal/demos/draggable.tsx b/src/modal/demos/draggable.tsx new file mode 100644 index 000000000..26a405e6e --- /dev/null +++ b/src/modal/demos/draggable.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; +import { Button, Space, Tooltip } from 'antd'; +import { Modal, TinyTag } from 'dt-react-component'; + +export default function Basic() { + const [visible, setVisible] = useState(false); + + return ( + <> + + Draggable Modal + + e.stopPropagation()} + value="Cancel" + style={{ color: '#1D78FF' }} + /> + + + } + draggable={{ + bounds: 'body', + }} + visible={visible} + onCancel={() => setVisible(false)} + onOk={() => setVisible(false)} + > +
    + {Array.from({ length: 300 }).map((_, i) => ( +
  • + {i} +
  • + ))} +
+
+ + + ); +} diff --git a/src/modal/index.md b/src/modal/index.md index d50924168..543524a42 100644 --- a/src/modal/index.md +++ b/src/modal/index.md @@ -18,6 +18,7 @@ toc: content + ## API diff --git a/src/modal/index.scss b/src/modal/index.scss index 78543bf4f..77847c2c7 100644 --- a/src/modal/index.scss +++ b/src/modal/index.scss @@ -1,6 +1,11 @@ $modal-max-height: 80vh; .dtc-modal { + &__draggable { + top: 0; + left: 0; + margin: 0; + } .ant-modal-content { max-height: $modal-max-height; display: flex; diff --git a/src/modal/modal.tsx b/src/modal/modal.tsx index 98d79a571..fbbf2d176 100644 --- a/src/modal/modal.tsx +++ b/src/modal/modal.tsx @@ -3,11 +3,17 @@ import { Alert, type AlertProps, Modal as AntdModal, type ModalProps } from 'ant import classNames from 'classnames'; import { omit } from 'lodash-es'; +import type { IFloatProps } from '../float'; +import Float from '../float'; +import useMergeOption from '../float/useMergeOption'; import './index.scss'; export interface IModalProps extends ModalProps { size?: 'small' | 'default' | 'middle' | 'large'; banner?: AlertProps['message'] | Omit; + draggable?: IFloatProps['draggable']; + position?: IFloatProps['position']; + onPositionChange?: IFloatProps['onChange']; } const getWidthFromSize = (size: IModalProps['size']) => { @@ -29,15 +35,41 @@ export default function Modal({ children, width, className, + draggable = false, + position, + onPositionChange, + modalRender, ...rest }: IModalProps) { const finalWidth = width ?? getWidthFromSize(size); + const mergedDraggable = useMergeOption( + typeof draggable === 'boolean' ? draggable : { handle: '.ant-modal-header', ...draggable } + ); + return ( + mergedDraggable.disabled ? ( + modalRender?.(modal) || modal + ) : ( + + {modalRender?.(modal) || modal} + + ) + } {...rest} > {banner && ( From 30e44e03e1722729ca5356fd4c7e93e07e265b9b Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Tue, 11 Feb 2025 17:43:36 +0800 Subject: [PATCH 3/8] feat(modal): modal support resizable --- package.json | 2 + pnpm-lock.yaml | 24 ++++++- src/float/index.tsx | 4 +- src/float/useMergeOption.ts | 9 +-- src/modal/demos/draggable.tsx | 3 + src/modal/demos/resizable.tsx | 37 +++++++++++ src/modal/demos/window.tsx | 69 ++++++++++++++++++++ src/modal/handle/index.scss | 65 +++++++++++++++++++ src/modal/handle/index.tsx | 12 ++++ src/modal/index.md | 2 + src/modal/index.scss | 11 +++- src/modal/modal.tsx | 115 +++++++++++++++++++++++++--------- 12 files changed, 316 insertions(+), 37 deletions(-) create mode 100644 src/modal/demos/resizable.tsx create mode 100644 src/modal/demos/window.tsx create mode 100644 src/modal/handle/index.scss create mode 100644 src/modal/handle/index.tsx diff --git a/package.json b/package.json index 9b3dbeedb..b7cbee70d 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@types/lodash-es": "^4.17.12", "@types/react": "^18.0.0", "@types/react-syntax-highlighter": "~15.5.13", + "@types/react-resizable": "^3.0.8", "@types/shortid": "^0.0.31", "@types/showdown": "^1.9.0", "@types/testing-library__jest-dom": "^5.14.5", @@ -125,6 +126,7 @@ "react-syntax-highlighter": "~15.5.0", "remark-gfm": "~3.0.1", "react-draggable": "~4.4.6", + "react-resizable": "^3.0.5", "shortid": "^2.2.16", "showdown": "^1.9.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7af128dfd..2eee588ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,7 +17,7 @@ specifiers: '@types/jest': ^29.2.3 '@types/lodash-es': ^4.17.12 '@types/react': ^18.0.0 - '@types/react-syntax-highlighter': ~15.5.13 + '@types/react-resizable': ^3.0.8 '@types/shortid': ^0.0.31 '@types/showdown': ^1.9.0 '@types/testing-library__jest-dom': ^5.14.5 @@ -46,6 +46,7 @@ specifiers: react: ^18.0.0 react-dom: ^18.0.0 react-draggable: ~4.4.6 + react-resizable: ^3.0.5 react-test-renderer: ^18.2.0 remark-gfm: ~3.0.1 resize-observer-polyfill: ^1.5.1 @@ -69,6 +70,7 @@ dependencies: rc-drawer: 5.1.0_react-dom@18.2.0+react@18.2.0 rc-virtual-list: 3.11.2_react-dom@18.2.0+react@18.2.0 react-draggable: 4.4.6_react-dom@18.2.0+react@18.2.0 + react-resizable: 3.0.5_react-dom@18.2.0+react@18.2.0 shortid: 2.2.16 showdown: 1.9.1 @@ -83,7 +85,7 @@ devDependencies: '@types/jest': 29.5.5 '@types/lodash-es': 4.17.12 '@types/react': 18.2.25 - '@types/react-syntax-highlighter': 15.5.13 + '@types/react-resizable': 3.0.8 '@types/shortid': 0.0.31 '@types/showdown': 1.9.4 '@types/testing-library__jest-dom': 5.14.9 @@ -2820,6 +2822,12 @@ packages: '@types/react': 18.2.25 dev: true + /@types/react-resizable/3.0.8: + resolution: {integrity: sha512-Pcvt2eGA7KNXldt1hkhVhAgZ8hK41m0mp89mFgQi7LAAEZiaLgm4fHJ5zbJZ/4m2LVaAyYrrRRv1LHDcrGQanA==} + dependencies: + '@types/react': 18.2.25 + dev: true + /@types/react/18.2.25: resolution: {integrity: sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==} dependencies: @@ -13720,6 +13728,18 @@ packages: engines: {node: '>=0.10.0'} dev: true + /react-resizable/3.0.5_react-dom@18.2.0+react@18.2.0: + resolution: {integrity: sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==} + peerDependencies: + react: '>= 16.3' + dependencies: + prop-types: 15.8.1 + react: 18.2.0 + react-draggable: 4.4.6_react-dom@18.2.0+react@18.2.0 + transitivePeerDependencies: + - react-dom + dev: false + /react-router-dom/6.3.0_react-dom@18.1.0+react@18.1.0: resolution: {integrity: sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==} peerDependencies: diff --git a/src/float/index.tsx b/src/float/index.tsx index b35648817..6edd97da1 100644 --- a/src/float/index.tsx +++ b/src/float/index.tsx @@ -2,13 +2,13 @@ import React, { useState } from 'react'; import Draggable, { DraggableEventHandler, type DraggableProps } from 'react-draggable'; import classNames from 'classnames'; -import useMergeOption from './useMergeOption'; +import useMergeOption, { MergeOption } from './useMergeOption'; import './index.scss'; export interface IFloatProps { className?: string; style?: React.CSSProperties; - draggable?: boolean | Partial>; + draggable?: MergeOption>>; position?: DraggableProps['position']; onChange?: DraggableProps['onStop']; } diff --git a/src/float/useMergeOption.ts b/src/float/useMergeOption.ts index d1d3a1d58..e42b59461 100644 --- a/src/float/useMergeOption.ts +++ b/src/float/useMergeOption.ts @@ -8,18 +8,19 @@ export type ReturnMergeOption> = { }; export default function useMergeOption>( - opt: MergeOption + opt: MergeOption, + defaultOpt?: T ): ReturnMergeOption { return useMemo(() => { - if (typeof opt === 'object') { + if (typeof opt === 'object' && !!opt) { return { disabled: false, - options: opt, + options: { ...defaultOpt, ...opt }, }; } return { disabled: !opt, - options: {}, + options: { ...defaultOpt }, }; }, [opt]); } diff --git a/src/modal/demos/draggable.tsx b/src/modal/demos/draggable.tsx index 26a405e6e..6ebb74b67 100644 --- a/src/modal/demos/draggable.tsx +++ b/src/modal/demos/draggable.tsx @@ -4,6 +4,7 @@ import { Modal, TinyTag } from 'dt-react-component'; export default function Basic() { const [visible, setVisible] = useState(false); + const [position, setPosition] = useState({ x: 120, y: 120 }); return ( <> @@ -23,6 +24,8 @@ export default function Basic() { draggable={{ bounds: 'body', }} + position={position} + onPositionChange={(_, { x, y }) => setPosition({ x, y })} visible={visible} onCancel={() => setVisible(false)} onOk={() => setVisible(false)} diff --git a/src/modal/demos/resizable.tsx b/src/modal/demos/resizable.tsx new file mode 100644 index 000000000..5801f028d --- /dev/null +++ b/src/modal/demos/resizable.tsx @@ -0,0 +1,37 @@ +import React, { useState } from 'react'; +import { Button } from 'antd'; +import { Modal, Resize, useModal } from 'dt-react-component'; +import { RectState } from 'dt-react-component/modal'; + +export default function Basic() { + const modal = useModal(); + const [rect, setRect] = useState({ width: 520, height: 520 }); + const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); + + return ( + setSize({ width: window.innerWidth, height: window.innerHeight })}> + modal.close()} + onOk={() => modal.close()} + > +
    + {Array.from({ length: 300 }).map((_, i) => ( +
  • + {i} +
  • + ))} +
+
+ +
+ ); +} diff --git a/src/modal/demos/window.tsx b/src/modal/demos/window.tsx new file mode 100644 index 000000000..bbd3341ae --- /dev/null +++ b/src/modal/demos/window.tsx @@ -0,0 +1,69 @@ +import React, { useRef, useState } from 'react'; +import { ResizeHandle } from 'react-resizable'; +import { Button } from 'antd'; +import { Modal, Resize } from 'dt-react-component'; +import { RectState } from 'dt-react-component/modal'; + +export default function Basic() { + const [visible, setVisible] = useState(false); + const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); + + const [position, setPosition] = useState({ x: 120, y: 120 }); + const [rect, setRect] = useState({ width: 520, height: 520 }); + + const resizeDirection = useRef('e'); + + // 限制 resize 超出当前屏幕 + const getMaxConstraints = (): [number, number] => { + switch (resizeDirection.current) { + case 'e': + return [size.width - position.x, size.height]; + case 'n': + return [size.width, position.y + rect.height]; + case 's': + return [size.width, size.height - position.y]; + case 'w': + return [position.x + rect.width, size.height]; + case 'ne': + return [size.width - position.x, position.y + rect.height]; + case 'nw': + return [position.x + rect.width, position.y + rect.height]; + case 'se': + return [size.width - position.x, size.height - position.y]; + case 'sw': + return [position.x + rect.width, size.height - position.y]; + default: + return [0, 0]; + } + }; + + return ( + setSize({ width: window.innerWidth, height: window.innerHeight })}> + { + resizeDirection.current = data.handle; + }, + }} + rect={rect} + draggable={{ + bounds: 'body', + }} + position={position} + onPositionChange={setPosition} + onRectChange={setRect} + onCancel={() => setVisible(false)} + onOk={() => setVisible(false)} + > + <>Just Dtstack It + + + + ); +} diff --git a/src/modal/handle/index.scss b/src/modal/handle/index.scss new file mode 100644 index 000000000..c7c362d46 --- /dev/null +++ b/src/modal/handle/index.scss @@ -0,0 +1,65 @@ +.dt-modal-resize-handle { + position: absolute; + z-index: 1; + pointer-events: initial; + &.handle-w { + cursor: col-resize; + left: -5px; + bottom: 0; + width: 10px; + height: 100%; + } + &.handle-e { + cursor: col-resize; + right: -5px; + bottom: 0; + width: 10px; + height: 100%; + } + &.handle-n { + cursor: row-resize; + left: 0; + top: -5px; + width: 100%; + height: 10px; + } + &.handle-s { + cursor: row-resize; + left: 0; + bottom: -5px; + width: 100%; + height: 10px; + } + &.handle-se { + cursor: se-resize; + right: 0; + bottom: -5px; + width: 10px; + height: 10px; + z-index: 11; + } + &.handle-ne { + cursor: ne-resize; + right: 0; + top: -5px; + width: 10px; + height: 10px; + z-index: 11; + } + &.handle-nw { + cursor: nw-resize; + left: 0; + top: -5px; + width: 10px; + height: 10px; + z-index: 11; + } + &.handle-sw { + cursor: sw-resize; + left: 0; + bottom: -5px; + width: 10px; + height: 10px; + z-index: 11; + } +} diff --git a/src/modal/handle/index.tsx b/src/modal/handle/index.tsx new file mode 100644 index 000000000..6a571cfdb --- /dev/null +++ b/src/modal/handle/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import './index.scss'; + +const Handler = React.forwardRef((props, ref) => { + const { handleAxis, ...restProps } = props; + return ( +
+ ); +}); + +export default Handler; diff --git a/src/modal/index.md b/src/modal/index.md index 543524a42..c2164bdf3 100644 --- a/src/modal/index.md +++ b/src/modal/index.md @@ -19,6 +19,8 @@ toc: content + + ## API diff --git a/src/modal/index.scss b/src/modal/index.scss index 77847c2c7..db75dff2e 100644 --- a/src/modal/index.scss +++ b/src/modal/index.scss @@ -6,8 +6,17 @@ $modal-max-height: 80vh; left: 0; margin: 0; } + &__resizable { + padding: 0; + } + // resizable 的时候会设置尺寸,所以不需要最大尺寸 + &:not(&__resizable) { + .ant-modal-content { + max-height: $modal-max-height; + } + } .ant-modal-content { - max-height: $modal-max-height; + height: 100%; display: flex; flex-direction: column; .ant-modal-body { diff --git a/src/modal/modal.tsx b/src/modal/modal.tsx index fbbf2d176..01a2bbeb8 100644 --- a/src/modal/modal.tsx +++ b/src/modal/modal.tsx @@ -1,19 +1,26 @@ -import React from 'react'; -import { Alert, type AlertProps, Modal as AntdModal, type ModalProps } from 'antd'; +import React, { useMemo } from 'react'; +import { Resizable, type ResizableProps } from 'react-resizable'; +import { Alert, type AlertProps, Modal, type ModalProps } from 'antd'; import classNames from 'classnames'; import { omit } from 'lodash-es'; import type { IFloatProps } from '../float'; import Float from '../float'; -import useMergeOption from '../float/useMergeOption'; +import useMergeOption, { MergeOption } from '../float/useMergeOption'; +import Handler from './handle'; import './index.scss'; +export type RectState = { width: number; height: number }; + export interface IModalProps extends ModalProps { size?: 'small' | 'default' | 'middle' | 'large'; banner?: AlertProps['message'] | Omit; draggable?: IFloatProps['draggable']; + resizable?: MergeOption>; + rect?: RectState; position?: IFloatProps['position']; - onPositionChange?: IFloatProps['onChange']; + onPositionChange?: (data: NonNullable) => void; + onRectChange?: (data: RectState) => void; } const getWidthFromSize = (size: IModalProps['size']) => { @@ -28,7 +35,7 @@ const isValidBanner = (banner: IModalProps['banner']): banner is AlertProps['mes return true; }; -export default function Modal({ +export default function InternalModal({ bodyStyle, banner, size = 'default', @@ -37,50 +44,102 @@ export default function Modal({ className, draggable = false, position, + resizable = false, + rect, + onRectChange, onPositionChange, modalRender, ...rest }: IModalProps) { - const finalWidth = width ?? getWidthFromSize(size); + const mergedDraggable = useMergeOption(draggable, { handle: '.ant-modal-header' }); + const mergedResizable = useMergeOption(resizable, { + axis: 'both', + resizeHandles: ['s', 'w', 'e', 'n', 'ne', 'nw', 'sw', 'se'], + width: 400, + height: 400, + minConstraints: [400, 400], + handle: , + }); - const mergedDraggable = useMergeOption( - typeof draggable === 'boolean' ? draggable : { handle: '.ant-modal-header', ...draggable } - ); + const final = useMemo(() => { + if (mergedResizable.disabled) + return { width: width ?? getWidthFromSize(size), height: 'auto' }; + return { + width: rect?.width || mergedResizable.options.width || 0, + height: rect?.height || mergedResizable.options.height || 0, + }; + }, [mergedResizable, width, size, rect]); + + const handleResize: ResizableProps['onResize'] = (e, data) => { + mergedResizable.options.onResize?.(e, data); + + const nextSize = { width: data.size.width, height: data.size.height }; + onRectChange?.(nextSize); + + if (mergedDraggable.disabled || !position) return; + const vertical = data.handle.includes('n'); + const horizontal = data.handle.includes('w'); + const offsetY = vertical ? nextSize.height - (final.height as number) : 0; + const offsetX = horizontal ? nextSize.width - (final.width as number) : 0; + const after = { + x: position.x - offsetX, + y: position.y - offsetY, + }; + onPositionChange?.(after); + }; + + const handleRenderModal = (modal: React.ReactNode) => { + const container = modalRender?.(modal) || modal; + let child = <>{container}; + if (!mergedResizable.disabled) { + child = ( + + {child} + + ); + } + if (!mergedDraggable.disabled) { + child = ( + onPositionChange?.({ x, y })} + > + {child} + + ); + } + return child; + }; return ( - - mergedDraggable.disabled ? ( - modalRender?.(modal) || modal - ) : ( - - {modalRender?.(modal) || modal} - - ) - } + style={{ height: final.height, width: final.width }} + width={final.width} + modalRender={handleRenderModal} {...rest} > {banner && ( )}
{children}
-
+ ); -} +} \ No newline at end of file From 37cee5c18f084873228bf5859dab394542a9ec7a Mon Sep 17 00:00:00 2001 From: mortalYoung Date: Wed, 19 Feb 2025 17:25:27 +0800 Subject: [PATCH 4/8] test(modal): add unit tests --- .../__snapshots__/handle.test.tsx.snap | 10 + .../__snapshots__/modal.test.tsx.snap | 249 +++++++++++++++++- src/modal/__tests__/handle.test.tsx | 19 ++ src/modal/__tests__/modal.test.tsx | 117 +++++++- src/modal/demos/draggable.tsx | 2 +- src/modal/index.md | 23 +- src/modal/modal.tsx | 4 +- 7 files changed, 408 insertions(+), 16 deletions(-) create mode 100644 src/modal/__tests__/__snapshots__/handle.test.tsx.snap create mode 100644 src/modal/__tests__/handle.test.tsx diff --git a/src/modal/__tests__/__snapshots__/handle.test.tsx.snap b/src/modal/__tests__/__snapshots__/handle.test.tsx.snap new file mode 100644 index 000000000..1dcc16280 --- /dev/null +++ b/src/modal/__tests__/__snapshots__/handle.test.tsx.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Handler Component should match snapshot 1`] = ` + +
+ +`; diff --git a/src/modal/__tests__/__snapshots__/modal.test.tsx.snap b/src/modal/__tests__/__snapshots__/modal.test.tsx.snap index ea4a52a9a..2cdec5a4a 100644 --- a/src/modal/__tests__/__snapshots__/modal.test.tsx.snap +++ b/src/modal/__tests__/__snapshots__/modal.test.tsx.snap @@ -17,7 +17,7 @@ exports[`Test Modal Component Should Match snapshot 1`] = ` aria-modal="true" class="ant-modal dtc-modal" role="dialog" - style="width: 520px;" + style="height: auto; width: 520px;" >