Skip to content

Commit 6922d43

Browse files
committed
wip
1 parent 362b8b2 commit 6922d43

File tree

4 files changed

+190
-164
lines changed

4 files changed

+190
-164
lines changed

src/app/conf/_components/footer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { SocialIcons } from "./social-icons"
22
import NextLink from "next/link"
33
import { ReactNode } from "react"
44
import { clsx } from "clsx"
5+
56
import { Badge } from "./badge"
67

78
export function Footer({

src/components/footer/index.tsx

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import NextLink from "next/link"
2+
import { useConfig, useThemeConfig } from "nextra-theme-docs"
3+
4+
import { GraphQLWordmarkLogo } from "@/icons"
5+
import { SocialIcons } from "@/app/conf/_components/social-icons"
6+
import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration"
7+
import blurBean from "@/app/conf/2025/components/footer/blur-bean.webp"
8+
9+
import { renderComponent } from "../utils"
10+
import { Anchor } from "@/app/conf/_design-system/anchor"
11+
12+
interface FooterLink {
13+
title: string
14+
route: string
15+
}
16+
17+
interface FooterSection {
18+
title?: string
19+
route?: string
20+
links: FooterLink[]
21+
}
22+
23+
const FOOTER_SECTIONS_COUNT = 4
24+
const MAX_LINKS_PER_SECTION = 5
25+
26+
function useFooterSections(): FooterSection[] {
27+
const { normalizePagesResult } = useConfig()
28+
29+
const sections: FooterSection[] = []
30+
const singleLinks: FooterLink[] = []
31+
32+
for (const item of normalizePagesResult.topLevelNavbarItems) {
33+
if (
34+
(item.type === "page" || item.type === "menu") &&
35+
item.children?.length &&
36+
sections.length < FOOTER_SECTIONS_COUNT - 1
37+
) {
38+
sections.push({
39+
title: item.title,
40+
route: item.route,
41+
links: (item.children || [])
42+
.slice(0, MAX_LINKS_PER_SECTION)
43+
.map(child => ({ title: child.title, route: child.route })),
44+
})
45+
} else if (singleLinks.length < MAX_LINKS_PER_SECTION) {
46+
if (!item.route?.startsWith("/conf/")) {
47+
singleLinks.push({ title: item.title, route: item.route })
48+
}
49+
}
50+
}
51+
52+
sections.push({ links: singleLinks })
53+
return sections
54+
}
55+
56+
export function Footer() {
57+
const sections = useFooterSections()
58+
const themeConfig = useThemeConfig()
59+
60+
// const footerSections: FooterSection[] =
61+
// normalizePagesResult.topLevelNavbarItems
62+
// .filter(item => item.type === "page" || item.type === "menu")
63+
// .filter(item => item.children)
64+
// .map(item => {
65+
// return {
66+
// title: item.title,
67+
// route: item.route,
68+
// links: (item.children || [])
69+
// .filter(
70+
// child =>
71+
// child.type === "doc" &&
72+
// child.route &&
73+
// !child.name.startsWith("--"),
74+
// )
75+
// .slice(0, MAX_LINKS_PER_SECTION)
76+
// .map(child => ({
77+
// title: child.title || child.name,
78+
// route: child.route,
79+
// })),
80+
// }
81+
// })
82+
83+
return (
84+
<footer className="typography-menu relative !bg-neu-100 text-neu-900 dark:!bg-neu-0 max-md:px-0 max-md:pt-0">
85+
<Stripes />
86+
87+
<div className="mx-auto max-w-[120rem] border-neu-400 dark:border-neu-100 3xl:border-x">
88+
{/* TODO: Move themeSwitch to the bottom section and remove padding. */}
89+
{/* Top section with logo and theme switch */}
90+
<div className="flex flex-wrap justify-between gap-4 p-4 max-md:w-full md:p-6 2xl:px-10">
91+
<NextLink href="/" className="nextra-logo flex items-center">
92+
<GraphQLWordmarkLogo className="h-6" title="GraphQL" />
93+
</NextLink>
94+
{themeConfig.darkMode && (
95+
<div className="flex items-center">
96+
{renderComponent(themeConfig.themeSwitch.component)}
97+
</div>
98+
)}
99+
</div>
100+
101+
{/* Navigation grid */}
102+
<ul className="grid grid-cols-2 gap-px bg-neu-400 py-px dark:bg-neu-100 lg:grid-cols-4">
103+
{sections.map((section, i) => (
104+
<li className="bg-neu-100 dark:bg-neu-0" key={i}>
105+
<div className="relative flex flex-col px-4 py-8 3xl:px-6 3xl:py-10">
106+
{section.title && (
107+
<h3 className="block h-full font-bold lg:mb-4 3xl:mb-10">
108+
{section.route ? (
109+
<Anchor
110+
className="gql-focus-visible block p-3 underline-offset-4 hover:underline"
111+
href={section.route}
112+
>
113+
{section.title}
114+
</Anchor>
115+
) : (
116+
<span className="block p-3">{section.title}</span>
117+
)}
118+
</h3>
119+
)}
120+
{section.links.map(link => (
121+
<Anchor
122+
key={link.route}
123+
href={link.route}
124+
className="gql-focus-visible block h-full p-3 underline-offset-4 hover:underline"
125+
>
126+
{link.title}
127+
</Anchor>
128+
))}
129+
</div>
130+
</li>
131+
))}
132+
</ul>
133+
134+
<div className="relative flex items-center justify-between gap-10 p-4 max-lg:flex-col md:px-6 2xl:px-10">
135+
<div className="flex flex-col font-light max-md:gap-5">
136+
<p>{renderComponent(themeConfig.footer.content)}</p>
137+
</div>
138+
<SocialIcons className="[&>a:focus]:text-current [&>a:focus]:ring-transparent [&>a:hover]:bg-neu-900/10 [&>a:hover]:text-current" />
139+
</div>
140+
</div>
141+
</footer>
142+
)
143+
}
144+
145+
function Stripes() {
146+
return (
147+
<div
148+
role="presentation"
149+
// prettier-ignore
150+
// false positive
151+
// eslint-disable-next-line tailwindcss/no-contradicting-classname
152+
className="pointer-events-none absolute inset-0
153+
[--start-1:rgba(255,204,239,.05)]
154+
[--end-1:hsl(var(--color-pri-base)/.8)]
155+
dark:[--start-1:hsl(var(--color-pri-darker))]
156+
dark:[--end-1:hsl(var(--color-pri-base)/.9)]
157+
158+
[--start-2:transparent]
159+
[--end-2:hsl(var(--color-pri-base)/.6)]
160+
dark:[--start-2:rgba(255,204,239,.1)]
161+
dark:[--end-2:hsl(var(--color-pri-base)/.8)]
162+
163+
mix-blend-darken
164+
dark:mix-blend-lighten
165+
"
166+
style={{
167+
maskImage: `url(${blurBean.src})`,
168+
WebkitMaskImage: `url(${blurBean.src})`,
169+
maskPosition: "center 260px",
170+
WebkitMaskPosition: "center 260px",
171+
maskSize: "200% 100%",
172+
WebkitMaskSize: "200% 100%",
173+
maskRepeat: "no-repeat",
174+
WebkitMaskRepeat: "no-repeat",
175+
maskOrigin: "top",
176+
}}
177+
>
178+
<StripesDecoration
179+
evenClassName="bg-[linear-gradient(180deg,var(--start-1)_0%,var(--end-1)_200%)]"
180+
oddClassName="bg-[linear-gradient(180deg,var(--start-2)_0%,var(--end-2)_200%)]"
181+
/>
182+
</div>
183+
)
184+
}

src/components/navbar/navbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export function Navbar({ items }: NavBarProps): ReactElement {
107107
)}
108108
>
109109
<BackdropBlur />
110-
<nav className="mx-auto flex h-[var(--nextra-navbar-height)] max-w-[90rem] items-center justify-end pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)]">
110+
<nav className="mx-auto flex h-[var(--nextra-navbar-height)] max-w-[120rem] items-center justify-end pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)]">
111111
{themeConfig.logoLink ? (
112112
<NextLink
113113
href={

theme.config.tsx

Lines changed: 4 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,15 @@
11
import { DocsThemeConfig, ThemeSwitch, useConfig } from "nextra-theme-docs"
2-
import NextLink from "next/link"
32

43
import { Navbar } from "@/components/navbar/navbar"
54
import { useRouter } from "next/router"
65

7-
import {
8-
GraphQLWordmarkLogo,
9-
StackOverflowIcon,
10-
GitHubIcon,
11-
DiscordIcon,
12-
TwitterIcon,
13-
} from "./src/icons"
14-
15-
// import { createElement } from "react"
16-
// import NextImage from "next-image-export-optimizer"
6+
import { GraphQLWordmarkLogo } from "./src/icons"
7+
import { Footer } from "@/components/footer"
178

189
const graphQLLogo = (
1910
<GraphQLWordmarkLogo className="nextra-logo h-6" title="GraphQL" />
2011
)
2112

22-
const classes = {
23-
link: "hover:underline decoration-from-font [text-underline-position:from-font]",
24-
}
25-
26-
function List({
27-
title,
28-
items,
29-
}: {
30-
title: string
31-
items: { title: string; url: string }[]
32-
}) {
33-
return (
34-
<ul className="flex flex-col gap-4 text-sm max-lg:w-[46%]">
35-
<h3 className="text-lg font-bold">{title}</h3>
36-
{items.map(item => (
37-
<li key={item.title}>
38-
<NextLink href={item.url} className={classes.link}>
39-
{item.title}
40-
</NextLink>
41-
</li>
42-
))}
43-
</ul>
44-
)
45-
}
46-
47-
function Footer() {
48-
return (
49-
<div className="w-full">
50-
<div className="mb-24 flex flex-wrap items-start justify-between gap-10">
51-
<NextLink href="/" className="max-lg:w-full">
52-
{graphQLLogo}
53-
</NextLink>
54-
<List
55-
title="Learn"
56-
items={[
57-
{ title: "Introduction to GraphQL", url: "/learn" },
58-
{ title: "Best Practices", url: "/learn/best-practices" },
59-
{ title: "Frequently Asked Questions", url: "/faq" },
60-
{
61-
title: "Training Courses",
62-
url: "/community/resources/training-courses",
63-
},
64-
]}
65-
/>
66-
<List
67-
title="Code"
68-
items={[
69-
{ title: "GitHub", url: "https://github.com/graphql" },
70-
{
71-
title: "GraphQL Specification",
72-
url: "https://spec.graphql.org",
73-
},
74-
{ title: "Libraries & Tools", url: "/code" },
75-
{ title: "Services & Vendors", url: "/code/?tags=services" },
76-
]}
77-
/>
78-
<List
79-
title="Community"
80-
items={[
81-
{
82-
title: "Resources",
83-
url: "/community/resources/official-channels",
84-
},
85-
{ title: "Events & Meetups", url: "/community/events" },
86-
{
87-
title: "Contribute to GraphQL",
88-
url: "/community/contribute/essential-links",
89-
},
90-
{ title: "Landscape", url: "https://landscape.graphql.org" },
91-
{ title: "Shop", url: "https://store.graphql.org" },
92-
]}
93-
/>
94-
<List
95-
title="& More"
96-
items={[
97-
{ title: "Blog", url: "/blog" },
98-
{ title: "GraphQL Foundation", url: "/foundation" },
99-
{
100-
title: "GraphQL Community Grant",
101-
url: "/foundation/community-grant",
102-
},
103-
{ title: "Logo and Brand Guidelines", url: "/brand" },
104-
{ title: "Code of Conduct", url: "/codeofconduct" },
105-
]}
106-
/>
107-
</div>
108-
<div className="flex flex-wrap justify-between gap-10">
109-
<p className="text-sm">
110-
Copyright © {new Date().getFullYear()} The GraphQL Foundation. All
111-
rights reserved.
112-
<br />
113-
For web site terms of use, trademark policy and general project
114-
policies please see{" "}
115-
<a
116-
href="https://lfprojects.org"
117-
target="_blank"
118-
rel="noreferrer"
119-
className={`text-primary ${classes.link}`}
120-
>
121-
https://lfprojects.org
122-
</a>
123-
</p>
124-
<div className="flex flex-col gap-4 lg:items-end">
125-
<ul className="flex gap-5">
126-
{[
127-
{ url: "https://github.com/graphql", icon: GitHubIcon },
128-
{ url: "https://discord.graphql.org", icon: DiscordIcon },
129-
{ url: "https://twitter.com/graphql", icon: TwitterIcon },
130-
{
131-
url: "http://stackoverflow.com/questions/tagged/graphql",
132-
icon: StackOverflowIcon,
133-
},
134-
].map(({ url, icon: Icon }) => (
135-
<li key={url}>
136-
<a
137-
href={url}
138-
target="_blank"
139-
rel="noreferrer"
140-
className="block transition-colors hover:text-primary"
141-
>
142-
<Icon className="h-5 w-auto *:fill-current" />
143-
</a>
144-
</li>
145-
))}
146-
</ul>
147-
<a
148-
href="https://nextra.site"
149-
target="_blank"
150-
rel="noreferrer"
151-
className="flex items-center gap-2 text-xs transition-colors hover:text-primary"
152-
>
153-
Powered by{" "}
154-
<svg
155-
viewBox="0 0 361 70"
156-
fill="currentColor"
157-
className="h-3 shrink-0"
158-
>
159-
<path d="M114.913 33.2763v28.7642h-11.57V12.9496h11.059v8.3416h.575c1.129-2.7485 2.93-4.9325 5.401-6.5518 2.493-1.6193 5.572-2.429 9.237-2.429 3.388 0 6.339.7244 8.853 2.1733 2.535 1.4489 4.496 3.5476 5.88 6.2962 1.407 2.7486 2.099 6.0831 2.078 10.0035v31.2571h-11.57V32.5732c0-3.2813-.852-5.8487-2.557-7.7024-1.683-1.8537-4.016-2.7806-6.999-2.7806-2.024 0-3.824.4475-5.401 1.3424-1.556.8736-2.781 2.1413-3.676 3.8032-.873 1.662-1.31 3.6755-1.31 6.0405Zm61.407 29.723c-4.922 0-9.172-1.0227-12.752-3.0681-3.558-2.0668-6.296-4.9858-8.214-8.7572-1.917-3.7926-2.876-8.2563-2.876-13.3913 0-5.0497.959-9.4815 2.876-13.2954 1.939-3.8353 4.645-6.8182 8.118-8.9489 3.473-2.152 7.553-3.228 12.241-3.228 3.026 0 5.881.4901 8.565 1.4702 2.706.9588 5.093 2.4503 7.159 4.4744 2.088 2.0242 3.729 4.6023 4.922 7.7344 1.193 3.1108 1.79 6.8182 1.79 11.1221v3.5476h-40.238v-7.7983h29.148c-.021-2.2159-.501-4.1868-1.438-5.9126-.938-1.7472-2.248-3.1215-3.931-4.1229-1.662-1.0014-3.601-1.5021-5.817-1.5021-2.365 0-4.443.5753-6.232 1.7258-1.79 1.1293-3.186 2.6208-4.187 4.4745-.98 1.8324-1.481 3.8459-1.502 6.0405v6.8075c0 2.8551.522 5.3054 1.566 7.3508 1.044 2.0242 2.503 3.5796 4.378 4.6662 1.875 1.0654 4.07 1.598 6.584 1.598 1.683 0 3.207-.2343 4.57-.7031 1.364-.49 2.546-1.2038 3.548-2.1413 1.001-.9375 1.758-2.0987 2.269-3.4837l10.803 1.2145c-.682 2.8551-1.982 5.348-3.9 7.4787-1.896 2.1094-4.325 3.75-7.286 4.9219-2.962 1.1506-6.35 1.7258-10.164 1.7258Zm34.777-50.0497 9.908 18.1215 10.067-18.1215h12.241l-14.798 24.5455 15.054 24.5454h-12.177l-10.387-17.674-10.291 17.674h-12.273l14.957-24.5454-14.574-24.5455h12.273Zm63.878 0v8.9489h-28.221v-8.9489h28.221ZM253.722 1.18825h11.569V47.2749c0 1.5554.235 2.7486.704 3.5795.49.8097 1.129 1.3637 1.917 1.662s1.662.4474 2.621.4474c.724 0 1.385-.0532 1.981-.1598.618-.1065 1.087-.2024 1.407-.2876l1.949 9.0447c-.618.2131-1.502.4475-2.652.7031-1.13.2557-2.515.4049-4.155.4475-2.898.0852-5.508-.3516-7.831-1.3104-2.322-.9801-4.165-2.4929-5.529-4.5383-1.342-2.0455-2.003-4.6023-1.981-7.6705V1.18825Zm29.129 60.85225V12.9496h11.218v8.1818h.512c.895-2.8338 2.429-5.0177 4.602-6.5518 2.173-1.5554 4.677-2.3331 7.511-2.3331 1.321 0 2.535.1598 3.643.4794 1.108.3196 2.088.7564 2.94 1.3104l-3.579 9.588c-.618-.2983-1.3-.5433-2.046-.7351-.745-.1917-1.587-.2876-2.524-.2876-2.003 0-3.814.4474-5.434 1.3423-1.619.8949-2.908 2.1414-3.867 3.7394-.937 1.5767-1.406 3.4091-1.406 5.4971v28.8601h-11.57Zm51.222.863c-3.856 0-7.308-.9908-10.355-2.9723-3.047-1.9816-5.454-4.858-7.223-8.6293-1.768-3.7713-2.652-8.3523-2.652-13.7429 0-5.4546.894-10.0568 2.684-13.8068 1.811-3.7713 4.251-6.6158 7.319-8.5334 3.068-1.9389 6.488-2.9084 10.259-2.9084 2.877 0 5.242.4901 7.095 1.4702 1.854.9588 3.324 2.12 4.411 3.4836 1.087 1.3424 1.928 2.6101 2.525 3.8033h.479v-8.1179h11.602v49.0909h-11.378v-7.7343h-.703c-.597 1.1931-1.46 2.4609-2.589 3.8032-1.129 1.321-2.621 2.4503-4.474 3.3878-1.854.9375-4.187 1.4063-7 1.4063Zm3.228-9.4922c2.451 0 4.539-.6605 6.265-1.9816 1.725-1.3423 3.036-3.2066 3.931-5.593s1.342-5.1669 1.342-8.3416c0-3.1747-.447-5.934-1.342-8.2777-.874-2.3438-2.174-4.1655-3.9-5.4652-1.704-1.2997-3.803-1.9496-6.296-1.9496-2.578 0-4.73.6712-6.456 2.0135s-3.025 3.196-3.899 5.5611c-.873 2.365-1.31 5.071-1.31 8.1179 0 3.0682.437 5.8061 1.31 8.2138.895 2.3863 2.205 4.272 3.931 5.6569 1.747 1.3636 3.889 2.0455 6.424 2.0455Z" />
160-
<path
161-
d="m64.8833 1.81335-2.8464 2.84638C47.1274 19.5692 22.9543 19.5692 8.04485 4.65972L5.19848 1.81335c-.93479-.93478-2.45037-.93478-3.38515 0-.93479.93478-.93478 2.45037 0 3.38515L4.6597 8.04487c14.9095 14.90953 14.9095 39.08263 0 53.99213l-2.84637 2.8463c-.93479.9348-.93479 2.4504 0 3.3852.93478.9348 2.45037.9348 3.38515 0l2.84637-2.8464c14.90955-14.9095 39.08255-14.9095 53.99205 0l2.8464 2.8464c.9348.9348 2.4504.9348 3.3852 0 .9347-.9348.9347-2.4504 0-3.3852l-2.8464-2.8463c-14.9095-14.9095-14.9095-39.0826 0-53.99213l2.8464-2.84637c.9347-.93478.9347-2.45037 0-3.38515-.9348-.93478-2.4504-.93478-3.3852 0Z"
162-
stroke="currentColor"
163-
strokeWidth="2"
164-
/>
165-
</svg>
166-
</a>
167-
</div>
168-
</div>
169-
</div>
170-
)
171-
}
172-
17313
export default {
17414
backgroundColor: {
17515
dark: "27,27,27",
@@ -235,7 +75,8 @@ export default {
23575
defaultMenuCollapseLevel: 1,
23676
},
23777
footer: {
238-
content: Footer,
78+
component: Footer,
79+
content: "Copyright © 2025 The GraphQL Foundation. All rights reserved.",
23980
},
24081
navbar: {
24182
component: Navbar,

0 commit comments

Comments
 (0)