diff --git a/src/app/conf/2025/components/testimonials/index.tsx b/src/app/conf/2025/components/testimonials/index.tsx index 1c2bf019e0..f34c4b8690 100644 --- a/src/app/conf/2025/components/testimonials/index.tsx +++ b/src/app/conf/2025/components/testimonials/index.tsx @@ -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" /> -
+
diff --git a/src/app/conf/_components/footer.tsx b/src/app/conf/_components/footer.tsx index cf764ae819..26a7b711d7 100644 --- a/src/app/conf/_components/footer.tsx +++ b/src/app/conf/_components/footer.tsx @@ -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({ diff --git a/src/app/conf/_components/social-icons.tsx b/src/app/conf/_components/social-icons.tsx index db65b94e61..5798c50363 100644 --- a/src/app/conf/_components/social-icons.tsx +++ b/src/app/conf/_components/social-icons.tsx @@ -13,10 +13,15 @@ const anchorProps = { rel: "noreferrer", } +export interface SocialIconsProps extends React.HTMLAttributes { + count?: 4 | 6 +} + export function SocialIcons({ className, + count = 6, ...rest -}: React.HTMLAttributes) { +}: SocialIconsProps) { return (
- - - - - - + {count === 6 && ( + <> + + + + + + + + )}
) } diff --git a/src/components/footer/conference-footer-box.tsx b/src/components/footer/conference-footer-box.tsx new file mode 100644 index 0000000000..8f1caa4f93 --- /dev/null +++ b/src/components/footer/conference-footer-box.tsx @@ -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 ( + +
+

+ Join GraphQLConf 2025 +

+
+ +
+
+ September 08–10 +
+ Amsterdam, Netherlands +
+ +
+ +
+
+
+ ) +} diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx new file mode 100644 index 0000000000..8911cda8bf --- /dev/null +++ b/src/components/footer/index.tsx @@ -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 ( +
+ + +
+
+ + + +
+ +
+ {sections.map((section, i) => ( +
+ {section.title && ( +

+ {section.route ? ( + + {section.title} + + ) : ( + {section.title} + )} +

+ )} + {section.links.map(link => ( + + {link.title} + + ))} +
+ ))} +
+ {hasConferenceBox && ( + + )} + +
+
+ +
+ {themeConfig.darkMode && ( + // todo: new theme switch component +
+ {renderComponent(themeConfig.themeSwitch.component)} +
+ )} +

+ {renderComponent(themeConfig.footer.content)} +

+
+
+
+ ) +} + +function Stripes() { + return ( +
+ +
+ ) +} + +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 } +} diff --git a/src/components/index-page/bring-your-own-code.tsx b/src/components/index-page/bring-your-own-code.tsx deleted file mode 100644 index f69098fbb1..0000000000 --- a/src/components/index-page/bring-your-own-code.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Code1, Code2, Code3, Code4 } from "@/components/code-blocks" -import { InfiniteMovingCards } from "./infinite-moving-cards" - -export function BringYourOwnCode() { - return ( - <> -
-
-

Bring your own data and code

- {/*Illustration of each field becoming a function?]*/} -

- GraphQL creates a uniform API across your entire application without - being limited by a specific storage engine. Write GraphQL APIs that - leverage your existing data and code with GraphQL engines available - in many languages. You provide functions for each field in the type - system, and GraphQL calls them with optimal concurrency. -

-
-
- - - - - - - - ) -} diff --git a/src/components/index-page/index.tsx b/src/components/index-page/index.tsx index 8439a19373..2f2b396b05 100644 --- a/src/components/index-page/index.tsx +++ b/src/components/index-page/index.tsx @@ -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 ( @@ -20,8 +19,7 @@ export function IndexPage() { - - +
) } diff --git a/src/components/index-page/join-the-community.tsx b/src/components/index-page/join-the-community.tsx new file mode 100644 index 0000000000..4d1cd5f829 --- /dev/null +++ b/src/components/index-page/join-the-community.tsx @@ -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 ( +
+
+
+

Join the community

+

+ GraphQL is community-driven, backed by thousands of developers and + companies worldwide. Become part of a network shaping the future of + API development. +

+
+
+ + Discord + + + + Explore community resources + + +
+
+
+ ) +} diff --git a/src/components/index-page/single-request/index.module.css b/src/components/index-page/single-request/index.module.css deleted file mode 100644 index 672158a126..0000000000 --- a/src/components/index-page/single-request/index.module.css +++ /dev/null @@ -1,59 +0,0 @@ -.query, -.response { - @apply absolute left-1/2; - @apply [&_pre]:bg-transparent [&_pre]:ring-0; -} - -.query { - animation: query-up 8s 0s infinite ease-in both; - @apply -translate-x-full; - - @keyframes query-up { - from { - opacity: 0; - top: 180px; - } - - 5%, - 25% { - opacity: 1; - } - - 40% { - opacity: 0; - } - - 50%, - 100% { - top: 70px; - opacity: 0; - } - } -} - -.response { - animation: response-down 8s 2.5s infinite ease-in both; - @apply -translate-x-1/2; - - @keyframes response-down { - 0% { - opacity: 0; - top: 95px; - } - - 10% { - opacity: 0; - } - - 25%, - 45% { - opacity: 1; - } - - 50%, - 100% { - opacity: 0; - top: 160px; - } - } -} diff --git a/src/components/index-page/single-request/index.tsx b/src/components/index-page/single-request/index.tsx deleted file mode 100644 index efab2127d6..0000000000 --- a/src/components/index-page/single-request/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { QueryHeroFriends, ResponseHeroFriends } from "../../code-blocks" -import classes from "./index.module.css" -import phoneImage from "public/img/phone.svg" -import serverImage from "public/img/server.svg" -import NextImage from "next-image-export-optimizer" - -export function SingleRequest() { - return ( -
-
-

- Get many resources
- in a single request -

- {/*Illustration: a query 2 or 3 levels deep]*/} -

- GraphQL queries access not just the properties of one resource but - also smoothly follow references between them. While typical REST APIs - require loading from multiple URLs, GraphQL APIs get all the data your - app needs in a single request. Apps using GraphQL can be quick even on - slow mobile network connections. -

-
-
- - -
- -
-
- -
-
-
- ) -} diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx index 85ef6f4109..cf93f175d8 100644 --- a/src/components/navbar/navbar.tsx +++ b/src/components/navbar/navbar.tsx @@ -107,7 +107,7 @@ export function Navbar({ items }: NavBarProps): ReactElement { )} > -