guide pages (why was that so easy wtf)
This commit is contained in:
2
packages/website/.gitignore
vendored
2
packages/website/.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
node_modules
|
||||
|
||||
/.cache
|
||||
/build
|
||||
/public/build
|
||||
@@ -9,3 +8,4 @@ cypress/videos
|
||||
cypress/screenshots
|
||||
*.out.css
|
||||
/api
|
||||
.astro
|
||||
|
||||
@@ -1,7 +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()],
|
||||
integrations: [
|
||||
tailwind({
|
||||
config: {
|
||||
applyBaseStyles: false,
|
||||
},
|
||||
}),
|
||||
react(),
|
||||
prefetch(),
|
||||
],
|
||||
})
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"typecheck": "tsc --noEmit && tsc --project cypress/tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/prefetch": "^0.2.0",
|
||||
"@astrojs/react": "^2.1.0",
|
||||
"@fontsource/jetbrains-mono": "^4.5.12",
|
||||
"@fontsource/rubik": "^4.5.14",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
export type Props = astroHTML.JSX.AnchorHTMLProps
|
||||
export type Props = astroHTML.JSX.AnchorHTMLAttributes
|
||||
---
|
||||
|
||||
<a rel="noopener noreferrer" target="_blank" {...Astro.props}>
|
||||
|
||||
38
packages/website/src/components/guide-layout.astro
Normal file
38
packages/website/src/components/guide-layout.astro
Normal 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>
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
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"
|
||||
@@ -16,6 +17,7 @@ const links = [
|
||||
label: "Guides",
|
||||
icon: DocumentTextIcon,
|
||||
component: "a",
|
||||
prefetch: true,
|
||||
},
|
||||
{
|
||||
href: "/api/",
|
||||
@@ -30,6 +32,8 @@ const links = [
|
||||
component: ExternalLink,
|
||||
},
|
||||
]
|
||||
|
||||
const guides = await getCollection("guides")
|
||||
---
|
||||
|
||||
<nav class="flex justify-between items-center h-16">
|
||||
@@ -43,6 +47,7 @@ const links = [
|
||||
<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}
|
||||
@@ -64,6 +69,12 @@ const links = [
|
||||
))
|
||||
}
|
||||
<hr class="border-black/25" />
|
||||
<!-- TODO: guide links -->
|
||||
{
|
||||
guides.map((guide) => (
|
||||
<a href={`/guides/${guide.slug}`} rel="prefetch">
|
||||
<MenuItem icon={DocumentTextIcon} label={guide.data.title} />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</Menu>
|
||||
</nav>
|
||||
|
||||
@@ -9,5 +9,5 @@ export type Props = {
|
||||
class="px-3 py-2 transition text-left font-medium block w-full opacity-50 inline-flex gap-1 items-center"
|
||||
>
|
||||
<Astro.props.icon class="inline-icon" className="inline-icon" />
|
||||
{Astro.props.label}
|
||||
<span class="flex-1">{Astro.props.label}</span>
|
||||
</div>
|
||||
|
||||
10
packages/website/src/content/config.ts
Normal file
10
packages/website/src/content/config.ts
Normal 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(),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
52
packages/website/src/content/guides/0-getting-started.md
Normal file
52
packages/website/src/content/guides/0-getting-started.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Learn how to get started with Reacord.
|
||||
slug: getting-started
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
These guides assume 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.
|
||||
|
||||
## Setup from template
|
||||
|
||||
[Use this starter template](https://github.com/itsMapleLeaf/reacord-starter) to get off the ground quickly.
|
||||
|
||||
## Adding to an existing project
|
||||
|
||||
Install Reacord and dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install reacord react discord.js
|
||||
|
||||
# yarn
|
||||
yarn add reacord react discord.js
|
||||
|
||||
# pnpm
|
||||
pnpm add reacord react discord.js
|
||||
```
|
||||
|
||||
Create a Discord.js client and a Reacord instance:
|
||||
|
||||
```js
|
||||
// main.jsx
|
||||
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)
|
||||
```
|
||||
|
||||
To use JSX in your code, run it with [tsx](https://npm.im/tsx):
|
||||
|
||||
```bash
|
||||
npm install tsx
|
||||
tsx main.tsx
|
||||
```
|
||||
166
packages/website/src/content/guides/1-sending-messages.md
Normal file
166
packages/website/src/content/guides/1-sending-messages.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Sending Messages
|
||||
description: Sending messages by creating Reacord instances
|
||||
slug: sending-messages
|
||||
---
|
||||
|
||||
# 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(() => {
|
||||
setCurrentTime(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>
|
||||
This section also applies to other kinds of application commands, such as context menu commands.
|
||||
</aside>
|
||||
|
||||
To reply to a command interaction, use the `.reply()` function. This function returns an instance that works the same way as the one from `.send()`. Here's an example:
|
||||
|
||||
```jsx
|
||||
import { Client } from "discord.js"
|
||||
import * as React from "react"
|
||||
import { Button, ReacordDiscordJs } from "reacord"
|
||||
|
||||
const client = new Client({ intents: [] })
|
||||
const reacord = new ReacordDiscordJs(client)
|
||||
|
||||
client.on("ready", () => {
|
||||
client.application?.commands.create({
|
||||
name: "ping",
|
||||
description: "pong!",
|
||||
})
|
||||
})
|
||||
|
||||
client.on("interactionCreate", (interaction) => {
|
||||
if (interaction.isCommand() && interaction.commandName === "ping") {
|
||||
// Use the reply() function instead of send
|
||||
reacord.reply(interaction, <>pong!</>)
|
||||
}
|
||||
})
|
||||
|
||||
client.login(process.env.DISCORD_TOKEN)
|
||||
```
|
||||
|
||||
<aside>
|
||||
This example uses <a href="https://discord.com/developers/docs/interactions/application-commands#registering-a-command">global commands</a>, so the command might take a while to show up 😅
|
||||
</aside>
|
||||
|
||||
However, the process of creating commands can get really repetitive and error-prone. A command framework could help with this, or you could make a small helper:
|
||||
|
||||
```jsx
|
||||
function handleCommands(client, commands) {
|
||||
client.on("ready", () => {
|
||||
for (const { name, description } of commands) {
|
||||
client.application?.commands.create({ name, description })
|
||||
}
|
||||
})
|
||||
|
||||
client.on("interactionCreate", (interaction) => {
|
||||
if (interaction.isCommand()) {
|
||||
for (const command of commands) {
|
||||
if (interaction.commandName === command.name) {
|
||||
command.run(interaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
handleCommands(client, [
|
||||
{
|
||||
name: "ping",
|
||||
description: "pong!",
|
||||
run: (interaction) => {
|
||||
reacord.reply(interaction, <>pong!</>)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hi",
|
||||
description: "say hi",
|
||||
run: (interaction) => {
|
||||
reacord.reply(interaction, <>hi</>)
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
## Ephemeral Command Replies
|
||||
|
||||
Ephemeral replies are replies that only appear for one user. To create them, use the `.ephemeralReply()` function.
|
||||
|
||||
```tsx
|
||||
handleCommands(client, [
|
||||
{
|
||||
name: "pong",
|
||||
description: "pong, but in secret",
|
||||
run: (interaction) => {
|
||||
reacord.ephemeralReply(interaction, <>(pong)</>)
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
The `ephemeralReply` function also returns an instance, but ephemeral replies cannot be updated via `instance.render()`. You can `.deactivate()` them, but `.destroy()` will not delete the message; only the user can hide it from view.
|
||||
63
packages/website/src/content/guides/2-embeds.md
Normal file
63
packages/website/src/content/guides/2-embeds.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Embeds
|
||||
description: Using embed components
|
||||
slug: embeds
|
||||
---
|
||||
|
||||
# Embeds
|
||||
|
||||
Reacord comes with an `<Embed />` component for sending rich embeds.
|
||||
|
||||
```jsx
|
||||
import { Embed } from "reacord"
|
||||
|
||||
function FancyMessage({ title, description }) {
|
||||
return (
|
||||
<Embed
|
||||
title={title}
|
||||
description={description}
|
||||
color={0x00ff00}
|
||||
timestamp={Date.now()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
reacord.send(channelId, <FancyMessage title="Hello" description="World" />)
|
||||
```
|
||||
|
||||
Reacord also comes with multiple embed components, for defining embeds on a piece-by-piece basis. This enables composition:
|
||||
|
||||
```jsx
|
||||
import { Embed, EmbedTitle } from "reacord"
|
||||
|
||||
function FancyDetails({ title, description }) {
|
||||
return (
|
||||
<>
|
||||
<EmbedTitle>{title}</EmbedTitle>
|
||||
{/* embed descriptions are just text */}
|
||||
{description}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FancyMessage({ children }) {
|
||||
return (
|
||||
<Embed color={0x00ff00} timestamp={Date.now()}>
|
||||
{children}
|
||||
</Embed>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
reacord.send(
|
||||
channelId,
|
||||
<FancyMessage>
|
||||
<FancyDetails title="Hello" description="World" />
|
||||
</FancyMessage>,
|
||||
)
|
||||
```
|
||||
|
||||
See the [API Reference](/api/index.html#EmbedAuthorProps) for the full list of embed components.
|
||||
46
packages/website/src/content/guides/3-buttons.md
Normal file
46
packages/website/src/content/guides/3-buttons.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Buttons
|
||||
description: Using button components
|
||||
slug: buttons
|
||||
---
|
||||
|
||||
# Buttons
|
||||
|
||||
Use the `<Button />` component to create a message with a button, and use the `onClick` callback to respond to button clicks.
|
||||
|
||||
```jsx
|
||||
import { Button } from "reacord"
|
||||
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<>
|
||||
You've clicked the button {count} times.
|
||||
<Button label="+1" onClick={() => setCount(count + 1)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `onClick` callback receives an `event` object. It includes some information, such as the user who clicked the button, and functions for creating new replies in response. These functions return message instances.
|
||||
|
||||
```jsx
|
||||
import { Button } from "reacord"
|
||||
|
||||
function TheButton() {
|
||||
function handleClick(event) {
|
||||
const name = event.guild.member.displayName || event.user.username
|
||||
|
||||
const publicReply = event.reply(`${name} clicked the button. wow`)
|
||||
setTimeout(() => publicReply.destroy(), 3000)
|
||||
|
||||
const privateReply = event.ephemeralReply("good job, you clicked it")
|
||||
privateReply.deactivate() // we don't need to listen to updates on this
|
||||
}
|
||||
|
||||
return <Button label="click me i dare you" onClick={handleClick} />
|
||||
}
|
||||
```
|
||||
|
||||
See the [API reference](/api/index.html#ButtonProps) for more information.
|
||||
24
packages/website/src/content/guides/4-links.md
Normal file
24
packages/website/src/content/guides/4-links.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Links
|
||||
description: Using link components
|
||||
slug: links
|
||||
---
|
||||
|
||||
# Links
|
||||
|
||||
In Discord, links are a type of button, and they work similarly. Clicking on it leads you to the given URL. They only have one style, and can't be listened to for clicks.
|
||||
|
||||
```jsx
|
||||
import { Link } from "reacord"
|
||||
|
||||
function AwesomeLinks() {
|
||||
return (
|
||||
<>
|
||||
<Link label="look at this" url="https://google.com" />
|
||||
<Link label="wow" url="https://youtube.com/watch?v=dQw4w9WgXcQ" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
See the [API reference](/api/index.html#Link) for more information.
|
||||
70
packages/website/src/content/guides/5-select-menu.md
Normal file
70
packages/website/src/content/guides/5-select-menu.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: Select Menus
|
||||
description: Using select menu components
|
||||
slug: select-menus
|
||||
---
|
||||
|
||||
# Select Menus
|
||||
|
||||
To create a select menu, use the `Select` component, and pass a list of `Option` components as children. Use the `value` prop to set a currently selected value. You can respond to changes in the value via `onChangeValue`.
|
||||
|
||||
```jsx
|
||||
export function FruitSelect({ onConfirm }) {
|
||||
const [value, setValue] = useState()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
placeholder="choose a fruit"
|
||||
value={value}
|
||||
onChangeValue={setValue}
|
||||
>
|
||||
<Option value="🍎" />
|
||||
<Option value="🍌" />
|
||||
<Option value="🍒" />
|
||||
</Select>
|
||||
<Button
|
||||
label="confirm"
|
||||
disabled={value == undefined}
|
||||
onClick={() => {
|
||||
if (value) onConfirm(value)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
const instance = reacord.send(
|
||||
channelId,
|
||||
<FruitSelect
|
||||
onConfirm={(value) => {
|
||||
instance.render(`you chose ${value}`)
|
||||
instance.deactivate()
|
||||
}}
|
||||
/>,
|
||||
)
|
||||
```
|
||||
|
||||
For a multi-select, use the `multiple` prop, then you can use `values` and `onChangeMultiple` to handle multiple values.
|
||||
|
||||
```tsx
|
||||
export function FruitSelect({ onConfirm }) {
|
||||
const [values, setValues] = useState([])
|
||||
|
||||
return (
|
||||
<Select
|
||||
placeholder="choose a fruit"
|
||||
values={values}
|
||||
onChangeMultiple={setValues}
|
||||
>
|
||||
<Option value="🍎" />
|
||||
<Option value="🍌" />
|
||||
<Option value="🍒" />
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The Select component accepts a variety of props beyond what's shown here. See the [API reference](/api/index.html#SelectChangeEvent) for more information.
|
||||
26
packages/website/src/content/guides/6-use-instance.md
Normal file
26
packages/website/src/content/guides/6-use-instance.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: useInstance
|
||||
description: Using useInstance to get the current instance within a component
|
||||
slug: use-instance
|
||||
---
|
||||
|
||||
# useInstance
|
||||
|
||||
You can use `useInstance` to get the current [instance](/guides/sending-messages) within a component. This can be used to let a component destroy or deactivate itself.
|
||||
|
||||
```jsx
|
||||
import { Button, useInstance } from "reacord"
|
||||
|
||||
function SelfDestruct() {
|
||||
const instance = useInstance()
|
||||
return (
|
||||
<Button
|
||||
style="danger"
|
||||
label="delete this"
|
||||
onClick={() => instance.destroy()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
reacord.send(channelId, <SelfDestruct />)
|
||||
```
|
||||
11
packages/website/src/content/guides/custom-adapters.md
Normal file
11
packages/website/src/content/guides/custom-adapters.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Using Reacord with other libraries
|
||||
description: Adapting Reacord to another Discord library
|
||||
slug: custom-adapters
|
||||
---
|
||||
|
||||
# Using Reacord with other libraries
|
||||
|
||||
Reacord's core is built to be library agnostic, and can be adapted to libraries other than Discord.js. However, Discord.js is the only built-in adapter at the moment, and the adapter API is still a work in progress.
|
||||
|
||||
If you're interested in creating a custom adapter, [see the code for the Discord.js adapter as an example](https://github.com/itsMapleLeaf/reacord/blob/main/packages/reacord/library/core/reacord-discord-js.ts). Feel free to [create an issue on GitHub](https://github.com/itsMapleLeaf/reacord/issues/new) if you run into issues.
|
||||
1
packages/website/src/env.d.ts
vendored
1
packages/website/src/env.d.ts
vendored
@@ -1 +1,2 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
58
packages/website/src/pages/guides/[slug].astro
Normal file
58
packages/website/src/pages/guides/[slug].astro
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
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"
|
||||
|
||||
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>
|
||||
<a class="link" href={`/guides/${guide.slug}`} rel="prefetch">
|
||||
{guide.data.title}
|
||||
</a>
|
||||
</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>
|
||||
@@ -3,8 +3,8 @@ import dotsBackgroundUrl from "~/assets/dots-background.svg"
|
||||
import AppFooter from "~/components/app-footer.astro"
|
||||
import AppLogo from "~/components/app-logo.astro"
|
||||
import { LandingAnimation } from "~/components/landing-animation"
|
||||
import Layout from "~/components/layout.astro"
|
||||
import MainNavigation from "~/components/main-navigation.astro"
|
||||
import Layout from "~/layout.astro"
|
||||
---
|
||||
|
||||
<Layout>
|
||||
|
||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -86,6 +86,7 @@ importers:
|
||||
|
||||
packages/website:
|
||||
specifiers:
|
||||
'@astrojs/prefetch': ^0.2.0
|
||||
'@astrojs/react': ^2.1.0
|
||||
'@astrojs/tailwind': ^3.1.0
|
||||
'@fontsource/jetbrains-mono': ^4.5.12
|
||||
@@ -127,6 +128,7 @@ importers:
|
||||
wait-on: ^6.0.1
|
||||
zod: ^3.17.10
|
||||
dependencies:
|
||||
'@astrojs/prefetch': 0.2.0
|
||||
'@astrojs/react': 2.1.0_v7vsrdtui4d36prbwvdnlb7tpq
|
||||
'@fontsource/jetbrains-mono': 4.5.12
|
||||
'@fontsource/rubik': 4.5.14
|
||||
@@ -224,6 +226,12 @@ packages:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/@astrojs/prefetch/0.2.0:
|
||||
resolution: {integrity: sha512-q68916tIId0tnRFphNL2OcORgoATnfVItaQAugz7wfIq4ghivNISG1scN86uAyF/md1N1YcKu6XPukT5f2lShQ==}
|
||||
dependencies:
|
||||
throttles: 1.0.1
|
||||
dev: false
|
||||
|
||||
/@astrojs/prism/2.1.0:
|
||||
resolution: {integrity: sha512-+II6nfIFGZ7iH0FunhRGcj/J1mCxjcHl85cZRuFePKLoIhFHJT3nC3myQnUw386hUaIn2W20McxxtAVf4leeRQ==}
|
||||
engines: {node: '>=16.12.0'}
|
||||
@@ -10660,6 +10668,11 @@ packages:
|
||||
resolution: {integrity: sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==}
|
||||
dev: true
|
||||
|
||||
/throttles/1.0.1:
|
||||
resolution: {integrity: sha512-fab7Xg+zELr9KOv4fkaBoe/b3L0GMGLd0IBSCn16GoE/Qx6/OfCr1eGNyEcDU2pUA79qQfZ8kPQWlRuok4YwTw==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/through/2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: true
|
||||
|
||||
Reference in New Issue
Block a user