feat: add GitHub username autocomplete suggestions#331
Conversation
✅ Deploy Preview for github-spy ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThis PR adds GitHub username autocomplete to the Tracker component. It introduces a ChangesGitHub Username Autocomplete
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🎉 Thank you @Prince2301p for your contribution. Please make sure your PR follows https://github.com/GitMetricsLab/github_tracker/blob/main/CONTRIBUTING.md#-pull-request-guidelines
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/Tracker/Tracker.tsx`:
- Around line 161-183: The username lookup effect (useEffect) currently calls
fetchUsers on every keystroke and doesn't cancel in-flight requests; modify the
effect around the fetchUsers function to debounce requests (e.g., using a
timeout of ~300–500ms) and use an AbortController to cancel any pending fetch
when the username changes or the component unmounts; ensure fetchUsers uses the
controller.signal and in the cleanup clears the debounce timer and calls
controller.abort(), and keep using setSuggestions(data.items?.slice(0, 5) || [])
only when the request was not aborted and the response matches the latest
username to avoid stale results.
- Around line 214-239: The dropdown Box rendering suggestions uses hardcoded
colors; update its sx styles to use theme palette values: replace
backgroundColor: "`#fff`" with theme.palette.background.paper, borderBottom: "1px
solid `#eee`" with `borderBottom: `1px solid ${theme.palette.divider}` (or use
borderBottomColor) and the hover backgroundColor "`#f5f5f5`" with
theme.palette.action.hover; implement these inside the sx (either as an object
using the theme parameter: sx={(theme) => ({ ... })} or use the theme from
useTheme) for the container and the child Box in the suggestions.map so
setUsername and setSuggestions behavior remains unchanged.
- Around line 225-244: The suggestion items rendered in suggestions.map use a
non-interactive Box with only onClick; make them keyboard-accessible by
replacing the Box (or augmenting it) with an interactive element (e.g., a button
or a div with role="button" and tabIndex={0}) and add an onKeyDown handler that
triggers setUsername(user.login) and setSuggestions([]) on Enter or Space,
handles ArrowUp/ArrowDown to move focus between suggestion items, and handles
Escape to clear/hide suggestions; also add appropriate ARIA attributes
(aria-selected/role="listbox" on the container and role="option" on each item)
to the suggestions list to improve screen reader semantics.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 40b3dd3b-eb50-435a-b625-c36240ea17fa
📒 Files selected for processing (1)
src/pages/Tracker/Tracker.tsx
| useEffect(() => { | ||
| if (!username) { | ||
| setSuggestions([]); | ||
| return; | ||
| } | ||
|
|
||
| const fetchUsers = async () => { | ||
| try { | ||
| const res = await fetch( | ||
| `https://api.github.com/search/users?q=${username}` | ||
| ); | ||
|
|
||
| const data = await res.json(); | ||
|
|
||
| setSuggestions(data.items?.slice(0, 5) || []); | ||
| } catch (err) { | ||
| console.log(err); | ||
| } | ||
| }; | ||
|
|
||
| fetchUsers(); | ||
| }, [username]); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What are the current GitHub Search Users API rate limits (authenticated vs unauthenticated), and what client-side patterns are recommended for autocomplete requests?
💡 Result:
The GitHub Search API enforces custom rate limits that are separate from the primary REST API rate limits [1][2]. These limits are designed to prevent abuse and ensure service availability [3]. GitHub Search API Rate Limits: - Authenticated requests: 30 requests per minute for most search endpoints [1][4]. The Search code endpoint is more restrictive, limited to 9 requests per minute [1][4]. - Unauthenticated requests: 10 requests per minute [1][4]. Note that these are distinct from the primary REST API rate limits (e.g., 5,000 requests per hour for authenticated users) [5][6]. You can monitor your remaining search quota by checking the rate limit response headers or by querying the GET /rate_limit endpoint [3][2]. Recommended Client-Side Patterns for Autocomplete: Because the Search API has strict per-minute limits, it is not suitable for raw, keystroke-level autocomplete without significant client-side optimization [7]. To implement autocomplete effectively while respecting these limits, the following patterns are recommended: 1. Debouncing: Implement a debounce function (typically 300ms) to delay API requests until the user has stopped typing for a specified duration [8][9]. This prevents flooding the API with requests for every single keystroke [10]. 2. Minimum Input Length: Require a minimum number of characters (e.g., 2 or 3) before triggering a search request to avoid overly broad or irrelevant queries [8]. 3. Caching: Store the results of recent queries in a local cache (e.g., a Map or object) to avoid re-fetching data for the same input [8]. 4. Request Cancellation: Ensure that if a new request is initiated, any pending or in-flight requests are cancelled to avoid race conditions and unnecessary consumption of your rate limit [11]. 5. Official Component Usage: For web implementations, consider using the official @github/auto-complete-element, which includes built-in handling for debouncing, accessibility (ARIA), and request management [12][9]. If your application requires high-frequency autocomplete, consider building a local index or a dedicated, lightweight search service that periodically syncs with GitHub data, rather than querying the GitHub Search API directly for every user interaction [7].
Citations:
- 1: https://docs.github.com/en/rest/reference/search
- 2: https://docs.github.com/rest/rate-limit/rate-limit
- 3: https://docs.github.com/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 4: https://docs.github.com/rest/search/search
- 5: https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api
- 6: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api
- 7: https://stackoverflow.com/questions/18511083/is-there-a-github-api-to-get-autocomplete-suggestions-for-user-organization-name
- 8: https://dev.to/apogeoapi/building-a-city-autocomplete-with-150k-cities-tutorial-5h9g
- 9: https://github.com/github/auto-complete-element/blob/main/src/autocomplete.ts
- 10: https://github.com/denis-taran/autocomplete
- 11: https://www.youtube.com/watch?v=zemBW3diXIs
- 12: https://github.com/github/auto-complete-element
🏁 Script executed:
cd src/pages/Tracker && wc -l Tracker.tsxRepository: GitMetricsLab/github_tracker
Length of output: 88
🏁 Script executed:
sed -n '161,183p' src/pages/Tracker/Tracker.tsxRepository: GitMetricsLab/github_tracker
Length of output: 477
Debounce and cancel username lookups to prevent API throttling and stale suggestions.
This effect fires on every keystroke and does not cancel in-flight requests. With the GitHub Search API rate-limited to 10 requests/minute for unauthenticated requests (30 for authenticated), rapid keystroke input will quickly exhaust the quota. Additionally, without request cancellation, older responses can overwrite newer input, causing race conditions.
Proposed fix
useEffect(() => {
if (!username) {
setSuggestions([]);
return;
}
- const fetchUsers = async () => {
+ const controller = new AbortController();
+ const timeoutId = window.setTimeout(async () => {
try {
const res = await fetch(
- `https://api.github.com/search/users?q=${username}`
+ `https://api.github.com/search/users?q=${encodeURIComponent(username)}`,
+ { signal: controller.signal }
);
+ if (!res.ok) {
+ setSuggestions([]);
+ return;
+ }
const data = await res.json();
- setSuggestions(data.items?.slice(0, 5) || []);
+ setSuggestions((data.items ?? []).slice(0, 5));
} catch (err) {
- console.log(err);
+ if ((err as Error).name !== "AbortError") {
+ console.error(err);
+ }
}
- };
-
- fetchUsers();
+ }, 300);
+
+ return () => {
+ controller.abort();
+ window.clearTimeout(timeoutId);
+ };
}, [username]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/Tracker/Tracker.tsx` around lines 161 - 183, The username lookup
effect (useEffect) currently calls fetchUsers on every keystroke and doesn't
cancel in-flight requests; modify the effect around the fetchUsers function to
debounce requests (e.g., using a timeout of ~300–500ms) and use an
AbortController to cancel any pending fetch when the username changes or the
component unmounts; ensure fetchUsers uses the controller.signal and in the
cleanup clears the debounce timer and calls controller.abort(), and keep using
setSuggestions(data.items?.slice(0, 5) || []) only when the request was not
aborted and the response matches the latest username to avoid stale results.
| position: "absolute", | ||
| top: "60px", | ||
| left: 0, | ||
| right: 0, | ||
| backgroundColor: "#fff", | ||
| zIndex: 9999, | ||
| borderRadius: 1, | ||
| maxHeight: 220, | ||
| overflowY: "auto", | ||
| }} | ||
| > | ||
| {suggestions.map((user) => ( | ||
| <Box | ||
| key={user.id} | ||
| onClick={() => { | ||
| setUsername(user.login); | ||
| setSuggestions([]); | ||
| }} | ||
| sx={{ | ||
| px: 2, | ||
| py: 1.5, | ||
| cursor: "pointer", | ||
| borderBottom: "1px solid #eee", | ||
| "&:hover": { | ||
| backgroundColor: "#f5f5f5", | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and examine lines 214-239
head -n 250 src/pages/Tracker/Tracker.tsx | tail -n +200Repository: GitMetricsLab/github_tracker
Length of output: 1652
🏁 Script executed:
# Check the imports and beginning of the file
head -n 30 src/pages/Tracker/Tracker.tsxRepository: GitMetricsLab/github_tracker
Length of output: 522
🏁 Script executed:
# Check for useTheme import and usage in the file
grep -n "useTheme\|theme\|Theme" src/pages/Tracker/Tracker.tsx | head -20Repository: GitMetricsLab/github_tracker
Length of output: 675
🌐 Web query:
MUI Material-UI theme palette divider action.hover properties
💡 Result:
You can theme MUI’s palette.action.hover (and related action.* tokens) via theme.palette.action, but it won’t automatically affect Divider’s “hover” behavior because Divider is not a “hover state” component; its appearance is controlled by divider-related palette tokens (theme.palette.divider) and/or component styling. 1) Overriding theme.palette.action.hover - MUI exposes palette.action.* tokens (including action.hover) that you can override in your theme palette. The Dark mode docs show the default value pattern for palette.action.hover as RGBA values (different for light/dark). [1] - You override it the same way as other palette entries, by providing an action object under palette.action in createTheme / theme definition. A working example is to set palette.action.hover in a light color scheme. [2] Example (MUI v5/v6 style): - createTheme({ colorSchemes: { light: { palette: { action: { hover: '#FF6A6A' }}}}}) This matches the documented/used theme shape with palette.action.hover. [2] 2) Divider and “action.hover” - The Divider component docs describe props and theming hooks like MuiDivider for style overrides, and Divider has CSS classes/slots for state (e.g., root, absolute, inset, etc.) rather than relying on palette.action.hover. [3][4] - For changing Divider’s line color, you typically use theme.palette.divider (or sx/classes targeting the divider pseudo-elements) rather than action.hover. For example, one approach is setting sx to use theme.palette.divider as the background color. [5] So: if your goal is “Divider hover color”, you usually need to style the Divider component’s CSS for hover (via sx or theme components.styleOverrides for MuiDivider), and/or use theme.palette.divider for its base color; setting palette.action.hover alone only affects components that explicitly use theme.palette.action.hover in their styles. [2][3][4] If you tell me your exact MUI version (v5 vs v6) and what you mean by “Divider action.hover properties” (e.g., Divider line color on mouseover, or a Button/ ListItem that contains a Divider), I can give the correct sx/styleOverrides snippet for that specific component.
Citations:
- 1: https://mui.com/material-ui/customization/dark-mode/
- 2: https://stackoverflow.com/questions/75281754/how-can-one-override-theme-palette-action-object-in-mui-theme
- 3: https://mui.com/material-ui/react-divider.md
- 4: https://v5.mui.com/material-ui/api/divider/
- 5: https://stackoverflow.com/questions/58295779/divider-color-change-react-material-ui
Use theme palette for dropdown colors instead of hardcoded light values.
The hardcoded colors #fff, #eee, and #f5f5f5 break dark mode support and are inconsistent with other styled elements in this component that already use theme.palette properties. Replace with theme.palette.background.paper, theme.palette.divider, and theme.palette.action.hover respectively, as demonstrated in the proposed fix.
Proposed fix
sx={{
position: "absolute",
top: "60px",
left: 0,
right: 0,
- backgroundColor: "`#fff`",
+ backgroundColor: theme.palette.background.paper,
zIndex: 9999,
borderRadius: 1,
maxHeight: 220,
overflowY: "auto",
}}
...
sx={{
px: 2,
py: 1.5,
cursor: "pointer",
- borderBottom: "1px solid `#eee`",
+ borderBottom: `1px solid ${theme.palette.divider}`,
"&:hover": {
- backgroundColor: "`#f5f5f5`",
+ backgroundColor: theme.palette.action.hover,
},
}}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| position: "absolute", | |
| top: "60px", | |
| left: 0, | |
| right: 0, | |
| backgroundColor: "#fff", | |
| zIndex: 9999, | |
| borderRadius: 1, | |
| maxHeight: 220, | |
| overflowY: "auto", | |
| }} | |
| > | |
| {suggestions.map((user) => ( | |
| <Box | |
| key={user.id} | |
| onClick={() => { | |
| setUsername(user.login); | |
| setSuggestions([]); | |
| }} | |
| sx={{ | |
| px: 2, | |
| py: 1.5, | |
| cursor: "pointer", | |
| borderBottom: "1px solid #eee", | |
| "&:hover": { | |
| backgroundColor: "#f5f5f5", | |
| }, | |
| position: "absolute", | |
| top: "60px", | |
| left: 0, | |
| right: 0, | |
| backgroundColor: theme.palette.background.paper, | |
| zIndex: 9999, | |
| borderRadius: 1, | |
| maxHeight: 220, | |
| overflowY: "auto", | |
| }} | |
| > | |
| {suggestions.map((user) => ( | |
| <Box | |
| key={user.id} | |
| onClick={() => { | |
| setUsername(user.login); | |
| setSuggestions([]); | |
| }} | |
| sx={{ | |
| px: 2, | |
| py: 1.5, | |
| cursor: "pointer", | |
| borderBottom: `1px solid ${theme.palette.divider}`, | |
| "&:hover": { | |
| backgroundColor: theme.palette.action.hover, | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/Tracker/Tracker.tsx` around lines 214 - 239, The dropdown Box
rendering suggestions uses hardcoded colors; update its sx styles to use theme
palette values: replace backgroundColor: "`#fff`" with
theme.palette.background.paper, borderBottom: "1px solid `#eee`" with
`borderBottom: `1px solid ${theme.palette.divider}` (or use borderBottomColor)
and the hover backgroundColor "`#f5f5f5`" with theme.palette.action.hover;
implement these inside the sx (either as an object using the theme parameter:
sx={(theme) => ({ ... })} or use the theme from useTheme) for the container and
the child Box in the suggestions.map so setUsername and setSuggestions behavior
remains unchanged.
| {suggestions.map((user) => ( | ||
| <Box | ||
| key={user.id} | ||
| onClick={() => { | ||
| setUsername(user.login); | ||
| setSuggestions([]); | ||
| }} | ||
| sx={{ | ||
| px: 2, | ||
| py: 1.5, | ||
| cursor: "pointer", | ||
| borderBottom: "1px solid #eee", | ||
| "&:hover": { | ||
| backgroundColor: "#f5f5f5", | ||
| }, | ||
| }} | ||
| > | ||
| {user.login} | ||
| </Box> | ||
| ))} |
There was a problem hiding this comment.
❓ Verification inconclusive
Script executed:
# First, let's verify the file exists and check the relevant lines
if [ -f "src/pages/Tracker/Tracker.tsx" ]; then
echo "File exists. Checking line count and content around lines 225-244:"
wc -l src/pages/Tracker/Tracker.tsx
echo "---"
sed -n '220,250p' src/pages/Tracker/Tracker.tsx
else
echo "File does not exist"
find . -name "Tracker.tsx" -type f
fiRepository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 0
stdout:
File exists. Checking line count and content around lines 225-244:
492 src/pages/Tracker/Tracker.tsx
---
borderRadius: 1,
maxHeight: 220,
overflowY: "auto",
}}
>
{suggestions.map((user) => (
<Box
key={user.id}
onClick={() => {
setUsername(user.login);
setSuggestions([]);
}}
sx={{
px: 2,
py: 1.5,
cursor: "pointer",
borderBottom: "1px solid `#eee`",
"&:hover": {
backgroundColor: "`#f5f5f5`",
},
}}
>
{user.login}
</Box>
))}
</Paper>
)}
</Box>
<TextField
label="Personal Access Token"
Script executed:
# Get more context around the suggestions implementation
sed -n '190,250p' src/pages/Tracker/Tracker.tsx | cat -nRepository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 0
stdout:
1 <Container maxWidth="lg" sx={{ mt: 4, minHeight: "80vh", color: theme.palette.text.primary }}>
2 {/* Auth Form */}
3 <Paper elevation={1} sx={{ p: 2, mb: 4, backgroundColor: theme.palette.background.paper }}>
4 <form onSubmit={handleSubmit}>
5 <Box sx={{ display: "flex", flexWrap: "wrap", gap: 2, alignItems: "flex-start" }}>
6 <Box
7 sx={{
8 position: "relative",
9 flex: 1,
10 minWidth: 250,
11 }}
12 >
13 <TextField
14 fullWidth
15 label="GitHub Username"
16 value={username}
17 onChange={(e) => setUsername(e.target.value)}
18 required
19 />
20
21 {suggestions.length > 0 && (
22 <Paper
23 elevation={3}
24 sx={{
25 position: "absolute",
26 top: "60px",
27 left: 0,
28 right: 0,
29 backgroundColor: "`#fff`",
30 zIndex: 9999,
31 borderRadius: 1,
32 maxHeight: 220,
33 overflowY: "auto",
34 }}
35 >
36 {suggestions.map((user) => (
37 <Box
38 key={user.id}
39 onClick={() => {
40 setUsername(user.login);
41 setSuggestions([]);
42 }}
43 sx={{
44 px: 2,
45 py: 1.5,
46 cursor: "pointer",
47 borderBottom: "1px solid `#eee`",
48 "&:hover": {
49 backgroundColor: "`#f5f5f5`",
50 },
51 }}
52 >
53 {user.login}
54 </Box>
55 ))}
56 </Paper>
57 )}
58 </Box>
59
60 <TextField
61 label="Personal Access Token"
Script executed:
# Check if MUI Autocomplete is being used elsewhere in the codebase
rg "Autocomplete" src/ --type tsx --type tsRepository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 2
stderr:
unrecognized file type: tsx
Script executed:
# Check if MUI Autocomplete is being used elsewhere in the codebase
rg "Autocomplete" src/ -A 2Repository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 1
Script executed:
# Check the imports and structure of the Tracker component
head -50 src/pages/Tracker/Tracker.tsxRepository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 0
stdout:
import React, { useState, useEffect } from "react"
import {
IssueOpenedIcon,
IssueClosedIcon,
GitPullRequestIcon,
GitPullRequestClosedIcon,
GitMergeIcon,
} from '`@primer/octicons-react`';
import {
Container,
Box,
TextField,
Button,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TablePagination,
Link,
Alert,
Tabs,
Tab,
Select,
MenuItem,
FormControl,
InputLabel,
Skeleton,
Typography,
} from "`@mui/material`";
import { useTheme } from "`@mui/material/styles`";
import { useGitHubAuth } from "../../hooks/useGitHubAuth";
import { useGitHubData } from "../../hooks/useGitHubData";
const ROWS_PER_PAGE = 10;
interface GitHubItem {
id: number;
title: string;
state: string;
created_at: string;
pull_request?: { merged_at: string | null };
repository_url: string;
html_url: string;
}
const Home: React.FC = () => {
Script executed:
# Check if there's any keyboard event handling in the Tracker component
rg "onKeyDown|onKeyUp|onKeyPress" src/pages/Tracker/Tracker.tsx -B 2 -A 2Repository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 1
Script executed:
# Get the state management around suggestions to understand the full flow
sed -n '40,100p' src/pages/Tracker/Tracker.tsx | cat -nRepository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 0
stdout:
1 id: number;
2 title: string;
3 state: string;
4 created_at: string;
5 pull_request?: { merged_at: string | null };
6 repository_url: string;
7 html_url: string;
8 }
9
10 const Home: React.FC = () => {
11
12 const theme = useTheme();
13
14 const {
15 username,
16 setUsername,
17 token,
18 setToken,
19 error: authError,
20 getOctokit,
21 } = useGitHubAuth();
22
23 const {
24 issues,
25 prs,
26 totalIssues,
27 totalPrs,
28 loading,
29 error: dataError,
30 fetchData,
31 } = useGitHubData(getOctokit);
32
33 const [tab, setTab] = useState(0);
34 const [page, setPage] = useState(0);
35
36 const [issueFilter, setIssueFilter] = useState("all");
37 const [prFilter, setPrFilter] = useState("all");
38 const [searchTitle, setSearchTitle] = useState("");
39 const [selectedRepo, setSelectedRepo] = useState("");
40 const [startDate, setStartDate] = useState("");
41 const [endDate, setEndDate] = useState("");
42 const [suggestions, setSuggestions] = useState<{ id: number; login: string }[]>([]);
43
44 // Fetch data when username, tab, or page changes
45 useEffect(() => {
46 if (username) {
47 fetchData(username, page + 1, ROWS_PER_PAGE);
48 }
49 }, [tab, page]);
50
51 const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
52 e.preventDefault();
53 setPage(0);
54 fetchData(username, 1, ROWS_PER_PAGE);
55 };
56
57 const handlePageChange = (_: unknown, newPage: number) => {
58 setPage(newPage);
59 };
60
61 const formatDate = (dateString: string): string =>
Script executed:
# Search for where suggestions are being set (API calls or logic)
rg "setSuggestions" src/pages/Tracker/Tracker.tsx -B 5 -A 5Repository: GitMetricsLab/github_tracker
Repository: GitMetricsLab/github_tracker
Exit code: 0
stdout:
const [prFilter, setPrFilter] = useState("all");
const [searchTitle, setSearchTitle] = useState("");
const [selectedRepo, setSelectedRepo] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [suggestions, setSuggestions] = useState<{ id: number; login: string }[]>([]);
// Fetch data when username, tab, or page changes
useEffect(() => {
if (username) {
fetchData(username, page + 1, ROWS_PER_PAGE);
--
return <IssueOpenedIcon size={16} className="icon-issue-open" />;
};
useEffect(() => {
if (!username) {
setSuggestions([]);
return;
}
const fetchUsers = async () => {
try {
--
`https://api.github.com/search/users?q=${username}`
);
const data = await res.json();
setSuggestions(data.items?.slice(0, 5) || []);
} catch (err) {
console.log(err);
}
};
--
{suggestions.map((user) => (
<Box
key={user.id}
onClick={() => {
setUsername(user.login);
setSuggestions([]);
}}
sx={{
px: 2,
py: 1.5,
cursor: "pointer",
Make suggestion rows keyboard-accessible.
The suggestion rows currently use a Box component with only an onClick handler, blocking keyboard users (including those using screen readers and keyboard navigation) from selecting suggestions. The component needs keyboard event handling and proper semantics.
The proposed fix converting to a button element with Enter and Space key support addresses the core issue. Consider also implementing arrow key navigation between suggestions, Escape key to close the dropdown, and appropriate ARIA attributes for a complete solution.
Proposed fix
- {suggestions.map((user) => (
+ {suggestions.map((user) => (
<Box
+ component="button"
+ type="button"
key={user.id}
onClick={() => {
setUsername(user.login);
setSuggestions([]);
}}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ setUsername(user.login);
+ setSuggestions([]);
+ }
+ }}
sx={{
+ width: "100%",
+ textAlign: "left",
px: 2,
py: 1.5,
cursor: "pointer",
+ border: "none",
+ background: "transparent",
borderBottom: "1px solid `#eee`",
"&:hover": {
backgroundColor: "`#f5f5f5`",
},
}}
>
{user.login}
</Box>
))}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {suggestions.map((user) => ( | |
| <Box | |
| key={user.id} | |
| onClick={() => { | |
| setUsername(user.login); | |
| setSuggestions([]); | |
| }} | |
| sx={{ | |
| px: 2, | |
| py: 1.5, | |
| cursor: "pointer", | |
| borderBottom: "1px solid #eee", | |
| "&:hover": { | |
| backgroundColor: "#f5f5f5", | |
| }, | |
| }} | |
| > | |
| {user.login} | |
| </Box> | |
| ))} | |
| {suggestions.map((user) => ( | |
| <Box | |
| component="button" | |
| type="button" | |
| key={user.id} | |
| onClick={() => { | |
| setUsername(user.login); | |
| setSuggestions([]); | |
| }} | |
| onKeyDown={(e) => { | |
| if (e.key === "Enter" || e.key === " ") { | |
| e.preventDefault(); | |
| setUsername(user.login); | |
| setSuggestions([]); | |
| } | |
| }} | |
| sx={{ | |
| width: "100%", | |
| textAlign: "left", | |
| px: 2, | |
| py: 1.5, | |
| cursor: "pointer", | |
| border: "none", | |
| background: "transparent", | |
| borderBottom: "1px solid `#eee`", | |
| "&:hover": { | |
| backgroundColor: "`#f5f5f5`", | |
| }, | |
| }} | |
| > | |
| {user.login} | |
| </Box> | |
| ))} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/Tracker/Tracker.tsx` around lines 225 - 244, The suggestion items
rendered in suggestions.map use a non-interactive Box with only onClick; make
them keyboard-accessible by replacing the Box (or augmenting it) with an
interactive element (e.g., a button or a div with role="button" and
tabIndex={0}) and add an onKeyDown handler that triggers setUsername(user.login)
and setSuggestions([]) on Enter or Space, handles ArrowUp/ArrowDown to move
focus between suggestion items, and handles Escape to clear/hide suggestions;
also add appropriate ARIA attributes (aria-selected/role="listbox" on the
container and role="option" on each item) to the suggestions list to improve
screen reader semantics.

Fixes #78
Changes Made
Why
Previously, users had to manually type full GitHub usernames.
This enhancement provides instant suggestions while typing, making the tracker easier and faster to use.
Testing
Screenshots
Summary by CodeRabbit
Release Notes
New Features
Style