buster/web/src/components/layout/AppSplitter.tsx

196 lines
5.2 KiB
TypeScript
Raw Normal View History

import { useAntToken } from '@/styles/useAntToken';
import { useMemoizedFn } from 'ahooks';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import SplitPane, { Pane } from '@/components/layout/SplitPane';
import { createAutoSaveId } from './helper';
import Cookies from 'js-cookie';
2025-01-10 03:04:57 +08:00
import { createStyles } from 'antd-style';
export const AppSplitter: React.FC<{
leftChildren: React.ReactNode;
rightChildren: React.ReactNode;
autoSaveId: string;
defaultLayout: (string | number)[];
leftPanelMinSize?: number | string;
rightPanelMinSize?: number | string;
leftPanelMaxSize?: number | string;
rightPanelMaxSize?: number | string;
className?: string;
allowResize?: boolean;
split?: 'vertical' | 'horizontal';
splitterClassName?: string;
preserveSide: 'left' | 'right' | null;
rightHidden?: boolean;
leftHidden?: boolean;
style?: React.CSSProperties;
hideSplitter?: boolean;
}> = ({
style,
leftChildren,
preserveSide,
rightChildren,
autoSaveId,
defaultLayout,
leftPanelMinSize,
rightPanelMinSize,
2025-01-10 03:04:57 +08:00
split = 'vertical',
leftPanelMaxSize,
rightPanelMaxSize,
allowResize,
className,
splitterClassName,
leftHidden,
rightHidden,
hideSplitter
}) => {
const [isDragging, setIsDragging] = useState(false);
const [sizes, setSizes] = useState<(number | string)[]>(defaultLayout);
const ref = React.useRef<HTMLDivElement>(null);
const hasHidden = useMemo(() => leftHidden || rightHidden, [leftHidden, rightHidden]);
const _allowResize = useMemo(() => (hasHidden ? false : allowResize), [hasHidden, allowResize]);
const _sizes = useMemo(
() => (hasHidden ? (leftHidden ? ['0px', 'auto'] : ['auto', '0px']) : sizes),
[hasHidden, leftHidden, sizes]
);
const memoizedLeftPaneStyle = useMemo(() => {
return {
display: leftHidden ? 'none' : undefined
};
}, [leftHidden]);
const memoizedRightPaneStyle = useMemo(() => {
return {
display: rightHidden ? 'none' : undefined
};
}, [rightHidden]);
const sashRender = useMemoizedFn((_: number, active: boolean) => (
<AppSplitterSash
hideSplitter={hideSplitter}
active={active}
splitterClassName={splitterClassName}
2025-01-10 03:04:57 +08:00
splitDirection={split}
/>
));
const onDragEnd = useMemoizedFn(() => {
setIsDragging(false);
});
const onDragStart = useMemoizedFn(() => {
setIsDragging(true);
});
const onChangePanels = useMemoizedFn((sizes: number[]) => {
if (!isDragging) return;
setSizes(sizes);
const key = createAutoSaveId(autoSaveId);
const sizesString = preserveSide === 'left' ? [sizes[0], 'auto'] : ['auto', sizes[1]];
Cookies.set(key, JSON.stringify(sizesString), { expires: 365 });
});
const onPreserveSide = useMemoizedFn(() => {
const [left, right] = sizes;
if (preserveSide === 'left') {
setSizes([left, 'auto']);
} else if (preserveSide === 'right') {
setSizes(['auto', right]);
}
});
useEffect(() => {
2025-01-10 03:04:57 +08:00
if (preserveSide && !hideSplitter && split === 'vertical') {
window.addEventListener('resize', onPreserveSide);
return () => {
window.removeEventListener('resize', onPreserveSide);
};
}
}, [preserveSide]);
return (
<div ref={ref} className="h-full w-full">
<SplitPane
split={split}
className={`${className}`}
sizes={_sizes}
style={style}
allowResize={_allowResize}
onChange={onChangePanels}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
resizerSize={3}
sashRender={sashRender}>
<Pane
style={memoizedLeftPaneStyle}
className="flex h-full flex-col"
minSize={leftPanelMinSize}
maxSize={leftPanelMaxSize}>
{leftHidden ? null : leftChildren}
</Pane>
<Pane
className="flex h-full flex-col"
style={memoizedRightPaneStyle}
minSize={rightPanelMinSize}
maxSize={rightPanelMaxSize}>
{rightHidden ? null : rightChildren}
</Pane>
</SplitPane>
</div>
);
};
AppSplitter.displayName = 'AppSplitter';
const AppSplitterSash: React.FC<{
active: boolean;
splitterClassName?: string;
hideSplitter?: boolean;
2025-01-10 03:04:57 +08:00
splitDirection?: 'vertical' | 'horizontal';
}> = React.memo(
({ active, splitterClassName = '', hideSplitter = false, splitDirection = 'vertical' }) => {
const { styles, cx } = useStyles();
2025-01-10 03:04:57 +08:00
return (
<div
className={cx(
splitterClassName,
styles.splitter,
'absolute transition',
`cursor-${splitDirection}-resize`,
splitDirection === 'vertical' ? 'h-full w-[0.5px]' : 'h-[0.5px] w-full',
hideSplitter && 'hide',
active && 'active',
!active && 'inactive'
)}
/>
);
}
);
AppSplitterSash.displayName = 'AppSplitterSash';
2025-01-10 03:04:57 +08:00
const useStyles = createStyles(({ css, token }) => ({
splitter: css`
background: ${token.colorPrimary};
left: 1px;
&.hide {
background: transparent;
&.active {
background: ${token.colorBorder};
}
}
&:not(.hide) {
&.active {
background: ${token.colorPrimary};
}
&.inactive {
background: ${token.colorBorder};
}
}
`
}));