diff --git a/.github/pr-assets/7803-after-dark.svg b/.github/pr-assets/7803-after-dark.svg new file mode 100644 index 000000000000..10473a0c9c13 --- /dev/null +++ b/.github/pr-assets/7803-after-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.github/pr-assets/7803-after-light.svg b/.github/pr-assets/7803-after-light.svg new file mode 100644 index 000000000000..a04da67be9ed --- /dev/null +++ b/.github/pr-assets/7803-after-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.github/pr-assets/7803-before-dark.svg b/.github/pr-assets/7803-before-dark.svg new file mode 100644 index 000000000000..37bfdec8b44a --- /dev/null +++ b/.github/pr-assets/7803-before-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.github/pr-assets/7803-before-light.svg b/.github/pr-assets/7803-before-light.svg new file mode 100644 index 000000000000..58826b81ed71 --- /dev/null +++ b/.github/pr-assets/7803-before-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/mobile/src/features/threads/PendingUserInputCard.tsx b/apps/mobile/src/features/threads/PendingUserInputCard.tsx index ddb625f9b219..158021395fa9 100644 --- a/apps/mobile/src/features/threads/PendingUserInputCard.tsx +++ b/apps/mobile/src/features/threads/PendingUserInputCard.tsx @@ -88,6 +88,7 @@ const CARD_LAYOUT_TRANSITION = LinearTransition.duration(200); export function PendingUserInputCard(props: PendingUserInputCardProps) { const iconSubtle = useThemeColor("--color-icon-subtle"); + const checkboxTint = useThemeColor("--color-primary-foreground"); const questionCount = props.pendingUserInput.questions.length; const cardCoverage = props.cardCoverage; @@ -263,8 +264,17 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { return ( + {/* A pill that can stay lit alongside its neighbours needs + to say so before it is tapped: the box marks the answer + as additive rather than exclusive. */} + {question.multiSelect ? ( + + {selected ? ( + + ) : null} + + ) : null} = {}, +) { return renderToStaticMarkup( {}} onAdvance={() => {}} @@ -58,4 +78,26 @@ describe("ComposerPendingUserInputPanel", () => { expect(markup).toContain("Incremental"); expect(markup).toContain("Big bang"); }); + + it("announces multi-select options as checkboxes and keeps their shortcuts", () => { + const markup = renderPanel(multiSelectPrompt, { + "question-2": { selectedOptionLabels: ["Web"] }, + }); + + const optionButtons = markup.match(/]*role="checkbox"[^>]*>/g) ?? []; + expect(optionButtons).toHaveLength(2); + expect(optionButtons[0]).toContain('aria-checked="true"'); + expect(optionButtons[1]).toContain('aria-checked="false"'); + // The trailing number shortcut survives selection, so every option stays + // reachable from the keyboard while the answer is being assembled. + expect(markup).toContain(" { + const markup = renderPanel(prompt, { "question-1": { selectedOptionLabels: ["Incremental"] } }); + + expect(markup).not.toContain('role="checkbox"'); + expect(markup).not.toContain("Select one or more options."); + }); }); diff --git a/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx b/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx index d50a0c264549..8a7b9fd79526 100644 --- a/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx +++ b/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx @@ -242,13 +242,42 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard( ); const content = ( <> + {/* Multi-select is the only mode where more than one option + can stay lit, so it is the only one that gets a box: a + leading checkbox reads as "toggle me", where the trailing + check of a single-select reads as "answered". */} + {activeQuestion.multiSelect ? ( + + ) : null}
{option.label} {option.description && option.description !== option.label ? ( {option.description} ) : null}
- {isSelected ? ( + {/* Multi-select keeps every number shortcut on screen while + options are being picked: the checkbox already carries the + state, and the keys stay live for the whole list. */} + {isSelected && !activeQuestion.multiSelect ? ( ) : shortcutKey !== null ? ( { handleOptionSelection(activeQuestion.id, option.label);