FN-7613: fix confirm Yes/No button selected-state visibility
Fix confirm Yes/No selected-state visibility in chat questions and add aria-pressed accessibility support. - Strengthen CSS specificity for .chat-question-response__confirm--selected so the selected style beats the global .btn/.btn:hover rules, using token-driven --cta-* colors for light/dark themes. - Add dedicated hover and focus-visible states for the selected confirm button. - Add aria-pressed to the Yes/No confirm buttons so assistive tech reflects the same selected state. - Add regression tests covering the selected visual/aria state. - Add changeset (patch) documenting the fix. Files changed: .changeset/fn-7613-confirm-selected-state.md | 7 ++++ .../app/components/ChatQuestionResponse.css | 40 ++++++++++++++++++++-- .../app/components/ChatQuestionResponse.tsx | 8 +++++ .../__tests__/ChatQuestionResponse.test.tsx | 26 ++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-7613 Fusion-Task-Lineage: d648044e-ab30-4542-9402-a7bfbfe6563e Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7613-confirm-selected-state.md
Normal file
7
.changeset/fn-7613-confirm-selected-state.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Yes/No chat question buttons now show a clear selected state after clicking.
|
||||||
|
category: fix
|
||||||
|
dev: Strengthened `.chat-question-response__confirm--selected` CSS specificity (compound selector + dedicated hover/focus-visible rules) so the CTA-token selected fill/border beats the global `.btn`/`.btn:hover` rules; added `aria-pressed` and a regression test asserting the selected class toggles correctly between Yes/No.
|
||||||
@@ -102,12 +102,48 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-question-response__option:hover,
|
.chat-question-response__option:hover,
|
||||||
.chat-question-response__option--selected,
|
.chat-question-response__option--selected {
|
||||||
.chat-question-response__confirm--selected {
|
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
background: color-mix(in srgb, var(--accent) 12%, var(--card));
|
background: color-mix(in srgb, var(--accent) 12%, var(--card));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:ChatQuestionResponse 2026-07-05-00:00:
|
||||||
|
The confirm Yes/No buttons also carry the global `.btn` class, whose base
|
||||||
|
background and (especially) `.btn:hover` rule (specificity 0,2,0) visually
|
||||||
|
swamped the old single-class `.chat-question-response__confirm--selected`
|
||||||
|
rule (0,1,0), so a clicked answer never looked selected. Use a compound
|
||||||
|
selector (`.chat-question-response__confirm.chat-question-response__confirm--selected`,
|
||||||
|
specificity 0,2,0) plus a dedicated `:hover` override so the selected state
|
||||||
|
beats `.btn`/`.btn:hover` in every interaction state, and keep everything
|
||||||
|
token-driven (`--cta-*` / `--accent` via color-mix) so light/dark themes and
|
||||||
|
the mobile column layout stay correct without hardcoded colors.
|
||||||
|
*/
|
||||||
|
.chat-question-response__confirm {
|
||||||
|
border-color: var(--border);
|
||||||
|
background: var(--card);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-question-response__confirm.chat-question-response__confirm--selected {
|
||||||
|
border-color: var(--cta-border);
|
||||||
|
background: color-mix(in srgb, var(--cta-bg) 55%, var(--card));
|
||||||
|
color: var(--cta-text);
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: var(--cta-glow), inset 0 0 0 var(--btn-border-width) var(--cta-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-question-response__confirm.chat-question-response__confirm--selected:hover {
|
||||||
|
background: color-mix(in srgb, var(--cta-bg-hover) 60%, var(--card));
|
||||||
|
border-color: var(--cta-border-hover);
|
||||||
|
color: var(--cta-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-question-response__confirm.chat-question-response__confirm--selected:focus-visible {
|
||||||
|
box-shadow: var(--focus-ring-strong), var(--cta-glow);
|
||||||
|
}
|
||||||
|
|
||||||
.chat-question-response__option input {
|
.chat-question-response__option input {
|
||||||
margin: calc(var(--space-xs) / 2) 0 0;
|
margin: calc(var(--space-xs) / 2) 0 0;
|
||||||
accent-color: var(--accent);
|
accent-color: var(--accent);
|
||||||
|
|||||||
@@ -168,11 +168,18 @@ function QuestionControls({
|
|||||||
if (question.type === "confirm") {
|
if (question.type === "confirm") {
|
||||||
return (
|
return (
|
||||||
<div className="chat-question-response__confirm-group" role="group" aria-label={question.question}>
|
<div className="chat-question-response__confirm-group" role="group" aria-label={question.question}>
|
||||||
|
{/*
|
||||||
|
FNXC:ChatQuestionResponse 2026-07-05-00:00:
|
||||||
|
Expose the confirm selection to assistive tech via aria-pressed so
|
||||||
|
screen reader users get the same clear selected/unselected signal
|
||||||
|
the strengthened CSS now provides visually.
|
||||||
|
*/}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn chat-question-response__confirm${value === true ? " chat-question-response__confirm--selected" : ""}`}
|
className={`btn chat-question-response__confirm${value === true ? " chat-question-response__confirm--selected" : ""}`}
|
||||||
data-testid={`chat-question-response-option-${question.id}-yes`}
|
data-testid={`chat-question-response-option-${question.id}-yes`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
aria-pressed={value === true}
|
||||||
onClick={() => setQuestionAnswer(question.id, true)}
|
onClick={() => setQuestionAnswer(question.id, true)}
|
||||||
>
|
>
|
||||||
{t("chat.questionConfirmYes", "Yes")}
|
{t("chat.questionConfirmYes", "Yes")}
|
||||||
@@ -182,6 +189,7 @@ function QuestionControls({
|
|||||||
className={`btn chat-question-response__confirm${value === false ? " chat-question-response__confirm--selected" : ""}`}
|
className={`btn chat-question-response__confirm${value === false ? " chat-question-response__confirm--selected" : ""}`}
|
||||||
data-testid={`chat-question-response-option-${question.id}-no`}
|
data-testid={`chat-question-response-option-${question.id}-no`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
aria-pressed={value === false}
|
||||||
onClick={() => setQuestionAnswer(question.id, false)}
|
onClick={() => setQuestionAnswer(question.id, false)}
|
||||||
>
|
>
|
||||||
{t("chat.questionConfirmNo", "No")}
|
{t("chat.questionConfirmNo", "No")}
|
||||||
|
|||||||
@@ -60,6 +60,32 @@ describe("ChatQuestionResponse", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("marks the clicked confirm option as visually selected and moves the marker on re-click (FN-7613)", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<ChatQuestionResponse parsed={parsed} onSubmit={vi.fn()} />);
|
||||||
|
|
||||||
|
const yesButton = screen.getByTestId("chat-question-response-option-confirm-yes");
|
||||||
|
const noButton = screen.getByTestId("chat-question-response-option-confirm-no");
|
||||||
|
|
||||||
|
// Neither selected before any click.
|
||||||
|
expect(yesButton).not.toHaveClass("chat-question-response__confirm--selected");
|
||||||
|
expect(noButton).not.toHaveClass("chat-question-response__confirm--selected");
|
||||||
|
expect(yesButton).toHaveAttribute("aria-pressed", "false");
|
||||||
|
expect(noButton).toHaveAttribute("aria-pressed", "false");
|
||||||
|
|
||||||
|
await user.click(yesButton);
|
||||||
|
expect(yesButton).toHaveClass("chat-question-response__confirm--selected");
|
||||||
|
expect(noButton).not.toHaveClass("chat-question-response__confirm--selected");
|
||||||
|
expect(yesButton).toHaveAttribute("aria-pressed", "true");
|
||||||
|
expect(noButton).toHaveAttribute("aria-pressed", "false");
|
||||||
|
|
||||||
|
await user.click(noButton);
|
||||||
|
expect(noButton).toHaveClass("chat-question-response__confirm--selected");
|
||||||
|
expect(yesButton).not.toHaveClass("chat-question-response__confirm--selected");
|
||||||
|
expect(noButton).toHaveAttribute("aria-pressed", "true");
|
||||||
|
expect(yesButton).toHaveAttribute("aria-pressed", "false");
|
||||||
|
});
|
||||||
|
|
||||||
it("supports compact mode", () => {
|
it("supports compact mode", () => {
|
||||||
render(<ChatQuestionResponse parsed={{ questions: [parsed.questions[0]!] }} compact onSubmit={vi.fn()} />);
|
render(<ChatQuestionResponse parsed={{ questions: [parsed.questions[0]!] }} compact onSubmit={vi.fn()} />);
|
||||||
expect(screen.getByTestId("chat-question-response")).toHaveClass("chat-question-response--compact");
|
expect(screen.getByTestId("chat-question-response")).toHaveClass("chat-question-response--compact");
|
||||||
|
|||||||
Reference in New Issue
Block a user