search
This commit is contained in:
parent
a7e4e82765
commit
66a770faec
2 changed files with 87 additions and 7 deletions
|
@ -6,10 +6,15 @@
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
|
|
||||||
min-width: 500px;
|
width: 500px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
|
|
||||||
padding: 12px;
|
padding: 6px;
|
||||||
|
|
||||||
|
text-wrap: wrap;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
|
@ -19,3 +24,30 @@
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searchResults {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchResult {
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: left;
|
||||||
|
background-color: unset;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,10 +11,18 @@ import {
|
||||||
useFocus,
|
useFocus,
|
||||||
useInteractions,
|
useInteractions,
|
||||||
} from "@floating-ui/react";
|
} from "@floating-ui/react";
|
||||||
import { useState } from "react";
|
import { useDebounce } from "use-debounce";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { atom, useAtom, useSetAtom } from "jotai";
|
||||||
|
import { nodesOpenedAtom } from "../App";
|
||||||
|
|
||||||
|
const searchQueryAtom = atom("");
|
||||||
|
const showMenuAtom = atom(false);
|
||||||
|
|
||||||
export default function SearchBar() {
|
export default function SearchBar() {
|
||||||
const [showMenu, setShowMenu] = useState(() => false);
|
const [showMenu, setShowMenu] = useAtom(showMenuAtom);
|
||||||
|
const [searchQuery, setSearchQuery] = useAtom(searchQueryAtom);
|
||||||
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
|
|
||||||
const { refs, context, floatingStyles } = useFloating({
|
const { refs, context, floatingStyles } = useFloating({
|
||||||
placement: "bottom-start",
|
placement: "bottom-start",
|
||||||
|
@ -29,6 +37,22 @@ export default function SearchBar() {
|
||||||
useDismiss(context),
|
useDismiss(context),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchResults([]);
|
||||||
|
const trimmed = searchQuery.trim();
|
||||||
|
if (trimmed === "") return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set("query", trimmed);
|
||||||
|
const resp = await fetch(
|
||||||
|
`http://localhost:5195/node/search?${params.toString()}`,
|
||||||
|
);
|
||||||
|
const data = await resp.json();
|
||||||
|
setSearchResults(data.results);
|
||||||
|
})();
|
||||||
|
}, [searchQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
|
@ -38,6 +62,8 @@ export default function SearchBar() {
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
onFocus={() => setShowMenu(true)}
|
onFocus={() => setShowMenu(true)}
|
||||||
ref={refs.setReference}
|
ref={refs.setReference}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(evt) => setSearchQuery(evt.target.value)}
|
||||||
{...getReferenceProps()}
|
{...getReferenceProps()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,7 +77,7 @@ export default function SearchBar() {
|
||||||
style={{ ...floatingStyles }}
|
style={{ ...floatingStyles }}
|
||||||
{...getFloatingProps()}
|
{...getFloatingProps()}
|
||||||
>
|
>
|
||||||
<SearchMenu />
|
<SearchMenu results={searchResults} />
|
||||||
</div>
|
</div>
|
||||||
</FloatingOverlay>
|
</FloatingOverlay>
|
||||||
</FloatingPortal>
|
</FloatingPortal>
|
||||||
|
@ -60,6 +86,28 @@ export default function SearchBar() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchMenu() {
|
function SearchMenu({ results }) {
|
||||||
return <>Search suggestions...</>;
|
const setNodesOpened = useSetAtom(nodesOpenedAtom);
|
||||||
|
const setSearchQuery = useSetAtom(searchQueryAtom);
|
||||||
|
const setShowMenu = useSetAtom(showMenuAtom);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.searchResults}>
|
||||||
|
{results.map((result) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={result.score.toString()}
|
||||||
|
className={styles.searchResult}
|
||||||
|
onClick={() => {
|
||||||
|
setSearchQuery("");
|
||||||
|
setShowMenu(false);
|
||||||
|
setNodesOpened((prev) => [result.node_id, ...prev]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles.title}>{result.title}</div>
|
||||||
|
<div className={styles.subtitle}>{result.score}</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue