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 }))
}}
/>