18 Commits

Author SHA1 Message Date
Renovate Bot
a827aa0e8b Update dependency eslint to v9 2024-10-07 17:05:15 +00:00
3305ea065c fix build warnings
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m2s
Took 3 minutes
2024-10-07 13:01:15 -04:00
de76a38096 fix icon
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m7s
Took 44 seconds
2024-10-07 12:57:58 -04:00
8cb3a38beb on this page scroll to top button
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Has been cancelled
Took 11 minutes
2024-10-07 12:57:15 -04:00
b7a5665036 make on this page sticky
Took 2 minutes
2024-10-07 12:44:49 -04:00
6e46732bcc on this page footer
Took 12 minutes
2024-10-07 12:42:30 -04:00
7c8d613799 on this page animation
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 59s
Took 11 minutes
2024-10-07 12:30:35 -04:00
666666e254 fix a few bugs
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m17s
Took 17 minutes
2024-10-07 12:19:32 -04:00
fe56169874 fix search dialog opening multiple times via the keybind
Took 5 minutes
2024-10-07 12:01:38 -04:00
83ea83bd18 fix some things and add publication date to doc footers
Took 38 minutes
2024-10-07 11:56:51 -04:00
8cb347b1dc fix build err
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 3m6s
Took 2 minutes
2024-10-06 21:38:15 -04:00
a548b41725 small fixes
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 20s
Took 2 minutes
2024-10-06 21:36:01 -04:00
505e56bcfb footer impl
Some checks failed
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Failing after 16s
Took 18 minutes
2024-10-06 21:33:44 -04:00
d9a721d6c4 structure the layout to support a global footer
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 1m1s
Took 25 minutes
2024-10-06 21:11:20 -04:00
a5bdce4ea6 make the doc footer buttons look better
Took 8 minutes
2024-10-06 20:42:35 -04:00
c93f11b710 on this page indentation
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 4m1s
Took 13 minutes
2024-10-06 20:35:01 -04:00
106233c01f highlight active section in on this page
All checks were successful
Deploy / deploy (ubuntu-latest, 2.44.0) (push) Successful in 57s
Took 33 seconds
2024-10-06 20:21:41 -04:00
99ab34b2ac fix heading mdx components always being h1, and not having an id
Took 19 minutes
2024-10-06 20:21:08 -04:00
21 changed files with 963 additions and 54 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -1,6 +1,6 @@
---
title: 'Hello'
published: '10-06-2024'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---

View File

@ -1,6 +1,6 @@
---
title: 'Hey'
published: '10-06-2024'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---

View File

@ -1,6 +1,6 @@
---
title: 'Hi'
published: '10-06-2024'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---

View File

@ -1,8 +1,293 @@
---
title: '🚀 Introduction'
published: '10-06-2024'
published: '2024-10-06'
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
---
# Get started with Pulse App!
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# an mel dissentiunt ponderum eius dicant adhuc,
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# vim an explicari eirmod pro singulis scripta iaculis fermentum.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# eruditi propriae vulputate elit venenatis reprehendunt delectus.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# dicunt antiopam ultricies nisl egestas voluptatibus harum,
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# viverra senserit cursus theophrastus elaboraret iudicabit ligula.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# posidonium dicat eum nostra auctor quaeque harum
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# doctus primis disputationi atqui magnis himenaeos fastidii
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# ligula cras prodesset litora ridens docendi euripidis
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# efficitur detraxit detraxit fames appareat mutat elit
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# donec nominavi qui dolorum adversarium eum eleifend
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
# nunc contentiones numquam pharetra his vero solum
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.
petentium usu tota noluisse errem elaboraret auctor.

View File

@ -20,11 +20,13 @@
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
"framer-motion": "^11.11.1",
"lucide-react": "^0.447.0",
"luxon": "^3.5.0",
"next": "^15.0.0-canary.179",
"next-themes": "^0.3.0",
"react": "^19.0.0-rc-1460d67c-20241003",
@ -35,13 +37,14 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"typescript": "^5",
"@types/luxon": "^3.4.2",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^9.0.0",
"eslint-config-next": "14.2.8",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^8",
"eslint-config-next": "14.2.8"
"typescript": "^5"
}
}

View File

@ -76,7 +76,7 @@ const DocsPage = async ({
</Breadcrumb>
{/* Content */}
<div className="flex justify-between">
<div className="flex gap-5 justify-between">
<div className="flex flex-col">
<CustomMDX source={page.content} />
</div>

View File

@ -4,6 +4,8 @@ import { ReactElement, ReactNode } from "react";
import { ThemeProvider } from "@/components/theme-provider";
import Navbar from "@/components/navbar/navbar";
import Sidebar from "@/components/sidebar/sidebar";
import Footer from "@/components/footer";
import { TooltipProvider } from "@/components/ui/tooltip";
/**
* The metadata for this app.
@ -48,15 +50,18 @@ const RootLayout = ({
}}
>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
<div className="px-7 max-w-[90rem] mx-auto min-h-screen flex flex-col">
<Navbar />
<div className="pt-[4.5rem] w-full h-full flex flex-grow gap-5">
<div className="relative hidden xs:flex pr-40 sm:pr-52">
<Sidebar />
<TooltipProvider delayDuration={100}>
<div className="px-7 max-w-[90rem] mx-auto min-h-screen flex flex-col">
<Navbar />
<div className="pt-[4.5rem] w-full h-full flex flex-grow gap-5">
<div className="relative hidden xs:flex">
<Sidebar />
</div>
{children}
</div>
{children}
</div>
</div>
<Footer />
</TooltipProvider>
</ThemeProvider>
</body>
</html>

View File

@ -1,10 +1,12 @@
"use client";
import { ReactElement } from "react";
import { ReactElement, useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import Link from "next/link";
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
import { Separator } from "@/components/ui/separator";
import { DateTime } from "luxon";
import SimpleTooltip from "@/components/simple-tooltip";
const DocsFooter = ({
pages,
@ -14,35 +16,72 @@ const DocsFooter = ({
const path: string = usePathname();
const current: number = pages.findIndex(
(page: DocsContentMetadata) => `/${page.slug}` === path
(page: DocsContentMetadata) =>
(path === "/" && page.slug === "intro") || path === `/${page.slug}`
);
const previous: DocsContentMetadata | undefined =
current > 0 ? pages[current - 1] : undefined;
const next: DocsContentMetadata | undefined =
current < pages.length - 1 ? pages[current + 1] : undefined;
const [publicationDate, setPublicationDate] = useState<string | null>(
DateTime.fromISO(pages[current]?.published).toRelative()
);
useEffect(() => {
const interval = setInterval(() => {
setPublicationDate(
DateTime.fromISO(pages[current]?.published).toRelative()
);
}, 1000);
return () => clearInterval(interval);
}, [current, pages]);
return (
<footer className="xs:mx-5 sm:mx-10 my-5 flex flex-col select-none transition-all transform-gpu">
<Separator className="mb-4" />
<footer className="xs:mx-5 sm:mx-10 my-2 flex flex-col select-none transition-all transform-gpu">
{/* Publish Date */}
<div className="ml-auto pt-4">
<SimpleTooltip
content={DateTime.fromISO(
pages[current]?.published
).toLocaleString(DateTime.DATETIME_MED)}
>
<span className="text-sm opacity-75">
Published {publicationDate}
</span>
</SimpleTooltip>
</div>
{/* Pages */}
<Separator className="my-4" />
<div className="flex justify-between">
{/* Previous */}
{previous && (
<Link
className="flex gap-2 items-center hover:opacity-75 transition-all transform-gpu group"
className="flex gap-2 items-end hover:opacity-75 transition-all transform-gpu group"
href={`/${previous.slug}` || "#"}
draggable={false}
>
<ChevronLeftIcon className="w-4 h-4 group-hover:-translate-x-0.5 transition-all transform-gpu" />
{previous.title}
<ChevronLeftIcon className="pb-1 w-4 h-4 group-hover:-translate-x-0.5 transition-all transform-gpu" />
<div className="flex flex-col">
<h1 className="text-sm opacity-75">Previous</h1>
<p>{previous.title}</p>
</div>
</Link>
)}
{/* Next */}
{next && (
<Link
className="ml-auto flex gap-2 items-center hover:opacity-75 transition-all transform-gpu group"
className="ml-auto flex gap-2 items-end hover:opacity-75 transition-all transform-gpu group"
href={`/${next.slug}` || "#"}
draggable={false}
>
{next.title}
<ChevronRightIcon className="w-4 h-4 group-hover:translate-x-0.5 transition-all transform-gpu" />
<div className="flex flex-col">
<h1 className="text-sm opacity-75">Next</h1>
<p>{next.title}</p>
</div>
<ChevronRightIcon className="pb-1 w-4 h-4 group-hover:translate-x-0.5 transition-all transform-gpu" />
</Link>
)}
</div>

195
src/components/footer.tsx Normal file
View File

@ -0,0 +1,195 @@
"use client";
import { ReactElement, ReactNode } from "react";
import AnimatedGridPattern from "@/components/ui/animated-grid-pattern";
import Link from "next/link";
import Image from "next/image";
import {
ArrowTopRightOnSquareIcon,
EnvelopeIcon,
} from "@heroicons/react/24/outline";
import { cn } from "@/lib/utils";
const links = {
Resources: [
{
name: "Support",
href: "https://support.pulseapp.cc",
},
{
name: "Jobs",
href: "https://jobs.pulseapp.cc",
},
{
name: "Developers",
shortName: "Devs",
href: "https://dev.pulseapp.cc",
external: true,
},
{
name: "System Status",
shortName: "Status",
href: "https://status.pulseapp.cc",
external: true,
},
],
Legal: [
{
name: "Terms & Conditions",
shortName: "Terms",
href: "/legal/terms",
},
{
name: "Privacy Policy",
shortName: "Privacy",
href: "/legal/privacy",
},
],
};
const Footer = (): ReactElement => (
<footer className="mt-3 relative h-[19.5rem] md:h-[17rem] flex justify-center border-t border-zinc-700/75 overflow-hidden select-none">
<div className="w-full md:max-w-[65rem]">
<div className="px-5 py-5 md:py-10 w-full flex flex-col md:flex-row items-center justify-around md:items-start gap-7">
{/* Top */}
<div className="flex flex-col gap-2.5 items-center md:items-start">
<Branding />
{/* Socials */}
<div className="pl-1 flex gap-2.5 items-center z-50">
<SocialLink
name="GitHub"
logo="github.svg"
href="https://github.com/PulseAppCC"
/>
<SocialLink
name="Discord"
logo="discord.svg"
href="https://discord.pulseapp.cc"
/>
<SocialLink
name="Email"
logo={
<EnvelopeIcon className="opacity-95 w-6 h-6" />
}
href="mailto:support@pulseapp.cc"
/>
</div>
</div>
{/* Links */}
<div className="flex gap-7 md:gap-12 transition-all transform-gpu">
{Object.entries(links).map(([title, links]) => (
<LinkCategory key={title} title={title}>
{links.map((link) => (
<FooterLink key={link.name} {...link} />
))}
</LinkCategory>
))}
</div>
</div>
{/* Copyright */}
<p className="absolute inset-x-0 bottom-3.5 flex text-sm text-center justify-center opacity-60">
Copyright &copy; {new Date().getFullYear()} Pulse App. All
rights reserved.
</p>
</div>
{/* Background */}
<AnimatedGridPattern
className="inset-x-0 skew-y-12 [mask-image:radial-gradient(500px_circle_at_center,white,transparent)]"
numSquares={30}
maxOpacity={0.1}
duration={3}
repeatDelay={1}
/>
</footer>
);
const Branding = () => (
<Link
className="flex gap-3 items-center hover:opacity-75 transition-all transform-gpu"
href="https://pulseapp.cc"
draggable={false}
>
<Image
src="/media/logo.png"
alt="Pulse App Logo"
width={40}
height={40}
draggable={false}
/>
<h1 className="text-xl font-bold">Pulse App</h1>
</Link>
);
const SocialLink = ({
name,
logo,
href,
}: {
name: string;
logo: string | ReactElement;
href: string;
}) => (
<Link
className="hover:opacity-75 transition-all transform-gpu"
href={href}
target="_blank"
draggable={false}
>
{typeof logo === "string" ? (
<Image
src={`/media/${logo}`}
alt={`${name}'s Logo`}
width={20}
height={20}
draggable={false}
/>
) : (
logo
)}
</Link>
);
const LinkCategory = ({
title,
children,
}: {
title: string;
children: ReactNode;
}): ReactElement => (
<div className="flex flex-col gap-0.5">
<h1 className="text-lg font-semibold">{title}</h1>
{children}
</div>
);
const FooterLink = ({
name,
shortName,
href,
}: {
name: string;
shortName?: string | undefined;
href: string;
}): ReactElement => {
const external: boolean = !href.startsWith("/");
return (
<Link
className="flex gap-2 items-center hover:opacity-75 transition-all transform-gpu"
href={href}
target={external ? "_blank" : undefined}
draggable={false}
>
<span className={cn("hidden sm:flex", !shortName && "flex")}>
{name}
</span>
{shortName && <span className="flex sm:hidden">{shortName}</span>}
{external && <ArrowTopRightOnSquareIcon className="w-3.5 h-3.5" />}
</Link>
);
};
export default Footer;

View File

@ -8,32 +8,32 @@ import remarkGfm from "remark-gfm";
*/
const components = {
h1: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={1} className="text-4xl">
<Heading as="h1" size={1} className="text-4xl">
{children}
</Heading>
),
h2: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={2} className="text-3xl">
<Heading as="h2" size={2} className="text-3xl">
{children}
</Heading>
),
h3: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={3} className="text-2xl">
<Heading as="h3" size={3} className="text-2xl">
{children}
</Heading>
),
h4: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={4} className="text-xl">
<Heading as="h4" size={4} className="text-xl">
{children}
</Heading>
),
h5: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={5} className="text-lg">
<Heading as="h5" size={5} className="text-lg">
{children}
</Heading>
),
h6: ({ children }: { children: ReactNode }): ReactElement => (
<Heading size={5} className="text-md">
<Heading as="h6" size={6} className="text-md">
{children}
</Heading>
),
@ -89,15 +89,31 @@ export const CustomMDX = (props: any): ReactElement => (
* @return the heading jsx
*/
const Heading = ({
as: Component,
className,
size,
children,
}: {
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
className: string;
size: number;
children: ReactNode;
}): ReactElement => (
<h1 className={cn("pt-2.5 font-bold", size >= 2 && "pt-7", className)}>
{children}
</h1>
);
}): ReactElement => {
const id: string | undefined =
typeof children === "string" ? slugify(children) : undefined;
return (
<Component
id={id}
className={cn("pt-2.5 font-bold", size >= 2 && "pt-7", className)}
>
{children}
</Component>
);
};
const slugify = (text: string): string =>
text
.toLowerCase()
.replace(/[^\w\s-]/g, "")
.replace(/[\s_-]+/g, "-")
.trim();

View File

@ -1,7 +1,6 @@
import { ReactElement } from "react";
import Link from "next/link";
import Image from "next/image";
import { cn } from "@/lib/utils";
import QuickSearchDialog from "@/components/navbar/search-dialog";
import { getDocsContent } from "@/lib/mdx";
import Sidebar from "@/components/sidebar/sidebar";
@ -9,12 +8,12 @@ import Sidebar from "@/components/sidebar/sidebar";
const Navbar = (): ReactElement => {
const pages: DocsContentMetadata[] = getDocsContent();
return (
<nav className="fixed left-0 inset-x-0 bg-white/[0.007] backdrop-saturate-100 backdrop-blur-xl border-b">
<nav className="fixed left-0 inset-x-0 bg-white/[0.007] backdrop-saturate-100 backdrop-blur-xl border-b z-50">
<div className="px-7 max-w-[90rem] mx-auto py-4 flex justify-between items-center">
{/* Branding */}
<Link
className="flex gap-1 items-end hover:opacity-75 transition-all transform-gpu select-none"
href="/public"
href="/"
draggable={false}
>
<h1 className="text-lg font-semibold">docs.</h1>
@ -31,7 +30,7 @@ const Navbar = (): ReactElement => {
<div className="flex gap-5 sm:gap-7 items-center transition-all transform-gpu">
{/* Search */}
<div className="hidden xs:flex">
<QuickSearchDialog pages={pages} />
<QuickSearchDialog pages={pages} bindKeybind />
</div>
{/* Social */}

View File

@ -21,14 +21,19 @@ import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.share
*/
const QuickSearchDialog = ({
pages,
bindKeybind = false,
}: {
pages: DocsContentMetadata[];
bindKeybind?: boolean;
}): ReactElement => {
const [open, setOpen] = useState<boolean>(false);
const router: AppRouterInstance = useRouter();
// Listen for CTRL + K keybinds to open this dialog
useEffect(() => {
if (!bindKeybind) {
return;
}
const handleKeyDown = (event: KeyboardEvent): void => {
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
event.preventDefault();
@ -37,7 +42,7 @@ const QuickSearchDialog = ({
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
}, [bindKeybind]);
// Render the contents
return (

View File

@ -1,8 +1,17 @@
"use client";
import { ReactElement, useEffect, useState } from "react";
import { Bars3CenterLeftIcon } from "@heroicons/react/24/outline";
import { ReactElement, useEffect, useRef, useState } from "react";
import {
ArrowLongRightIcon,
ArrowLongUpIcon,
Bars3CenterLeftIcon,
} from "@heroicons/react/24/outline";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { truncateText } from "@/lib/string";
import { motion, useInView } from "framer-motion";
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
type Header = {
id: string;
@ -12,6 +21,13 @@ type Header = {
const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
const [headers, setHeaders] = useState<Header[]>([]);
const [activeHeader, setActiveHeader] = useState<string | undefined>(
undefined
);
const observerRef = useRef<IntersectionObserver | undefined>(undefined);
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref);
useEffect(() => {
// Regular expression to match markdown headers
@ -33,27 +49,139 @@ const OnThisPage = ({ page }: { page: DocsContentMetadata }): ReactElement => {
setHeaders(extractedHeaders);
}, [page.content]);
useEffect(() => {
// Cleanup existing observer
if (observerRef.current) {
observerRef.current.disconnect();
}
const observer = new IntersectionObserver(
(entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if (entry.isIntersecting) {
setActiveHeader(entry.target.id);
}
});
},
{ rootMargin: "0px 0px -80% 0px", threshold: 0.1 }
);
observerRef.current = observer;
// Observe all header elements
headers.forEach((header: Header) => {
const element: HTMLElement | null = document.getElementById(
header.id
);
if (element) {
observer.observe(element);
}
});
return () => observer.disconnect();
}, [headers]);
return (
<div className="flex flex-col gap-2 text-sm">
<motion.div
ref={ref}
className="sticky top-[5.5rem] w-44 max-h-[calc(100vh-3.5rem)] flex flex-col gap-2 text-sm select-none"
initial={{ opacity: 0 }}
animate={{ opacity: inView ? 1 : 0 }}
transition={{ duration: 0.2 }}
>
{/* Title */}
<div className="flex gap-2.5 items-center select-none">
<div className="flex gap-2.5 items-center">
<Bars3CenterLeftIcon className="w-5 h-5" />
<h1>On This Page</h1>
</div>
{/* Headers */}
<ul>
<ul className="relative">
{headers.map((header: Header) => (
<li
key={header.id}
className="opacity-65 hover:opacity-80 transition-all transform-gpu"
style={{ marginLeft: `${(header.level - 1) * 16}px` }}
className={cn(
"hover:opacity-80 transition-all transform-gpu relative",
activeHeader === header.id
? "font-semibold text-primary"
: "opacity-65"
)}
style={{ paddingLeft: `${(header.level - 1) * 16}px` }}
>
<Link href={`#${header.id}`}>{header.text}</Link>
{/* Indentation */}
{header.level > 1 && (
<div
className="absolute left-0 top-0 bottom-0 border-l border-muted"
style={{
left: `${(header.level - 2) * 16 + 4}px`,
}}
/>
)}
{/* Header */}
<Link
href={`#${header.id}`}
draggable={false}
className="block py-1"
>
{truncateText(header.text, 24)}
</Link>
</li>
))}
</ul>
</div>
{/* Footer */}
<div>
<Separator className="mt-1 mb-3.5" />
<Footer page={page} />
</div>
</motion.div>
);
};
const Footer = ({ page }: { page: DocsContentMetadata }): ReactElement => {
const [hasScrolled, setHasScrolled] = useState<boolean>(false);
useEffect(() => {
const handleScroll = () => setHasScrolled(window.scrollY > 400);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<footer className="flex flex-col opacity-75">
{/* Edit on Git */}
<Link
className="flex gap-1.5 items-center text-xs hover:opacity-75 transition-all transform-gpu group"
href={`https://git.rainnny.club/PulseApp/docs/src/branch/master/docs/${page.slug}.md`}
target="_blank"
draggable={false}
>
<span>Edit this page on GitHub</span>
<ArrowLongRightIcon className="w-4 h-4 group-hover:translate-x-0.5 transition-all transform-gpu" />
</Link>
{/* Scroll to Top */}
<div
className={cn(
"transition-opacity transform-gpu",
hasScrolled
? "opacity-100"
: "opacity-0 pointer-events-none"
)}
>
<Button
className="p-0 justify-start flex gap-1.5 items-center text-xs hover:bg-transparent hover:opacity-75 transition-all transform-gpu group"
variant="ghost"
onClick={() =>
window.scrollTo({ top: 0, behavior: "smooth" })
}
>
<span>Scroll to Top</span>
<ArrowLongUpIcon className="w-4 h-4 group-hover:translate-x-0.5 transition-all transform-gpu" />
</Button>
</div>
</footer>
);
};
export default OnThisPage;

View File

@ -101,11 +101,10 @@ const CategoryItem = ({
animate="open"
exit="collapsed"
variants={{
open: { opacity: 1, height: "auto", y: 0 },
open: { opacity: 1, height: "auto" },
collapsed: {
opacity: 0,
height: 0,
y: -20,
},
}}
transition={{

View File

@ -22,7 +22,7 @@ const Sidebar = (): ReactElement => (
</div>
{/* Desktop */}
<div className="hidden fixed top-[4.3rem] inset-y-0 min-w-32 w-40 sm:w-52 py-3 xs:flex flex-col justify-between transition-all transform-gpu">
<div className="hidden xs:flex sticky top-[4.3rem] max-h-[calc(100vh-3.5rem)] overflow-y-auto min-w-32 w-40 lg:w-52 py-5 flex-col justify-between transition-all transform-gpu">
<SidebarContent />
</div>
</>

View File

@ -0,0 +1,47 @@
import { ReactElement, ReactNode } from "react";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { SIDE_OPTIONS } from "@radix-ui/react-popper";
/**
* The props for a simple tooltip.
*/
type SimpleTooltipProps = {
/**
* The content to display in the tooltip.
*/
content: string | ReactElement;
/**
* The side to display the tooltip on.
*/
side?: (typeof SIDE_OPTIONS)[number];
/**
* The children to render in this tooltip.
*/
children: ReactNode;
};
/**
* A simple tooltip, this is wrapping the
* shadcn tooltip to make it easier to use.
*
* @return the tooltip jsx
*/
const SimpleTooltip = ({
content,
side,
children,
}: SimpleTooltipProps): ReactElement => (
<Tooltip>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent className="bg-muted text-white" side={side}>
{content}
</TooltipContent>
</Tooltip>
);
export default SimpleTooltip;

View File

@ -0,0 +1,150 @@
"use client";
import { useEffect, useId, useRef, useState } from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
interface GridPatternProps {
width?: number;
height?: number;
x?: number;
y?: number;
strokeDasharray?: any;
numSquares?: number;
className?: string;
maxOpacity?: number;
duration?: number;
repeatDelay?: number;
}
export function GridPattern({
width = 40,
height = 40,
x = -1,
y = -1,
strokeDasharray = 0,
numSquares = 50,
className,
maxOpacity = 0.5,
duration = 4,
repeatDelay = 0.5,
...props
}: GridPatternProps) {
const id = useId();
const containerRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [squares, setSquares] = useState(() => generateSquares(numSquares));
function getPos() {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
];
}
// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}));
}
// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq
)
);
};
// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares));
}
}, [dimensions, numSquares, generateSquares]);
// Resize observer to update container dimensions
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
setDimensions({
width: entry.contentRect.width,
height: entry.contentRect.height,
});
}
});
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [containerRef]);
return (
<svg
ref={containerRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
className
)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path
d={`M.5 ${height}V.5H${width}`}
fill="none"
strokeDasharray={strokeDasharray}
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
transition={{
duration,
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
fill="currentColor"
strokeWidth="0"
/>
))}
</svg>
</svg>
);
}
export default GridPattern;

View File

@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-[450ms] data-[state=open]:animate-in data-[state=closed]:animate-out",
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-&lsqb;state=open&rsqb;:duration-&lsqb;450ms&rsqb; data-[state=open]:animate-in data-[state=closed]:animate-out",
{
variants: {
side: {

View File

@ -0,0 +1,30 @@
"use client";
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-105 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View File

@ -8,3 +8,11 @@
export const capitalizeWords = (str: string | undefined): string | undefined =>
str &&
str.toLowerCase().replace(/\b\w/g, (char: string) => char.toUpperCase());
export const truncateText = (
text: string | undefined,
maxLength: number
): string | undefined =>
text && text.length > maxLength
? text.slice(0, maxLength - 3).trim() + "..."
: text;