Created
November 6, 2017 07:37
-
-
Save Skitionek/46c9b7508c48f3c4710b1b3d8ba03acc to your computer and use it in GitHub Desktop.
Amazingly simple yet powerful, flex based Split Pane implementation (draggable!)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by Dominik Maszczyk (https://www.linkedin.com/in/dominik-maszczyk/) | |
* on 30/10/17. | |
*/ | |
import React, {Component, PureComponent} from "react"; | |
import "./style.css"; | |
class Resizer extends PureComponent { | |
onDrag = (e) => { | |
let horizontal = this.props.orientation === "horizontal", | |
client = horizontal ? e.clientX : e.clientY, | |
offset = horizontal ? this.resizer.offsetLeft : this.resizer.offsetTop, | |
delta = client - offset; | |
if (!client || Math.abs(delta) < 5) return; // avoid jerky montion while hold but not move + limit frequency of update events | |
this.props.update(delta); | |
}; | |
onMouseDown = () => { | |
window.addEventListener("mousemove", this.onMouseMove); | |
window.addEventListener("mouseup", this.onMouseUp, {passive: true, once: true}); // removed once fired | |
}; | |
onMouseMove = (e) => { | |
e.preventDefault(); | |
this.onDrag(e); | |
}; | |
onMouseUp = () => { | |
window.removeEventListener("mousemove", this.onMouseMove); | |
}; | |
render() { | |
return <div onMouseDown={this.onMouseDown} | |
ref={(resizer) => this.resizer = resizer} | |
className="Resizer"/>; | |
} | |
} | |
class SplitPane extends Component { | |
constructor(props) { | |
super(props); | |
this.state = this.calculateState(props); | |
} | |
calculateState(props) { | |
let styles = this.state && this.state.styles || [], | |
children = []; | |
props.children.forEach((child, i, iterable) => { | |
let flex = (props.flex && typeof props.flex[i] === 'number'); | |
let old_flex = (this.props.flex && typeof this.props.flex[i] === 'number'); | |
if(!styles[i] || flex[i]!==old_flex[i]) { // if child has no flex value or flex props update... | |
styles.push({ // ...update its style with default 1 | |
flexGrow: flex ? props.flex[i] : 1 | |
}); | |
} | |
children.push(child); | |
let update = this.update(i); | |
if (iterable[i + 1]) children.push(<Resizer key={"r" + i} update={update} orientation={this.props.orientation}/>); | |
}); | |
return { | |
styles, | |
children, | |
horizontal: props.orientation | |
}; | |
} | |
update(index) { | |
return (delta) => { | |
let styles = this.state.styles, | |
size = this.state.horizontal ? window.outerWidth : window.outerHeight, // size can be container based instead | |
step = delta * (this.props.children.length-1) / size; // Δflex ~= Δpx * children.lenght/size (closest empirical approx) | |
let flex0 = styles[index].flexGrow + step, | |
flex1 = styles[index + 1].flexGrow - step; | |
flex0*=flex0>0?1:0; // flex grow should not have value smaller than 0 | |
flex1*=flex1>0?1:0; | |
styles[index] = {flexGrow: flex0}; //update just neighbour containers, one might want to update all of them depends on desired behaviour | |
styles[index + 1] = {flexGrow: flex1}; | |
this.setState({ | |
styles | |
}); | |
} | |
} | |
componentWillReceiveProps(props) { | |
if (this.props.children.length !== props.children.length) { | |
this.setState(this.calculateState(props)); | |
} | |
} | |
render() { | |
return <div className={`SplitPane ${this.props.orientation}`}> | |
{this.state.children.map((child, i) => { | |
if (i % 2 === 0) return <div key={"c" + i} style={this.state.styles[i / 2]}>{child}</div>; // even - child | |
else return child; // odd - resizer | |
})} | |
</div>; | |
} | |
} | |
export default SplitPane; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by Dominik Maszczyk (https://www.linkedin.com/in/dominik-maszczyk/) | |
* on 30/10/17. | |
*/ | |
// core styles which makes everything works | |
.SplitPane { | |
display: flex; | |
&>* { | |
flex: 1 1; | |
} | |
.Resizer { | |
flex: 0 1; | |
position: relative; | |
} | |
&.horizontal { | |
height: 100%; | |
flex-direction: row; | |
} | |
&.vertical { | |
height: 100%; | |
flex-direction: column; | |
} | |
} | |
//example theme | |
$resizer-border: 4px; | |
.SplitPane { | |
.Resizer{ | |
background-color: rgba(0,0,0,0.1); | |
padding: $resizer-border / 2; | |
border: $resizer-border solid transparent; | |
background-clip: padding-box; | |
transition-property: background-color, border-color; | |
transition: 0.2s 0.1s; | |
&:hover { | |
background-color: rgba(0,0,0,0.2); | |
border-color: rgba(0,0,0,0.1); | |
} | |
} | |
&.horizontal .Resizer { | |
cursor: col-resize; | |
} | |
&.vertical .Resizer { | |
cursor: row-resize; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SplitPane from "./SplitPane"; | |
// ... | |
<SplitPane orientation="horizontal" flex={[0.2, 0.8]}> // horizontal split with init values of 20 and 80% | |
<SplitPane orientation="vertical"> // it can be nested | |
<div/> // and filled with various number and kinds of elements | |
<span/> | |
<p/> | |
</SplitPane> | |
<div /> | |
</SplitPane> | |
//... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment