From f1f3fccc47d6dc20b7b05cff3875ee11708738bc Mon Sep 17 00:00:00 2001 From: zhouyun1 Date: Mon, 21 Aug 2023 17:09:38 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(resize-box):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/outlined/drag-dot.svg | 1 + .../components/common/drag-dot-outlined.tsx | 23 +++ packages/icons/src/icon-summation.ts | 1 + packages/icons/stories/basic.stories.tsx | 2 + packages/ui/resize-box/README.md | 11 ++ .../resize-box/__tests__/resize-box.test.js | 5 + packages/ui/resize-box/jest.config.js | 1 + packages/ui/resize-box/package.json | 63 ++++++ packages/ui/resize-box/src/ResizeBox.tsx | 181 ++++++++++++++++++ packages/ui/resize-box/src/ResizeBoxPane.tsx | 41 ++++ packages/ui/resize-box/src/Separator.tsx | 28 +++ packages/ui/resize-box/src/index.ts | 6 + packages/ui/resize-box/src/styles/index.scss | 1 + .../ui/resize-box/src/styles/resize-box.scss | 29 +++ packages/ui/resize-box/src/types.ts | 12 ++ .../ui/resize-box/stories/basic.stories.tsx | 23 +++ .../ui/resize-box/stories/index.stories.tsx | 12 ++ .../resize-box/stories/min-width.stories.tsx | 23 +++ .../resize-box/stories/separator.stories.tsx | 37 ++++ packages/ui/resize-box/tsconfig.json | 4 + yarn.lock | 8 + 21 files changed, 512 insertions(+) create mode 100644 packages/icons/icon-resources/common/outlined/drag-dot.svg create mode 100644 packages/icons/src/components/common/drag-dot-outlined.tsx create mode 100644 packages/ui/resize-box/README.md create mode 100644 packages/ui/resize-box/__tests__/resize-box.test.js create mode 100644 packages/ui/resize-box/jest.config.js create mode 100644 packages/ui/resize-box/package.json create mode 100644 packages/ui/resize-box/src/ResizeBox.tsx create mode 100644 packages/ui/resize-box/src/ResizeBoxPane.tsx create mode 100644 packages/ui/resize-box/src/Separator.tsx create mode 100644 packages/ui/resize-box/src/index.ts create mode 100644 packages/ui/resize-box/src/styles/index.scss create mode 100644 packages/ui/resize-box/src/styles/resize-box.scss create mode 100644 packages/ui/resize-box/src/types.ts create mode 100644 packages/ui/resize-box/stories/basic.stories.tsx create mode 100644 packages/ui/resize-box/stories/index.stories.tsx create mode 100644 packages/ui/resize-box/stories/min-width.stories.tsx create mode 100644 packages/ui/resize-box/stories/separator.stories.tsx create mode 100644 packages/ui/resize-box/tsconfig.json diff --git a/packages/icons/icon-resources/common/outlined/drag-dot.svg b/packages/icons/icon-resources/common/outlined/drag-dot.svg new file mode 100644 index 000000000..fa2bfdc6a --- /dev/null +++ b/packages/icons/icon-resources/common/outlined/drag-dot.svg @@ -0,0 +1 @@ + diff --git a/packages/icons/src/components/common/drag-dot-outlined.tsx b/packages/icons/src/components/common/drag-dot-outlined.tsx new file mode 100644 index 000000000..ccb44a946 --- /dev/null +++ b/packages/icons/src/components/common/drag-dot-outlined.tsx @@ -0,0 +1,23 @@ + +import React, { forwardRef } from 'react' +import { cx, getPrefixCls } from '@hi-ui/classname' +import { __DEV__ } from '@hi-ui/env' +import { IconProps } from '../../@types/props' + +const _prefix = getPrefixCls('icon-drag-dot-outlined') + +export const DragDotOutlined = forwardRef( + ({ prefixCls = _prefix, className, children, size, style: styleProp, ...rest }, ref) => { + const cls = cx(prefixCls, className) + const style = { fontSize: size, ...styleProp } + + return ( + + ) + } +) + +if (__DEV__) { + DragDotOutlined.displayName = 'DragDotOutlined' +} + \ No newline at end of file diff --git a/packages/icons/src/icon-summation.ts b/packages/icons/src/icon-summation.ts index 9c690a274..e8dc77b67 100644 --- a/packages/icons/src/icon-summation.ts +++ b/packages/icons/src/icon-summation.ts @@ -144,6 +144,7 @@ export { DislikeOutlined } from './components/common/dislike-outlined' export { DocumentOutlined } from './components/common/document-outlined' export { DocumentExclamationOutlined } from './components/common/document-exclamation-outlined' export { DownloadOutlined } from './components/common/download-outlined' +export { DragDotOutlined } from './components/common/drag-dot-outlined' export { EndDateOutlined } from './components/common/end-date-outlined' export { ExportOutlined } from './components/common/export-outlined' export { ExpressionOutlined } from './components/common/expression-outlined' diff --git a/packages/icons/stories/basic.stories.tsx b/packages/icons/stories/basic.stories.tsx index c8de37c98..b36d1a097 100644 --- a/packages/icons/stories/basic.stories.tsx +++ b/packages/icons/stories/basic.stories.tsx @@ -335,6 +335,8 @@ export const Basic = () => { { component: Icons.DownloadOutlined, tagName: 'DownloadOutlined' }, + { component: Icons.DragDotOutlined, tagName: 'DragDotOutlined' }, + { component: Icons.EndDateOutlined, tagName: 'EndDateOutlined' }, { component: Icons.ExportOutlined, tagName: 'ExportOutlined' }, diff --git a/packages/ui/resize-box/README.md b/packages/ui/resize-box/README.md new file mode 100644 index 000000000..0c5cb050d --- /dev/null +++ b/packages/ui/resize-box/README.md @@ -0,0 +1,11 @@ +# `@hi-ui/resize-box` + +> TODO: description + +## Usage + +``` +const ResizeBox = require('@hi-ui/resize-box'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/ui/resize-box/__tests__/resize-box.test.js b/packages/ui/resize-box/__tests__/resize-box.test.js new file mode 100644 index 000000000..ec3ed8c08 --- /dev/null +++ b/packages/ui/resize-box/__tests__/resize-box.test.js @@ -0,0 +1,5 @@ +const ResizeBox = require('../src') + +describe('@hi-ui/resize-box', () => { + it('needs tests', () => {}) +}) diff --git a/packages/ui/resize-box/jest.config.js b/packages/ui/resize-box/jest.config.js new file mode 100644 index 000000000..e33c14b5d --- /dev/null +++ b/packages/ui/resize-box/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../../../jest.config') diff --git a/packages/ui/resize-box/package.json b/packages/ui/resize-box/package.json new file mode 100644 index 000000000..8f53191b1 --- /dev/null +++ b/packages/ui/resize-box/package.json @@ -0,0 +1,63 @@ +{ + "name": "@hi-ui/resize-box", + "version": "4.0.0-alpha.0", + "description": "A sub-package for @hi-ui/hiui.", + "keywords": [], + "author": "HiUI ", + "homepage": "https://github.com/XiaoMi/hiui/tree/master/packages/ui/resize-box#readme", + "license": "MIT", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib" + ], + "main": "lib/cjs/index.js", + "module": "lib/esm/index.js", + "types": "lib/types/index.d.ts", + "typings": "lib/types/index.d.ts", + "exports": { + ".": { + "require": "./lib/cjs/index.js", + "default": "./lib/esm/index.js" + } + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/XiaoMi/hiui.git" + }, + "scripts": { + "test": "jest", + "clean": "rimraf lib", + "prebuild": "yarn clean", + "build:esm": "hi-build ./src/index.ts --format esm -d ./lib/esm", + "build:cjs": "hi-build ./src/index.ts --format cjs -d ./lib/cjs", + "build:types": "tsc --emitDeclarationOnly --declaration --declarationDir lib/types", + "build": "concurrently yarn:build:*" + }, + "bugs": { + "url": "https://github.com/XiaoMi/hiui/issues" + }, + "dependencies": { + "@hi-ui/classname": "^4.0.0", + "@hi-ui/env": "^4.0.0", + "@hi-ui/use-merge-refs": "^4.0.1", + "@hi-ui/use-uncontrolled-state": "^4.0.1", + "react-resizable": "^3.0.5" + }, + "peerDependencies": { + "@hi-ui/core": ">=4.0.0", + "react": ">=16.8.6", + "react-dom": ">=16.8.6" + }, + "devDependencies": { + "@hi-ui/core": "^4.0.0", + "@hi-ui/core-css": "^4.0.0", + "react": "^17.0.1", + "react-dom": "^17.0.1" + } +} diff --git a/packages/ui/resize-box/src/ResizeBox.tsx b/packages/ui/resize-box/src/ResizeBox.tsx new file mode 100644 index 000000000..bcfebd101 --- /dev/null +++ b/packages/ui/resize-box/src/ResizeBox.tsx @@ -0,0 +1,181 @@ +import React, { forwardRef } from 'react' +import { Resizable } from 'react-resizable' +import { cx, getPrefixCls } from '@hi-ui/classname' +import { __DEV__ } from '@hi-ui/env' +import { HiBaseHTMLProps } from '@hi-ui/core' +import { useMergeRefs } from '@hi-ui/use-merge-refs' +import { useUncontrolledState } from '@hi-ui/use-uncontrolled-state' +import { ResizeBoxPane, ResizeBoxPaneProps } from './ResizeBoxPane' +import { Separator } from './Separator' + +const RESIZE_BOX_PREFIX = getPrefixCls('resize-box') + +export const ResizeBox = forwardRef( + ( + { prefixCls = RESIZE_BOX_PREFIX, role = 'resize-box', className, children, separator, ...rest }, + ref + ) => { + const cls = cx(prefixCls, className) + + const innerRef = React.useRef(null) + const mergedRef = useMergeRefs(ref, innerRef) + + const [colWidths, tryChangeColWidths] = useUncontrolledState([]) + const minColWidthsRef = React.useRef([]) + + /** + * 计算内容面板宽度 + * 如果有设置默认宽度,则使用默认宽度,否则使用平均宽度 + * 如果有设置最小宽度,则使用最小宽度,否则使用默认宽度的一半 + */ + const calcPaneWidth = React.useCallback(() => { + const container = innerRef.current + const containerWidth = container?.getBoundingClientRect().width ?? 0 + const minColWidths: number[] = [] + let defaultColWidths: number[] = [] + let calcWidth = 0 + let avgWidth = 0 + + React.Children.forEach(children, (child) => { + const { + props: { defaultWidth = 0, minWidth = 0 }, + } = child as React.ReactElement + + defaultColWidths.push(defaultWidth) + minColWidths?.push(minWidth) + + calcWidth += defaultWidth + }) + + if (calcWidth > containerWidth) { + console.error('default width is greater than container width') + return + } + + if (calcWidth < containerWidth) { + const noDefaultWidthLength = defaultColWidths.filter((item) => !item).length + + avgWidth = Math.floor((containerWidth - calcWidth) / noDefaultWidthLength) + + defaultColWidths = defaultColWidths.map((item) => { + if (!item) { + return avgWidth + } else { + return item + } + }) + } + + tryChangeColWidths(defaultColWidths) + + minColWidthsRef.current = minColWidths.map((item, index) => { + // 如果没有设置最小宽度,则最小宽度是默认宽度的一半 + return item || defaultColWidths[index] * 0.5 + }) + }, [children, tryChangeColWidths]) + + const panesContent = React.useMemo(() => { + if (!children) { + console.error('children is required') + return null + } + + if (!Array.isArray(children)) { + console.error('children must be array') + return children + } + + return React.Children.map( + children as React.ReactElement[], + (child, index) => { + if (!React.isValidElement(child)) { + console.error('child is not valid element') + return + } + + const { type, props } = child + const { style, onResize, ...rest } = props + + if (type !== ResizeBoxPane) { + console.error('ResizeBox children must be ResizeBoxPane') + return + } + + if (index !== children?.length - 1) { + return ( + } + height={0} + width={colWidths[index] ?? 0} + onResize={(evt, options) => { + const { width: resizedWidth } = options.size + + tryChangeColWidths((prev) => { + const nextColWidths = [...prev] + const currentWidth = nextColWidths[index] + const siblingWidth = nextColWidths[index + 1] + const minColWidth = minColWidthsRef.current[index] + const siblingMinColWidth = minColWidthsRef.current[index + 1] + const width = + resizedWidth <= minColWidth + ? // 显示最小宽度 + minColWidth + : currentWidth + siblingWidth - resizedWidth < siblingMinColWidth + ? // 能够显示的最大宽度 + currentWidth + siblingWidth - siblingMinColWidth + : // 显示拖拽后的宽度 + resizedWidth + const resizeWidth = width - currentWidth + + nextColWidths[index] = width + nextColWidths[index + 1] = siblingWidth - resizeWidth + + onResize?.(width) + + return nextColWidths + }) + }} + > + {React.cloneElement(child, { + ...rest, + style: { + ...style, + width: colWidths[index], + }, + })} + + ) + } else { + return React.cloneElement(child, { + ...rest, + style: { + ...style, + width: colWidths[index], + }, + }) + } + } + ) + }, [children, colWidths, prefixCls, separator, tryChangeColWidths]) + + React.useEffect(() => { + calcPaneWidth() + }, [calcPaneWidth]) + + return ( +
+ {panesContent} +
+ ) + } +) + +export interface ResizeBoxProps extends HiBaseHTMLProps<'div'> { + separator?: React.ReactNode +} + +if (__DEV__) { + ResizeBox.displayName = 'ResizeBox' +} diff --git a/packages/ui/resize-box/src/ResizeBoxPane.tsx b/packages/ui/resize-box/src/ResizeBoxPane.tsx new file mode 100644 index 000000000..74fad9729 --- /dev/null +++ b/packages/ui/resize-box/src/ResizeBoxPane.tsx @@ -0,0 +1,41 @@ +import React, { forwardRef } from 'react' +import { cx, getPrefixCls } from '@hi-ui/classname' +import { __DEV__ } from '@hi-ui/env' +import { HiBaseHTMLProps } from '@hi-ui/core' + +const RESIZE_BOX_PANE_PREFIX = getPrefixCls('resize-box-pane') + +export const ResizeBoxPane = forwardRef( + ( + { + prefixCls = RESIZE_BOX_PANE_PREFIX, + role = 'resize-box-pane', + className, + children, + defaultWidth, + minWidth, + onResize, + ...rest + }, + ref + ) => { + const cls = cx(prefixCls, className) + + return ( +
+ {children} +
+ ) + } +) + +export interface ResizeBoxPaneProps extends HiBaseHTMLProps<'div'> { + defaultWidth?: number + width?: number + minWidth?: number + onResize?: (width: number) => void +} + +if (__DEV__) { + ResizeBoxPane.displayName = 'ResizeBoxPane' +} diff --git a/packages/ui/resize-box/src/Separator.tsx b/packages/ui/resize-box/src/Separator.tsx new file mode 100644 index 000000000..0b35727f3 --- /dev/null +++ b/packages/ui/resize-box/src/Separator.tsx @@ -0,0 +1,28 @@ +import React, { forwardRef } from 'react' +import { cx, getPrefixCls } from '@hi-ui/classname' +import { __DEV__ } from '@hi-ui/env' +import { HiBaseHTMLProps } from '@hi-ui/core' +import { DragDotOutlined } from '@hi-ui/icons' + +const SEPARATOR_PREFIX = getPrefixCls('resize-box-separator') + +export const Separator = forwardRef( + ( + { prefixCls = SEPARATOR_PREFIX, role = 'resize-box-separator', className, children, ...rest }, + ref + ) => { + const cls = cx(prefixCls, className) + + return ( +
+ +
+ ) + } +) + +export interface SeparatorProps extends HiBaseHTMLProps<'div'> {} + +if (__DEV__) { + Separator.displayName = 'SEPARATOR' +} diff --git a/packages/ui/resize-box/src/index.ts b/packages/ui/resize-box/src/index.ts new file mode 100644 index 000000000..9c6ec5311 --- /dev/null +++ b/packages/ui/resize-box/src/index.ts @@ -0,0 +1,6 @@ +import './styles/index.scss' + +export * from './ResizeBox' +export { ResizeBox as default } from './ResizeBox' + +export * from './ResizeBoxPane' diff --git a/packages/ui/resize-box/src/styles/index.scss b/packages/ui/resize-box/src/styles/index.scss new file mode 100644 index 000000000..846141425 --- /dev/null +++ b/packages/ui/resize-box/src/styles/index.scss @@ -0,0 +1 @@ +@import './resize-box.scss'; diff --git a/packages/ui/resize-box/src/styles/resize-box.scss b/packages/ui/resize-box/src/styles/resize-box.scss new file mode 100644 index 000000000..029411fda --- /dev/null +++ b/packages/ui/resize-box/src/styles/resize-box.scss @@ -0,0 +1,29 @@ +@import '~@hi-ui/core-css/lib/index.scss'; + +$prefix: '#{$component-prefix}-resize-box' !default; + +.#{$prefix} { + display: flex; + box-sizing: border-box; + user-select: auto; + + &-pane { + display: flex; + justify-content: space-between; + overflow: hidden; + } + + &-separator { + position: relative; + width: 6px; + height: 100%; + cursor: col-resize; + flex-shrink: 0; + display: flex; + justify-content: center; + align-items: center; + font-size: use-text-size('sm'); + color: use-color('gray', 600); + background-color: use-color('gray', 300); + } +} diff --git a/packages/ui/resize-box/src/types.ts b/packages/ui/resize-box/src/types.ts new file mode 100644 index 000000000..c732c7b7e --- /dev/null +++ b/packages/ui/resize-box/src/types.ts @@ -0,0 +1,12 @@ +import React from 'react' + +export interface ResizeBoxDataItem { + /** + * 节点唯一 id + */ + id: React.ReactText + /** + * 节点标题 + */ + title: React.ReactNode +} diff --git a/packages/ui/resize-box/stories/basic.stories.tsx b/packages/ui/resize-box/stories/basic.stories.tsx new file mode 100644 index 000000000..c1a6c21e1 --- /dev/null +++ b/packages/ui/resize-box/stories/basic.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import ResizeBox, { ResizeBoxPane } from '../src' + +/** + * @title 基础用法 + */ +export const Basic = () => { + return ( + <> +

Basic

+
+ + +
left content
+
+ +
right content
+
+
+
+ + ) +} diff --git a/packages/ui/resize-box/stories/index.stories.tsx b/packages/ui/resize-box/stories/index.stories.tsx new file mode 100644 index 000000000..3a2181d58 --- /dev/null +++ b/packages/ui/resize-box/stories/index.stories.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import ResizeBox from '../src' + +export * from './basic.stories' +export * from './min-width.stories' +export * from './separator.stories' + +export default { + title: 'Others/ResizeBox', + component: ResizeBox, + decorators: [(story: Function) =>
{story()}
], +} diff --git a/packages/ui/resize-box/stories/min-width.stories.tsx b/packages/ui/resize-box/stories/min-width.stories.tsx new file mode 100644 index 000000000..e87e89815 --- /dev/null +++ b/packages/ui/resize-box/stories/min-width.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import ResizeBox, { ResizeBoxPane } from '../src' + +/** + * @title 设置 pane 最小宽度 + */ +export const MinWidth = () => { + return ( + <> +

MinWidth

+
+ + +
left content
+
+ +
right content
+
+
+
+ + ) +} diff --git a/packages/ui/resize-box/stories/separator.stories.tsx b/packages/ui/resize-box/stories/separator.stories.tsx new file mode 100644 index 000000000..3d2b67742 --- /dev/null +++ b/packages/ui/resize-box/stories/separator.stories.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import ResizeBox, { ResizeBoxPane } from '../src' + +/** + * @title 自定义分割器 + */ +export const Separator = () => { + const customSeparator = ( +
+ ) + + return ( + <> +

Separator

+
+ + +
left content
+
+ +
right content
+
+
+
+ + ) +} diff --git a/packages/ui/resize-box/tsconfig.json b/packages/ui/resize-box/tsconfig.json new file mode 100644 index 000000000..f7bbdb2fe --- /dev/null +++ b/packages/ui/resize-box/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["./src"] +} diff --git a/yarn.lock b/yarn.lock index d194e712d..55f06d8b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15601,6 +15601,14 @@ react-resizable@^3.0.4: prop-types "15.x" react-draggable "^4.0.3" +react-resizable@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-3.0.5.tgz#362721f2efbd094976f1780ae13f1ad7739786c1" + integrity sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w== + dependencies: + prop-types "15.x" + react-draggable "^4.0.3" + react-resize-detector@^6.7.6: version "6.7.6" resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-6.7.6.tgz#4416994e5ead7eba76606e3a248a1dfca49b67a3" From 1ba52b615282c84a7bfa14043e167a6601ef0838 Mon Sep 17 00:00:00 2001 From: zhouyun1 Date: Wed, 23 Aug 2023 20:06:17 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat(hiui):=20=E5=A2=9E=E5=8A=A0=20ResizeBo?= =?UTF-8?q?x=20=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/hiui/package.json | 1 + packages/ui/hiui/src/index.ts | 3 ++ packages/ui/resize-box/hi-docs.config.mdx | 11 ++++++ packages/ui/resize-box/src/ResizeBox.tsx | 33 +++++++++++++---- packages/ui/resize-box/src/ResizeBoxPane.tsx | 4 ++ packages/ui/resize-box/src/Separator.tsx | 3 +- .../ui/resize-box/src/styles/resize-box.scss | 17 +++++++-- .../ui/resize-box/stories/basic.stories.tsx | 24 +++++++++++- .../ui/resize-box/stories/index.stories.tsx | 1 - .../resize-box/stories/min-width.stories.tsx | 24 +++++++++++- .../resize-box/stories/separator.stories.tsx | 37 ------------------- 11 files changed, 102 insertions(+), 56 deletions(-) create mode 100644 packages/ui/resize-box/hi-docs.config.mdx delete mode 100644 packages/ui/resize-box/stories/separator.stories.tsx diff --git a/packages/ui/hiui/package.json b/packages/ui/hiui/package.json index 67d208977..b6f4be8ee 100644 --- a/packages/ui/hiui/package.json +++ b/packages/ui/hiui/package.json @@ -87,6 +87,7 @@ "@hi-ui/provider": "^4.0.5", "@hi-ui/radio": "^4.0.4", "@hi-ui/rating": "^4.0.5", + "@hi-ui/resize-box": "^4.0.0", "@hi-ui/result": "^4.0.4", "@hi-ui/scrollbar": "^4.0.1", "@hi-ui/search": "^4.0.8", diff --git a/packages/ui/hiui/src/index.ts b/packages/ui/hiui/src/index.ts index 9bd2077c6..9b5168556 100644 --- a/packages/ui/hiui/src/index.ts +++ b/packages/ui/hiui/src/index.ts @@ -139,6 +139,9 @@ export { default as Radio } from '@hi-ui/radio' export * from '@hi-ui/rating' export { default as Rating } from '@hi-ui/rating' +export * from '@hi-ui/resize-box' +export { default as ResizeBox } from '@hi-ui/resize-box' + export * from '@hi-ui/result' export { default as Result } from '@hi-ui/result' diff --git a/packages/ui/resize-box/hi-docs.config.mdx b/packages/ui/resize-box/hi-docs.config.mdx new file mode 100644 index 000000000..1f1c95531 --- /dev/null +++ b/packages/ui/resize-box/hi-docs.config.mdx @@ -0,0 +1,11 @@ +# ResizeBox 伸缩框 + +用于可调整大小的布局 + +## 使用示例 + + + +## Props + + diff --git a/packages/ui/resize-box/src/ResizeBox.tsx b/packages/ui/resize-box/src/ResizeBox.tsx index bcfebd101..493c43654 100644 --- a/packages/ui/resize-box/src/ResizeBox.tsx +++ b/packages/ui/resize-box/src/ResizeBox.tsx @@ -17,8 +17,8 @@ export const ResizeBox = forwardRef( ) => { const cls = cx(prefixCls, className) - const innerRef = React.useRef(null) - const mergedRef = useMergeRefs(ref, innerRef) + const containerRef = React.useRef(null) + const mergedRef = useMergeRefs(ref, containerRef) const [colWidths, tryChangeColWidths] = useUncontrolledState([]) const minColWidthsRef = React.useRef([]) @@ -29,7 +29,7 @@ export const ResizeBox = forwardRef( * 如果有设置最小宽度,则使用最小宽度,否则使用默认宽度的一半 */ const calcPaneWidth = React.useCallback(() => { - const container = innerRef.current + const container = containerRef.current const containerWidth = container?.getBoundingClientRect().width ?? 0 const minColWidths: number[] = [] let defaultColWidths: number[] = [] @@ -94,7 +94,7 @@ export const ResizeBox = forwardRef( } const { type, props } = child - const { style, onResize, ...rest } = props + const { style, onResizeStart, onResizeEnd, onResize, ...rest } = props if (type !== ResizeBoxPane) { console.error('ResizeBox children must be ResizeBoxPane') @@ -109,8 +109,13 @@ export const ResizeBox = forwardRef( handle={separator ?? } height={0} width={colWidths[index] ?? 0} - onResize={(evt, options) => { - const { width: resizedWidth } = options.size + onResizeStart={onResizeStart} + onResizeStop={onResizeEnd} + onResize={(evt, data) => { + evt.stopPropagation() + evt.preventDefault() + + const { width: resizedWidth } = data.size tryChangeColWidths((prev) => { const nextColWidths = [...prev] @@ -160,8 +165,20 @@ export const ResizeBox = forwardRef( ) }, [children, colWidths, prefixCls, separator, tryChangeColWidths]) - React.useEffect(() => { - calcPaneWidth() + React.useLayoutEffect(() => { + if (containerRef.current) { + calcPaneWidth() + + const resizeObserver = new ResizeObserver(() => { + calcPaneWidth() + }) + + resizeObserver.observe(containerRef.current) + + return () => { + resizeObserver.disconnect() + } + } }, [calcPaneWidth]) return ( diff --git a/packages/ui/resize-box/src/ResizeBoxPane.tsx b/packages/ui/resize-box/src/ResizeBoxPane.tsx index 74fad9729..1660c1d97 100644 --- a/packages/ui/resize-box/src/ResizeBoxPane.tsx +++ b/packages/ui/resize-box/src/ResizeBoxPane.tsx @@ -14,6 +14,8 @@ export const ResizeBoxPane = forwardRef { defaultWidth?: number width?: number minWidth?: number + onResizeStart?: () => void + onResizeEnd?: () => void onResize?: (width: number) => void } diff --git a/packages/ui/resize-box/src/Separator.tsx b/packages/ui/resize-box/src/Separator.tsx index 0b35727f3..117857d4f 100644 --- a/packages/ui/resize-box/src/Separator.tsx +++ b/packages/ui/resize-box/src/Separator.tsx @@ -2,7 +2,6 @@ import React, { forwardRef } from 'react' import { cx, getPrefixCls } from '@hi-ui/classname' import { __DEV__ } from '@hi-ui/env' import { HiBaseHTMLProps } from '@hi-ui/core' -import { DragDotOutlined } from '@hi-ui/icons' const SEPARATOR_PREFIX = getPrefixCls('resize-box-separator') @@ -15,7 +14,7 @@ export const Separator = forwardRef( return (
- +
) } diff --git a/packages/ui/resize-box/src/styles/resize-box.scss b/packages/ui/resize-box/src/styles/resize-box.scss index 029411fda..9200b87f3 100644 --- a/packages/ui/resize-box/src/styles/resize-box.scss +++ b/packages/ui/resize-box/src/styles/resize-box.scss @@ -15,15 +15,24 @@ $prefix: '#{$component-prefix}-resize-box' !default; &-separator { position: relative; - width: 6px; height: 100%; + padding: 0 2px; cursor: col-resize; flex-shrink: 0; display: flex; justify-content: center; align-items: center; - font-size: use-text-size('sm'); - color: use-color('gray', 600); - background-color: use-color('gray', 300); + + &-content { + width: 2px; + height: 100%; + background-color: use-color('gray', 300); + } + + &:hover { + .#{$prefix}-separator-content { + background-color: use-color('brandblue', 300); + } + } } } diff --git a/packages/ui/resize-box/stories/basic.stories.tsx b/packages/ui/resize-box/stories/basic.stories.tsx index c1a6c21e1..633ab13ba 100644 --- a/packages/ui/resize-box/stories/basic.stories.tsx +++ b/packages/ui/resize-box/stories/basic.stories.tsx @@ -11,10 +11,30 @@ export const Basic = () => {
-
left content
+
+ left content +
-
right content
+
+ right content +
diff --git a/packages/ui/resize-box/stories/index.stories.tsx b/packages/ui/resize-box/stories/index.stories.tsx index 3a2181d58..d7a14925c 100644 --- a/packages/ui/resize-box/stories/index.stories.tsx +++ b/packages/ui/resize-box/stories/index.stories.tsx @@ -3,7 +3,6 @@ import ResizeBox from '../src' export * from './basic.stories' export * from './min-width.stories' -export * from './separator.stories' export default { title: 'Others/ResizeBox', diff --git a/packages/ui/resize-box/stories/min-width.stories.tsx b/packages/ui/resize-box/stories/min-width.stories.tsx index e87e89815..db99a32f1 100644 --- a/packages/ui/resize-box/stories/min-width.stories.tsx +++ b/packages/ui/resize-box/stories/min-width.stories.tsx @@ -11,10 +11,30 @@ export const MinWidth = () => {
-
left content
+
+ left content +
-
right content
+
+ right content +
diff --git a/packages/ui/resize-box/stories/separator.stories.tsx b/packages/ui/resize-box/stories/separator.stories.tsx deleted file mode 100644 index 3d2b67742..000000000 --- a/packages/ui/resize-box/stories/separator.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import ResizeBox, { ResizeBoxPane } from '../src' - -/** - * @title 自定义分割器 - */ -export const Separator = () => { - const customSeparator = ( -
- ) - - return ( - <> -

Separator

-
- - -
left content
-
- -
right content
-
-
-
- - ) -} From ad7e8de5645072eb95a97d25ef6e443919b93e76 Mon Sep 17 00:00:00 2001 From: zhouyun1 Date: Wed, 23 Aug 2023 20:55:25 +0800 Subject: [PATCH 3/6] =?UTF-8?q?chore(resize-box):=20=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/resize-box/src/ResizeBox.tsx | 46 +++++++++++++++++------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/ui/resize-box/src/ResizeBox.tsx b/packages/ui/resize-box/src/ResizeBox.tsx index 493c43654..f3fd2fd92 100644 --- a/packages/ui/resize-box/src/ResizeBox.tsx +++ b/packages/ui/resize-box/src/ResizeBox.tsx @@ -11,10 +11,7 @@ import { Separator } from './Separator' const RESIZE_BOX_PREFIX = getPrefixCls('resize-box') export const ResizeBox = forwardRef( - ( - { prefixCls = RESIZE_BOX_PREFIX, role = 'resize-box', className, children, separator, ...rest }, - ref - ) => { + ({ prefixCls = RESIZE_BOX_PREFIX, role = 'resize-box', className, children, ...rest }, ref) => { const cls = cx(prefixCls, className) const containerRef = React.useRef(null) @@ -23,6 +20,10 @@ export const ResizeBox = forwardRef( const [colWidths, tryChangeColWidths] = useUncontrolledState([]) const minColWidthsRef = React.useRef([]) + const startXRef = React.useRef(0) + const movingXRef = React.useRef(0) + const draggableRef = React.useRef(true) + /** * 计算内容面板宽度 * 如果有设置默认宽度,则使用默认宽度,否则使用平均宽度 @@ -106,17 +107,40 @@ export const ResizeBox = forwardRef( } + handle={} height={0} width={colWidths[index] ?? 0} - onResizeStart={onResizeStart} + onResizeStart={(evt) => { + // 记录开始拖拽时的鼠标位置 + startXRef.current = (evt as React.MouseEvent).clientX + draggableRef.current = true + onResizeStart?.() + }} onResizeStop={onResizeEnd} onResize={(evt, data) => { - evt.stopPropagation() - evt.preventDefault() + const mouseEvent = evt as React.MouseEvent + + mouseEvent.stopPropagation() + mouseEvent.preventDefault() const { width: resizedWidth } = data.size + // 记录拖拽时的鼠标位置 + movingXRef.current = mouseEvent.clientX + + // 向左或向右拖动到最小宽度时禁止拖拽 + if ( + (movingXRef.current - startXRef.current < 0 && + resizedWidth < minColWidthsRef.current[index]) || + (movingXRef.current - startXRef.current > 0 && + colWidths[index] + colWidths[index + 1] - resizedWidth < + minColWidthsRef.current[index + 1]) + ) { + draggableRef.current = false + } + + if (!draggableRef.current) return + tryChangeColWidths((prev) => { const nextColWidths = [...prev] const currentWidth = nextColWidths[index] @@ -163,7 +187,7 @@ export const ResizeBox = forwardRef( } } ) - }, [children, colWidths, prefixCls, separator, tryChangeColWidths]) + }, [children, colWidths, prefixCls, tryChangeColWidths]) React.useLayoutEffect(() => { if (containerRef.current) { @@ -189,9 +213,7 @@ export const ResizeBox = forwardRef( } ) -export interface ResizeBoxProps extends HiBaseHTMLProps<'div'> { - separator?: React.ReactNode -} +export interface ResizeBoxProps extends HiBaseHTMLProps<'div'> {} if (__DEV__) { ResizeBox.displayName = 'ResizeBox' From cae6e3123f0cdd64f8178dad846fdf6b6ed2402f Mon Sep 17 00:00:00 2001 From: zhouyun1 Date: Thu, 24 Aug 2023 11:31:11 +0800 Subject: [PATCH 4/6] =?UTF-8?q?chore(resize-box):=20=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=8F=98=E6=9B=B4=E8=AE=B0=E5=BD=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset-pending/README.md | 1 + .changeset-pending/eleven-actors-beg.md | 5 +++ .changeset-pending/sixty-elephants-join.md | 6 ++++ packages/ui/resize-box/src/ResizeBox.tsx | 39 ++++++++++++---------- 4 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 .changeset-pending/README.md create mode 100644 .changeset-pending/eleven-actors-beg.md create mode 100644 .changeset-pending/sixty-elephants-join.md diff --git a/.changeset-pending/README.md b/.changeset-pending/README.md new file mode 100644 index 000000000..a0665e70f --- /dev/null +++ b/.changeset-pending/README.md @@ -0,0 +1 @@ +暂时不发版的变更记录文件放在此目录 diff --git a/.changeset-pending/eleven-actors-beg.md b/.changeset-pending/eleven-actors-beg.md new file mode 100644 index 000000000..0f2ea8ab9 --- /dev/null +++ b/.changeset-pending/eleven-actors-beg.md @@ -0,0 +1,5 @@ +--- +"@hi-ui/icons": patch +--- + +增加 DragDot 图标 diff --git a/.changeset-pending/sixty-elephants-join.md b/.changeset-pending/sixty-elephants-join.md new file mode 100644 index 000000000..cb29c087f --- /dev/null +++ b/.changeset-pending/sixty-elephants-join.md @@ -0,0 +1,6 @@ +--- +"@hi-ui/hiui": minor +"@hi-ui/resize-box": minor +--- + +feat: 增加 ResizeBox 组件 diff --git a/packages/ui/resize-box/src/ResizeBox.tsx b/packages/ui/resize-box/src/ResizeBox.tsx index f3fd2fd92..ab61296a0 100644 --- a/packages/ui/resize-box/src/ResizeBox.tsx +++ b/packages/ui/resize-box/src/ResizeBox.tsx @@ -6,12 +6,22 @@ import { HiBaseHTMLProps } from '@hi-ui/core' import { useMergeRefs } from '@hi-ui/use-merge-refs' import { useUncontrolledState } from '@hi-ui/use-uncontrolled-state' import { ResizeBoxPane, ResizeBoxPaneProps } from './ResizeBoxPane' -import { Separator } from './Separator' +import { Separator, SeparatorProps } from './Separator' const RESIZE_BOX_PREFIX = getPrefixCls('resize-box') export const ResizeBox = forwardRef( - ({ prefixCls = RESIZE_BOX_PREFIX, role = 'resize-box', className, children, ...rest }, ref) => { + ( + { + prefixCls = RESIZE_BOX_PREFIX, + role = 'resize-box', + className, + children, + separatorProps, + ...rest + }, + ref + ) => { const cls = cx(prefixCls, className) const containerRef = React.useRef(null) @@ -20,8 +30,6 @@ export const ResizeBox = forwardRef( const [colWidths, tryChangeColWidths] = useUncontrolledState([]) const minColWidthsRef = React.useRef([]) - const startXRef = React.useRef(0) - const movingXRef = React.useRef(0) const draggableRef = React.useRef(true) /** @@ -107,12 +115,10 @@ export const ResizeBox = forwardRef( } + handle={} height={0} width={colWidths[index] ?? 0} - onResizeStart={(evt) => { - // 记录开始拖拽时的鼠标位置 - startXRef.current = (evt as React.MouseEvent).clientX + onResizeStart={() => { draggableRef.current = true onResizeStart?.() }} @@ -125,16 +131,11 @@ export const ResizeBox = forwardRef( const { width: resizedWidth } = data.size - // 记录拖拽时的鼠标位置 - movingXRef.current = mouseEvent.clientX - // 向左或向右拖动到最小宽度时禁止拖拽 if ( - (movingXRef.current - startXRef.current < 0 && - resizedWidth < minColWidthsRef.current[index]) || - (movingXRef.current - startXRef.current > 0 && - colWidths[index] + colWidths[index + 1] - resizedWidth < - minColWidthsRef.current[index + 1]) + resizedWidth < minColWidthsRef.current[index] || + colWidths[index] + colWidths[index + 1] - resizedWidth < + minColWidthsRef.current[index + 1] ) { draggableRef.current = false } @@ -187,7 +188,7 @@ export const ResizeBox = forwardRef( } } ) - }, [children, colWidths, prefixCls, tryChangeColWidths]) + }, [children, colWidths, prefixCls, separatorProps, tryChangeColWidths]) React.useLayoutEffect(() => { if (containerRef.current) { @@ -213,7 +214,9 @@ export const ResizeBox = forwardRef( } ) -export interface ResizeBoxProps extends HiBaseHTMLProps<'div'> {} +export interface ResizeBoxProps extends HiBaseHTMLProps<'div'> { + separatorProps?: SeparatorProps +} if (__DEV__) { ResizeBox.displayName = 'ResizeBox' From 937052db36ecfa50fef53df13d159bee0d08fa41 Mon Sep 17 00:00:00 2001 From: zhouyun1 Date: Thu, 24 Aug 2023 11:34:30 +0800 Subject: [PATCH 5/6] =?UTF-8?q?chore(icons):=20=E8=B0=83=E6=95=B4=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E8=AE=B0=E5=BD=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {.changeset-pending => .changeset}/eleven-actors-beg.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {.changeset-pending => .changeset}/eleven-actors-beg.md (100%) diff --git a/.changeset-pending/eleven-actors-beg.md b/.changeset/eleven-actors-beg.md similarity index 100% rename from .changeset-pending/eleven-actors-beg.md rename to .changeset/eleven-actors-beg.md From 5aef6a53b453bbdfbb974b4c7a347d46a9a2143a Mon Sep 17 00:00:00 2001 From: zhouyun1 Date: Thu, 24 Aug 2023 11:38:18 +0800 Subject: [PATCH 6/6] =?UTF-8?q?chore(resize-box):=20=E5=8E=BB=E6=8E=89?= =?UTF-8?q?=E5=A4=9A=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/resize-box/src/types.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 packages/ui/resize-box/src/types.ts diff --git a/packages/ui/resize-box/src/types.ts b/packages/ui/resize-box/src/types.ts deleted file mode 100644 index c732c7b7e..000000000 --- a/packages/ui/resize-box/src/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' - -export interface ResizeBoxDataItem { - /** - * 节点唯一 id - */ - id: React.ReactText - /** - * 节点标题 - */ - title: React.ReactNode -}