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%;
+ }
+}