refactor(docs): use shared @unom/app-ui/footer component
apple / swift (push) Successful in 1m2s
android / android (push) Successful in 4m23s
deb / build-publish (push) Successful in 2m30s
decky / build-publish (push) Successful in 13s
ci / rust (push) Successful in 4m47s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 58s
apple / screenshots (push) Successful in 5m16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
ci / bench (push) Successful in 4m39s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m29s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m23s
docker / deploy-docs (push) Successful in 18s

The docs footer was a hand-maintained mirror of the marketing site's. Both now
render the same @unom/app-ui/footer component, so they stay in sync. The shared
view themes itself through @unom/style tokens (which the docs already map onto
their Fumadocs surfaces), and a resolveHref hook rebases root-relative links
onto the marketing-site origin. Footer types now come from the library too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
enricobuehler
2026-06-28 14:34:45 +00:00
parent 30d0d36efe
commit 1bd60ffb34
5 changed files with 31 additions and 60 deletions
+15 -39
View File
@@ -1,51 +1,27 @@
import { getRouteApi } from '@tanstack/react-router'
import type { NavigationLink, NavigationSection } from '@/lib/cms'
import { FooterView } from '@unom/app-ui/footer'
const rootApi = getRouteApi('__root__')
// The docs share the marketing site's footer (same CMS global). Root-relative
// links target the website, so resolve them against its origin — the docs don't
// host /legal/* etc. themselves. Mirrors the website Footer, themed for docs.
// Footer markup is shared with the marketing site via @unom/app-ui so the two
// stay in sync. It themes itself through @unom/style tokens, which the docs map
// onto their Fumadocs surfaces. Root-relative links target the website (the
// docs don't host /legal/* etc.), so rebase them onto its origin.
const SITE_URL = 'https://punktfunk.unom.io'
const resolve = (to?: string | null) =>
to ? (to.startsWith('/') ? `${SITE_URL}${to}` : to) : '#'
const resolveHref = (to: string) =>
to.startsWith('/') ? `${SITE_URL}${to}` : to
export default function Footer() {
const { footer } = rootApi.useLoaderData()
const sections: NavigationSection[] = footer?.sections ?? []
const tagline = footer?.tagline?.trim()
if (!sections.length && !tagline) return null
return (
<footer className="border-t border-fd-border bg-fd-card">
<div className="mx-auto flex w-full max-w-6xl flex-row flex-wrap gap-12 px-4 py-12 sm:px-6">
{sections.map((group, gi) => (
<div key={group.id ?? gi}>
{group.title && (
<h3 className="mb-2 text-sm font-semibold text-fd-foreground">
{group.title}
</h3>
)}
<div className="flex flex-col gap-1">
{(group.entries ?? []).map((item: NavigationLink, i) => (
<a
key={item.id ?? `${item.to}-${i}`}
href={resolve(item.to)}
className="text-sm text-fd-muted-foreground transition-colors hover:text-fd-foreground"
>
{item.label}
</a>
))}
</div>
</div>
))}
{tagline && (
<p className="ml-auto self-end text-sm text-fd-muted-foreground">
{tagline}
</p>
)}
</div>
</footer>
<FooterView
sections={footer?.sections}
tagline={footer?.tagline}
socials={footer?.socials}
socialsLabel="Socials"
resolveHref={resolveHref}
className="border-t border-fd-border"
/>
)
}
+9 -21
View File
@@ -1,33 +1,21 @@
// The docs reuse the punktfunk footer from the shared unom CMS (cms.unom.io).
// The CMS is multi-tenant: footer is a per-tenant collection, so scope the read
// to this project's tenant. Read-only GET, so a plain typed fetch rather than
// pulling in the Payload SDK + generated types.
// The footer shape comes from @unom/app-ui/footer so the docs and the marketing
// site share one type. The CMS is multi-tenant: footer is a per-tenant
// collection, so scope the read to this project's tenant. Read-only GET, so a
// plain typed fetch rather than pulling in the Payload SDK + generated types.
import type { FooterData } from '@unom/app-ui/footer'
const CMS_URL = 'https://cms.unom.io'
// This project's tenant in the shared CMS.
const TENANT = 'punktfunk'
export interface NavigationLink {
id?: string | null
label?: string | null
to?: string | null
}
export type { FooterData as Footer } from '@unom/app-ui/footer'
export interface NavigationSection {
id?: string | null
title?: string | null
entries?: NavigationLink[] | null
}
export interface Footer {
tagline?: string | null
sections?: NavigationSection[] | null
}
export async function findFooter(): Promise<Footer | null> {
export async function findFooter(): Promise<FooterData | null> {
const query = `where%5Btenant.slug%5D%5Bequals%5D=${TENANT}&locale=en&depth=1&limit=1`
const res = await fetch(`${CMS_URL}/api/footers?${query}`)
if (!res.ok) throw new Error(`CMS footer request failed: ${res.status}`)
const data = (await res.json()) as { docs?: Footer[] }
const data = (await res.json()) as { docs?: FooterData[] }
return data.docs?.[0] ?? null
}
+1
View File
@@ -7,6 +7,7 @@
design-token system the punktfunk marketing site also builds on. */
@source '../../node_modules/fumadocs-ui/dist/**/*.js';
@source '../../node_modules/@unom/ui/dist/**/*.{js,mjs}';
@source '../../node_modules/@unom/app-ui/dist/**/*.{js,mjs}';
/* ── punktfunk brand ────────────────────────────────────────────────────────
The brand colour is the violet lens mark. (The marketing site's blue is just