This commit is contained in:
commit
4ea7794fdc
6
.eslintrc.json
Normal file
6
.eslintrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"next/typescript"
|
||||
]
|
||||
}
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.lockb binary diff=lockb
|
31
.gitea/workflows/deploy.yml
Normal file
31
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,31 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- LICENSE
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ "ubuntu-latest" ]
|
||||
git-version: [ "2.44.0" ]
|
||||
runs-on: ${{ matrix.arch }}
|
||||
|
||||
# Steps to run
|
||||
steps:
|
||||
# Checkout the repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Deploy to Dokku
|
||||
- name: Deploy to Dokku
|
||||
uses: dokku/github-action@master
|
||||
with:
|
||||
git_remote_url: "ssh://dokku@10.10.70.73:22/docs"
|
||||
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
|
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
node_modules
|
||||
.idea/
|
||||
.vscode/
|
||||
.VSCodeCounter/
|
||||
.next/
|
||||
.env*.local
|
||||
next-env.d.ts
|
||||
.sentryclirc
|
||||
.env
|
||||
sw.*
|
||||
workbox-*
|
||||
swe-worker-*
|
||||
dist/
|
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4
|
||||
}
|
45
Dockerfile
Normal file
45
Dockerfile
Normal file
@ -0,0 +1,45 @@
|
||||
FROM imbios/bun-node AS base
|
||||
|
||||
|
||||
# Install dependencies
|
||||
FROM base AS depends
|
||||
WORKDIR /usr/src/app
|
||||
COPY package.json* bun.lockb* ./
|
||||
RUN bun install --frozen-lockfile --quiet
|
||||
|
||||
|
||||
# Build the app
|
||||
FROM base AS builder
|
||||
WORKDIR /usr/src/app
|
||||
COPY --from=depends /usr/src/app/node_modules ./node_modules
|
||||
COPY . .
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
RUN bun run build
|
||||
|
||||
|
||||
# Run the app
|
||||
FROM base AS runner
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN addgroup --system --gid 1007 nextjs
|
||||
RUN adduser --system --uid 1007 nextjs
|
||||
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nextjs .next
|
||||
|
||||
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/.next ./.next
|
||||
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/next.config.mjs ./next.config.mjs
|
||||
COPY --from=builder --chown=nextjs:nextjs /usr/src/app/package.json ./package.json
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Exposting on port 80 so we can
|
||||
# access via a reverse proxy for Dokku
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
EXPOSE 80
|
||||
ENV PORT 80
|
||||
|
||||
USER nextjs
|
||||
CMD node server.js
|
20
components.json
Normal file
20
components.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
7
docs/bob/hello.md
Normal file
7
docs/bob/hello.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 'Hello'
|
||||
published: '10-06-2024'
|
||||
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
|
||||
---
|
||||
|
||||
# hello
|
7
docs/bob/hey.md
Normal file
7
docs/bob/hey.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 'Hey'
|
||||
published: '10-06-2024'
|
||||
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
|
||||
---
|
||||
|
||||
# hey
|
7
docs/bob/hi.md
Normal file
7
docs/bob/hi.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 'Hi'
|
||||
published: '10-06-2024'
|
||||
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
|
||||
---
|
||||
|
||||
# hi
|
8
docs/home.md
Normal file
8
docs/home.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: 'Home'
|
||||
published: '10-06-2024'
|
||||
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
|
||||
---
|
||||
|
||||
# Get started with Pulse App!
|
||||
petentium usu tota noluisse errem elaboraret auctor.
|
14
next.config.mjs
Normal file
14
next.config.mjs
Normal file
@ -0,0 +1,14 @@
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "cdn.pulseapp.cc",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
44
package.json
Normal file
44
package.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Braydon (Rainnny)",
|
||||
"url": "https://rainnny.club",
|
||||
"email": "braydonrainnny@gmail.com"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbo",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-collapsible": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.11.1",
|
||||
"lucide-react": "^0.447.0",
|
||||
"next": "^15.0.0-canary.179",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^19.0.0-rc-1460d67c-20241003",
|
||||
"react-dom": "^19.0.0-rc-1460d67c-20241003",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remote-mdx": "^0.0.8",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.8"
|
||||
}
|
||||
}
|
8
postcss.config.mjs
Normal file
8
postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import("postcss-load-config").Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 165 KiB |
4
public/media/discord.svg
Normal file
4
public/media/discord.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36">
|
||||
<path fill="#fff"
|
||||
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 777 B |
5
public/media/github.svg
Normal file
5
public/media/github.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="#fff"/>
|
||||
</svg>
|
After Width: | Height: | Size: 986 B |
BIN
public/media/logo.png
Normal file
BIN
public/media/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
3
renovate.json
Normal file
3
renovate.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
35
src/app/[[...slug]]/page.tsx
Normal file
35
src/app/[[...slug]]/page.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { ReactElement } from "react";
|
||||
import { getDocsContent } from "@/lib/mdx";
|
||||
import { notFound } from "next/navigation";
|
||||
import { CustomMDX } from "@/components/mdx";
|
||||
|
||||
/**
|
||||
* The page to render the documentation markdown content.
|
||||
*
|
||||
* @param params the url params
|
||||
*/
|
||||
const DocsPage = async ({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string[] }>;
|
||||
}): Promise<ReactElement> => {
|
||||
const slug: string = (((await params).slug as string[]) || undefined)?.join(
|
||||
"/"
|
||||
);
|
||||
|
||||
// Get the content to display based on the provided slug
|
||||
const content: DocsContentMetadata | undefined = getDocsContent().find(
|
||||
(metadata: DocsContentMetadata): boolean =>
|
||||
metadata.slug === (slug || "home")
|
||||
);
|
||||
if (!content) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<main>
|
||||
<CustomMDX source={content.content} />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
export default DocsPage;
|
63
src/app/layout.tsx
Normal file
63
src/app/layout.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import "./styles/globals.css";
|
||||
import { ReactElement, ReactNode } from "react";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import Navbar from "@/components/navbar";
|
||||
import Sidebar from "@/components/sidebar/sidebar";
|
||||
|
||||
/**
|
||||
* The metadata for this app.
|
||||
*/
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "Pulse App Docs",
|
||||
template: "%s • Pulse App Docs",
|
||||
},
|
||||
description:
|
||||
"A lightweight service monitoring solution for tracking the availability of whatever service your heart desires!",
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: "https://pulseapp.cc/media/logo.png",
|
||||
width: 128,
|
||||
height: 128,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
},
|
||||
};
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#A855F7",
|
||||
};
|
||||
|
||||
/**
|
||||
* The primary layout for this app.
|
||||
*/
|
||||
const RootLayout = ({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: ReactNode;
|
||||
}>): ReactElement => (
|
||||
<html lang="en">
|
||||
<body
|
||||
className="scroll-smooth antialiased"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(to top, hsl(240, 6%, 10%), hsl(var(--background)))",
|
||||
}}
|
||||
>
|
||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
|
||||
<div className="px-10 max-w-[90rem] mx-auto min-h-screen flex flex-col">
|
||||
<Navbar />
|
||||
<div className="w-full h-full flex flex-grow gap-5">
|
||||
<Sidebar />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
export default RootLayout;
|
80
src/app/styles/globals.css
Normal file
80
src/app/styles/globals.css
Normal file
@ -0,0 +1,80 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 271 91% 65%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 272 72% 47%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
46
src/app/types/mdx.ts
Normal file
46
src/app/types/mdx.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Metadata for documentation content.
|
||||
*/
|
||||
type DocsContentMetadata = MDXMetadata & {
|
||||
/**
|
||||
* The title of this content.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* The date this content was published.
|
||||
*/
|
||||
published: string;
|
||||
|
||||
/**
|
||||
* The summary of this content.
|
||||
*/
|
||||
summary: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Metadata for an MDX file.
|
||||
*/
|
||||
type MDXMetadata = {
|
||||
/**
|
||||
* The slug of the file, defined once read.
|
||||
*/
|
||||
slug?: string | undefined;
|
||||
|
||||
/**
|
||||
* The extension of the file, defined once read.
|
||||
*/
|
||||
extension?: string | undefined;
|
||||
|
||||
/**
|
||||
* The metadata of the file.
|
||||
*/
|
||||
metadata: {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The content of the file.
|
||||
*/
|
||||
content: string;
|
||||
};
|
103
src/components/mdx.tsx
Normal file
103
src/components/mdx.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { ReactElement, ReactNode } from "react";
|
||||
import { MDXRemote } from "remote-mdx/rsc";
|
||||
import { cn } from "@/lib/utils";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
/**
|
||||
* The MDX components to style.
|
||||
*/
|
||||
const components = {
|
||||
h1: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={1} className="text-4xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h2: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={2} className="text-3xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h3: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={3} className="text-2xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h4: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={4} className="text-xl">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h5: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={5} className="text-lg">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h6: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<Heading size={5} className="text-md">
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
a: ({
|
||||
href,
|
||||
children,
|
||||
}: {
|
||||
href: string;
|
||||
children: ReactNode;
|
||||
}): ReactElement => (
|
||||
<a
|
||||
className="text-minecraft-green-4 cursor-pointer hover:opacity-85 transition-all transform-gpu"
|
||||
href={href}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
p: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<p className="leading-4 text-zinc-300/80">{children}</p>
|
||||
),
|
||||
ul: ({ children }: { children: ReactNode }): ReactElement => (
|
||||
<ul className="px-3 list-disc list-inside">{children}</ul>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
* The custom render for MDX.
|
||||
*
|
||||
* @param props the props for the MDX
|
||||
* @return the custom mdx
|
||||
*/
|
||||
export const CustomMDX = (props): ReactElement => (
|
||||
<MDXRemote
|
||||
{...props}
|
||||
components={{
|
||||
...components,
|
||||
...(props.components || {}),
|
||||
}}
|
||||
options={{
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
/**
|
||||
* A heading component.
|
||||
*
|
||||
* @param className the class name of the heading
|
||||
* @param size the size of the heading
|
||||
* @param children the children within the heading
|
||||
* @return the heading jsx
|
||||
*/
|
||||
const Heading = ({
|
||||
className,
|
||||
size,
|
||||
children,
|
||||
}: {
|
||||
className: string;
|
||||
size: number;
|
||||
children: ReactNode;
|
||||
}): ReactElement => (
|
||||
<h1 className={cn("pt-2.5 font-bold", size >= 2 && "pt-7", className)}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
71
src/components/navbar.tsx
Normal file
71
src/components/navbar.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { ReactElement } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
const Navbar = (): ReactElement => (
|
||||
<nav
|
||||
className={cn(
|
||||
"py-4 flex justify-between items-center",
|
||||
"after:absolute after:inset-x-0 after:top-[4.2rem] after:h-0.5 after:bg-muted/55"
|
||||
)}
|
||||
>
|
||||
{/* Branding */}
|
||||
<Link
|
||||
className="flex gap-1 items-end hover:opacity-75 transition-all transform-gpu select-none"
|
||||
href="/"
|
||||
draggable={false}
|
||||
>
|
||||
<h1 className="text-lg font-semibold">docs.</h1>
|
||||
<Image
|
||||
src="/media/logo.png"
|
||||
alt="Pulse App Logo"
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Right */}
|
||||
<div className="flex gap-7 items-center">
|
||||
{/* Search */}
|
||||
<Input
|
||||
className="hidden xs:flex rounded-lg"
|
||||
placeholder="Search the docs..."
|
||||
disabled
|
||||
/>
|
||||
|
||||
{/* Social */}
|
||||
<div className="flex gap-5 items-center">
|
||||
<SocialLink
|
||||
name="GitHub"
|
||||
link="https://github.com/PulseAppCC"
|
||||
icon="/media/github.svg"
|
||||
/>
|
||||
<SocialLink
|
||||
name="Discord"
|
||||
link="https://discord.pulseapp.cc"
|
||||
icon="/media/discord.svg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
|
||||
const SocialLink = ({
|
||||
name,
|
||||
link,
|
||||
icon,
|
||||
}: {
|
||||
name: string;
|
||||
link: string;
|
||||
icon: string;
|
||||
}): ReactElement => (
|
||||
<div className="relative w-6 h-6 hover:opacity-75 transition-all transform-gpu select-none">
|
||||
<Link href={link} target="_blank" draggable={false}>
|
||||
<Image src={icon} alt={`${name} Logo`} fill draggable={false} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Navbar;
|
161
src/components/sidebar/sidebar-links.tsx
Normal file
161
src/components/sidebar/sidebar-links.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
"use client";
|
||||
|
||||
import { ReactElement, useMemo, useState } from "react";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
const SidebarLinks = ({
|
||||
pages,
|
||||
}: {
|
||||
pages: DocsContentMetadata[];
|
||||
}): ReactElement => {
|
||||
const tree = useMemo(() => buildTree(pages), [pages]);
|
||||
return (
|
||||
<>
|
||||
{Object.values(tree).map((node: TreeNode) => (
|
||||
<CategoryItem key={node.slug} node={node} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type TreeNode = {
|
||||
title: string;
|
||||
slug: string;
|
||||
isFolder: boolean;
|
||||
children: Record<string, TreeNode>;
|
||||
};
|
||||
|
||||
const CategoryItem = ({
|
||||
node,
|
||||
depth = 0,
|
||||
isLast = true,
|
||||
}: {
|
||||
node: TreeNode;
|
||||
depth?: number;
|
||||
isLast?: boolean;
|
||||
}) => {
|
||||
const path = usePathname();
|
||||
const active =
|
||||
(path === "/" && node.slug === "home") || path === `/${node.slug}`;
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const hasChildren = Object.keys(node.children).length > 0;
|
||||
|
||||
return (
|
||||
<div className={`relative ${depth > 0 ? "ml-4" : ""}`}>
|
||||
{/* Indentation */}
|
||||
{depth > 0 && (
|
||||
<div
|
||||
className={`absolute left-0 top-1 bottom-0 border-l-2 border-muted`}
|
||||
style={{
|
||||
height: isLast ? "30px" : "100%",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
||||
{/* Trigger */}
|
||||
<CollapsibleTrigger asChild>
|
||||
<Link
|
||||
href={node.isFolder ? "#" : `/${node.slug}`}
|
||||
draggable={false}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
`relative ${depth > 0 ? "pl-4" : ""} w-full justify-between`,
|
||||
active &&
|
||||
"bg-primary/15 hover:bg-primary/20 text-primary/95 hover:text-primary"
|
||||
)}
|
||||
>
|
||||
{node.title}
|
||||
{hasChildren && (
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ rotate: isOpen ? 180 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</motion.div>
|
||||
)}
|
||||
</Button>
|
||||
</Link>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
{/* Content */}
|
||||
<AnimatePresence initial={false}>
|
||||
{hasChildren && isOpen && (
|
||||
<CollapsibleContent forceMount>
|
||||
<motion.div
|
||||
className="relative overflow-hidden"
|
||||
initial="collapsed"
|
||||
animate="open"
|
||||
exit="collapsed"
|
||||
variants={{
|
||||
open: { opacity: 1, height: "auto", y: 0 },
|
||||
collapsed: {
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
y: -20,
|
||||
},
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
ease: [0.04, 0.62, 0.23, 0.98],
|
||||
}}
|
||||
>
|
||||
{Object.values(node.children).map(
|
||||
(child, index, array) => (
|
||||
<CategoryItem
|
||||
key={child.slug}
|
||||
node={child}
|
||||
depth={depth + 1}
|
||||
isLast={index === array.length - 1}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</motion.div>
|
||||
</CollapsibleContent>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const buildTree = (pages: DocsContentMetadata[]): Record<string, TreeNode> => {
|
||||
const tree: Record<string, TreeNode> = {};
|
||||
|
||||
pages.forEach((page) => {
|
||||
const parts: string[] | undefined = page.slug?.split("/");
|
||||
let currentLevel = tree;
|
||||
|
||||
parts?.forEach((part: string, index: number) => {
|
||||
if (!currentLevel[part]) {
|
||||
currentLevel[part] = {
|
||||
title: part,
|
||||
slug: parts.slice(0, index + 1).join("/"),
|
||||
isFolder: index < parts.length - 1,
|
||||
children: {},
|
||||
};
|
||||
}
|
||||
if (index === parts.length - 1) {
|
||||
currentLevel[part].title = page.title;
|
||||
currentLevel[part].isFolder = false;
|
||||
}
|
||||
currentLevel = currentLevel[part].children;
|
||||
});
|
||||
});
|
||||
return tree;
|
||||
};
|
||||
|
||||
export default SidebarLinks;
|
24
src/components/sidebar/sidebar.tsx
Normal file
24
src/components/sidebar/sidebar.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { ReactElement } from "react";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { getDocsContent } from "@/lib/mdx";
|
||||
import SidebarLinks from "@/components/sidebar/sidebar-links";
|
||||
|
||||
const Sidebar = (): ReactElement => {
|
||||
const pages: DocsContentMetadata[] = getDocsContent();
|
||||
return (
|
||||
<div className="w-52 py-3 flex flex-col justify-between">
|
||||
{/* Links */}
|
||||
<div className="flex flex-col">
|
||||
<SidebarLinks pages={pages} />
|
||||
</div>
|
||||
|
||||
{/* Theme Switcher */}
|
||||
<div className="flex flex-col">
|
||||
<Separator className="mb-3" />
|
||||
<span>Theme Switcher</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
9
src/components/theme-provider.tsx
Normal file
9
src/components/theme-provider.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
|
||||
export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
};
|
57
src/components/ui/button.tsx
Normal file
57
src/components/ui/button.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
11
src/components/ui/collapsible.tsx
Normal file
11
src/components/ui/collapsible.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||
|
||||
const Collapsible = CollapsiblePrimitive.Root
|
||||
|
||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
25
src/components/ui/input.tsx
Normal file
25
src/components/ui/input.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
31
src/components/ui/separator.tsx
Normal file
31
src/components/ui/separator.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
)
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
106
src/lib/mdx.ts
Normal file
106
src/lib/mdx.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import * as fs from "node:fs";
|
||||
import { Stats } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* The regex to match for metadata.
|
||||
*/
|
||||
const METADATA_REGEX: RegExp = /---\s*([\s\S]*?)\s*---/;
|
||||
|
||||
/**
|
||||
* The directory docs are stored in.
|
||||
*/
|
||||
const DOCS_DIR: string = path.join(process.cwd(), "docs");
|
||||
|
||||
/**
|
||||
* Get the content to
|
||||
* display in the docs.
|
||||
*/
|
||||
export const getDocsContent = (): DocsContentMetadata[] => {
|
||||
const content: DocsContentMetadata[] = [];
|
||||
for (const directory of getRecursiveDirectories(DOCS_DIR)) {
|
||||
content.push(...getMetadata<DocsContentMetadata>(DOCS_DIR, directory));
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the metadata of mdx
|
||||
* files in the given directory.
|
||||
*
|
||||
* @param parent the parent directory to search
|
||||
* @param directory the directory to search
|
||||
*/
|
||||
const getMetadata = <T extends MDXMetadata>(
|
||||
parent: string,
|
||||
directory: string
|
||||
): T[] => {
|
||||
const files: string[] = fs
|
||||
.readdirSync(directory)
|
||||
.filter((file: string): boolean => {
|
||||
const extension: string = path.extname(file); // The file extension
|
||||
return extension === ".md" || extension === ".mdx";
|
||||
}); // Read the MDX files
|
||||
return files.map((file: string): T => {
|
||||
const filePath: string = path.join(directory, file); // The path of the file
|
||||
return {
|
||||
slug: filePath
|
||||
.replace(parent, "")
|
||||
.replace(/\\/g, "/") // Normalize the path
|
||||
.replace(/\.mdx?$/, "")
|
||||
.substring(1),
|
||||
extension: path.extname(file),
|
||||
...parseMetadata<T>(fs.readFileSync(filePath, "utf-8")),
|
||||
}; // Map each file to its metadata
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the metadata from
|
||||
* the given content.
|
||||
*
|
||||
* @param content the content to parse
|
||||
* @returns the metadata and content
|
||||
* @template T the type of metadata
|
||||
*/
|
||||
const parseMetadata = <T extends MDXMetadata>(content: string): T => {
|
||||
const metadataBlock: string = METADATA_REGEX.exec(content)![1]; // Get the block of metadata
|
||||
content = content.replace(METADATA_REGEX, "").trim(); // Remove the metadata block from the content
|
||||
const metadata: Partial<{
|
||||
[key: string]: string;
|
||||
}> = {}; // The metadata to return
|
||||
|
||||
// Parse the metadata block as a key-value pair
|
||||
metadataBlock
|
||||
.trim() // Trim any leading or trailing whitespace
|
||||
.split("\n") // Get each line
|
||||
.forEach((line: string): void => {
|
||||
const split: string[] = line.split(": "); // Split the metadata by the colon
|
||||
let value: string = split[1].trim(); // The value of the metadata
|
||||
value = value.replace(/^['"](.*)['"]$/, "$1"); // Remove quotes
|
||||
metadata[split[0].trim()] = value; // Add the metadata to the object
|
||||
});
|
||||
|
||||
// Return the metadata and content. The initial
|
||||
// slug is empty, and is defined later on.
|
||||
return { ...metadata, content } as T;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get directories recursively
|
||||
* in the given directory.
|
||||
*
|
||||
* @param directory the directory to search
|
||||
* @return the directories
|
||||
*/
|
||||
const getRecursiveDirectories = (directory: string): string[] => {
|
||||
const directories: string[] = [directory]; // The directories to return
|
||||
for (const sub of fs.readdirSync(directory)) {
|
||||
const subDirPath: string = path.join(directory, sub); // The sub dir path
|
||||
const stats: Stats = fs.statSync(subDirPath); // Get file stats
|
||||
if (stats.isDirectory()) {
|
||||
directories.push(...getRecursiveDirectories(subDirPath)); // Recursively get directories
|
||||
}
|
||||
}
|
||||
return directories;
|
||||
};
|
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const cn = (...inputs: ClassValue[]) => {
|
||||
return twMerge(clsx(inputs));
|
||||
};
|
70
tailwind.config.ts
Normal file
70
tailwind.config.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
|
||||
const config: Config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
screens: {
|
||||
xs: "475px",
|
||||
...defaultTheme.screens,
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
chart: {
|
||||
"1": "hsl(var(--chart-1))",
|
||||
"2": "hsl(var(--chart-2))",
|
||||
"3": "hsl(var(--chart-3))",
|
||||
"4": "hsl(var(--chart-4))",
|
||||
"5": "hsl(var(--chart-5))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
};
|
||||
export default config;
|
40
tsconfig.json
Normal file
40
tsconfig.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user