commit
d2a2fdbf6c
|
@ -1,14 +1,33 @@
|
|||
.Rule-value {
|
||||
transition: background 0.8s;
|
||||
font-size: 105%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Animation of summary figures changes : flash ! */
|
||||
.flash-enter {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.flash-leave {
|
||||
/* Completely hide the button while it's being animated and before it's removed from the DOM. */
|
||||
.evaporate {
|
||||
display: none;
|
||||
font-size: 80%;
|
||||
/* text-shadow: 0 0 2px var(--colour); */
|
||||
}
|
||||
.evaporate-enter {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -20px;
|
||||
opacity: 0;
|
||||
animation: evaporate 1.6s ease-out;
|
||||
transform: scaleY(0.1);
|
||||
}
|
||||
@keyframes evaporate {
|
||||
5% {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
70% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(-15px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,91 @@
|
|||
/* @flow */
|
||||
import withLanguage from 'Components/utils/withLanguage'
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component, PureComponent } from 'react'
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
|
||||
import './AnimatedTargetValue.css'
|
||||
|
||||
type Props = {
|
||||
value: ?number,
|
||||
language: string
|
||||
}
|
||||
type State = {
|
||||
difference: number
|
||||
}
|
||||
export default withLanguage(
|
||||
class AnimatedTargetValue extends Component {
|
||||
class AnimatedTargetValue extends Component<Props, State> {
|
||||
previousValue: ?number = null
|
||||
timeoutId: ?TimeoutID = null
|
||||
state = { difference: 0 }
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.value === this.props.value) {
|
||||
return
|
||||
}
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId)
|
||||
}
|
||||
this.previousValue =
|
||||
this.previousValue === null ? prevProps.value : this.previousValue
|
||||
|
||||
this.timeoutId = setTimeout(() => {
|
||||
this.setState({
|
||||
difference: (this.props.value || 0) - (this.previousValue || 0)
|
||||
})
|
||||
this.previousValue = null
|
||||
this.timeoutId = null
|
||||
}, 250)
|
||||
}
|
||||
format = value => {
|
||||
return value == null
|
||||
? ''
|
||||
: Intl.NumberFormat(this.props.language, {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
maximumFractionDigits: 0,
|
||||
minimumFractionDigits: 0
|
||||
}).format(value)
|
||||
}
|
||||
render() {
|
||||
let { value, language } = this.props
|
||||
let formattedValue =
|
||||
value == null
|
||||
? ''
|
||||
: Intl.NumberFormat(language, {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
maximumFractionDigits: 0,
|
||||
minimumFractionDigits: 0
|
||||
}).format(value)
|
||||
const formattedValue = this.format(this.props.value)
|
||||
const formattedDifference = this.format(this.state.difference)
|
||||
const shouldDisplayDifference =
|
||||
Math.abs(this.state.difference) > 1 &&
|
||||
formattedDifference !== formattedValue &&
|
||||
this.props.value != null &&
|
||||
this.state.difference < 0.5 * this.props.value
|
||||
return (
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName="flash"
|
||||
transitionEnterTimeout={100}
|
||||
transitionLeaveTimeout={100}>
|
||||
<span key={value} className="Rule-value">
|
||||
{' '}
|
||||
<span>{formattedValue}</span>
|
||||
<>
|
||||
<span key={this.props.value} className="Rule-value">
|
||||
{shouldDisplayDifference && (
|
||||
<Evaporate
|
||||
style={{
|
||||
color: this.state.difference > 0 ? 'chartreuse' : 'red'
|
||||
}}>
|
||||
{(this.state.difference > 0 ? '+' : '') + formattedDifference}
|
||||
</Evaporate>
|
||||
)}{' '}
|
||||
<span>{this.format(this.props.value)}</span>
|
||||
</span>
|
||||
</ReactCSSTransitionGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
class Evaporate extends PureComponent<{ children: string, style: Object }> {
|
||||
render() {
|
||||
return (
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName="evaporate"
|
||||
transitionEnterTimeout={1600}
|
||||
transitionLeaveTimeout={1}>
|
||||
<span
|
||||
key={this.props.children}
|
||||
style={this.props.style}
|
||||
className="evaporate">
|
||||
{this.props.children}
|
||||
</span>
|
||||
</ReactCSSTransitionGroup>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,18 +43,16 @@ export var FormDecorator = formType => RenderField =>
|
|||
}
|
||||
render() {
|
||||
let {
|
||||
setFormValue,
|
||||
stepAction,
|
||||
subquestion,
|
||||
valueType,
|
||||
defaultValue,
|
||||
fieldName,
|
||||
inversion,
|
||||
setFormValue,
|
||||
themeColours
|
||||
} = this.props
|
||||
|
||||
let validate = buildValidationFunction(valueType)
|
||||
|
||||
let submit = cause => stepAction('fold', fieldName, cause),
|
||||
stepProps = {
|
||||
...this.props,
|
||||
|
|
|
@ -3,6 +3,7 @@ import withColours from 'Components/utils/withColours'
|
|||
import { compose } from 'ramda'
|
||||
import React, { Component } from 'react'
|
||||
import { withI18n } from 'react-i18next'
|
||||
import { debounce } from '../../utils'
|
||||
import { FormDecorator } from './FormDecorator'
|
||||
import InputSuggestions from './InputSuggestions'
|
||||
import SendButton from './SendButton'
|
||||
|
@ -13,6 +14,7 @@ export default compose(
|
|||
withColours
|
||||
)(
|
||||
class Input extends Component {
|
||||
debouncedOnChange = debounce(750, this.props.input.onChange)
|
||||
render() {
|
||||
let {
|
||||
input,
|
||||
|
@ -27,16 +29,18 @@ export default compose(
|
|||
suffixed = answerSuffix != null,
|
||||
inputError = dirty && error,
|
||||
submitDisabled = !dirty || inputError
|
||||
|
||||
return (
|
||||
<span>
|
||||
<div className="answer">
|
||||
<input
|
||||
ref={el => {
|
||||
this.inputElement = el
|
||||
}}
|
||||
type="text"
|
||||
{...input}
|
||||
key={input.value}
|
||||
autoFocus
|
||||
defaultValue={input.value}
|
||||
onChange={e => {
|
||||
e.persist()
|
||||
this.debouncedOnChange(e)
|
||||
}}
|
||||
className={classnames({ suffixed })}
|
||||
id={'step-' + dottedName}
|
||||
inputMode="numeric"
|
||||
|
|
|
@ -9,11 +9,11 @@ export let parseDataAttributes = (value: any) =>
|
|||
value === 'undefined'
|
||||
? undefined
|
||||
: value === null
|
||||
? null
|
||||
: !isNaN(value)
|
||||
? +value
|
||||
: /* value is a normal string */
|
||||
value
|
||||
? null
|
||||
: !isNaN(value)
|
||||
? +value
|
||||
: /* value is a normal string */
|
||||
value
|
||||
|
||||
export let getIframeOption = (optionName: string) => {
|
||||
let url = getUrl(),
|
||||
|
|
Loading…
Reference in New Issue