Skip to content

Instantly share code, notes, and snippets.

@pete-murphy
Last active January 21, 2019 15:41
Show Gist options
  • Save pete-murphy/3019f26d8402c63d345824db377a0098 to your computer and use it in GitHub Desktop.
Save pete-murphy/3019f26d8402c63d345824db377a0098 to your computer and use it in GitHub Desktop.
Simple SVG Toggle in React
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 }))
  }}
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment