Skip to content

Instantly share code, notes, and snippets.

@forgo
Created January 24, 2019 02:29
Show Gist options
  • Select an option

  • Save forgo/183f70a771c2fdddd5de455d6fbed699 to your computer and use it in GitHub Desktop.

Select an option

Save forgo/183f70a771c2fdddd5de455d6fbed699 to your computer and use it in GitHub Desktop.
Wrapper React component, allowing for more complex interaction-based styles "out-of-the props", so to speak.
import React from 'react'
export const Key = {
TAB: 9,
ENTER: 13,
SPACE: 32,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
}
class Interactive extends React.PureComponent {
componentWillMount() {
this.setState({
hovering: false,
pressing: false,
pressingGlobal: false,
focusing: false,
keying: false,
isTouch: false,
touching: false,
touchingGlobal: false,
})
}
componentDidMount() {
document.addEventListener('mouseup', this.interactiveGlobalMouseUp, false)
document.addEventListener(
'mousedown',
this.interactiveGlobalMouseDown,
false
)
document.addEventListener(
'touchstart',
this.interactiveGlobalTouchStart,
false
)
document.addEventListener('touchend', this.interactiveGlobalTouchEnd, false)
}
componentWillUnmount() {
document.removeEventListener(
'mouseup',
this.interactiveGlobalMouseUp,
false
)
document.removeEventListener(
'mousedown',
this.interactiveGlobalMouseDown,
false
)
document.removeEventListener(
'touchstart',
this.interactiveGlobalTouchStart,
false
)
document.removeEventListener(
'touchend',
this.interactiveGlobalTouchEnd,
false
)
}
interactiveMouseOver = event => {
this.setState(prevState => {
return {
...prevState,
hovering: !prevState.isTouch,
pressing: prevState.pressingGlobal,
touching: false,
isTouch: false,
}
})
}
interactiveMouseOut = event => {
this.setState(prevState => {
return {
...prevState,
hovering: false,
pressing: false,
}
})
}
interactiveGlobalMouseUp = event => {
this.setState(prevState => {
return {
...prevState,
pressingGlobal: false,
}
})
}
interactiveGlobalMouseDown = event => {
this.setState(prevState => {
return {
...prevState,
pressingGlobal: !prevState.isTouch,
}
})
}
interactiveMouseDown = event => {
this.setState(prevState => {
return {
...prevState,
pressing: true,
}
})
}
interactiveMouseUp = event => {
this.setState(prevState => {
return {
...prevState,
pressing: false,
}
})
}
interactiveFocus = event => {
this.setState(prevState => {
return {
...prevState,
focusing: true,
}
})
}
interactiveBlur = event => {
this.setState(prevState => {
return {
...prevState,
focusing: false,
keying: false,
}
})
}
interactiveKeyDown = event => {
const stateKeyingIfFocusing = prevState => {
return {
...prevState,
keying: prevState.focusing,
}
}
if (event.keyCode === Key.ENTER) {
this.setState(stateKeyingIfFocusing)
}
else if (event.keyCode === Key.SPACE) {
// prevent scrolling down on space press
event.preventDefault()
// TODO: allow boolean to style with SPACE if SPACE is a valid key interaction?
// this.setState(stateKeyingIfFocusing)
}
}
interactiveKeyUp = event => {
this.setState(prevState => {
return {
...prevState,
keying: false,
}
})
}
interactiveTouchStart = event => {
this.setState(prevState => {
return {
...prevState,
isTouch: true,
touching: true,
}
})
}
interactiveGlobalTouchStart = event => {
this.setState(prevState => {
return {
...prevState,
touchingGlobal: true,
}
})
}
interactiveTouchMove = event => {
this.setState(prevState => {
return {
...prevState,
}
})
}
interactiveTouchCancel = event => {
this.setState(prevState => {
return {
...prevState,
touching: false,
}
})
}
interactiveTouchEnd = event => {
this.setState(prevState => {
return {
...prevState,
touching: false,
}
})
}
interactiveGlobalTouchEnd = event => {
this.setState(prevState => {
return {
...prevState,
touchingGlobal: false,
}
})
}
interactiveEvents = () => {
return {
tabIndex: 0,
onMouseOver: this.interactiveMouseOver,
onMouseOut: this.interactiveMouseOut,
onMouseDown: this.interactiveMouseDown,
onMouseUp: this.interactiveMouseUp,
onFocus: this.interactiveFocus,
onBlur: this.interactiveBlur,
onKeyDown: this.interactiveKeyDown,
onKeyUp: this.interactiveKeyUp,
onTouchStart: this.interactiveTouchStart,
onTouchMove: this.interactiveTouchMove,
onTouchCancel: this.interactiveTouchCancel,
onTouchEnd: this.interactiveTouchEnd,
}
}
interactiveStylesMerge = (styles, styleProp) => {
if (styles[styleProp]) {
const normalStyle = styles[styleProp].normal
const hoverStyle =
this.state.hovering && !this.state.isTouch
? styles[styleProp].hover
: {}
const pressStyle =
(this.state.pressing || this.state.keying) && !this.state.isTouch
? styles[styleProp].press
: {}
const focusStyle =
this.state.focusing && !this.state.isTouch
? styles[styleProp].focus
: {}
const keyStyle =
this.state.keying && !this.state.isTouch ? styles[styleProp].key : {}
const touchStyle = this.state.touching ? styles[styleProp].touch : {}
return {
...normalStyle,
...hoverStyle,
...pressStyle,
...focusStyle,
...keyStyle,
...touchStyle,
}
}
return {}
}
interactiveStyles = styles => {
return (...styleProps) => {
return styleProps.reduce((styleAccumulator, styleProp) => {
return {
...styleAccumulator,
...this.interactiveStylesMerge(styles, styleProp),
}
}, {})
}
}
render() {
const {userProps, Component, istyles} = this.props
const mergedProps = {
...userProps,
ievents: this.interactiveEvents(),
istate: this.state,
istyles: this.interactiveStyles(istyles),
}
return <Component {...mergedProps} />
}
}
export default (Component, istyles) => {
return props => {
const mergedProps = {
userProps: props,
Component: Component,
istyles: istyles ? istyles : {},
}
return <Interactive {...mergedProps} />
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment