Merge pull request #428 from betagouv/vue-difference

Vue difference
pull/434/head
Johan Girod 2018-11-22 16:30:00 +01:00 committed by GitHub
commit d2a2fdbf6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 41 deletions

View File

@ -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;
}
}

View File

@ -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>
)
}
}

View File

@ -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,

View File

@ -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"

View File

@ -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(),