mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-06 10:20:09 +02:00
feat(website): search in showcase (#6333)
* feat(website): search in showcase * fix SSR
This commit is contained in:
parent
95955ecfb0
commit
9c4187a5b9
3 changed files with 79 additions and 21 deletions
|
@ -34,7 +34,9 @@ export default function ShowcaseFilterToggle(): JSX.Element {
|
||||||
setOperator((o) => !o);
|
setOperator((o) => !o);
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const searchParams = new URLSearchParams(location.search);
|
||||||
searchParams.delete(OperatorQueryKey);
|
searchParams.delete(OperatorQueryKey);
|
||||||
searchParams.append(OperatorQueryKey, operator ? 'OR' : 'AND');
|
if (!operator) {
|
||||||
|
searchParams.append(OperatorQueryKey, operator ? 'OR' : 'AND');
|
||||||
|
}
|
||||||
history.push({
|
history.push({
|
||||||
...location,
|
...location,
|
||||||
search: searchParams.toString(),
|
search: searchParams.toString(),
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
import ShowcaseTooltip from './_components/ShowcaseTooltip';
|
import ShowcaseTooltip from './_components/ShowcaseTooltip';
|
||||||
|
|
||||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||||
import {useLocation} from '@docusaurus/router';
|
import {useHistory, useLocation} from '@docusaurus/router';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
@ -64,11 +64,24 @@ export function prepareUserState(): UserState | undefined {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SearchNameQueryKey = 'name';
|
||||||
|
|
||||||
|
function readSearchName(search: string) {
|
||||||
|
return new URLSearchParams(search).get(SearchNameQueryKey);
|
||||||
|
}
|
||||||
|
|
||||||
function filterUsers(
|
function filterUsers(
|
||||||
users: User[],
|
users: User[],
|
||||||
selectedTags: TagType[],
|
selectedTags: TagType[],
|
||||||
operator: Operator,
|
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) {
|
if (selectedTags.length === 0) {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
@ -85,33 +98,23 @@ function filterUsers(
|
||||||
}
|
}
|
||||||
|
|
||||||
function useFilteredUsers() {
|
function useFilteredUsers() {
|
||||||
const selectedTags = useSelectedTags();
|
|
||||||
const location = useLocation<UserState>();
|
const location = useLocation<UserState>();
|
||||||
const [operator, setOperator] = useState<Operator>('OR');
|
const [operator, setOperator] = useState<Operator>('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<UserState>();
|
|
||||||
|
|
||||||
// On SSR / first mount (hydration) no tag is selected
|
// On SSR / first mount (hydration) no tag is selected
|
||||||
const [selectedTags, setSelectedTags] = useState<TagType[]>([]);
|
const [selectedTags, setSelectedTags] = useState<TagType[]>([]);
|
||||||
|
const [searchName, setSearchName] = useState<string | null>(null);
|
||||||
// Sync tags from QS to state (delayed on purpose to avoid SSR/Client hydration mismatch)
|
// Sync tags from QS to state (delayed on purpose to avoid SSR/Client hydration mismatch)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedTags(readSearchTags(location.search));
|
setSelectedTags(readSearchTags(location.search));
|
||||||
|
setOperator(readOperator(location.search));
|
||||||
|
setSearchName(readSearchName(location.search));
|
||||||
restoreUserState(location.state);
|
restoreUserState(location.state);
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
return selectedTags;
|
return useMemo(
|
||||||
|
() => filterUsers(sortedUsers, selectedTags, operator, searchName),
|
||||||
|
[selectedTags, operator, searchName],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ShowcaseHeader() {
|
function ShowcaseHeader() {
|
||||||
|
@ -190,8 +193,41 @@ const otherUsers = sortedUsers.filter(
|
||||||
(user) => !user.tags.includes('favorite'),
|
(user) => !user.tags.includes('favorite'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function SearchBar() {
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
|
const [value, setValue] = useState<string | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(readSearchName(location.search));
|
||||||
|
}, [location]);
|
||||||
|
return (
|
||||||
|
<div className={styles.searchContainer}>
|
||||||
|
<input
|
||||||
|
id="searchbar"
|
||||||
|
placeholder="Search for site name..."
|
||||||
|
value={value ?? undefined}
|
||||||
|
onInput={(e) => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ShowcaseCards() {
|
function ShowcaseCards() {
|
||||||
const selectedTags = useSelectedTags();
|
|
||||||
const filteredUsers = useFilteredUsers();
|
const filteredUsers = useFilteredUsers();
|
||||||
|
|
||||||
if (filteredUsers.length === 0) {
|
if (filteredUsers.length === 0) {
|
||||||
|
@ -199,6 +235,7 @@ function ShowcaseCards() {
|
||||||
<section className="margin-top--lg margin-bottom--xl">
|
<section className="margin-top--lg margin-bottom--xl">
|
||||||
<div className="container padding-vert--md text--center">
|
<div className="container padding-vert--md text--center">
|
||||||
<h2>No result</h2>
|
<h2>No result</h2>
|
||||||
|
<SearchBar />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -206,7 +243,7 @@ function ShowcaseCards() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="margin-top--lg margin-bottom--xl">
|
<section className="margin-top--lg margin-bottom--xl">
|
||||||
{selectedTags.length === 0 ? (
|
{filteredUsers.length === sortedUsers.length ? (
|
||||||
<>
|
<>
|
||||||
<div className={styles.showcaseFavorite}>
|
<div className={styles.showcaseFavorite}>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
|
@ -217,6 +254,7 @@ function ShowcaseCards() {
|
||||||
)}>
|
)}>
|
||||||
<h2>Our favorites</h2>
|
<h2>Our favorites</h2>
|
||||||
<FavoriteIcon svgClass={styles.svgIconFavorite} />
|
<FavoriteIcon svgClass={styles.svgIconFavorite} />
|
||||||
|
<SearchBar />
|
||||||
</div>
|
</div>
|
||||||
<ul className={clsx('container', styles.showcaseList)}>
|
<ul className={clsx('container', styles.showcaseList)}>
|
||||||
{favoriteUsers.map((user) => (
|
{favoriteUsers.map((user) => (
|
||||||
|
@ -236,6 +274,13 @@ function ShowcaseCards() {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'margin-bottom--md',
|
||||||
|
styles.showcaseFavoriteHeader,
|
||||||
|
)}>
|
||||||
|
<SearchBar />
|
||||||
|
</div>
|
||||||
<ul className={styles.showcaseList}>
|
<ul className={styles.showcaseList}>
|
||||||
{filteredUsers.map((user) => (
|
{filteredUsers.map((user) => (
|
||||||
<ShowcaseCard key={user.title} user={user} />
|
<ShowcaseCard key={user.title} user={user} />
|
||||||
|
|
|
@ -52,6 +52,17 @@
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searchContainer {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchContainer input {
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid gray;
|
||||||
|
}
|
||||||
|
|
||||||
.showcaseList {
|
.showcaseList {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue