ldx-widgets
Version:
widgets
214 lines (169 loc) • 6.93 kB
text/coffeescript
###&
@general
This component is intended to be used inside of a modal or popover, but could be used other places to confirm a save event was successful.
Tips for using this..
- It is absolutely positioned w/ all zeroes, to add to the top level of the render tree
- Create a local 'saveState' state on your component, and default it to null
- render like this:
```coffee
ConfirmSave {
key: 'confirm'
done: @close
saveState: @state.saveState
} if @state.saveState?
```
This way, when you trigger the save, also call @setState({saveState: 'pending'})
Then pass a call back to the save action, which just calls @setState({saveState: 'complete'})
@props.saveState - REQUIRED - [String]
This can be either 'pending' or 'complete' or 'failed'
- When 'pending', a spinner shows
- When 'complete', a green check mark flashes before calling the done callback
- When 'warning', a yellow exclamation mark flashes before calling the done callback
- When 'failed', a red x flashes before calling the fail callback
@props.saveMessage - REQUIRED - [String]
A message that will be displayed alongside the saveState imagery
@props.longSaveMessage - OPTIONAL - [Boolean] - default false
Will improve formatting for long error messages
@props.dismissBtnText - OPTIONAL - [String]
Text that will be displayed as the dismiss button
@props.done - REQUIRED - [Function]
This method will be called animationDuration ms after the saveState hits complete
It will usually be a method that sets the parent's saveState back to null, which will remove this widget from the DOM, and will usually be a method that also performs some other post save action
@props.fail - OPTIONAL - [Function]
This method will be called animationDuration ms after the saveState hits failed
It is REQUIRED if there is any change saveState will ever hit 'failed', otherwise the component will never hide after a failed save
It will usually be a method that sets the parent's saveState back to null, which will remove this widget from the DOM
@props.scaleCheckmark - OPTIONAL - [Number] - default 1
percent to scale the confirm check and spinner
@props.vTranslateCheckmark - OPTIONAL - default 0
number of pixels to move the checkmark up above the middle of the container
@props.animationDuration - OPTIONAL - default 800
ms over which the check scalling takes place
@props.displayProgressBar - OPTIONAL - [Boolean]
Defaults to no, whether or not the confirm/save show the progress bar instead of the spinner
@props.uploadProgress - OPTIONAL - [Number]
Progress of a file being uploaded
&###
React = require 'react'
createClass = require 'create-react-class'
Animation = require 'ainojs-animation'
easing = require 'ainojs-easing'
Spinner = React.createFactory(require './spinner')
ProgressBar = React.createFactory(require './progress_bar')
{div, span, button} = require 'react-dom-factories'
ConfirmSave = createClass
displayName: 'ConfirmSave'
getDefaultProps: ->
dismissBtnText: 'Dismiss'
scaleCheckmark: 1
animationDuration: 800
vTranslateCheckmark: 0
saveMessage: ''
displayProgressBar: no
uploadProgress: ''
longSaveMessage: no
getInitialState: ->
scale: .25
render: ->
{dismissBtnText, saveState, scaleCheckmark, vTranslateCheckmark, saveMessage, displayProgressBar, uploadProgress, longSaveMessage} = @props
{scale} = @state
confirmSaveClass = 'confirm-save-wrap'
confirmSaveClass += ' has-message' if saveMessage
if saveState is 'pending' and displayProgressBar and uploadProgress isnt ''
confirmSaveClass += ' progress-background'
content = ProgressBar {
key: 'progress'
progress: uploadProgress
className: "modal-bar"
labelPosition: "top"
}
else if saveState is 'pending'
content = Spinner {
lines: 12
length: 10
width: 3
radius: 8
color: 'white'
}
# Use a separate component for messages
else if saveMessage
checkClassName = 'confirm-check-message'
checkClassName += ' failed' if saveState is 'failed'
checkClassName += ' warning' if saveState is 'warning'
checkClassName += ' long' if longSaveMessage
# Don't do a scale animation when there's a message
scaleCheckmark = 1
content = [
div {
key: 'icon'
className: checkClassName
},
span {
className: 'confirm-message'
}, saveMessage
div {
key: 'dismiss'
className: 'message-dismiss'
},
button {
onClick: @end
}, dismissBtnText
]
else
checkClassName = 'confirm-check'
checkClassName += ' failed' if saveState is 'failed'
checkClassName += ' warning' if saveState is 'warning'
content = div {
className: checkClassName
style:
transform: "scale(#{scale})"
msTransform: "scale(#{scale})"
WebkitTransform: "scale(#{scale})"
}
if saveState is 'pending' and displayProgressBar and uploadProgress isnt ''
displayContent = div {
className: 'progress-bar-frame'
}, content
else
displayContent = div {
className: 'confirm-frame'
style:
transform: "scale(#{scaleCheckmark}) translateY(#{vTranslateCheckmark}px)"
msTransform: "scale(#{scaleCheckmark}) translateY(#{vTranslateCheckmark}px)"
WebkitTransform: "scale(#{scaleCheckmark}) translateY(#{vTranslateCheckmark}px)"
}, content
div {
className: confirmSaveClass
}, displayContent
componentWillMount: ->
@endHasBeenCalled = no
# Handles the case when the saveState is never pending
if @props.saveState is 'complete' or @props.saveState is 'failed'
@animateImmediately = yes
componentDidMount: ->
@animateCheck() if @animateImmediately
componentDidUpdate: (prevProps) ->
return unless prevProps.saveState is 'pending' and (@props.saveState is 'complete' or @props.saveState is 'warning' or @props.saveState is 'failed')
@animateCheck()
componentWillUnmount: ->
if @animation?.isAnimating() then @animation.end()
animateCheck: ->
if @animation?.isAnimating() then @animation.end()
{scale} = @state
{animationDuration, saveMessage} = @props
@animation = new Animation
duration: animationDuration
easing: easing('easeOutElastic')
.init {scale}
.on 'frame', @onFrame
.on 'complete', if saveMessage then -> else @end
.animateTo {scale: 1}
onFrame: (e) ->
@setState e.values
end: ->
{saveState, done, fail} = @props
if not @endHasBeenCalled
@endHasBeenCalled = yes
if saveState is 'complete' then @props.done?()
else @props.fail?()
module.exports = ConfirmSave