feat:🔥New Component: Splitter (#50038)

* feat: SplitPanel init

* feat: SplitPanel update

* feat: SplitPanel update

* feat: splitPanel update useResize

* feat: SplitPanel update

* feat: splitPanel update useResize

* feat: SplitPanel update

* feat: splitPanel demo

* feat: splitPanel update

* feat: splitPanel support complicated combination

* feat: SplitPanel rename to Splitter

* feat: Splitter support onRize

* feat: support collapsible

* feat: support token and collapsible

* feat: update docs

* feat: size defaultSize support string

* feat: min max support string

* feat: update

* feat: support DOM structure

* feat: Optimize UI

* feat: Optimize Code

* fix: Add a default size during initialization to prevent failure to obtain container size

* feat: optimized code

* feat: optimized code

* feat: Optimize Code

* Update components/splitter/demo/layout.tsx

Co-authored-by: lijianan <574980606@qq.com>
Signed-off-by: Wanpan <wanpan96@163.com>

* Update components/splitter/demo/multiple.tsx

Co-authored-by: lijianan <574980606@qq.com>
Signed-off-by: Wanpan <wanpan96@163.com>

* docs: update

* feat: Modify the style and optimize the interface

* feat: use PropsWithChildren

* feat: support rtl

* feat: collapsible supports object types

* fix: when collapsible is boolean not work

* feat: Splitter add test

* feat: update

* test: update snapshots

* docs: update

* test: update snapshots

* test: update

* test: update

* test: update

* test: update

* fix: Removed invalid min and max restrictions when collapsible exists

* test: update

* test: update

* test: update

* test: test coverage

* Revert "test: test coverage"

This reverts commit d247193722.

* test: test coverage

* feat: rename

* feat: optimized code

* ci: lint

* feat: optimized code

* feat: add useag tips

* feat: optimized code

* feat: Modify splitbar layout

* feat: optimized code

* feat: numerical precision

* feat: optimized code

* feat: Optimized trigger region

* feat: Support configuration animation

* fix: Fix Collapsible exception when using multiple panels

* fix: Fixed the issue of drag area overlapping when multiple panels are folded

* feat: optimized code

* feat: annotation

* feAt: optimized code

* fix: bgcolor

* fix: Modify the initial value calculation method

* test: update

* feat: add cover image

* chore: adjust logic

* chore: use items size

* chore: rtl

* chore: limit

* chore: controlled

* docs: update demo

* docs: adjust style

* chore: add split style

* chore: hor collapisble style

* chore: collapse icon

* chore: update warning

* chore: clean up

* chore: collapse logic

* chore: adjust demo

* chore: clean up

* test: adjust logic

* docs: update demo

* docs: rm useless demo

* docs: demo

* test: add demo test

* test: test of them

* test: 100% coverage

* chore: fix lint

* docs: update demo

* refactor: unique resize config

* docs: add demo

* fix: support virtual resiable

* chore: add cursor mask

* test: update snapshot

* test: add test case

* test: update snapshot

* chore: use px base

* chore: rm useless code

---------

Signed-off-by: Wanpan <wanpan96@163.com>
Co-authored-by: lijianan <574980606@qq.com>
Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: ice <49827327+coding-ice@users.noreply.github.com>
This commit is contained in:
Wanpan 2024-09-09 19:23:25 +08:00 committed by GitHub
parent 33533ff22c
commit 742ce37887
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 3971 additions and 0 deletions

View File

@ -54,6 +54,7 @@ exports[`antd exports modules correctly 1`] = `
"Slider",
"Space",
"Spin",
"Splitter",
"Statistic",
"Steps",
"Switch",

View File

@ -170,3 +170,5 @@ export type { UploadFile, UploadProps } from './upload';
export { default as version } from './version';
export { default as Watermark } from './watermark';
export type { WatermarkProps } from './watermark';
export { default as Splitter } from './splitter';
export type { SplitterProps } from './splitter';

View File

@ -0,0 +1,32 @@
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import type { InternalPanelProps, PanelProps } from './interface';
export const InternalPanel = forwardRef<
HTMLDivElement,
React.PropsWithChildren<InternalPanelProps>
>((props, ref) => {
const { prefixCls, className, children, size, style = {} } = props;
const panelClassName = classNames(
`${prefixCls}-panel`,
{
[`${prefixCls}-panel-hidden`]: !size,
},
className,
);
return (
<div ref={ref} className={panelClassName} style={{ ...style, flexBasis: size }}>
{children}
</div>
);
});
if (process.env.NODE_ENV !== 'production') {
InternalPanel.displayName = 'Panel';
}
const Panel: React.FC<React.PropsWithChildren<PanelProps>> = () => null;
export default Panel;

View File

@ -0,0 +1,139 @@
import React, { useState } from 'react';
import DownOutlined from '@ant-design/icons/DownOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import UpOutlined from '@ant-design/icons/UpOutlined';
import classNames from 'classnames';
export interface SplitBarProps {
index: number;
active: boolean;
prefixCls: string;
resizable: boolean;
startCollapsible: boolean;
endCollapsible: boolean;
onOffsetStart: (index: number) => void;
onOffsetUpdate: (index: number, offsetX: number, offsetY: number) => void;
onOffsetEnd: VoidFunction;
onCollapse: (index: number, type: 'start' | 'end') => void;
vertical: boolean;
ariaNow: number;
ariaMin: number;
ariaMax: number;
}
const SplitBar: React.FC<SplitBarProps> = (props) => {
const {
prefixCls,
vertical,
index,
active,
ariaNow,
ariaMin,
ariaMax,
resizable,
startCollapsible,
endCollapsible,
onOffsetStart,
onOffsetUpdate,
onOffsetEnd,
onCollapse,
} = props;
const splitBarPrefixCls = `${prefixCls}-bar`;
// ======================== Resize ========================
const [startPos, setStartPos] = useState<[x: number, y: number] | null>(null);
const onMouseDown: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (resizable && e.currentTarget) {
setStartPos([e.pageX, e.pageY]);
onOffsetStart(index);
}
};
React.useEffect(() => {
if (startPos) {
const onMouseMove = (e: MouseEvent) => {
const { pageX, pageY } = e;
const offsetX = pageX - startPos[0];
const offsetY = pageY - startPos[1];
onOffsetUpdate(index, offsetX, offsetY);
};
const onMouseUp = () => {
setStartPos(null);
onOffsetEnd();
};
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
return () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
}
}, [startPos]);
// ======================== Render ========================
const StartIcon = vertical ? UpOutlined : LeftOutlined;
const EndIcon = vertical ? DownOutlined : RightOutlined;
return (
<div
className={splitBarPrefixCls}
role="separator"
aria-valuenow={Math.round(ariaNow)}
aria-valuemin={Math.round(ariaMin)}
aria-valuemax={Math.round(ariaMax)}
>
<div
className={classNames(`${splitBarPrefixCls}-dragger`, {
[`${splitBarPrefixCls}-dragger-disabled`]: !resizable,
[`${splitBarPrefixCls}-dragger-active`]: active,
})}
onMouseDown={onMouseDown}
/>
{/* Start Collapsible */}
{startCollapsible && (
<div
className={classNames(
`${splitBarPrefixCls}-collapse-bar`,
`${splitBarPrefixCls}-collapse-bar-start`,
)}
>
<StartIcon
className={classNames(
`${splitBarPrefixCls}-collapse-icon`,
`${splitBarPrefixCls}-collapse-start`,
)}
onClick={() => onCollapse(index, 'start')}
/>
</div>
)}
{/* End Collapsible */}
{endCollapsible && (
<div
className={classNames(
`${splitBarPrefixCls}-collapse-bar`,
`${splitBarPrefixCls}-collapse-bar-end`,
)}
>
<EndIcon
className={classNames(
`${splitBarPrefixCls}-collapse-icon`,
`${splitBarPrefixCls}-collapse-end`,
)}
onClick={() => onCollapse(index, 'end')}
/>
</div>
)}
</div>
);
};
export default SplitBar;

View File

@ -0,0 +1,219 @@
/* eslint-disable react/no-array-index-key */
import React, { useState } from 'react';
import classNames from 'classnames';
import ResizeObserver from 'rc-resize-observer';
import { useEvent } from 'rc-util';
import type { GetProp } from '../_util/type';
import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useItems from './hooks/useItems';
import useResizable from './hooks/useResizable';
import useResize from './hooks/useResize';
import useSizes from './hooks/useSizes';
import type { SplitterProps } from './interface';
import { InternalPanel } from './Panel';
import SplitBar from './SplitBar';
import useStyle from './style';
const Splitter: React.FC<React.PropsWithChildren<SplitterProps>> = (props) => {
const {
prefixCls: customizePrefixCls,
className,
style,
layout,
children,
rootClassName,
onResizeStart,
onResize,
onResizeEnd,
} = props;
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('splitter', customizePrefixCls);
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
// ======================== Direct ========================
const isVertical = layout === 'vertical';
const isRTL = direction === 'rtl';
const reverse = !isVertical && isRTL;
// ====================== Items Data ======================
const items = useItems(children);
// >>> Warning for uncontrolled
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Splitter');
let existSize = false;
let existUndefinedSize = false;
items.forEach((item) => {
if (item.size !== undefined) {
existSize = true;
} else {
existUndefinedSize = true;
}
});
if (existSize && existUndefinedSize && !onResize) {
warning(
false,
'usage',
'When part of `Splitter.Panel` has `size`, `onResize` is required or change `size` to `defaultSize`.',
);
}
}
// ====================== Container =======================
const [containerSize, setContainerSize] = useState<number>(100);
const onContainerResize: GetProp<typeof ResizeObserver, 'onResize'> = (size) => {
setContainerSize(isVertical ? size.offsetHeight : size.offsetWidth);
};
// ========================= Size =========================
const [itemPxSizes, itemPtgSizes, itemPtgMinSizes, itemPtgMaxSizes, updateSizes] = useSizes(
items,
containerSize,
);
// ====================== Resizable =======================
const resizableInfos = useResizable(items, itemPxSizes);
const [onOffsetStart, onOffsetUpdate, onOffsetEnd, onCollapse, movingIndex] = useResize(
items,
resizableInfos,
itemPtgSizes,
containerSize,
updateSizes,
);
// ======================== Events ========================
const onInternalResizeStart = useEvent((index: number) => {
onOffsetStart(index);
onResizeStart?.(itemPxSizes);
});
const onInternalResizeUpdate = useEvent((index: number, offset: number) => {
const nextSizes = onOffsetUpdate(index, offset);
onResize?.(nextSizes);
});
const onInternalResizeEnd = useEvent(() => {
onOffsetEnd();
onResizeEnd?.(itemPxSizes);
});
const onInternalCollapse = useEvent((index: number, type: 'start' | 'end') => {
const nextSizes = onCollapse(index, type);
onResize?.(nextSizes);
onResizeEnd?.(nextSizes);
});
// ======================== Styles ========================
const containerClassName = classNames(
prefixCls,
className,
{
[`${prefixCls}-horizontal`]: !isVertical,
[`${prefixCls}-vertical`]: isVertical,
[`${prefixCls}-rtl`]: isRTL,
},
rootClassName,
cssVarCls,
rootCls,
hashId,
);
// ======================== Render ========================
const maskCls = `${prefixCls}-mask`;
const stackSizes = React.useMemo(() => {
const mergedSizes = [];
let stack = 0;
for (let i = 0; i < items.length; i += 1) {
stack += itemPtgSizes[i];
mergedSizes.push(stack);
}
return mergedSizes;
}, [itemPtgSizes]);
return wrapCSSVar(
<>
<ResizeObserver onResize={onContainerResize}>
<div style={style} className={containerClassName}>
{items.map((item, idx) => {
// Panel
const panel = <InternalPanel {...item} prefixCls={prefixCls} size={itemPxSizes[idx]} />;
// Split Bar
let splitBar: React.ReactElement | null = null;
const resizableInfo = resizableInfos[idx];
if (resizableInfo) {
const ariaMinStart = (stackSizes[idx - 1] || 0) + itemPtgMinSizes[idx];
const ariaMinEnd = (stackSizes[idx + 1] || 100) - itemPtgMaxSizes[idx + 1];
const ariaMaxStart = (stackSizes[idx - 1] || 0) + itemPtgMaxSizes[idx];
const ariaMaxEnd = (stackSizes[idx + 1] || 100) - itemPtgMinSizes[idx + 1];
splitBar = (
<SplitBar
index={idx}
active={movingIndex === idx}
prefixCls={prefixCls}
vertical={isVertical}
resizable={resizableInfo.resizable}
ariaNow={stackSizes[idx] * 100}
ariaMin={Math.max(ariaMinStart, ariaMinEnd) * 100}
ariaMax={Math.min(ariaMaxStart, ariaMaxEnd) * 100}
startCollapsible={resizableInfo.startCollapsible}
endCollapsible={resizableInfo.endCollapsible}
onOffsetStart={onInternalResizeStart}
onOffsetUpdate={(index, offsetX, offsetY) => {
let offset = isVertical ? offsetY : offsetX;
if (reverse) {
offset = -offset;
}
onInternalResizeUpdate(index, offset);
}}
onOffsetEnd={onInternalResizeEnd}
onCollapse={onInternalCollapse}
/>
);
}
return (
<React.Fragment key={`split-panel-${idx}`}>
{panel}
{splitBar}
</React.Fragment>
);
})}
</div>
</ResizeObserver>
{/* Fake mask for cursor */}
{typeof movingIndex === 'number' && (
<div
aria-hidden
className={classNames(maskCls, {
[`${maskCls}-horizontal`]: !isVertical,
[`${maskCls}-vertical`]: isVertical,
})}
/>
)}
</>,
);
};
if (process.env.NODE_ENV !== 'production') {
Splitter.displayName = 'Splitter';
}
export default Splitter;

View File

@ -0,0 +1,913 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/splitter/demo/collapsible.tsx extend context correctly 1`] = `
<div
class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<div
class="ant-splitter ant-splitter-horizontal"
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); height: 200px;"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
first
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="20"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="left"
class="anticon anticon-left ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end"
>
<span
aria-label="right"
class="anticon anticon-right ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
second
</h5>
</div>
</div>
</div>
<div
class="ant-splitter ant-splitter-vertical"
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); height: 300px;"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
first
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="20"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="up"
class="anticon anticon-up ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</div>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end"
>
<span
aria-label="down"
class="anticon anticon-down ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
second
</h5>
</div>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/collapsible.tsx extend context correctly 2`] = `[]`;
exports[`renders components/splitter/demo/control.tsx extend context correctly 1`] = `
<div
class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
First
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Second
</h5>
</div>
</div>
</div>
<div
class="ant-flex ant-flex-justify-space-between ant-flex-gap-middle"
>
<button
aria-checked="true"
class="ant-switch ant-switch-checked"
role="switch"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
Enabled
</span>
<span
class="ant-switch-inner-unchecked"
>
Disabled
</span>
</span>
</button>
<button
class="ant-btn ant-btn-default ant-btn-outlined"
type="button"
>
<span>
Reset
</span>
</button>
</div>
</div>
`;
exports[`renders components/splitter/demo/control.tsx extend context correctly 2`] = `[]`;
exports[`renders components/splitter/demo/debug.tsx extend context correctly 1`] = `
<div
class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<h3
class="ant-typography"
>
[true, 0, false]
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 1
</h5>
</div>
</div>
<div
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel ant-splitter-panel-hidden"
style="flex-basis: 0px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 2
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="50"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger ant-splitter-bar-dragger-disabled"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 3
</h5>
</div>
</div>
</div>
<h3
class="ant-typography"
>
[false, 0, true]
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 1
</h5>
</div>
</div>
<div
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger ant-splitter-bar-dragger-disabled"
/>
</div>
<div
class="ant-splitter-panel ant-splitter-panel-hidden"
style="flex-basis: 0px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 2
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="50"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 3
</h5>
</div>
</div>
</div>
<h3
class="ant-typography"
>
Start have min & max
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 1
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="50"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 2
</h5>
</div>
</div>
</div>
<h3
class="ant-typography"
>
End have min & max
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 1
</h5>
</div>
</div>
<div
aria-valuemax="80"
aria-valuemin="30"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 2
</h5>
</div>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/debug.tsx extend context correctly 2`] = `[]`;
exports[`renders components/splitter/demo/group.tsx extend context correctly 1`] = `
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 300px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Left
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="left"
class="anticon anticon-left ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-splitter ant-splitter-vertical"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Top
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Bottom
</h5>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/group.tsx extend context correctly 2`] = `[]`;
exports[`renders components/splitter/demo/multiple.tsx extend context correctly 1`] = `
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 33.33333333333333px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 1
</h5>
</div>
</div>
<div
aria-valuemax="67"
aria-valuemin="0"
aria-valuenow="33"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="left"
class="anticon anticon-left ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end"
>
<span
aria-label="right"
class="anticon anticon-right ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 33.33333333333333px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 2
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="33"
aria-valuenow="67"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 33.33333333333333px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space: nowrap;"
>
Panel 3
</h5>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/multiple.tsx extend context correctly 2`] = `[]`;
exports[`renders components/splitter/demo/size.tsx extend context correctly 1`] = `
<div
class="ant-splitter ant-splitter-horizontal"
style="height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 40px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
>
First
</h5>
</div>
</div>
<div
aria-valuemax="70"
aria-valuemin="20"
aria-valuenow="40"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 60px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
>
Second
</h5>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/size.tsx extend context correctly 2`] = `[]`;
exports[`renders components/splitter/demo/vertical.tsx extend context correctly 1`] = `
<div
class="ant-splitter ant-splitter-vertical"
style="height: 300px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"
>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
>
First
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis: 50px;"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height: 100%;"
>
<h5
class="ant-typography ant-typography-secondary"
>
Second
</h5>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/vertical.tsx extend context correctly 2`] = `[]`;

View File

@ -0,0 +1,925 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/splitter/demo/collapsible.tsx correctly 1`] = `
<div
class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<div
class="ant-splitter ant-splitter-horizontal"
style="box-shadow:0 0 10px rgba(0, 0, 0, 0.1);height:200px"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
first
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="20"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="left"
class="anticon anticon-left ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end"
>
<span
aria-label="right"
class="anticon anticon-right ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
second
</h5>
</div>
</div>
</div>
<div
class="ant-splitter ant-splitter-vertical"
style="box-shadow:0 0 10px rgba(0, 0, 0, 0.1);height:300px"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
first
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="20"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="up"
class="anticon anticon-up ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</div>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end"
>
<span
aria-label="down"
class="anticon anticon-down ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
second
</h5>
</div>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/control.tsx correctly 1`] = `
<div
class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<div
class="ant-splitter ant-splitter-horizontal"
style="height:200px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
First
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Second
</h5>
</div>
</div>
</div>
<div
class="ant-flex ant-flex-justify-space-between ant-flex-gap-middle"
>
<button
aria-checked="true"
class="ant-switch ant-switch-checked"
role="switch"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
Enabled
</span>
<span
class="ant-switch-inner-unchecked"
>
Disabled
</span>
</span>
</button>
<button
class="ant-btn ant-btn-default ant-btn-outlined"
type="button"
>
<span>
Reset
</span>
</button>
</div>
</div>
`;
exports[`renders components/splitter/demo/debug.tsx correctly 1`] = `
<div
class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<h3
class="ant-typography"
>
[true, 0, false]
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height:200px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
1
</h5>
</div>
</div>
<div
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel ant-splitter-panel-hidden"
style="flex-basis:0"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
2
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="50"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger ant-splitter-bar-dragger-disabled"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
3
</h5>
</div>
</div>
</div>
<h3
class="ant-typography"
>
[false, 0, true]
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height:200px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
1
</h5>
</div>
</div>
<div
aria-valuemax="50"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger ant-splitter-bar-dragger-disabled"
/>
</div>
<div
class="ant-splitter-panel ant-splitter-panel-hidden"
style="flex-basis:0"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
2
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="50"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
3
</h5>
</div>
</div>
</div>
<h3
class="ant-typography"
>
Start have min & max
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height:200px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
1
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="50"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
2
</h5>
</div>
</div>
</div>
<h3
class="ant-typography"
>
End have min & max
</h3>
<div
class="ant-splitter ant-splitter-horizontal"
style="height:200px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
1
</h5>
</div>
</div>
<div
aria-valuemax="80"
aria-valuemin="30"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
2
</h5>
</div>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/group.tsx correctly 1`] = `
<div
class="ant-splitter ant-splitter-horizontal"
style="height:300px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Left
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="left"
class="anticon anticon-left ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-splitter ant-splitter-vertical"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Top
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Bottom
</h5>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/multiple.tsx correctly 1`] = `
<div
class="ant-splitter ant-splitter-horizontal"
style="height:200px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:33.33333333333333px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
1
</h5>
</div>
</div>
<div
aria-valuemax="67"
aria-valuemin="0"
aria-valuenow="33"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-start"
>
<span
aria-label="left"
class="anticon anticon-left ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-start"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</div>
<div
class="ant-splitter-bar-collapse-bar ant-splitter-bar-collapse-bar-end"
>
<span
aria-label="right"
class="anticon anticon-right ant-splitter-bar-collapse-icon ant-splitter-bar-collapse-end"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:33.33333333333333px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
2
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="33"
aria-valuenow="67"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:33.33333333333333px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
style="white-space:nowrap"
>
Panel
<!-- -->
3
</h5>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/size.tsx correctly 1`] = `
<div
class="ant-splitter ant-splitter-horizontal"
style="height:200px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:40px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
>
First
</h5>
</div>
</div>
<div
aria-valuemax="70"
aria-valuemin="20"
aria-valuenow="40"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:60px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
>
Second
</h5>
</div>
</div>
</div>
`;
exports[`renders components/splitter/demo/vertical.tsx correctly 1`] = `
<div
class="ant-splitter ant-splitter-vertical"
style="height:300px;box-shadow:0 0 10px rgba(0, 0, 0, 0.1)"
>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
>
First
</h5>
</div>
</div>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="ant-splitter-bar"
role="separator"
>
<div
class="ant-splitter-bar-dragger"
/>
</div>
<div
class="ant-splitter-panel"
style="flex-basis:50px"
>
<div
class="ant-flex ant-flex-align-center ant-flex-justify-center"
style="height:100%"
>
<h5
class="ant-typography ant-typography-secondary"
>
Second
</h5>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,3 @@
import { extendTest } from '../../../tests/shared/demoTest';
extendTest('splitter');

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('splitter');

View File

@ -0,0 +1,5 @@
import { imageDemoTest } from '../../../tests/shared/imageTest';
describe('Splitter image', () => {
imageDemoTest('splitter');
});

View File

@ -0,0 +1,452 @@
import React from 'react';
import type { GetProps, SplitterProps } from 'antd';
import { ConfigProvider, Splitter } from 'antd';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import { resetWarned } from '../../_util/warning';
import {
act,
createEvent,
fireEvent,
render,
triggerResize,
waitFakeTimer,
} from '../../../tests/utils';
type PanelProps = GetProps<typeof Splitter.Panel>;
const SplitterDemo = ({ items = [{}, {}], ...props }: { items?: PanelProps[] } & SplitterProps) => (
<Splitter {...props}>
{items?.map((item, idx) => {
const key = `panel-${idx}`;
return <Splitter.Panel key={key} {...item} />;
})}
</Splitter>
);
describe('Splitter', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
let containerSize = 100;
beforeAll(() => {
spyElementPrototypes(HTMLElement, {
offsetWidth: {
get: () => containerSize,
},
offsetHeight: {
get: () => containerSize,
},
});
});
beforeEach(() => {
containerSize = 100;
errSpy.mockReset();
resetWarned();
});
afterEach(() => {
jest.clearAllTimers();
jest.useRealTimers();
});
it('should correct render', () => {
const { container } = render(<SplitterDemo />);
expect(container.querySelector('.ant-splitter')).toBeTruthy();
expect(container.querySelectorAll('.ant-splitter-panel')).toHaveLength(2);
expect(container.querySelector('.ant-splitter-bar')).toBeTruthy();
});
it('should correct render panel size', async () => {
const { container } = render(<SplitterDemo items={[{ size: 20 }, { size: '45%' }, {}]} />);
const panels = container.querySelectorAll('.ant-splitter-panel');
expect(panels?.[0]).toHaveStyle('flex-basis: 20px');
expect(panels?.[1]).toHaveStyle('flex-basis: 45px');
expect(panels?.[2]).toHaveStyle('flex-basis: 35px');
});
it('The layout should work fine', () => {
const { container, rerender } = render(<SplitterDemo />);
expect(container.querySelector('.ant-splitter-horizontal')).toBeTruthy();
rerender(<SplitterDemo items={[{}, {}, {}]} layout="vertical" />);
expect(container.querySelector('.ant-splitter-vertical')).toBeTruthy();
});
it('The resizable should work fine', () => {
const { container, rerender } = render(
<SplitterDemo items={[{ size: 20 }, { resizable: false }, {}]} />,
);
expect(container.querySelectorAll('.ant-splitter-bar-dragger')).toHaveLength(2);
expect(container.querySelectorAll('.ant-splitter-bar-dragger-disabled')).toHaveLength(2);
rerender(<SplitterDemo items={[{ size: 20 }, {}, { resizable: false }]} />);
expect(container.querySelectorAll('.ant-splitter-bar-dragger')).toHaveLength(2);
expect(container.querySelectorAll('.ant-splitter-bar-dragger-disabled')).toHaveLength(1);
});
it('Splitter.Panel is syntactic sugar', () => {
const { container } = render(<Splitter.Panel />);
expect(container.innerHTML).toEqual('');
});
// ============================== Resizable ==============================
describe('drag', () => {
function mockDrag(draggerEle: HTMLElement, offset: number) {
// Down
const downEvent = createEvent.mouseDown(draggerEle);
(downEvent as any).pageX = 0;
(downEvent as any).pageY = 0;
fireEvent(draggerEle, downEvent);
// Move
const moveEvent = createEvent.mouseMove(draggerEle);
(moveEvent as any).pageX = offset;
(moveEvent as any).pageY = offset;
fireEvent(draggerEle, moveEvent);
// Up
fireEvent.mouseUp(draggerEle);
}
it('The mousemove should work fine', async () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo items={[{}, {}]} onResize={onResize} onResizeEnd={onResizeEnd} />,
);
// Right
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, 40);
expect(onResize).toHaveBeenCalledWith([90, 10]);
expect(onResizeEnd).toHaveBeenCalledWith([90, 10]);
// Left
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, -200);
expect(onResize).toHaveBeenCalledWith([0, 100]);
expect(onResizeEnd).toHaveBeenCalledWith([0, 100]);
});
it('with min', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo items={[{ min: 10 }, {}]} onResize={onResize} onResizeEnd={onResizeEnd} />,
);
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, -100);
expect(onResize).toHaveBeenCalledWith([10, 90]);
expect(onResizeEnd).toHaveBeenCalledWith([10, 90]);
});
it('with max', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo items={[{ max: 90 }, {}]} onResize={onResize} onResizeEnd={onResizeEnd} />,
);
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, 100);
expect(onResize).toHaveBeenCalledWith([90, 10]);
expect(onResizeEnd).toHaveBeenCalledWith([90, 10]);
});
it('both panel has min and max', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[
{ min: 10, max: 80 },
{ min: 10, max: 80 },
]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, -100);
expect(onResize).toHaveBeenCalledWith([20, 80]);
expect(onResizeEnd).toHaveBeenCalledWith([20, 80]);
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, 100);
expect(onResize).toHaveBeenCalledWith([80, 20]);
expect(onResizeEnd).toHaveBeenCalledWith([80, 20]);
});
it('rtl', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<ConfigProvider direction="rtl">
<SplitterDemo items={[{}, {}]} onResize={onResize} onResizeEnd={onResizeEnd} />
</ConfigProvider>,
);
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, -40);
expect(onResize).toHaveBeenCalledWith([90, 10]);
expect(onResizeEnd).toHaveBeenCalledWith([90, 10]);
});
it('[true, 0, true] can be move left', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[{}, { defaultSize: 0 }, {}]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
mockDrag(container.querySelectorAll<HTMLDivElement>('.ant-splitter-bar-dragger')[1], -100);
expect(onResize).toHaveBeenCalledWith([0, 50, 50]);
expect(onResizeEnd).toHaveBeenCalledWith([0, 50, 50]);
});
it('[false, 0, true] can not be move left', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[{ resizable: false }, { defaultSize: 0 }, {}]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
mockDrag(container.querySelectorAll<HTMLDivElement>('.ant-splitter-bar-dragger')[1], -100);
expect(onResize).toHaveBeenCalledWith([50, 0, 50]);
expect(onResizeEnd).toHaveBeenCalledWith([50, 0, 50]);
});
});
// ============================= Collapsible =============================
describe('collapsible', () => {
it('Basic', () => {
const { container, rerender } = render(
<SplitterDemo items={[{ size: 20, collapsible: true }, { collapsible: true }]} />,
);
expect(container.querySelectorAll('.ant-splitter-bar-collapse-icon')).toHaveLength(2);
expect(container.querySelector('.ant-splitter-bar-collapse-start')).toBeTruthy();
expect(container.querySelector('.ant-splitter-bar-collapse-end')).toBeTruthy();
// support collapsible is object
rerender(
<SplitterDemo
items={[
{
size: 20,
collapsible: true,
},
{
collapsible: true,
},
{},
]}
/>,
);
expect(container.querySelectorAll('.ant-splitter-bar-collapse-start')).toHaveLength(2);
expect(container.querySelectorAll('.ant-splitter-bar-collapse-end')).toHaveLength(1);
});
it('collapsible - true', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[
{
size: 20,
collapsible: true,
},
{},
]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-start')!);
expect(onResize).toHaveBeenCalledWith([0, 100]);
expect(onResizeEnd).toHaveBeenCalledWith([0, 100]);
});
it('collapsible - start:true', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[
{},
{
size: 20,
collapsible: {
start: true,
},
},
{},
]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
expect(container.querySelector('.ant-splitter-bar-collapse-start')).toBeFalsy();
expect(container.querySelector('.ant-splitter-bar-collapse-end')).toBeTruthy();
fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-end')!);
expect(onResize).toHaveBeenCalledWith([60, 0, 40]);
expect(onResizeEnd).toHaveBeenCalledWith([60, 0, 40]);
});
it('collapsible - end:true', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[
{},
{
size: 20,
collapsible: {
end: true,
},
},
{},
]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
expect(container.querySelector('.ant-splitter-bar-collapse-start')).toBeTruthy();
expect(container.querySelector('.ant-splitter-bar-collapse-end')).toBeFalsy();
fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-start')!);
expect(onResize).toHaveBeenCalledWith([40, 0, 60]);
expect(onResizeEnd).toHaveBeenCalledWith([40, 0, 60]);
});
it('both collapsible', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[
{
collapsible: true,
},
{
collapsible: true,
},
]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
function expectClick(element: HTMLElement, size: number[]) {
onResize.mockReset();
onResizeEnd.mockReset();
fireEvent.click(element);
expect(onResize).toHaveBeenCalledWith(size);
expect(onResizeEnd).toHaveBeenCalledWith(size);
}
expectClick(container.querySelector('.ant-splitter-bar-collapse-start')!, [0, 100]);
expectClick(container.querySelector('.ant-splitter-bar-collapse-end')!, [50, 50]);
expectClick(container.querySelector('.ant-splitter-bar-collapse-end')!, [100, 0]);
expectClick(container.querySelector('.ant-splitter-bar-collapse-start')!, [50, 50]);
});
it('collapsible with min', () => {
const onResize = jest.fn();
const onResizeEnd = jest.fn();
const { container } = render(
<SplitterDemo
items={[
{
defaultSize: 20,
collapsible: true,
min: 10,
},
{
collapsible: true,
min: '80%',
},
]}
onResize={onResize}
onResizeEnd={onResizeEnd}
/>,
);
// Collapse left
fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-start')!);
expect(onResize).toHaveBeenCalledWith([0, 100]);
expect(onResizeEnd).toHaveBeenCalledWith([0, 100]);
expect(container.querySelector('.ant-splitter-bar-dragger-disabled')).toBeTruthy();
// Collapse back
onResize.mockReset();
onResizeEnd.mockReset();
fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-end')!);
expect(onResize).toHaveBeenCalledWith([5, 95]);
expect(onResizeEnd).toHaveBeenCalledWith([5, 95]);
expect(container.querySelector('.ant-splitter-bar-dragger-disabled')).toBeFalsy();
// Collapse right
onResize.mockReset();
onResizeEnd.mockReset();
fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-end')!);
expect(onResize).toHaveBeenCalledWith([100, 0]);
expect(onResizeEnd).toHaveBeenCalledWith([100, 0]);
expect(container.querySelector('.ant-splitter-bar-dragger-disabled')).toBeTruthy();
});
});
it('auto resize', async () => {
containerSize = 200;
const onResize = jest.fn();
const { container } = render(
<SplitterDemo
items={[
{
collapsible: true,
},
{},
]}
onResize={onResize}
/>,
);
triggerResize(container.querySelector('.ant-splitter')!);
await act(async () => {
await waitFakeTimer();
});
fireEvent.click(container.querySelector('.ant-splitter-bar-collapse-start')!);
expect(onResize).toHaveBeenCalledWith([0, 200]);
});
});

View File

@ -0,0 +1,7 @@
## zh-CN
配置 `collapsible` 提供快捷收缩能力。可以通过 `min` 限制收缩后不能通过拖拽展开。
## en-US
Set `collapsible` to enable collapse. Can through `min` to limit dragging to expand when collapsed.

View File

@ -0,0 +1,47 @@
import React from 'react';
import { Flex, Splitter, Typography } from 'antd';
import type { SplitterProps } from 'antd';
const renderDesc = (text: string) => (
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title
type="secondary"
level={5}
style={{
whiteSpace: 'nowrap',
}}
>
{text}
</Typography.Title>
</Flex>
);
const renderSplitter = ({ style, ...restProps }: SplitterProps) => (
<Splitter
style={{
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
...style,
}}
{...restProps}
>
<Splitter.Panel collapsible min="20%">
{renderDesc('first')}
</Splitter.Panel>
<Splitter.Panel collapsible>{renderDesc('second')}</Splitter.Panel>
</Splitter>
);
const App: React.FC = () => (
<Flex gap="middle" vertical>
{renderSplitter({
style: { height: 200 },
})}
{renderSplitter({
style: { height: 300 },
layout: 'vertical',
})}
</Flex>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
受控调整尺寸。当 Panel 之间任意一方禁用 `resizable`,则其拖拽将被禁用。
## en-US
Control the size of the splitter. When one of the panels disables `resizable`, dragging will be disabled.

View File

@ -0,0 +1,62 @@
import React from 'react';
import { Button, Flex, Splitter, Switch, Typography } from 'antd';
const renderDesc = (text: string) => (
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title
type="secondary"
level={5}
style={{
whiteSpace: 'nowrap',
}}
>
{text}
</Typography.Title>
</Flex>
);
const App: React.FC = () => {
const [sizes, setSizes] = React.useState<(number | string)[]>(['50%', '50%']);
const [enabled, setEnabled] = React.useState(true);
return (
<Flex vertical gap="middle">
<Splitter
style={{
height: 200,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
onResize={(nextSizes) => {
setSizes(nextSizes);
}}
>
<Splitter.Panel size={sizes[0]} resizable={enabled}>
{renderDesc('First')}
</Splitter.Panel>
<Splitter.Panel size={sizes[1]}>{renderDesc('Second')}</Splitter.Panel>
</Splitter>
<Flex gap="middle" justify="space-between">
<Switch
value={enabled}
onChange={() => {
setEnabled(!enabled);
}}
checkedChildren="Enabled"
unCheckedChildren="Disabled"
/>
<Button
onClick={() => {
setSizes(['50%', '50%']);
}}
>
Reset
</Button>
</Flex>
</Flex>
);
};
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
面板 2 宽度为 0面板 3 禁止调整大小。
## en-US
Panel 2 width is 0, panel 3 is not resizable.

View File

@ -0,0 +1,78 @@
import React from 'react';
import { Flex, Splitter, Typography } from 'antd';
const renderDesc = (id: number) => (
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title
type="secondary"
level={5}
style={{
whiteSpace: 'nowrap',
}}
>
Panel {id}
</Typography.Title>
</Flex>
);
const App: React.FC = () => (
<Flex vertical gap="middle">
<Typography.Title level={3}>[true, 0, false]</Typography.Title>
<Splitter
style={{
height: 200,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel>{renderDesc(1)}</Splitter.Panel>
<Splitter.Panel defaultSize={0}>{renderDesc(2)}</Splitter.Panel>
<Splitter.Panel resizable={false}>{renderDesc(3)}</Splitter.Panel>
</Splitter>
<Typography.Title level={3}>[false, 0, true]</Typography.Title>
<Splitter
style={{
height: 200,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel resizable={false}>{renderDesc(1)}</Splitter.Panel>
<Splitter.Panel defaultSize={0}>{renderDesc(2)}</Splitter.Panel>
<Splitter.Panel>{renderDesc(3)}</Splitter.Panel>
</Splitter>
<Typography.Title level={3}>Start have min & max</Typography.Title>
<Splitter
style={{
height: 200,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel min={50} max={100}>
{renderDesc(1)}
</Splitter.Panel>
<Splitter.Panel>{renderDesc(2)}</Splitter.Panel>
</Splitter>
<Typography.Title level={3}>End have min & max</Typography.Title>
<Splitter
style={{
height: 200,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel>{renderDesc(1)}</Splitter.Panel>
<Splitter.Panel min="20%" max="70%">
{renderDesc(2)}
</Splitter.Panel>
</Splitter>
</Flex>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
复杂组合面板,快捷折叠,禁止改变大小
## en-US
Complex combination panel, quick folding, prohibited from changing size

View File

@ -0,0 +1,35 @@
import React from 'react';
import { Flex, Splitter, Typography } from 'antd';
const renderDesc = (text: string) => (
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title
type="secondary"
level={5}
style={{
whiteSpace: 'nowrap',
}}
>
{text}
</Typography.Title>
</Flex>
);
const App: React.FC = () => (
<Splitter
style={{
height: 300,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel collapsible>{renderDesc('Left')}</Splitter.Panel>
<Splitter.Panel>
<Splitter layout="vertical">
<Splitter.Panel>{renderDesc('Top')}</Splitter.Panel>
<Splitter.Panel>{renderDesc('Bottom')}</Splitter.Panel>
</Splitter>
</Splitter.Panel>
</Splitter>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
多面板
## en-US
Multiple panels.

View File

@ -0,0 +1,39 @@
import React from 'react';
import { Flex, Splitter, Typography } from 'antd';
const renderDesc = (id: number) => (
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title
type="secondary"
level={5}
style={{
whiteSpace: 'nowrap',
}}
>
Panel {id}
</Typography.Title>
</Flex>
);
const App: React.FC = () => (
<Splitter
style={{
height: 200,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel collapsible>{renderDesc(1)}</Splitter.Panel>
<Splitter.Panel
collapsible={{
start: true,
}}
>
{renderDesc(2)}
</Splitter.Panel>
<Splitter.Panel>{renderDesc(3)}</Splitter.Panel>
</Splitter>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
初始化面板大小,面板大小限制。
## en-US
Initialize panel size, panel size limit.

View File

@ -0,0 +1,27 @@
import React from 'react';
import { Flex, Splitter, Typography } from 'antd';
const renderDesc = (text: string) => (
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title type="secondary" level={5}>
{text}
</Typography.Title>
</Flex>
);
const App: React.FC = () => (
<Splitter
style={{
height: 200,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel defaultSize="40%" min="20%" max="70%">
{renderDesc('First')}
</Splitter.Panel>
<Splitter.Panel>{renderDesc('Second')}</Splitter.Panel>
</Splitter>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
使用垂直布局。
## en-US
Use vertical layout.

View File

@ -0,0 +1,26 @@
import React from 'react';
import { Flex, Splitter, Typography } from 'antd';
const renderDesc = (text: string) => (
<Flex justify="center" align="center" style={{ height: '100%' }}>
<Typography.Title type="secondary" level={5}>
{text}
</Typography.Title>
</Flex>
);
const App: React.FC = () => (
<Splitter
layout="vertical"
style={{
height: 300,
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
}}
>
<Splitter.Panel>{renderDesc('First')}</Splitter.Panel>
<Splitter.Panel>{renderDesc('Second')}</Splitter.Panel>
</Splitter>
);
export default App;

View File

@ -0,0 +1,45 @@
import * as React from 'react';
import toArray from 'rc-util/lib/Children/toArray';
import type { PanelProps } from '../interface';
function getCollapsible(collapsible?: PanelProps['collapsible']) {
if (collapsible && typeof collapsible === 'object') {
return collapsible;
}
const mergedCollapsible = !!collapsible;
return {
start: mergedCollapsible,
end: mergedCollapsible,
};
}
export type ItemType = Omit<PanelProps, 'collapsible'> & {
collapsible: {
start?: boolean;
end?: boolean;
};
};
/**
* Convert `children` into `items`.
*/
export default function useItems(children: React.ReactNode): ItemType[] {
const items = React.useMemo(
() =>
toArray(children)
.filter(React.isValidElement)
.map((node) => {
const { props } = node as React.ReactElement<PanelProps>;
const { collapsible, ...restProps } = props;
return {
...restProps,
collapsible: getCollapsible(collapsible),
};
}),
[children],
);
return items;
}

View File

@ -0,0 +1,62 @@
import * as React from 'react';
import type { ItemType } from './useItems';
export type ResizableInfo = {
resizable: boolean;
startCollapsible: boolean;
endCollapsible: boolean;
};
export default function useResizable(items: ItemType[], pxSizes: number[]) {
return React.useMemo(() => {
const resizeInfos: ResizableInfo[] = [];
for (let i = 0; i < items.length - 1; i += 1) {
const prevItem = items[i];
const nextItem = items[i + 1];
const prevSize = pxSizes[i];
const nextSize = pxSizes[i + 1];
const {
resizable: prevResizable = true,
min: prevMin,
collapsible: prevCollapsible,
} = prevItem;
const {
resizable: nextResizable = true,
min: nextMin,
collapsible: nextCollapsible,
} = nextItem;
const mergedResizable =
// Both need to be resizable
prevResizable &&
nextResizable &&
// Prev is not collapsed and limit min size
(prevSize !== 0 || !prevMin) &&
// Next is not collapsed and limit min size
(nextSize !== 0 || !nextMin);
const startCollapsible =
// Self is collapsible
(prevCollapsible.end && prevSize > 0) ||
// Collapsed and can be collapsed
(nextCollapsible.start && nextSize === 0 && prevSize > 0);
const endCollapsible =
// Self is collapsible
(nextCollapsible.start && nextSize > 0) ||
// Collapsed and can be collapsed
(prevCollapsible.end && prevSize === 0 && nextSize > 0);
resizeInfos[i] = {
resizable: mergedResizable,
startCollapsible: !!startCollapsible,
endCollapsible: !!endCollapsible,
};
}
return resizeInfos;
}, [pxSizes, items]);
}

View File

@ -0,0 +1,150 @@
import * as React from 'react';
import type { ItemType } from './useItems';
import type { ResizableInfo } from './useResizable';
import { getPtg } from './useSizes';
export default function useResize(
items: ItemType[],
resizableInfos: ResizableInfo[],
percentSizes: number[],
containerSize: number,
updateSizes: (sizes: number[]) => void,
) {
const limitSizes = items.map((item) => [item.min, item.max]);
const ptg2px = (ptg: number) => ptg * containerSize;
// ======================== Resize ========================
function getLimitSize(str: string | number | undefined, defaultLimit: number) {
if (typeof str === 'string') {
return ptg2px(getPtg(str));
}
return str ?? defaultLimit;
}
// Real px sizes
const [cacheSizes, setCacheSizes] = React.useState<number[]>([]);
/**
* When start drag, check the direct is `start` or `end`.
* This will handle when 2 splitter bar are in the same position.
*/
const [movingIndex, setMovingIndex] = React.useState<{
index: number;
confirmed: boolean;
} | null>(null);
const getPxSizes = () => percentSizes.map(ptg2px);
const onOffsetStart = (index: number) => {
setCacheSizes(getPxSizes());
setMovingIndex({
index,
confirmed: false,
});
};
const onOffsetUpdate = (index: number, offset: number) => {
// First time trigger move index update is not sync in the state
let confirmedIndex: number | null = null;
// We need to know what the real index is.
if ((!movingIndex || !movingIndex.confirmed) && offset !== 0) {
// Search for the real index
if (offset > 0) {
confirmedIndex = index;
setMovingIndex({
index,
confirmed: true,
});
} else {
for (let i = index; i >= 0; i -= 1) {
if (cacheSizes[i] > 0 && resizableInfos[i].resizable) {
confirmedIndex = i;
setMovingIndex({
index: i,
confirmed: true,
});
break;
}
}
}
}
const mergedIndex = confirmedIndex ?? movingIndex?.index ?? index;
const numSizes = [...cacheSizes];
const nextIndex = mergedIndex + 1;
// Get boundary
const startMinSize = getLimitSize(limitSizes[mergedIndex][0], 0);
const endMinSize = getLimitSize(limitSizes[nextIndex][0], 0);
const startMaxSize = getLimitSize(limitSizes[mergedIndex][1], containerSize);
const endMaxSize = getLimitSize(limitSizes[nextIndex][1], containerSize);
let mergedOffset = offset;
// Align with the boundary
if (numSizes[mergedIndex] + mergedOffset < startMinSize) {
mergedOffset = startMinSize - numSizes[mergedIndex];
}
if (numSizes[nextIndex] - mergedOffset < endMinSize) {
mergedOffset = numSizes[nextIndex] - endMinSize;
}
if (numSizes[mergedIndex] + mergedOffset > startMaxSize) {
mergedOffset = startMaxSize - numSizes[mergedIndex];
}
if (numSizes[nextIndex] - mergedOffset > endMaxSize) {
mergedOffset = numSizes[nextIndex] - endMaxSize;
}
// Do offset
numSizes[mergedIndex] += mergedOffset;
numSizes[nextIndex] -= mergedOffset;
updateSizes(numSizes);
return numSizes;
};
const onOffsetEnd = () => {
setMovingIndex(null);
};
// ======================= Collapse =======================
const onCollapse = (index: number, type: 'start' | 'end') => {
const currentSizes = getPxSizes();
const currentIndex = type === 'start' ? index : index + 1;
const targetIndex = type === 'start' ? index + 1 : index;
const currentSize = currentSizes[currentIndex];
const targetSize = currentSizes[targetIndex];
if (currentSize !== 0 && targetSize !== 0) {
// Collapse directly
currentSizes[currentIndex] = 0;
currentSizes[targetIndex] += currentSize;
} else {
const totalSize = currentSize + targetSize;
const currentSizeMin = getLimitSize(limitSizes[currentIndex][0], 0);
const currentSizeMax = getLimitSize(limitSizes[currentIndex][1], containerSize);
const targetSizeMin = getLimitSize(limitSizes[targetIndex][0], 0);
const targetSizeMax = getLimitSize(limitSizes[targetIndex][1], containerSize);
const limitStart = Math.max(currentSizeMin, totalSize - targetSizeMax);
const limitEnd = Math.min(currentSizeMax, totalSize - targetSizeMin);
const halfOffset = (limitEnd - limitStart) / 2;
currentSizes[currentIndex] -= halfOffset;
currentSizes[targetIndex] += halfOffset;
}
updateSizes(currentSizes);
return currentSizes;
};
return [onOffsetStart, onOffsetUpdate, onOffsetEnd, onCollapse, movingIndex?.index] as const;
}

View File

@ -0,0 +1,113 @@
import React from 'react';
import type { PanelProps } from '../interface';
export function getPtg(str: string) {
return Number(str.slice(0, -1)) / 100;
}
function isPtg(itemSize: string | number | undefined): itemSize is string {
return typeof itemSize === 'string' && itemSize.endsWith('%');
}
/**
* Save the size state.
* Align the size into flex percentage base.
*/
export default function useSizes(items: PanelProps[], containerSize: number) {
const propSizes = items.map((item) => item.size);
const itemsCount = items.length;
const ptg2px = (ptg: number) => ptg * containerSize;
// We do not need care the size state match the `items` length in `useState`.
// It will calculate later.
const [innerSizes, setInnerSizes] = React.useState<(string | number | undefined)[]>(() =>
items.map((item) => item.defaultSize),
);
const sizes = React.useMemo(() => {
const mergedSizes = [];
for (let i = 0; i < itemsCount; i += 1) {
mergedSizes[i] = propSizes[i] ?? innerSizes[i];
}
return mergedSizes;
}, [itemsCount, innerSizes, propSizes]);
// Post handle the size. Will do:
// 1. Convert all the px into percentage if not empty.
// 2. Get rest percentage for exist percentage.
// 3. Fill the rest percentage into empty item.
const postPercentSizes = React.useMemo(() => {
let ptgList: (number | undefined)[] = [];
let emptyCount = 0;
// Fill default percentage
for (let i = 0; i < itemsCount; i += 1) {
const itemSize = sizes[i];
if (isPtg(itemSize)) {
ptgList[i] = getPtg(itemSize);
} else if (itemSize || itemSize === 0) {
const num = Number(itemSize);
if (!Number.isNaN(num)) {
ptgList[i] = num / containerSize;
}
} else {
emptyCount += 1;
ptgList[i] = undefined;
}
}
const totalPtg = ptgList.reduce((acc: number, ptg) => acc + (ptg || 0), 0);
if (totalPtg > 1 || !emptyCount) {
// If total percentage is larger than 1, we will scale it down.
const scale = 1 / totalPtg;
ptgList = ptgList.map((ptg) => (ptg === undefined ? 0 : ptg * scale));
} else {
// If total percentage is smaller than 1, we will fill the rest.
const avgRest = (1 - totalPtg) / emptyCount;
ptgList = ptgList.map((ptg) => (ptg === undefined ? avgRest : ptg));
}
return ptgList as number[];
}, [sizes, containerSize]);
const postPxSizes = React.useMemo(
() => postPercentSizes.map(ptg2px),
[postPercentSizes, containerSize],
);
const postPercentMinSizes = React.useMemo(
() =>
items.map((item) => {
if (isPtg(item.min)) {
return getPtg(item.min);
}
return (item.min || 0) / containerSize;
}),
[items, containerSize],
);
const postPercentMaxSizes = React.useMemo(
() =>
items.map((item) => {
if (isPtg(item.max)) {
return getPtg(item.max);
}
return (item.max || containerSize) / containerSize;
}),
[items, containerSize],
);
return [
postPxSizes,
postPercentSizes,
postPercentMinSizes,
postPercentMaxSizes,
setInnerSizes,
] as const;
}

View File

@ -0,0 +1,55 @@
---
category: Components
group: Layout
title: Splitter
description: Split panels are used to isolate areas and display multiple contents.
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*mQmMTYq6OjYAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*7v4GTo2Ely8AAAAAAAAAAAAADrJ8AQ/original
demo:
cols: 1
tag: 5.21.0
---
## When To Use
You need to display multiple contents, and you want users to be able to adjust the size of each content freely.
- The Splitter component needs to calculate the panel size through its child elements, so its child elements only support `Splitter.Panel`
## Examples
<!-- prettier-ignore -->
<code src="./demo/size.tsx">Basic</code>
<code src="./demo/control.tsx">Control mode</code>
<code src="./demo/vertical.tsx">Vertical</code>
<code src="./demo/collapsible.tsx">Collapsible</code>
<code src="./demo/multiple.tsx">Multiple panels</code>
<code src="./demo/group.tsx">Complex combination</code>
<code src="./demo/debug.tsx" debug>Debug</code>
## API
Common props ref[Common props](/docs/react/common-props)
### Splitter
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| layout | Layout direction | `horizontal` \| `vertical` | `horizontal` | - |
| onResizeStart | Callback before dragging starts | `(sizes: number[]) => void` | - | - |
| onResize | Panel size change callback | `(sizes: number[]) => void` | - | - |
| onResizeEnd | Drag end callback | `(sizes: number[]) => void` | - | - |
### Panel
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| min | Minimum threshold support number for px or 'percent%' usage | `number \| string` | - | - |
| max | Maximum threshold support number for px or 'percent%' usage | `number \| string` | - | - |
| size | Controlled panel size support number for px or 'percent%' usage | `number \| string` | - | - |
| collapsible | Quick folding | `boolean \| { start?: boolean; end?: boolean }` | `false` | - |
| resizable | Whether to enable drag and drop | `boolean` | `true` | - |
## Design Token
<ComponentTokenTable component='Splitter'></ComponentTokenTable>

View File

@ -0,0 +1,13 @@
import Panel from './Panel';
import SplitterComp from './Splitter';
export type { SplitterProps } from './interface';
type CompoundedComponent = typeof SplitterComp & {
Panel: typeof Panel;
};
const Splitter = SplitterComp as CompoundedComponent;
Splitter.Panel = Panel;
export default Splitter;

View File

@ -0,0 +1,56 @@
---
category: Components
group: 布局
title: Splitter
subtitle: 分隔面板
description: 分割面板用于隔离区域,展示多个内容。
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*mQmMTYq6OjYAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*7v4GTo2Ely8AAAAAAAAAAAAADrJ8AQ/original
demo:
cols: 1
tag: 5.21.0
---
## 何时使用
需要展示多个内容,并且希望用户可以自由调整每个内容的大小。
- Splitter 组件需要通过子元素计算面板大小,因而其子元素仅支持 `Splitter.Panel`
## 代码演示
<!-- prettier-ignore -->
<code src="./demo/size.tsx">基本用法</code>
<code src="./demo/control.tsx">受控模式</code>
<code src="./demo/vertical.tsx">垂直方向</code>
<code src="./demo/collapsible.tsx">可折叠</code>
<code src="./demo/multiple.tsx">多面板</code>
<code src="./demo/group.tsx">复杂组合</code>
<code src="./demo/debug.tsx" debug>调试</code>
## API
通用属性参考:[通用属性](/docs/react/common-props)
### Splitter
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ------------- | ---------------- | --------------------------- | ------------ | ---- |
| layout | 布局方向 | `horizontal` \| `vertical` | `horizontal` | - |
| onResizeStart | 开始拖拽之前回调 | `(sizes: number[]) => void` | - | - |
| onResize | 面板大小变化回调 | `(sizes: number[]) => void` | - | - |
| onResizeEnd | 拖拽结束回调 | `(sizes: number[]) => void` | - | - |
### Panel
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| min | 最小阈值,支持数字 px 或者文字 '百分比%' 类型 | `number \| string` | - | - |
| max | 最大阈值,支持数字 px 或者文字 '百分比%' 类型 | `number \| string` | - | - |
| size | 受控面板大小,支持数字 px 或者文字 '百分比%' 类型 | `number \| string` | - | - |
| collapsible | 快速折叠 | `boolean \| { start?: boolean; end?: boolean }` | `false` | - |
| resizable | 是否开启拖拽伸缩 | `boolean` | `true` | - |
## 主题变量Design Token
<ComponentTokenTable component='Splitter'></ComponentTokenTable>

View File

@ -0,0 +1,72 @@
// ================ outside ================
export interface SplitterProps {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
rootClassName?: string;
layout?: 'horizontal' | 'vertical';
onResizeStart?: (sizes: number[]) => void;
onResize?: (sizes: number[]) => void;
onResizeEnd?: (sizes: number[]) => void;
}
export interface PanelProps {
className?: string;
style?: React.CSSProperties;
min?: number | string;
max?: number | string;
size?: number | string;
collapsible?:
| boolean
| {
start?: boolean;
end?: boolean;
};
resizable?: boolean;
defaultSize?: number | string;
}
// ================ inside ================
export interface InternalPanelProps extends PanelProps {
className?: string;
prefixCls?: string;
}
export interface UseResizeProps extends Pick<SplitterProps, 'onResize'> {
basicsState: number[];
items: PanelProps[];
panelsRef: React.RefObject<(HTMLDivElement | null)[]>;
reverse: boolean;
setBasicsState: React.Dispatch<React.SetStateAction<number[]>>;
}
export interface UseResize {
setSize: (data: { size: number; index: number }[]) => void;
setOffset: (offset: number, containerSize: number, index: number) => void;
}
export interface UseHandleProps
extends Pick<SplitterProps, 'layout' | 'onResizeStart' | 'onResizeEnd'> {
basicsState: number[];
containerRef?: React.RefObject<HTMLDivElement | null>;
setOffset: UseResize['setOffset'];
setResizing: React.Dispatch<React.SetStateAction<boolean>>;
}
export interface UseHandle {
onStart: (x: number, y: number, index: number) => void;
}
export interface UseCollapsibleProps {
basicsState: number[];
collapsible?: PanelProps['collapsible'];
index: number;
reverse: boolean;
setSize?: UseResize['setSize'];
}
export interface UseCollapsible {
nextIcon: boolean;
overlap: boolean;
previousIcon: boolean;
onFold: (type: 'previous' | 'next') => void;
setOldBasics: () => void;
}

View File

@ -0,0 +1,338 @@
import type { CSSObject } from '@ant-design/cssinjs';
import { resetComponent } from '../../style';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks } from '../../theme/internal';
export interface ComponentToken {
/**
* @desc
* @descEN Height of content area
*/
resizeSpinnerSize: number;
/**
* @desc
* @descEN Drag the element size
*/
splitBarSize: number;
/**
* @desc
* @descEN Drag and drop trigger area size
*/
splitTriggerSize: number;
}
interface SplitterToken extends FullToken<'Splitter'> {}
const genRtlStyle = (token: SplitterToken): CSSObject => {
const { componentCls } = token;
return {
[`&-rtl${componentCls}-horizontal`]: {
[`> ${componentCls}-bar`]: {
[`${componentCls}-bar-collapse-previous`]: {
insetInlineEnd: 0,
insetInlineStart: 'unset',
},
[`${componentCls}-bar-collapse-next`]: {
insetInlineEnd: 'unset',
insetInlineStart: 0,
},
},
},
[`&-rtl${componentCls}-vertical`]: {
[`> ${componentCls}-bar`]: {
[`${componentCls}-bar-collapse-previous`]: {
insetInlineEnd: '50%',
insetInlineStart: 'unset',
},
[`${componentCls}-bar-collapse-next`]: {
insetInlineEnd: '50%',
insetInlineStart: 'unset',
},
},
},
};
};
const centerStyle: CSSObject = {
position: 'absolute',
top: '50%',
left: {
_skip_check_: true,
value: '50%',
},
transform: 'translate(-50%, -50%)',
};
const genSplitterStyle: GenerateStyle<SplitterToken> = (token: SplitterToken): CSSObject => {
const {
componentCls,
colorFill,
resizeSpinnerSize,
splitBarSize,
splitTriggerSize,
controlItemBgHover,
controlItemBgActive,
controlItemBgActiveHover,
} = token;
const splitBarCls = `${componentCls}-bar`;
const halfTriggerSize = token.calc(splitTriggerSize).div(2).equal();
return {
[componentCls]: {
...resetComponent(token),
display: 'flex',
width: '100%',
height: '100%',
alignItems: 'stretch',
// ======================== SplitBar ========================
// Use `>` to avoid conflict with mix layout
[`> ${splitBarCls}`]: {
flex: 'none',
position: 'relative',
userSelect: 'none',
// ======================= Dragger =======================
[`${splitBarCls}-dragger`]: {
...centerStyle,
zIndex: 1,
// Hover background
'&:before': {
content: '""',
background: controlItemBgHover,
...centerStyle,
},
// Spinner
'&:after': {
content: '""',
background: colorFill,
...centerStyle,
},
// Hover
[`&:hover:not(${splitBarCls}-dragger-active)`]: {
'&:before': {
background: controlItemBgActive,
},
},
// Active
'&-active': {
zIndex: 2,
'&:before': {
background: controlItemBgActiveHover,
},
},
// Disabled, not use `pointer-events: none` since still need trigger collapse
[`&-disabled${splitBarCls}-dragger`]: {
zIndex: 0,
'&, &:hover, &-active': {
cursor: 'default',
'&:before': {
background: controlItemBgHover,
},
},
'&:after': {
display: 'none',
},
},
},
// ======================= Collapse =======================
[`${splitBarCls}-collapse-bar`]: {
...centerStyle,
zIndex: 1,
background: controlItemBgHover,
fontSize: token.fontSizeSM,
borderRadius: token.borderRadiusXS,
color: token.colorText,
cursor: 'pointer',
opacity: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
// Hover
'&:hover': {
background: controlItemBgActive,
},
// Active
'&:active': {
background: controlItemBgActiveHover,
},
},
// ======================== Status ========================
// Hover
'&:hover, &:active': {
[`${splitBarCls}-collapse-bar`]: {
opacity: 1,
},
},
},
// =========================== Mask =========================
// Util dom for handle cursor
'&-mask': {
position: 'fixed',
zIndex: token.zIndexPopupBase,
inset: 0,
'&-horizontal': {
cursor: 'col-resize',
},
'&-vertical': {
cursor: 'row-resize',
},
},
// ==========================================================
// == Layout ==
// ==========================================================
'&-horizontal': {
flexDirection: 'row',
[`> ${splitBarCls}`]: {
width: 0,
// ======================= Dragger =======================
[`${splitBarCls}-dragger`]: {
cursor: 'col-resize',
height: '100%',
width: splitTriggerSize,
'&:before': {
height: '100%',
width: splitBarSize,
},
'&:after': {
height: resizeSpinnerSize,
width: splitBarSize,
},
},
// ======================= Collapse =======================
[`${splitBarCls}-collapse-bar`]: {
width: token.fontSizeSM,
height: token.controlHeightSM,
'&-start': {
left: {
_skip_check_: true,
value: 'auto',
},
right: {
_skip_check_: true,
value: halfTriggerSize,
},
transform: 'translateY(-50%)',
},
'&-end': {
left: {
_skip_check_: true,
value: halfTriggerSize,
},
right: {
_skip_check_: true,
value: 'auto',
},
transform: 'translateY(-50%)',
},
},
},
},
'&-vertical': {
flexDirection: 'column',
[`> ${splitBarCls}`]: {
height: 0,
// ======================= Dragger =======================
[`${splitBarCls}-dragger`]: {
cursor: 'row-resize',
width: '100%',
height: splitTriggerSize,
'&:before': {
width: '100%',
height: splitBarSize,
},
'&:after': {
width: resizeSpinnerSize,
height: splitBarSize,
},
},
// ======================= Collapse =======================
[`${splitBarCls}-collapse-bar`]: {
height: token.fontSizeSM,
width: token.controlHeightSM,
'&-start': {
top: 'auto',
bottom: halfTriggerSize,
transform: 'translateX(-50%)',
},
'&-end': {
top: halfTriggerSize,
bottom: 'auto',
transform: 'translateX(-50%)',
},
},
},
},
// ========================= Panels =========================
'&-panel': {
overflow: 'auto',
padding: '0 1px',
scrollbarWidth: 'thin',
boxSizing: 'border-box',
},
'&-panel-hidden': {
padding: 0,
},
...genRtlStyle(token),
},
};
};
export const prepareComponentToken: GetDefaultToken<'Splitter'> = (token) => {
const splitBarSize = token.splitBarSize || 2;
const splitTriggerSize = token.splitTriggerSize || 6;
const resizeSpinnerSize = token.resizeSpinnerSize || 20;
return {
splitBarSize,
splitTriggerSize,
resizeSpinnerSize,
};
};
// ============================== Export ==============================
export default genStyleHooks(
'Splitter',
(token) => [genSplitterStyle(token)],
prepareComponentToken,
);

View File

@ -63,6 +63,7 @@ import type { ComponentToken as TreeSelectComponentToken } from '../../tree-sele
import type { ComponentToken as TreeComponentToken } from '../../tree/style';
import type { ComponentToken as TypographyComponentToken } from '../../typography/style';
import type { ComponentToken as UploadComponentToken } from '../../upload/style';
import type { ComponentToken as SplitterComponentToken } from '../../splitter/style';
export interface ComponentTokenMap {
Affix?: AffixComponentToken;
@ -109,6 +110,7 @@ export interface ComponentTokenMap {
Spin?: SpinComponentToken;
Statistic?: StatisticComponentToken;
Switch?: SwitchComponentToken;
Splitter?: SplitterComponentToken;
Tag?: TagComponentToken;
Tree?: TreeComponentToken;
TreeSelect?: TreeSelectComponentToken;

View File

@ -349,5 +349,8 @@
"title": "Ant Design",
"tnpm": {
"mode": "npm"
},
"overrides": {
"@umijs/mako": "0.8.8-rc.1"
}
}

View File

@ -208,6 +208,10 @@ exports[`site test Component components/spin en Page 1`] = `1`;
exports[`site test Component components/spin zh Page 1`] = `1`;
exports[`site test Component components/splitter en Page 1`] = `2`;
exports[`site test Component components/splitter zh Page 1`] = `2`;
exports[`site test Component components/statistic en Page 1`] = `2`;
exports[`site test Component components/statistic zh Page 1`] = `2`;

View File

@ -54,6 +54,7 @@ exports[`antd dist files exports modules correctly 1`] = `
"Slider",
"Space",
"Spin",
"Splitter",
"Statistic",
"Steps",
"Switch",