From 05b97b305b91e98ce3d09d0ed67c78061d02d57b Mon Sep 17 00:00:00 2001 From: ManiBAJPAI22 Date: Sat, 23 Aug 2025 06:59:31 +0530 Subject: [PATCH 1/2] feat: add keyboard navigation for court search results --- .../pages/Courts/CourtDetails/TopSearch.tsx | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/TopSearch.tsx b/web/src/pages/Courts/CourtDetails/TopSearch.tsx index 5ac47aa43..274d06b07 100644 --- a/web/src/pages/Courts/CourtDetails/TopSearch.tsx +++ b/web/src/pages/Courts/CourtDetails/TopSearch.tsx @@ -72,15 +72,17 @@ const SearchResultsContainer = styled(OverlayScrollbarsComponent)` border-top-right-radius: 0; `; -const StyledCard = styled(Card)<{ selected: boolean }>` +const StyledCard = styled(Card)<{ selected: boolean; keyboardSelected: boolean }>` ${hoverShortTransitionTiming} height: auto; width: 100%; - padding: ${({ selected }) => (selected ? "16px 13px" : "16px")}; + padding: ${({ selected, keyboardSelected }) => (selected || keyboardSelected ? "16px 13px" : "16px")}; cursor: pointer; border: none; - border-left: ${({ selected, theme }) => (selected ? `3px solid ${theme.primaryBlue}` : "none")}; - background-color: ${({ selected, theme }) => (selected ? theme.mediumBlue : "transparent")}; + border-left: ${({ selected, keyboardSelected, theme }) => + selected || keyboardSelected ? `3px solid ${theme.primaryBlue}` : "none"}; + background-color: ${({ selected, keyboardSelected, theme }) => + selected || keyboardSelected ? theme.mediumBlue : "transparent"}; border-radius: 0; :hover { @@ -112,6 +114,7 @@ const TopSearch: React.FC = () => { const items = useMemo(() => !isUndefined(data?.court) && [rootCourtToItems(data.court)], [data]); const isUniversity = isKlerosUniversity(); const [search, setSearch] = useState(""); + const [selectedIndex, setSelectedIndex] = useState(-1); const filteredCourts = useMemo(() => { if (!data?.court) return []; @@ -122,6 +125,39 @@ const TopSearch: React.FC = () => { return [selectedCourt, ...courts.filter((c) => c.id !== currentCourtId)]; }, [data, search, currentCourtId]); + const handleKeyDown = (e: React.KeyboardEvent) => { + if (!search || filteredCourts.length === 0) return; + + switch (e.key) { + case "ArrowDown": + e.preventDefault(); + setSelectedIndex((prev) => Math.min(prev + 1, filteredCourts.length - 1)); + break; + case "ArrowUp": + e.preventDefault(); + setSelectedIndex((prev) => Math.max(prev - 1, -1)); + break; + case "Enter": + if (selectedIndex >= 0) { + navigate(`/courts/${filteredCourts[selectedIndex].id}`); + setSearch(""); + setSelectedIndex(-1); + } + break; + } + }; + + const handleSearchChange = (e: React.ChangeEvent) => { + setSearch(e.target.value); + setSelectedIndex(-1); + }; + + const handleCourtClick = (courtId: string) => { + navigate(`/courts/${courtId}`); + setSearch(""); + setSelectedIndex(-1); + }; + return ( {items ? ( @@ -137,18 +173,17 @@ const TopSearch: React.FC = () => { type="text" placeholder="Search" value={search} - onChange={(e) => setSearch(e.target.value)} + onChange={handleSearchChange} + onKeyDown={handleKeyDown} /> {search && filteredCourts.length > 0 && ( - {filteredCourts.map((court) => ( + {filteredCourts.map((court, index) => ( { - navigate(`/courts/${court.id}`); - setSearch(""); - }} + selected={selectedIndex === -1 && court.id === currentCourtId} + keyboardSelected={index === selectedIndex} + onClick={() => handleCourtClick(court.id)} > {court.parentName && {court.parentName} / } {court.name} From 989e579fcc127710948f059ce2120779465fd676 Mon Sep 17 00:00:00 2001 From: ManiBAJPAI22 Date: Fri, 29 Aug 2025 15:38:52 +0530 Subject: [PATCH 2/2] fix: prevent negative selectedIndex in keyboard navigation --- .../pages/Courts/CourtDetails/TopSearch.tsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/web/src/pages/Courts/CourtDetails/TopSearch.tsx b/web/src/pages/Courts/CourtDetails/TopSearch.tsx index 274d06b07..625e4a9a3 100644 --- a/web/src/pages/Courts/CourtDetails/TopSearch.tsx +++ b/web/src/pages/Courts/CourtDetails/TopSearch.tsx @@ -98,7 +98,7 @@ const CourtNameSpan = styled.span` color: ${({ theme }) => theme.primaryText}; `; -function flattenCourts(court, parent = null) { +function flattenCourts(court: any, parent: any = null) { const current = { ...court, parentName: parent?.name ?? null, @@ -114,7 +114,7 @@ const TopSearch: React.FC = () => { const items = useMemo(() => !isUndefined(data?.court) && [rootCourtToItems(data.court)], [data]); const isUniversity = isKlerosUniversity(); const [search, setSearch] = useState(""); - const [selectedIndex, setSelectedIndex] = useState(-1); + const [selectedIndex, setSelectedIndex] = useState(0); const filteredCourts = useMemo(() => { if (!data?.court) return []; @@ -131,31 +131,32 @@ const TopSearch: React.FC = () => { switch (e.key) { case "ArrowDown": e.preventDefault(); - setSelectedIndex((prev) => Math.min(prev + 1, filteredCourts.length - 1)); + setSelectedIndex((prev) => (prev + 1) % filteredCourts.length); break; case "ArrowUp": e.preventDefault(); - setSelectedIndex((prev) => Math.max(prev - 1, -1)); + setSelectedIndex((prev) => { + const newIndex = prev - 1; + return newIndex < 0 ? filteredCourts.length - 1 : newIndex; + }); break; case "Enter": - if (selectedIndex >= 0) { - navigate(`/courts/${filteredCourts[selectedIndex].id}`); - setSearch(""); - setSelectedIndex(-1); - } + navigate(`/courts/${filteredCourts[selectedIndex].id}`); + setSearch(""); + setSelectedIndex(0); break; } }; const handleSearchChange = (e: React.ChangeEvent) => { setSearch(e.target.value); - setSelectedIndex(-1); + setSelectedIndex(0); }; const handleCourtClick = (courtId: string) => { navigate(`/courts/${courtId}`); setSearch(""); - setSelectedIndex(-1); + setSelectedIndex(0); }; return ( @@ -181,7 +182,7 @@ const TopSearch: React.FC = () => { {filteredCourts.map((court, index) => ( handleCourtClick(court.id)} >