diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..69b6635 --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import "../styles/components/SearchBar.css"; + +interface SearchBarProps { + searchQuery: string; + setSearchQuery: (query: string) => void; + placeholder?: string; +} + +const SearchBar: React.FC = ({ + searchQuery, + setSearchQuery, + placeholder = "Search projects..." +}) => { + return ( +
+ setSearchQuery(e.target.value)} + /> + {searchQuery && ( + + )} +
+ ); +}; + +export default SearchBar; diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx index 8d4c986..2074f9b 100644 --- a/src/pages/Projects.tsx +++ b/src/pages/Projects.tsx @@ -5,6 +5,7 @@ import { REPO_DATA_TYPE } from "../utils/types"; import RepoData from '../data/repo_data.json'; import SortDropdown from "../components/SortDropdown"; import PaginatedCardGrid from "../sections/CardGrid"; +import SearchBar from "../components/SearchBar"; const Projects = () => { const repoList: REPO_DATA_TYPE[] = RepoData as REPO_DATA_TYPE[] @@ -12,12 +13,21 @@ const Projects = () => { const [sortType, setSortType] = useState("desc") const languages = [...new Set(repoList.map(repo => repo.language).flat())].filter(lang => lang !== null).sort(); const [selectedLanguages, setSelectedLanguages] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); const [filteredRepos, setFilteredRepos] = useState(repoList); useEffect(() => { - let result = repoList.filter((repo) => - selectedLanguages.length === 0 || repo.language.some(lang => selectedLanguages.includes(lang)) - ); + let result = repoList.filter((repo) => { + // Filter by language + const languageMatch = selectedLanguages.length === 0 || repo.language.some(lang => selectedLanguages.includes(lang)); + + // Filter by search query (name or description) + const searchMatch = searchQuery === "" || + repo.name.toLowerCase().includes(searchQuery.toLowerCase()) || + repo.description.toLowerCase().includes(searchQuery.toLowerCase()); + + return languageMatch && searchMatch; + }); if (sortField === "name") { result.sort((a, b) => { @@ -47,7 +57,7 @@ const Projects = () => { } setFilteredRepos(result) - }, [selectedLanguages, sortField, sortType]) + }, [selectedLanguages, sortField, sortType, searchQuery]) @@ -72,6 +82,19 @@ const Projects = () => {

+
+ + +
{languages.map(lang => (
- - - +
+
+ Showing {filteredRepos.length} of {repoList.length} projects
diff --git a/src/styles/components/SearchBar.css b/src/styles/components/SearchBar.css new file mode 100644 index 0000000..4ff04e1 --- /dev/null +++ b/src/styles/components/SearchBar.css @@ -0,0 +1,53 @@ +.search-bar-container { + position: relative; + width: 100%; + max-width: 400px; +} + +.search-input { + width: 100%; + padding: 10px 40px 10px 15px; + font-size: 14px; + border: none; + border-radius: 0.375rem; + background-color: #2f2f2f; + color: #f3f3f3; + outline: none; + transition: all 0.3s ease; +} + +.search-input::placeholder { + color: #a0a0a0; +} + +.search-input:focus { + background-color: #3a3a3a; + box-shadow: 0 0 0 2px rgba(77, 77, 77, 0.5); +} + +.clear-button { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #a0a0a0; + font-size: 18px; + cursor: pointer; + padding: 5px; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s ease; +} + +.clear-button:hover { + color: #f3f3f3; +} + +@media (max-width: 768px) { + .search-bar-container { + max-width: 100%; + } +} diff --git a/src/styles/pages/Projects.css b/src/styles/pages/Projects.css index d66b529..a2e391f 100644 --- a/src/styles/pages/Projects.css +++ b/src/styles/pages/Projects.css @@ -14,6 +14,13 @@ flex-direction: column; } +.search-sort-row { + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; +} + .filter-button { border: none; padding: 8px 15px; @@ -35,8 +42,20 @@ font-weight: bold; } +.results-info { + color: #a0a0a0; + font-size: 14px; + margin: 10px 0; + font-style: italic; +} + @media (max-width:1024px) { .filter-container { flex-direction: column; } -} \ No newline at end of file + + .search-sort-row { + flex-direction: column; + width: 100%; + } +}