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,25 +0,0 @@
import React from "react"
import { createRoot } from "react-dom"
import { HeadProvider } from "react-head"
import type { PageContextBuiltInClient } from "vite-plugin-ssr/client"
import { getPage } from "vite-plugin-ssr/client"
import { App } from "./app"
import { RouteContextProvider } from "./route-context"
const context = await getPage<PageContextBuiltInClient>()
createRoot(document.querySelector("#app")!).render(
<HeadProvider>
<RouteContextProvider value={{ routeParams: {}, ...(context as any) }}>
<App>
<context.Page />
</App>
</RouteContextProvider>
</HeadProvider>,
)
declare module "react-dom" {
export function createRoot(element: Element): {
render(element: React.ReactNode): void
}
}

View File

@@ -1,49 +0,0 @@
import React from "react"
import { renderToStaticMarkup, renderToString } from "react-dom/server.js"
import { HeadProvider } from "react-head"
import type { PageContextBuiltIn } from "vite-plugin-ssr"
import { dangerouslySkipEscape, escapeInject } from "vite-plugin-ssr"
import { App } from "./app"
import { RouteContextProvider } from "./route-context"
export const passToClient = ["routeParams", "pageData"]
export function render(context: PageContextBuiltIn) {
const headTags: React.ReactElement[] = []
const pageHtml = renderToString(
<HeadProvider headTags={headTags}>
<RouteContextProvider value={context}>
<App>
<context.Page />
</App>
</RouteContextProvider>
</HeadProvider>,
)
const documentHtml = escapeInject/* HTML */ `
<!DOCTYPE html>
<html lang="en" class="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"
/>
${dangerouslySkipEscape(renderToStaticMarkup(<>{headTags}</>))}
</head>
<body>
<div id="app" class="contents">${dangerouslySkipEscape(pageHtml)}</div>
</body>
</html>
`
return { documentHtml }
}

View File

@@ -1,14 +0,0 @@
import { description } from "reacord/package.json"
import { Meta, Title } from "react-head"
import "tailwindcss/tailwind.css"
import "./styles/prism-theme.css"
export function App({ children }: { children: React.ReactNode }) {
return (
<>
<Title>Reacord</Title>
<Meta name="description" content={description} />
{children}
</>
)
}

View File

@@ -1,26 +0,0 @@
import { ExternalLink } from "./external-link"
export type AppLinkProps = {
type: "internal" | "external"
label: React.ReactNode
to: string
className?: string
}
export function AppLink(props: AppLinkProps) {
switch (props.type) {
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,48 +0,0 @@
import clsx from "clsx"
import { guideLinks } from "../data/guide-links.preval"
import { useScrolled } from "../hooks/dom/use-scrolled"
import {
docsProseClass,
linkClass,
maxWidthContainer,
} from "../styles/components"
import { AppLink } from "./app-link"
import { MainNavigation } from "./main-navigation"
export function GuidePageLayout() {
return (
<>
<HeaderPanel>
<div className={maxWidthContainer}>
<MainNavigation />
</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">
{guideLinks.map((link) => (
<li key={link.to}>
<AppLink {...link} className={linkClass} />
</li>
))}
</ul>
</nav>
<section className={clsx(docsProseClass, "pb-8 flex-1 min-w-0")}>
{/* todo */}
</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,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,39 +0,0 @@
import { AppLink } from "../components/app-link"
import { guideLinks } from "../data/guide-links.preval"
import { mainLinks } from "../data/main-links"
import { linkClass } from "../styles/components"
import { PopoverMenu } from "./popover-menu"
export function MainNavigation() {
return (
<nav className="flex justify-between items-center h-16">
<a href="/">
<h1 className="text-3xl font-light">reacord</h1>
</a>
<div className="hidden md:flex gap-4">
{mainLinks.map((link) => (
<AppLink {...link} key={link.to} className={linkClass} />
))}
</div>
<div className="md:hidden">
<PopoverMenu>
{mainLinks.map((link) => (
<AppLink
{...link}
key={link.to}
className={PopoverMenu.itemClass}
/>
))}
<hr className="border-0 h-[2px] bg-black/50" />
{guideLinks.map((link) => (
<AppLink
{...link}
key={link.to}
className={PopoverMenu.itemClass}
/>
))}
</PopoverMenu>
</div>
</nav>
)
}

View File

@@ -1,67 +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 { linkClass } from "../styles/components"
// todo: remove useRect usage and rely on css absolute positioning instead
export 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>
</>
)
}
PopoverMenu.itemClass = clsx`
px-3 py-2 transition text-left font-medium block
opacity-50 hover:opacity-100 hover:bg-black/30
`

View File

@@ -1,34 +0,0 @@
import glob from "fast-glob"
import grayMatter from "gray-matter"
import { readFile } from "node:fs/promises"
import { join } from "node:path"
import type { AppLinkProps } from "../components/app-link"
const docsFolderPath = new URL("../docs", import.meta.url).pathname
const guideFiles = await glob("**/*.md", { cwd: docsFolderPath })
const entries = await Promise.all(
guideFiles.map(async (file) => {
const content = await readFile(join(docsFolderPath, file), "utf-8")
const { data } = grayMatter(content)
let order = Number(data.order)
if (!Number.isFinite(order)) {
order = Number.POSITIVE_INFINITY
}
return {
route: `/docs/${file.replace(/\.mdx?$/, "")}`,
title: String(data.title || ""),
order,
}
}),
)
export const guideLinks: AppLinkProps[] = entries
.sort((a, b) => a.order - b.order)
.map((item) => ({
type: "internal",
label: item.title,
to: item.route,
}))

View File

@@ -1,37 +0,0 @@
import {
CodeIcon,
DocumentTextIcon,
ExternalLinkIcon,
} from "@heroicons/react/solid"
import type { AppLinkProps } from "../components/app-link"
import { inlineIconClass } from "../styles/components"
export const mainLinks: AppLinkProps[] = [
{
type: "internal",
to: "/docs/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
</>
),
},
]

View File

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

View File

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

View File

@@ -1,43 +0,0 @@
---
order: 0
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,8 +0,0 @@
---
title: Select Menus
description: Using select menu components
---
# Select Menus
todo

View File

@@ -1,78 +0,0 @@
---
order: 1
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.
```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,11 +0,0 @@
import { lazy } from "react"
export function lazyNamed<
Key extends string,
Component extends React.ComponentType,
>(key: Key, loadModule: () => Promise<Record<Key, Component>>) {
return lazy<Component>(async () => {
const mod = await loadModule()
return { default: mod[key] }
})
}

View File

@@ -1,8 +0,0 @@
import { useState } from "react"
import { useWindowEvent } from "./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 +0,0 @@
export default "/docs/*"

View File

@@ -1,23 +0,0 @@
import type { OnBeforeRenderFn } from "../router-types"
export type DocsPageProps = {
title?: string
description?: string
content: string
}
export const onBeforeRender: OnBeforeRenderFn<DocsPageProps> = async (
context,
) => {
const documentPath = context.routeParams["*"]
const document = await import(`../docs/${documentPath}.md`)
return {
pageContext: {
pageData: {
title: document.attributes.title,
description: document.attributes.description,
content: document.html,
},
},
}
}

View File

@@ -1,55 +0,0 @@
import clsx from "clsx"
import { Meta, Title } from "react-head"
import { AppLink } from "../components/app-link"
import { MainNavigation } from "../components/main-navigation"
import { guideLinks } from "../data/guide-links.preval"
import { useScrolled } from "../hooks/dom/use-scrolled"
import { usePageData } from "../route-context"
import {
docsProseClass,
linkClass,
maxWidthContainer,
} from "../styles/components"
import type { DocsPageProps } from "./docs.page.server"
export default function DocsPage() {
const data = usePageData<DocsPageProps>()
return (
<>
<Title>{data.title} | Reacord</Title>
<Meta name="description" content={data.description} />
<HeaderPanel>
<div className={maxWidthContainer}>
<MainNavigation />
</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">
{guideLinks.map((link) => (
<li key={link.to}>
<AppLink {...link} className={linkClass} />
</li>
))}
</ul>
</nav>
<section
className={clsx(docsProseClass, "pb-8 flex-1 min-w-0")}
dangerouslySetInnerHTML={{ __html: data.content }}
/>
</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,30 +0,0 @@
import packageJson from "reacord/package.json"
import { html as landingExampleHtml } from "../components/landing-example.md"
import { MainNavigation } from "../components/main-navigation"
import { maxWidthContainer } from "../styles/components"
export default function LandingPage() {
return (
<div className="flex flex-col min-w-0 min-h-screen text-center">
<header className={maxWidthContainer}>
<MainNavigation />
</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"
dangerouslySetInnerHTML={{ __html: landingExampleHtml }}
/>
<p className="text-2xl font-light">{packageJson.description}</p>
<a
href="/docs/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
</a>
</main>
</div>
</div>
)
}

View File

@@ -1,4 +0,0 @@
import "react"
declare module "react" {
export function createContext<Value>(): Context<Value | undefined>
}

View File

@@ -1,18 +0,0 @@
import { createContext, useContext } from "react"
export type RouteContextValue = {
routeParams: Record<string, string>
pageData?: Record<string, unknown>
}
const Context = createContext<RouteContextValue>()
export const RouteContextProvider = Context.Provider
export function useRouteParams() {
return useContext(Context)?.routeParams ?? {}
}
export function usePageData<T>() {
return useContext(Context)?.pageData as T
}

View File

@@ -1,10 +0,0 @@
import type { Promisable } from "type-fest"
import type { PageContextBuiltIn } from "vite-plugin-ssr"
export type OnBeforeRenderFn<PageProps> = (
context: PageContextBuiltIn,
) => Promisable<{
pageContext: {
pageData: PageProps
}
}>

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
`

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,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,8 +0,0 @@
/// <reference types="vite/client" />
declare module "*.md" {
import type { ComponentType } from "react"
export const attributes: Record<string, any>
export const html: string
export const ReactComponent: ComponentType
}