From d285b56ccf7404da707eaa2be183c76245524784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Tue, 8 Mar 2022 15:00:08 +0100 Subject: [PATCH] Ajout du selecteur radio par block --- .../règles/exonération-covid.yaml | 2 + .../components/conversation/ChoicesInput.tsx | 35 ++++++++-- .../components/conversation/RuleInput.tsx | 28 +++++++- .../design-system/field/Radio/Radio.tsx | 65 +++++++++++++++++-- .../Simulateurs/ExonerationCovid/index.tsx | 1 + 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/exoneration-covid/règles/exonération-covid.yaml b/exoneration-covid/règles/exonération-covid.yaml index f8d2a0096..ea7688ba3 100644 --- a/exoneration-covid/règles/exonération-covid.yaml +++ b/exoneration-covid/règles/exonération-covid.yaml @@ -3,6 +3,8 @@ secteur: une possibilité: choix obligatoire: oui possibilités: [S1, S1bis, S2] + metadata: + component: ToggleRadioBlock secteur . S1: valeur: secteur = 'S1' diff --git a/site/source/components/conversation/ChoicesInput.tsx b/site/source/components/conversation/ChoicesInput.tsx index 1ab8b0469..23c8636d9 100644 --- a/site/source/components/conversation/ChoicesInput.tsx +++ b/site/source/components/conversation/ChoicesInput.tsx @@ -3,6 +3,7 @@ import Emoji from '@/components/utils/Emoji' import { Markdown } from '@/components/utils/markdown' import ButtonHelp from '@/design-system/buttons/ButtonHelp' import { Radio, RadioGroup, ToggleGroup } from '@/design-system/field' +import { RadioBlock } from '@/design-system/field/Radio/Radio' import { Spacing } from '@/design-system/layout' import { H4 } from '@/design-system/typography/heading' import { DottedName } from 'modele-social' @@ -48,20 +49,28 @@ export const HiddenOptionContext = createContext>([]) export function MultipleAnswerInput({ choice, + type = 'radio', + inline, ...props -}: { choice: Choice } & InputProps) { +}: { + choice: Choice + type?: 'radio' | 'toggle' + inline?: boolean +} & InputProps) { // seront stockées ainsi dans le state : // [parent object path]: dotted fieldName relative to parent const { handleChange, defaultValue, currentSelection } = useSelection(props) + const Component = type === 'toggle' ? ToggleGroup : RadioGroup return ( - + - + ) } @@ -69,10 +78,12 @@ function RadioChoice({ choice, autoFocus, rootDottedName, + inline, }: { choice: Choice autoFocus?: string rootDottedName: Names + inline?: boolean }) { const relativeDottedName = (radioDottedName: string) => radioDottedName.split(rootDottedName + ' . ')[1] @@ -96,10 +107,14 @@ function RadioChoice({

{node.title}

- + - ) : ( + ) : inline ? ( ({ )} + ) : ( + )} ))} diff --git a/site/source/components/conversation/RuleInput.tsx b/site/source/components/conversation/RuleInput.tsx index c1ea8bbd9..6c291fa1a 100644 --- a/site/source/components/conversation/RuleInput.tsx +++ b/site/source/components/conversation/RuleInput.tsx @@ -9,6 +9,7 @@ import Engine, { Evaluation, PublicodesExpression, reduceAST, + Rule, RuleNode, } from 'publicodes' import React, { useContext } from 'react' @@ -59,11 +60,22 @@ export const binaryQuestion = [ { value: 'non', label: 'Non' }, ] as const +interface RuleWithMetadata extends Rule { + metadata: T +} + +const isMetadata = >( + rule: Rule +): rule is T => 'metadata' in rule + // This function takes the unknown rule and finds which React component should // be displayed to get a user input through successive if statements // That's not great, but we won't invest more time until we have more diverse // input components and a better type system. -export default function RuleInput({ +export default function RuleInput< + Names extends string = DottedName, + Metadata extends { component?: string } = { component?: string } +>({ dottedName, onChange, showSuggestions = true, @@ -90,11 +102,25 @@ export default function RuleInput({ suggestions: showSuggestions ? rule.suggestions : {}, ...props, } + + if ( + isMetadata>(rule.rawNode) && + rule.rawNode.metadata.component === 'ToggleRadioBlock' + ) { + return ( + + ) + } if (getVariant(engine.getRule(dottedName))) { return ( ) } diff --git a/site/source/design-system/field/Radio/Radio.tsx b/site/source/design-system/field/Radio/Radio.tsx index 6bd1aa28e..823170634 100644 --- a/site/source/design-system/field/Radio/Radio.tsx +++ b/site/source/design-system/field/Radio/Radio.tsx @@ -5,17 +5,25 @@ import { FocusStyle } from '@/design-system/global-style' import { Body } from '@/design-system/typography/paragraphs' import React, { createContext, useContext, useRef } from 'react' import styled, { css } from 'styled-components' +import Emoji from '@/components/utils/Emoji' +import { Strong } from '@/design-system/typography' +import { Markdown } from '@/components/utils/markdown' const RadioContext = createContext(null) -export function Radio(props: AriaRadioProps) { - const { children } = props +export function Radio( + props: AriaRadioProps & { + LabelBodyAs?: Parameters['0']['as'] + } +) { + const { LabelBodyAs: bodyType, ...ariaProps } = props + const { children } = ariaProps const state = useContext(RadioContext) if (!state) { throw new Error("Radio can't be instanciated outside a RadioContext") } const ref = useRef(null) - const { inputProps } = useRadio(props, state, ref) + const { inputProps } = useRadio(ariaProps, state, ref) return ( ) @@ -99,6 +107,54 @@ const VisibleRadio = styled.div` } ` +const RadioLabel = styled.p` + margin: ${({ theme }) => theme.spacings.sm} 0; + font-style: italic; +` + +const RadioWrapper = styled.span` + flex: 0 0 100%; + + ${VisibleRadio} { + width: 100%; + border-radius: var(--radius) !important; + margin-bottom: ${({ theme }) => theme.spacings.xs} !important; + } + + ${RadioButton} { + align-self: baseline; + margin-top: 0.2rem; + } +` + +export function RadioBlock({ + value, + title, + emoji, + description, + autoFocus, +}: RadioGroupProps & { + value: string + title: string + emoji?: string + description?: string + autoFocus?: boolean +}) { + return ( + + + + {title} {emoji && } + + + {description && ( + {description ?? ''} + )} + + + ) +} + const LabelBody = styled(Body)` margin: ${({ theme }) => theme.spacings.xs} 0px; margin-left: ${({ theme }) => theme.spacings.xxs}; @@ -149,6 +205,7 @@ export function ToggleGroup( const ToggleGroupContainer = styled.div<{ hideRadio: boolean }>` --radius: 0.25rem; display: inline-flex; + flex-wrap: wrap; ${VisibleRadio} { position: relative; diff --git a/site/source/pages/Simulateurs/ExonerationCovid/index.tsx b/site/source/pages/Simulateurs/ExonerationCovid/index.tsx index 60f1f21c7..65a2128a7 100644 --- a/site/source/pages/Simulateurs/ExonerationCovid/index.tsx +++ b/site/source/pages/Simulateurs/ExonerationCovid/index.tsx @@ -24,6 +24,7 @@ export default function ExonérationCovid() { return ( <> +

{covidEngine.getRule('secteur').rawNode.question}

updateSituation('secteur', value)}