-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #355 from wwnorton/multiple-choice-pattern
Multiple choice pattern
- Loading branch information
Showing
30 changed files
with
639 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
@use '../../util'; | ||
|
||
@mixin style { | ||
@include util.declare('multiple-choice') { | ||
.nds-multiple-choice { | ||
&__intro { | ||
margin-bottom: 0.25rem; | ||
} | ||
|
||
&__stem { | ||
margin-bottom: 0.25rem; | ||
font-weight: bold; | ||
} | ||
|
||
&__instructions { | ||
margin-bottom: 2rem; | ||
font-size: var(--nds-font-size-xs); | ||
} | ||
|
||
&__choice { | ||
& > input, | ||
& > label { | ||
position: absolute; | ||
|
||
// FIXME: When adding a div as a children the hidden controls appear | ||
// we need to fix this in the ChoiceField component | ||
display: block; | ||
width: 0; | ||
height: 0; | ||
visibility: hidden; | ||
} | ||
|
||
& .nds-field__label { | ||
display: flex; | ||
gap: 1rem; | ||
} | ||
} | ||
|
||
&__feedback { | ||
min-width: 5rem; | ||
} | ||
|
||
&__choice-label { | ||
font-weight: bold; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export { FeedbackModal } from './FeedbackModal'; | ||
export type { FeedbackModalProps } from './types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,5 @@ export * from './providers'; | |
|
||
// Public utilities | ||
export * from './utilities'; | ||
|
||
export * from './patterns'; |
28 changes: 28 additions & 0 deletions
28
packages/react/src/patterns/MultipleChoice/AnswerChoice.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import React, { useCallback } from 'react'; | ||
import { Radio } from '../../components/Radio'; | ||
import { styles } from './styles'; | ||
import { OnSelectInput } from './types'; | ||
|
||
interface AnswerChoiceProps { | ||
label?: string; | ||
checked?: boolean; | ||
value?: number; | ||
onSelect?: (input: OnSelectInput) => void; | ||
children: React.ReactNode; | ||
} | ||
|
||
export const AnswerChoice = ({ label, children, onSelect, value, checked }: AnswerChoiceProps) => { | ||
const onChange = useCallback(() => { | ||
if (value === undefined || !label || !onSelect) { | ||
return; | ||
} | ||
onSelect({ index: value, label }); | ||
}, [onSelect, value, label]); | ||
|
||
return ( | ||
<Radio onChange={onChange} checked={checked}> | ||
<span className={styles.choiceLabel}>{label}. </span> | ||
<span>{children}</span> | ||
</Radio> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import React from 'react'; | ||
import { styles } from './styles'; | ||
|
||
export const Instructions = ({ children }: React.PropsWithChildren<unknown>) => ( | ||
<div className={styles.instructions}>{children}</div> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import React from 'react'; | ||
import { styles } from './styles'; | ||
|
||
export const Intro = ({ children }: React.PropsWithChildren<unknown>) => ( | ||
<div className={styles.intro}>{children}</div> | ||
); |
92 changes: 92 additions & 0 deletions
92
packages/react/src/patterns/MultipleChoice/MultipleChoice.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import React from 'react'; | ||
import { RadioGroup } from '../../components/Radio'; | ||
import { ResponseIndicator } from '../../components/ResponseIndicator'; | ||
import { AnswerChoice } from './AnswerChoice'; | ||
import { Instructions } from './Instructions'; | ||
import { Intro } from './Intro'; | ||
import { Stem } from './Stem'; | ||
import { styles } from './styles'; | ||
import { LabelType, OnSelectInput } from './types'; | ||
import { resolveLabelType } from './utils'; | ||
|
||
export interface MultipleChoiceProps { | ||
stem: string | React.ReactElement<void, typeof Stem>; | ||
intro?: string | React.ReactElement<void, typeof Intro>; | ||
instructions?: string | React.ReactElement<void, typeof Instructions>; | ||
choices: string[]; | ||
/** | ||
* @default 'lower-alpha' | ||
*/ | ||
labelType?: LabelType; | ||
status: 'correct' | 'incorrect' | 'unanswered'; | ||
onSelect?: (input: OnSelectInput) => void; | ||
selected?: number; | ||
// TODO: support styling pieces | ||
} | ||
|
||
export const MultipleChoice = ({ | ||
stem, | ||
intro, | ||
instructions, | ||
labelType = 'lower-alpha', | ||
choices, | ||
status, | ||
onSelect, | ||
selected, | ||
}: MultipleChoiceProps) => { | ||
const introElement = typeof intro === 'string' ? <Intro>{intro}</Intro> : intro; | ||
const stemElement = typeof stem === 'string' ? <Stem>{stem}</Stem> : stem; | ||
const instructionsElement = | ||
typeof instructions === 'string' ? <Instructions>{instructions}</Instructions> : instructions; | ||
|
||
return ( | ||
<div> | ||
{introElement} | ||
{stemElement} | ||
{instructionsElement} | ||
<div> | ||
<RadioGroup label={undefined}> | ||
{choices.map((choice, index) => { | ||
const isCorrect = status === 'correct' && index === selected; | ||
const isIncorrect = status === 'incorrect' && index === selected; | ||
const label = resolveLabelType(labelType, index); | ||
|
||
let feedback: React.ReactNode = null; | ||
if (isCorrect) { | ||
feedback = ( | ||
<ResponseIndicator className={styles.feedback} withIcon={false} variant="correct" /> | ||
); | ||
} else if (isIncorrect) { | ||
feedback = ( | ||
<ResponseIndicator | ||
className={styles.feedback} | ||
withIcon={false} | ||
variant="incorrect" | ||
/> | ||
); | ||
} else { | ||
feedback = <div className={styles.feedback} />; | ||
} | ||
|
||
const checked = index === selected; | ||
|
||
// TODO: think a way to allow control of response indicator layout | ||
// | ||
// TODO: use grid to solve issues with incorrect response | ||
// indicator | ||
return ( | ||
<div key={label} className={styles.choice}> | ||
{feedback} | ||
<div> | ||
<AnswerChoice label={label} checked={checked} value={index} onSelect={onSelect}> | ||
{choice} | ||
</AnswerChoice> | ||
</div> | ||
</div> | ||
); | ||
})} | ||
</RadioGroup> | ||
</div> | ||
</div> | ||
); | ||
}; |
43 changes: 43 additions & 0 deletions
43
packages/react/src/patterns/MultipleChoice/PatternExample.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import React, { useCallback } from 'react'; | ||
import { Button } from '../../components/Button'; | ||
import { FeedbackModal } from '../../components/FeedbackModal'; | ||
import { MultipleChoice } from './MultipleChoice'; | ||
import { useMultipleChoice } from './useMultipleChoice'; | ||
|
||
interface PatternExampleProps { | ||
choices: string[]; | ||
} | ||
|
||
export const PatternExample = ({ choices }: PatternExampleProps) => { | ||
const { questionState, setStatus, modalState } = useMultipleChoice(choices); | ||
|
||
const handleSubmit = useCallback( | ||
(event: React.FormEvent<HTMLFormElement>) => { | ||
event.preventDefault(); | ||
|
||
if (questionState.selected === 1) { | ||
setStatus('correct'); | ||
} else { | ||
setStatus('incorrect'); | ||
} | ||
}, | ||
[setStatus, questionState.selected], | ||
); | ||
|
||
return ( | ||
<form onSubmit={handleSubmit}> | ||
<MultipleChoice | ||
intro="Three people are present when a pregnant person suddenly goes into labor and gives birth in a bank lobby." | ||
stem="Which of the people is likely to best remember the event afterward?" | ||
instructions="Select one that applies. You have 2 attempts remaining." | ||
{...questionState} | ||
/> | ||
<FeedbackModal {...modalState} /> | ||
<div> | ||
<Button type="submit" variant="solid" color="primary"> | ||
Submit | ||
</Button> | ||
</div> | ||
</form> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import React from 'react'; | ||
import { styles } from './styles'; | ||
|
||
export const Stem = ({ children }: React.PropsWithChildren<unknown>) => ( | ||
<div className={styles.stem}>{children}</div> | ||
); |
Oops, something went wrong.