refactor(docs): share site header and footer
This commit is contained in:
@@ -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> </span>
|
||||
<span>R</span>
|
||||
<span className="text-slate-300 dark:text-slate-400">eact</span>
|
||||
<span> </span>
|
||||
<span>A</span>
|
||||
<span className="text-slate-300 dark:text-slate-400">pp</span>
|
||||
<span> </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
@@ -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
@@ -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> </span>
|
||||
<span>R</span>
|
||||
<span className="text-slate-300 dark:text-slate-400">eact</span>
|
||||
<span> </span>
|
||||
<span>A</span>
|
||||
<span className="text-slate-300 dark:text-slate-400">pp</span>
|
||||
<span> </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
@@ -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 />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user