Ajout du selecteur radio par block
parent
4eff86a18a
commit
d285b56ccf
|
@ -3,6 +3,8 @@ secteur:
|
|||
une possibilité:
|
||||
choix obligatoire: oui
|
||||
possibilités: [S1, S1bis, S2]
|
||||
metadata:
|
||||
component: ToggleRadioBlock
|
||||
|
||||
secteur . S1:
|
||||
valeur: secteur = 'S1'
|
||||
|
|
|
@ -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<Array<DottedName>>([])
|
|||
|
||||
export function MultipleAnswerInput<Names extends string = DottedName>({
|
||||
choice,
|
||||
type = 'radio',
|
||||
inline,
|
||||
...props
|
||||
}: { choice: Choice } & InputProps<Names>) {
|
||||
}: {
|
||||
choice: Choice
|
||||
type?: 'radio' | 'toggle'
|
||||
inline?: boolean
|
||||
} & InputProps<Names>) {
|
||||
// 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 (
|
||||
<RadioGroup onChange={handleChange} value={currentSelection ?? undefined}>
|
||||
<Component onChange={handleChange} value={currentSelection ?? undefined}>
|
||||
<RadioChoice
|
||||
autoFocus={defaultValue}
|
||||
choice={choice}
|
||||
rootDottedName={props.dottedName}
|
||||
inline={inline}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -69,10 +78,12 @@ function RadioChoice<Names extends string = DottedName>({
|
|||
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<Names extends string = DottedName>({
|
|||
<H4 id={node.dottedName + '-legend'}>{node.title}</H4>
|
||||
<Spacing lg />
|
||||
<StyledSubRadioGroup>
|
||||
<RadioChoice choice={node} rootDottedName={rootDottedName} />
|
||||
<RadioChoice
|
||||
inline={inline}
|
||||
choice={node}
|
||||
rootDottedName={rootDottedName}
|
||||
/>
|
||||
</StyledSubRadioGroup>
|
||||
</div>
|
||||
) : (
|
||||
) : inline ? (
|
||||
<span>
|
||||
<Radio
|
||||
autoFocus={
|
||||
|
@ -116,6 +131,16 @@ function RadioChoice<Names extends string = DottedName>({
|
|||
</ButtonHelp>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<RadioBlock
|
||||
autoFocus={
|
||||
autoFocus === `'${relativeDottedName(node.dottedName)}'`
|
||||
}
|
||||
value={`'${relativeDottedName(node.dottedName)}'`}
|
||||
title={node.title}
|
||||
emoji={node.rawNode.icônes}
|
||||
description={node.rawNode.description}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
|
|
|
@ -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<T> extends Rule {
|
||||
metadata: T
|
||||
}
|
||||
|
||||
const isMetadata = <T extends RuleWithMetadata<unknown>>(
|
||||
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<Names extends string = DottedName>({
|
||||
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<Names extends string = DottedName>({
|
|||
suggestions: showSuggestions ? rule.suggestions : {},
|
||||
...props,
|
||||
}
|
||||
|
||||
if (
|
||||
isMetadata<RuleWithMetadata<Metadata>>(rule.rawNode) &&
|
||||
rule.rawNode.metadata.component === 'ToggleRadioBlock'
|
||||
) {
|
||||
return (
|
||||
<MultipleAnswerInput
|
||||
{...commonProps}
|
||||
choice={buildVariantTree(engine, dottedName)}
|
||||
type="toggle"
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (getVariant(engine.getRule(dottedName))) {
|
||||
return (
|
||||
<MultipleAnswerInput
|
||||
{...commonProps}
|
||||
choice={buildVariantTree(engine, dottedName)}
|
||||
inline
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<RadioGroupState | null>(null)
|
||||
|
||||
export function Radio(props: AriaRadioProps) {
|
||||
const { children } = props
|
||||
export function Radio(
|
||||
props: AriaRadioProps & {
|
||||
LabelBodyAs?: Parameters<typeof LabelBody>['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 (
|
||||
<label>
|
||||
|
@ -25,7 +33,7 @@ export function Radio(props: AriaRadioProps) {
|
|||
<OutsideCircle />
|
||||
<InsideCircle />
|
||||
</RadioButton>
|
||||
<LabelBody>{children}</LabelBody>
|
||||
<LabelBody as={bodyType}>{children}</LabelBody>
|
||||
</VisibleRadio>
|
||||
</label>
|
||||
)
|
||||
|
@ -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 (
|
||||
<RadioWrapper>
|
||||
<Radio autoFocus={autoFocus} value={value} LabelBodyAs={'div'}>
|
||||
<Strong>
|
||||
{title} {emoji && <Emoji emoji={emoji} />}
|
||||
</Strong>
|
||||
|
||||
{description && (
|
||||
<Markdown as={RadioLabel}>{description ?? ''}</Markdown>
|
||||
)}
|
||||
</Radio>
|
||||
</RadioWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -24,6 +24,7 @@ export default function ExonérationCovid() {
|
|||
return (
|
||||
<>
|
||||
<EngineProvider value={covidEngine}>
|
||||
<H3>{covidEngine.getRule('secteur').rawNode.question}</H3>
|
||||
<RuleInput
|
||||
dottedName={'secteur'}
|
||||
onChange={(value) => updateSituation('secteur', value)}
|
||||
|
|
Loading…
Reference in New Issue