🎨 Nouveau style pour les boutons d'explication
Ils sont en dehors des éléments qu'ils décrivent, donc plus visibles. Suppression de constructStepMeta, inutile.pull/138/head
parent
ead20c9eb3
commit
3158bd1263
|
@ -135,18 +135,18 @@ export default class extends Component {
|
|||
let fieldName =
|
||||
(unfolded &&
|
||||
inputInversions &&
|
||||
R.path(step.name.split('.'), inputInversions)) ||
|
||||
step.name
|
||||
R.path(step.dottedName.split('.'), inputInversions)) ||
|
||||
step.dottedName
|
||||
|
||||
return (
|
||||
<step.component
|
||||
key={step.name}
|
||||
key={step.dottedName}
|
||||
{...step}
|
||||
unfolded={unfolded}
|
||||
step={step}
|
||||
situationGate={situationGate}
|
||||
fieldName={fieldName}
|
||||
inverted={step.name !== fieldName}
|
||||
inverted={step.dottedName !== fieldName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,37 +5,28 @@
|
|||
border-radius: .2em;
|
||||
display: inline-block;
|
||||
margin-left: .8em;
|
||||
opacity: .5;
|
||||
padding: .15em;
|
||||
opacity: .4;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 1em;
|
||||
cursor: pointer;
|
||||
vertical-align: baseline;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.explicable:hover .icon {
|
||||
opacity: 1;
|
||||
border-color: white;
|
||||
}
|
||||
.explicable.dark:hover .icon {
|
||||
border-color: #333350;
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
.explicable .icon:hover {
|
||||
opacity: 1;
|
||||
background: white;
|
||||
color: #4A89DC;
|
||||
}
|
||||
.explicable.dark .icon:hover {
|
||||
background: #333350;
|
||||
color: white;
|
||||
/* Add explanation text to icon, feels too heavy
|
||||
.explicable .icon:hover::after {
|
||||
content: '+ d\'infos';
|
||||
margin-left: .2em;
|
||||
font-size: 90%;
|
||||
opacity: .3
|
||||
}
|
||||
|
||||
*/
|
||||
.explicable.explained .icon {
|
||||
opacity: 1;
|
||||
background: #4A89DC;
|
||||
color: white;
|
||||
}
|
||||
.explicable.dark.explained .icon {
|
||||
background: #333350;
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -11,46 +11,35 @@ import ReactPiwik from '../Tracker';
|
|||
@connect(state => ({explained: state.explainedVariable}), dispatch => ({
|
||||
explain: variableName => dispatch({type: EXPLAIN_VARIABLE, variableName})
|
||||
}))
|
||||
@HoverDecorator
|
||||
export default class Explicable extends React.Component {
|
||||
render(){
|
||||
let {
|
||||
dottedName, hover, label,
|
||||
dottedName,
|
||||
explain, explained,
|
||||
lightBackground
|
||||
} = this.props
|
||||
} = this.props
|
||||
|
||||
// Rien à expliquer ici, ce n'est pas une règle
|
||||
if (dottedName == null)
|
||||
return <span>{label}</span>
|
||||
return this.props.children
|
||||
|
||||
let rule = findRuleByDottedName(rules, dottedName)
|
||||
|
||||
|
||||
let ruleLabel = (
|
||||
label || rule.title
|
||||
).replace(/\s\?$/g, '\u00a0?') // le possible ' ?' final est rendu insécable
|
||||
|
||||
// Rien à expliquer ici, il n'y a pas de champ description dans la règle
|
||||
if (!rule.description)
|
||||
return <span>{ruleLabel}</span>
|
||||
if (rule.description == null)
|
||||
return this.props.children
|
||||
|
||||
//TODO montrer les variables de type 'une possibilité'
|
||||
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classNames('explicable', {explained: dottedName === explained, dark: lightBackground})} >
|
||||
{ruleLabel}
|
||||
className={classNames('explicable', {explained: dottedName === explained})} >
|
||||
{this.props.children}
|
||||
<span
|
||||
className="icon"
|
||||
onClick={e => {
|
||||
ReactPiwik.push(['trackEvent', 'help', dottedName]);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClick={() => {
|
||||
ReactPiwik.push(['trackEvent', 'help', dottedName])
|
||||
explain(dottedName)
|
||||
}}>
|
||||
<i className="fa fa-info" aria-hidden="true"></i>
|
||||
<i className="fa fa-book" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
|
|
|
@ -6,6 +6,8 @@ import { stepAction } from '../../actions'
|
|||
import StepAnswer from './StepAnswer'
|
||||
import { capitalise0 } from '../../utils'
|
||||
import R from 'ramda'
|
||||
import Explicable from 'Components/conversation/Explicable'
|
||||
|
||||
/*
|
||||
This higher order component wraps "Form" components (e.g. Question.js), that represent user inputs,
|
||||
with a header, click actions and more goodies.
|
||||
|
@ -28,7 +30,7 @@ export var FormDecorator = formType => RenderField =>
|
|||
)
|
||||
class extends Component {
|
||||
state = {
|
||||
helpVisible: false,
|
||||
helpVisible: false
|
||||
}
|
||||
render() {
|
||||
let {
|
||||
|
@ -59,7 +61,7 @@ export var FormDecorator = formType => RenderField =>
|
|||
inverted,
|
||||
//TODO hack, enables redux-form/CHANGE to update the form state before the traverse functions are run
|
||||
submit: () => setTimeout(() => stepAction('fold', fieldName), 1),
|
||||
setFormValue: (value, name=fieldName) => setFormValue(name, value)
|
||||
setFormValue: (value, name = fieldName) => setFormValue(name, value)
|
||||
}
|
||||
|
||||
/* There won't be any answer zone here, widen the question zone */
|
||||
|
@ -102,18 +104,25 @@ export var FormDecorator = formType => RenderField =>
|
|||
< Le titre de ma question > ----------- < (? bulle d'aide) OU résultat >
|
||||
*/
|
||||
renderHeader(unfolded, valueType, human, helpText, wideQuestion) {
|
||||
let { subquestion } = this.props.step
|
||||
let { step: { subquestion }, fieldName, inversion } = this.props
|
||||
return (
|
||||
<span className="form-header">
|
||||
{unfolded
|
||||
? this.renderQuestion(unfolded, helpText, wideQuestion, subquestion)
|
||||
? this.renderQuestion(
|
||||
unfolded,
|
||||
helpText,
|
||||
wideQuestion,
|
||||
subquestion,
|
||||
fieldName,
|
||||
inversion
|
||||
)
|
||||
: this.renderTitleAndAnswer(valueType, human)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
renderQuestion = (unfolded, helpText, wideQuestion, subquestion) => (
|
||||
<div className="step-question">
|
||||
renderQuestion(unfolded, helpText, wideQuestion, subquestion, fieldName, inversion) {
|
||||
let question = (
|
||||
<h1
|
||||
style={{
|
||||
// border: '2px solid ' + this.props.themeColours.colour, // higher border width and colour to emphasize focus
|
||||
|
@ -125,24 +134,41 @@ export var FormDecorator = formType => RenderField =>
|
|||
{R.path(['props', 'step', 'inversion', 'question'])(this) ||
|
||||
this.props.step.question}
|
||||
</h1>
|
||||
<div
|
||||
className="step-subquestion"
|
||||
dangerouslySetInnerHTML={{ __html: subquestion }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
return (
|
||||
<div className="step-question">
|
||||
{inversion ? (
|
||||
question
|
||||
) : (
|
||||
<Explicable dottedName={fieldName}>{question}</Explicable>
|
||||
)}
|
||||
<div
|
||||
className="step-subquestion"
|
||||
dangerouslySetInnerHTML={{ __html: subquestion }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderTitleAndAnswer(valueType, human) {
|
||||
let { step, stepAction, situationGate, themeColours, step: { title }, fieldName } = this.props
|
||||
let {
|
||||
step,
|
||||
stepAction,
|
||||
situationGate,
|
||||
themeColours,
|
||||
step: { title },
|
||||
fieldName
|
||||
} = this.props
|
||||
let inversionTitle = R.path(['props', 'step', 'inversion', 'title'])(this)
|
||||
|
||||
|
||||
let answer = situationGate(fieldName)
|
||||
|
||||
return (
|
||||
<div className="foldedQuestion">
|
||||
<span className="borderWrapper">
|
||||
<span className="title">{capitalise0(inversionTitle || title)}</span>
|
||||
<span className="title">
|
||||
{capitalise0(inversionTitle || title)}
|
||||
</span>
|
||||
<span className="answer">{answer}</span>
|
||||
</span>
|
||||
<button
|
||||
|
|
|
@ -13,7 +13,7 @@ export default class Input extends Component {
|
|||
render() {
|
||||
let {
|
||||
input,
|
||||
stepProps: { name, attributes, submit, valueType },
|
||||
stepProps: { dottedName, attributes, submit, valueType },
|
||||
meta: { touched, error, active },
|
||||
themeColours
|
||||
} = this.props,
|
||||
|
@ -34,7 +34,7 @@ export default class Input extends Component {
|
|||
{...input}
|
||||
value={hoverSuggestion != null ? hoverSuggestion : input.value}
|
||||
className={classnames({ suffixed })}
|
||||
id={'step-' + name}
|
||||
id={'step-' + dottedName}
|
||||
{...attributes}
|
||||
style={
|
||||
!active
|
||||
|
@ -52,7 +52,7 @@ export default class Input extends Component {
|
|||
{suffixed && (
|
||||
<label
|
||||
className="suffix"
|
||||
htmlFor={'step-' + name}
|
||||
htmlFor={'step-' + dottedName}
|
||||
style={!active ? { color: '#888' } : { color: '#222' }}
|
||||
>
|
||||
{answerSuffix}
|
||||
|
@ -80,23 +80,23 @@ export default class Input extends Component {
|
|||
}
|
||||
|
||||
componentDidMount(){
|
||||
let { stepProps: { name, inversion, setFormValue} } = this.props
|
||||
let { stepProps: { dottedName, inversion, setFormValue} } = this.props
|
||||
if (!inversion) return null
|
||||
// initialize the form field in renderinversions
|
||||
setFormValue(inversion.inversions[0].dottedName, 'inversions.' + name)
|
||||
setFormValue(inversion.inversions[0].dottedName, 'inversions.' + dottedName)
|
||||
}
|
||||
renderInversions() {
|
||||
let { stepProps: { name, inversion} } = this.props
|
||||
let { stepProps: { dottedName, inversion} } = this.props
|
||||
if (!inversion) return null
|
||||
|
||||
if (inversion.inversions.length === 1) return (
|
||||
<span>{inversion.inversions[0].title || inversion.inversions[0].name}</span>
|
||||
<span>{inversion.inversions[0].title || inversion.inversions[0].dottedName}</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<Field
|
||||
component="select"
|
||||
name={'inversions.' + name}
|
||||
name={'inversions.' + dottedName}
|
||||
>
|
||||
{inversion.inversions.map(({ name, title, dottedName }) => (
|
||||
<option key={dottedName} value={dottedName}>
|
||||
|
@ -107,7 +107,7 @@ export default class Input extends Component {
|
|||
)
|
||||
}
|
||||
renderSuggestions(themeColours) {
|
||||
let { setFormValue, submit, suggestions, input, name, inverted } = this.props.stepProps
|
||||
let { setFormValue, submit, suggestions, inverted } = this.props.stepProps
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import React, {Component} from 'react'
|
||||
import {FormDecorator} from './FormDecorator'
|
||||
import {answer, answered} from './userAnswerButtonStyle'
|
||||
import React, { Component } from 'react'
|
||||
import { FormDecorator } from './FormDecorator'
|
||||
import { answer, answered } from './userAnswerButtonStyle'
|
||||
import HoverDecorator from '../HoverDecorator'
|
||||
import Explicable from './Explicable'
|
||||
import R from 'ramda'
|
||||
|
||||
|
||||
/* Ceci est une saisie de type "radio" : l'utilisateur choisit une réponse dans une liste, ou une liste de listes.
|
||||
Les données @choices sont un arbre de type:
|
||||
- nom: motif CDD # La racine, unique, qui formera la Question. Ses enfants sont les choix possibles
|
||||
|
@ -25,9 +24,7 @@ import R from 'ramda'
|
|||
let dottedNameToObject = R.pipe(
|
||||
R.split(' . '),
|
||||
R.reverse,
|
||||
R.reduce((memo, next) => (
|
||||
{[next]: memo}
|
||||
), 'oui')
|
||||
R.reduce((memo, next) => ({ [next]: memo }), 'oui')
|
||||
)
|
||||
|
||||
// FormDecorator permet de factoriser du code partagé par les différents types de saisie,
|
||||
|
@ -35,83 +32,121 @@ let dottedNameToObject = R.pipe(
|
|||
@FormDecorator('question')
|
||||
export default class Question extends Component {
|
||||
render() {
|
||||
let {
|
||||
stepProps: {choices},
|
||||
} = this.props
|
||||
let { stepProps: { choices } } = this.props
|
||||
|
||||
if (R.is(Array)(choices))
|
||||
return this.renderBinaryQuestion()
|
||||
else
|
||||
return this.renderChildren(choices)
|
||||
if (R.is(Array)(choices)) return this.renderBinaryQuestion()
|
||||
else return this.renderChildren(choices)
|
||||
}
|
||||
renderBinaryQuestion(){
|
||||
renderBinaryQuestion() {
|
||||
let {
|
||||
input, // vient de redux-form
|
||||
stepProps: {submit, choices},
|
||||
stepProps: { submit, choices },
|
||||
themeColours
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{ choices.map(({value, label}) =>
|
||||
<RadioLabel key={value} {...{value, label, input, submit, themeColours}}/>
|
||||
)}
|
||||
{choices.map(({ value, label }) => (
|
||||
<RadioLabel
|
||||
key={value}
|
||||
{...{ value, label, input, submit, themeColours }}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
renderChildren(choices) {
|
||||
let {
|
||||
input, // vient de redux-form
|
||||
stepProps,
|
||||
themeColours
|
||||
} = this.props,
|
||||
{name} = input,
|
||||
{submit} = stepProps,
|
||||
input, // vient de redux-form
|
||||
stepProps,
|
||||
themeColours
|
||||
} = this.props,
|
||||
{ name } = input,
|
||||
{ submit } = stepProps,
|
||||
// seront stockées ainsi dans le state :
|
||||
// [parent object path]: dotted name relative to parent
|
||||
relativeDottedName = radioDottedName =>
|
||||
radioDottedName.split(name + ' . ')[1]
|
||||
|
||||
return (<ul>
|
||||
{choices.canGiveUp &&
|
||||
<li key='aucun' className="variantLeaf aucun">
|
||||
<RadioLabel {...{value: 'non', label: 'Aucun', input, submit, themeColours, dottedName: null}}/>
|
||||
</li>
|
||||
}
|
||||
{ choices.children && choices.children.map( ({name, title, dottedName, children}) =>
|
||||
children ?
|
||||
<li key={name} className="variant">
|
||||
<div>{title}</div>
|
||||
{this.renderChildren({children})}
|
||||
return (
|
||||
<ul>
|
||||
{choices.canGiveUp && (
|
||||
<li key="aucun" className="variantLeaf aucun">
|
||||
<RadioLabel
|
||||
{...{
|
||||
value: 'non',
|
||||
label: 'Aucun',
|
||||
input,
|
||||
submit,
|
||||
themeColours,
|
||||
dottedName: null
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
: <li key={name} className="variantLeaf">
|
||||
<RadioLabel {...{value: relativeDottedName(dottedName), label: title, dottedName, input, submit, themeColours}}/>
|
||||
</li>
|
||||
)}
|
||||
</ul>)
|
||||
)}
|
||||
{choices.children &&
|
||||
choices.children.map(
|
||||
({ name, title, dottedName, children }) =>
|
||||
children ? (
|
||||
<li key={name} className="variant">
|
||||
<div>{title}</div>
|
||||
{this.renderChildren({ children })}
|
||||
</li>
|
||||
) : (
|
||||
<li key={name} className="variantLeaf">
|
||||
<RadioLabel
|
||||
{...{
|
||||
value: relativeDottedName(dottedName),
|
||||
label: title,
|
||||
dottedName,
|
||||
input,
|
||||
submit,
|
||||
themeColours
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@HoverDecorator
|
||||
class RadioLabel extends Component {
|
||||
let RadioLabel = props => (
|
||||
<Explicable dottedName={props.dottedName}>
|
||||
<RadioLabelContent {...props} />
|
||||
</Explicable>
|
||||
)
|
||||
|
||||
@HoverDecorator
|
||||
class RadioLabelContent extends Component {
|
||||
render() {
|
||||
let {value, label, input, submit, hover, themeColours, dottedName} = this.props,
|
||||
let {
|
||||
value,
|
||||
label,
|
||||
input,
|
||||
submit,
|
||||
hover,
|
||||
themeColours,
|
||||
} = this.props,
|
||||
// value = R.when(R.is(Object), R.prop('value'))(choice),
|
||||
labelStyle =
|
||||
Object.assign(
|
||||
(value === input.value || hover) ? answered(themeColours) : answer(themeColours),
|
||||
value === '_' ? {fontWeight: 'bold'} : null
|
||||
)
|
||||
labelStyle = Object.assign(
|
||||
value === input.value || hover
|
||||
? answered(themeColours)
|
||||
: answer(themeColours),
|
||||
value === '_' ? { fontWeight: 'bold' } : null
|
||||
)
|
||||
|
||||
return (
|
||||
<label key={value}
|
||||
style={labelStyle}
|
||||
className="radio" >
|
||||
<label key={value} style={labelStyle} className="radio">
|
||||
{label}
|
||||
<input
|
||||
type="radio" {...input} onClick={submit}
|
||||
value={value} checked={value === input.value ? 'checked' : ''} />
|
||||
<Explicable dottedName={dottedName} label={label}/>
|
||||
type="radio"
|
||||
{...input}
|
||||
onClick={submit}
|
||||
value={value}
|
||||
checked={value === input.value ? 'checked' : ''}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import React from 'react'
|
||||
import R from 'ramda'
|
||||
|
||||
import Explicable from 'Components/conversation/Explicable'
|
||||
import Question from 'Components/conversation/Question'
|
||||
import Input from 'Components/conversation/Input'
|
||||
import Select from 'Components/conversation/select/Select'
|
||||
|
@ -51,32 +49,6 @@ export let nextSteps = (situationGate, flatRules, analysis) => {
|
|||
return R.map(R.head, sortedPairs)
|
||||
}
|
||||
|
||||
export let constructStepMeta = ({
|
||||
title,
|
||||
question,
|
||||
subquestion,
|
||||
dottedName,
|
||||
name
|
||||
}) => ({
|
||||
// name: dottedName.split(' . ').join('.'),
|
||||
name: dottedName,
|
||||
// <Explicable/> ajoutera une aide au clic sur un icône [?]
|
||||
// Son texte est la question s'il y en a une à poser. Sinon on prend le titre.
|
||||
question: (
|
||||
<Explicable
|
||||
label={question || name}
|
||||
dottedName={dottedName}
|
||||
lightBackground={true}
|
||||
/>
|
||||
),
|
||||
title,
|
||||
subquestion,
|
||||
|
||||
// Legacy properties :
|
||||
|
||||
visible: true
|
||||
// helpText: 'Voila un peu d\'aide poto'
|
||||
})
|
||||
|
||||
let isVariant = R.path(['formule', 'une possibilité'])
|
||||
|
||||
|
@ -153,10 +125,9 @@ export let makeQuestion = (flatRules, targetNames) => dottedName => {
|
|||
choices: buildVariantTree(flatRules, dottedName)
|
||||
})
|
||||
|
||||
let common = constructStepMeta(rule)
|
||||
|
||||
return Object.assign(
|
||||
common,
|
||||
rule,
|
||||
isVariant(rule)
|
||||
? multiChoiceQuestion(rule)
|
||||
: rule.format == null
|
||||
|
|
Loading…
Reference in New Issue