Skip to content

Commit

Permalink
feat(search)!: Migrate to new Search UI powered by Meilisearch (#240)
Browse files Browse the repository at this point in the history
* Implement new search UI

* Move "no results" into its own component

* re-lock dependencies

* Refactor: Move search logic into reusable hooks

* Refactor: clean up, combine useSearchIndex

* Refactor: Separate QueryInput, add ability to clear results

* Cleanup

* Tweak UI

* Cleanup: Remove unused types

* Move searchParams out; lower debounce ms

* UI: Make "Clear" button smaller

* Remove OpenSearchModalButton from mobile nav

* Revert "Remove OpenSearchModalButton from mobile nav"

This reverts commit 035fc9a.

* cleanup

* Refactor to a generic response transformation method

* Remove redundant `Array.from`

* Remove baseUri from slug

* fix: close modal on result click
  • Loading branch information
half0wl authored Apr 25, 2023
1 parent b1d398b commit 5413c12
Show file tree
Hide file tree
Showing 16 changed files with 473 additions and 153 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"dayjs": "^1.10.4",
"fathom-client": "^3.1.0",
"fuse.js": "^6.4.6",
"interweave": "^13.1.0",
"meilisearch": "^0.32.3",
"nanostores": "^0.7.4",
"next": "13.2.1",
"next-contentlayer": "^0.3.0",
Expand Down
1 change: 1 addition & 0 deletions src/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const ModalDialog: React.FC<PropsWithChildren<Props>> = ({
css={[
tw`fixed top-0 right-0 bottom-0 left-0 select-none z-50`,
tw`bg-black bg-opacity-50`,
tw`overflow-scroll`,
]}
>
<FocusScope contain restoreFocus autoFocus>
Expand Down
7 changes: 4 additions & 3 deletions src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Home, Menu, X } from "react-feather";
import tw from "twin.macro";
import { Link } from "./Link";
import { Logo } from "./Logo";
import { OpenSearchModalButton } from "@/components/Search";
import { MobileSidebar } from "./Sidebar";
import { ThemeSwitcher } from "./ThemeSwitcher";

Expand Down Expand Up @@ -55,9 +56,9 @@ export const MobileNav: React.FC = () => {
<Logo tw="w-10 h-10" />
</Link>

{/* <div tw="w-full block">
<Search />
</div> */}
<div tw="w-full block">
<OpenSearchModalButton />
</div>

<div tw="flex items-center space-x-4 md:space-x-4">
<button
Expand Down
144 changes: 0 additions & 144 deletions src/components/Search.tsx

This file was deleted.

62 changes: 62 additions & 0 deletions src/components/Search/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useDebouncedSearch } from "@/hooks/useDebouncedSearch";
import { Search } from "@/types";
import React from "react";
import tw from "twin.macro";
import NoResults from "./NoResults";
import QueryInput from "./QueryInput";
import Results from "./Results";

interface Props {
closeModal: () => void;
}

const Modal: React.FC<Props> = ({ closeModal }) => {
const searchParams = {
limit: 10,
attributesToHighlight: ["*"],
highlightPreTag: "<span>",
highlightPostTag: "</span>",
};
const { clearResponse, query, setQuery, results } = useDebouncedSearch<
Search.Document,
Search.Result
>(
process.env.NEXT_PUBLIC_MEILISEARCH_HOST ?? "",
process.env.NEXT_PUBLIC_MEILISEARCH_READ_API_KEY ?? "",
process.env.NEXT_PUBLIC_MEILISEARCH_INDEX_NAME ?? "",
searchParams,
200,
);

return (
<div
css={[tw`px-2 mt-12 mb-12`, `sm:px-4`, `md:px-0 md:mt-28 md:mb-28`]}
onPointerDown={() => {
closeModal();
}}
>
<div
onPointerDown={e => e.stopPropagation()}
css={[tw`bg-background border rounded-lg w-full md:w-1/2 mx-auto`]}
>
<div className="search-input">
<QueryInput
clearResponse={clearResponse}
query={query}
setQuery={setQuery}
/>
</div>
<div className="search-results">
{results &&
(Object.keys(results).length === 0 ? (
<NoResults />
) : (
<Results closeModal={closeModal} results={results} />
))}
</div>
</div>
</div>
);
};

export default Modal;
43 changes: 43 additions & 0 deletions src/components/Search/NoResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { DiscordIcon } from "@/components/Icons";
import { Link } from "@/components/Link";
import React from "react";
import { HelpCircle, Mail } from "react-feather";
import tw, { styled } from "twin.macro";

const ContactButton = styled(Link)`
${[
tw`flex flex-row items-center gap-2 p-2`,
tw`border border-solid rounded-lg`,
tw`hover:bg-pink-100`,
tw`[&>svg]:w-8`,
tw`[&>svg]:h-8`,
]}
`;

const NoResults: React.FC = () => {
return (
<div css={[tw`flex flex-col items-center justify-center mb-4`]}>
<HelpCircle size={64} css={tw`mt-8 mb-4`} />
<div css={tw`flex flex-col items-center justify-center p-4`}>
<p css={tw`font-bold mb-4 text-center`}>
We couldn't find what you're searching for.
</p>
<div>
<p css={tw`mb-4 text-center`}>Reach out to us if you need help:</p>
<div css={tw`flex flex-row gap-4 items-center justify-center`}>
<ContactButton href="https://discord.gg/railway">
<DiscordIcon />
Discord
</ContactButton>
<ContactButton href="mailto:[email protected]">
<Mail />
Email
</ContactButton>
</div>
</div>
</div>
</div>
);
};

export default NoResults;
28 changes: 28 additions & 0 deletions src/components/Search/OpenModalButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { searchStore } from "@/store";
import React from "react";
import { Search as SearchIcon } from "react-feather";
import tw from "twin.macro";

const OpenModalButton: React.FC = () => {
return (
<>
<button
onClick={() => searchStore.set(true)}
css={[
tw`flex items-center justify-between space-x-4 w-full`,
tw`rounded border border-gray-200 cursor-pointer`,
tw`px-2 py-2 md:py-1 text-gray-300 text-left`,
tw`focus:outline-none md:hover:border-pink-300`,
]}
>
<div tw="flex items-center space-x-2">
<SearchIcon tw="w-4 h-4" />
<span tw="text-sm">Search</span>
</div>
<div tw="text-gray-300 text-sm hidden md:block">⌘K</div>
</button>
</>
);
};

export default OpenModalButton;
47 changes: 47 additions & 0 deletions src/components/Search/QueryInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from "react";
import { Search as SearchIcon } from "react-feather";
import tw from "twin.macro";

interface Props {
clearResponse: () => void;
query: string;
setQuery: (q: string) => void;
}

const QueryInput: React.FC<Props> = ({ clearResponse, query, setQuery }) => {
return (
<div css={tw`flex flex-col`}>
<form css={tw`flex flex-row`}>
<span css={tw`flex items-center px-3`}>
<SearchIcon />
</span>
<input
autoFocus
css={tw`w-full focus:outline-none bg-transparent`}
type="text"
placeholder="Search"
value={query}
onChange={e => setQuery(e.target.value)}
/>
<span css={tw`flex items-center p-2`}>
<button
css={[
tw`flex items-center justify-center w-14 h-8`,
tw`rounded border border-solid rounded-lg`,
tw`text-black text-sm dark:text-white`,
tw`hover:bg-pink-100`,
]}
onClick={e => {
e.preventDefault();
clearResponse();
}}
>
Clear
</button>
</span>
</form>
</div>
);
};

export default QueryInput;
Loading

0 comments on commit 5413c12

Please sign in to comment.