Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f34c797
Added search UI
Pikalot Jun 19, 2025
747b736
update: Only 5 search suggestions are shown at once. Scroll to see more.
Pikalot Jun 21, 2025
e217247
Resolved incoming conflict when merging with dev
Pikalot Jun 21, 2025
ad090a9
Resolved incoming conflict when merging with dev
Pikalot Jun 21, 2025
1d7812b
Merge branch 'devTA_shortcut_key' into devTA_search_ui
Pikalot Jun 26, 2025
f82d85d
Merge branch 'devTA_shortcut_key' into devTA_search_ui
Pikalot Jun 26, 2025
dbf556d
Improved routes caching in SearchModal using useMemo
Pikalot Jun 27, 2025
1900dfc
Resolved conflict when merge with devTA_shortcut_key
Pikalot Jun 27, 2025
c4d15f6
Merge branch 'devTA_shortcut_key' into devTA_search_ui
Pikalot Jun 29, 2025
03ab038
1. Removed scrollable dropdown codes. Just shows top 5 results.
Pikalot Jun 29, 2025
6da541d
removed e.key === 'K', only implement api fetch for users list when o…
Pikalot Jun 30, 2025
2474714
Fix: hide search suggestions when input is empty
Pikalot Jul 1, 2025
fb40204
Refactor: removed duplicate route filtering and improved keyboard sel…
Pikalot Jul 1, 2025
28db66d
Update src/Components/ShortcutKeyModal/SearchModal.js
Pikalot Jul 1, 2025
3f4f280
Update src/Components/ShortcutKeyModal/SearchModal.js
Pikalot Jul 1, 2025
eaf4153
Committed changes as suggestions
Pikalot Jul 1, 2025
57283fc
Undo the changes in Home.js
Pikalot Jul 1, 2025
e770ff9
Removed a space in line 1 of Home.js
Pikalot Jul 1, 2025
eab0ca0
Spitted PR and moved searching debounce logic to the new PR
Pikalot Jul 1, 2025
01cc796
Merged with latest devTA_shortcut_key
Pikalot Jul 1, 2025
129b8fd
Merge branch 'devTA_shortcut_key' into devTA_search_ui
Pikalot Jul 2, 2025
93f19d9
Merge branch 'devTA_shortcut_key' into devTA_search_ui
Pikalot Jul 3, 2025
8cd5787
Merged with Aidan's routes.js code
Pikalot Jul 3, 2025
25fdbde
Merge branch 'devTA_shortcut_key' into devTA_search_ui
Pikalot Jul 3, 2025
b94ae33
Update src/index.js
Pikalot Jul 3, 2025
444f3b3
Update src/Components/ShortcutKeyModal/SearchModal.js
Pikalot Jul 3, 2025
4a72502
Used scoped CSS
Pikalot Jul 3, 2025
ed3f51b
Moved suggestions rendering to a separate function. Switched to scope…
Pikalot Jul 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/Components/ShortcutKeyModal/SearchModal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.search-modal {
position: fixed;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
/* semi-transparent dark overlay */
backdrop-filter: blur(4px);
/* blurred background */
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 33vh;
z-index: 9999;
}

.search-modal .input-wrapper {
padding: 8px;
background: white;
border-radius: 8px;
/* height: auto; */
}

.search-modal input {
border: 1.5px solid darkgray;
width: 35rem;
border-radius: 0.25rem;
padding: 0.75rem;
height: 2.25rem;
font-size: 1rem;
}

/* .suggestion-item:hover, */
.search-modal .active {
background: #3B82F6;
color: white;
border-radius: 0.25rem;
padding: 0.25rem;
}

.search-modal .suggestion-list {
background: lightcyan;
}

.search-modal .hidden-tab {
font-size: small;
color: white;
}
142 changes: 142 additions & 0 deletions src/Components/ShortcutKeyModal/SearchModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
import './SearchModal.css';
import { officerOrAdminRoutes, signedOutRoutes, memberRoutes, notAuthenticatedRoutes } from '../../Routes';
import { membershipState } from '../../Enums';
import { useUser } from '../context/UserContext';

export default function SearchModal({ appProps }) {
const [open, setOpen] = useState(false);
const inputRef = useRef(null);
const [keyword, setKeyword] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [selectItem, setSelectItem] = useState(0);
const { user } = useUser();
const [errorMsg, setErrorMsg] = useState('');

const routes = useMemo(() => {
if (user.accessLevel === membershipState.MEMBER) return [...memberRoutes, ...signedOutRoutes];
if (user.accessLevel >= membershipState.OFFICER) return [...officerOrAdminRoutes, ...signedOutRoutes];
if (!appProps.authenticated) return [...notAuthenticatedRoutes, ...signedOutRoutes];
return [...signedOutRoutes];
}, [user.accessLevel]);

function handleChanges(e) {
setKeyword(e.target.value);
setSelectItem(0);
}

function getSuggestions() {
if (suggestions.length === 0) return <></>;

return (
<ul className='suggestion-list'>
{suggestions.map((r, index) => (
<li
key={index}
className={`suggestion-item ${index === selectItem ? 'active' : ''}`}
onMouseEnter={() => setSelectItem(index)}
onClick={() => {
window.location.href = r.path;
setOpen(false);
}}
>
<span style={{ marginRight: '0.5rem' }}>
{r.type === 'user' ? '👤' : '📄'}
</span>
{r.pageName}
<div className='hidden-tab'>{selectItem === index && `${window.location.origin}${r.path}`}</div>
</li>
)).slice(0, 5)}
</ul>
);
}

/**
* An effect that instantly shows all hardcoded routes.
* @dependencies keyword, routes, open
*/
useEffect(() => {
if (!open) return;

// Return if keyword is blank
if (!keyword) {
setSuggestions([]);
return;
}

// Instantly display for the hardcoded page recommendations
const routeMatches = routes.filter((r) =>
r.pageName?.toLowerCase().includes(keyword.toLowerCase())
);
setSuggestions(routeMatches);
}, [open, keyword, routes]);

/**
* Executes a search when Enter is pressed
* @dependencies selectItem, suggestions
*/
const handleSearch = useCallback(() => {
if (suggestions.length === 0) return; // Check if suggestions is empty

const target = suggestions[selectItem];
if (target && target.path) {
window.location.href = target.path;
setOpen(false);
}
}, [suggestions, selectItem]);

/**
* Listens for keyboard input and executes shortcut actions.
* @dependencies open, suggestions, selectItem
*/
useEffect(() => {
const listener = (e) => {
if ((e.ctrlKey || e.metaKey) && (e.key.toLowerCase() === 'k')) {
e.preventDefault();
setOpen(prev => !prev);
} else if (e.key === 'Escape') {
setOpen(false);
} else if (e.key === 'Enter' && open) {
e.preventDefault();
handleSearch();
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (suggestions.length > 0) {
const minLength = Math.min(suggestions.length - 1, 4);
setSelectItem(prev => Math.min(prev + 1, minLength));
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setSelectItem(prev => Math.max(prev - 1, 0));
}
};

window.addEventListener('keydown', listener);
return () => window.removeEventListener('keydown', listener);
}, [open, suggestions, selectItem]);

useEffect(() => {
if (open && inputRef.current) {
inputRef.current.focus();
}
}, [open]);

if (!open) return null;

return (
<div className="search-modal">
<div className='input-wrapper'>
<input
ref={inputRef}
placeholder="Search here"
value={keyword}
onChange={handleChanges} />

{getSuggestions()}
</div>
<div>
{errorMsg && <p>{errorMsg}</p>}
</div>
</div>
);
}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './index.css';
import Routing from './Routing';
import { checkIfUserIsSignedIn } from './APIFunctions/Auth';
import { UserContext } from './Components/context/UserContext';
import SearchModal from './Components/ShortcutKeyModal/SearchModal';

function App(props) {
const [authenticated, setAuthenticated] = useState(false);
Expand All @@ -29,6 +30,7 @@ function App(props) {
!isAuthenticating && (
<UserContext.Provider value={{ user, setUser }}>
<BrowserRouter>
<SearchModal appProps={{ authenticated }} />
<Routing appProps={{ authenticated, setAuthenticated, user }} />
</BrowserRouter>
</UserContext.Provider>
Expand Down