use new docs

This commit is contained in:
MapleLeaf
2022-01-02 22:02:10 -06:00
committed by Darius
parent 381f933fd1
commit ca520db701
70 changed files with 186 additions and 2964 deletions

View File

@@ -1,47 +0,0 @@
import {
CodeIcon,
DocumentTextIcon,
ExternalLinkIcon,
} from "@heroicons/react/solid"
import type { AppLinkProps } from "~/components/app-link"
import { createContentIndex } from "~/helpers/create-index.server"
import { inlineIconClass } from "~/styles"
export const mainLinks: AppLinkProps[] = [
{
type: "router",
to: "/docs/guides/getting-started",
label: (
<>
<DocumentTextIcon className={inlineIconClass} /> Guides
</>
),
},
{
type: "internal",
to: "/docs/api",
label: (
<>
<CodeIcon className={inlineIconClass} /> API Reference
</>
),
},
{
type: "external",
to: "https://github.com/itsMapleLeaf/reacord",
label: (
<>
<ExternalLinkIcon className={inlineIconClass} /> GitHub
</>
),
},
]
export async function getGuideLinks(): Promise<AppLinkProps[]> {
const entries = await createContentIndex("app/routes/docs/guides")
return entries.map((entry) => ({
type: "router",
label: entry.title,
to: entry.route,
}))
}

View File

@@ -1,34 +0,0 @@
import { Link } from "remix"
import { ExternalLink } from "~/components/external-link"
export type AppLinkProps = {
type: "router" | "internal" | "external"
label: React.ReactNode
to: string
className?: string
}
export function AppLink(props: AppLinkProps) {
switch (props.type) {
case "router":
return (
<Link className={props.className} to={props.to}>
{props.label}
</Link>
)
case "internal":
return (
<a className={props.className} href={props.to}>
{props.label}
</a>
)
case "external":
return (
<ExternalLink className={props.className} href={props.to}>
{props.label}
</ExternalLink>
)
}
}

View File

@@ -1,9 +0,0 @@
import type { ComponentPropsWithoutRef } from "react"
export function ExternalLink(props: ComponentPropsWithoutRef<"a">) {
return (
<a target="_blank" rel="noopener noreferrer" {...props}>
{props.children}
</a>
)
}

View File

@@ -1,19 +0,0 @@
{/* prettier-ignore */}
```tsx
import * as React from "react"
import { Embed, Button } from "reacord"
function Counter() {
const [count, setCount] = React.useState(0)
return (
<>
<Embed title="Counter">
This button has been clicked {count} times.
</Embed>
<Button onClick={() => setCount(count + 1)}>
+1
</Button>
</>
)
}
```

View File

@@ -1,102 +0,0 @@
import { MenuAlt4Icon } from "@heroicons/react/outline"
import { useRect } from "@reach/rect"
import clsx from "clsx"
import { useRef, useState } from "react"
import { FocusOn } from "react-focus-on"
import { Link } from "remix"
import { mainLinks } from "~/app-links"
import { AppLink } from "~/components/app-link"
import type { ContentIndexEntry } from "~/helpers/create-index.server"
import { linkClass } from "~/styles"
const menuItemClass = clsx`
px-3 py-2 transition text-left font-medium block
opacity-50 hover:opacity-100 hover:bg-black/30
`
export function MainNavigation({
guideRoutes,
}: {
guideRoutes: ContentIndexEntry[]
}) {
return (
<nav className="flex justify-between items-center h-16">
<Link to="/">
<h1 className="text-3xl font-light">reacord</h1>
</Link>
<div className="hidden md:flex gap-4">
{mainLinks.map((link) => (
<AppLink key={link.to} className={linkClass} {...link} />
))}
</div>
<div className="md:hidden">
<PopoverMenu>
{mainLinks.map((link) => (
<AppLink key={link.to} className={menuItemClass} {...link} />
))}
<hr className="border-0 h-[2px] bg-black/50" />
{guideRoutes.map((route) => (
<Link key={route.route} to={route.route} className={menuItemClass}>
{route.title}
</Link>
))}
</PopoverMenu>
</div>
</nav>
)
}
function PopoverMenu({ children }: { children: React.ReactNode }) {
const [visible, setVisible] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const buttonRect = useRect(buttonRef)
const panelRef = useRef<HTMLDivElement>(null)
const panelRect = useRect(panelRef)
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
return (
<>
<button
title="Menu"
className={linkClass}
onClick={() => setVisible(!visible)}
ref={buttonRef}
>
<MenuAlt4Icon className="w-6" />
</button>
<FocusOn
enabled={visible}
onClickOutside={() => setVisible(false)}
onEscapeKey={() => setVisible(false)}
>
<div
className="fixed"
style={{
left: (buttonRect?.right ?? 0) - (panelRect?.width ?? 0),
top: (buttonRect?.bottom ?? 0) + 8,
}}
onClick={() => setVisible(false)}
>
<div
className={clsx(
"transition-all",
visible
? "opacity-100 visible"
: "translate-y-2 opacity-0 invisible",
)}
>
<div ref={panelRef}>
<div className="w-48 bg-slate-800 rounded-lg shadow overflow-hidden max-h-[calc(100vh-4rem)] overflow-y-auto">
{children}
</div>
</div>
</div>
</div>
</FocusOn>
</>
)
}

View File

@@ -1,4 +0,0 @@
import { hydrate } from "react-dom"
import { RemixBrowser } from "remix"
hydrate(<RemixBrowser />, document)

View File

@@ -1,21 +0,0 @@
import { renderToString } from "react-dom/server"
import { RemixServer } from "remix"
import type { EntryContext } from "remix"
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
const markup = renderToString(
<RemixServer context={remixContext} url={request.url} />,
)
responseHeaders.set("Content-Type", "text/html")
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
})
}

View File

@@ -1,39 +0,0 @@
import glob from "fast-glob"
import matter from "gray-matter"
import { readFile } from "node:fs/promises"
import { join, parse, posix } from "node:path"
export type ContentIndexEntry = {
title: string
route: string
order: number
}
export async function createContentIndex(
contentFolderPath: string,
): Promise<ContentIndexEntry[]> {
const contentFiles = await glob(["**/*.mdx", "**/*.md"], {
cwd: contentFolderPath,
absolute: true,
})
const entries = await Promise.all(contentFiles.map(getIndexInfo))
return entries.sort((a, b) => a.order - b.order)
}
async function getIndexInfo(filePath: string): Promise<ContentIndexEntry> {
const { dir, name } = parse(filePath)
const route = "/" + posix.relative("app/routes", join(dir, name))
const { data } = matter(await readFile(filePath, "utf8"))
const title = String(data.meta?.title ?? "")
let order = Number(data.order)
if (!Number.isFinite(order)) {
order = Number.POSITIVE_INFINITY
}
return { title, route, order }
}

View File

@@ -1,10 +0,0 @@
import { stat } from "node:fs/promises"
export async function isFile(path: string) {
try {
const result = await stat(path)
return result.isFile()
} catch {
return false
}
}

View File

@@ -1,8 +0,0 @@
import { useState } from "react"
import { useWindowEvent } from "~/hooks/dom/use-window-event"
export function useScrolled() {
const [scrolled, setScrolled] = useState(false)
useWindowEvent("scroll", () => setScrolled(window.scrollY > 0))
return scrolled
}

View File

@@ -1,11 +0,0 @@
import { useEffect } from "react"
export function useWindowEvent<EventType extends keyof WindowEventMap>(
type: EventType,
handler: (event: WindowEventMap[EventType]) => void,
) {
useEffect(() => {
window.addEventListener(type, handler)
return () => window.removeEventListener(type, handler)
})
}

View File

@@ -1,133 +0,0 @@
/**
* Nord Theme Originally by Arctic Ice Studio
* https://nordtheme.com
*
* Ported for PrismJS by Zane Hitchcoxc (@zwhitchcox) and Gabriel Ramos (@gabrieluizramos)
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
/* font-family: "Fira Code", Consolas, Monaco, "Andale Mono", "Ubuntu Mono",
monospace; */
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.7;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
/* background: #2e3440; */
background: rgba(0, 0, 0, 0.3);
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #636f88;
}
.token.punctuation {
color: #81a1c1;
}
.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #81a1c1;
}
.token.number {
color: #b48ead;
}
.token.boolean {
color: #81a1c1;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #a3be8c;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #81a1c1;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: #88c0d0;
}
.token.keyword {
color: #81a1c1;
}
.token.regex,
.token.important {
color: #ebcb8b;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.code-line.highlight-line {
background-color: rgba(255, 255, 255, 0.08);
padding: 0 1rem;
margin: 0 -1rem;
display: block;
}

View File

@@ -1,50 +0,0 @@
import packageJson from "reacord/package.json"
import type { LinksFunction, MetaFunction } from "remix"
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "remix"
import prismThemeCss from "./prism-theme.css"
export const meta: MetaFunction = () => ({
title: "Reacord",
description: packageJson.description,
})
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: prismThemeCss },
{ rel: "stylesheet", href: "/tailwind.css" },
]
export default function App() {
return (
<html lang="en" className="bg-slate-900 text-slate-100">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@500&family=Rubik:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
rel="stylesheet"
/>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
)
}

View File

@@ -1,56 +0,0 @@
import clsx from "clsx"
import type { LoaderFunction } from "remix"
import { Link, Outlet, useLoaderData } from "remix"
import { MainNavigation } from "~/components/main-navigation"
import type { ContentIndexEntry } from "~/helpers/create-index.server"
import { createContentIndex } from "~/helpers/create-index.server"
import { useScrolled } from "~/hooks/dom/use-scrolled"
import { docsProseClass, linkClass, maxWidthContainer } from "~/styles"
type LoaderData = ContentIndexEntry[]
export const loader: LoaderFunction = async () => {
const data: LoaderData = await createContentIndex("app/routes/docs/guides")
return data
}
export default function Docs() {
const data: LoaderData = useLoaderData()
return (
<>
<HeaderPanel>
<div className={maxWidthContainer}>
<MainNavigation guideRoutes={data} />
</div>
</HeaderPanel>
<main className={clsx(maxWidthContainer, "mt-8 flex items-start gap-4")}>
<nav className="w-48 sticky top-24 hidden md:block">
<h2 className="text-2xl">Guides</h2>
<ul className="mt-3 flex flex-col gap-2 items-start">
{data.map(({ title, route }) => (
<li key={route}>
<Link className={linkClass} to={route}>
{title}
</Link>
</li>
))}
</ul>
</nav>
<section className={clsx(docsProseClass, "pb-8 flex-1 min-w-0")}>
<Outlet />
</section>
</main>
</>
)
}
function HeaderPanel({ children }: { children: React.ReactNode }) {
const isScrolled = useScrolled()
const className = clsx(
isScrolled ? "bg-slate-700/30" : "bg-slate-800",
"shadow sticky top-0 backdrop-blur-sm transition z-10 flex",
)
return <header className={className}>{children}</header>
}

View File

@@ -1,10 +0,0 @@
---
order: 3
meta:
title: Buttons
description: Using button components
---
# Buttons
todo

View File

@@ -1,10 +0,0 @@
---
order: 2
meta:
title: Embeds
description: Using embed components
---
# Embeds
todo

View File

@@ -1,44 +0,0 @@
---
order: 0
meta:
title: Getting Started
description: Learn how to get started with Reacord.
---
# Getting Started
This guide assumes some familiarity with JavaScript, [React](https://reactjs.org), [Discord.js](https://discord.js.org) and the [Discord API](https://discord.dev). Keep these pages as reference if you need it.
**Note:** Ensure your project has support for running code with JSX. I recommend using [esno](https://npm.im/esno).
## Install
```bash
# npm
npm install reacord discord.js
# yarn
yarn add reacord discord.js
# pnpm
pnpm add reacord discord.js
```
## Setup
Create a Discord.js client and a Reacord instance:
```js
// main.js
import { Client } from "discord.js"
import { ReacordDiscordJs } from "reacord"
const client = new Client()
const reacord = new ReacordDiscordJs(client)
client.on("ready", () => {
console.log("Ready!")
})
await client.login(process.env.BOT_TOKEN)
```

View File

@@ -1,9 +0,0 @@
---
meta:
title: Select Menus
description: Using select menu components
---
# Select Menus
todo

View File

@@ -1,88 +0,0 @@
---
order: 1
meta:
title: Sending Messages
description: Sending messages by creating Reacord instances
---
# Sending Messages with Instances
You can send messages via Reacord to a channel like so.
<details>
<summary>In case you're unaware, click here to see how to get a channel ID.</summary>
1. Enable "Developer Mode" in your Discord client settings.
![Enabling developer mode](/images/developer-mode.png)
1. Right click any channel, and select "Copy ID".
![Copying the channel ID](/images/copy-channel-id.png)
</details>
```jsx
const channelId = "abc123deadbeef"
client.on("ready", () => {
reacord.send(channelId, "Hello, world!")
})
```
The `.send()` function creates a **Reacord instance**. You can pass strings, numbers, or anything that can be rendered by React, such as JSX!
Components rendered through this instance can include state and effects, and the message on Discord will update automatically.
```jsx
function Uptime() {
const [startTime] = useState(Date.now())
const [currentTime, setCurrentTime] = useState(Date.now())
useEffect(() => {
const interval = setInterval(() => {
currentTime(Date.now())
}, 3000)
return () => clearInterval(interval)
}, [])
return <>this message has been shown for {currentTime - startTime}ms</>
}
client.on("ready", () => {
reacord.send(channelId, <Uptime />)
})
```
The instance can be rendered to multiple times, which will update the message each time.
```jsx
const Hello = ({ subject }) => <>Hello, {subject}!</>
client.on("ready", () => {
const instance = reacord.send(channel)
instance.render(<Hello subject="World" />)
instance.render(<Hello subject="Moon" />)
})
```
## Cleaning Up Instances
If you no longer want to use the instance, you can clean it up in a few ways:
- `instance.destroy()` - This will remove the message.
- `instance.deactivate()` - This will keep the message, but it will disable the components on the message, and no longer listen to user interactions.
By default, Reacord has a max limit on the number of active instances, and deactivates older instances to conserve memory. This can be configured through the Reacord options:
```js
const reacord = new ReacordDiscordJs(client, {
// after sending four messages,
// the first one will be deactivated
maxInstances: 3,
})
```
## Discord Slash Commands
<aside className="opacity-75 italic">
This section also applies to other kinds of application commands, such as context menu commands.
</aside>
todo

View File

@@ -1,41 +0,0 @@
import packageJson from "reacord/package.json"
import type { LoaderFunction } from "remix"
import { Link, useLoaderData } from "remix"
import LandingExample from "~/components/landing-example.mdx"
import { MainNavigation } from "~/components/main-navigation"
import type { ContentIndexEntry } from "~/helpers/create-index.server"
import { createContentIndex } from "~/helpers/create-index.server"
import { maxWidthContainer } from "~/styles"
type LoaderData = ContentIndexEntry[]
export const loader: LoaderFunction = async () => {
const data: LoaderData = await createContentIndex("app/routes/docs/guides")
return data
}
export default function Landing() {
const data: LoaderData = useLoaderData()
return (
<div className="flex flex-col min-w-0 min-h-screen text-center">
<header className={maxWidthContainer}>
<MainNavigation guideRoutes={data} />
</header>
<div className="px-4 pb-8 flex flex-1">
<main className="px-4 py-6 rounded-lg shadow bg-slate-800 space-y-5 m-auto w-full max-w-xl">
<h1 className="text-6xl font-light">reacord</h1>
<section className="mx-auto text-sm sm:text-base">
<LandingExample />
</section>
<p className="text-2xl font-light">{packageJson.description}</p>
<Link
to="/docs/guides/getting-started"
className="inline-block px-4 py-3 text-xl transition rounded-lg bg-emerald-700 hover:translate-y-[-2px] hover:bg-emerald-800 hover:shadow"
>
Get Started
</Link>
</main>
</div>
</div>
)
}

View File

@@ -1,4 +0,0 @@
import type { LoaderFunction } from "remix"
import { serveTailwindCss } from "remix-tailwind"
export const loader: LoaderFunction = () => serveTailwindCss()

View File

@@ -1,24 +0,0 @@
import clsx from "clsx"
export const maxWidthContainer = clsx`mx-auto w-full max-w-screen-lg px-4`
export const inlineIconClass = clsx`inline w-5 align-sub`
export const linkClass = clsx`
font-medium inline-block relative
opacity-60 hover:opacity-100 transition-opacity
after:absolute after:block after:w-full after:h-px after:bg-white/50 after:translate-y-[3px] after:opacity-0 after:transition
hover:after:translate-y-[-1px] hover:after:opacity-100
`
export const docsProseClass = clsx`
prose prose-invert
prose-h1:font-light prose-h1:mb-4 prose-h1:text-3xl lg:prose-h1:text-4xl
prose-h2:font-light
prose-h3:font-light
prose-p:my-4
prose-a:font-medium prose-a:text-emerald-400 hover:prose-a:no-underline
prose-strong:font-medium prose-strong:text-emerald-400
prose-pre:font-monospace prose-pre:overflow-x-auto
max-w-none
`