From 9c4187a5b9bae362c2acdf415a8be094bdf03289 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 15 Jan 2022 11:38:57 +0800 Subject: [PATCH] feat(website): search in showcase (#6333) * feat(website): search in showcase * fix SSR --- .../ShowcaseFilterToggle/index.tsx | 4 +- website/src/pages/showcase/index.tsx | 85 ++++++++++++++----- website/src/pages/showcase/styles.module.css | 11 +++ 3 files changed, 79 insertions(+), 21 deletions(-) diff --git a/website/src/pages/showcase/_components/ShowcaseFilterToggle/index.tsx b/website/src/pages/showcase/_components/ShowcaseFilterToggle/index.tsx index 5106d5c88a..931128ff7f 100644 --- a/website/src/pages/showcase/_components/ShowcaseFilterToggle/index.tsx +++ b/website/src/pages/showcase/_components/ShowcaseFilterToggle/index.tsx @@ -34,7 +34,9 @@ export default function ShowcaseFilterToggle(): JSX.Element { setOperator((o) => !o); const searchParams = new URLSearchParams(location.search); searchParams.delete(OperatorQueryKey); - searchParams.append(OperatorQueryKey, operator ? 'OR' : 'AND'); + if (!operator) { + searchParams.append(OperatorQueryKey, operator ? 'OR' : 'AND'); + } history.push({ ...location, search: searchParams.toString(), diff --git a/website/src/pages/showcase/index.tsx b/website/src/pages/showcase/index.tsx index 96e271194c..c020361111 100644 --- a/website/src/pages/showcase/index.tsx +++ b/website/src/pages/showcase/index.tsx @@ -29,7 +29,7 @@ import { import ShowcaseTooltip from './_components/ShowcaseTooltip'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -import {useLocation} from '@docusaurus/router'; +import {useHistory, useLocation} from '@docusaurus/router'; import styles from './styles.module.css'; @@ -64,11 +64,24 @@ export function prepareUserState(): UserState | undefined { return undefined; } +const SearchNameQueryKey = 'name'; + +function readSearchName(search: string) { + return new URLSearchParams(search).get(SearchNameQueryKey); +} + function filterUsers( users: User[], selectedTags: TagType[], operator: Operator, + searchName: string | null, ) { + if (searchName) { + // eslint-disable-next-line no-param-reassign + users = users.filter((user) => + user.title.toLowerCase().includes(searchName.toLowerCase()), + ); + } if (selectedTags.length === 0) { return users; } @@ -85,33 +98,23 @@ function filterUsers( } function useFilteredUsers() { - const selectedTags = useSelectedTags(); const location = useLocation(); const [operator, setOperator] = useState('OR'); - useEffect(() => { - setOperator(readOperator(location.search)); - restoreUserState(location.state); - }, [location]); - return useMemo( - () => filterUsers(sortedUsers, selectedTags, operator), - [selectedTags, operator], - ); -} - -function useSelectedTags() { - // The search query-string is the source of truth! - const location = useLocation(); - // On SSR / first mount (hydration) no tag is selected const [selectedTags, setSelectedTags] = useState([]); - + const [searchName, setSearchName] = useState(null); // Sync tags from QS to state (delayed on purpose to avoid SSR/Client hydration mismatch) useEffect(() => { setSelectedTags(readSearchTags(location.search)); + setOperator(readOperator(location.search)); + setSearchName(readSearchName(location.search)); restoreUserState(location.state); }, [location]); - return selectedTags; + return useMemo( + () => filterUsers(sortedUsers, selectedTags, operator, searchName), + [selectedTags, operator, searchName], + ); } function ShowcaseHeader() { @@ -190,8 +193,41 @@ const otherUsers = sortedUsers.filter( (user) => !user.tags.includes('favorite'), ); +function SearchBar() { + const history = useHistory(); + const location = useLocation(); + const [value, setValue] = useState(null); + useEffect(() => { + setValue(readSearchName(location.search)); + }, [location]); + return ( +
+ { + setValue(e.currentTarget.value); + const newSearch = new URLSearchParams(location.search); + newSearch.delete(SearchNameQueryKey); + if (e.currentTarget.value) { + newSearch.set(SearchNameQueryKey, e.currentTarget.value); + } + history.push({ + ...location, + search: newSearch.toString(), + state: prepareUserState(), + }); + setTimeout(() => { + document.getElementById('searchbar')?.focus(); + }, 0); + }} + /> +
+ ); +} + function ShowcaseCards() { - const selectedTags = useSelectedTags(); const filteredUsers = useFilteredUsers(); if (filteredUsers.length === 0) { @@ -199,6 +235,7 @@ function ShowcaseCards() {

No result

+
); @@ -206,7 +243,7 @@ function ShowcaseCards() { return (
- {selectedTags.length === 0 ? ( + {filteredUsers.length === sortedUsers.length ? ( <>
@@ -217,6 +254,7 @@ function ShowcaseCards() { )}>

Our favorites

+
    {favoriteUsers.map((user) => ( @@ -236,6 +274,13 @@ function ShowcaseCards() { ) : (
    +
    + +
      {filteredUsers.map((user) => ( diff --git a/website/src/pages/showcase/styles.module.css b/website/src/pages/showcase/styles.module.css index 7529cfb951..779b8c6031 100644 --- a/website/src/pages/showcase/styles.module.css +++ b/website/src/pages/showcase/styles.module.css @@ -52,6 +52,17 @@ margin-right: 0; } +.searchContainer { + margin-left: auto; +} + +.searchContainer input { + height: 30px; + border-radius: 15px; + padding: 10px; + border: 1px solid gray; +} + .showcaseList { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));