Example use:
<Toggle
width="5rem"
on={this.state.on}
onClick={() => {
this.setState(({ on }) => ({ on: !on }))
}}
/>
import React, { Component } from "react" | |
export default class Toggle extends Component { | |
static defaultProps = { | |
on: false, | |
onClick: () => {}, | |
colors: { | |
on: "tomato", | |
off: "whitesmoke", | |
fill: "white", | |
stroke: "black" | |
}, | |
width: "auto", | |
height: "auto" | |
} | |
generateState = stateName => { | |
const { colors } = this.props | |
switch (stateName) { | |
case "OFF": { | |
return { | |
x: 3, | |
width: 6, | |
fill: colors.off | |
} | |
} | |
case "ON": { | |
return { | |
x: 11, | |
width: 6, | |
fill: colors.on | |
} | |
} | |
case "OFF_TRANSITION": { | |
return { | |
x: 3, | |
width: 8, | |
fill: colors.off | |
} | |
} | |
case "ON_TRANSITION": { | |
return { | |
x: 9, | |
width: 8, | |
fill: colors.on | |
} | |
} | |
default: { | |
return null | |
} | |
} | |
} | |
goToState = stateName => { | |
this.setState({ | |
name: stateName, | |
machine: this.generateState(stateName) | |
}) | |
} | |
state = { | |
mouseDown: false, | |
name: "OFF", | |
machine: this.generateState("OFF") | |
} | |
handleMouseUp = ({ target }) => { | |
document.removeEventListener("mouseup", this.handleMouseUp) | |
this.setState(() => ({ mouseDown: false })) | |
const { id, on } = this.props | |
if (target === this.inner || target === this.container) | |
this.props.onClick({ id, on }) | |
this.props.on ? this.goToState("ON") : this.goToState("OFF") | |
} | |
handleMouseDown = () => { | |
document.addEventListener("mouseup", this.handleMouseUp) | |
this.props.on | |
? this.goToState("ON_TRANSITION") | |
: this.goToState("OFF_TRANSITION") | |
this.setState(() => ({ mouseDown: true })) | |
} | |
endTransition = () => { | |
!this.state.mouseDown && | |
(this.props.on ? this.goToState("ON") : this.goToState("OFF")) | |
} | |
componentDidMount() { | |
const { on } = this.props | |
this.setState(() => ({ on })) | |
on ? this.goToState("ON") : this.goToState("OFF") | |
this.inner.addEventListener("transitionend", this.endTransition) | |
} | |
componentDidUpdate() { | |
const { x, width, fill } = this.state.machine | |
this.inner.setAttribute("x", x) | |
this.inner.setAttribute("width", width) | |
this.container.setAttribute("fill", fill) | |
} | |
componentWillUnmount() { | |
document.removeEventListener("mouseup", this.handleMouseUp) | |
this.inner.removeEventListener("transitionend", this.endTransition) | |
} | |
render() { | |
const transition = { transition: "200ms ease-out" } | |
const { colors, height, width } = this.props | |
return ( | |
<svg | |
style={{ display: "inline-block", height, width }} | |
ref={svg => (this.svg = svg)} | |
height={height} | |
viewBox="0 0 20 12" | |
xmlns="http://www.w3.org/2000/svg" | |
onMouseDown={this.handleMouseDown} | |
> | |
<rect | |
ref={container => (this.container = container)} | |
style={transition} | |
fill={colors.fill} | |
stroke={colors.stroke} | |
strokeWidth="0.5" | |
x="2" | |
y="2" | |
width="16" | |
height="8" | |
rx="4" | |
ry="4" | |
/> | |
<rect | |
ref={inner => (this.inner = inner)} | |
style={transition} | |
fill={colors.fill} | |
stroke={colors.stroke} | |
strokeWidth="0.5" | |
x="3" | |
y="3" | |
width="6" | |
height="6" | |
rx="3" | |
ry="3" | |
/> | |
</svg> | |
) | |
} | |
} |
Example use:
<Toggle
width="5rem"
on={this.state.on}
onClick={() => {
this.setState(({ on }) => ({ on: !on }))
}}
/>