From 05ef06f95c02fe60a00075ca4c4973d17e373a9f Mon Sep 17 00:00:00 2001
From: Martin Berg Alstad <git@martials.no>
Date: Sat, 15 Mar 2025 15:34:24 +0100
Subject: [PATCH] :sparkles: Breadcrumbs with navigation

---
 TODO.md                         |  2 +-
 src/components/Breadcrumb.astro | 38 +++++++++++++++++++++++++++++++++
 src/layouts/Layout.astro        |  4 ++--
 src/utils/linking.ts            | 21 ++++++++++++++++++
 4 files changed, 62 insertions(+), 3 deletions(-)
 create mode 100644 src/components/Breadcrumb.astro

diff --git a/TODO.md b/TODO.md
index e4cf9cd..418b854 100644
--- a/TODO.md
+++ b/TODO.md
@@ -13,7 +13,7 @@
 
 ## Layout
 - [ ] Dark mode toggle
-- [ ] Navigate using pathname / breadcrumbs
+- [x] Navigate using pathname / breadcrumbs
 - [ ] Better style for \<code /> blocks
 
 ## Accessibility
diff --git a/src/components/Breadcrumb.astro b/src/components/Breadcrumb.astro
new file mode 100644
index 0000000..19ab7c6
--- /dev/null
+++ b/src/components/Breadcrumb.astro
@@ -0,0 +1,38 @@
+---
+import { type NavLink, resolvePathname } from "@/utils/linking"
+import LocaleLink from "@/components/links/LocaleLink.astro"
+
+const pathname = resolvePathname(Astro.originPathname)
+
+let paths: string[]
+if (pathname === "/") {
+  paths = ["~"]
+} else {
+  paths = ["~", ...pathname.split("/").slice(1)]
+}
+
+function getLink(path: string): NavLink {
+  switch (path) {
+    case "~":
+      return "/"
+    default:
+      return `/${path}` as NavLink
+  }
+}
+---
+
+<div>
+  {
+    paths.map((path, index) => (
+      <span>
+        {index != paths.length - 1 ? (
+          <span>
+            <LocaleLink to={getLink(path)}>{path}</LocaleLink>/
+          </span>
+        ) : (
+          path
+        )}
+      </span>
+    ))
+  }
+</div>
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index 0f1a302..285e2e9 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -1,8 +1,8 @@
 ---
 import Footer from "@/components/Footer.astro"
 import Header from "@/components/header/Header.astro"
+import Breadcrumb from "@/components/Breadcrumb.astro"
 import { languageTag } from "@/paraglide/runtime"
-import { resolvePathname } from "@/utils/linking"
 
 interface Props {
   title: string
@@ -33,7 +33,7 @@ const mainClass =
     <Header />
     <main class:list={[mainClass, clazz]}>
       <h1 class="text-center not-sm:hidden">
-        ~{resolvePathname(Astro.originPathname)}
+        <Breadcrumb />
       </h1>
       <div class="my-5">
         <slot />
diff --git a/src/utils/linking.ts b/src/utils/linking.ts
index aac1a06..0f8ddaf 100644
--- a/src/utils/linking.ts
+++ b/src/utils/linking.ts
@@ -22,6 +22,8 @@ const paths: Set<NavLink> = new Set([
   "/uses",
 ])
 
+const projectPaths: Set<string> = new Set<string>(["homepage", "sb1budget"])
+
 /**
  * Defines the localized pathnames for the site.
  * The key must be used to navigate to the correct path.
@@ -63,3 +65,22 @@ export function resolvePathname(pathname: string): AbsolutePathname {
   }
   return pathname as AbsolutePathname
 }
+
+export function isAbsolutePathname(path: string): path is AbsolutePathname {
+  return path.startsWith("/")
+}
+
+export function isNavLink(path: string): path is NavLink {
+  if (path.startsWith("/en")) {
+    path = path.slice(2)
+  }
+  if (paths.has(path as NavLink)) {
+    return true
+  }
+  const pathSplit = path.split("/").slice(1)
+  return (
+    pathSplit.length === 2 &&
+    pathSplit[0] === "projects" &&
+    projectPaths.has(pathSplit[1])
+  )
+}