Skip to content

Commit

Permalink
Unified search (#1258)
Browse files Browse the repository at this point in the history
Co-authored-by: Manu Salmeron <[email protected]>
Co-authored-by: Lucas Arce <[email protected]>
Co-authored-by: Guido <[email protected]>
  • Loading branch information
4 people authored Aug 25, 2023
1 parent 87a5f39 commit 86ef606
Show file tree
Hide file tree
Showing 8 changed files with 635 additions and 59 deletions.
28 changes: 28 additions & 0 deletions src/assets/scss/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,34 @@ html[data-theme="light"] {
}
}

[class*="SearchWrapper"] {
> input {
background-color: #fff;

}
span, button {
> svg {
fill: var(--liftedBlack);
path {
fill: var(--liftedBlack);
}
}
}
> p {
background-color: #f4f4f4;
}
> [class*="SearchResult"] {
a {
color: black;
&:hover {
color: var(--casperBlue) !important;
}
small {
color: black !important;
}
}
}
}
[class*="container_search"] {
--casperYellow: var(--casperBlue);
> input {
Expand Down
5 changes: 3 additions & 2 deletions src/theme/Navbar/Content/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ export default function NavbarContent() {
<NavbarItems items={rightItems} />
{/* Doc NavBar theme color toggle disabled */}
{/* <NavbarColorModeToggle className={styles.colorModeToggle} /> */}
{!searchBarItem && (
{/* Disabled Search Bar. Unified with the Website Search Bar */}
{/*!searchBarItem && (
<NavbarSearch>
<SearchBar />
</NavbarSearch>
)}
)*/}
</>
}
/>
Expand Down
175 changes: 141 additions & 34 deletions src/theme/Navbar/ExtendedNavbar/Search/SearchResult/index.tsx

Large diffs are not rendered by default.

126 changes: 116 additions & 10 deletions src/theme/Navbar/ExtendedNavbar/Search/SearchResult/styles.module.scss
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
@use "../../../../../assets/scss/mixins";

.results_container {
position: absolute;
width: 100%;
.results_portal_title {
background-color: var(--liftedBlack);
border: 1px solid rgba(255, 255, 255, 0.25);
margin-bottom: 0;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
cursor: pointer;

svg {
pointer-events: none;
path {
fill: var(--casperWhite);
}
}
> p {
margin: 0px;
}
}
.results_container {
background-color: var(--black);
border-radius: 2px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
gap: 27px;
padding: 20px 15px;
max-height: 381px;
padding: 10px 15px;
max-height: 265px;
overflow-y: scroll;
transition: all 500ms;

@include mixins.custom_scrollbar(var(--black) var(--casperWhite));

Expand All @@ -25,8 +43,23 @@
width: 100%;
color: #ffffff;
text-decoration: none;
max-height: 50px;

cursor: pointer;

span,
p {
overflow: hidden;
text-overflow: ellipsis;
width: 90%;
}
a {
margin-left: 5px;
color: var(--casperWhite);
font-size: 14px;

&:hover {
color: var(--casperYellow);
}
}

svg {
path {
Expand All @@ -49,10 +82,15 @@
display: flex;
flex-direction: column;
gap: 3px;
width: 100%;
width: 80%;
justify-content: flex-start;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
p {
font-weight: 500;
margin-bottom: 0px;
}

> small {
max-width: 100%;
Expand All @@ -61,17 +99,85 @@
color: #c3c3c3;
}
}
.docElement {
width: 80%;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
display: flex;
justify-content: center;
flex-direction: column;
gap: 5px;
font-size: 12px;
color: #c3c3c3;

> :first-child {
font-weight: 500;
cursor: default;
color: var(--casperWhite);
font-size: 16px;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
&:hover {
@include mixins.transition(0.3s all);
color: var(--casperYellow);
}
}
}

em {
color: var(--casperYellow);
background-color: transparent;
font-style: normal;
}

:global {
.algolia-docsearch-suggestion--highlight {
color: var(--casperYellow);
}
}
}
}
}

.showMore {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
padding-left: 45px;
padding-bottom: 6px;
padding-top: 6px;
background-color: var(--black);
border: none;
&:hover {
text-decoration: underline;
}
}

.centerSpinner {
margin: auto;
}

.hiddenResults {
transition: all 500ms;
max-height: 0px;
padding: 0px 15px;
width: 100%;
overflow-x: hidden;
}

.rotateSvg {
transform: rotate(180deg);
}

.hitWeighTitle {
font-weight: 500;
}

@media (max-width: 996px) {
.results_container {
z-index: 6;
}
}
}
155 changes: 155 additions & 0 deletions src/theme/Navbar/ExtendedNavbar/Search/SearchWrapper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React, { useEffect, useRef, useState } from "react";
import useEventListener from "../../../../../hooks/useEventListener";
import icons from "../../../../../icons";
import SearchResult from "../SearchResult";
import useClickOutside from "../UseClickOutside";
import styles from "./styles.module.scss";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
interface ISearchWrapperProps {
searchIndexes: any[];
locale: string;
siteUrl: string;
placeholder: string;
hitsPerIndex: number;
}

export default function SearchWrapper({ searchIndexes, locale, siteUrl, placeholder, hitsPerIndex = 20 }: ISearchWrapperProps) {
const { siteConfig } = useDocusaurusContext();
const [searchTerm, setSearchTerm] = useState<string>("");
const refInput = useRef<HTMLInputElement>(null);
const [hasFocus, setHasFocus] = useState<boolean>(false);
const [showResults, setShowResults] = useState<boolean>(false);
const [hits, setHits] = useState<any>({});
const docsIndexName = (siteConfig.themeConfig.algolia?.indexName as string) ?? "casperlabs";
const siteIndexName = (siteConfig.customFields.siteAlgoliaIndexName as string) ?? "casper";

let delayDebounceFn: NodeJS.Timeout;

const handleKeyClose = (e: KeyboardEvent): void => {
if (e.key === "Escape") resetState();
};

function resetState() {
setSearchTerm("");
clearInput();
}

function triggerSearchIndexes(val: string) {
let promiseArr = [];

for (let index of searchIndexes) {
promiseArr.push(
index.client.search(val, {
hitsPerPage: index.client.indexName === docsIndexName ? 4 : hitsPerIndex,
}),
);
}

Promise.allSettled(promiseArr)
.then((results: any) => {
const hits = {};
for (let i = 0; i < results.length; i++) {
let parsedHits: any[] = [];
const basePath = searchIndexes[i].base;
const result = results[i];
if (result.status === "fulfilled") {
let parsedRes = result.value.hits;

parsedRes = parsedRes.map((element: any) => {
return { ...element, basePath: basePath, path: basePath ? basePath + element.path : element.path };
});

parsedHits = [...parsedHits, ...parsedRes];
hits[`${searchIndexes[i].client.indexName}`] = parsedHits;
} else {
console.log(`${result.reason.name} ${result.reason.message}`);
}
}
setHits(hits);
})
.catch((err) => console.log(err));
}

function clearSearch() {
setHits([]);
}

function handleChangeSearchTerm(e: React.ChangeEvent<HTMLInputElement>) {
clearTimeout(delayDebounceFn);
delayDebounceFn = setTimeout(() => {
setShowResults(false);
const value = e.target.value;
setSearchTerm(value);
if (value) {
triggerSearchIndexes(value);
} else {
clearSearch();
}
}, 500);
}

useEffect(() => {
// -- Only true if search term has a value
// -- Avoid to show the empty results
setShowResults(searchTerm || hits.length > 0 ? true : false);
}, [searchTerm, hits]);

useEventListener("keydown", handleKeyClose);

useClickOutside(refInput, (isInside: boolean) => setHasFocus(isInside));

function clearInput() {
const buttons = document.getElementsByClassName(styles.container_input);
for (const button of buttons) {
(button as HTMLInputElement).value = "";
}
setSearchTerm("");
setShowResults(false);
}

return (
<div ref={refInput} tabIndex={-1} className={styles.container} onFocus={() => setHasFocus(true)}>
<>
<input
id="inputSearch"
tabIndex={0}
className={styles.container_input}
onChange={handleChangeSearchTerm}
placeholder={placeholder}
autoComplete="off"
/>
<span className={styles.container_icon_search}>{icons.search}</span>
{searchTerm && (
<button className={styles.container_icon_cancel} onClick={() => clearInput()}>
{icons.cancel}
</button>
)}
</>
{hasFocus && showResults && (
<>
<div className={styles.results_wrapper}>
<SearchResult
hits={hits[siteIndexName]}
searchTitle={"Portal Results"}
setHasFocus={setHasFocus}
locale={locale}
siteUrl={siteUrl}
></SearchResult>
<SearchResult
hits={hits[docsIndexName]}
searchTitle={"Documents Results"}
setHasFocus={setHasFocus}
locale={locale}
siteUrl={siteUrl}
></SearchResult>
{hits[docsIndexName] && hits[docsIndexName].length > 0 && (
<div className={`${styles.search_link} halfTitleEyebrow`}>
<a href={`/search?q=${searchTerm}`}>Show all documentation results</a>
</div>
)}
</div>
</>
)}
</div>
);
}
Loading

0 comments on commit 86ef606

Please sign in to comment.