feat: astro guide (#8714)

This commit is contained in:
Noel
2022-10-06 23:53:35 +02:00
committed by GitHub
parent 01d75c8b8b
commit 2628659747
74 changed files with 3740 additions and 102 deletions

View File

@@ -0,0 +1,18 @@
import type { PropsWithChildren } from 'react';
import { VscWarning } from 'react-icons/vsc';
export function Caution({ children }: PropsWithChildren<{}>) {
return (
<div className="rounded border border-blue-500 p-4">
<div className="flex flex-row place-items-center gap-4">
<span className="text-blue-500">
<VscWarning size={20} />
</span>
<div className="flex flex-col gap-2 text-sm">
<span className="font-semibold text-blue-500">Caution</span>
{children}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,3 @@
export function DocsLink() {
return null;
}

View File

@@ -0,0 +1,18 @@
import type { PropsWithChildren } from 'react';
import { VscInfo } from 'react-icons/vsc';
export function Info({ children }: PropsWithChildren<{}>) {
return (
<div className="rounded border border-blue-500 p-4">
<div className="flex flex-row place-items-center gap-4">
<span className="text-blue-500">
<VscInfo size={20} />
</span>
<div className="flex flex-col gap-2 text-sm">
<span className="font-semibold text-blue-500">Info</span>
{children}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,70 @@
import { Button } from 'ariakit/button';
import { useState, useEffect } from 'react';
import { FiCommand } from 'react-icons/fi';
import { VscColorMode, VscGithubInverted, VscMenu, VscSearch } from 'react-icons/vsc';
import { useMedia } from 'react-use';
import { Sidebar } from './Sidebar.jsx';
import type { MDXPage } from './SidebarItems.jsx';
export function Navbar({ pages }: { pages?: MDXPage[] | undefined }) {
const matches = useMedia('(min-width: 992px)', false);
const [opened, setOpened] = useState(false);
useEffect(() => {
if (matches) {
setOpened(false);
}
}, [matches]);
return (
<>
<header className="dark:bg-dark-600 dark:border-dark-100 bg-light-600 border-light-800 fixed top-0 left-0 z-20 w-full border-b">
<div className="h-18 block px-6">
<div className="flex h-full flex-row place-content-between place-items-center">
<Button
aria-label="Menu"
className="focus:ring-width-2 focus:ring-blurple 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 outline-0 focus:ring active:translate-y-px lg:hidden"
onClick={() => setOpened((open) => !open)}
>
<VscMenu size={24} />
</Button>
<div className="hidden md:flex md:flex-row">Placeholder</div>
<div className="flex flex-row place-items-center gap-4">
<Button
as="div"
className="dark:bg-dark-800 focus:ring-width-2 focus:ring-blurple rounded bg-white px-4 py-2.5 outline-0 focus:ring"
// 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
aria-label="GitHub"
as="a"
className="focus:ring-width-2 focus:ring-blurple flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded rounded-full border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-0 focus:ring active:translate-y-px"
href="https://github.com/discordjs/discord.js"
rel="noopener noreferrer"
target="_blank"
>
<VscGithubInverted size={24} />
</Button>
<Button
aria-label="Toggle theme"
className="focus:ring-width-2 focus:ring-blurple flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded-full rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-0 focus:ring active:translate-y-px"
// onClick={() => toggleTheme()}
>
<VscColorMode size={24} />
</Button>
</div>
</div>
</div>
</header>
<Sidebar opened={opened} pages={pages} />
</>
);
}

View File

@@ -0,0 +1,3 @@
export function ResultingCode() {
return null;
}

View File

@@ -0,0 +1,43 @@
import { Disclosure, DisclosureContent, useDisclosureState } from 'ariakit/disclosure';
import type { PropsWithChildren } from 'react';
import { VscChevronDown } from 'react-icons/vsc';
export function Section({
title,
icon,
padded = false,
dense = false,
defaultClosed = false,
children,
}: PropsWithChildren<{
defaultClosed?: boolean;
dense?: boolean;
icon?: JSX.Element;
padded?: boolean;
title: string;
}>) {
const disclosure = useDisclosureState({ defaultOpen: !defaultClosed });
return (
<div className="flex flex-col">
<Disclosure
className="bg-light-600 hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 focus:ring-width-2 focus:ring-blurple rounded p-3 outline-0 focus:ring"
state={disclosure}
>
<div className="flex flex-row place-content-between place-items-center">
<div className="flex flex-row place-items-center gap-3">
{icon ?? null}
<span className="font-semibold">{title}</span>
</div>
<VscChevronDown
className={`transform transition duration-150 ease-in-out ${disclosure.open ? 'rotate-180' : 'rotate-0'}`}
size={20}
/>
</div>
</Disclosure>
<DisclosureContent state={disclosure}>
{padded ? <div className={`py-5 ${dense ? 'mx-2 px-0' : 'px-4.5 mx-6.5'}`}>{children}</div> : children}
</DisclosureContent>
</div>
);
}

View File

@@ -0,0 +1,24 @@
import { Scrollbars } from 'react-custom-scrollbars-2';
import type { MDXPage } from './SidebarItems';
export function Sidebar({ pages, opened }: { opened: boolean; pages?: MDXPage[] | undefined }) {
return (
<nav
className={`h-[calc(100vh - 73px)] dark:bg-dark-600 dark:border-dark-100 border-light-800 fixed top-[73px] left-0 bottom-0 z-20 w-full border-r bg-white ${
opened ? 'block' : 'hidden'
} lg:w-76 lg:max-w-76 lg:block`}
>
<Scrollbars
autoHide
hideTracksWhenNotNeeded
renderThumbVertical={(props) => <div {...props} className="dark:bg-dark-100 bg-light-900 z-30 rounded" />}
renderTrackVertical={(props) => (
<div {...props} className="absolute top-0.5 right-0.5 bottom-0.5 z-30 w-1.5 rounded" />
)}
universal
>
{pages ?? null}
</Scrollbars>
</nav>
);
}

View File

@@ -0,0 +1,37 @@
import type { MDXInstance } from 'astro';
import { Section } from './Section.jsx';
export type MDXPage = MDXInstance<{ category: string; title: string }>;
export function SidebarItems({ pages }: { pages: MDXPage[] }) {
const categories = pages.reduce<Record<string, MDXPage[]>>((acc, page) => {
if (acc[page.frontmatter.category]) {
acc[page.frontmatter.category]?.push(page);
} else {
acc[page.frontmatter.category] = [page];
}
return acc;
}, {});
return Object.keys(categories).map((category, idx) => (
<Section key={idx} title={category}>
{categories[category]?.map((member, index) => (
<a
className={`dark:border-dark-100 border-light-800 focus:ring-width-2 focus:ring-blurple ml-5 flex flex-col border-l p-[5px] pl-6 outline-0 focus:rounded focus:border-0 focus:ring ${
false
? 'bg-blurple text-white'
: 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
}`}
href={member.url}
key={index}
title={member.frontmatter.title}
>
<div className="flex flex-row place-items-center gap-2 lg:text-sm">
<span className="truncate">{member.frontmatter.title}</span>
</div>
</a>
)) ?? null}
</Section>
));
}

View File

@@ -0,0 +1,89 @@
---
import { Navbar } from './Navbar.jsx';
import { SidebarItems } from './SidebarItems.jsx';
const pages = await Astro.glob<{ category: string; title: string }>('../pages/**/*.mdx');
---
<Navbar client:load>
<div class="flex flex-col gap-3 p-3 pb-32 lg:pb-12" slot="pages">
<SidebarItems client:load pages={pages} />
</div>
</Navbar>
<main class="pt-18 lg:pl-76">
<article class="dark:bg-dark-600 bg-light-600">
<div class="dark:bg-dark-800 relative z-10 min-h-[calc(100vh_-_70px)] bg-white p-6 pb-20 shadow">
<div class="prose max-w-none">
<slot />
</div>
</div>
<div class="h-76 md:h-52"></div>
<footer class="dark:bg-dark-600 h-76 lg:pl-84 bg-light-600 fixed bottom-0 left-0 right-0 md:h-52 md:pl-4 md:pr-16">
<div class="mx-auto flex max-w-6xl flex-col place-items-center gap-12 pt-12 lg:place-content-center">
<div class="flex w-full flex-col place-content-between place-items-center gap-12 md:flex-row md:gap-0">
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
rel="noopener noreferrer"
target="_blank"
title="Vercel"
>
<img alt="Vercel" src="/powered-by-vercel.svg" />
</a>
<div class="flex flex-row gap-6 md:gap-12">
<div class="flex flex-col gap-2">
<div class="text-lg font-semibold">Community</div>
<div class="flex flex-col gap-1">
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://discord.gg/djs"
rel="noopener noreferrer"
target="_blank"
>
Discord
</a>
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://github.com/discordjs/discord.js/discussions"
rel="noopener noreferrer"
target="_blank"
>
GitHub discussions
</a>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="text-lg font-semibold">Project</div>
<div class="flex flex-col gap-1">
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://github.com/discordjs/discord.js"
rel="noopener noreferrer"
target="_blank"
>
discord.js
</a>
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://discordjs.guide"
rel="noopener noreferrer"
target="_blank"
>
discord.js guide
</a>
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://discord-api-types.dev"
rel="noopener noreferrer"
target="_blank"
>
discord-api-types
</a>
</div>
</div>
</div>
</div>
</div>
</footer>
</article>
</main>

View File

@@ -0,0 +1,18 @@
import type { PropsWithChildren } from 'react';
import { VscFlame } from 'react-icons/vsc';
export function Tip({ children }: PropsWithChildren<{}>) {
return (
<div className="my-4 rounded border border-green-500 p-4">
<div className="flex flex-row place-items-center gap-4">
<span className="text-green-500">
<VscFlame size={20} />
</span>
<div className="flex flex-col gap-2 text-sm">
<span className="font-semibold text-green-500">Tip</span>
{children}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
import type { PropsWithChildren, ReactNode } from 'react';
import { DiscordMessageAuthor, type IDiscordMessageAuthor } from './MessageAuthor.jsx';
import { DiscordMessageInteraction, type IDiscordMessageInteraction } from './MessageInteraction.jsx';
import { DiscordMessageReply, type IDiscordMessageReply } from './MessageReply.jsx';
export function DiscordMessage({
reply,
replyNode,
interaction,
interactionNode,
author,
authorNode,
followUp,
time,
children,
}: PropsWithChildren<{
author?: IDiscordMessageAuthor | undefined;
authorNode?: ReactNode | undefined;
followUp?: boolean;
interaction?: IDiscordMessageInteraction | undefined;
interactionNode?: ReactNode | undefined;
reply?: IDiscordMessageReply | undefined;
replyNode?: ReactNode | undefined;
time?: string | undefined;
}>) {
return (
<div className="relative" id="outer-message-wrapper">
<div
className={`pl-18 hover:bg-[rgb(4_4_5)]/7 group py-0.5 pr-12 leading-snug ${followUp ? '' : 'mt-4'}`}
id="message-wrapper"
>
{(reply || replyNode) && !followUp ? reply ? <DiscordMessageReply {...reply} /> : replyNode ?? null : null}
{(interaction || interactionNode) && !(reply || replyNode) && !followUp ? (
interaction ? (
<DiscordMessageInteraction {...interaction} />
) : (
interactionNode ?? null
)
) : null}
<div className="static" id="content-wrapper">
{followUp ? (
<span
className="h-5.5 absolute left-0 mr-1 hidden w-[56px] cursor-default select-none text-right text-xs leading-loose group-hover:inline-block"
id="time"
>
{time}
</span>
) : author ? (
<DiscordMessageAuthor {...author} />
) : (
authorNode
)}
<div className="[&>p]:m-0 [&>p]:leading-snug text-white" id="message-content">
{children}
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,31 @@
export interface IDiscordMessageAuthor {
avatar: string;
bot?: boolean;
time: string;
username: string;
}
export function DiscordMessageAuthor({ avatar, username, bot, time }: IDiscordMessageAuthor) {
return (
<>
<img
alt={`${username}'s avatar`}
className="absolute left-[16px] mt-0.5 h-10 w-10 cursor-pointer select-none rounded-full"
src={avatar}
/>
<h2 className="text-size-inherit m-0 font-medium leading-snug" id="user-info">
<span className="mr-1" id="username">
<span className="cursor-pointer text-base font-medium text-white hover:underline">{username}</span>
{bot ? (
<span className="bg-blurple vertical-top relative top-1 ml-1 rounded px-1 text-xs" id="bot">
BOT
</span>
) : null}
</span>
<span className="ml-1 cursor-default text-xs leading-snug text-[rgb(163_166_170)]" id="time">
{time}
</span>
</h2>
</>
);
}

View File

@@ -0,0 +1,21 @@
export interface IDiscordMessageAuthorReply {
avatar: string;
bot?: boolean;
username: string;
}
export function DiscordMessageAuthorReply({ avatar, bot, username }: IDiscordMessageAuthorReply) {
return (
<>
<img alt={`${username}'s avatar`} className="mr-1 h-4 w-4 select-none rounded-full" src={avatar} />
{bot ? (
<div className="bg-blurple vertical-top mr-1 rounded px-1 text-xs" id="bot">
BOT
</div>
) : null}
<span className="mr-1 cursor-pointer select-none text-sm font-medium leading-snug text-white hover:underline">
{username}
</span>
</>
);
}

View File

@@ -0,0 +1,20 @@
import type { PropsWithChildren, ReactNode } from 'react';
import { DiscordMessageAuthorReply, type IDiscordMessageAuthorReply } from './MessageAuthorReply.jsx';
export function DiscordMessageBaseReply({
author,
authorNode,
children,
}: PropsWithChildren<{ author?: IDiscordMessageAuthorReply | undefined; authorNode?: ReactNode | undefined }>) {
return (
<div
className="before:rounded-tl-1.5 relative mb-1 flex place-items-center before:absolute before:left-[-36px] before:right-full before:top-[50%] before:bottom-0 before:mr-1 before:block before:border-l-2 before:border-t-2 before:border-[rgb(79_84_92)] before:content-none"
id="reply-wrapper"
>
<div className="[&>span]:opacity-60 flex place-items-center">
{author ? <DiscordMessageAuthorReply {...author} /> : authorNode}
</div>
{children}
</div>
);
}

View File

@@ -0,0 +1,38 @@
import type { PropsWithChildren, ReactNode } from 'react';
import { DiscordMessageEmbedAuthor, type IDiscordMessageEmbedAuthor } from './MessageEmbedAuthor.jsx';
import { DiscordMessageEmbedFooter, type IDiscordMessageEmbedFooter } from './MessageEmbedFooter.jsx';
import { DiscordMessageEmbedTitle, type IDiscordMessageEmbedTitle } from './MessageEmbedTitle.jsx';
export interface IDiscordMessageEmbed {
author?: IDiscordMessageEmbedAuthor | undefined;
authorNode?: ReactNode | undefined;
footer?: IDiscordMessageEmbedFooter | undefined;
footerNode?: ReactNode | undefined;
title?: IDiscordMessageEmbedTitle | undefined;
titleNode?: ReactNode | undefined;
}
export function DiscordMessageEmbed({
author,
authorNode,
title,
titleNode,
children,
footer,
footerNode,
}: PropsWithChildren<IDiscordMessageEmbed>) {
return (
<div className="py-0.5" id="outer-embed-wrapper">
<div className="border-l-blurple grid max-w-max rounded border-l-4 bg-[rgb(47_49_54)]" id="embed-wrapper">
<div className="max-w-128">
<div className="pt-2 pr-4 pb-4 pl-3">
{author ? <DiscordMessageEmbedAuthor {...author} /> : authorNode ?? null}
{title ? <DiscordMessageEmbedTitle {...title} /> : titleNode ?? null}
{children ? <div className="mt-2 text-sm">{children}</div> : null}
{footer ? <DiscordMessageEmbedFooter {...footer} /> : footerNode ?? null}
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,13 @@
export interface IDiscordMessageEmbedAuthor {
avatar: string;
username: string;
}
export function DiscordMessageEmbedAuthor({ avatar, username }: IDiscordMessageEmbedAuthor) {
return (
<div className="mt-2 flex place-items-center">
<img alt={`${username}'s avatar`} className="mr-2 h-6 w-6 select-none rounded-full" src={avatar} />
<span className="text-sm font-medium hover:underline">{username}</span>
</div>
);
}

View File

@@ -0,0 +1,7 @@
export interface IDiscordMessageEmbedFooter {
content: string;
}
export function DiscordMessageEmbedFooter({ content }: IDiscordMessageEmbedFooter) {
return <div className="mt-2 text-xs">{content}</div>;
}

View File

@@ -0,0 +1,7 @@
export interface IDiscordMessageEmbedTitle {
title: string;
}
export function DiscordMessageEmbedTitle({ title }: IDiscordMessageEmbedTitle) {
return <div className="mt-2 font-medium">{title}</div>;
}

View File

@@ -0,0 +1,18 @@
import type { ReactNode } from 'react';
import type { IDiscordMessageAuthorReply } from './MessageAuthorReply.jsx';
import { DiscordMessageBaseReply } from './MessageBaseReply.jsx';
export interface IDiscordMessageInteraction {
author?: IDiscordMessageAuthorReply | undefined;
authorNode?: ReactNode | undefined;
command?: string;
}
export function DiscordMessageInteraction({ author, authorNode, command }: IDiscordMessageInteraction) {
return (
<DiscordMessageBaseReply author={author} authorNode={authorNode}>
<span className="mr-1 select-none text-sm leading-snug text-white">used</span>
<div className="text-blurple cursor-pointer text-sm leading-snug hover:underline">{command}</div>
</DiscordMessageBaseReply>
);
}

View File

@@ -0,0 +1,19 @@
import type { ReactNode } from 'react';
import type { IDiscordMessageAuthorReply } from './MessageAuthorReply.jsx';
import { DiscordMessageBaseReply } from './MessageBaseReply.jsx';
export interface IDiscordMessageReply {
author?: IDiscordMessageAuthorReply | undefined;
authorNode?: ReactNode | undefined;
content: string;
}
export function DiscordMessageReply({ author, authorNode, content }: IDiscordMessageReply) {
return (
<DiscordMessageBaseReply author={author} authorNode={authorNode}>
<div className="cursor-pointer select-none text-sm leading-snug text-[rgb(163_166_170)] hover:text-white">
{content}
</div>
</DiscordMessageBaseReply>
);
}

View File

@@ -0,0 +1,9 @@
import type { PropsWithChildren } from 'react';
export function DiscordMessages({ rounded, children }: PropsWithChildren<{ rounded?: boolean }>) {
return (
<div className={`pt-0.1 bg-[rgb(54_57_63)] pb-4 ${rounded ? 'rounded' : ''}`} id="messages-wrapper">
{children}
</div>
);
}