diff --git a/src/components/Projects/StyledProjects.tsx b/src/components/Projects/StyledProjects.tsx
index fa3c8c0b..8d0920ad 100644
--- a/src/components/Projects/StyledProjects.tsx
+++ b/src/components/Projects/StyledProjects.tsx
@@ -40,6 +40,10 @@ export const ProjectsHeader = styled.div`
}
}
}
+ .projectCount {
+ display: flex;
+ gap: 0.5rem;
+ }
`;
export const SearchInput = styled.input`
diff --git a/src/components/Projects/index.js b/src/components/Projects/index.js
index c9fd19d2..a48d007b 100644
--- a/src/components/Projects/index.js
+++ b/src/components/Projects/index.js
@@ -1,8 +1,11 @@
-import React, { useEffect, useRef, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Highlighter from 'react-highlight-words';
+import { LoadingOutlined } from '@ant-design/icons';
+import { Spin } from 'antd';
import Box from 'components/Box';
import ProjectLink from 'components/link/Project';
+import { debounce } from 'lib/util';
import {
ProjectsHeader,
@@ -18,6 +21,8 @@ import {
*/
const Projects = ({ projects = [], initialSearch }) => {
const [searchInput, setSearchInput] = useState(initialSearch);
+ const [isFiltering, setIsFiltering] = useState(false);
+ const [filteredProjects, setFilteredProjects] = useState(projects);
const searchInputRef = useRef(null);
@@ -26,32 +31,94 @@ const Projects = ({ projects = [], initialSearch }) => {
searchInputRef.current.focus();
}
}, []);
- const filteredProjects = projects.filter(key => {
- const sortByName = key.name.toLowerCase().includes(searchInput.trim().toLowerCase());
- let sortByUrl = '';
- if (key.environments[0] !== void 0) {
- if (key.environments[0].route !== null) {
- sortByUrl = key.environments[0].route.toLowerCase().includes(searchInput.trim().toLowerCase());
- }
- }
- return ['name', 'environments', '__typename'].includes(key) ? false : (true && sortByName) || sortByUrl;
- });
+
+ const timerLengthPercentage = useMemo(
+ () => Math.min(1000, Math.max(40, Math.floor(projects.length * 0.0725))),
+ [projects.length]
+ );
+
+ const debouncedSearch = useCallback(
+ debounce(searchVal => {
+ setSearchInput(searchVal);
+ }, timerLengthPercentage),
+ []
+ );
+
+ const handleSearch = searchVal => {
+ setIsFiltering(true);
+ debouncedSearch(searchVal);
+ };
+
+ useEffect(() => {
+ const filterProjects = async () => {
+ return new Promise(resolve => {
+ const filtered = projects.filter(key => {
+ const sortByName = key.name.toLowerCase().includes(searchInput.trim().toLowerCase());
+ let sortByUrl = '';
+ if (key.environments[0] !== void 0) {
+ if (key.environments[0].route !== null) {
+ sortByUrl = key.environments[0].route.toLowerCase().includes(searchInput.trim().toLowerCase());
+ }
+ }
+ return ['name', 'environments', '__typename'].includes(key) ? false : (true && sortByName) || sortByUrl;
+ });
+
+ resolve(filtered);
+ });
+ };
+
+ filterProjects()
+ .then(filtered => setFilteredProjects(filtered))
+ .finally(() => setIsFiltering(false));
+ }, [searchInput, projects]);
+
+ const MemoizedHighlighter = React.memo(Highlighter);
+
+ const mappedProjects = useMemo(() => {
+ return filteredProjects.map(project => (
+
+
+
+
+
+
+
+ {project.environments.map((environment, index) => (
+
+ ))}
+
+
+
+
+
+ ));
+ }, [filteredProjects]);
return (
-
);
};
diff --git a/src/lib/util.js b/src/lib/util.js
index ba246da5..c42fb188 100644
--- a/src/lib/util.js
+++ b/src/lib/util.js
@@ -9,3 +9,14 @@ export const queryStringToObject = R.pipe(
);
export const makeSafe = string => string.toLocaleLowerCase().replace(/[^0-9a-z-]/g, '-');
+
+export const debounce = (fn, delay) => {
+ let timeoutId;
+
+ return function (val) {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => {
+ fn.call(null, val);
+ }, delay);
+ };
+};