|
22 | 22 | * SOFTWARE.
|
23 | 23 | */
|
24 | 24 |
|
25 |
| -import * as React from "react"; |
| 25 | +import React, { CSSProperties, PropsWithChildren, useRef } from "react"; |
| 26 | + |
| 27 | +import { isNumber } from "@/util/types"; |
26 | 28 | import Splitter, { SplitDir } from "./Splitter";
|
27 |
| -import { Theme } from "@mui/material/styles"; |
28 |
| -import { WithStyles } from "@mui/styles"; |
29 |
| -import createStyles from "@mui/styles/createStyles"; |
30 |
| -import withStyles from "@mui/styles/withStyles"; |
31 |
| -import classNames from "classnames"; |
32 | 29 |
|
33 | 30 | // noinspection JSUnusedLocalSymbols
|
34 |
| -const styles = (_theme: Theme) => |
35 |
| - createStyles({ |
36 |
| - hor: { |
37 |
| - display: "flex", |
38 |
| - flexFlow: "row nowrap", |
39 |
| - flex: "auto", // same as "flex: 1 1 auto;" |
40 |
| - }, |
41 |
| - ver: { |
42 |
| - // width: "100%", |
43 |
| - height: "100%", |
44 |
| - display: "flex", |
45 |
| - flexFlow: "column nowrap", |
46 |
| - flex: "auto", // same as "flex: 1 1 auto;" |
47 |
| - }, |
48 |
| - childHor: { |
49 |
| - flex: "none", |
50 |
| - }, |
51 |
| - childVer: { |
52 |
| - flex: "none", |
53 |
| - }, |
54 |
| - }); |
| 31 | +const styles: Record<string, CSSProperties> = { |
| 32 | + hor: { |
| 33 | + display: "flex", |
| 34 | + flexFlow: "row nowrap", |
| 35 | + flex: "auto", // same as "flex: 1 1 auto;" |
| 36 | + }, |
| 37 | + ver: { |
| 38 | + // width: "100%", |
| 39 | + height: "100%", |
| 40 | + display: "flex", |
| 41 | + flexFlow: "column nowrap", |
| 42 | + flex: "auto", // same as "flex: 1 1 auto;" |
| 43 | + }, |
| 44 | + childHor: { |
| 45 | + flex: "none", |
| 46 | + }, |
| 47 | + childVer: { |
| 48 | + flex: "none", |
| 49 | + }, |
| 50 | +}; |
55 | 51 |
|
56 |
| -export interface ISplitPaneProps extends WithStyles<typeof styles> { |
| 52 | +export interface SplitPaneProps { |
57 | 53 | dir: SplitDir;
|
58 |
| - initialSize?: number; |
59 |
| - onChange?: (newSize: number, oldSize: number) => void; |
60 |
| - style?: React.CSSProperties; |
61 |
| - child1Style?: React.CSSProperties; |
62 |
| - child2Style?: React.CSSProperties; |
63 |
| - className?: string; |
64 |
| - child1ClassName?: string; |
65 |
| - child2ClassName?: string; |
66 |
| - children: [React.ReactNode, React.ReactNode]; |
67 |
| -} |
68 |
| - |
69 |
| -export interface ISplitPaneState { |
70 |
| - size: number; |
| 54 | + splitPosition: number; |
| 55 | + setSplitPosition: (splitPosition: number) => void; |
| 56 | + style?: CSSProperties; |
| 57 | + child1Style?: CSSProperties; |
| 58 | + child2Style?: CSSProperties; |
| 59 | + children: React.ReactNode[]; |
71 | 60 | }
|
72 | 61 |
|
73 | 62 | /**
|
74 | 63 | * A simple SplitPane component which must have exactly two child elements.
|
75 |
| - * |
76 |
| - * Properties: |
77 |
| - * - dir: the split direction, either "hor" or "ver" |
78 |
| - * - initialSize: the initial width ("hor") or height ("ver") of the first child's container |
79 | 64 | */
|
80 |
| -class _SplitPane extends React.PureComponent<ISplitPaneProps, ISplitPaneState> { |
81 |
| - constructor(props: ISplitPaneProps) { |
82 |
| - super(props); |
83 |
| - this.handleSplitDelta = this.handleSplitDelta.bind(this); |
84 |
| - this.state = { size: props.initialSize || 50 }; |
85 |
| - } |
| 65 | +export default function SplitPane({ |
| 66 | + dir, |
| 67 | + splitPosition, |
| 68 | + setSplitPosition, |
| 69 | + children, |
| 70 | + style, |
| 71 | + child1Style, |
| 72 | + child2Style, |
| 73 | +}: PropsWithChildren<SplitPaneProps>) { |
| 74 | + const child1Ref = useRef<HTMLDivElement | null>(null); |
86 | 75 |
|
87 |
| - private handleSplitDelta(delta: number) { |
88 |
| - this.setState((state: ISplitPaneState) => { |
89 |
| - const oldSize = state.size; |
90 |
| - const newSize = oldSize + delta; |
91 |
| - if (this.props.onChange) { |
92 |
| - this.props.onChange(newSize, oldSize); |
93 |
| - } |
94 |
| - return { size: newSize }; |
95 |
| - }); |
| 76 | + if (!children || !Array.isArray(children) || children.length !== 2) { |
| 77 | + return null; |
96 | 78 | }
|
97 | 79 |
|
98 |
| - render() { |
99 |
| - const children = this.props.children as React.ReactNode; |
100 |
| - if (!children || !Array.isArray(children)) { |
101 |
| - return children; |
102 |
| - } |
103 |
| - if (children.length === 1) { |
104 |
| - return children[0]; |
105 |
| - } |
106 |
| - if (children.length > 2) { |
107 |
| - throw new Error("SplitPane expects not more than two children"); |
108 |
| - } |
109 |
| - let className; |
110 |
| - let childClassName; |
111 |
| - let child1Style; |
112 |
| - let child2Style; |
113 |
| - if (this.props.dir === "hor") { |
114 |
| - const width1 = this.state.size; |
115 |
| - className = this.props.classes.hor; |
116 |
| - childClassName = this.props.classes.childVer; |
117 |
| - child1Style = { width: width1, ...this.props.child1Style }; |
118 |
| - child2Style = this.props.child2Style; |
119 |
| - } else { |
120 |
| - const height1 = this.state.size; |
121 |
| - className = this.props.classes.ver; |
122 |
| - childClassName = this.props.classes.childVer; |
123 |
| - child1Style = { height: height1, ...this.props.child1Style }; |
124 |
| - child2Style = this.props.child2Style; |
| 80 | + const childStyle = dir === "hor" ? styles.childHor : styles.childVer; |
| 81 | + |
| 82 | + const child1SizeStyle = |
| 83 | + dir === "hor" ? { width: splitPosition } : { height: splitPosition }; |
| 84 | + |
| 85 | + const handleSplitChange = (delta: number) => { |
| 86 | + if (child1Ref.current && isNumber(child1Ref.current.clientWidth)) { |
| 87 | + setSplitPosition(child1Ref.current.clientWidth + delta); |
125 | 88 | }
|
126 |
| - return ( |
| 89 | + }; |
| 90 | + |
| 91 | + return ( |
| 92 | + <div |
| 93 | + id="SplitPane" |
| 94 | + style={{ ...style, ...(dir === "hor" ? styles.hor : styles.ver) }} |
| 95 | + > |
127 | 96 | <div
|
128 |
| - id="SplitPane" |
129 |
| - className={classNames(className, this.props.className)} |
130 |
| - style={this.props.style} |
| 97 | + ref={child1Ref} |
| 98 | + id="SplitPane-Child-1" |
| 99 | + style={{ ...childStyle, ...child1Style, ...child1SizeStyle }} |
131 | 100 | >
|
132 |
| - <div |
133 |
| - id="SplitPane-Child-1" |
134 |
| - className={classNames(childClassName, this.props.child1ClassName)} |
135 |
| - style={child1Style} |
136 |
| - > |
137 |
| - {children[0]} |
138 |
| - </div> |
139 |
| - <Splitter dir={this.props.dir} onChange={this.handleSplitDelta} /> |
140 |
| - <div |
141 |
| - id="SplitPane-Child-2" |
142 |
| - className={classNames(childClassName, this.props.child2ClassName)} |
143 |
| - style={child2Style} |
144 |
| - > |
145 |
| - {children[1]} |
146 |
| - </div> |
| 101 | + {children[0]} |
147 | 102 | </div>
|
148 |
| - ); |
149 |
| - } |
| 103 | + <Splitter dir={dir} onChange={handleSplitChange} /> |
| 104 | + <div id="SplitPane-Child-2" style={{ ...childStyle, ...child2Style }}> |
| 105 | + {children[1]} |
| 106 | + </div> |
| 107 | + </div> |
| 108 | + ); |
150 | 109 | }
|
151 |
| - |
152 |
| -const SplitPane = withStyles(styles)(_SplitPane); |
153 |
| -export default SplitPane; |
0 commit comments