@@ -3,6 +3,7 @@ import { glob } from "node:fs/promises"
33import { readFile } from "node:fs/promises"
44import matter from "gray-matter"
55
6+ import type { CSSProperties } from "react"
67import { Button } from "@/app/conf/_design-system/button"
78import blurCorner from "./blur-corner.webp"
89import { Eyebrow } from "@/_design-system/eyebrow"
@@ -46,7 +47,13 @@ async function loadLibraries(): Promise<LibraryEntry[]> {
4647 entries . push ( { name, href, group, tags } )
4748 }
4849
49- return entries
50+ const deduped = entries . filter (
51+ ( item , index , self ) =>
52+ index ===
53+ self . findIndex ( t => t . name . toLowerCase ( ) === item . name . toLowerCase ( ) ) ,
54+ )
55+
56+ return deduped
5057}
5158
5259function displayName ( id : string ) {
@@ -100,7 +107,7 @@ export async function CategoryToolsLibrariesSection({
100107 < h2 className = "typography-h3 text-pretty" >
101108 Build GraphQL with tools and libraries
102109 </ h2 >
103- < p className = "typography-body-md text-neu-700 dark:text-neu-100 " >
110+ < p className = "typography-body-md text-neu-800 " >
104111 Explore language and platform tooling to ship production-ready
105112 graphs.
106113 </ p >
@@ -111,35 +118,53 @@ export async function CategoryToolsLibrariesSection({
111118 </ div >
112119
113120 < div className = "flex flex-wrap gap-4 pb-2 lg:overflow-visible" >
114- { grouped . map ( group => (
115- < div
116- key = { group . id }
117- className = "min-w-[480px] shrink-0 grow border border-neu-200 bg-neu-50 dark:bg-neu-50/50 lg:w-1/3 lg:min-w-0"
118- >
119- < div className = "typography-body-lg flex items-center gap-3 border-b border-inherit bg-neu-50 px-4 py-3 text-neu-900" >
120- { /* todo: we should have an icon here */ }
121- { group . name }
121+ { grouped . map ( ( group , index ) => {
122+ const nextLength = grouped [ index + 1 ] ?. items . length ?? 0
123+ const columns =
124+ nextLength > 0 && group . items . length >= nextLength * 1.9 ? 2 : 1
125+ const listStyle = { "--item-columns" : columns } as CSSProperties
126+ const breakIndex =
127+ columns === 2 ? Math . floor ( group . items . length / 2 ) : 0
128+
129+ return (
130+ < div
131+ key = { group . id }
132+ className = "min-w-[480px] shrink-0 grow border border-neu-200 bg-neu-50 dark:bg-neu-50/50 lg:w-1/3 lg:min-w-0"
133+ >
134+ < div className = "typography-body-lg flex items-center gap-3 border-b border-inherit bg-neu-50 px-4 py-3 text-neu-900" >
135+ { /* todo: we should have an icon here */ }
136+ { group . name }
137+ </ div >
138+ < ul
139+ className = "gap-0 divide-y divide-neu-200 dark:divide-neu-100 lg:[column-count:var(--item-columns,1)]"
140+ style = { listStyle }
141+ >
142+ { group . items . map ( ( item , i ) => (
143+ < li
144+ key = { `${ group . id } -${ item . name } ` }
145+ style = { {
146+ borderTop : breakIndex === i ? "none" : "" ,
147+ borderLeftWidth : breakIndex >= i ? "1px" : "" ,
148+ } }
149+ >
150+ { item . href ? (
151+ < a
152+ href = { item . href }
153+ className = "flex items-center justify-between bg-neu-0/40 px-4 py-3 text-neu-900 transition-colors hover:bg-neu-0 hover:duration-0"
154+ >
155+ { item . name }
156+ </ a >
157+ ) : (
158+ < span className = "flex items-center justify-between bg-neu-50 px-4 py-3 text-neu-900" >
159+ { item . name }
160+ </ span >
161+ ) }
162+ </ li >
163+ ) ) }
164+ </ ul >
122165 </ div >
123- < ul className = "divide-y divide-neu-200 dark:divide-neu-100" >
124- { group . items . map ( item => (
125- < li key = { `${ group . id } -${ item . name } ` } >
126- { item . href ? (
127- < a
128- href = { item . href }
129- className = "flex items-center justify-between bg-neu-0/40 px-4 py-3 text-neu-900 transition-colors hover:bg-neu-0 hover:duration-0"
130- >
131- { item . name }
132- </ a >
133- ) : (
134- < span className = "flex items-center justify-between bg-neu-50 px-4 py-3 text-neu-900" >
135- { item . name }
136- </ span >
137- ) }
138- </ li >
139- ) ) }
140- </ ul >
141- </ div >
142- ) ) }
166+ )
167+ } ) }
143168 </ div >
144169 </ section >
145170 </ div >
0 commit comments