Skip to content

Commit 07f549e

Browse files
authored
Merge pull request #37 from hasparus/new-landing--footer
new landing — footer
2 parents cf2baad + 6535656 commit 07f549e

File tree

12 files changed

+321
-315
lines changed

12 files changed

+321
-315
lines changed

src/app/conf/2025/components/testimonials/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ export function TestimonialAuthor({
113113
alt={author.name}
114114
width={128}
115115
height={128}
116-
className="size-16 saturate-0 xl:size-32"
116+
className="size-16 saturate-[.1] xl:size-32"
117117
/>
118-
<div className="absolute inset-0 z-[1] bg-pri-darker mix-blend-plus-lighter" />
118+
<div className="absolute inset-0 z-[1] bg-pri-darker opacity-80 mix-blend-plus-lighter" />
119119
<Stripes />
120120
</div>
121121
<AuthorNameAndRole author={author} className="contents md:hidden" />

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/app/conf/_components/social-icons.tsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@ const anchorProps = {
1313
rel: "noreferrer",
1414
}
1515

16+
export interface SocialIconsProps extends React.HTMLAttributes<HTMLDivElement> {
17+
count?: 4 | 6
18+
}
19+
1620
export function SocialIcons({
1721
className,
22+
count = 6,
1823
...rest
19-
}: React.HTMLAttributes<HTMLDivElement>) {
24+
}: SocialIconsProps) {
2025
return (
2126
<div
2227
className={clsx(
@@ -40,12 +45,19 @@ export function SocialIcons({
4045
>
4146
<LinkedInIcon />
4247
</a>
43-
<a href="https://youtube.com/@GraphQLFoundation" {...anchorProps}>
44-
<YouTubeIcon className="fill-current" />
45-
</a>
46-
<a href="https://facebook.com/groups/graphql.community" {...anchorProps}>
47-
<FacebookIcon />
48-
</a>
48+
{count === 6 && (
49+
<>
50+
<a href="https://youtube.com/@GraphQLFoundation" {...anchorProps}>
51+
<YouTubeIcon className="fill-current" />
52+
</a>
53+
<a
54+
href="https://facebook.com/groups/graphql.community"
55+
{...anchorProps}
56+
>
57+
<FacebookIcon />
58+
</a>
59+
</>
60+
)}
4961
</div>
5062
)
5163
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { clsx } from "clsx"
2+
3+
import { Anchor } from "@/app/conf/_design-system/anchor"
4+
import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr"
5+
6+
interface ConferenceFooterBoxProps {
7+
href: string
8+
className?: string
9+
}
10+
11+
export function ConferenceFooterBox({
12+
href,
13+
className,
14+
}: ConferenceFooterBoxProps) {
15+
return (
16+
<Anchor
17+
href={href}
18+
className={clsx(
19+
"dark group relative inline-flex flex-col bg-pri-base text-neu-900 after:absolute after:inset-0 hover:after:bg-white/[.025] dark:bg-pri-dark",
20+
className,
21+
)}
22+
>
23+
<div className="flex flex-1 flex-col gap-4 border-b border-pri-lighter p-4 dark:border-pri-light/50">
24+
<h2 className="font-mono text-2xl uppercase leading-none md:text-3xl">
25+
<span className="max-xs:hidden">Join</span> GraphQLConf 2025
26+
</h2>
27+
</div>
28+
29+
<div className="typography-body-sm flex font-sans">
30+
<div className="flex-1 justify-center border-r border-pri-lighter p-4 dark:border-pri-light/50">
31+
September <span className="whitespace-nowrap">08–10</span>
32+
<br />
33+
Amsterdam, Netherlands
34+
</div>
35+
36+
<div className="flex w-[min(calc(25%-.5px),73.5px)] items-center justify-center">
37+
<ArrowDownIcon className="size-10 -rotate-90 text-pri-lighter transition group-hover:translate-x-0.5 group-hover:text-neu-900" />
38+
</div>
39+
</div>
40+
</Anchor>
41+
)
42+
}

src/components/footer/index.tsx

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

src/components/index-page/bring-your-own-code.tsx

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/components/index-page/index.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { Hero } from "./hero"
22
import { TrustedBy } from "./trusted-by"
3-
import { SingleRequest } from "./single-request"
4-
import { BringYourOwnCode } from "./bring-your-own-code"
53
import { HowItWorks } from "./how-it-works"
64
import { ProvenSolution } from "./proven-solution"
75
import { FivePillars } from "./five-pillars"
86
import { PoweredByCommunity } from "./powered-by-community"
97
import { GraphQLAdvantages } from "./graphql-advantages"
108
import { QuotesFromTheIndustry } from "./quotes-from-the-industry"
9+
import { JoinTheCommunity } from "./join-the-community"
1110

1211
export function IndexPage() {
1312
return (
@@ -20,8 +19,7 @@ export function IndexPage() {
2019
<PoweredByCommunity />
2120
<GraphQLAdvantages />
2221
<QuotesFromTheIndustry />
23-
<SingleRequest />
24-
<BringYourOwnCode />
22+
<JoinTheCommunity />
2523
</div>
2624
)
2725
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Anchor } from "@/app/conf/_design-system/anchor"
2+
3+
import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr"
4+
import { DiscordIcon } from "@/icons"
5+
6+
export function JoinTheCommunity() {
7+
return (
8+
<section className="gql-section lg:pb-16 xl:pb-24">
9+
<div className="gql-container typography-body-lg flex bg-pri-dark text-white dark:bg-pri-darker max-lg:flex-col">
10+
<div className="border-pri-light p-6 max-lg:border-b lg:border-r lg:p-16">
11+
<h2 className="typography-h2 text-balance">Join the community</h2>
12+
<p className="mt-8">
13+
GraphQL is community-driven, backed by thousands of developers and
14+
companies worldwide. Become part of a network shaping the future of
15+
API development.
16+
</p>
17+
</div>
18+
<div className="flex flex-col *:flex-1">
19+
<Anchor
20+
href="/community/tools-and-libraries"
21+
className="flex items-center justify-between gap-4 whitespace-pre border-b border-pri-light px-6 py-8 hover:bg-white/10 lg:h-1/3 lg:px-8 lg:pr-12 xl:gap-6"
22+
>
23+
Discord
24+
<DiscordIcon className="size-8 fill-white" />
25+
</Anchor>
26+
<Anchor
27+
href="/community/events"
28+
className="flex items-center justify-between gap-4 whitespace-pre border-pri-light px-6 py-8 hover:bg-white/10 lg:h-1/3 lg:px-8 lg:pr-12 xl:gap-6"
29+
>
30+
Explore community resources
31+
<ArrowDownIcon className="size-10 -rotate-90 text-pri-light" />
32+
</Anchor>
33+
</div>
34+
</div>
35+
</section>
36+
)
37+
}

0 commit comments

Comments
 (0)