Compare commits
18 Commits
97f2a2ce64
...
d777ab2a64
Author | SHA1 | Date | |
---|---|---|---|
|
d777ab2a64 | ||
3305ea065c | |||
de76a38096 | |||
8cb3a38beb | |||
b7a5665036 | |||
6e46732bcc | |||
7c8d613799 | |||
666666e254 | |||
fe56169874 | |||
83ea83bd18 | |||
8cb347b1dc | |||
a548b41725 | |||
505e56bcfb | |||
d9a721d6c4 | |||
a5bdce4ea6 | |||
c93f11b710 | |||
106233c01f | |||
99ab34b2ac |
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Hello'
|
||||
published: '10-06-2024'
|
||||
published: '2024-10-06'
|
||||
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
|
||||
---
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Hey'
|
||||
published: '10-06-2024'
|
||||
published: '2024-10-06'
|
||||
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
|
||||
---
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Hi'
|
||||
published: '10-06-2024'
|
||||
published: '2024-10-06'
|
||||
summary: 'petentium usu tota noluisse errem elaboraret auctor.'
|
||||
---
|
||||
|
||||
|
287
docs/intro.md
287
docs/intro.md
@ -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.
|
@ -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": "^8",
|
||||
"eslint-config-next": "14.2.14",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.8"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
195
src/components/footer.tsx
Normal 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 © {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;
|
@ -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();
|
||||
|
@ -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 */}
|
||||
|
@ -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 (
|
||||
|
@ -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;
|
||||
|
@ -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={{
|
||||
|
@ -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>
|
||||
</>
|
||||
|
47
src/components/simple-tooltip.tsx
Normal file
47
src/components/simple-tooltip.tsx
Normal 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;
|
150
src/components/ui/animated-grid-pattern.tsx
Normal file
150
src/components/ui/animated-grid-pattern.tsx
Normal 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;
|
@ -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-[state=open]:duration-[450ms] data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
|
30
src/components/ui/tooltip.tsx
Normal file
30
src/components/ui/tooltip.tsx
Normal 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 };
|
@ -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;
|
||||
|
Reference in New Issue
Block a user