11"use client"
22
33import clsx from "clsx"
4- import { useState } from "react"
4+ import { useState , Fragment } from "react"
55
66import { Button } from "@/app/conf/_design-system/button"
7- import ArrowRightIcon from "./arrow-right.svg?svgr"
87import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration"
8+ import ArrowDownIcon from "@/app/conf/_design-system/pixelarticons/arrow-down.svg?svgr"
99
1010import blurBean from "./blur-bean.webp"
1111
@@ -29,28 +29,35 @@ const USE_CASES: UseCase[] = [
2929 description :
3030 "Fetch only the data you need to minimize payload size and round-trips. Build resilient UIs that work well on variable networks." ,
3131 cta : "Mobile Patterns" ,
32- href : "/learn/best-practices#mobile " ,
32+ href : "TODO " ,
3333 } ,
3434 {
3535 label : "A frontend-heavy app with advanced UI needs" ,
3636 description :
3737 "Co-locate queries with components and keep state consistent. Compose data from many backends without bespoke endpoints." ,
3838 cta : "Frontend Integration Guide" ,
39- href : "/learn/queries#components " ,
39+ href : "TODO " ,
4040 } ,
4141 {
4242 label : "An app with real-time updates" ,
4343 description :
4444 "Use subscriptions for low-latency updates while keeping the schema as the single contract for clients and servers." ,
4545 cta : "Real-time with Subscriptions" ,
46- href : "/learn/subscriptions " ,
46+ href : "TODO " ,
4747 } ,
4848 {
4949 label : "A simple full stack TypeScript app" ,
5050 description :
5151 "Strong types end-to-end with code generation and great DX. Ship faster without compromising correctness." ,
5252 cta : "Full Stack TS Starter" ,
53- href : "/learn#typescript" ,
53+ href : "TODO" ,
54+ } ,
55+ {
56+ label : "An AI-powered app" ,
57+ description :
58+ "Build apps with soft core with GraphQL MCP, using GraphQL schema introspection to give access and teach an LLM about your data." ,
59+ cta : "MCP GraphQL" ,
60+ href : "https://github.com/graphql/graphql-mcp" ,
5461 } ,
5562]
5663
@@ -75,39 +82,71 @@ export function UseCases({
7582 </ p >
7683
7784 < div className = "3xl:flex-1" />
78- < ul className = "mt-8 divide-y divide-sec-dark border border-sec-dark lg:mt-16" >
85+ < div
86+ role = "tablist"
87+ className = "mt-8 divide-y divide-sec-dark border border-sec-dark max-lg:sr-only lg:mt-16"
88+ >
7989 { USE_CASES . map ( ( useCase , i ) => (
80- < li key = { useCase . label } >
90+ < button
91+ role = "tab"
92+ type = "button"
93+ key = { useCase . label }
94+ tabIndex = { i === 0 ? 0 : - 1 }
95+ onKeyDown = { arrowsMoveSideways }
96+ onPointerDown = { ( ) => setSelectedIndex ( i ) }
97+ onFocus = { ( ) => setSelectedIndex ( i ) }
98+ aria-selected = { i === selectedIndex ? "true" : undefined }
99+ className = "gql-focus-visible group flex w-full items-center justify-between gap-6 border-b border-sec-dark px-3 py-4 text-left transition-colors hover:bg-sec-lighter aria-selected:bg-sec-base aria-selected:hover:bg-sec-lighter hover:dark:bg-neu-100/25 dark:aria-selected:bg-sec-darker"
100+ >
101+ < span className = "typography-body-lg" > { useCase . label } </ span >
102+ < ArrowDownIcon className = "size-10 shrink-0 -rotate-90 text-sec-dark opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100 group-aria-selected:opacity-100 dark:text-neu-900" />
103+ </ button >
104+ ) ) }
105+ </ div >
106+ < div className = "3xl:flex-1" />
107+ </ div >
108+
109+ < article className = "relative flex h-auto flex-col bg-sec-base dark:bg-sec-darker" >
110+ < Stripes />
111+ < div className = "flex flex-1 justify-center overflow-hidden max-lg:flex-col lg:items-center" >
112+ { USE_CASES . map ( ( useCase , i ) => (
113+ < Fragment key = { useCase . label } >
81114 < button
82115 type = "button"
83- onClick = { ( ) => setSelectedIndex ( i ) }
116+ onPointerDown = { ( ) => setSelectedIndex ( i ) }
117+ onFocus = { ( ) => setSelectedIndex ( i ) }
84118 aria-selected = { i === selectedIndex ? "true" : undefined }
85- className = "group flex w-full items-center justify-between gap-6 px-3 py-4 text-left transition-colors hover:bg-sec-lighter aria-selected:bg-sec-base aria-selected:hover: bg-sec-lighter hover: dark:bg-neu-100/25 dark:aria-selected:bg-sec-darker"
119+ className = "z-[1] flex w-full items-center justify-between gap-6 border-b border-sec-dark px-3 py-4 text-left first:border-t hover:bg-sec-light aria-selected:bg-sec-light dark: bg-sec-darker dark:hover: bg-neu-100/25 dark:aria-selected:bg-sec-darker max-lg:dark:bg-neu-50 lg:hidden "
86120 >
87- < span className = "typography-body-lg" > { useCase . label } </ span >
88- < ArrowRightIcon className = "size-10 shrink-0 text-sec-dark opacity-0 transition-opacity group-hover:opacity-100 group-aria-selected:opacity-100 dark:text-neu-900" />
121+ < span className = "typography-body-lg text-neu-900" >
122+ { useCase . label }
123+ </ span >
124+ < ArrowDownIcon className = "size-6 shrink-0 text-sec-dark transition-transform [[aria-selected=false]>&]:rotate-180" />
89125 </ button >
90- </ li >
126+ < div
127+ role = "tabpanel"
128+ className = { clsx (
129+ "relative h-full flex-1 p-8 lg:p-12 xl:p-16" ,
130+ selectedIndex === i ? "border-b border-sec-dark" : "hidden" ,
131+ ) }
132+ >
133+ < div className = "relative z-10 my-auto max-h-[528px] max-w-2xl" >
134+ < h3 className = "typography-body-lg text-sec-darker dark:text-sec-lighter max-lg:hidden" >
135+ { useCase . label }
136+ </ h3 >
137+ < p className = "typography-h3 text-neu-800 lg:mt-10 lg:max-xl:text-xl" >
138+ { useCase . description }
139+ </ p >
140+ < div className = "mt-8 flex xl:mt-[120px]" >
141+ < Button href = { useCase . href } variant = "primary" >
142+ { useCase . cta }
143+ < ArrowDownIcon className = "size-6 shrink-0 -rotate-90 text-neu-0" />
144+ </ Button >
145+ </ div >
146+ </ div >
147+ </ div >
148+ </ Fragment >
91149 ) ) }
92- </ ul >
93- < div className = "3xl:flex-1" />
94- </ div >
95-
96- < article className = "relative flex h-auto flex-col bg-sec-base p-8 dark:bg-sec-darker md:p-12 lg:p-16" >
97- < Stripes />
98- < div className = "z-10 my-auto max-h-[528px] max-w-2xl" >
99- < h3 className = "typography-body-lg text-sec-darker dark:text-sec-lighter" >
100- { selected . label }
101- </ h3 >
102- < p className = "typography-h3 mt-10 text-neu-800" >
103- { selected . description }
104- </ p >
105- < div className = "mt-8 flex xl:mt-[120px]" >
106- < Button href = { selected . href } variant = "primary" >
107- { selected . cta }
108- < ArrowRightIcon className = "size-6 shrink-0 text-neu-0" />
109- </ Button >
110- </ div >
111150 </ div >
112151 </ article >
113152 </ div >
@@ -137,3 +176,19 @@ function Stripes() {
137176 </ div >
138177 )
139178}
179+
180+ function arrowsMoveSideways ( event : React . KeyboardEvent < HTMLButtonElement > ) {
181+ if ( event . key === "ArrowLeft" || event . key === "ArrowUp" ) {
182+ const previousElement = event . currentTarget . previousElementSibling
183+ if ( previousElement ) {
184+ event . preventDefault ( )
185+ ; ( previousElement as HTMLElement ) . focus ( )
186+ }
187+ } else if ( event . key === "ArrowRight" || event . key === "ArrowDown" ) {
188+ const nextElement = event . currentTarget . nextElementSibling
189+ if ( nextElement ) {
190+ event . preventDefault ( )
191+ ; ( nextElement as HTMLElement ) . focus ( )
192+ }
193+ }
194+ }
0 commit comments