From d9ef6c59a6a4e281f11666369d81b877193edfa0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 22 Feb 2026 15:12:17 -0800 Subject: [PATCH 1/9] migrate to app router --- src/{pages => app}/404Page.module.css | 0 src/{pages => app}/Home.module.css | 0 .../docs}/DocsPage.module.css | 0 src/app/docs/[...path]/page.tsx | 80 ++++++++++++ .../docs/docs-page-content.tsx} | 85 ++----------- src/app/docs/page.tsx | 48 ++++++++ .../download/DownloadPage.module.css | 0 .../index.tsx => app/download/page.tsx} | 56 ++++----- .../download/release-download-page.tsx} | 8 +- .../download/tip-download-page.tsx} | 6 +- src/app/home-client.tsx | 97 +++++++++++++++ src/app/layout.tsx | 45 +++++++ src/{pages/404.tsx => app/not-found.tsx} | 39 +++--- src/app/page.tsx | 16 +++ src/components/animated-terminal/index.tsx | 2 + src/components/codeblock/index.tsx | 2 + src/components/custom-mdx/index.tsx | 2 + src/components/jumplink-header/index.tsx | 8 +- src/components/nav-tree/index.tsx | 2 + src/components/navbar/index.tsx | 22 ++-- src/components/scroll-to-top/index.tsx | 2 + src/components/sidecar/index.tsx | 2 + .../tabbed-terminals-section/index.tsx | 2 + src/components/terminal/index.tsx | 2 + src/components/vt-sequence/index.tsx | 6 +- src/layouts/nav-footer-layout/index.tsx | 9 +- src/layouts/root-layout/index.tsx | 70 ++--------- src/lib/docs-config.ts | 3 + src/lib/docs-metadata-title.ts | 16 +++ src/lib/fetch-docs.ts | 24 ++-- src/lib/fetch-latest-ghostty-version.ts | 2 +- src/lib/mdx-components.tsx | 115 ++++++++++++++++++ src/pages/_app.tsx | 6 - src/pages/_document.tsx | 13 -- src/pages/docs/index.tsx | 6 - src/pages/index.tsx | 115 ------------------ tsconfig.json | 11 +- 37 files changed, 553 insertions(+), 369 deletions(-) rename src/{pages => app}/404Page.module.css (100%) rename src/{pages => app}/Home.module.css (100%) rename src/{pages/docs/[...path] => app/docs}/DocsPage.module.css (100%) create mode 100644 src/app/docs/[...path]/page.tsx rename src/{pages/docs/[...path]/index.tsx => app/docs/docs-page-content.tsx} (50%) create mode 100644 src/app/docs/page.tsx rename src/{pages => app}/download/DownloadPage.module.css (100%) rename src/{pages/download/index.tsx => app/download/page.tsx} (57%) rename src/{pages/download/release.tsx => app/download/release-download-page.tsx} (94%) rename src/{pages/download/tip.tsx => app/download/tip-download-page.tsx} (90%) create mode 100644 src/app/home-client.tsx create mode 100644 src/app/layout.tsx rename src/{pages/404.tsx => app/not-found.tsx} (53%) create mode 100644 src/app/page.tsx create mode 100644 src/lib/docs-config.ts create mode 100644 src/lib/docs-metadata-title.ts create mode 100644 src/lib/mdx-components.tsx delete mode 100644 src/pages/_app.tsx delete mode 100644 src/pages/_document.tsx delete mode 100644 src/pages/docs/index.tsx delete mode 100644 src/pages/index.tsx diff --git a/src/pages/404Page.module.css b/src/app/404Page.module.css similarity index 100% rename from src/pages/404Page.module.css rename to src/app/404Page.module.css diff --git a/src/pages/Home.module.css b/src/app/Home.module.css similarity index 100% rename from src/pages/Home.module.css rename to src/app/Home.module.css diff --git a/src/pages/docs/[...path]/DocsPage.module.css b/src/app/docs/DocsPage.module.css similarity index 100% rename from src/pages/docs/[...path]/DocsPage.module.css rename to src/app/docs/DocsPage.module.css diff --git a/src/app/docs/[...path]/page.tsx b/src/app/docs/[...path]/page.tsx new file mode 100644 index 00000000..7d871701 --- /dev/null +++ b/src/app/docs/[...path]/page.tsx @@ -0,0 +1,80 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import type { Breadcrumb } from "@/components/breadcrumbs"; +import type { NavTreeNode } from "@/components/nav-tree"; +import { DOCS_DIRECTORY, DOCS_PAGES_ROOT_PATH } from "@/lib/docs-config"; +import { + type DocsPageData, + loadAllDocsPageSlugs, + loadDocsPage, +} from "@/lib/fetch-docs"; +import { docsMetadataTitle } from "@/lib/docs-metadata-title"; +import { loadDocsNavTreeData } from "@/lib/fetch-nav"; +import { navTreeToBreadcrumbs } from "@/lib/nav-tree-to-breadcrumbs"; +import DocsPageContent from "../docs-page-content"; + +export const dynamic = "force-static"; +export const dynamicParams = false; + +interface DocsRouteProps { + params: Promise<{ path: string[] }>; +} + +async function loadDocsRouteData(path: string[]): Promise<{ + navTreeData: NavTreeNode[]; + docsPageData: DocsPageData; + breadcrumbs: Breadcrumb[]; +}> { + const activePageSlug = path.join("/"); + const navTreeData = await loadDocsNavTreeData(DOCS_DIRECTORY, activePageSlug); + + let docsPageData: DocsPageData; + try { + docsPageData = await loadDocsPage(DOCS_DIRECTORY, activePageSlug); + } catch { + notFound(); + } + + const breadcrumbs = navTreeToBreadcrumbs( + "Ghostty Docs", + DOCS_PAGES_ROOT_PATH, + navTreeData, + activePageSlug, + ); + return { navTreeData, docsPageData, breadcrumbs }; +} + +export async function generateStaticParams(): Promise< + Array<{ path: string[] }> +> { + const docsPageSlugs = await loadAllDocsPageSlugs(DOCS_DIRECTORY); + return docsPageSlugs + .filter((slug) => slug !== "index") + .map((slug) => ({ path: slug.split("/") })); +} + +export async function generateMetadata({ + params, +}: DocsRouteProps): Promise { + const { path } = await params; + const { docsPageData, breadcrumbs } = await loadDocsRouteData(path); + + return { + title: docsMetadataTitle(breadcrumbs), + description: docsPageData.description, + }; +} + +export default async function DocsPage({ params }: DocsRouteProps) { + const { path } = await params; + const { navTreeData, docsPageData, breadcrumbs } = + await loadDocsRouteData(path); + + return ( + + ); +} diff --git a/src/pages/docs/[...path]/index.tsx b/src/app/docs/docs-page-content.tsx similarity index 50% rename from src/pages/docs/[...path]/index.tsx rename to src/app/docs/docs-page-content.tsx index 90baeb3a..b3db7fb6 100644 --- a/src/pages/docs/[...path]/index.tsx +++ b/src/app/docs/docs-page-content.tsx @@ -1,73 +1,22 @@ import Breadcrumbs, { type Breadcrumb } from "@/components/breadcrumbs"; -import CustomMDX from "@/components/custom-mdx"; import NavTree, { type NavTreeNode } from "@/components/nav-tree"; import ScrollToTopButton from "@/components/scroll-to-top"; import Sidecar from "@/components/sidecar"; import { H1, P } from "@/components/text"; import NavFooterLayout from "@/layouts/nav-footer-layout"; -import { - type DocsPageData, - loadAllDocsPageSlugs, - loadDocsPage, -} from "@/lib/fetch-docs"; -import { loadDocsNavTreeData } from "@/lib/fetch-nav"; -import { navTreeToBreadcrumbs } from "@/lib/nav-tree-to-breadcrumbs"; +import type { DocsPageData } from "@/lib/fetch-docs"; +import { DOCS_PAGES_ROOT_PATH, GITHUB_REPO_URL } from "@/lib/docs-config"; import { Pencil } from "lucide-react"; import s from "./DocsPage.module.css"; +import customMdxStyles from "@/components/custom-mdx/CustomMDX.module.css"; -// This is the location that we expect our docs mdx files to be located, -// relative to the root of the Next.js project. -export const DOCS_DIRECTORY = "./docs"; -const GITHUB_REPO_URL = "https://github.com/ghostty-org/website"; -// This is the URL path for all of our docs pages -export const DOCS_PAGES_ROOT_PATH = "/docs"; - -export async function getStaticPaths() { - const docsPageSlugs = await loadAllDocsPageSlugs(DOCS_DIRECTORY); - return { - paths: docsPageSlugs.map((slug: string): StaticPropsParams => { - return { - params: { - path: slug.split("/"), - }, - }; - }), - fallback: false, - }; -} - -interface StaticPropsParams { - params: { - path: Array; - }; -} - -export async function getStaticProps({ params: { path } }: StaticPropsParams) { - const activePageSlug = path.join("/"); - const navTreeData = await loadDocsNavTreeData(DOCS_DIRECTORY, activePageSlug); - const docsPageData = await loadDocsPage(DOCS_DIRECTORY, activePageSlug); - const breadcrumbs = navTreeToBreadcrumbs( - "Ghostty Docs", - DOCS_PAGES_ROOT_PATH, - navTreeData, - activePageSlug, - ); - return { - props: { - navTreeData, - docsPageData, - breadcrumbs, - }, - }; -} - -interface DocsPageProps { +interface DocsPageContentProps { navTreeData: NavTreeNode[]; docsPageData: DocsPageData; breadcrumbs: Breadcrumb[]; } -export default function DocsPage({ +export default function DocsPageContent({ navTreeData, docsPageData: { title, @@ -79,29 +28,15 @@ export default function DocsPage({ hideSidecar, }, breadcrumbs, -}: DocsPageProps) { +}: DocsPageContentProps) { // Calculate the "Edit in Github" link. If it's not provided // in the frontmatter, point to the website repo mdx file. - editOnGithubLink = editOnGithubLink + const resolvedEditOnGithubLink = editOnGithubLink ? editOnGithubLink : `${GITHUB_REPO_URL}/edit/main/${relativeFilePath}`; return ( - 1 - ? breadcrumbs - .slice(1) - .reverse() - .slice(0, 2) - .map((breadcrumb) => breadcrumb.text) - .join(" - ") - : breadcrumbs[0].text, - description, - }} - > +
@@ -130,10 +65,10 @@ export default function DocsPage({ {description}

- +
{content}

diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx new file mode 100644 index 00000000..8763071f --- /dev/null +++ b/src/app/docs/page.tsx @@ -0,0 +1,48 @@ +import type { Metadata } from "next"; +import { type DocsPageData, loadDocsPage } from "@/lib/fetch-docs"; +import { loadDocsNavTreeData } from "@/lib/fetch-nav"; +import { navTreeToBreadcrumbs } from "@/lib/nav-tree-to-breadcrumbs"; +import { docsMetadataTitle } from "@/lib/docs-metadata-title"; +import { DOCS_DIRECTORY, DOCS_PAGES_ROOT_PATH } from "@/lib/docs-config"; +import DocsPageContent from "./docs-page-content"; +import type { NavTreeNode } from "@/components/nav-tree"; +import type { Breadcrumb } from "@/components/breadcrumbs"; + +export const dynamic = "force-static"; + +async function loadDocsRouteData(activePageSlug: string): Promise<{ + navTreeData: NavTreeNode[]; + docsPageData: DocsPageData; + breadcrumbs: Breadcrumb[]; +}> { + const navTreeData = await loadDocsNavTreeData(DOCS_DIRECTORY, activePageSlug); + const docsPageData = await loadDocsPage(DOCS_DIRECTORY, activePageSlug); + const breadcrumbs = navTreeToBreadcrumbs( + "Ghostty Docs", + DOCS_PAGES_ROOT_PATH, + navTreeData, + activePageSlug, + ); + + return { navTreeData, docsPageData, breadcrumbs }; +} + +export async function generateMetadata(): Promise { + const { docsPageData, breadcrumbs } = await loadDocsRouteData("index"); + return { + title: docsMetadataTitle(breadcrumbs), + description: docsPageData.description, + }; +} + +export default async function DocsIndexPage() { + const { navTreeData, docsPageData, breadcrumbs } = + await loadDocsRouteData("index"); + return ( + + ); +} diff --git a/src/pages/download/DownloadPage.module.css b/src/app/download/DownloadPage.module.css similarity index 100% rename from src/pages/download/DownloadPage.module.css rename to src/app/download/DownloadPage.module.css diff --git a/src/pages/download/index.tsx b/src/app/download/page.tsx similarity index 57% rename from src/pages/download/index.tsx rename to src/app/download/page.tsx index 3d7d5ecd..76c49868 100644 --- a/src/pages/download/index.tsx +++ b/src/app/download/page.tsx @@ -1,45 +1,41 @@ +import type { Metadata } from "next"; +import Image from "next/image"; import type { NavTreeNode } from "@/components/nav-tree"; import SectionWrapper from "@/components/section-wrapper"; import { H1, P } from "@/components/text"; import NavFooterLayout from "@/layouts/nav-footer-layout"; import { fetchLatestGhosttyVersion } from "@/lib/fetch-latest-ghostty-version"; import { loadDocsNavTreeData } from "@/lib/fetch-nav"; -import Image from "next/image"; +import { DOCS_DIRECTORY } from "@/lib/docs-config"; import SVGIMG from "../../../public/ghostty-logo.svg"; -import { DOCS_DIRECTORY } from "../docs/[...path]"; +import ReleaseDownloadPage from "./release-download-page"; +import TipDownloadPage from "./tip-download-page"; import s from "./DownloadPage.module.css"; -import ReleaseDownloadPage from "./release"; -import TipDownloadPage from "./tip"; -export async function getStaticProps() { - return { - props: { - latestVersion: await fetchLatestGhosttyVersion(), - docsNavTree: await loadDocsNavTreeData(DOCS_DIRECTORY, ""), - }, - }; -} +export const dynamic = "force-static"; + +export const metadata: Metadata = { + title: "Download Ghostty", + description: + "Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.", +}; -export interface DownloadPageProps { +async function loadPageData(): Promise<{ latestVersion: string; docsNavTree: NavTreeNode[]; +}> { + return { + latestVersion: await fetchLatestGhosttyVersion(), + docsNavTree: await loadDocsNavTreeData(DOCS_DIRECTORY, ""), + }; } -export default function DownloadPage({ - latestVersion, - docsNavTree, -}: DownloadPageProps) { +export default async function DownloadPage() { + const { latestVersion, docsNavTree } = await loadPageData(); const isTip = process.env.GIT_COMMIT_REF === "tip"; return ( - +
@@ -57,15 +53,9 @@ export default function DownloadPage({ )}
{isTip ? ( - + ) : ( - + )}
diff --git a/src/pages/download/release.tsx b/src/app/download/release-download-page.tsx similarity index 94% rename from src/pages/download/release.tsx rename to src/app/download/release-download-page.tsx index 859cc971..f1016187 100644 --- a/src/pages/download/release.tsx +++ b/src/app/download/release-download-page.tsx @@ -2,12 +2,14 @@ import { ButtonLink } from "@/components/link"; import GenericCard from "@/components/generic-card"; import { CodeXml, Download, Package } from "lucide-react"; import s from "./DownloadPage.module.css"; -import type { DownloadPageProps } from "./index"; + +interface ReleaseDownloadPageProps { + latestVersion: string; +} export default function ReleaseDownloadPage({ latestVersion, - docsNavTree, -}: DownloadPageProps) { +}: ReleaseDownloadPageProps) { return (
{ + function updateSize() { + setWidth(window.innerWidth); + setHeight(window.innerHeight); + } + window.addEventListener("resize", updateSize); + updateSize(); + return () => window.removeEventListener("resize", updateSize); + }, []); + return [width, height]; +} + +export default function HomeClient({ terminalData }: HomeClientProps) { + const animationFrames = Object.keys(terminalData) + .filter((k) => { + return k.startsWith("home/animation_frames"); + }) + .map((k) => terminalData[k]); + + // Calculate what font size we should use based off of + // Width & Height considerations. We will pick the smaller + // of the two values. + const [windowWidth, windowHeight] = useWindowSize(); + const widthSize = + windowWidth > 1100 ? "small" : windowWidth > 674 ? "tiny" : "xtiny"; + const heightSize = + windowHeight > 900 ? "small" : windowHeight > 750 ? "tiny" : "xtiny"; + let fontSize: TerminalFontSize = "small"; + const sizePriority = ["xtiny", "tiny", "small"]; + for (const size of sizePriority) { + if (widthSize === size || heightSize === size) { + fontSize = size; + break; + } + } + + return ( +
+ {/* Don't render the content until the window width has been + calculated, else there will be a flash from the smallest size + of the terminal to the true calculated size */} + {windowWidth > 0 && ( + <> +
+ 950 ? 20 : windowWidth > 850 ? 10 : 0 + } + className={s.animatedTerminal} + columns={100} + rows={41} + frames={animationFrames} + frameLengthMs={31} + /> +
+ + +

+ Ghostty is a fast, feature-rich, and cross-platform terminal + emulator that uses platform-native UI and GPU acceleration. +

+
+ + + + + + + )} +
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 00000000..71ee878d --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,45 @@ +import RootLayout from "@/layouts/root-layout"; +import "@/styles/globals.css"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + metadataBase: new URL("https://ghostty.org"), + title: "Ghostty", + description: + "Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.", + openGraph: { + type: "website", + siteName: "Ghostty", + url: "https://ghostty.org", + images: [ + { + url: "/social-share-card.jpg", + width: 1800, + height: 3200, + }, + ], + }, + twitter: { + images: ["https://ghostty.org/social-share-card.jpg"], + }, + icons: { + icon: [ + { url: "/favicon-32.png", sizes: "32x32", type: "image/png" }, + { url: "/favicon-16.png", sizes: "16x16", type: "image/png" }, + ], + shortcut: "/favicon.ico", + }, + other: { + "darkreader-lock": "", + }, +}; + +export default function AppLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/src/pages/404.tsx b/src/app/not-found.tsx similarity index 53% rename from src/pages/404.tsx rename to src/app/not-found.tsx index de62be26..b15ff275 100644 --- a/src/pages/404.tsx +++ b/src/app/not-found.tsx @@ -1,33 +1,24 @@ -import s from "./404Page.module.css"; +import Image from "next/image"; +import type { Metadata } from "next"; import NavFooterLayout from "@/layouts/nav-footer-layout"; -import { loadDocsNavTreeData } from "@/lib/fetch-nav"; -import { DOCS_DIRECTORY } from "./docs/[...path]"; -import type { NavTreeNode } from "@/components/nav-tree"; import { H2, P } from "@/components/text"; -import Image from "next/image"; +import { loadDocsNavTreeData } from "@/lib/fetch-nav"; +import { DOCS_DIRECTORY } from "@/lib/docs-config"; +import s from "./404Page.module.css"; -export async function getStaticProps() { - return { - props: { - docsNavTree: await loadDocsNavTreeData(DOCS_DIRECTORY, ""), - }, - }; -} +export const dynamic = "force-static"; -interface NotFoundProps { - docsNavTree: NavTreeNode[]; -} +export const metadata: Metadata = { + title: "Page not found | Ghostty", + description: + "Oops! We couldn’t find what you were looking for. Try browsing our docs or visit our download page.", +}; + +export default async function NotFoundPage() { + const docsNavTree = await loadDocsNavTreeData(DOCS_DIRECTORY, ""); -export default function NotFound({ docsNavTree }: NotFoundProps) { return ( - +

This page could not be found.

diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 00000000..accff367 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,16 @@ +import { loadAllTerminalFiles } from "@/lib/fetch-terminal-content"; +import type { Metadata } from "next"; +import HomeClient from "./home-client"; + +export const dynamic = "force-static"; + +export const metadata: Metadata = { + title: "Ghostty", + description: + "Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.", +}; + +export default async function HomePage() { + const terminalData = await loadAllTerminalFiles("/home"); + return ; +} diff --git a/src/components/animated-terminal/index.tsx b/src/components/animated-terminal/index.tsx index 0e582e52..d4216234 100644 --- a/src/components/animated-terminal/index.tsx +++ b/src/components/animated-terminal/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useEffect, useState } from "react"; import Terminal, { type TerminalProps } from "../terminal"; diff --git a/src/components/codeblock/index.tsx b/src/components/codeblock/index.tsx index 2b87a4ba..8c860705 100644 --- a/src/components/codeblock/index.tsx +++ b/src/components/codeblock/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import classNames from "classnames"; import { jetbrainsMono } from "../text"; import s from "./CodeBlock.module.css"; diff --git a/src/components/custom-mdx/index.tsx b/src/components/custom-mdx/index.tsx index 6c366ced..b3a6e63c 100644 --- a/src/components/custom-mdx/index.tsx +++ b/src/components/custom-mdx/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import { MDXRemote, type MDXRemoteSerializeResult } from "next-mdx-remote"; import { isValidElement, useEffect, type ReactElement } from "react"; import Blockquote from "../blockquote"; diff --git a/src/components/jumplink-header/index.tsx b/src/components/jumplink-header/index.tsx index ff71a24a..495df699 100644 --- a/src/components/jumplink-header/index.tsx +++ b/src/components/jumplink-header/index.tsx @@ -1,16 +1,19 @@ +"use client"; + import classNames from "classnames"; import { Link } from "lucide-react"; import slugify from "slugify"; import Text from "../text"; import s from "./JumplinkHeader.module.css"; import { useInView } from "react-intersection-observer"; -import { isValidElement, useEffect, useState } from "react"; +import { isValidElement, useEffect } from "react"; import { useStore } from "@/lib/use-store"; interface JumplinkHeaderProps { as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; className?: string; children?: React.ReactNode; + id?: string; "data-index"?: string; } @@ -18,9 +21,10 @@ export default function JumplinkHeader({ className, children, as, + id: providedID, "data-index": dataIndex, }: JumplinkHeaderProps) { - const id = headerDeeplinkIdentifier(children, dataIndex); + const id = providedID ?? headerDeeplinkIdentifier(children, dataIndex); const { ref, inView } = useInView({ // This is our header height! This also impacts our // margin below, but TBH I actually like it needing to diff --git a/src/components/nav-tree/index.tsx b/src/components/nav-tree/index.tsx index 802054f0..f3e15399 100644 --- a/src/components/nav-tree/index.tsx +++ b/src/components/nav-tree/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import classNames from "classnames"; import { ChevronDown } from "lucide-react"; import { useState } from "react"; diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx index 95b99e16..03c97874 100644 --- a/src/components/navbar/index.tsx +++ b/src/components/navbar/index.tsx @@ -1,4 +1,5 @@ -import { DOCS_PAGES_ROOT_PATH } from "@/pages/docs/[...path]"; +"use client"; + import classNames from "classnames"; import Image from "next/image"; import NextLink from "next/link"; @@ -13,7 +14,8 @@ import NavTree, { } from "../nav-tree"; import GhosttyWordmark from "./ghostty-wordmark.svg"; import s from "./Navbar.module.css"; -import { useRouter } from "next/router"; + +const DOCS_PAGES_ROOT_PATH = "/docs"; export interface NavbarProps { className?: string; @@ -31,7 +33,6 @@ export default function Navbar({ docsNavTree, }: NavbarProps) { const pathname = usePathname(); - const router = useRouter(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const mobileContentRef = useRef(null); const activeItemRef = useRef(null); @@ -75,19 +76,12 @@ export default function Navbar({ } }, [mobileMenuOpen]); - /* Instead of closing the menu with the NavTree's onNavLinkClicked prop, - * we'll close it when the route changes. This avoids the annoying flicker - * between the old and new pages when the menu closes. */ + // Close the mobile menu when navigation changes the pathname. useEffect(() => { - const handleRouteChangeComplete = () => { + if (mobileMenuOpen) { setMobileMenuOpen(false); - }; - - router.events.on("routeChangeComplete", handleRouteChangeComplete); - return () => { - router.events.off("routeChangeComplete", handleRouteChangeComplete); - }; - }, [router]); + } + }, [pathname, mobileMenuOpen]); return (
); } diff --git a/src/app/download/page.tsx b/src/app/download/page.tsx index 76c49868..6cff795b 100644 --- a/src/app/download/page.tsx +++ b/src/app/download/page.tsx @@ -1,12 +1,8 @@ import type { Metadata } from "next"; import Image from "next/image"; -import type { NavTreeNode } from "@/components/nav-tree"; import SectionWrapper from "@/components/section-wrapper"; import { H1, P } from "@/components/text"; -import NavFooterLayout from "@/layouts/nav-footer-layout"; import { fetchLatestGhosttyVersion } from "@/lib/fetch-latest-ghostty-version"; -import { loadDocsNavTreeData } from "@/lib/fetch-nav"; -import { DOCS_DIRECTORY } from "@/lib/docs-config"; import SVGIMG from "../../../public/ghostty-logo.svg"; import ReleaseDownloadPage from "./release-download-page"; import TipDownloadPage from "./tip-download-page"; @@ -22,43 +18,39 @@ export const metadata: Metadata = { async function loadPageData(): Promise<{ latestVersion: string; - docsNavTree: NavTreeNode[]; }> { return { latestVersion: await fetchLatestGhosttyVersion(), - docsNavTree: await loadDocsNavTreeData(DOCS_DIRECTORY, ""), }; } export default async function DownloadPage() { - const { latestVersion, docsNavTree } = await loadPageData(); + const { latestVersion } = await loadPageData(); const isTip = process.env.GIT_COMMIT_REF === "tip"; return ( - -
- -
- {""} -

Download Ghostty

- {!isTip && ( -

- Version {latestVersion} -{" "} - - Release Notes - -

- )} -
- {isTip ? ( - - ) : ( - +
+ +
+ {""} +

Download Ghostty

+ {!isTip && ( +

+ Version {latestVersion} -{" "} + + Release Notes + +

)} - -
- +
+ {isTip ? ( + + ) : ( + + )} + + ); } diff --git a/src/layouts/root-layout/RootLayout.module.css b/src/app/layout.module.css similarity index 85% rename from src/layouts/root-layout/RootLayout.module.css rename to src/app/layout.module.css index c5062b17..d01c2385 100644 --- a/src/layouts/root-layout/RootLayout.module.css +++ b/src/app/layout.module.css @@ -2,14 +2,14 @@ font-family: var(--pretendard-std-variable); & a { - color: var(--gray-9); + color: var(--gray-9); } & table { --color-border: var(--gray-2); --color-header-border: var(--gray-3); --color-fg: var(--gray-5); - --color-header-fg:var(--gray-7); + --color-header-fg: var(--gray-7); display: block; table-layout: auto; @@ -17,8 +17,9 @@ margin: 32px 0; overflow: auto; - & th, td { - color: var(--color-fg); + & th, + td { + color: var(--color-fg); text-align: start; padding: 10px 16px 10px 2px; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 71ee878d..f635059f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,35 @@ -import RootLayout from "@/layouts/root-layout"; +import Footer from "@/components/footer"; +import PathnameFilter from "@/components/pathname-filter"; +import type { SimpleLink } from "@/components/link"; +import Navbar from "@/components/navbar"; +import PreviewBanner from "@/components/preview-banner"; +import { jetbrainsMono, pretendardStdVariable } from "@/components/text"; +import { DOCS_DIRECTORY } from "@/lib/docs-config"; +import { loadDocsNavTreeData } from "@/lib/fetch-nav"; import "@/styles/globals.css"; +import classNames from "classnames"; import type { Metadata } from "next"; +import s from "./layout.module.css"; + +// Navigation links for our nav bars. This currently applies to both +// the sidebar and footer links equally. +const navLinks: Array = [ + { + text: "Docs", + href: "/docs", + }, + { + text: "Discord", + href: "https://discord.gg/ghostty", + }, + { + text: "GitHub", + href: "https://github.com/ghostty-org/ghostty", + }, +]; + +// The paths that don't have the navbar/footer "chrome". +const NO_CHROME_PATHS = ["/"]; export const metadata: Metadata = { metadataBase: new URL("https://ghostty.org"), @@ -34,11 +63,49 @@ export const metadata: Metadata = { }, }; -export default function AppLayout({ children }: { children: React.ReactNode }) { +export default async function AppLayout({ + children, +}: { + children: React.ReactNode; +}) { + // Load the docs tree once at the root so navbar/mobile docs navigation + // can be rendered across the site. + const docsNavTree = await loadDocsNavTreeData(DOCS_DIRECTORY, ""); + const currentYear = new Date().getFullYear(); + return ( - - {children} + + + + + + {children} + +