🎨 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
mama 2017-11-24 12:35:10 +01:00
parent ead20c9eb3
commit 3158bd1263
7 changed files with 166 additions and 154 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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