mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: searchbar
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { ApiItemKind } from '@microsoft/api-extractor-model';
|
||||
import { Dialog, useDialogState } from 'ariakit/dialog';
|
||||
import { Dialog } from 'ariakit/dialog';
|
||||
import { Command } from 'cmdk';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from 'react-icons/vsc';
|
||||
import { useKey } from 'react-use';
|
||||
import useSWR from 'swr';
|
||||
import { useCmdK } from '~/contexts/cmdK';
|
||||
import { PACKAGES } from '~/util/constants';
|
||||
import { fetcher } from '~/util/fetcher';
|
||||
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 dialog = useDialogState();
|
||||
const dialog = useCmdK();
|
||||
const [search, setSearch] = useState('');
|
||||
const [page, setPage] = 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"
|
||||
onSelect={() => {
|
||||
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">
|
||||
@@ -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"
|
||||
onSelect={() => {
|
||||
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 flex-row place-items-center gap-4">
|
||||
{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>
|
||||
<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">
|
||||
@@ -142,7 +143,7 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
|
||||
|
||||
return false;
|
||||
},
|
||||
dialog.toggle,
|
||||
dialog!.toggle,
|
||||
{ event: 'keydown', options: {} },
|
||||
[],
|
||||
);
|
||||
@@ -154,11 +155,12 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialog.open) {
|
||||
if (!dialog!.open) {
|
||||
setSearch('');
|
||||
setPage('');
|
||||
}
|
||||
}, [dialog.open]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialog!.open]);
|
||||
|
||||
useEffect(() => {
|
||||
const searchDoc = async (searchString: string) => {
|
||||
@@ -175,7 +177,7 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
|
||||
}, [search]);
|
||||
|
||||
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
|
||||
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"
|
||||
@@ -183,7 +185,7 @@ export function CmdkDialog({ currentPackageName }: { currentPackageName?: string
|
||||
>
|
||||
<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"
|
||||
placeholder="Type to search..."
|
||||
placeholder="Quick search..."
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
@@ -8,12 +8,22 @@ import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { type PropsWithChildren, useState, useEffect, useMemo, Fragment } from 'react';
|
||||
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 useSWR from 'swr';
|
||||
import vercelLogo from '../assets/powered-by-vercel.svg';
|
||||
import { CmdkDialog } from './Cmdk';
|
||||
import { CmdKDialog } from './CmdK';
|
||||
import { SidebarItems } from './SidebarItems';
|
||||
import { useCmdK } from '~/contexts/cmdK';
|
||||
import { PACKAGES } from '~/util/constants';
|
||||
import { fetcher } from '~/util/fetcher';
|
||||
import type { findMember } from '~/util/model.server';
|
||||
@@ -49,6 +59,7 @@ export function SidebarLayout({
|
||||
children,
|
||||
}: PropsWithChildren<Partial<SidebarLayoutProps>>) {
|
||||
const router = useRouter();
|
||||
const dialog = useCmdK();
|
||||
const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState('');
|
||||
const { data: versions } = useSWR<string[]>(`https://docs.discordjs.dev/api/info?package=${packageName}`, fetcher);
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
@@ -157,7 +168,20 @@ export function SidebarLayout({
|
||||
<VscMenu size={24} />
|
||||
</Button>
|
||||
<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
|
||||
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"
|
||||
@@ -315,7 +339,7 @@ export function SidebarLayout({
|
||||
</footer>
|
||||
</article>
|
||||
</main>
|
||||
<CmdkDialog currentPackageName={packageName} />
|
||||
<CmdKDialog currentPackageName={packageName} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
15
packages/website/src/contexts/cmdK.tsx
Normal file
15
packages/website/src/contexts/cmdK.tsx
Normal 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);
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import { Function } from '~/components/model/Function';
|
||||
import { Interface } from '~/components/model/Interface';
|
||||
import { TypeAlias } from '~/components/model/TypeAlias';
|
||||
import { Variable } from '~/components/model/Variable';
|
||||
import { CmdKProvider } from '~/contexts/cmdK';
|
||||
import { MemberProvider } from '~/contexts/member';
|
||||
import { PACKAGES } from '~/util/constants';
|
||||
import { findMember, findMemberByKey } from '~/util/model.server';
|
||||
@@ -256,23 +257,25 @@ export default function SlugPage(props: Partial<SidebarLayoutProps & { error?: s
|
||||
return props.error ? (
|
||||
<div className="flex h-full max-h-full w-full max-w-full flex-row">{props.error}</div>
|
||||
) : (
|
||||
<MemberProvider member={props.data?.member}>
|
||||
<SidebarLayout {...props}>
|
||||
{props.data?.member ? (
|
||||
<>
|
||||
<Head>
|
||||
<title key="title">{name}</title>
|
||||
<meta key="og_title" property="og:title" content={ogTitle} />
|
||||
</Head>
|
||||
{member(props.data.member)}
|
||||
</>
|
||||
) : props.data?.source ? (
|
||||
<div className="prose max-w-none">
|
||||
<MDXRemote {...props.data.source} />
|
||||
</div>
|
||||
) : null}
|
||||
</SidebarLayout>
|
||||
</MemberProvider>
|
||||
<CmdKProvider>
|
||||
<MemberProvider member={props.data?.member}>
|
||||
<SidebarLayout {...props}>
|
||||
{props.data?.member ? (
|
||||
<>
|
||||
<Head>
|
||||
<title key="title">{name}</title>
|
||||
<meta key="og_title" property="og:title" content={ogTitle} />
|
||||
</Head>
|
||||
{member(props.data.member)}
|
||||
</>
|
||||
) : props.data?.source ? (
|
||||
<div className="prose max-w-none">
|
||||
<MDXRemote {...props.data.source} />
|
||||
</div>
|
||||
) : null}
|
||||
</SidebarLayout>
|
||||
</MemberProvider>
|
||||
</CmdKProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,14 @@ export default function IndexRoute() {
|
||||
>
|
||||
Guide <FiExternalLink />
|
||||
</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>
|
||||
<SyntaxHighlighter code={CODE_EXAMPLE} />
|
||||
|
||||
Reference in New Issue
Block a user