refactor: docs (#10126)

This commit is contained in:
Noel
2024-02-29 04:37:52 +01:00
committed by GitHub
parent 0f9017ef95
commit 18cce83d80
192 changed files with 8116 additions and 6321 deletions

View File

@@ -0,0 +1,67 @@
import { VscFlame } from '@react-icons/all-files/vsc/VscFlame';
import { VscInfo } from '@react-icons/all-files/vsc/VscInfo';
import { VscWarning } from '@react-icons/all-files/vsc/VscWarning';
import type { PropsWithChildren } from 'react';
interface IAlert {
readonly title?: string | undefined;
readonly type: 'danger' | 'info' | 'success' | 'warning';
}
function resolveType(type: IAlert['type']) {
switch (type) {
case 'danger': {
return {
text: 'text-red-500',
border: 'border-red-500',
icon: <VscWarning aria-hidden size={20} />,
};
}
case 'info': {
return {
text: 'text-blue-500',
border: 'border-blue-500',
icon: <VscInfo aria-hidden size={20} />,
};
}
case 'success': {
return {
text: 'text-green-500',
border: 'border-green-500',
icon: <VscFlame aria-hidden size={20} />,
};
}
case 'warning': {
return {
text: 'text-yellow-500',
border: 'border-yellow-500',
icon: <VscWarning aria-hidden size={20} />,
};
}
}
}
export async function Alert({ title, type, children }: PropsWithChildren<IAlert>) {
const { text, border, icon } = resolveType(type);
return (
<div className="mb-4 mt-6" role="alert">
<div className="relative flex">
<div className="p-4">{children}</div>
<div className="pointer-events-none absolute flex h-full w-full">
<div className={`w-4 shrink-0 rounded-bl-md rounded-tl-md border-b-2 border-l-2 border-t-2 ${border}`} />
<div className={`relative border-b-2 ${border}`}>
<div className={`pointer-events-auto flex -translate-y-1/2 place-items-center gap-2 px-2 ${text}`}>
{icon}
{title ? <span className={`font-semibold ${text}`}>{title}</span> : null}
</div>
</div>
<div className={`flex-1 rounded-br-md rounded-tr-md border-b-2 border-r-2 border-t-2 ${border}`} />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,3 @@
'use client';
export { Button } from 'react-aria-components';

View File

@@ -0,0 +1,148 @@
'use client';
import { Command } from 'cmdk';
import { useAtom, useSetAtom } from 'jotai';
import { ArrowRight } from 'lucide-react';
import MeiliSearch from 'meilisearch';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useDebounceValue } from 'usehooks-ts';
import { isCmdKOpenAtom } from '~/stores/cmdk';
import { isDrawerOpenAtom } from '~/stores/drawer';
import { resolveKind } from '~/util/resolveNodeKind';
import { OverlayScrollbarsComponent } from '../OverlayScrollbars';
const client = new MeiliSearch({
host: 'https://search.discordjs.dev',
apiKey: 'b51923c6abb574b1e97be9a03dc6414b6c69fb0c5696d0ef01a82b0f77d223db',
});
export function CmdK({ dependencies }: { readonly dependencies: string[] }) {
const pathname = usePathname();
const router = useRouter();
const [open, setOpen] = useAtom(isCmdKOpenAtom);
const setDrawerOpen = useSetAtom(isDrawerOpenAtom);
const [search, setSearch] = useDebounceValue('', 250);
const [searchResults, setSearchResults] = useState<any[]>([]);
const packageName = useMemo(() => pathname?.split('/').slice(3, 4)[0], [pathname]);
const branchName = useMemo(() => pathname?.split('/').slice(4, 5)[0], [pathname]);
const searchResultItems = useMemo(
() =>
searchResults?.map((item, idx) => (
<Command.Item
key={`${item.id}-${idx}`}
className="flex cursor-pointer place-items-center gap-2 rounded-md p-2 data-[selected]:bg-neutral-200 dark:data-[selected]:bg-neutral-800"
onSelect={() => {
router.push(item.path);
setOpen(false);
}}
value={item.id}
>
{resolveKind(item.kind)}
<div className="flex flex-grow flex-col">
<span className="font-semibold">{item.name}</span>
<span className="line-clamp-1 text-sm">{item.summary}</span>
<span className="truncate text-xs">{item.path}</span>
</div>
<ArrowRight aria-hidden className="flex-shrink-0" />
</Command.Item>
)) ?? [],
[router, searchResults, setOpen],
);
// Toggle the menu when ⌘K is pressed
useEffect(() => {
const down = (event: KeyboardEvent) => {
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener('keydown', down);
return () => {
document.removeEventListener('keydown', down);
};
}, [setOpen]);
useEffect(() => {
if (open) {
setDrawerOpen(false);
setSearch('');
}
return () => {
document.body.style.pointerEvents = 'auto';
};
}, [open, setDrawerOpen, setSearch]);
useEffect(() => {
// const searchDoc = async (searchString: string, version: string) => {
// console.log(dependencies);
// const res = await client
// .index(`${packageName?.replaceAll('.', '-')}-${version}`)
// .search(searchString, { limit: 25 });
// setSearchResults(res.hits);
// };
const searchDoc = async (searchString: string, version: string) => {
const result = await client.multiSearch({
queries: [`${packageName?.replaceAll('.', '-')}-${version}`, ...dependencies].map((dep) => ({
indexUid: dep,
// eslint-disable-next-line id-length
q: searchString,
limit: 25,
attributesToSearchOn: ['name'],
})),
});
setSearchResults(result.results.flatMap((res) => res.hits));
};
if (search && packageName) {
void searchDoc(search, branchName?.replaceAll('.', '-') ?? 'main');
} else {
setSearchResults([]);
}
}, [branchName, dependencies, packageName, search]);
return (
<Command.Dialog
className="w-full rounded-md border border-neutral-300 bg-neutral-100 p-2 shadow-md dark:border-neutral-700 dark:bg-neutral-900"
open={open}
onOpenChange={setOpen}
label="Command Menu"
shouldFilter={false}
>
<Command.Input
className="mb-4 w-full border-b border-neutral-300 bg-transparent px-2 pb-4 pt-2 outline-none dark:border-neutral-700"
onValueChange={setSearch}
placeholder="Quick search..."
/>
<OverlayScrollbarsComponent
className="max-h-96 pr-3"
defer
options={{
overflow: { x: 'hidden' },
scrollbars: {
autoHide: 'scroll',
autoHideDelay: 500,
autoHideSuspend: true,
clickScroll: true,
},
}}
>
<Command.List>
{search && searchResultItems.length ? (
searchResultItems
) : (
<div role="presentation" className="flex h-12 place-content-center place-items-center text-sm">
No results found.
</div>
)}
</Command.List>
</OverlayScrollbarsComponent>
</Command.Dialog>
);
}

View File

@@ -0,0 +1,3 @@
'use client';
export { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@radix-ui/react-collapsible';

View File

@@ -0,0 +1,37 @@
'use client';
import { useAtom } from 'jotai';
import { ChevronUp } from 'lucide-react';
import { useEffect, type PropsWithChildren } from 'react';
import { useMediaQuery } from 'usehooks-ts';
import { Drawer as Vaul } from 'vaul';
import { isDrawerOpenAtom } from '~/stores/drawer';
export function Drawer({ children }: PropsWithChildren) {
const [open, setOpen] = useAtom(isDrawerOpenAtom);
const isMedium = useMediaQuery('(min-width: 768px)');
useEffect(() => {
if (isMedium) {
setOpen(false);
}
}, [isMedium, setOpen]);
return (
<Vaul.Root open={open} onOpenChange={setOpen}>
<Vaul.Trigger
aria-label="Open navigation"
className="flex h-12 w-full place-content-center place-items-center rounded-t-lg border-t border-neutral-300 bg-neutral-100 p-2 dark:border-neutral-700 dark:bg-neutral-900"
>
<ChevronUp aria-hidden size={28} />
</Vaul.Trigger>
<Vaul.Portal>
<Vaul.Overlay className="fixed inset-0 bg-black/40" />
<Vaul.Content className="fixed bottom-0 left-0 right-0 flex max-h-[86%] flex-col rounded-t-lg bg-neutral-100 p-4 dark:bg-neutral-900">
<div className="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-neutral-400" />
{children}
</Vaul.Content>
</Vaul.Portal>
</Vaul.Root>
);
}

View File

@@ -0,0 +1,88 @@
import Image from 'next/image';
import vercelLogo from '~/assets/powered-by-vercel.svg';
import workersLogo from '~/assets/powered-by-workers.png';
export function Footer() {
return (
<footer className="md:pl-12 md:pr-12">
<div className="flex flex-col flex-wrap place-content-center gap-6 pt-12 sm:flex-row md:gap-12">
<div className="flex flex-wrap place-content-center place-items-center gap-4">
<a
className="rounded"
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
rel="external noopener noreferrer"
target="_blank"
title="Vercel"
>
<Image
alt="Vercel"
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABLCAQAAAA1k5H2AAAAi0lEQVR42u3SMQEAAAgDoC251a3gL2SgmfBYBRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAgwWEOSWBnYbKggAAAABJRU5ErkJggg=="
height={44}
placeholder="blur"
src={vercelLogo}
width={212}
/>
</a>
<a
className="rounded"
href="https://www.cloudflare.com"
rel="external noopener noreferrer"
target="_blank"
title="Cloudflare Workers"
>
<Image
alt="Cloudflare"
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABLCAQAAAA1k5H2AAAAi0lEQVR42u3SMQEAAAgDoC251a3gL2SgmfBYBRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAgwWEOSWBnYbKggAAAABJRU5ErkJggg=="
height={44}
placeholder="blur"
priority
src={workersLogo}
/>
</a>
</div>
<div className="flex flex-col gap-6 place-self-center sm:flex-row md:gap-12">
<div className="flex max-w-max flex-col gap-2">
<div className="text-lg font-semibold">Community</div>
<div className="flex flex-col gap-1">
<a className="rounded" href="https://discord.gg/djs" rel="external noopener noreferrer" target="_blank">
Discord
</a>
<a
className="rounded"
href="https://github.com/discordjs/discord.js/discussions"
rel="external noopener noreferrer"
target="_blank"
>
GitHub discussions
</a>
</div>
</div>
<div className="flex max-w-max flex-col gap-2">
<div className="text-lg font-semibold">Project</div>
<div className="flex flex-col gap-1">
<a
className="rounded"
href="https://github.com/discordjs/discord.js"
rel="external noopener noreferrer"
target="_blank"
>
discord.js
</a>
<a className="rounded" href="https://discord.js.org/docs" rel="noopener noreferrer" target="_blank">
discord.js documentation
</a>
<a
className="rounded"
href="https://discord-api-types.dev"
rel="external noopener noreferrer"
target="_blank"
>
discord-api-types
</a>
</div>
</div>
</div>
</div>
</footer>
);
}

View File

@@ -0,0 +1,33 @@
'use client';
import { Copy, CopyCheck } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useCopyToClipboard } from 'usehooks-ts';
export function InstallButton({ className = '' }: { readonly className?: string }) {
const [interacted, setInteracted] = useState(false);
const [copiedText, copyToClipboard] = useCopyToClipboard();
useEffect(() => {
const timer = setTimeout(() => setInteracted(false), 2_000);
return () => clearTimeout(timer);
}, [interacted]);
return (
<button
className={`cursor-copy rounded-md border border-neutral-300 bg-white px-4 py-2 font-mono hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800 ${className}`}
onClick={async () => {
setInteracted(true);
await copyToClipboard('npm install discord.js');
}}
type="button"
>
<span className="font-semibold text-blurple">{'>'}</span> npm install discord.js{' '}
{copiedText && interacted ? (
<CopyCheck aria-hidden size={20} className="ml-1 inline-block text-green-500" />
) : (
<Copy aria-hidden size={20} className="ml-1 inline-block" />
)}
</button>
);
}

View File

@@ -0,0 +1,3 @@
'use client';
export { ListBox, ListBoxItem } from 'react-aria-components';

View File

@@ -0,0 +1,95 @@
'use client';
import { ChevronsUpDown } from 'lucide-react';
import { useEffect, useState } from 'react';
import type { Key } from 'react-aria-components';
import { useMediaQuery } from 'usehooks-ts';
import { Drawer as Vaul } from 'vaul';
import { PACKAGES } from '~/util/constants';
import { Button } from './Button';
import { ListBox, ListBoxItem } from './ListBox';
import { Popover } from './Popover';
import { Select, SelectValue } from './Select';
export function PackageSelect({ packageName }: { readonly packageName: string }) {
const [selectedPackage, setSelectedPackage] = useState<Key>(packageName);
const [open, setOpen] = useState(false);
const isMedium = useMediaQuery('(min-width: 768px)');
useEffect(() => {
if (isMedium) {
setOpen(false);
}
}, [isMedium, setOpen]);
return (
<>
<Select
aria-label="Select a package"
className="hidden md:block"
selectedKey={selectedPackage}
onSelectionChange={(selected) => {
setSelectedPackage(selected);
}}
>
<Button className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800">
<SelectValue className="font-medium" />
<ChevronsUpDown aria-hidden size={20} />
</Button>
<Popover className="max-h-60 w-[--trigger-width] overflow-auto rounded-md border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
<ListBox shouldFocusWrap items={PACKAGES}>
{(item) => (
<ListBoxItem
id={item.name}
textValue={item.name}
href={`/docs/packages/${item.name}/main`}
className="flex p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
>
{item.name}
</ListBoxItem>
)}
</ListBox>
</Popover>
</Select>
<Vaul.NestedRoot open={open} onOpenChange={setOpen} dismissible={false}>
<Vaul.Trigger
aria-label="Open package select"
className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800 md:hidden"
>
<span className="font-medium">{selectedPackage}</span>
<ChevronsUpDown aria-hidden size={20} />
</Vaul.Trigger>
<Vaul.Portal>
<Vaul.Overlay className="fixed inset-0 bg-black/40" />
<Vaul.Content className="fixed bottom-0 left-0 right-0 flex max-h-[80%] flex-col rounded-t-lg bg-neutral-100 p-4 dark:bg-neutral-900">
<div className="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-neutral-400" />
<ListBox
aria-label="Select a package"
className="flex flex-col gap-2 overflow-auto"
shouldFocusWrap
items={PACKAGES}
selectionMode="single"
selectedKeys={[selectedPackage]}
onSelectionChange={(selected) => {
const [val] = selected;
setSelectedPackage(val as Key);
}}
>
{(item) => (
<ListBoxItem
id={item.name}
textValue={item.name}
href={`/docs/packages/${item.name}/main`}
className="rounded-md p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
>
{item.name}
</ListBoxItem>
)}
</ListBox>
</Vaul.Content>
</Vaul.Portal>
</Vaul.NestedRoot>
</>
);
}

View File

@@ -0,0 +1,3 @@
'use client';
export { Popover } from 'react-aria-components';

View File

@@ -0,0 +1,26 @@
'use client';
import { useSetAtom } from 'jotai';
import { Command, Search } from 'lucide-react';
import { isCmdKOpenAtom } from '~/stores/cmdk';
export function SearchButton() {
const setIsOpen = useSetAtom(isCmdKOpenAtom);
return (
<button
aria-label="Open search"
className="flex place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800"
type="button"
onClick={() => setIsOpen(true)}
>
<span className="flex place-items-center gap-2">
<Search aria-hidden size={20} />
Search...
</span>
<span className="hidden place-items-center gap-1 md:flex">
<Command aria-hidden size={20} /> K
</span>
</button>
);
}

View File

@@ -0,0 +1,3 @@
'use client';
export { Select, SelectValue } from 'react-aria-components';

View File

@@ -0,0 +1,3 @@
'use client';
export { Tabs, TabList, Tab, TabPanel } from 'react-aria-components';

View File

@@ -0,0 +1,16 @@
'use client';
import { VscColorMode } from '@react-icons/all-files/vsc/VscColorMode';
import { useTheme } from 'next-themes';
import { Button } from './Button';
export function ThemeSwitch() {
const { resolvedTheme, setTheme } = useTheme();
const toggleTheme = () => setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
return (
<Button aria-label="Toggle theme" className="rounded-full" onPress={() => toggleTheme()}>
<VscColorMode aria-hidden size={24} />
</Button>
);
}

View File

@@ -0,0 +1,102 @@
'use client';
import { ChevronsUpDown } from 'lucide-react';
import { useEffect, useState } from 'react';
import type { Key } from 'react-aria-components';
import { useMediaQuery } from 'usehooks-ts';
import { Drawer as Vaul } from 'vaul';
import { Button } from './Button';
import { ListBox, ListBoxItem } from './ListBox';
import { Popover } from './Popover';
import { Select, SelectValue } from './Select';
export function VersionSelect({
packageName,
version,
versions,
}: {
readonly packageName: string;
readonly version: string;
readonly versions: { readonly version: string }[];
}) {
const [selectedVersion, setSelectedVersion] = useState<Key>(version);
const [open, setOpen] = useState(false);
const isMedium = useMediaQuery('(min-width: 768px)');
useEffect(() => {
if (isMedium) {
setOpen(false);
}
}, [isMedium, setOpen]);
return (
<>
<Select
aria-label="Select a version"
className="hidden md:block"
selectedKey={selectedVersion}
onSelectionChange={(selected) => {
setSelectedVersion(selected);
}}
>
<Button className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800">
<SelectValue className="font-medium" />
<ChevronsUpDown aria-hidden size={20} />
</Button>
<Popover className="max-h-60 w-[--trigger-width] overflow-auto rounded-md border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
<ListBox shouldFocusWrap items={versions}>
{(item) => (
<ListBoxItem
id={item.version}
textValue={item.version}
href={`/docs/packages/${packageName}/${item.version}`}
className="flex p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
>
{item.version}
</ListBoxItem>
)}
</ListBox>
</Popover>
</Select>
<Vaul.NestedRoot open={open} onOpenChange={setOpen} dismissible={false}>
<Vaul.Trigger
aria-label="Open version select"
className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800 md:hidden"
>
<span className="font-medium">{selectedVersion}</span>
<ChevronsUpDown aria-hidden size={20} />
</Vaul.Trigger>
<Vaul.Portal>
<Vaul.Overlay className="fixed inset-0 bg-black/40" />
<Vaul.Content className="fixed bottom-0 left-0 right-0 flex max-h-[80%] flex-col rounded-t-lg bg-neutral-100 p-4 dark:bg-neutral-900">
<div className="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-neutral-400" />
<ListBox
aria-label="Select a version"
className="flex flex-col gap-2 overflow-auto"
shouldFocusWrap
items={versions}
selectionMode="single"
selectedKeys={[selectedVersion]}
onSelectionChange={(selected) => {
const [val] = selected;
setSelectedVersion(val as Key);
}}
>
{(item) => (
<ListBoxItem
id={item.version}
textValue={item.version}
href={`/docs/packages/${packageName}/${item.version}`}
className="rounded-md p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
>
{item.version}
</ListBoxItem>
)}
</ListBox>
</Vaul.Content>
</Vaul.Portal>
</Vaul.NestedRoot>
</>
);
}