diff --git a/components/ProjectCard.tsx b/components/ProjectCard.tsx
index edc66a4..e37cb6e 100644
--- a/components/ProjectCard.tsx
+++ b/components/ProjectCard.tsx
@@ -7,7 +7,8 @@ interface ProjectCardProps {
project: Project;
vertical?: boolean;
preload?: boolean;
- githubColors: GitHubColors
+ githubColors: GitHubColors;
+ searchQuery?: string;
}
interface ProjectCardImageProps {
@@ -43,19 +44,53 @@ function ProjectCardImage({ project, preload }: ProjectCardImageProps) {
}
interface ProjectCardBodyProps {
- githubColors: GitHubColors,
- project: Project
+ githubColors: GitHubColors;
+ project: Project;
+ searchQuery?: string;
}
-function ProjectCardBody(props: ProjectCardBodyProps) {
- const {
- name,
- description,
- repo,
- link,
- lang,
- topics,
- } = props.project;
+const escapeSpecialCharacters = (string: string) => {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+};
+
+const highlightText = (text: string, query: string) => {
+ query = query.trim();
+ if (!query) return text;
+
+ try {
+ const escapedQuery = escapeSpecialCharacters(query);
+ const parts = text.split(new RegExp(`(${escapedQuery})`, 'gi'));
+ return parts.map((part, index) =>
+ part.toLowerCase() === query.toLowerCase() ? (
+ {part}
+ ) : (
+ part
+ ),
+ );
+ } catch (e) {
+ return text;
+ }
+};
+
+function ProjectCardBody({
+ project,
+ searchQuery = '',
+ ...props
+}: ProjectCardBodyProps) {
+ const { repo, link, description, lang } = project;
+
+ const name = highlightText(project.name, searchQuery);
+
+ const topics = (() => {
+ if (project.topics.length === 0) return [];
+ return project.topics.map((topic, index) => (
+
+ {highlightText(topic, searchQuery)}
+ {index < project.topics.length - 1 && ', '}
+
+ ));
+ })();
+
return (
@@ -70,10 +105,10 @@ function ProjectCardBody(props: ProjectCardBodyProps) {
: 'black',
}}
>{' '}
- {lang || 'Markdown'}
- {topics.length > 0 && • {topics.join(', ')}}
+ {highlightText(lang, searchQuery) || 'Markdown'}
+ {topics.length > 0 && • {topics}}
-
{description}
+
{highlightText(description, searchQuery)}
GitHub Repository
);
@@ -86,12 +121,17 @@ function ProjectCard({
vertical = false,
preload = false,
githubColors,
+ searchQuery = '',
}: ProjectCardProps): JSX.Element {
if (vertical) {
return (
);
}
@@ -102,7 +142,11 @@ function ProjectCard({
diff --git a/components/SearchFilter/SearchFilter.tsx b/components/SearchFilter/SearchFilter.tsx
index 0a1f3a7..ad7bf25 100644
--- a/components/SearchFilter/SearchFilter.tsx
+++ b/components/SearchFilter/SearchFilter.tsx
@@ -1,10 +1,12 @@
-import React, { ChangeEvent, useState, useEffect } from 'react';
+import React, { ChangeEvent, useEffect } from 'react';
import Searchbar from './Searchbar';
import { Project } from '../../util';
interface SearchFilterProps {
projects: Project[];
setFilteredProjects: (projectList: Project[]) => void;
+ searchQuery: string;
+ setSearchQuery: (search: string) => void;
}
function lowercaseRemove(s: string) {
@@ -16,12 +18,14 @@ function lowercaseRemove(s: string) {
return newString;
}
-function SearchFilter({projects, setFilteredProjects}: SearchFilterProps): JSX.Element {
-
- const [searchbarText, setSearchbarText] = useState('');
-
+function SearchFilter({
+ projects,
+ setFilteredProjects,
+ setSearchQuery,
+ searchQuery,
+}: SearchFilterProps): JSX.Element {
const handleSearchInput = (e: ChangeEvent) => {
- setSearchbarText(e.target.value);
+ setSearchQuery(e.target.value);
};
const filterProjectsBySearchText = (project: Project) => {
@@ -30,21 +34,20 @@ function SearchFilter({projects, setFilteredProjects}: SearchFilterProps): JSX.E
// remove - and _ and white spaces from search, and make it lowercase to make it easier for the user to
// search things without typing the exact name
// e.g. if the user searches "devpathways" or "dev pathways" they should still be able to see "Dev-Pathways"
- const search = lowercaseRemove(searchbarText);
+ const search = lowercaseRemove(searchQuery);
- const {
- name,
- description,
- lang,
- topics,
- } = project;
+ const { name, description, lang, topics } = project;
// can search by name, description, or language
const lowercaseName = lowercaseRemove(name);
const lowercaseDescription = lowercaseRemove(description);
const lowercaseLang = lowercaseRemove(lang);
- if (lowercaseName.includes(search) || lowercaseDescription.includes(search) || lowercaseLang.includes(search)) {
+ if (
+ lowercaseName.includes(search) ||
+ lowercaseDescription.includes(search) ||
+ lowercaseLang.includes(search)
+ ) {
return true;
}
@@ -64,16 +67,16 @@ function SearchFilter({projects, setFilteredProjects}: SearchFilterProps): JSX.E
useEffect(() => {
const tempProjects = projects.filter(filterProjectsBySearchText);
setFilteredProjects(tempProjects);
- }, [searchbarText]);
+ }, [searchQuery]);
return (
) => handleSearchInput(e)}
/>
);
}
-export default SearchFilter;
\ No newline at end of file
+export default SearchFilter;
diff --git a/pages/projects.tsx b/pages/projects.tsx
index 3901db4..44cb34f 100644
--- a/pages/projects.tsx
+++ b/pages/projects.tsx
@@ -4,7 +4,7 @@ import React, { useState } from 'react';
import Layout from '../components/Layout';
import ProjectCard from '../components/ProjectCard';
import SearchFilter from '../components/SearchFilter/SearchFilter';
-import { getProjects, Project, GitHubColors, getGithubColors } from '../util';
+import { getGithubColors, getProjects, GitHubColors, Project } from '../util';
interface ProjectsProps {
projects: Project[];
@@ -15,6 +15,8 @@ function Projects({ projects, githubColors }: ProjectsProps): JSX.Element {
// projects is a master list of all the projects that we fetched, filteredProjects is the one that we render
// to the user
const [filteredProjects, setFilteredProjects] = useState(projects);
+ const [searchQuery, setSearchQuery] = useState('');
+
return (
@@ -43,6 +45,8 @@ function Projects({ projects, githubColors }: ProjectsProps): JSX.Element {
@@ -51,6 +55,7 @@ function Projects({ projects, githubColors }: ProjectsProps): JSX.Element {
return (