refactor(docs): share site header and footer

This commit is contained in:
Maofeng
2026-09-21 16:12:12 +08:00
parent 6711401f5f
commit df5bbc739c
4 changed files with 138 additions and 54 deletions
+104 -4
View File
@@ -1,9 +1,10 @@
import { Icon } from "@icones/react"
import { cn } from "../lib/utils"
import { useTranslate } from "../lib/locale"
import { useLocale, useTranslate } from "../lib/locale"
import React from "react"
import { SearchTrigger } from "./search-trigger"
import { SettingsLink } from "./settings-link"
import { getDocsHomeHref } from "../content/registry"
export const rootCSSVariables = {
"--cw": "60rem",
@@ -83,11 +84,110 @@ export function SlashLine({ className }: { className?: string }) {
)
}
export function Actions({ className }: { className?: string }) {
export function Actions() {
return (
<div className={cn("inline-flex items-center gap-2", className)}>
<>
<SearchTrigger />
<SettingsLink />
</div>
</>
)
}
export function SiteHeader({
banner,
bottom,
leading,
subleading,
actions,
locale,
...rest
}: React.ComponentProps<"header"> & {
banner?: React.ReactNode
bottom?: React.ReactNode
leading?: React.ReactNode
subleading?: React.ReactNode
actions?: React.ReactNode
locale?: string
}) {
return (
<header {...rest}>
{banner}
<HeaderInner
leading={leading}
subleading={subleading}
locale={locale}
actions={actions}
/>
{bottom}
</header>
)
}
function HeaderInner({
leading,
subleading,
actions,
locale,
}: {
leading?: React.ReactNode
subleading?: React.ReactNode
actions?: React.ReactNode
locale?: string
}) {
const resolvedLocale = useLocale() ?? locale
const hasLeading = React.Children.count(leading) > 0
const hasSubleading = React.Children.count(subleading) > 0
const children = []
if (hasLeading || hasSubleading) {
children.push(
hasLeading ? (
leading
) : hasSubleading ? (
<HomeButton href={getDocsHomeHref(resolvedLocale)} />
) : null,
hasSubleading && <SlashLine />,
subleading
)
} else {
children.push(
<h1 className="min-w-0 text-2xl font-semibold sm:text-3xl">
<span>S</span>
<span className="text-slate-300 dark:text-slate-400">imple</span>
<span>&nbsp;</span>
<span>R</span>
<span className="text-slate-300 dark:text-slate-400">eact</span>
<span>&nbsp;</span>
<span>A</span>
<span className="text-slate-300 dark:text-slate-400">pp</span>
<span>&nbsp;</span>
<span>K</span>
<span className="text-slate-300 dark:text-slate-400">it</span>
</h1>
)
}
if (actions != null) {
children.push(
<div className="-mr-2 ml-auto inline-flex items-center gap-2">
{actions}
</div>
)
}
return (
<Container className="flex size-full items-center gap-4">
{children}
</Container>
)
}
export function SiteFooter({ className, ...rest }: React.ComponentProps<"footer">) {
return (
<footer className={cn("py-8", className)} {...rest}>
<Container className="text-center text-slate-500 dark:text-slate-400">
<div>Copyright © 2026</div>
</Container>
</footer>
)
}
+10 -16
View File
@@ -5,11 +5,14 @@ import {
getDocHref,
getDocPackages,
getDocSectionLabel,
getDocsHomeHref,
resolveDocLocation,
} from "../content/registry"
import type { DocLocation, DocPage, DocTocItem } from "../content/types"
import { Actions, Container, HomeButton, SlashLine } from "../components/layout"
import {
Actions,
SiteFooter,
SiteHeader,
} from "../components/layout"
import { mdxComponents } from "../components/mdx"
import { useLocale, useTranslate } from "../lib/locale"
@@ -113,17 +116,12 @@ export default function ArticlePage({ pathname }: { pathname: string }) {
return (
<>
<header
<SiteHeader
data-article-sticky-header
className="z-10 h-16 border-b bg-white/70 backdrop-blur-sm md:sticky md:top-0 dark:border-slate-800 dark:bg-slate-950/80"
>
<Container className="flex size-full items-center gap-4">
<HomeButton href={getDocsHomeHref(docLocation.locale)} />
<SlashLine />
<h1 className="truncate">{docLocation.package.label}</h1>
<Actions className="ml-auto" />
</Container>
</header>
subleading={<h1 className="truncate">{docLocation.package.label}</h1>}
actions={<Actions />}
/>
<section
data-article-sticky-header
@@ -248,11 +246,7 @@ export default function ArticlePage({ pathname }: { pathname: string }) {
</div>
</div>
</div>
<footer className="border-t py-8 text-slate-500 dark:border-slate-800 dark:text-slate-400">
<Container className="flex h-full w-full items-center">
Copyright © 2026
</Container>
</footer>
<SiteFooter className="border-t py-8 text-slate-500 dark:border-slate-800 dark:text-slate-400" />
</>
)
}
+11 -23
View File
@@ -1,4 +1,9 @@
import { Actions, Container } from "../components/layout"
import {
Actions,
Container,
SiteFooter,
SiteHeader,
} from "../components/layout"
import { getDocHref, getDocPackages } from "../content/registry"
import { useLocale, useTranslate } from "../lib/locale"
@@ -9,23 +14,10 @@ export default function IndexPage() {
return (
<>
<Container className="flex items-center justify-between gap-4 pt-8 pb-10 sm:pt-12 sm:pb-14 lg:pb-16">
<h1 className="min-w-0 text-2xl font-semibold sm:text-3xl">
<span>S</span>
<span className="text-slate-300 dark:text-slate-400">imple</span>
<span>&nbsp;</span>
<span>R</span>
<span className="text-slate-300 dark:text-slate-400">eact</span>
<span>&nbsp;</span>
<span>A</span>
<span className="text-slate-300 dark:text-slate-400">pp</span>
<span>&nbsp;</span>
<span>K</span>
<span className="text-slate-300 dark:text-slate-400">it</span>
</h1>
<span className="text-xs align-bottom">alpha</span>
<Actions className="ml-auto" />
</Container>
<SiteHeader
className="pt-8 pb-10 sm:pt-12 sm:pb-14 lg:pb-16"
actions={<Actions />}
/>
<Container
className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 lg:gap-8"
aria-label={t({ zh: "文档导航", us: "Documentation" })}
@@ -50,11 +42,7 @@ export default function IndexPage() {
</a>
))}
</Container>
<footer className="py-8 sm:py-12">
<Container className="text-center text-slate-500 dark:text-slate-400">
<div>Copyright © 2026</div>
</Container>
</footer>
<SiteFooter className="sm:py-12" />
</>
)
}
+13 -11
View File
@@ -1,7 +1,11 @@
import { Icon } from "@icones/react"
import { usePreference } from "@workspace/preferences"
import { BackButton, Container, SlashLine } from "../components/layout"
import {
Container,
SiteFooter,
SiteHeader,
} from "../components/layout"
import { LocaleFlag } from "../components/locale-flag"
import { SearchTrigger } from "../components/search-trigger"
import { Swatch } from "../components/swatch"
@@ -80,19 +84,16 @@ export default function SettingsPage() {
return (
<>
<header className="border-b bg-white/70 backdrop-blur-sm dark:border-slate-800 dark:bg-slate-950/80">
<Container className="flex h-16 items-center gap-4">
<BackButton />
<SlashLine />
<SiteHeader
subleading={
<h1 className="text-xl font-semibold">{t(messages.settings)}</h1>
<div className="ml-auto">
<SearchTrigger />
</div>
</Container>
</header>
}
className="h-16 border-b bg-white/70 backdrop-blur-sm dark:border-slate-800 dark:bg-slate-950/80"
actions={<SearchTrigger />}
/>
<main>
<Container className="py-8 sm:py-12">
<Container className="pt-8 sm:pt-12">
<section aria-labelledby="appearance-heading">
<h2 id="appearance-heading" className="mb-4 text-xl font-semibold">
{t(messages.appearance)}
@@ -271,6 +272,7 @@ export default function SettingsPage() {
</section>
</Container>
</main>
<SiteFooter />
</>
)
}