diff --git a/apps/blog/src/app/(blog)/layout.tsx b/apps/blog/src/app/(blog)/layout.tsx index ec90473df7..dd8c53b1ed 100644 --- a/apps/blog/src/app/(blog)/layout.tsx +++ b/apps/blog/src/app/(blog)/layout.tsx @@ -1,23 +1,120 @@ -import { HomeLayout } from 'fumadocs-ui/layouts/home'; -import { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; -export function baseOptions(): BaseLayoutProps { +import { Button } from "@prisma-docs/eclipse"; +import { + Logo, + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + NavigationWrapper, + Socials, +} from "@prisma-docs/ui/components/navigation-menu"; +import { StarCount } from "@prisma-docs/ui/components/star-count"; +export function baseOptions() { return { nav: { - title: 'My App', + title: "My App", }, links: [ { - url: '/docs', - text: 'Docs', + text: "Products", + sub: [ + { + text: "Postgres", + url: "/postgres", + desc: "Managed Postgres for global workloads", + }, + { + text: "ORM", + url: "/orm", + }, + ], }, { - url: '/blog', - text: 'Blog', + url: "/pricing", + text: "Pricing", + }, + { + text: "Resources", + sub: [ + { + text: "MCP", + url: "/mcp", + desc: "Managed Postgres for global workloads", + }, + { + text: "Tutorials", + url: "/learn", + }, + ], + }, + { + url: "/partners", + text: "Partners", + }, + { + url: "/blog", + text: "Blog", }, ], }; } -export default function Layout({ children, }: { children: React.ReactNode; }) { - return {children}; +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + + + + + + {Logo} + + +
+ {baseOptions().links.map((link: any) => + link.url ? ( + + + {link.text} + + + ) : link.sub ? ( + + {link.text} + + {link.sub.map((sublink: any) => ( + + {sublink.text} + + ))} + + + ) : null, + )} +
+
+ + + + + + + + + + + + +
+
+ {children} + + ); } diff --git a/apps/blog/src/app/layout.tsx b/apps/blog/src/app/layout.tsx index 3f0c7dbe77..bc100fb775 100644 --- a/apps/blog/src/app/layout.tsx +++ b/apps/blog/src/app/layout.tsx @@ -1,26 +1,32 @@ -import { RootProvider } from 'fumadocs-ui/provider/next'; -import './global.css'; -import { Inter, Barlow } from 'next/font/google'; +import { RootProvider } from "fumadocs-ui/provider/next"; +import "./global.css"; +import { Inter, Barlow } from "next/font/google"; +import Script from "next/script"; const inter = Inter({ - subsets: ['latin'], - variable: '--font-inter', + subsets: ["latin"], + variable: "--font-inter", }); const barlow = Barlow({ - subsets: ['latin'], - weight: ['400', '500', '600', '700'], - variable: '--font-barlow', + subsets: ["latin"], + weight: ["400", "500", "600", "700"], + variable: "--font-barlow", }); -export default function Layout({ children }: LayoutProps<'/'>) { +export default function Layout({ children }: LayoutProps<"/">) { return ( - + + + {children} diff --git a/packages/eclipse/src/components/button.tsx b/packages/eclipse/src/components/button.tsx index a5b1735fff..3e18871bd1 100644 --- a/packages/eclipse/src/components/button.tsx +++ b/packages/eclipse/src/components/button.tsx @@ -2,15 +2,15 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../lib/cn"; -const buttonVariants = cva("border", { +const buttonVariants = cva("", { variants: { variant: { ppg: "bg-background-ppg-reverse text-foreground-ppg-reverse hover:bg-background-ppg-reverse-strong", orm: "bg-background-orm-reverse text-foreground-orm-reverse hover:bg-background-orm-reverse-strong", default: - "bg-background-default border-stroke-neutral text-foreground-neutral", + "bg-background-default border border-stroke-neutral text-foreground-neutral", "default-stronger": - "bg-background-neutral border-stroke-neutral text-foreground-neutral hover:bg-background-neutral-strong", + "bg-background-neutral text-foreground-neutral hover:bg-background-neutral-strong", "default-weaker": "bg-background-neutral text-foreground-neutral-weaker", error: "bg-background-error-reverse text-foreground-error-reverse hover:bg-backΩground-error-reverse-strong focus-visible:ring-stroke-error", diff --git a/packages/eclipse/src/static/fonts/monaspace_neon_var.woff b/packages/eclipse/src/static/fonts/monaspace_neon_var.woff new file mode 100644 index 0000000000..0df0364fd8 Binary files /dev/null and b/packages/eclipse/src/static/fonts/monaspace_neon_var.woff differ diff --git a/packages/eclipse/src/static/fonts/monaspace_neon_var.woff2 b/packages/eclipse/src/static/fonts/monaspace_neon_var.woff2 new file mode 100644 index 0000000000..9b7d48d487 Binary files /dev/null and b/packages/eclipse/src/static/fonts/monaspace_neon_var.woff2 differ diff --git a/packages/eclipse/src/styles/globals.css b/packages/eclipse/src/styles/globals.css index a34a0c667f..e6fab02853 100644 --- a/packages/eclipse/src/styles/globals.css +++ b/packages/eclipse/src/styles/globals.css @@ -3,6 +3,13 @@ @source "../components/**/*.tsx"; @source "../components/ui/**/*.tsx"; +@font-face { + font-family: "Monaspace Neon Var"; + src: + url("../static/fonts/monaspace_neon_var.woff") format("woff"), + url("../static/fonts/monaspace_neon_var.woff2") format("woff2"); +} + @theme { /* Box Shadow */ --shadow-drop-low: 0 1px 2px 0 rgba(0, 0, 0, 0.04); @@ -75,7 +82,7 @@ --font-family-display: var(--font-mona-sans), Inter, sans-serif; --font-family-sans-display: Inter, sans-serif; --font-family-sans: Inter, system-ui, sans-serif; - --font-family-mono: Jetbrains Mono, monospace; + --font-family-mono: Monaspace Neon Var; /* Tailwind Font Family Mappings */ --font-display: var(--font-mona-sans), Inter, sans-serif; diff --git a/packages/eclipse/src/tokens/index.ts b/packages/eclipse/src/tokens/index.ts index 913a167f5d..a5817e9560 100644 --- a/packages/eclipse/src/tokens/index.ts +++ b/packages/eclipse/src/tokens/index.ts @@ -235,7 +235,7 @@ export const typography = { fontFamily: { "sans-display": "Inter", sans: "Inter", - monospace: "Jetbrains Mono", + monospace: "Monaspace Neon Var", }, fontSize: { "2xs": 11, diff --git a/packages/ui/src/components/navigation-menu.tsx b/packages/ui/src/components/navigation-menu.tsx new file mode 100644 index 0000000000..32c37e4674 --- /dev/null +++ b/packages/ui/src/components/navigation-menu.tsx @@ -0,0 +1,244 @@ +"use client"; +import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"; +import { cva } from "class-variance-authority"; + +import { cn } from "../lib/cn"; +import { ChevronDownIcon } from "lucide-react"; +import { useScrollThreshold } from "../hooks/use-scroll-threshold"; +import { StarCount } from "./star-count"; + +const Logo = ( + + + +); + +function NavigationMenu({ + align = "start", + className, + children, + ...props +}: NavigationMenuPrimitive.Root.Props & + Pick) { + return ( + + {children} + + + ); +} + +function NavigationWrapper({ + className, + ...props +}: React.ComponentPropsWithRef<"div">) { + const scroll = useScrollThreshold(64); + + return ( +
+ {props.children} +
+ ); +} + +function NavigationMenuList({ + className, + ...props +}: React.ComponentPropsWithRef) { + return ( + + ); +} + +function NavigationMenuItem({ + className, + ...props +}: React.ComponentPropsWithRef) { + return ( + + ); +} + +const navigationMenuTriggerStyle = cva( + "bg-background hover:bg-muted focus:bg-muted data-open:hover:bg-muted data-open:focus:bg-muted data-open:bg-muted/50 focus-visible:ring-ring/50 data-popup-open:bg-muted/50 data-popup-open:hover:bg-muted rounded-none px-2.5 py-1.5 text-base font-semibold transition-all focus-visible:ring-1 focus-visible:outline-1 disabled:opacity-50 group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center disabled:pointer-events-none outline-none", +); + +function NavigationMenuTrigger({ + className, + children, + ...props +}: NavigationMenuPrimitive.Trigger.Props) { + return ( + + {children}{" "} + + ); +} + +function NavigationMenuContent({ + className, + ...props +}: NavigationMenuPrimitive.Content.Props) { + return ( + + ); +} + +function NavigationMenuPositioner({ + className, + side = "bottom", + sideOffset = 8, + align = "start", + alignOffset = 0, + ...props +}: NavigationMenuPrimitive.Positioner.Props) { + return ( + + + + + + + + ); +} + +function NavigationMenuLink({ + className, + ...props +}: NavigationMenuPrimitive.Link.Props) { + return ( + + ); +} + +function NavigationMenuIndicator({ + className, + ...props +}: React.ComponentPropsWithRef) { + return ( + +
+ + ); +} + +function Socials() { + const scroll = useScrollThreshold(64); + + return ( +
+ + + + + + + + + + + +
+ ); +} +export { + Logo, + NavigationMenu, + NavigationMenuContent, + NavigationMenuIndicator, + NavigationMenuItem, + NavigationMenuLink, + NavigationWrapper, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, + NavigationMenuPositioner, + Socials, +}; diff --git a/packages/ui/src/components/star-count.tsx b/packages/ui/src/components/star-count.tsx new file mode 100644 index 0000000000..a340bb8b35 --- /dev/null +++ b/packages/ui/src/components/star-count.tsx @@ -0,0 +1,20 @@ +"use client"; +import { useStarCount } from "../hooks/use-star-count"; + +export const StarCount = ({ className }: any) => { + const { starCount } = useStarCount(); + + const getStarCount = () => { + const stars = (starCount / 1000).toFixed(1); + return Number(stars) % 1 ? stars : Number(stars).toFixed(0); + }; + + return ( + + {getStarCount()}K + + ); +}; diff --git a/packages/ui/src/hooks/use-scroll-threshold.ts b/packages/ui/src/hooks/use-scroll-threshold.ts new file mode 100644 index 0000000000..a01769ae75 --- /dev/null +++ b/packages/ui/src/hooks/use-scroll-threshold.ts @@ -0,0 +1,20 @@ +import { useEffect, useState } from "react"; + +export const useScrollThreshold = (threshold: number = 64) => { + const [isScrolled, setIsScrolled] = useState(false); + + useEffect(() => { + const scrollListener = () => { + if (window.scrollY >= threshold) { + setIsScrolled(true); + } else { + setIsScrolled(false); + } + }; + + window.addEventListener("scroll", scrollListener); + return () => window.removeEventListener("scroll", scrollListener); + }, [threshold]); + + return isScrolled; +}; diff --git a/packages/ui/src/hooks/use-star-count.ts b/packages/ui/src/hooks/use-star-count.ts new file mode 100644 index 0000000000..b4fa604520 --- /dev/null +++ b/packages/ui/src/hooks/use-star-count.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from "react"; + +const GITHUB_API_URL = "https://api.github.com/repos/prisma/prisma"; + +type Repo = { + name: string; + stargazers_count: number; +}; + +export const useStarCount = () => { + const [starCount, setStarCount] = useState(0); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchStarCount = async () => { + try { + setIsLoading(true); + const response = await fetch(GITHUB_API_URL); + + if (!response.ok) { + throw new Error("Error fetching GitHub repository data"); + } + + const data: Repo = await response.json(); + setStarCount(data.stargazers_count); + setError(null); + } catch (err) { + setError((err as Error).message); + } finally { + setIsLoading(false); + } + }; + + fetchStarCount(); + }, []); + + return { starCount, isLoading, error }; +}; diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css index af02e31598..6f8f1fd195 100644 --- a/packages/ui/src/styles/globals.css +++ b/packages/ui/src/styles/globals.css @@ -4,6 +4,7 @@ @custom-variant dark (&:is(.dark *)); @theme inline { + --breakpoint-md: 874px; --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); @@ -124,3 +125,7 @@ @apply bg-background text-foreground; } } + +.transition-navbar { + transition: max-width 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); +}