diff --git a/components/splitter/SplitBar.tsx b/components/splitter/SplitBar.tsx index 27549e4c7c0b..f9e2c8e08989 100644 --- a/components/splitter/SplitBar.tsx +++ b/components/splitter/SplitBar.tsx @@ -6,6 +6,8 @@ import UpOutlined from '@ant-design/icons/UpOutlined'; import useEvent from '@rc-component/util/lib/hooks/useEvent'; import classNames from 'classnames'; +import type { SplitterProps } from './interface'; + export interface SplitBarProps { index: number; active: boolean; @@ -13,6 +15,8 @@ export interface SplitBarProps { resizable: boolean; startCollapsible: boolean; endCollapsible: boolean; + draggerIcon?: SplitterProps['draggerIcon']; + collapsibleIcon?: SplitterProps['collapsibleIcon']; onOffsetStart: (index: number) => void; onOffsetUpdate: (index: number, offsetX: number, offsetY: number) => void; onOffsetEnd: VoidFunction; @@ -39,6 +43,8 @@ const SplitBar: React.FC = (props) => { ariaMin, ariaMax, resizable, + draggerIcon, + collapsibleIcon, startCollapsible, endCollapsible, onOffsetStart, @@ -160,8 +166,22 @@ const SplitBar: React.FC = (props) => { }; // ======================== Render ======================== - const StartIcon = vertical ? UpOutlined : LeftOutlined; - const EndIcon = vertical ? DownOutlined : RightOutlined; + const [startIcon, endIcon, startCustomize, endCustomize] = React.useMemo(() => { + let startIcon = null; + let endIcon = null; + const startCustomize = collapsibleIcon?.start !== undefined; + const endCustomize = collapsibleIcon?.end !== undefined; + + if (vertical) { + startIcon = startCustomize ? collapsibleIcon.start : ; + endIcon = endCustomize ? collapsibleIcon.end : ; + } else { + startIcon = startCustomize ? collapsibleIcon.start : ; + endIcon = endCustomize ? collapsibleIcon.end : ; + } + + return [startIcon, endIcon, startCustomize, endCustomize]; + }, [collapsibleIcon, vertical]); return (
= (props) => { className={classNames(`${splitBarPrefixCls}-dragger`, { [`${splitBarPrefixCls}-dragger-disabled`]: !resizable, [`${splitBarPrefixCls}-dragger-active`]: active, + [`${splitBarPrefixCls}-dragger-customize`]: !!draggerIcon, })} onMouseDown={onMouseDown} onTouchStart={onTouchStart} - /> + > + {draggerIcon ? ( +
{draggerIcon}
+ ) : null} +
{/* Start Collapsible */} {startCollapsible && ( @@ -195,15 +220,20 @@ const SplitBar: React.FC = (props) => { className={classNames( `${splitBarPrefixCls}-collapse-bar`, `${splitBarPrefixCls}-collapse-bar-start`, + { + [`${splitBarPrefixCls}-collapse-bar-customize`]: startCustomize, + }, )} onClick={() => onCollapse(index, 'start')} > - + > + {startIcon} + )} @@ -213,15 +243,20 @@ const SplitBar: React.FC = (props) => { className={classNames( `${splitBarPrefixCls}-collapse-bar`, `${splitBarPrefixCls}-collapse-bar-end`, + { + [`${splitBarPrefixCls}-collapse-bar-customize`]: endCustomize, + }, )} onClick={() => onCollapse(index, 'end')} > - + > + {endIcon} + )} diff --git a/components/splitter/Splitter.tsx b/components/splitter/Splitter.tsx index 8653de140688..bc999de226ba 100644 --- a/components/splitter/Splitter.tsx +++ b/components/splitter/Splitter.tsx @@ -24,6 +24,8 @@ const Splitter: React.FC> = (props) => { style, layout = 'horizontal', children, + draggerIcon, + collapsibleIcon, rootClassName, onResizeStart, onResize, @@ -177,6 +179,8 @@ const Splitter: React.FC> = (props) => { prefixCls={prefixCls} vertical={isVertical} resizable={resizableInfo.resizable} + draggerIcon={draggerIcon} + collapsibleIcon={collapsibleIcon} ariaNow={stackSizes[idx] * 100} ariaMin={Math.max(ariaMinStart, ariaMinEnd) * 100} ariaMax={Math.min(ariaMaxStart, ariaMaxEnd) * 100} diff --git a/components/splitter/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/splitter/__tests__/__snapshots__/demo-extend.test.ts.snap index 6e1bae7f4505..6d3e288f8767 100644 --- a/components/splitter/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/splitter/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -198,6 +198,208 @@ exports[`renders components/splitter/demo/control.tsx extend context correctly 1 exports[`renders components/splitter/demo/control.tsx extend context correctly 2`] = `[]`; +exports[`renders components/splitter/demo/customize.tsx extend context correctly 1`] = ` +Array [ +
+
+
+
+ Panel 1 +
+
+
+ +
+
+
+ Panel 2 +
+
+
+ +
+
+
+ Panel 3 +
+
+
+
, +
+
+
+
+ First +
+
+
+ +
+
+
+ Second +
+
+
+
, +] +`; + +exports[`renders components/splitter/demo/customize.tsx extend context correctly 2`] = `[]`; + exports[`renders components/splitter/demo/debug.tsx extend context correctly 1`] = `
`; +exports[`renders components/splitter/demo/customize.tsx correctly 1`] = ` +Array [ +
+
+
+
+ Panel 1 +
+
+
+ +
+
+
+ Panel 2 +
+
+
+ +
+
+
+ Panel 3 +
+
+
+
, +
+
+
+
+ First +
+
+
+ +
+
+
+ Second +
+
+
+
, +] +`; + exports[`renders components/splitter/demo/debug.tsx correctly 1`] = `
{ containerSize = 100; errSpy.mockReset(); resetWarned(); + jest.useFakeTimers(); }); afterEach(() => { @@ -595,4 +597,43 @@ describe('Splitter', () => { fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-start')!); expect(onResize).toHaveBeenCalledWith([0, 200]); }); + + // ============================= customize ============================= + describe('customize', () => { + it('customize draggerIcon', () => { + const { container } = render( + } />, + ); + const draggerEle = container.querySelector('.ant-splitter-bar-dragger')!; + + expect(draggerEle).toHaveClass('ant-splitter-bar-dragger-customize'); + expect(draggerEle.querySelector('.ant-splitter-bar-dragger-icon')).toBeTruthy(); + expect(draggerEle.querySelector('.customize-dragger-icon')).toBeTruthy(); + }); + + it('customize collapsibleIcon', async () => { + const { container } = render( + , + end: , + }} + />, + ); + + await resizeSplitter(); + const startEle = container.querySelector('.ant-splitter-bar-collapse-bar-start')!; + const endEle = container.querySelector('.ant-splitter-bar-collapse-bar-end')!; + + expect(startEle).toHaveClass('ant-splitter-bar-collapse-bar-customize'); + expect(endEle).toHaveClass('ant-splitter-bar-collapse-bar-customize'); + + expect(startEle.querySelector('.customize-icon-start')).toBeTruthy(); + expect(endEle.querySelector('.customize-icon-end')).toBeTruthy(); + + expect(startEle).toHaveStyle({ background: 'transparent' }); + expect(endEle).toHaveStyle({ background: 'transparent' }); + }); + }); }); diff --git a/components/splitter/__tests__/lazy.test.tsx b/components/splitter/__tests__/lazy.test.tsx index d82fce6825e3..6b7ac8caee88 100644 --- a/components/splitter/__tests__/lazy.test.tsx +++ b/components/splitter/__tests__/lazy.test.tsx @@ -41,6 +41,7 @@ describe('Splitter lazy', () => { containerSize = 100; errSpy.mockReset(); resetWarned(); + jest.useFakeTimers(); }); afterEach(() => { diff --git a/components/splitter/demo/customize.md b/components/splitter/demo/customize.md new file mode 100644 index 000000000000..d3c48b451974 --- /dev/null +++ b/components/splitter/demo/customize.md @@ -0,0 +1,7 @@ +## zh-CN + +自定义操作元素样式 + +## en-US + +customize handle elements and style diff --git a/components/splitter/demo/customize.tsx b/components/splitter/demo/customize.tsx new file mode 100644 index 000000000000..a4620723ee6e --- /dev/null +++ b/components/splitter/demo/customize.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { + CaretDownOutlined, + CaretLeftOutlined, + CaretRightOutlined, + CaretUpOutlined, + ColumnHeightOutlined, + ColumnWidthOutlined, +} from '@ant-design/icons'; +import { ConfigProvider, Flex, Splitter, Typography } from 'antd'; +import { createStyles } from 'antd-style'; + +const useStyles = createStyles(({ token }) => ({ + draggerIcon: { + '&:hover': { + color: token.colorPrimary, + }, + }, + collapsibleIcon: { + fontSize: 16, + color: token.colorTextDescription, + + '&:hover': { + color: token.colorPrimary, + }, + }, +})); + +const Desc: React.FC> = (props) => ( + + + {props.text} + + +); + +const App: React.FC = () => { + const { styles } = useStyles(); + + return ( + + } + collapsibleIcon={{ + start: , + end: , + }} + > + + + + + + + + + + + + + + } + collapsibleIcon={{ + start: , + end: , + }} + > + + + + + + + + + + ); +}; + +export default App; diff --git a/components/splitter/index.en-US.md b/components/splitter/index.en-US.md index e116db937cbc..4c0293e191ec 100644 --- a/components/splitter/index.en-US.md +++ b/components/splitter/index.en-US.md @@ -23,8 +23,9 @@ Can be used to separate areas horizontally or vertically. When you need to freel Collapsible Multiple panels Complex combination -Nested in tabs Lazy +Customize +Nested in tabs Debug ## API @@ -42,6 +43,8 @@ Common props ref:[Common props](/docs/react/common-props) | onResize | Panel size change callback | `(sizes: number[]) => void` | - | - | | onResizeEnd | Drag end callback | `(sizes: number[]) => void` | - | - | | lazy | Lazy mode | `boolean` | `false` | 5.23.0 | +| collapsibleIcon | custom collapsible icon | `{start: ReactNode; end: ReactNode}` | - | 6.0.0 | +| draggerIcon | custom dragger icon | `ReactNode` | - | 6.0.0 | ### Panel diff --git a/components/splitter/index.zh-CN.md b/components/splitter/index.zh-CN.md index 5234aa22446f..805dccbb9c61 100644 --- a/components/splitter/index.zh-CN.md +++ b/components/splitter/index.zh-CN.md @@ -26,8 +26,9 @@ tag: 5.21.0 可折叠 多面板 复杂组合 -标签页中嵌套 延迟渲染模式 +自定义样式 +标签页中嵌套 调试 ## API @@ -38,13 +39,15 @@ tag: 5.21.0 ### Splitter -| 参数 | 说明 | 类型 | 默认值 | 版本 | -| ------------- | ---------------- | --------------------------- | ------------ | ------ | -| layout | 布局方向 | `horizontal` \| `vertical` | `horizontal` | - | -| onResizeStart | 开始拖拽之前回调 | `(sizes: number[]) => void` | - | - | -| onResize | 面板大小变化回调 | `(sizes: number[]) => void` | - | - | -| onResizeEnd | 拖拽结束回调 | `(sizes: number[]) => void` | - | - | -| lazy | 延迟渲染模式 | `boolean` | `false` | 5.23.0 | +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| layout | 布局方向 | `horizontal` \| `vertical` | `horizontal` | - | +| onResizeStart | 开始拖拽之前回调 | `(sizes: number[]) => void` | - | - | +| onResize | 面板大小变化回调 | `(sizes: number[]) => void` | - | - | +| onResizeEnd | 拖拽结束回调 | `(sizes: number[]) => void` | - | - | +| lazy | 延迟渲染模式 | `boolean` | `false` | 5.23.0 | +| collapsibleIcon | 折叠图标 | `{start?: ReactNode; end?: ReactNode}` | - | 6.0.0 | +| draggerIcon | 拖拽图标 | `ReactNode` | - | 6.0.0 | ### Panel diff --git a/components/splitter/interface.ts b/components/splitter/interface.ts index 4f5bc2ec799a..9a5aff1a1fe6 100644 --- a/components/splitter/interface.ts +++ b/components/splitter/interface.ts @@ -5,6 +5,11 @@ export interface SplitterProps { style?: React.CSSProperties; rootClassName?: string; layout?: 'horizontal' | 'vertical'; + draggerIcon?: React.ReactNode; + collapsibleIcon?: { + start?: React.ReactNode; + end?: React.ReactNode; + }; onResizeStart?: (sizes: number[]) => void; onResize?: (sizes: number[]) => void; onResizeEnd?: (sizes: number[]) => void; diff --git a/components/splitter/style/index.ts b/components/splitter/style/index.ts index 1cb7b9d07f40..a58cdbeba904 100644 --- a/components/splitter/style/index.ts +++ b/components/splitter/style/index.ts @@ -17,8 +17,8 @@ export interface ComponentToken { */ splitBarDraggableSize: number; /** - * @desc 拖拽元素大小 - * @descEN Drag the element size + * @desc 拖拽元素显示大小 + * @descEN Drag the element display size */ splitBarSize: number; /** @@ -84,6 +84,7 @@ const genSplitterStyle: GenerateStyle = (token: SplitterToken): C controlItemBgActive, controlItemBgActiveHover, prefixCls, + colorPrimary, } = token; const splitBarCls = `${componentCls}-bar`; @@ -153,6 +154,11 @@ const genSplitterStyle: GenerateStyle = (token: SplitterToken): C background: controlItemBgActiveHover, }, }, + [`&-active${splitBarCls}-dragger-customize`]: { + [`${splitBarCls}-dragger-icon`]: { + color: colorPrimary, + }, + }, // Disabled, not use `pointer-events: none` since still need trigger collapse [`&-disabled${splitBarCls}-dragger`]: { @@ -168,6 +174,24 @@ const genSplitterStyle: GenerateStyle = (token: SplitterToken): C '&::after': { display: 'none', }, + + [`${splitBarCls}-dragger-icon`]: { + display: 'none', + }, + }, + + // customize dragger icon + '&-customize': { + [`${splitBarCls}-dragger-icon`]: { + ...centerStyle, + display: 'flex', + alignItems: 'center', + color: colorFill, + }, + + '&::after': { + display: 'none', + }, }, }, @@ -186,14 +210,22 @@ const genSplitterStyle: GenerateStyle = (token: SplitterToken): C justifyContent: 'center', // Hover - '&:hover': { + [`&:hover:not(${splitBarCls}-collapse-bar-customize)`]: { background: controlItemBgActive, }, // Active - '&:active': { + [`&:active:not(${splitBarCls}-collapse-bar-customize)`]: { background: controlItemBgActiveHover, }, + + [`${splitBarCls}-collapse-icon`]: { + display: 'flex', + alignItems: 'center', + }, + }, + [`${splitBarCls}-collapse-bar-customize`]: { + background: 'transparent', }, // ======================== Status ========================