mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 01:53:33 -06:00
192 lines
4.8 KiB
TypeScript
192 lines
4.8 KiB
TypeScript
import { css, cx } from '@emotion/css';
|
|
import React, { createRef, MutableRefObject, PureComponent } from 'react';
|
|
import SplitPane, { Split } from 'react-split-pane';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { config } from 'app/core/config';
|
|
|
|
interface Props {
|
|
splitOrientation?: Split;
|
|
paneSize: number;
|
|
splitVisible?: boolean;
|
|
minSize?: number;
|
|
maxSize?: number;
|
|
primary?: 'first' | 'second';
|
|
onDragFinished?: (size?: number) => void;
|
|
parentStyle?: React.CSSProperties;
|
|
paneStyle?: React.CSSProperties;
|
|
secondaryPaneStyle?: React.CSSProperties;
|
|
}
|
|
|
|
export class SplitPaneWrapper extends PureComponent<React.PropsWithChildren<Props>> {
|
|
//requestAnimationFrame reference
|
|
rafToken: MutableRefObject<number | null> = createRef();
|
|
|
|
componentDidMount() {
|
|
window.addEventListener('resize', this.updateSplitPaneSize);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
window.removeEventListener('resize', this.updateSplitPaneSize);
|
|
}
|
|
|
|
updateSplitPaneSize = () => {
|
|
if (this.rafToken.current !== undefined) {
|
|
window.cancelAnimationFrame(this.rafToken.current!);
|
|
}
|
|
this.rafToken.current = window.requestAnimationFrame(() => {
|
|
this.forceUpdate();
|
|
});
|
|
};
|
|
|
|
onDragFinished = (size?: number) => {
|
|
document.body.style.cursor = 'auto';
|
|
|
|
if (this.props.onDragFinished && size !== undefined) {
|
|
this.props.onDragFinished(size);
|
|
}
|
|
};
|
|
|
|
onDragStarted = () => {
|
|
document.body.style.cursor = this.props.splitOrientation === 'horizontal' ? 'row-resize' : 'col-resize';
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
children,
|
|
paneSize,
|
|
splitOrientation,
|
|
maxSize,
|
|
minSize,
|
|
primary,
|
|
parentStyle,
|
|
paneStyle,
|
|
secondaryPaneStyle,
|
|
splitVisible = true,
|
|
} = this.props;
|
|
|
|
let childrenArr = [];
|
|
if (Array.isArray(children)) {
|
|
childrenArr = children;
|
|
} else {
|
|
childrenArr.push(children);
|
|
}
|
|
|
|
// Limit options pane width to 90% of screen.
|
|
const styles = getStyles(config.theme2, splitVisible);
|
|
|
|
// Need to handle when width is relative. ie a percentage of the viewport
|
|
const paneSizePx =
|
|
paneSize <= 1
|
|
? paneSize * (splitOrientation === 'horizontal' ? window.innerHeight : window.innerWidth)
|
|
: paneSize;
|
|
|
|
// the react split pane library always wants 2 children. This logic ensures that happens, even if one child is passed in
|
|
const childrenFragments = [
|
|
<React.Fragment key="leftPane">{childrenArr[0]}</React.Fragment>,
|
|
<React.Fragment key="rightPane">{childrenArr[1] || undefined}</React.Fragment>,
|
|
];
|
|
|
|
return (
|
|
<SplitPane
|
|
className={styles.splitPane}
|
|
split={splitOrientation}
|
|
minSize={minSize}
|
|
maxSize={maxSize}
|
|
size={splitVisible ? paneSizePx : 0}
|
|
primary={splitVisible ? primary : 'second'}
|
|
resizerClassName={splitOrientation === 'horizontal' ? styles.resizerH : styles.resizerV}
|
|
onDragStarted={() => this.onDragStarted()}
|
|
onDragFinished={(size) => this.onDragFinished(size)}
|
|
style={parentStyle}
|
|
paneStyle={paneStyle}
|
|
pane2Style={secondaryPaneStyle}
|
|
>
|
|
{childrenFragments}
|
|
</SplitPane>
|
|
);
|
|
}
|
|
}
|
|
|
|
const getStyles = (theme: GrafanaTheme2, hasSplit: boolean) => {
|
|
const handleColor = theme.v1.palette.blue95;
|
|
const paneSpacing = theme.spacing(2);
|
|
|
|
const resizer = css`
|
|
position: relative;
|
|
display: ${hasSplit ? 'block' : 'none'};
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
transition: 0.2s border-color ease-in-out;
|
|
}
|
|
|
|
&::after {
|
|
background: ${theme.components.panel.borderColor};
|
|
content: '';
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
transition: 0.2s background ease-in-out;
|
|
transform: translate(-50%, -50%);
|
|
border-radius: ${theme.shape.radius.default};
|
|
}
|
|
|
|
&:hover {
|
|
&::before {
|
|
border-color: ${handleColor};
|
|
}
|
|
|
|
&::after {
|
|
background: ${handleColor};
|
|
}
|
|
}
|
|
`;
|
|
|
|
return {
|
|
splitPane: css({
|
|
overflow: 'visible !important',
|
|
}),
|
|
resizerV: cx(
|
|
resizer,
|
|
css`
|
|
cursor: col-resize;
|
|
width: ${paneSpacing};
|
|
|
|
&::before {
|
|
border-right: 1px solid transparent;
|
|
height: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
}
|
|
|
|
&::after {
|
|
height: 200px;
|
|
width: 4px;
|
|
}
|
|
`
|
|
),
|
|
resizerH: cx(
|
|
resizer,
|
|
css`
|
|
height: ${paneSpacing};
|
|
cursor: row-resize;
|
|
margin-left: ${paneSpacing};
|
|
|
|
&::before {
|
|
border-top: 1px solid transparent;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 100%;
|
|
}
|
|
|
|
&::after {
|
|
height: 4px;
|
|
width: 200px;
|
|
}
|
|
`
|
|
),
|
|
};
|
|
};
|