feat: searchbar

This commit is contained in:
iCrawl
2022-09-18 20:16:50 +02:00
parent 8a8d519c9c
commit 2fc3d86f56
5 changed files with 84 additions and 32 deletions

View File

@@ -1,5 +1,5 @@
import type { ApiItemKind } from '@microsoft/api-extractor-model'; import type { ApiItemKind } from '@microsoft/api-extractor-model';
import { Dialog, useDialogState } from 'ariakit/dialog'; import { Dialog } from 'ariakit/dialog';
import { Command } from 'cmdk'; import { Command } from 'cmdk';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
@@ -17,6 +17,7 @@ import {
} from 'react-icons/vsc'; } from 'react-icons/vsc';
import { useKey } from 'react-use'; import { useKey } from 'react-use';
import useSWR from 'swr'; import useSWR from 'swr';
import { useCmdK } from '~/contexts/cmdK';
import { PACKAGES } from '~/util/constants'; import { PACKAGES } from '~/util/constants';
import { fetcher } from '~/util/fetcher'; import { fetcher } from '~/util/fetcher';
import { client } from '~/util/search'; import { client } from '~/util/search';
@@ -40,9 +41,9 @@ function resolveIcon(item: keyof ApiItemKind) {
} }
} }
export function CmdkDialog({ currentPackageName }: { currentPackageName?: string | undefined }) { export function CmdKDialog({ currentPackageName }: { currentPackageName?: string | undefined }) {
const router = useRouter(); const router = useRouter();
const dialog = useDialogState(); const dialog = useCmdK();
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [page, setPage] = useState(''); const [page, setPage] = useState('');
const [packageName, setPackageName] = useState(''); const [packageName, setPackageName] = useState('');
@@ -86,7 +87,7 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
className="dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 [&[aria-selected]]:ring-blurple [&[aria-selected]]:ring-offset-0 [&[aria-selected]]:ring-width-4 [&[aria-selected]]:ring flex flex transform-gpu cursor-pointer select-none appearance-none flex-col place-content-center rounded bg-transparent px-4 py-2 text-base font-semibold leading-none text-black outline-0 hover:bg-neutral-100 active:translate-y-px active:bg-neutral-200 dark:text-white" className="dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 [&[aria-selected]]:ring-blurple [&[aria-selected]]:ring-offset-0 [&[aria-selected]]:ring-width-4 [&[aria-selected]]:ring flex flex transform-gpu cursor-pointer select-none appearance-none flex-col place-content-center rounded bg-transparent px-4 py-2 text-base font-semibold leading-none text-black outline-0 hover:bg-neutral-100 active:translate-y-px active:bg-neutral-200 dark:text-white"
onSelect={() => { onSelect={() => {
void router.push(`/docs/packages/${packageName}/${version}`); void router.push(`/docs/packages/${packageName}/${version}`);
dialog.setOpen(false); dialog!.setOpen(false);
}} }}
> >
<div className="flex grow flex-row place-content-between place-items-center gap-4"> <div className="flex grow flex-row place-content-between place-items-center gap-4">
@@ -111,13 +112,13 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
className="dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 [&[aria-selected]]:ring-blurple [&[aria-selected]]:ring-offset-0 [&[aria-selected]]:ring-width-4 [&[aria-selected]]:ring flex flex transform-gpu cursor-pointer select-none appearance-none flex-col place-content-center rounded bg-transparent px-4 py-2 text-base font-semibold leading-none text-black outline-0 hover:bg-neutral-100 active:translate-y-px active:bg-neutral-200 dark:text-white" className="dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 [&[aria-selected]]:ring-blurple [&[aria-selected]]:ring-offset-0 [&[aria-selected]]:ring-width-4 [&[aria-selected]]:ring flex flex transform-gpu cursor-pointer select-none appearance-none flex-col place-content-center rounded bg-transparent px-4 py-2 text-base font-semibold leading-none text-black outline-0 hover:bg-neutral-100 active:translate-y-px active:bg-neutral-200 dark:text-white"
onSelect={() => { onSelect={() => {
void router.push(item.path); void router.push(item.path);
dialog.setOpen(false); dialog!.setOpen(false);
}} }}
> >
<div className="flex grow flex-row place-content-between place-items-center gap-4"> <div className="flex grow flex-row place-content-between place-items-center gap-4">
<div className="flex flex-row place-items-center gap-4"> <div className="flex flex-row place-items-center gap-4">
{resolveIcon(item.kind)} {resolveIcon(item.kind)}
<div className="w-50 flex flex-col sm:w-full"> <div className="w-50 sm:w-100 flex flex-col">
<h2 className="font-semibold">{item.name}</h2> <h2 className="font-semibold">{item.name}</h2>
<div className="line-clamp-1 text-sm font-normal">{item.summary}</div> <div className="line-clamp-1 text-sm font-normal">{item.summary}</div>
<div className="line-clamp-1 hidden text-xs font-light opacity-75 dark:opacity-50 sm:block"> <div className="line-clamp-1 hidden text-xs font-light opacity-75 dark:opacity-50 sm:block">
@@ -142,7 +143,7 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
return false; return false;
}, },
dialog.toggle, dialog!.toggle,
{ event: 'keydown', options: {} }, { event: 'keydown', options: {} },
[], [],
); );
@@ -154,11 +155,12 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
); );
useEffect(() => { useEffect(() => {
if (!dialog.open) { if (!dialog!.open) {
setSearch(''); setSearch('');
setPage(''); setPage('');
} }
}, [dialog.open]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [dialog!.open]);
useEffect(() => { useEffect(() => {
const searchDoc = async (searchString: string) => { const searchDoc = async (searchString: string) => {
@@ -175,7 +177,7 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
}, [search]); }, [search]);
return ( return (
<Dialog className="fixed top-1/4 left-1/2 z-50 -translate-x-1/2" state={dialog}> <Dialog className="fixed top-1/4 left-1/2 z-50 -translate-x-1/2" state={dialog!}>
<Command <Command
label="Command Menu" label="Command Menu"
className="dark:bg-dark-300 min-w-xs sm:min-w-lg max-w-xs rounded bg-white sm:max-w-lg" className="dark:bg-dark-300 min-w-xs sm:min-w-lg max-w-xs rounded bg-white sm:max-w-lg"
@@ -183,7 +185,7 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
> >
<Command.Input <Command.Input
className="dark:bg-dark-300 caret-blurple placeholder:text-dark-300/75 mt-4 border-0 bg-white p-4 pt-0 text-lg outline-0 dark:placeholder:text-white/75" className="dark:bg-dark-300 caret-blurple placeholder:text-dark-300/75 mt-4 border-0 bg-white p-4 pt-0 text-lg outline-0 dark:placeholder:text-white/75"
placeholder="Type to search..." placeholder="Quick search..."
value={search} value={search}
onValueChange={setSearch} onValueChange={setSearch}
/> />

View File

@@ -8,12 +8,22 @@ import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
import { useTheme } from 'next-themes'; import { useTheme } from 'next-themes';
import { type PropsWithChildren, useState, useEffect, useMemo, Fragment } from 'react'; import { type PropsWithChildren, useState, useEffect, useMemo, Fragment } from 'react';
import { Scrollbars } from 'react-custom-scrollbars-2'; import { Scrollbars } from 'react-custom-scrollbars-2';
import { VscChevronDown, VscColorMode, VscGithubInverted, VscMenu, VscPackage, VscVersions } from 'react-icons/vsc'; import { FiCommand } from 'react-icons/fi';
import {
VscChevronDown,
VscColorMode,
VscGithubInverted,
VscMenu,
VscPackage,
VscSearch,
VscVersions,
} from 'react-icons/vsc';
import { useMedia /* useLockBodyScroll */ } from 'react-use'; import { useMedia /* useLockBodyScroll */ } from 'react-use';
import useSWR from 'swr'; import useSWR from 'swr';
import vercelLogo from '../assets/powered-by-vercel.svg'; import vercelLogo from '../assets/powered-by-vercel.svg';
import { CmdkDialog } from './Cmdk'; import { CmdKDialog } from './CmdK';
import { SidebarItems } from './SidebarItems'; import { SidebarItems } from './SidebarItems';
import { useCmdK } from '~/contexts/cmdK';
import { PACKAGES } from '~/util/constants'; import { PACKAGES } from '~/util/constants';
import { fetcher } from '~/util/fetcher'; import { fetcher } from '~/util/fetcher';
import type { findMember } from '~/util/model.server'; import type { findMember } from '~/util/model.server';
@@ -49,6 +59,7 @@ export function SidebarLayout({
children, children,
}: PropsWithChildren<Partial<SidebarLayoutProps>>) { }: PropsWithChildren<Partial<SidebarLayoutProps>>) {
const router = useRouter(); const router = useRouter();
const dialog = useCmdK();
const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState(''); const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState('');
const { data: versions } = useSWR<string[]>(`https://docs.discordjs.dev/api/info?package=${packageName}`, fetcher); const { data: versions } = useSWR<string[]>(`https://docs.discordjs.dev/api/info?package=${packageName}`, fetcher);
const { resolvedTheme, setTheme } = useTheme(); const { resolvedTheme, setTheme } = useTheme();
@@ -157,7 +168,20 @@ export function SidebarLayout({
<VscMenu size={24} /> <VscMenu size={24} />
</Button> </Button>
<div className="hidden md:flex md:flex-row">{breadcrumbs}</div> <div className="hidden md:flex md:flex-row">{breadcrumbs}</div>
<div className="flex flex-row gap-4"> <div className="flex flex-row place-items-center gap-4">
<Button
as="div"
className="dark:bg-dark-800 rounded bg-white px-4 py-2.5"
onClick={() => dialog?.toggle()}
>
<div className="flex flex-row place-items-center gap-4">
<VscSearch size={18} />
<span className="opacity-65">Search...</span>
<div className="opacity-65 flex flex-row place-items-center gap-2">
<FiCommand size={18} /> K
</div>
</div>
</Button>
<Button <Button
as="a" as="a"
className="flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline active:translate-y-px" className="flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline active:translate-y-px"
@@ -315,7 +339,7 @@ export function SidebarLayout({
</footer> </footer>
</article> </article>
</main> </main>
<CmdkDialog currentPackageName={packageName} /> <CmdKDialog currentPackageName={packageName} />
</> </>
); );
} }

View File

@@ -0,0 +1,15 @@
import { type DisclosureState, useDialogState } from 'ariakit';
import type { PropsWithChildren } from 'react';
import { createContext, useContext } from 'react';
export const CmdKContext = createContext<DisclosureState | null>(null);
export const CmdKProvider = ({ children }: PropsWithChildren) => {
const dialog = useDialogState();
return <CmdKContext.Provider value={dialog}>{children}</CmdKContext.Provider>;
};
export function useCmdK() {
return useContext(CmdKContext);
}

View File

@@ -37,6 +37,7 @@ import { Function } from '~/components/model/Function';
import { Interface } from '~/components/model/Interface'; import { Interface } from '~/components/model/Interface';
import { TypeAlias } from '~/components/model/TypeAlias'; import { TypeAlias } from '~/components/model/TypeAlias';
import { Variable } from '~/components/model/Variable'; import { Variable } from '~/components/model/Variable';
import { CmdKProvider } from '~/contexts/cmdK';
import { MemberProvider } from '~/contexts/member'; import { MemberProvider } from '~/contexts/member';
import { PACKAGES } from '~/util/constants'; import { PACKAGES } from '~/util/constants';
import { findMember, findMemberByKey } from '~/util/model.server'; import { findMember, findMemberByKey } from '~/util/model.server';
@@ -256,23 +257,25 @@ export default function SlugPage(props: Partial<SidebarLayoutProps & { error?: s
return props.error ? ( return props.error ? (
<div className="flex h-full max-h-full w-full max-w-full flex-row">{props.error}</div> <div className="flex h-full max-h-full w-full max-w-full flex-row">{props.error}</div>
) : ( ) : (
<MemberProvider member={props.data?.member}> <CmdKProvider>
<SidebarLayout {...props}> <MemberProvider member={props.data?.member}>
{props.data?.member ? ( <SidebarLayout {...props}>
<> {props.data?.member ? (
<Head> <>
<title key="title">{name}</title> <Head>
<meta key="og_title" property="og:title" content={ogTitle} /> <title key="title">{name}</title>
</Head> <meta key="og_title" property="og:title" content={ogTitle} />
{member(props.data.member)} </Head>
</> {member(props.data.member)}
) : props.data?.source ? ( </>
<div className="prose max-w-none"> ) : props.data?.source ? (
<MDXRemote {...props.data.source} /> <div className="prose max-w-none">
</div> <MDXRemote {...props.data.source} />
) : null} </div>
</SidebarLayout> ) : null}
</MemberProvider> </SidebarLayout>
</MemberProvider>
</CmdKProvider>
); );
} }

View File

@@ -33,6 +33,14 @@ export default function IndexRoute() {
> >
Guide <FiExternalLink /> Guide <FiExternalLink />
</a> </a>
<a
className="dark:bg-dark-400 dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 border-light-900 hover:bg-light-200 active:bg-light-300 flex h-11 transform-gpu cursor-pointer select-none appearance-none place-items-center gap-2 rounded border bg-transparent px-4 text-base font-semibold leading-none text-black no-underline active:translate-y-px dark:text-white"
href="https://github.com/discordjs/discord.js"
target="_blank"
rel="noopener noreferrer"
>
GitHub <FiExternalLink />
</a>
</div> </div>
</div> </div>
<SyntaxHighlighter code={CODE_EXAMPLE} /> <SyntaxHighlighter code={CODE_EXAMPLE} />