ldx-widgets
Version:
widgets
244 lines (216 loc) • 8.8 kB
JavaScript
/*&
@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
&
*/
(function() {
var Animation, ConfirmSave, ProgressBar, React, Spinner, button, createClass, div, easing, ref, span;
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'));
ref = require('react-dom-factories'), div = ref.div, span = ref.span, button = ref.button;
ConfirmSave = createClass({
displayName: 'ConfirmSave',
getDefaultProps: function() {
return {
dismissBtnText: 'Dismiss',
scaleCheckmark: 1,
animationDuration: 800,
vTranslateCheckmark: 0,
saveMessage: '',
displayProgressBar: false,
uploadProgress: '',
longSaveMessage: false
};
},
getInitialState: function() {
return {
scale: .25
};
},
render: function() {
var checkClassName, confirmSaveClass, content, dismissBtnText, displayContent, displayProgressBar, longSaveMessage, ref1, saveMessage, saveState, scale, scaleCheckmark, uploadProgress, vTranslateCheckmark;
ref1 = this.props, dismissBtnText = ref1.dismissBtnText, saveState = ref1.saveState, scaleCheckmark = ref1.scaleCheckmark, vTranslateCheckmark = ref1.vTranslateCheckmark, saveMessage = ref1.saveMessage, displayProgressBar = ref1.displayProgressBar, uploadProgress = ref1.uploadProgress, longSaveMessage = ref1.longSaveMessage;
scale = this.state.scale;
confirmSaveClass = 'confirm-save-wrap';
if (saveMessage) {
confirmSaveClass += ' has-message';
}
if (saveState === 'pending' && displayProgressBar && uploadProgress !== '') {
confirmSaveClass += ' progress-background';
content = ProgressBar({
key: 'progress',
progress: uploadProgress,
className: "modal-bar",
labelPosition: "top"
});
} else if (saveState === 'pending') {
content = Spinner({
lines: 12,
length: 10,
width: 3,
radius: 8,
color: 'white'
});
} else if (saveMessage) {
checkClassName = 'confirm-check-message';
if (saveState === 'failed') {
checkClassName += ' failed';
}
if (saveState === 'warning') {
checkClassName += ' warning';
}
if (longSaveMessage) {
checkClassName += ' long';
}
scaleCheckmark = 1;
content = [
div({
key: 'icon',
className: checkClassName
}, span({
className: 'confirm-message'
}, saveMessage)), div({
key: 'dismiss',
className: 'message-dismiss'
}, button({
onClick: this.end
}, dismissBtnText))
];
} else {
checkClassName = 'confirm-check';
if (saveState === 'failed') {
checkClassName += ' failed';
}
if (saveState === 'warning') {
checkClassName += ' warning';
}
content = div({
className: checkClassName,
style: {
transform: "scale(" + scale + ")",
msTransform: "scale(" + scale + ")",
WebkitTransform: "scale(" + scale + ")"
}
});
}
if (saveState === 'pending' && displayProgressBar && uploadProgress !== '') {
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);
}
return div({
className: confirmSaveClass
}, displayContent);
},
componentWillMount: function() {
this.endHasBeenCalled = false;
if (this.props.saveState === 'complete' || this.props.saveState === 'failed') {
return this.animateImmediately = true;
}
},
componentDidMount: function() {
if (this.animateImmediately) {
return this.animateCheck();
}
},
componentDidUpdate: function(prevProps) {
if (!(prevProps.saveState === 'pending' && (this.props.saveState === 'complete' || this.props.saveState === 'warning' || this.props.saveState === 'failed'))) {
return;
}
return this.animateCheck();
},
componentWillUnmount: function() {
var ref1;
if ((ref1 = this.animation) != null ? ref1.isAnimating() : void 0) {
return this.animation.end();
}
},
animateCheck: function() {
var animationDuration, ref1, ref2, saveMessage, scale;
if ((ref1 = this.animation) != null ? ref1.isAnimating() : void 0) {
this.animation.end();
}
scale = this.state.scale;
ref2 = this.props, animationDuration = ref2.animationDuration, saveMessage = ref2.saveMessage;
return this.animation = new Animation({
duration: animationDuration,
easing: easing('easeOutElastic')
}).init({
scale: scale
}).on('frame', this.onFrame).on('complete', saveMessage ? function() {} : this.end).animateTo({
scale: 1
});
},
onFrame: function(e) {
return this.setState(e.values);
},
end: function() {
var base, base1, done, fail, ref1, saveState;
ref1 = this.props, saveState = ref1.saveState, done = ref1.done, fail = ref1.fail;
if (!this.endHasBeenCalled) {
this.endHasBeenCalled = true;
if (saveState === 'complete') {
return typeof (base = this.props).done === "function" ? base.done() : void 0;
} else {
return typeof (base1 = this.props).fail === "function" ? base1.fail() : void 0;
}
}
}
});
module.exports = ConfirmSave;
}).call(this);