Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a5e9e3f
Extract both LookingForMore instances to one component
hasparus Dec 5, 2025
6acec96
Add `ResourceTag` type
hasparus Dec 5, 2025
44b9aed
Add JSON files for Resources Hub data
hasparus Dec 5, 2025
87c212b
Add Resources missing from Northstar's Google Sheets
hasparus Dec 5, 2025
aafdec9
Add arktype to main package
hasparus Dec 5, 2025
54bb9c1
Add icons from Figma
hasparus Dec 5, 2025
a7465ab
Add ResourceMetadata schema
hasparus Dec 5, 2025
9d2d29b
Make LearnHeroStripes configurable
hasparus Dec 5, 2025
e7d8bbd
Make the blog section mobile friendly
hasparus Dec 5, 2025
99ab5a9
Add categories section
hasparus Dec 5, 2025
347046c
Add resources hub index
hasparus Dec 5, 2025
d0620a0
Add reading resources section
hasparus Dec 5, 2025
295bf58
Add Resources Hero
hasparus Dec 5, 2025
04cc435
Add specification section
hasparus Dec 5, 2025
ecc2e08
Add tools and libraries section
hasparus Dec 5, 2025
c16db1b
Add video resources section
hasparus Dec 5, 2025
a9f5b56
Add redirects from old to new pages
hasparus Dec 5, 2025
8ba9c40
Lock arktype version in sync-working-groups package
hasparus Dec 5, 2025
3bd6193
Add transition
hasparus Dec 5, 2025
9a52ec1
Tweak colors
hasparus Dec 5, 2025
91f0375
Use proper icon
hasparus Dec 5, 2025
888aea5
Optimize and simplify icons
hasparus Dec 5, 2025
ae2a582
Format
hasparus Dec 5, 2025
b9efe84
Make the Specification section pink
hasparus Dec 6, 2025
2e1c8b1
Use the Eyebrow component
hasparus Dec 6, 2025
debbeda
Update styles
hasparus Dec 6, 2025
5f8b974
Tag `src/code/*.mdx` files
hasparus Dec 6, 2025
0c55c11
Read json files
hasparus Dec 6, 2025
a578a02
Import proper icons
hasparus Dec 6, 2025
168b712
Use proper icons, tweak layout
hasparus Dec 6, 2025
cc0d8da
Improve styles
hasparus Dec 6, 2025
b7c2aad
Improve the styles
hasparus Dec 8, 2025
faa585d
Truncate breadcrumbs
hasparus Dec 8, 2025
49f128b
Render blog category links in blog section
hasparus Dec 8, 2025
f7a8eca
Improve styles
hasparus Dec 8, 2025
c9eaa31
Implement mobile styles for blog section
hasparus Dec 8, 2025
640a164
Improve category list style
hasparus Dec 8, 2025
1b03575
Unify card styling with the new design
hasparus Dec 8, 2025
fb00a16
Draft `resources/[category]` page.
hasparus Dec 8, 2025
874ebc0
Change "client" tag to "frontend"
hasparus Dec 8, 2025
df9eb6a
Read code/**/*.mdx
hasparus Dec 9, 2025
8be74e4
change server tag to backend tag
hasparus Dec 9, 2025
f811d67
Improve styles
hasparus Dec 9, 2025
ae3397e
Add typography-h4
hasparus Dec 9, 2025
d44f4d4
Link to proper subpages
hasparus Dec 9, 2025
bc5c685
Add ResourceHubCard
hasparus Dec 9, 2025
abb04fe
Add missing authors
hasparus Dec 9, 2025
16c1457
wip
hasparus Dec 9, 2025
c92c199
Read blog articles, remove redundant JSON files
hasparus Dec 9, 2025
cdb6b50
Remove duplicates
hasparus Dec 9, 2025
b245d89
Change tag to blog-or-newsletter to disambiguate with "blog or post"
hasparus Dec 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
"@tailwindcss/typography": "^0.5.15",
"arktype": "2.1.28",
"autoprefixer": "^10.4.20",
"calendar-link": "^2.10.0",
"clsx": "^2.1.1",
Expand Down
41 changes: 22 additions & 19 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion scripts/sync-working-groups/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"start": "node ./sync-working-groups.ts"
},
"dependencies": {
"arktype": "^2.1.27"
"arktype": "2.1.28"
}
}
3 changes: 2 additions & 1 deletion src/_design-system/breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ export const Breadcrumbs = ({

const title = extractStringsFromReactNode(item.title)
const className = clsx(
"text-neu-700 dark:text-neu-400 min-w-6 last:text-neu-800 dark:last:text-neu-800 leading-none",
"text-neu-700 dark:text-neu-400 min-w-6 last:text-neu-800 dark:last:text-neu-800 leading-none whitespace-pre",
href &&
"gql-focus-visible ring-inset hover:text-neu-900 hover:underline underline-offset-2",
item.title.length > 8 ? "overflow-hidden truncate" : "shrink-0",
)

return (
Expand Down
61 changes: 61 additions & 0 deletions src/app/(main)/resources/[category]/blog-posts-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client"

import { Button } from "@/app/conf/_design-system/button"
import { Eyebrow } from "@/_design-system/eyebrow"
import { BlogCard } from "@/components/blog-page/blog-card"

export interface BlogPost {
href: string
title: string
author: string
date?: Date
tags: string[]
}

export interface BlogPostsSectionProps {
title: string
description: string
posts: BlogPost[]
readAllHref?: string
readAllLabel?: string
}

export function BlogPostsSection({
title,
description,
posts,
readAllHref = "/blog",
readAllLabel = "Read all GraphQL stories",
}: BlogPostsSectionProps) {
return (
<section className="flex flex-col gap-10 lg:gap-16">
<header className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
<div className="flex flex-col gap-3">
<Eyebrow>Blog posts</Eyebrow>
<h2 className="typography-h2 max-w-[700px] text-pretty">{title}</h2>
<p className="typography-body-md max-w-[577px] text-neu-800">
{description}
</p>
</div>
<Button href={readAllHref} variant="secondary" size="md">
{readAllLabel}
</Button>
</header>

<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{posts.map(post => (
<BlogCard
key={post.href}
route={post.href}
frontMatter={{
title: post.title,
byline: post.author,
date: post.date ?? new Date(),
tags: post.tags,
}}
/>
))}
</div>
</section>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import path from "node:path"
import { glob } from "node:fs/promises"
import { readFile } from "node:fs/promises"
import matter from "gray-matter"

import { clsx } from "clsx"
import { Button } from "@/app/conf/_design-system/button"
import { Eyebrow } from "@/_design-system/eyebrow"
import slugMap from "@/code/slug-map.json"
import { type Topic } from "@/resources/types"

interface LibraryEntry {
name: string
href?: string
group: string
tags: string[]
}

const librariesPromise = loadLibraries()

async function loadLibraries(): Promise<LibraryEntry[]> {
const entries: LibraryEntry[] = []

for await (const file of glob("src/code/**/*.md")) {
const relative = path.relative("src/code", file)
const segments = relative.split(path.sep)
const top = segments[0]
const group =
top === "language-support" ? (segments[1] ?? "language-support") : top
if (!group) continue

const raw = await readFile(file, "utf8")
const { data } = matter(raw)
const tags: string[] = Array.isArray(data.tags) ? data.tags : []
if (!tags.includes("tools-and-libraries")) continue

const name: string | undefined = data.name
if (!name) continue

const href: string | undefined =
data.url ??
(data.github ? `https://github.com/${data.github}` : undefined) ??
(data.npm ? `https://npmjs.com/package/${data.npm}` : undefined)

entries.push({ name, href, group, tags })
}

return entries
}

function displayName(id: string) {
const key = id as keyof typeof slugMap
return slugMap[key] ?? id
}

export async function CategoryToolsLibrariesSection({
category,
}: {
category: Topic
}) {
const libraries = await librariesPromise
const filtered = libraries.filter(item => item.tags.includes(category))

const grouped = Array.from(
filtered.reduce<Map<string, LibraryEntry[]>>((acc, item) => {
const list = acc.get(item.group) ?? []
list.push(item)
acc.set(item.group, list)
return acc
}, new Map()),
)
.map(([group, items]) => ({
id: group,
name: displayName(group),
items: items
.sort((a, b) =>
a.name.localeCompare(b.name, "en", { sensitivity: "base" }),
)
.slice(0, 20),
}))
.sort((a, b) => a.name.localeCompare(b.name, "en", { sensitivity: "base" }))

if (grouped.length === 0) {
return null
}

const desktopLayoutClass =
grouped.length > 2 ? "lg:grid lg:grid-cols-1 lg:gap-6" : "lg:grid lg:grid-cols-2 lg:gap-6"

return (
<section
id="tools-and-libraries"
className="flex flex-col gap-8 border border-sec-base bg-sec-lighter p-6 dark:border-sec-darker dark:bg-sec-darker/15 lg:gap-10 lg:p-10"
>
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-col gap-3">
<Eyebrow className="!text-sec-darker dark:!text-sec-light">
key tools & libraries
</Eyebrow>
<h2 className="typography-h3 text-pretty">
Build GraphQL with tools and libraries
</h2>
<p className="typography-body-md text-neu-800">
Explore language and platform tooling to ship production-ready
graphs.
</p>
</div>
<Button href="/code" variant="tertiary" className="w-fit">
See all Tools & Libraries
</Button>
</div>

<div
className={clsx(
"flex gap-4 overflow-x-auto pb-2 lg:overflow-visible",
desktopLayoutClass,
)}
>
{grouped.map(group => (
<div
key={group.id}
className="min-w-[280px] shrink-0 border border-neu-200 bg-neu-0 shadow-[0_1px_0_#E5E7EB] dark:border-neu-100 dark:bg-neu-0/60 lg:min-w-0"
>
<div className="flex items-center gap-3 border-b border-neu-200 px-4 py-3 text-neu-900 dark:border-neu-100">
<span className="font-mono text-sm uppercase text-neu-700">
{group.name}
</span>
</div>
<ul className="divide-y divide-neu-200 dark:divide-neu-100">
{group.items.map(item => (
<li key={`${group.id}-${item.name}`}>
{item.href ? (
<a
href={item.href}
className="flex items-center justify-between px-4 py-3 text-neu-900 transition-colors hover:bg-neu-50 dark:hover:bg-neu-50/50"
>
<span>{item.name}</span>
<span aria-hidden className="text-neu-500">
</span>
</a>
) : (
<span className="flex items-center justify-between px-4 py-3 text-neu-900">
<span>{item.name}</span>
</span>
)}
</li>
))}
</ul>
</div>
))}
</div>
</section>
)
}
Loading