From 38bf3475345b9d51ad0f4a7b37934dd75500b4fb Mon Sep 17 00:00:00 2001 From: AnnuKumar Date: Fri, 10 Jul 2026 20:47:52 +0530 Subject: [PATCH] feat(a11y): add escape key listener and active focus rings for keyboard navigation #578 --- frontend/src/components/SettingsPanel.jsx | 65 ++++++++++++------- .../src/components/SettingsPanel.test.jsx | 27 ++++++++ 2 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 frontend/src/components/SettingsPanel.test.jsx diff --git a/frontend/src/components/SettingsPanel.jsx b/frontend/src/components/SettingsPanel.jsx index 2ae4b09..5b9ce77 100644 --- a/frontend/src/components/SettingsPanel.jsx +++ b/frontend/src/components/SettingsPanel.jsx @@ -1,56 +1,78 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { SettingsIcon } from "./Icons"; -const MODELS = ["llama3","mistral","phi3","gemma2","deepseek-r1"]; +const MODELS = ["llama3", "mistral", "phi3", "gemma2", "deepseek-r1"]; const LANGUAGES = [{code:"en",label:"English"},{code:"hi",label:"हिन्दी"},{code:"ta",label:"தமிழ்"},{code:"te",label:"తెలుగు"},{code:"kn",label:"ಕನ್ನಡ"},{code:"fr",label:"Français"}]; export default function SettingsPanel({ settings, onSave, onClose }) { const [form, setForm] = useState({ - default_model: settings.default_model || "llama3", - default_language: settings.default_language || "en", - temperature: settings.temperature ?? 0.7, - max_history_turns:settings.max_history_turns|| 10, - rag_top_k: settings.rag_top_k || 4, - theme: settings.theme || "dark", + default_model: settings?.default_model || "llama3", + default_language: settings?.default_language || "en", + temperature: settings?.temperature ?? 0.7, + max_history_turns:settings?.max_history_turns|| 10, + rag_top_k: settings?.rag_top_k || 4, + theme: settings?.theme || "dark", }); + // FIXED (#578): Global keyboard keydown handler to intercept Escape key dismiss triggers + useEffect(() => { + function handleKeyDown(e) { + if (e.key === "Escape") { + onClose(); + } + } + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [onClose]); + function set(key, val) { setForm(p => ({...p, [key]: val})); } return ( -
+
-

Settings

- +

+ Settings +

+
- set("default_model",e.target.value)} className="sel focus:ring-2 focus:ring-purple-500"> {MODELS.map(m => )} - set("default_language",e.target.value)} className="sel focus:ring-2 focus:ring-purple-500"> {LANGUAGES.map(l=>)} set("temperature",parseFloat(e.target.value))} - className="w-full accent-purple-500" /> + className="w-full accent-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 rounded" /> set("rag_top_k",parseInt(e.target.value))} - className="w-full accent-purple-500" /> + className="w-full accent-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 rounded" /> set("max_history_turns",parseInt(e.target.value))} - className="w-full accent-purple-500" /> + className="w-full accent-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 rounded" /> - set("theme",e.target.value)} className="sel focus:ring-2 focus:ring-purple-500"> @@ -59,17 +81,16 @@ export default function SettingsPanel({ settings, onSave, onClose }) {
- -
+
); } @@ -80,4 +101,4 @@ function Field({ label, children }) { {children}
); -} +} \ No newline at end of file diff --git a/frontend/src/components/SettingsPanel.test.jsx b/frontend/src/components/SettingsPanel.test.jsx new file mode 100644 index 0000000..dc380fe --- /dev/null +++ b/frontend/src/components/SettingsPanel.test.jsx @@ -0,0 +1,27 @@ +// @vitest-environment jsdom +import { render, screen, fireEvent, cleanup } from "@testing-library/react"; +import { describe, test, expect, vi, afterEach } from "vitest"; +import SettingsPanel from "./SettingsPanel"; +const mockSettings = { + default_model: "llama3", + default_language: "en", + temperature: 0.7, + max_history_turns: 10, + rag_top_k: 4, + theme: "dark", +}; +afterEach(() => { + cleanup(); +}); + +describe("SettingsPanel Keyboard Navigation Suite (#578)", () => { + test("fires the onClose callback trigger instantly when the Escape key is pressed", () => { + const mockOnClose = vi.fn(); + render(); + + // Simulates standard window keyboard Escape sequence tracking + fireEvent.keyDown(window, { key: "Escape" }); + + expect(mockOnClose).toHaveBeenCalledTimes(1); + }); +}); \ No newline at end of file