Skip to content

new landing — footer #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: new-landing--quotes-from-the-industry
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/conf/2025/components/testimonials/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ export function TestimonialAuthor({
alt={author.name}
width={128}
height={128}
className="size-16 saturate-0 xl:size-32"
className="size-16 saturate-[.1] xl:size-32"
/>
<div className="absolute inset-0 z-[1] bg-pri-darker mix-blend-plus-lighter" />
<div className="absolute inset-0 z-[1] bg-pri-darker opacity-80 mix-blend-plus-lighter" />
<Stripes />
</div>
<AuthorNameAndRole author={author} className="contents md:hidden" />
Expand Down
1 change: 1 addition & 0 deletions src/app/conf/_components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SocialIcons } from "./social-icons"
import NextLink from "next/link"
import { ReactNode } from "react"
import { clsx } from "clsx"

import { Badge } from "./badge"

export function Footer({
Expand Down
26 changes: 19 additions & 7 deletions src/app/conf/_components/social-icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ const anchorProps = {
rel: "noreferrer",
}

export interface SocialIconsProps extends React.HTMLAttributes<HTMLDivElement> {
count?: 4 | 6
}

export function SocialIcons({
className,
count = 6,
...rest
}: React.HTMLAttributes<HTMLDivElement>) {
}: SocialIconsProps) {
return (
<div
className={clsx(
Expand All @@ -40,12 +45,19 @@ export function SocialIcons({
>
<LinkedInIcon />
</a>
<a href="https://youtube.com/@GraphQLFoundation" {...anchorProps}>
<YouTubeIcon className="fill-current" />
</a>
<a href="https://facebook.com/groups/graphql.community" {...anchorProps}>
<FacebookIcon />
</a>
{count === 6 && (
<>
<a href="https://youtube.com/@GraphQLFoundation" {...anchorProps}>
<YouTubeIcon className="fill-current" />
</a>
<a
href="https://facebook.com/groups/graphql.community"
{...anchorProps}
>
<FacebookIcon />
</a>
</>
)}
</div>
)
}
42 changes: 42 additions & 0 deletions src/components/footer/conference-footer-box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { clsx } from "clsx"

import { Anchor } from "@/app/conf/_design-system/anchor"
import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr"

interface ConferenceFooterBoxProps {
href: string
className?: string
}

export function ConferenceFooterBox({
href,
className,
}: ConferenceFooterBoxProps) {
return (
<Anchor
href={href}
className={clsx(
"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",
className,
)}
>
<div className="flex flex-1 flex-col gap-4 border-b border-pri-lighter p-4 dark:border-pri-light/50">
<h2 className="font-mono text-2xl uppercase leading-none md:text-3xl">
<span className="max-xs:hidden">Join</span> GraphQLConf 2025
</h2>
</div>

<div className="typography-body-sm flex font-sans">
<div className="flex-1 justify-center border-r border-pri-lighter p-4 dark:border-pri-light/50">
September <span className="whitespace-nowrap">08–10</span>
<br />
Amsterdam, Netherlands
</div>

<div className="flex w-[min(calc(25%-.5px),73.5px)] items-center justify-center">
<ArrowDownIcon className="size-10 -rotate-90 text-pri-lighter transition group-hover:translate-x-0.5 group-hover:text-neu-900" />
</div>
</div>
</Anchor>
)
}
192 changes: 192 additions & 0 deletions src/components/footer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import NextLink from "next/link"
import { useConfig, useThemeConfig } from "nextra-theme-docs"

import { GraphQLWordmarkLogo } from "@/icons"
import { SocialIcons } from "@/app/conf/_components/social-icons"
import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration"
import blurBean from "@/app/conf/2025/components/footer/blur-bean.webp"

import { renderComponent } from "../utils"
import { Anchor } from "@/app/conf/_design-system/anchor"
import type { ReactNode } from "react"
import { ConferenceFooterBox } from "./conference-footer-box"

interface FooterLink {
title: ReactNode
route: string
}

interface FooterSection {
title?: ReactNode
route?: string
links: FooterLink[]
}

const FOOTER_SECTIONS_COUNT = 4
const MAX_LINKS_PER_SECTION = 5
const CONFERENCE_YEAR = 2025

export function Footer({ extraLinks }: { extraLinks: FooterLink[] }) {
const { sections, hasConferenceBox } = useFooterSections(extraLinks)
const themeConfig = useThemeConfig()

return (
<footer className="relative !bg-neu-100 text-neu-900 dark:!bg-neu-0 max-md:px-0 max-md:pt-0">
<Stripes />

<div className="mx-auto max-w-[120rem] border-neu-400 dark:border-neu-100 3xl:border-x">
<div className="flex flex-wrap justify-between gap-4 p-4 max-md:w-full md:p-6 2xl:px-10">
<NextLink href="/" className="nextra-logo flex items-center">
<GraphQLWordmarkLogo className="h-6" title="GraphQL" />
</NextLink>
</div>

<div className="grid grid-cols-2 gap-px bg-neu-400 py-px dark:bg-neu-100 lg:grid-cols-5">
{sections.map((section, i) => (
<div
className="typography-menu relative bg-neu-100 py-4 dark:bg-neu-0 lg:py-6 3xl:py-10"
key={i}
>
{section.title && (
<h3 className="font-bold lg:mb-4 3xl:mb-10">
{section.route ? (
<Anchor
className="gql-focus-visible block p-4 underline-offset-4 hover:underline md:px-6 2xl:px-10"
href={section.route}
>
{section.title}
</Anchor>
) : (
<span className="block p-4 3xl:px-10">{section.title}</span>
)}
</h3>
)}
{section.links.map(link => (
<Anchor
key={link.route}
href={link.route}
className="gql-focus-visible block p-4 underline-offset-4 hover:underline md:px-6 2xl:px-10"
>
{link.title}
</Anchor>
))}
</div>
))}
<div className="flex flex-col max-lg:contents">
{hasConferenceBox && (
<ConferenceFooterBox
href={`/conf/${CONFERENCE_YEAR}`}
className="z-[2] col-span-full flex-1 max-lg:row-start-1"
/>
)}
<SocialIcons
count={4}
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"
/>
</div>
</div>

<div className="relative flex items-center justify-between gap-4 p-4 md:px-6 2xl:px-10">
{themeConfig.darkMode && (
// todo: new theme switch component
<div className="typography-menu flex items-center *:rounded-none dark:*:text-neu-900">
{renderComponent(themeConfig.themeSwitch.component)}
</div>
)}
<p className="typography-body-xs flex flex-col text-pretty max-md:gap-5">
{renderComponent(themeConfig.footer.content)}
</p>
</div>
</div>
</footer>
)
}

function Stripes() {
return (
<div
role="presentation"
// prettier-ignore
// false positive
// eslint-disable-next-line tailwindcss/no-contradicting-classname
className="pointer-events-none absolute inset-0 z-[1]
[--start-1:rgba(255,204,239,.05)]
[--end-1:hsl(var(--color-pri-base)/.8)]
dark:[--start-1:hsl(var(--color-pri-darker))]
dark:[--end-1:hsl(var(--color-pri-base)/.9)]

[--start-2:transparent]
[--end-2:hsl(var(--color-pri-base)/.6)]
dark:[--start-2:rgba(255,204,239,.1)]
dark:[--end-2:hsl(var(--color-pri-base)/.8)]

mix-blend-darken
dark:mix-blend-lighten

max-sm:![mask-size:300vw_200px]
max-sm:![mask-position:center_calc(100%-100px)]

sm:max-lg:![mask-origin:bottom]
sm:max-lg:![mask-size:250%_300px]
sm:max-lg:![mask-position:center_calc(100%-30px)]

max-lg:rotate-180
"
style={{
maskImage: `url(${blurBean.src})`,
WebkitMaskImage: `url(${blurBean.src})`,
maskPosition: "center 260px",
WebkitMaskPosition: "center 260px",
maskSize: "200% 100%",
WebkitMaskSize: "200% 100%",
maskRepeat: "no-repeat",
WebkitMaskRepeat: "no-repeat",
maskOrigin: "top",
}}
>
<StripesDecoration
evenClassName="bg-[linear-gradient(180deg,var(--start-1)_0%,var(--end-1)_200%)]"
oddClassName="bg-[linear-gradient(180deg,var(--start-2)_0%,var(--end-2)_200%)]"
/>
</div>
)
}

function useFooterSections(extraLinks: FooterLink[]): {
sections: FooterSection[]
hasConferenceBox: boolean
} {
const { normalizePagesResult } = useConfig()

const sections: FooterSection[] = []
const singleLinks: FooterLink[] = []
let hasConferenceBox = false

for (const item of normalizePagesResult.topLevelNavbarItems) {
if (
(item.type === "page" || item.type === "menu") &&
item.children?.length &&
sections.length < FOOTER_SECTIONS_COUNT - 1
) {
sections.push({
title: item.title,
route: item.route,
links: (item.children || [])
.filter(child => child.route)
.slice(0, MAX_LINKS_PER_SECTION)
.map(child => ({ title: child.title, route: child.route })),
})
} else if (singleLinks.length < MAX_LINKS_PER_SECTION) {
if (item.route && item.route.startsWith("/conf/")) {
hasConferenceBox = true
} else {
singleLinks.push({ title: item.title, route: item.route })
}
}
}

singleLinks.push(...extraLinks)
sections.push({ links: singleLinks })

return { sections, hasConferenceBox }
}
32 changes: 0 additions & 32 deletions src/components/index-page/bring-your-own-code.tsx

This file was deleted.

6 changes: 2 additions & 4 deletions src/components/index-page/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Hero } from "./hero"
import { TrustedBy } from "./trusted-by"
import { SingleRequest } from "./single-request"
import { BringYourOwnCode } from "./bring-your-own-code"
import { HowItWorks } from "./how-it-works"
import { ProvenSolution } from "./proven-solution"
import { FivePillars } from "./five-pillars"
import { PoweredByCommunity } from "./powered-by-community"
import { GraphQLAdvantages } from "./graphql-advantages"
import { QuotesFromTheIndustry } from "./quotes-from-the-industry"
import { JoinTheCommunity } from "./join-the-community"

export function IndexPage() {
return (
Expand All @@ -20,8 +19,7 @@ export function IndexPage() {
<PoweredByCommunity />
<GraphQLAdvantages />
<QuotesFromTheIndustry />
<SingleRequest />
<BringYourOwnCode />
<JoinTheCommunity />
</div>
)
}
37 changes: 37 additions & 0 deletions src/components/index-page/join-the-community.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Anchor } from "@/app/conf/_design-system/anchor"

import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr"
import { DiscordIcon } from "@/icons"

export function JoinTheCommunity() {
return (
<section className="gql-section lg:pb-16 xl:pb-24">
<div className="gql-container typography-body-lg flex bg-pri-dark text-white dark:bg-pri-darker max-lg:flex-col">
<div className="border-pri-light p-6 max-lg:border-b lg:border-r lg:p-16">
<h2 className="typography-h2 text-balance">Join the community</h2>
<p className="mt-8">
GraphQL is community-driven, backed by thousands of developers and
companies worldwide. Become part of a network shaping the future of
API development.
</p>
</div>
<div className="flex flex-col *:flex-1">
<Anchor
href="/community/tools-and-libraries"
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"
>
Discord
<DiscordIcon className="size-8 fill-white" />
</Anchor>
<Anchor
href="/community/events"
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"
>
Explore community resources
<ArrowDownIcon className="size-10 -rotate-90 text-pri-light" />
</Anchor>
</div>
</div>
</section>
)
}
Loading