Skip to content

Commit 05b97b3

Browse files
committed
feat: add keyboard navigation for court search results
1 parent f02d718 commit 05b97b3

File tree

1 file changed

+46
-11
lines changed

1 file changed

+46
-11
lines changed

web/src/pages/Courts/CourtDetails/TopSearch.tsx

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,17 @@ const SearchResultsContainer = styled(OverlayScrollbarsComponent)`
7272
border-top-right-radius: 0;
7373
`;
7474

75-
const StyledCard = styled(Card)<{ selected: boolean }>`
75+
const StyledCard = styled(Card)<{ selected: boolean; keyboardSelected: boolean }>`
7676
${hoverShortTransitionTiming}
7777
height: auto;
7878
width: 100%;
79-
padding: ${({ selected }) => (selected ? "16px 13px" : "16px")};
79+
padding: ${({ selected, keyboardSelected }) => (selected || keyboardSelected ? "16px 13px" : "16px")};
8080
cursor: pointer;
8181
border: none;
82-
border-left: ${({ selected, theme }) => (selected ? `3px solid ${theme.primaryBlue}` : "none")};
83-
background-color: ${({ selected, theme }) => (selected ? theme.mediumBlue : "transparent")};
82+
border-left: ${({ selected, keyboardSelected, theme }) =>
83+
selected || keyboardSelected ? `3px solid ${theme.primaryBlue}` : "none"};
84+
background-color: ${({ selected, keyboardSelected, theme }) =>
85+
selected || keyboardSelected ? theme.mediumBlue : "transparent"};
8486
border-radius: 0;
8587
8688
:hover {
@@ -112,6 +114,7 @@ const TopSearch: React.FC = () => {
112114
const items = useMemo(() => !isUndefined(data?.court) && [rootCourtToItems(data.court)], [data]);
113115
const isUniversity = isKlerosUniversity();
114116
const [search, setSearch] = useState("");
117+
const [selectedIndex, setSelectedIndex] = useState(-1);
115118

116119
const filteredCourts = useMemo(() => {
117120
if (!data?.court) return [];
@@ -122,6 +125,39 @@ const TopSearch: React.FC = () => {
122125
return [selectedCourt, ...courts.filter((c) => c.id !== currentCourtId)];
123126
}, [data, search, currentCourtId]);
124127

128+
const handleKeyDown = (e: React.KeyboardEvent) => {
129+
if (!search || filteredCourts.length === 0) return;
130+
131+
switch (e.key) {
132+
case "ArrowDown":
133+
e.preventDefault();
134+
setSelectedIndex((prev) => Math.min(prev + 1, filteredCourts.length - 1));
135+
break;
136+
case "ArrowUp":
137+
e.preventDefault();
138+
setSelectedIndex((prev) => Math.max(prev - 1, -1));
139+
break;
140+
case "Enter":
141+
if (selectedIndex >= 0) {
142+
navigate(`/courts/${filteredCourts[selectedIndex].id}`);
143+
setSearch("");
144+
setSelectedIndex(-1);
145+
}
146+
break;
147+
}
148+
};
149+
150+
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
151+
setSearch(e.target.value);
152+
setSelectedIndex(-1);
153+
};
154+
155+
const handleCourtClick = (courtId: string) => {
156+
navigate(`/courts/${courtId}`);
157+
setSearch("");
158+
setSelectedIndex(-1);
159+
};
160+
125161
return (
126162
<Container>
127163
{items ? (
@@ -137,18 +173,17 @@ const TopSearch: React.FC = () => {
137173
type="text"
138174
placeholder="Search"
139175
value={search}
140-
onChange={(e) => setSearch(e.target.value)}
176+
onChange={handleSearchChange}
177+
onKeyDown={handleKeyDown}
141178
/>
142179
{search && filteredCourts.length > 0 && (
143180
<SearchResultsContainer>
144-
{filteredCourts.map((court) => (
181+
{filteredCourts.map((court, index) => (
145182
<StyledCard
146183
key={court.id}
147-
selected={court.id === currentCourtId}
148-
onClick={() => {
149-
navigate(`/courts/${court.id}`);
150-
setSearch("");
151-
}}
184+
selected={selectedIndex === -1 && court.id === currentCourtId}
185+
keyboardSelected={index === selectedIndex}
186+
onClick={() => handleCourtClick(court.id)}
152187
>
153188
{court.parentName && <CourtParentSpan>{court.parentName} / </CourtParentSpan>}
154189
<CourtNameSpan>{court.name}</CourtNameSpan>

0 commit comments

Comments
 (0)