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 (