28 Commits

Author SHA1 Message Date
itsMapleLeaf
69b3112d32 account for trailing slashes 2023-03-12 16:50:11 -05:00
itsMapleLeaf
b4fb6bc47c website cleanup 2023-03-12 16:38:32 -05:00
itsMapleLeaf
7aaef5f85f lint fixe 2023-03-12 16:38:26 -05:00
itsMapleLeaf
b755290569 skip website test for now 2023-03-12 16:29:46 -05:00
itsMapleLeaf
f0ad743080 menu improvements 2023-03-12 16:28:51 -05:00
itsMapleLeaf
af3d1c5058 add start script 2023-03-12 16:26:47 -05:00
itsMapleLeaf
bdee9454f2 disable ligatures 2023-03-12 16:19:52 -05:00
itsMapleLeaf
84348d287f active link style 2023-03-12 16:14:46 -05:00
itsMapleLeaf
6da6008d2c guide pages (why was that so easy wtf) 2023-03-12 15:58:47 -05:00
itsMapleLeaf
bece6c42fc ignore .vercel 2023-03-12 15:12:41 -05:00
itsMapleLeaf
abc809c9fb always run typedoc before build 2023-03-12 15:08:57 -05:00
itsMapleLeaf
d35659f6f6 fix website build 2023-03-12 15:01:12 -05:00
itsMapleLeaf
3969e6471f finished landing 2023-03-12 14:43:53 -05:00
itsMapleLeaf
95041acfd4 root layout 2023-03-12 13:36:03 -05:00
itsMapleLeaf
eb0409f1a2 add astro and configure some things 2023-03-12 13:20:49 -05:00
itsMapleLeaf
fbab145f39 fix dev script 2023-03-12 12:53:37 -05:00
itsMapleLeaf
f59323f245 remove node dep 2022-10-15 00:27:33 -05:00
itsMapleLeaf
1c37d37c28 vercel must be stopped 2022-10-14 14:01:58 -05:00
itsMapleLeaf
408ab4ce89 add version to helpers package 2022-10-14 13:59:27 -05:00
itsMapleLeaf
a8a64e601a pnpm lock 2022-10-14 13:56:45 -05:00
itsMapleLeaf
d88b982830 remove old release script 2022-10-14 13:55:16 -05:00
itsMapleLeaf
d87c27183a upgrades 2022-10-14 13:42:50 -05:00
itsMapleLeaf
b141ca1868 Merge branch 'main' of https://github.com/itsMapleLeaf/reacord 2022-10-14 13:35:14 -05:00
itsMapleLeaf
dfa7f8090c update github workflows 2022-10-14 13:35:12 -05:00
Darius
82068d2d83 Merge pull request #22 from itsMapleLeaf/changeset-release/main
Version Packages
2022-10-14 13:28:30 -05:00
github-actions[bot]
216ae7a29a Version Packages 2022-10-14 18:25:32 +00:00
itsMapleLeaf
9813a01a19 import react-reconciler/constants.js for esm 2022-10-14 13:24:12 -05:00
itsMapleLeaf
0be321b64e move helpers to new workspace folder 2022-10-14 13:22:55 -05:00
108 changed files with 2645 additions and 7521 deletions

View File

@@ -13,6 +13,7 @@ module.exports = {
], ],
parserOptions: { parserOptions: {
project: require.resolve("./tsconfig.base.json"), project: require.resolve("./tsconfig.base.json"),
extraFileExtensions: [".astro"],
}, },
overrides: [ overrides: [
{ {
@@ -21,5 +22,17 @@ module.exports = {
project: require.resolve("./packages/website/cypress/tsconfig.json"), project: require.resolve("./packages/website/cypress/tsconfig.json"),
}, },
}, },
{
files: ["*.astro"],
parser: "astro-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
},
rules: {
"react/no-unknown-property": "off",
"react/jsx-key": "off",
"react/jsx-no-undef": "off",
},
},
], ],
} }

View File

@@ -24,8 +24,8 @@ jobs:
# so we test them separate # so we test them separate
- name: test reacord - name: test reacord
run: pnpm -C packages/reacord test run: pnpm -C packages/reacord test
- name: test website # - name: test website
run: pnpm -C packages/website test # run: pnpm -C packages/website test
- name: build - name: build
run: pnpm --recursive run build run: pnpm --recursive run build
- name: lint - name: lint
@@ -35,11 +35,19 @@ jobs:
name: ${{ matrix.command.name }} name: ${{ matrix.command.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - name: checkout
- uses: actions/setup-node@v2 uses: actions/checkout@v3
- name: setup pnpm
uses: pnpm/action-setup@v2
with: with:
# https://github.com/actions/setup-node#supported-version-syntax version: 7.13.4
node-version: "16"
- run: npm i -g pnpm@7.5.0 - name: setup node
uses: actions/setup-node@v3
with:
node-version: 16
cache: pnpm
- run: pnpm install --frozen-lockfile - run: pnpm install --frozen-lockfile
- run: ${{ matrix.command.run }} - run: ${{ matrix.command.run }}

View File

@@ -14,15 +14,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 7.13.4
- name: setup node - name: setup node
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
cache: pnpm
- name: install pnpm
run: npm install -g pnpm
- name: install deps - name: install deps
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ coverage
build build
.cache .cache
.vercel

14
.prettierrc.cjs Normal file
View File

@@ -0,0 +1,14 @@
const base = require("@itsmapleleaf/configs/prettier")
module.exports = {
...base,
plugins: [require.resolve("prettier-plugin-astro")],
overrides: [
{
files: "*.astro",
options: {
parser: "astro",
},
},
],
}

View File

@@ -10,17 +10,17 @@
"release": "pnpm -r run build && changeset publish" "release": "pnpm -r run build && changeset publish"
}, },
"devDependencies": { "devDependencies": {
"@changesets/cli": "^2.24.0", "@changesets/cli": "^2.25.0",
"@itsmapleleaf/configs": "^1.1.5", "@itsmapleleaf/configs": "^1.1.7",
"@rushstack/eslint-patch": "^1.1.4", "@rushstack/eslint-patch": "^1.2.0",
"@types/eslint": "^8.4.5", "@types/eslint": "^8.4.6",
"eslint": "^8.20.0", "astro-eslint-parser": "^0.12.0",
"node": "^16.16.0", "eslint": "^8.36.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"typescript": "^4.7.4" "prettier-plugin-astro": "^0.8.0",
"typescript": "^4.8.4"
}, },
"resolutions": { "resolutions": {
"esbuild": "latest" "esbuild": "latest"
}, }
"prettier": "@itsmapleleaf/configs/prettier"
} }

View File

@@ -0,0 +1,11 @@
{
"name": "@reacord/helpers",
"version": "0.0.0",
"private": true,
"dependencies": {
"@types/lodash-es": "^4.17.6",
"lodash-es": "^4.17.21",
"type-fest": "^2.17.0",
"vitest": "^0.18.1"
}
}

View File

@@ -10,7 +10,7 @@ export function pruneNullishValues<T>(input: T): PruneNullishValues<T> {
} }
const result: any = {} const result: any = {}
for (const [key, value] of Object.entries(input)) { for (const [key, value] of Object.entries(input as any)) {
if (value != undefined) { if (value != undefined) {
result[key] = pruneNullishValues(value) result[key] = pruneNullishValues(value)
} }

View File

@@ -1,5 +1,13 @@
# reacord # reacord
## 0.5.2
### Patch Changes
- 9813a01: import react-reconciler/constants.js for esm
ESM projects which tried to import reacord would fail due to the lack of .js on this import
## 0.5.1 ## 0.5.1
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
import { snakeCaseDeep } from "@reacord/helpers/convert-object-property-case"
import { omit } from "@reacord/helpers/omit"
import React from "react" import React from "react"
import { snakeCaseDeep } from "../../../helpers/convert-object-property-case"
import { omit } from "../../../helpers/omit"
import { ReacordElement } from "../../internal/element.js" import { ReacordElement } from "../../internal/element.js"
import type { MessageOptions } from "../../internal/message" import type { MessageOptions } from "../../internal/message"
import { Node } from "../../internal/node.js" import { Node } from "../../internal/node.js"

View File

@@ -1,7 +1,7 @@
import { isInstanceOf } from "@reacord/helpers/is-instance-of"
import { randomUUID } from "node:crypto" import { randomUUID } from "node:crypto"
import type { ReactNode } from "react" import type { ReactNode } from "react"
import React from "react" import React from "react"
import { isInstanceOf } from "../../../helpers/is-instance-of"
import { ReacordElement } from "../../internal/element.js" import { ReacordElement } from "../../internal/element.js"
import type { ComponentInteraction } from "../../internal/interaction" import type { ComponentInteraction } from "../../internal/interaction"
import type { import type {

View File

@@ -1,5 +1,5 @@
import { raise } from "@reacord/helpers/raise"
import * as React from "react" import * as React from "react"
import { raise } from "../../helpers/raise"
import type { ReacordInstance } from "./instance" import type { ReacordInstance } from "./instance"
const Context = React.createContext<ReacordInstance | undefined>(undefined) const Context = React.createContext<ReacordInstance | undefined>(undefined)

View File

@@ -1,10 +1,10 @@
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
import { pick } from "@reacord/helpers/pick"
import { pruneNullishValues } from "@reacord/helpers/prune-nullish-values"
import { raise } from "@reacord/helpers/raise"
import * as Discord from "discord.js" import * as Discord from "discord.js"
import type { ReactNode } from "react" import type { ReactNode } from "react"
import type { Except } from "type-fest" import type { Except } from "type-fest"
import { pick } from "../../helpers/pick"
import { pruneNullishValues } from "../../helpers/prune-nullish-values"
import { raise } from "../../helpers/raise"
import type { ComponentInteraction } from "../internal/interaction" import type { ComponentInteraction } from "../internal/interaction"
import type { import type {
Message, Message,
@@ -207,7 +207,7 @@ export class ReacordDiscordJs extends Reacord {
]), ]),
), ),
displayName: interaction.member.displayName, displayName: interaction.member.displayName,
roles: [...interaction.member.roles.cache.map((role) => role.id)], roles: interaction.member.roles.cache.map((role) => role.id),
joinedAt: interaction.member.joinedAt?.toISOString(), joinedAt: interaction.member.joinedAt?.toISOString(),
premiumSince: interaction.member.premiumSince?.toISOString(), premiumSince: interaction.member.premiumSince?.toISOString(),
communicationDisabledUntil: communicationDisabledUntil:

View File

@@ -1,5 +1,5 @@
import { last } from "@reacord/helpers/last"
import type { Except } from "type-fest" import type { Except } from "type-fest"
import { last } from "../../helpers/last"
import type { EmbedOptions } from "../core/components/embed-options" import type { EmbedOptions } from "../core/components/embed-options"
import type { SelectProps } from "../core/components/select" import type { SelectProps } from "../core/components/select"

View File

@@ -1,7 +1,7 @@
import { raise } from "@reacord/helpers/raise.js"
import type { HostConfig } from "react-reconciler" import type { HostConfig } from "react-reconciler"
import ReactReconciler from "react-reconciler" import ReactReconciler from "react-reconciler"
import { DefaultEventPriority } from "react-reconciler/constants" import { DefaultEventPriority } from "react-reconciler/constants.js"
import { raise } from "../../helpers/raise.js"
import { Node } from "./node.js" import { Node } from "./node.js"
import type { Renderer } from "./renderers/renderer" import type { Renderer } from "./renderers/renderer"
import { TextNode } from "./text-node.js" import { TextNode } from "./text-node.js"

View File

@@ -2,7 +2,7 @@
"name": "reacord", "name": "reacord",
"type": "module", "type": "module",
"description": "Create interactive Discord messages using React.", "description": "Create interactive Discord messages using React.",
"version": "0.5.1", "version": "0.5.2",
"types": "./dist/main.d.ts", "types": "./dist/main.d.ts",
"homepage": "https://reacord.mapleleaf.dev", "homepage": "https://reacord.mapleleaf.dev",
"repository": "https://github.com/itsMapleLeaf/reacord.git", "repository": "https://github.com/itsMapleLeaf/reacord.git",
@@ -41,8 +41,7 @@
"test": "vitest --coverage --no-watch", "test": "vitest --coverage --no-watch",
"test-dev": "vitest", "test-dev": "vitest",
"test-manual": "nodemon --exec tsx --ext ts,tsx ./scripts/discordjs-manual-test.tsx", "test-manual": "nodemon --exec tsx --ext ts,tsx ./scripts/discordjs-manual-test.tsx",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit"
"release": "bash scripts/release.sh"
}, },
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
@@ -61,6 +60,7 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@reacord/helpers": "workspace:*",
"@types/lodash-es": "^4.17.6", "@types/lodash-es": "^4.17.6",
"c8": "^7.12.0", "c8": "^7.12.0",
"discord.js": "^14.0.3", "discord.js": "^14.0.3",
@@ -70,7 +70,6 @@
"prettier": "^2.7.1", "prettier": "^2.7.1",
"pretty-ms": "^8.0.0", "pretty-ms": "^8.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"release-it": "^15.1.3",
"tsup": "^6.1.3", "tsup": "^6.1.3",
"tsx": "^3.8.0", "tsx": "^3.8.0",
"type-fest": "^2.17.0", "type-fest": "^2.17.0",

View File

@@ -1,14 +1,14 @@
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
/* eslint-disable require-await */ /* eslint-disable require-await */
import { logPretty } from "@reacord/helpers/log-pretty"
import { omit } from "@reacord/helpers/omit"
import { pruneNullishValues } from "@reacord/helpers/prune-nullish-values"
import { raise } from "@reacord/helpers/raise"
import { waitFor } from "@reacord/helpers/wait-for"
import { randomUUID } from "node:crypto" import { randomUUID } from "node:crypto"
import { setTimeout } from "node:timers/promises" import { setTimeout } from "node:timers/promises"
import type { ReactNode } from "react" import type { ReactNode } from "react"
import { expect } from "vitest" import { expect } from "vitest"
import { logPretty } from "../helpers/log-pretty"
import { omit } from "../helpers/omit"
import { pruneNullishValues } from "../helpers/prune-nullish-values"
import { raise } from "../helpers/raise"
import { waitFor } from "../helpers/wait-for"
import type { import type {
ChannelInfo, ChannelInfo,
GuildInfo, GuildInfo,

View File

@@ -1,3 +1,4 @@
{ {
"extends": "../../tsconfig.base.json" "extends": "../../tsconfig.base.json",
"include": ["**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"]
} }

View File

@@ -1,5 +1,4 @@
node_modules node_modules
/.cache /.cache
/build /build
/public/build /public/build
@@ -9,3 +8,4 @@ cypress/videos
cypress/screenshots cypress/screenshots
*.out.css *.out.css
/api /api
.astro

View File

@@ -1,5 +1,12 @@
# website # website
## 0.4.3
### Patch Changes
- Updated dependencies [9813a01]
- reacord@0.5.2
## 0.4.2 ## 0.4.2
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,21 +0,0 @@
import { renderToString } from "react-dom/server"
import type { EntryContext } from "@remix-run/node"
import { RemixServer } from "@remix-run/react"
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,27 +0,0 @@
import { HeartIcon } from "@heroicons/react/solid"
import clsx from "clsx"
import { ExternalLink } from "~/modules/dom/external-link"
import { linkClass, maxWidthContainer } from "~/modules/ui/components"
export function AppFooter() {
return (
<footer className={clsx(maxWidthContainer, "text-xs opacity-75")}>
<address className="not-italic">
&copy; {new Date().getFullYear()} itsMapleLeaf
</address>
<p>
Coded with <HeartIcon className="inline w-4 align-sub" /> using{" "}
<ExternalLink className={linkClass()} href="https://remix.run">
Remix
</ExternalLink>
</p>
<p>
Uses{" "}
<ExternalLink className={linkClass()} href="https://umami.is/">
umami
</ExternalLink>{" "}
for simple, non-identifying analytics.
</p>
</footer>
)
}

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,20 +0,0 @@
import type { ReactNode } from "react"
import { useEffect, useRef } from "react"
import { createPortal } from "react-dom"
export function Portal({ children }: { children: ReactNode }) {
const containerRef = useRef<Element>()
if (!containerRef.current && typeof document !== "undefined") {
containerRef.current = document.createElement("react-portal")
document.body.append(containerRef.current)
}
useEffect(() => () => containerRef.current!.remove(), [])
return containerRef.current ? (
createPortal(children, containerRef.current)
) : (
<>{children}</>
)
}

View File

@@ -1,3 +0,0 @@
export function raise(error: unknown): never {
throw error instanceof Error ? error : new Error(String(error))
}

View File

@@ -1,26 +0,0 @@
{/* prettier-ignore */}
```tsx
import * as React from "react"
import { Button, useInstance } from "reacord"
function Counter() {
const [count, setCount] = React.useState(0)
const instance = useInstance()
return (
<>
this button was clicked {count} times
<Button
label="+1"
style="success"
onClick={() => setCount(count + 1)}
/>
<Button
label="delete"
emoji="🗑"
style="danger"
onClick={() => instance.destroy()}
/>
</>
)
}
```

View File

@@ -1,14 +0,0 @@
import type { ReactNode } from "react"
import type { PathPattern } from "react-router"
import { useMatch } from "react-router"
export function ActiveLink({
to,
children,
}: {
to: string | PathPattern
children: (props: { active: boolean }) => ReactNode
}) {
const match = useMatch(to)
return <>{children({ active: match != undefined })}</>
}

View File

@@ -1,32 +0,0 @@
import type { ComponentPropsWithoutRef } from "react"
import { Link } from "@remix-run/react"
import { ExternalLink } from "~/modules/dom/external-link"
export type AppLinkProps = ComponentPropsWithoutRef<"a"> & {
type: "internal" | "external" | "router"
to: string
}
export function AppLink({ type, to, children, ...props }: AppLinkProps) {
if (type === "internal") {
return (
<a href={to} {...props}>
{children}
</a>
)
}
if (type === "external") {
return (
<ExternalLink href={to} {...props}>
{children}
</ExternalLink>
)
}
return (
<Link to={to} {...props}>
{children}
</Link>
)
}

View File

@@ -1,10 +0,0 @@
import { createContext, useContext } from "react"
import type { GuideLink } from "~/modules/navigation/load-guide-links.server"
const Context = createContext<GuideLink[]>([])
export const GuideLinksProvider = Context.Provider
export function useGuideLinksContext() {
return useContext(Context)
}

View File

@@ -1,39 +0,0 @@
import glob from "fast-glob"
import grayMatter from "gray-matter"
import { readFile } from "node:fs/promises"
import { join, parse } from "node:path"
import { z } from "zod"
const guidesFolder = "app/routes/guides"
const frontmatterSchema = z.object({
meta: z.object({
title: z.string(),
description: z.string(),
}),
order: z.number().optional(),
})
export type GuideLink = Awaited<ReturnType<typeof loadGuideLinks>>[0]
export async function loadGuideLinks() {
const guideFiles = await glob(`**/*.md`, { cwd: guidesFolder })
const links = await Promise.all(
guideFiles.map(async (file) => {
const result = grayMatter(await readFile(join(guidesFolder, file)))
const data = frontmatterSchema.parse(result.data)
return {
title: data.meta.title,
order: data.order ?? Number.POSITIVE_INFINITY,
link: {
type: "router" as const,
to: `/guides/${parse(file).name}`,
children: data.meta.title,
},
}
}),
)
return links.sort((a, b) => a.order - b.order)
}

View File

@@ -1,37 +0,0 @@
import {
CodeIcon,
DocumentTextIcon,
ExternalLinkIcon,
} from "@heroicons/react/solid"
import type { AppLinkProps } from "~/modules/navigation/app-link"
import { inlineIconClass } from "../ui/components"
export const mainLinks: AppLinkProps[] = [
{
type: "internal",
to: "/guides/getting-started",
children: (
<>
<DocumentTextIcon className={inlineIconClass} /> Guides
</>
),
},
{
type: "internal",
to: "/api/",
children: (
<>
<CodeIcon className={inlineIconClass} /> API Reference
</>
),
},
{
type: "external",
to: "https://github.com/itsMapleLeaf/reacord",
children: (
<>
<ExternalLinkIcon className={inlineIconClass} /> GitHub
</>
),
},
]

View File

@@ -1,69 +0,0 @@
import { Menu, Transition } from "@headlessui/react"
import { MenuAlt4Icon } from "@heroicons/react/outline"
import clsx from "clsx"
import { ActiveLink } from "~/modules/navigation/active-link"
import { useGuideLinksContext } from "~/modules/navigation/guide-links-context"
import { Popper } from "~/modules/ui/popper"
import { AppLink } from "./app-link"
import { mainLinks } from "./main-links"
export function MainNavigationMenu() {
const guideLinks = useGuideLinksContext()
return (
<Menu>
<Popper
renderReference={(reference) => (
<Menu.Button {...reference}>
<MenuAlt4Icon className="w-6" />
<span className="sr-only">Menu</span>
</Menu.Button>
)}
renderPopover={() => (
<Transition
enter="transition"
enterFrom="translate-y-4 opacity-0"
enterTo="translate-y-0 opacity-100"
leave="transition"
leaveFrom="translate-y-0 opacity-100"
leaveTo="translate-y-4 opacity-0"
>
<Menu.Items className="w-48 max-h-[calc(100vh-5rem)] bg-slate-800 shadow rounded-lg overflow-hidden overflow-y-auto focus:outline-none">
{mainLinks.map((link) => (
<Menu.Item key={link.to}>
{({ active }) => (
<AppLink {...link} className={menuItemClass({ active })} />
)}
</Menu.Item>
))}
<Menu.Item disabled>
<hr className="border-0 h-[2px] bg-black/50" />
</Menu.Item>
{guideLinks.map(({ link }) => (
<Menu.Item key={link.to}>
{(menuItem) => (
<ActiveLink to={link.to}>
{(activeLink) => (
<AppLink
{...link}
className={menuItemClass({
active: activeLink.active || menuItem.active,
})}
/>
)}
</ActiveLink>
)}
</Menu.Item>
))}
</Menu.Items>
</Transition>
)}
/>
</Menu>
)
}
const menuItemClass = ({ active = false }) =>
clsx(
clsx`px-3 py-2 transition text-left font-medium block opacity-50`,
active && clsx`opacity-100 bg-black/75 text-emerald-400`,
)

View File

@@ -1,24 +0,0 @@
import { AppLogo } from "~/modules/app/app-logo"
import { linkClass } from "../ui/components"
import { AppLink } from "./app-link"
import { mainLinks } from "./main-links"
import { MainNavigationMenu } from "./main-navigation-menu"
export function MainNavigation() {
return (
<nav className="flex justify-between items-center h-16">
<a href="/">
<AppLogo className="w-32" />
<span className="sr-only">Home</span>
</a>
<div className="hidden md:flex gap-4">
{mainLinks.map((link) => (
<AppLink {...link} key={link.to} className={linkClass()} />
))}
</div>
<div className="md:hidden">
<MainNavigationMenu />
</div>
</nav>
)
}

View File

@@ -1,44 +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 = ({ active = false } = {}) =>
clsx(
clsx`font-medium inline-block relative`,
clsx`opacity-60 hover:opacity-100 transition-opacity`,
clsx`after:absolute after:block after:w-full after:h-px after:bg-white/50 after:translate-y-[3px] after:opacity-0 after:transition`,
clsx`hover:after:translate-y-[-1px] hover:after:opacity-100`,
active
? clsx`text-emerald-500 after:bg-emerald-500`
: clsx`after:bg-white/50`,
)
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-3
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
prose-code:before:hidden prose-code:after:hidden prose-code:text-slate-400
prose-li:mb-5
max-w-none
`
export const buttonClass = ({
variant,
}: {
variant: "solid" | "semiblack"
}) => {
return clsx(
clsx`inline-block mt-4 px-4 py-2.5 text-xl transition rounded-lg`,
clsx`hover:translate-y-[-2px] hover:shadow`,
clsx`active:translate-y-[0px] active:transition-none`, // using translate-y-[0px] instead of just -0 so it takes priority
variant === "solid" && clsx`bg-emerald-700 hover:bg-emerald-800`,
variant === "semiblack" && clsx`bg-black/25 hover:bg-black/40`,
)
}

View File

@@ -1,80 +0,0 @@
import { XIcon } from "@heroicons/react/outline"
import clsx from "clsx"
import type { ReactNode } from "react"
import { useEffect, useRef, useState } from "react"
import { FocusOn } from "react-focus-on"
import { Portal } from "~/modules/dom/portal"
export function Modal({
children,
visible,
onClose,
}: {
children: ReactNode
visible: boolean
onClose: () => void
}) {
const closeButtonRef = useRef<HTMLButtonElement>(null)
useEffect(() => {
if (visible) {
// trying to immediately focus doesn't work for whatever reason
// neither did requestAnimationFrame
setTimeout(() => {
closeButtonRef.current?.focus()
}, 50)
}
}, [visible])
return (
<Portal>
<div
className={clsx(
"bg-black/70 fixed inset-0 transition-all flex flex-col p-4",
visible ? "opacity-100 visible" : "opacity-0 invisible",
)}
>
<FocusOn
className={clsx(
"m-auto flex flex-col gap-2 w-full max-h-full max-w-screen-sm overflow-y-auto transition",
visible ? "translate-y-0" : "translate-y-3",
)}
enabled={visible}
onClickOutside={onClose}
onEscapeKey={onClose}
>
<button
type="button"
className="self-end"
onClick={onClose}
ref={closeButtonRef}
>
<span className="sr-only">Close</span>
<XIcon aria-hidden className="w-6 text-white" />
</button>
<div className={clsx("bg-slate-700 rounded-md shadow p-4")}>
{children}
</div>
</FocusOn>
</div>
</Portal>
)
}
export function UncontrolledModal({
children,
button,
}: {
children: ReactNode
button: (buttonProps: { onClick: () => void }) => void
}) {
const [visible, setVisible] = useState(false)
return (
<>
{button({ onClick: () => setVisible(true) })}
<Modal visible={visible} onClose={() => setVisible(false)}>
{children}
</Modal>
</>
)
}

View File

@@ -1,41 +0,0 @@
import { useRect } from "@reach/rect"
import * as React from "react"
import { Portal } from "~/modules/dom/portal"
export function Popper({
renderReference,
renderPopover,
}: {
renderReference: (referenceProps: {
ref: (element: HTMLElement | null | undefined) => void
}) => React.ReactNode
renderPopover: () => React.ReactNode
}) {
const [reference, referenceRef] = React.useState<HTMLElement | null>()
const referenceRect = useRect(useValueAsRefObject(reference))
return (
<>
{renderReference({ ref: referenceRef })}
<Portal>
{referenceRect && (
<div
className="fixed -translate-x-full"
style={{
left: referenceRect.right,
top: referenceRect.bottom + 16,
}}
>
{renderPopover()}
</div>
)}
</Portal>
</>
)
}
function useValueAsRefObject<Value>(value: Value): { readonly current: Value } {
const ref = React.useRef<Value>(value)
ref.current = value
return ref
}

View File

@@ -1,9 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.prose aside {
@apply opacity-75 italic border-l-4 pl-3 border-white/50;
}
}

View File

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

View File

@@ -1,2 +0,0 @@
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node/globals" />

View File

@@ -1,94 +0,0 @@
import type { LinksFunction, MetaFunction } from "@remix-run/node"
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from "@remix-run/react"
import packageJson from "reacord/package.json"
import bannerUrl from "~/assets/banner.png"
import faviconUrl from "~/assets/favicon.png"
import { GuideLinksProvider } from "~/modules/navigation/guide-links-context"
import { loadGuideLinks } from "~/modules/navigation/load-guide-links.server"
import prismThemeCss from "~/modules/ui/prism-theme.css"
import tailwindCss from "~/modules/ui/tailwind.out.css"
export const meta: MetaFunction = () => ({
"title": "Reacord",
"description": packageJson.description,
"theme-color": "#21754b",
"og:url": "https://reacord.mapleleaf.dev/",
"og:type": "website",
"og:title": "Reacord",
"og:description": "Create interactive Discord messages using React",
"og:image": bannerUrl,
"twitter:card": "summary_large_image",
"twitter:domain": "reacord.mapleleaf.dev",
"twitter:url": "https://reacord.mapleleaf.dev/",
"twitter:title": "Reacord",
"twitter:description": "Create interactive Discord messages using React",
"twitter:image": bannerUrl,
})
export const links: LinksFunction = () => [
{ rel: "icon", type: "image/png", href: faviconUrl },
{ rel: "stylesheet", href: tailwindCss },
{ rel: "stylesheet", href: prismThemeCss },
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "preload",
as: "style",
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",
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",
},
]
export async function loader() {
return {
guideLinks: await loadGuideLinks(),
}
}
export default function App() {
const data = useLoaderData<typeof loader>()
return (
<html lang="en" className="bg-slate-900 text-slate-100">
<head>
{/* eslint-disable-next-line unicorn/text-encoding-identifier-case */}
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
{process.env.NODE_ENV === "production" && (
<script
async
defer
data-website-id="e3ce3a50-720e-4489-be37-cc091c1b7029"
src="https://umami-production-72bc.up.railway.app/umami.js"
></script>
)}
</head>
<body>
<GuideLinksProvider value={data.guideLinks}>
<Outlet />
</GuideLinksProvider>
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
)
}

View File

@@ -1,43 +0,0 @@
import clsx from "clsx"
import { Outlet } from "@remix-run/react"
import { ActiveLink } from "~/modules/navigation/active-link"
import { AppLink } from "~/modules/navigation/app-link"
import { useGuideLinksContext } from "~/modules/navigation/guide-links-context"
import { MainNavigation } from "~/modules/navigation/main-navigation"
import {
docsProseClass,
linkClass,
maxWidthContainer,
} from "~/modules/ui/components"
export default function GuidePage() {
const guideLinks = useGuideLinksContext()
return (
<div className="isolate">
<header className="bg-slate-700/30 shadow sticky top-0 backdrop-blur-sm transition z-10 flex">
<div className={maxWidthContainer}>
<MainNavigation />
</div>
</header>
<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}>
<ActiveLink to={link.to}>
{({ active }) => (
<AppLink {...link} className={linkClass({ active })} />
)}
</ActiveLink>
</li>
))}
</ul>
</nav>
<section className={clsx(docsProseClass, "pb-8 flex-1 min-w-0")}>
<Outlet />
</section>
</main>
</div>
)
}

View File

@@ -1,63 +0,0 @@
import dotsBackgroundUrl from "~/assets/dots-background.svg"
import { AppFooter } from "~/modules/app/app-footer"
import { AppLogo } from "~/modules/app/app-logo"
import LandingCode from "~/modules/landing/landing-code.mdx"
import { MainNavigation } from "~/modules/navigation/main-navigation"
import { buttonClass, maxWidthContainer } from "~/modules/ui/components"
import { LandingAnimation } from "../modules/landing/landing-animation"
import { UncontrolledModal } from "../modules/ui/modal"
export default function Landing() {
return (
<>
<div
className="fixed inset-0 rotate-6 scale-125 opacity-20"
style={{ backgroundImage: `url(${dotsBackgroundUrl})` }}
/>
<div className="flex flex-col relative min-w-0 min-h-screen pb-4 gap-4">
<header className={maxWidthContainer}>
<MainNavigation />
</header>
<div className="flex flex-col gap-4 my-auto px-4">
<AppLogo className="w-full max-w-lg mx-auto" />
<div className="max-w-md w-full mx-auto">
<LandingAnimation />
</div>
<p className="text-center text-lg font-light -mb-1">
Create interactive Discord messages with React.
</p>
<div className="flex gap-4 self-center">
<a
href="/guides/getting-started"
className={buttonClass({ variant: "solid" })}
>
Get Started
</a>
<UncontrolledModal
button={(button) => (
<button
{...button}
className={buttonClass({ variant: "semiblack" })}
>
Show Code
</button>
)}
>
<div className="text-sm sm:text-base">
<LandingCode />
</div>
</UncontrolledModal>
</div>
</div>
<div className="text-center">
<AppFooter />
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,17 @@
import prefetch from "@astrojs/prefetch"
import react from "@astrojs/react"
import tailwind from "@astrojs/tailwind"
import { defineConfig } from "astro/config"
// https://astro.build/config
export default defineConfig({
integrations: [
tailwind({
config: {
applyBaseStyles: false,
},
}),
react(),
prefetch(),
],
})

View File

@@ -1,8 +0,0 @@
import { defineConfig } from "cypress"
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {},
baseUrl: "http://localhost:3000/",
},
})

View File

@@ -1,12 +0,0 @@
export {}
describe("main popover menu", () => {
it("should toggle on button click", () => {
cy.viewport(480, 720)
cy.visit("/")
cy.findByRole("button", { name: "Menu" }).click()
cy.findByRole("menu").should("be.visible")
cy.findByRole("button", { name: "Menu" }).click()
cy.findByRole("menu").should("not.exist")
})
})

View File

@@ -1 +0,0 @@
import "@testing-library/cypress/add-commands"

View File

@@ -1 +0,0 @@
import "./commands"

View File

@@ -1,8 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["cypress", "@testing-library/cypress"]
},
"include": ["."],
"exclude": []
}

View File

@@ -1,53 +1,42 @@
{ {
"type": "module",
"name": "website", "name": "website",
"version": "0.4.2", "version": "0.4.3",
"private": true, "private": true,
"sideEffects": false,
"scripts": { "scripts": {
"dev": "concurrently 'typedoc --watch' 'pnpm tailwind -- --watch' 'remix dev'", "dev": "run-p --race --print-label dev:*",
"dev:typedoc": "typedoc --watch",
"dev:astro": "astro dev",
"test": "node ./scripts/test.js", "test": "node ./scripts/test.js",
"test-dev": "pnpm dev & wait-on http-get://localhost:3000 && cypress open", "test-dev": "run-p --race --print-label dev:* test-dev:*",
"build": "typedoc && pnpm tailwind -- --minify && remix build", "test-dev:cypress": "wait-on http-get://localhost:3000 && cypress open",
"tailwind": "tailwindcss --config tailwind.config.cjs --input app/modules/ui/tailwind.css --output app/modules/ui/tailwind.out.css", "start": "astro preview",
"build": "typedoc && astro build",
"typecheck": "tsc --noEmit && tsc --project cypress/tsconfig.json --noEmit" "typecheck": "tsc --noEmit && tsc --project cypress/tsconfig.json --noEmit"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.6.6", "@astrojs/prefetch": "^0.2.0",
"@heroicons/react": "^1.0.6", "@astrojs/react": "^2.1.0",
"@reach/rect": "^0.17.0", "@fontsource/jetbrains-mono": "^4.5.12",
"@remix-run/node": "^1.6.5", "@fontsource/rubik": "^4.5.14",
"@remix-run/react": "^1.6.5", "@heroicons/react": "^2.0.16",
"@remix-run/vercel": "^1.7.2", "@tailwindcss/typography": "^0.5.9",
"@tailwindcss/typography": "^0.5.4", "astro": "^2.1.2",
"@vercel/node": "^2.5.21",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"fast-glob": "^3.2.11",
"gray-matter": "^4.0.3",
"reacord": "workspace:*", "reacord": "workspace:*",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0"
"react-focus-on": "^3.6.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"zod": "^3.17.10"
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^1.6.5", "@astrojs/tailwind": "^3.1.0",
"@remix-run/serve": "^1.6.5",
"@testing-library/cypress": "^8.0.3",
"@types/node": "*", "@types/node": "*",
"@types/react": "^18.0.15", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.11",
"@types/wait-on": "^5.3.1", "npm-run-all": "^4.1.5",
"autoprefixer": "^10.4.7", "tailwindcss": "^3.2.7",
"concurrently": "^7.3.0", "typedoc": "^0.23.26",
"cypress": "^10.3.1", "typescript": "^4.9.5",
"execa": "^6.1.0", "wait-on": "^7.0.1"
"postcss": "^8.4.14", }
"rehype-prism-plus": "^1.4.2",
"tailwindcss": "^3.1.6",
"typedoc": "^0.23.8",
"typescript": "^4.7.4",
"wait-on": "^6.0.1"
},
"sideEffects": false
} }

View File

@@ -1,20 +0,0 @@
/* eslint-disable unicorn/prefer-module */
/**
* @type {import('@remix-run/dev/config').AppConfig}
*/
module.exports = {
serverBuildTarget: "vercel",
server: process.env.NODE_ENV === "development" ? undefined : "./server.js",
devServerPort: 8002,
ignoredRouteFiles: ["**/.*"],
// appDirectory: "app",
// assetsBuildDirectory: "public/build",
// publicPath: "/build/",
// serverBuildDirectory: "build",
mdx: async () => {
const rehypePrism = await import("rehype-prism-plus")
return {
rehypePlugins: [rehypePrism.default],
}
},
}

View File

@@ -1,3 +0,0 @@
{
"type": "module"
}

View File

@@ -1,9 +0,0 @@
import cypress from "cypress"
import { execa } from "execa"
import waitOn from "wait-on"
await execa("pnpm", ["build"], { stdio: "inherit" })
const app = execa("pnpm", ["start"], { stdio: "inherit" })
await waitOn({ resources: ["http-get://localhost:3000"] })
await cypress.run()
app.kill()

View File

@@ -1,4 +0,0 @@
import * as build from "@remix-run/dev/server-build"
import { createRequestHandler } from "@remix-run/vercel"
export default createRequestHandler({ build, mode: process.env.NODE_ENV })

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 146 B

After

Width:  |  Height:  |  Size: 146 B

View File

Before

Width:  |  Height:  |  Size: 658 B

After

Width:  |  Height:  |  Size: 658 B

View File

@@ -0,0 +1,14 @@
---
import { HeartIcon } from "@heroicons/react/20/solid"
import ExternalLink from "./external-link.astro"
---
<footer class="container text-xs opacity-75">
<address class="not-italic">
&copy; {new Date().getFullYear()} itsMapleLeaf
</address>
<p>
Coded with <HeartIcon className="inline w-4 align-sub" /> using{" "}
<ExternalLink class="link" href="https://astro.build">Astro</ExternalLink>
</p>
</footer>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
---
export type Props = astroHTML.JSX.AnchorHTMLAttributes
---
<a rel="noopener noreferrer" target="_blank" {...Astro.props}>
<slot />
</a>

View File

@@ -0,0 +1,38 @@
---
import { getCollection } from "astro:content"
import Layout from "./layout.astro"
import MainNavigation from "./main-navigation.astro"
const guides = await getCollection("guides")
---
<Layout>
<div class="isolate">
<header
class="bg-slate-700/30 shadow sticky top-0 backdrop-blur-sm transition z-10 flex"
>
<div class="container">
<MainNavigation />
</div>
</header>
<main class="container mt-8 flex items-start gap-4">
<nav class="w-48 sticky top-24 hidden md:block">
<h2 class="text-2xl">Guides</h2>
<ul class="mt-3 flex flex-col gap-2 items-start">
{
guides.map((guide) => (
<li>
<a class="link" href={`/guides/${guide.slug}`}>
{guide.data.title}
</a>
</li>
))
}
</ul>
</nav>
<section class="prose prose-invert pb-8 flex-1 min-w-0">
<slot />
</section>
</main>
</div>
</Layout>

View File

@@ -186,7 +186,7 @@ export function LandingAnimation() {
<img <img
src={cursorUrl} src={cursorUrl}
alt="" alt=""
className="transition-all duration-500 absolute scale-75" className="transition-all duration-500 absolute scale-75 bg-transparent"
style={{ left: state.cursorLeft, bottom: state.cursorBottom }} style={{ left: state.cursorLeft, bottom: state.cursorBottom }}
ref={cursorRef} ref={cursorRef}
/> />

View File

@@ -0,0 +1,44 @@
---
import "@fontsource/jetbrains-mono/500.css"
import "@fontsource/rubik/variable.css"
import packageJson from "reacord/package.json"
import bannerUrl from "~/assets/banner.png"
import faviconUrl from "~/assets/favicon.png"
import "~/styles/prism-theme.css"
import "~/styles/tailwind.css"
---
<!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.0" />
<meta name="description" content={packageJson.description} />
<meta name="theme-color" content="#21754b" />
<meta property="og:url" content="https://reacord.mapleleaf.dev/" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Reacord" />
<meta
property="og:description"
content="Create interactive Discord messages using React"
/>
<meta property="og:image" content={bannerUrl} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:domain" content="reacord.mapleleaf.dev" />
<meta name="twitter:url" content="https://reacord.mapleleaf.dev/" />
<meta name="twitter:title" content="Reacord" />
<meta
name="twitter:description"
content="Create interactive Discord messages using React"
/>
<meta name="twitter:image" content={bannerUrl} />
<title>Reacord</title>
<link rel="icon" href={faviconUrl} />
</head>
<body>
<slot />
</body>
</html>

View File

@@ -0,0 +1,80 @@
---
import {
ArrowTopRightOnSquareIcon,
CodeBracketIcon,
DocumentTextIcon,
} from "@heroicons/react/20/solid"
import { Bars3Icon } from "@heroicons/react/24/outline"
import { getCollection } from "astro:content"
import AppLogo from "./app-logo.astro"
import ExternalLink from "./external-link.astro"
import MenuItem from "./menu-item.astro"
import Menu from "./menu.astro"
const links = [
{
href: "/guides/getting-started",
label: "Guides",
icon: DocumentTextIcon,
component: "a",
prefetch: true,
},
{
href: "/api/",
label: "API Reference",
icon: CodeBracketIcon,
component: "a",
},
{
href: "https://github.com/itsMapleLeaf/reacord",
label: "GitHub",
icon: ArrowTopRightOnSquareIcon,
component: ExternalLink,
},
]
const guides = await getCollection("guides")
---
<nav class="flex justify-between items-center h-16">
<a href="/">
<AppLogo class="w-32" />
<span class="sr-only">Home</span>
</a>
<div class="hidden md:flex gap-4">
{
links.map((link) => (
<link.component
href={link.href}
class="link inline-flex gap-1 items-center"
rel={link.prefetch ? "prefetch" : undefined}
>
<link.icon className="inline-icon" />
{link.label}
</link.component>
))
}
</div>
<Menu>
<Fragment slot="button">
<Bars3Icon className="w-6" />
<span class="sr-only">Menu</span>
</Fragment>
{
links.map((link) => (
<link.component href={link.href}>
<MenuItem icon={link.icon} label={link.label} />
</link.component>
))
}
<hr class="border-black/25" />
{
guides.map((guide) => (
<a href={`/guides/${guide.slug}`} rel="prefetch">
<MenuItem icon={DocumentTextIcon} label={guide.data.title} />
</a>
))
}
</Menu>
</nav>

View File

@@ -0,0 +1,13 @@
---
export type Props = {
icon: (props: { class?: string; className?: string }) => any
label: string
}
---
<div
class="px-3 py-2 transition text-left font-medium block w-full opacity-50 inline-flex gap-1 items-center hover:opacity-100 hover:text-emerald-500"
>
<Astro.props.icon class="inline-icon" className="inline-icon" />
<span class="flex-1">{Astro.props.label}</span>
</div>

View File

@@ -0,0 +1,30 @@
<details class="md:hidden relative" data-menu>
<summary
class="list-none p-2 -m-2 cursor-pointer hover:text-emerald-500 transition"
>
<slot name="button" />
</summary>
<div
class="w-48 max-h-[calc(100vh-5rem)] bg-slate-800 shadow rounded-lg overflow-x-hidden overflow-y-auto top-[calc(100%+8px)] right-0 absolute z-10"
>
<slot />
</div>
</details>
<script>
for (const menu of document.querySelectorAll<HTMLDetailsElement>(
"[data-menu]",
)) {
window.addEventListener("click", (event) => {
if (!menu.contains(event.target as Node)) {
menu.open = false
}
})
menu.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
menu.open = false
menu.querySelector("summary")!.focus()
}
})
}
</script>

View File

@@ -0,0 +1,17 @@
---
export type Props = astroHTML.JSX.AnchorHTMLAttributes & {
href: string
}
const removeTrailingSlash = (str: string) => str.replace(/\/$/, "")
const linkUrl = new URL(Astro.props.href, Astro.url)
const isActive =
removeTrailingSlash(Astro.url.pathname) ===
removeTrailingSlash(linkUrl.pathname)
---
<a {...Astro.props} data-active={isActive || undefined}>
<slot />
</a>

View File

@@ -0,0 +1,10 @@
import { defineCollection, z } from "astro:content"
export const collections = {
guides: defineCollection({
schema: z.object({
title: z.string(),
description: z.string(),
}),
}),
}

View File

@@ -1,8 +1,7 @@
--- ---
order: 0
meta:
title: Getting Started title: Getting Started
description: Learn how to get started with Reacord. description: Learn how to get started with Reacord.
slug: getting-started
--- ---
# Getting Started # Getting Started

View File

@@ -1,8 +1,7 @@
--- ---
order: 1
meta:
title: Sending Messages title: Sending Messages
description: Sending messages by creating Reacord instances description: Sending messages by creating Reacord instances
slug: sending-messages
--- ---
# Sending Messages with Instances # Sending Messages with Instances

View File

@@ -1,8 +1,7 @@
--- ---
order: 2
meta:
title: Embeds title: Embeds
description: Using embed components description: Using embed components
slug: embeds
--- ---
# Embeds # Embeds

View File

@@ -1,8 +1,7 @@
--- ---
order: 3
meta:
title: Buttons title: Buttons
description: Using button components description: Using button components
slug: buttons
--- ---
# Buttons # Buttons

View File

@@ -1,8 +1,7 @@
--- ---
order: 3
meta:
title: Links title: Links
description: Using link components description: Using link components
slug: links
--- ---
# Links # Links

View File

@@ -1,8 +1,7 @@
--- ---
order: 4
meta:
title: Select Menus title: Select Menus
description: Using select menu components description: Using select menu components
slug: select-menus
--- ---
# Select Menus # Select Menus

View File

@@ -1,8 +1,7 @@
--- ---
order: 5
meta:
title: useInstance title: useInstance
description: Using useInstance to get the current instance within a component description: Using useInstance to get the current instance within a component
slug: use-instance
--- ---
# useInstance # useInstance

View File

@@ -1,7 +1,7 @@
--- ---
meta:
title: Using Reacord with other libraries title: Using Reacord with other libraries
description: Adapting Reacord to another Discord library description: Adapting Reacord to another Discord library
slug: custom-adapters
--- ---
# Using Reacord with other libraries # Using Reacord with other libraries

2
packages/website/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View File

@@ -0,0 +1,63 @@
---
import { type GetStaticPaths } from "astro"
import { getCollection, type CollectionEntry } from "astro:content"
import AppFooter from "~/components/app-footer.astro"
import Layout from "~/components/layout.astro"
import MainNavigation from "~/components/main-navigation.astro"
import NavLink from "~/components/nav-link.astro"
export type Props = {
guide: CollectionEntry<"guides">
}
export const getStaticPaths: GetStaticPaths = async () => {
const guides = await getCollection("guides")
return guides.map((guide) => ({
params: { slug: guide.slug },
props: { guide },
}))
}
const guides = await getCollection("guides")
const { Content } = await Astro.props.guide.render()
---
<Layout>
<div class="isolate">
<header
class="bg-slate-700/30 shadow sticky top-0 backdrop-blur-sm transition z-10 flex"
>
<div class="container">
<MainNavigation />
</div>
</header>
<main class="container mt-8 flex items-start gap-4">
<nav class="w-48 sticky top-24 hidden md:block">
<h2 class="text-2xl">Guides</h2>
<ul class="mt-3 flex flex-col gap-2 items-start">
{
guides.map((guide) => (
<li>
<NavLink
class="link data-[active]:link-active"
href={`/guides/${guide.slug}`}
rel="prefetch"
>
{guide.data.title}
</NavLink>
</li>
))
}
</ul>
</nav>
<section
class="prose prose-invert 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-3 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 prose-code:before:hidden prose-code:after:hidden prose-code:text-slate-400 prose-li:mb-5 max-w-none pb-8 flex-1 min-w-0"
>
<Content />
</section>
</main>
<div class="py-2">
<AppFooter />
</div>
</div>
</Layout>

Some files were not shown because too many files have changed in this diff Show More