Skip to content

Instantly share code, notes, and snippets.

@tkh44
Last active September 13, 2022 01:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tkh44/2af4944f3ab2ec095d7998f7455aa249 to your computer and use it in GitHub Desktop.
Save tkh44/2af4944f3ab2ec095d7998f7455aa249 to your computer and use it in GitHub Desktop.
wrapper wround react-dropzone so that you can pass some utilties to the children via function args
import { Component, createElement, DOM } from 'react'
import compose from 'recompose/compose'
import defaultProps from 'recompose/defaultProps'
import withHandlers from 'recompose/withHandlers'
import withReducer from 'recompose/withReducer'
import DropZone from 'react-dropzone'
import { TransitionMotion, spring } from 'react-motion'
import { StyleSheet, css } from 'aphrodite/no-important'
import { animation, colors, commonStyles, font } from 'style'
import Icon from 'art/Icon'
const { div } = DOM
const initialState = { entered: false, acceptedFile: null, rejectedFile: null }
const PANEL_SPRING = { stiffness: 300, damping: 28 }
export default compose(
defaultProps({
activeClassName: 'active-file-drop',
className: 'file-drop', // this lib will style the drop zone if no class is provided
enabled: true,
fullscreenBackdrop: true,
uploadMessage: 'drop image here'
}),
withReducer(
'dropState',
'updateState',
(state, action) => {
switch (action.type) {
case 'DRAG_ENTER':
return { ...state, ...{ entered: true } }
case 'DRAG_LEAVE':
return { ...state, ...{ entered: false } }
case 'ACCEPT_FILE':
return { entered: false, acceptedFile: action.payload, rejectedFile: null }
case 'REJECT_FILE':
return { entered: false, acceptedFile: null, rejectedFile: action.payload }
case 'RESET':
return initialState
default:
return state
}
},
initialState
),
withHandlers({
handleDropAccepted: ({ enabled, onDropAccepted, updateState, state }) => (files) => {
const file = files[0]
updateState({ type: 'ACCEPT_FILE', payload: file })
if (onDropAccepted) {
onDropAccepted(file)
}
},
handleDropRejected: ({ onDropRejected, updateState, state }) => (files) => {
const file = files[0]
updateState({ type: 'REJECT_FILE', payload: file })
if (onDropRejected) {
onDropRejected(file)
}
},
handleDragEnter: ({ updateState }) => () => updateState({ type: 'DRAG_ENTER' }),
handleDragLeave: ({ updateState }) => () => updateState({ type: 'DRAG_LEAVE' }),
handleReset: ({ updateState }) => () => {
updateState({ type: 'RESET' })
}
})
)(class FileDrop extends Component {
render () {
const {
activeClassName,
children,
className,
dropState,
enabled,
fullscreenBackdrop,
handleDragEnter,
handleDragLeave,
handleDropAccepted,
handleDropRejected,
handleReset,
style,
uploadMessage
} = this.props
if (!enabled) {
return div(
{
className,
style
},
children({
...dropState,
openDropZoneManually: this.openDropZoneManually,
resetDropZone: handleReset
})
)
}
return createElement(
DropZone,
{
ref: (node) => (this.dropZone = node),
className,
style,
activeClassName,
multiple: false,
accept: 'image/*',
disableClick: true,
onDragEnter: handleDragEnter,
onDragLeave: handleDragLeave,
onDropAccepted: handleDropAccepted,
onDropRejected: handleDropRejected
},
fullscreenBackdrop && createElement(
TransitionMotion,
{
willEnter: this.willEnter,
willLeave: this.willLeave,
styles: this.getStyles()
},
(currentStyles) => div({},
currentStyles.map((config) => {
return div(
{
key: config.key,
className: css(styles.dropBackdrop),
style: {
opacity: dropState.entered ? 1 : 0
}
},
div(
{
className: css(styles.dropHere),
style: {
transform: `scale3d(${config.style.scale}, ${config.style.scale}, ${config.style.scale})`,
opacity: config.style.opacity
}
},
createElement(
Icon,
{
name: 'image',
className: css(styles.dropHereIcon)
}
),
div({ className: css(styles.dropText) }, uploadMessage)
),
)
})
)
),
children({
...dropState,
openDropZoneManually: this.openDropZoneManually,
resetDropZone: handleReset
})
)
}
getStyles = () => {
const { dropState } = this.props
if (!dropState.entered) {
return []
}
return [{
key: 'drop-panel',
data: {},
style: { scale: spring(1, PANEL_SPRING), opacity: spring(1, PANEL_SPRING) }
}]
}
willEnter = () => ({ scale: 0.8, opacity: 0 })
willLeave = () => ({ scale: 0.8, opacity: 0 })
openDropZoneManually = () => {
this.dropZone.open()
}
})
const styles = StyleSheet.create({
dropBackdrop: {
...commonStyles.opaqueBg('rgba(255, 255, 255, 0.8)'),
position: 'fixed',
top: 0,
right: 0,
left: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 499
},
dropHere: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '50vw',
height: '50vh',
padding: 16,
backgroundColor: 'rgba(255,255,255,0.8)',
border: `4px dashed ${colors.lightgray}`,
borderRadius: 2,
zIndex: 501
},
dropHereIcon: {
width: 152,
height: 152,
fill: colors.brandgreen,
animationName: animation.keyframes.pulse,
animationDuration: '2s',
animationIterationCount: 'infinite'
},
dropText: {
...font.headline,
color: colors.lightgray,
textAlign: 'center'
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment