-
-
Notifications
You must be signed in to change notification settings - Fork 80
fix: make repository info popup keyboard accessible #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ export default function RepositoriesPage() { | |
| const [openInfo, setOpenInfo] = useState(false) | ||
| const infoRef = useRef(null) | ||
|
|
||
|
|
||
| useEffect(() => { | ||
| function handleClickOutside(event) { | ||
| if ( | ||
|
|
@@ -31,11 +32,22 @@ export default function RepositoriesPage() { | |
| setOpenInfo(false) | ||
| } | ||
| } | ||
|
|
||
| function handleEscape(event) { | ||
| if (event.key === 'Escape' && openInfo) { | ||
| setOpenInfo(false); | ||
| document.getElementById('info-button')?.focus(); | ||
| } | ||
| } | ||
|
|
||
| document.addEventListener('mousedown', handleClickOutside) | ||
| document.addEventListener('keydown', handleEscape) | ||
|
|
||
| return () => { | ||
| document.removeEventListener('mousedown', handleClickOutside) | ||
| document.removeEventListener('keydown', handleEscape) | ||
| } | ||
| }, []) | ||
| }, [openInfo]) | ||
|
|
||
| const navigate = useNavigate() | ||
| const allRepos = model?.totalRepos ?? [] | ||
|
|
@@ -54,7 +66,7 @@ export default function RepositoriesPage() { | |
| const { sorted, sortConfig, onSort } = useSortedData(filtered, 'healthScore', 'desc') | ||
| const visible = sorted.slice(0, shown) | ||
|
|
||
| if(loading) return <RepositorySkeleton /> | ||
| if (loading) return <RepositorySkeleton /> | ||
| if (!model) return null | ||
|
|
||
| const TABLE_COLS = [ | ||
|
|
@@ -82,8 +94,21 @@ export default function RepositoriesPage() { | |
| Repository Explorer | ||
|
|
||
| <button | ||
| onMouseEnter={() => setOpenInfo(true)} | ||
| onMouseLeave={() => setOpenInfo(false)} | ||
| id="info-button" | ||
| onClick={() => setOpenInfo(!openInfo)} | ||
| onKeyDown={(e) => { | ||
| if (e.key === 'Enter' || e.key === ' ') { | ||
| e.preventDefault(); | ||
| setOpenInfo(!openInfo); | ||
| } | ||
| if (e.key === 'Escape' && openInfo) { | ||
| setOpenInfo(false); | ||
| document.getElementById('info-button')?.focus(); | ||
| } | ||
| }} | ||
| aria-expanded={openInfo} | ||
| aria-controls="repository-info-popup" | ||
| aria-label="Repository health metrics information" | ||
|
Comment on lines
+109
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Externalize the accessible label.
As per path instructions: User-visible strings should be externalized to resource files (i18n). 🤖 Prompt for AI AgentsSource: Path instructions |
||
| className="p-3 rounded-full hover:bg-(--bg) transition" | ||
| > | ||
| <AiOutlineInfoCircle className="text-(--text) cursor-pointer" /> | ||
|
|
@@ -103,6 +128,10 @@ export default function RepositoriesPage() { | |
|
|
||
| {openInfo && ( | ||
| <div | ||
| id="repository-info-popup" | ||
| role="dialog" | ||
| aria-labelledby="info-popup-title" | ||
| aria-modal="true" | ||
|
Comment on lines
+131
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- file outline ---'
ast-grep outline src/pages/RepositoriesPage.jsx --view expanded
printf '%s\n' '--- relevant source ---'
sed -n '1,180p' src/pages/RepositoriesPage.jsx
printf '%s\n' '--- related popup symbols/usages ---'
rg -n -C 3 'repository-info-popup|info-popup-title|aria-modal|openInfo|onKeyDown|handle.*Info|info.*button' srcRepository: AOSSIE-Org/OrgExplorer Length of output: 14998 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- remaining popup markup ---'
sed -n '120,240p' src/pages/RepositoriesPage.jsx
printf '%s\n' '--- focus-management and localization patterns ---'
rg -n -i -C 2 'focus\(|tabindex|aria-modal|role="dialog"|i18n|useTranslation|t\(' src package.json
printf '%s\n' '--- static popup verifier ---'
python3 - <<'PY'
from pathlib import Path
import re
text = Path("src/pages/RepositoriesPage.jsx").read_text()
match = re.search(r'\{openInfo && \(\s*<div\b(?P<body>.*?)\n\s*</div>\s*\)\}', text, re.S)
if not match:
raise SystemExit("popup block not found")
body = match.group("body")
tags = re.findall(r'<(button|a|input|select|textarea|summary|iframe)\b', body)
focus_calls = re.findall(r'\.focus\s*\(', body)
tabindex = re.findall(r'tabIndex\s*=', body)
print("interactive descendants:", tags or "none")
print("focus calls inside popup:", len(focus_calls))
print("tabIndex declarations inside popup:", len(tabindex))
print("aria-modal:", re.search(r'aria-modal\s*=\s*"([^"]+)"', body).group(1))
PYRepository: AOSSIE-Org/OrgExplorer Length of output: 50378 🌐 Web query:
💡 Result: According to the W3C WAI-ARIA Authoring Practices Guide (APG), the modal dialog pattern requires strict focus management to ensure accessibility, as the aria-modal="true" attribute does not provide this functionality automatically [1]. Developers must implement the following behaviors using JavaScript: 1. Moving Focus into the Dialog: When a modal dialog opens, keyboard focus must be moved to an element within the dialog [2][3][4]. Generally, focus is initially placed on the first focusable element, though for large content, placing focus on a static element (e.g., the dialog title or a container with tabindex="-1") is recommended to ensure the user perceives the dialog content correctly [2][3]. 2. Containing Focus (Focus Trap): Focus must remain within the dialog until it is closed [2][4]. The tab sequence is "contained," meaning that pressing Tab or Shift + Tab must not move focus outside the dialog window [2][3]. If focus reaches the last focusable element and the user presses Tab, it must cycle back to the first focusable element; similarly, if focus is on the first element and the user presses Shift + Tab, it must cycle back to the last element [3][5]. 3. Returning Focus: When the dialog is closed, focus must be returned to the element that originally triggered the dialog [2][6][4]. The aria-modal="true" attribute informs assistive technologies that the content outside the dialog is inert [2][1]. However, it is the developer's responsibility to ensure that the application code prevents all users from interacting with content outside the dialog and that the background is visually obscured [1][7]. Failure to maintain the focus trap or to return focus correctly significantly impacts the experience for keyboard and screen reader users [4]. Citations:
Align If this popup is modal, move focus into it, trap 🧰 Tools🪛 React Doctor (0.9.3)[warning] 132-132: Keyboard users can tab out of this Replace the wrapper with (prefer-html-dialog) 🤖 Prompt for AI AgentsSources: MCP tools, Linters/SAST tools |
||
| style={{ | ||
| position: 'absolute', | ||
| top: 50, | ||
|
|
@@ -116,13 +145,8 @@ export default function RepositoriesPage() { | |
| boxShadow: '0 8px 30px rgba(0,0,0,.4)' | ||
| }} | ||
| > | ||
| <div | ||
| style={{ | ||
| fontWeight: 600, | ||
| marginBottom: 10, | ||
| color: 'var(--accent)' | ||
| }} | ||
| > | ||
|
|
||
| <div id="info-popup-title" style={{ fontWeight: 600, marginBottom: 10, color: 'var(--accent)' }}> | ||
| Repository Health Metrics | ||
| </div> | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: AOSSIE-Org/OrgExplorer
Length of output: 15479
🌐 Web query:
HTML native button Enter Space keydown preventDefault suppress click activation browser behavior specification💡 Result:
According to the HTML specification and established browser behavior, native HTML buttons (the element) possess a built-in activation behavior that triggers a click event when the button is activated via the keyboard [1][2]. Specifically, the specification dictates that when a user triggers an element with a defined activation behavior (like a button) via keyboard input, the default action of that interaction must be to fire a click event at the element [1]. For native buttons, this activation occurs as follows: - Enter key: Triggers the click event on the keydown phase [3]. - Space key: Triggers the click event on the keyup phase [3][4]. Because the browser's activation of the button is a default action associated with these key events, calling event.preventDefault on the corresponding keydown or keyup event will suppress that default behavior and prevent the subsequent click event from firing [5][6]. Practical implications include: - Suppression Strategy: If you need to intercept a key press (e.g., to perform a custom action or prevent a side effect) without triggering the button's native click handler, calling preventDefault within the appropriate event handler (keydown for Enter, keyup for Space) is the standard method to achieve this [7][6]. - Browser/Screen Reader Variability: While the core specification is consistent, the interplay between browser implementations and assistive technology can vary [3]. Some screen readers intercept keyboard events before they reach the browser's DOM event loop, which may lead to inconsistencies in whether or not your preventDefault logic is respected [3]. - Development Best Practice: Relying on native elements and allowing their default keyboard behavior is generally recommended for accessibility [8]. If you are creating a custom component that mimics a button (e.g., a
Citations:
🏁 Script executed:
Repository: AOSSIE-Org/OrgExplorer
Length of output: 2305
🌐 Web query:
native HTML button Space keydown preventDefault click fires on keyup Enter keydown keyboard activation💡 Result:
Native HTML buttons (the
Citations:
🏁 Script executed:
Repository: AOSSIE-Org/OrgExplorer
Length of output: 545
Handle Space activation only once.
A native button fires
clickon Enter keydown and Space keyup.preventDefault()on keydown does not cancel the Space click, so this handler togglesopenInfotwice for one Space press. Remove custom Enter/Space handling and keeponClick; retain custom handling only for Escape. Add tests for one state transition per key.🤖 Prompt for AI Agents
Source: MCP tools