From 415568c6cab5907270f09fbdfa0e0e4b63e136e6 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:03:17 +0100 Subject: [PATCH 01/23] Grab authors from The Guild blog --- packages/web/docs/src/authors.ts | 54 ---- packages/web/docs/src/authors/index.ts | 255 ++++++++++++++++++ .../team-section => authors}/saihaj.webp | Bin .../index.tsx => team-section.tsx} | 102 ++----- 4 files changed, 283 insertions(+), 128 deletions(-) delete mode 100644 packages/web/docs/src/authors.ts create mode 100644 packages/web/docs/src/authors/index.ts rename packages/web/docs/src/{components/team-section => authors}/saihaj.webp (100%) rename packages/web/docs/src/components/{team-section/index.tsx => team-section.tsx} (58%) diff --git a/packages/web/docs/src/authors.ts b/packages/web/docs/src/authors.ts deleted file mode 100644 index d4776c5559f..00000000000 --- a/packages/web/docs/src/authors.ts +++ /dev/null @@ -1,54 +0,0 @@ -type Author = { - name: string; - link: `https://${string}`; - github?: string; - twitter?: string; -}; - -export const authors: Record = { - kamil: { - name: 'Kamil Kisiela', - link: 'https://x.com/kamilkisiela', - github: 'kamilkisiela', - }, - laurin: { - name: 'Laurin Quast', - link: 'https://x.com/n1rual', - github: 'n1ru4l', - }, - arda: { - name: 'Arda Tanrikulu', - link: 'https://twitter.com/ardatanrikulu', - github: 'ardatan', - }, - aleksandra: { - name: 'Aleksandra Sikora', - link: 'https://x.com/aleksandrasays', - github: 'beerose', - }, - jiri: { - name: 'Jiri Spac', - link: 'https://x.com/capajj', - github: 'capaj', - }, - dimitri: { - name: 'Dimitri Postolov', - link: 'https://x.com/dimaMachina_', - github: 'dimaMachina', - }, - denis: { - name: 'Denis Badurina', - link: 'https://github.com/enisdenjo', - github: 'enisdenjo', - }, - dotan: { - name: 'Dotan Simha', - link: 'https://github.com/dotansimha', - github: 'dotansimha', - }, - jdolle: { - name: 'Jeff Dolle', - link: 'https://github.com/jdolle', - github: 'jdolle', - }, -}; diff --git a/packages/web/docs/src/authors/index.ts b/packages/web/docs/src/authors/index.ts new file mode 100644 index 00000000000..765478b61dc --- /dev/null +++ b/packages/web/docs/src/authors/index.ts @@ -0,0 +1,255 @@ +import type { StaticImageData } from 'next/image'; +import saihajPhoto from './saihaj.webp'; + +export type Author = + | { + name: string; + link: `https://${string}`; + twitter?: string; + github?: string; + avatar: string | StaticImageData; + } + | { + name: string; + link: `https://${string}`; + twitter?: string; + github: string; + // if the author has no avatar, we'll take it from GitHub + avatar?: string | StaticImageData; + }; + +export const authors = { + kamil: { + name: 'Kamil Kisiela', + link: 'https://x.com/kamilkisiela', + github: 'kamilkisiela', + }, + laurin: { + name: 'Laurin Quast', + link: 'https://twitter.com/n1rual', + github: 'n1ru4l', + }, + arda: { + name: 'Arda Tanrikulu', + link: 'https://twitter.com/ardatanrikulu', + github: 'ardatan', + }, + aleksandra: { + name: 'Aleksandra Sikora', + link: 'https://x.com/aleksandrasays', + github: 'beerose', + }, + jiri: { + name: 'Jiri Spac', + link: 'https://x.com/capajj', + github: 'capaj', + }, + dimitri: { + name: 'Dimitri Postolov', + link: 'https://x.com/dimaMachina_', + github: 'dimaMachina', + }, + denis: { + name: 'Denis Badurina', + link: 'https://github.com/enisdenjo', + github: 'enisdenjo', + }, + dotan: { + name: 'Dotan Simha', + link: 'https://github.com/dotansimha', + github: 'dotansimha', + }, + jdolle: { + name: 'Jeff Dolle', + link: 'https://github.com/jdolle', + github: 'jdolle', + }, + jason: { + name: 'Jason Kuhrt', + link: 'https://github.com/jasonkuhrt', + github: 'jasonkuhrt', + }, + valentin: { + name: 'Valentin Cocaud', + link: 'https://github.com/EmrysMyrddin', + github: 'EmrysMyrddin', + }, + tuval: { + name: 'Tuval Simha', + link: 'https://github.com/tuvalsimha', + github: 'tuvalsimha', + }, + uri: { + name: 'Uri Goldshtein', + link: 'https://github.com/Urigo', + github: 'Urigo', + }, + gil: { + name: 'Gil Gardosh', + link: 'https://github.com/gilgardosh', + github: 'gilgardosh', + }, + saihaj: { + name: 'Saihajpreet Singh', + link: 'https://github.com/saihaj', + github: 'saihaj', + avatar: saihajPhoto, + }, + eytan: { + name: 'Eytan Manor', + link: 'https://twitter.com/eytan_manor', + github: 'DAB0mB', + }, + leonardo: { + name: 'Leonardo Ascione', + link: 'https://twitter.com/leonardfactory', + github: 'leonardfactory', + }, + niccolo: { + name: 'Niccolo Belli', + link: 'https://twitter.com/niccolobelli', + github: 'darkbasic', + }, + david: { + name: 'David Yahalomi', + link: 'https://twitter.com/DavidYahalomi', + github: 'davidyaha', + }, + enisdenjo: { + name: 'Denis Badurina', + link: 'https://twitter.com/enisdenjo', + github: 'enisdenjo', + }, + ephelan: { + name: 'Enda Phelan', + link: 'https://twitter.com/PhelanEnda', + github: 'craicoverflow', + }, + soichi: { + name: 'Soichi Takamura', + link: 'https://twitter.com/piglovesyou1', + github: 'piglovesyou', + }, + giladtidhar: { + name: 'Gilad Tidhar', + link: 'https://twitter.com/tidhar_gilad', + github: 'giladd123', + }, + gmac: { + name: 'Greg MacWilliam', + link: 'https://twitter.com/gmacwilliam', + github: 'gmac', + }, + croutonn: { + name: 'Yuta Haga', + link: 'https://twitter.com/croutnn', + github: 'croutonn', + }, + jycouet: { + name: 'Jean-Yves Couët', + link: 'https://twitter.com/jycouet', + github: 'jycouet', + }, + AlecAivazis: { + name: 'Alec Aivazis', + link: 'https://twitter.com/AlecAivazis', + github: 'AlecAivazis', + }, + tvvignesh: { + name: 'Vignesh T.V.', + link: 'https://twitter.com/techahoy', + github: 'tvvignesh', + }, + charlypoly: { + name: 'Charly Poly', + link: 'https://charlypoly.com', + github: 'charlypoly', + }, + maticzav: { + name: 'Matic Zavadlal', + link: 'https://twitter.com/maticzav', + github: 'maticzav', + }, + pablosz: { + name: 'Pablo Sáez', + link: 'https://twitter.com/PabloSz_', + github: 'PabloSzx', + }, + dimatill: { + name: 'Dmitry Til', + link: 'https://github.com/dimatill', + github: 'dimatill', + }, + gthau: { + name: 'Ghislain Thau', + link: 'https://github.com/gthau', + github: 'gthau', + }, + notrab: { + name: 'Jamie Barton', + link: 'https://graphql.wtf', + github: 'notrab', + }, + tuvalSimha: { + name: 'Tuval Simha', + link: 'https://twitter.com/SimhaTuval', + github: 'TuvalSimha', + }, + gabotechs: { + name: 'Gabriel Musat', + link: 'https://github.com/gabotechs', + github: 'gabotechs', + }, + shuding: { + name: 'Shu Ding', + link: 'https://shud.in', + github: 'shuding', + }, + eddeee888: { + name: 'Eddy Nguyen', + link: 'https://twitter.com/eddeee888', + github: 'eddeee888', + }, + tshedor: { + name: 'Tim Shedor', + link: 'https://github.com/tshedor', + github: 'tshedor', + }, + josiassejod1: { + name: 'Dalvin Sejour', + link: 'https://github.com/josiassejod1', + github: 'josiassejod1', + }, + warrenjday: { + name: 'Warren Day', + link: 'https://twitter.com/warrenjday', + github: 'warrenjday', + }, + jessevelden: { + name: 'Jesse van der Velden', + link: 'https://twitter.com/JesseVelden', + github: 'jessevelden', + }, + yassin: { + name: 'Yassin Eldeeb', + link: 'https://twitter.com/yassineldeeb7', + github: 'yassineldeeb', + }, + chimame: { + name: 'Rito Tamata', + link: 'https://twitter.com/chimame_rt', + github: 'chimame', + }, + nohehf: { + name: 'Nohé Hinniger-Foray', + link: 'https://twitter.com/NoheHf', + github: 'nohehf', + }, + egoodwinx: { + name: 'Emily Goodwin', + link: 'https://www.linkedin.com/in/emily-y-goodwin/', + github: 'egoodwinx', + }, +} satisfies Record; + +export type AuthorId = keyof typeof authors; diff --git a/packages/web/docs/src/components/team-section/saihaj.webp b/packages/web/docs/src/authors/saihaj.webp similarity index 100% rename from packages/web/docs/src/components/team-section/saihaj.webp rename to packages/web/docs/src/authors/saihaj.webp diff --git a/packages/web/docs/src/components/team-section/index.tsx b/packages/web/docs/src/components/team-section.tsx similarity index 58% rename from packages/web/docs/src/components/team-section/index.tsx rename to packages/web/docs/src/components/team-section.tsx index ffadd063769..a0b4f26ca55 100644 --- a/packages/web/docs/src/components/team-section/index.tsx +++ b/packages/web/docs/src/components/team-section.tsx @@ -1,7 +1,7 @@ -import Image, { StaticImageData } from 'next/image'; +import Image from 'next/image'; import { CallToAction, cn, Heading } from '@theguild/components'; -import { ArrowIcon } from '../arrow-icon'; -import saihajPhoto from './saihaj.webp'; +import { Author, authors } from '../authors'; +import { ArrowIcon } from './arrow-icon'; export function TeamSection({ className }: { className?: string }) { return ( @@ -36,7 +36,7 @@ export function TeamSection({ className }: { className?: string }) { @@ -44,67 +44,19 @@ export function TeamSection({ className }: { className?: string }) { ); } -type TeamMember = [name: string, avatar: string | StaticImageData, social: string]; -const team: TeamMember[] = [ - [ - 'Denis Badurina', - 'https://avatars.githubusercontent.com/enisdenjo?v=4&s=180', - 'https://github.com/enisdenjo', - ], - [ - 'Dotan Simha', - 'https://avatars.githubusercontent.com/dotansimha?v=4&s=180', - 'https://github.com/dotansimha', - ], - [ - 'Gil Gardosh', - 'https://avatars.githubusercontent.com/gilgardosh?v=4&s=180', - 'https://github.com/gilgardosh', - ], - - [ - 'Kamil Kisiela', - 'https://avatars.githubusercontent.com/kamilkisiela?v=4&s=180', - 'https://github.com/kamilkisiela', - ], - [ - 'Laurin Quast', - 'https://avatars.githubusercontent.com/n1ru4l?v=4&s=180', - 'https://github.com/n1ru4l', - ], - // ['Noam Malka', noamPhoto, 'https://noam-malka.com/'], - ['Saihajpreet Singh', saihajPhoto, 'https://github.com/saihaj'], - - [ - 'Tuval Simha', - 'https://avatars.githubusercontent.com/tuvalsimha?v=4&s=180', - 'https://github.com/tuvalsimha', - ], - [ - 'Uri Goldshtein', - 'https://avatars.githubusercontent.com/Urigo?v=4&s=180', - 'https://github.com/Urigo', - ], - [ - 'Valentin Cocaud', - 'https://avatars.githubusercontent.com/EmrysMyrddin?v=4&s=180', - 'https://github.com/EmrysMyrddin', - ], - [ - 'Jason Kuhrt', - 'https://avatars.githubusercontent.com/jasonkuhrt?v=4&s=180', - 'https://github.com/jasonkuhrt', - ], - [ - 'Arda Tanrikulu', - 'https://avatars.githubusercontent.com/ardatan?v=4&s=180', - 'https://github.com/ardatan', - ], - [ - 'Jeff Dolle', - 'https://avatars.githubusercontent.com/jdolle?v=4&s=180', - 'https://github.com/jdolle', - ], +const teamMembers: Author[] = [ + authors.denis, + authors.dotan, + authors.gil, + authors.kamil, + authors.laurin, + authors.saihaj, + authors.tuval, + authors.uri, + authors.valentin, + authors.jason, + authors.arda, + authors.jdolle, ]; function TeamGallery(props: React.HTMLAttributes) { @@ -114,14 +66,14 @@ function TeamGallery(props: React.HTMLAttributes) { className={cn( 'nextra-scrollbar flex shrink-0 grid-cols-5 flex-row items-stretch justify-items-stretch gap-2 [scrollbar-color:#00000029_transparent] [scrollbar-width:auto] max-lg:overflow-auto lg:flex-wrap lg:gap-6 lg:max-xl:grid', '[--size:120px]', - team.length <= 12 && 'grid-cols-6 xl:[&>:nth-child(8n-7)]:ml-[calc(var(--size)/2)]', - team.length === 13 && + teamMembers.length <= 12 && 'grid-cols-6 xl:[&>:nth-child(8n-7)]:ml-[calc(var(--size)/2)]', + teamMembers.length === 13 && 'grid-cols-5 xl:[--size:112px] xl:[&>:nth-child(9n-8)]:ml-[calc(var(--size)/2)]', - team.length > 13 && 'nextra-scrollbar size-full flex-col p-1 xl:overflow-scroll', + teamMembers.length > 13 && 'nextra-scrollbar size-full flex-col p-1 xl:overflow-scroll', props.className, )} > - {team.map((member, i) => ( + {teamMembers.map((member, i) => (
  • @@ -130,11 +82,11 @@ function TeamGallery(props: React.HTMLAttributes) { ); } -function TeamAvatar({ data: [name, avatar, social] }: { data: TeamMember }) { +function TeamAvatar({ data: { name, avatar, link, github } }: { data: Author }) { return ( @@ -143,9 +95,11 @@ function TeamAvatar({ data: [name, avatar, social] }: { data: TeamMember }) { From ec776d3b5ed1c73a05263bd5dce1974d58d57fd6 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:05:03 +0100 Subject: [PATCH 02/23] Move Case Study prose styles one level up --- .eslintrc.cjs | 1 + .../web/docs/src/app/case-studies/(posts)/layout.tsx | 10 +++++----- .../case-studies-styles.css => hive-prose-styles.css} | 7 +------ .../web/docs/src/app/{case-studies => }/quotemark.svg | 0 4 files changed, 7 insertions(+), 11 deletions(-) rename packages/web/docs/src/app/{case-studies/case-studies-styles.css => hive-prose-styles.css} (89%) rename packages/web/docs/src/app/{case-studies => }/quotemark.svg (100%) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 8bfe5454026..6e31851af63 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -231,6 +231,7 @@ module.exports = { 'nextra-scrollbar', 'no-scrollbar', // from Nextra 'hive-slider', + 'hive-prose', 'subheader', ], config: path.join(__dirname, './packages/web/docs/tailwind.config.ts'), diff --git a/packages/web/docs/src/app/case-studies/(posts)/layout.tsx b/packages/web/docs/src/app/case-studies/(posts)/layout.tsx index 15f3d75b30f..19cc3261add 100644 --- a/packages/web/docs/src/app/case-studies/(posts)/layout.tsx +++ b/packages/web/docs/src/app/case-studies/(posts)/layout.tsx @@ -1,21 +1,21 @@ import { GetYourAPIGameWhite } from '#components/get-your-api-game-white'; -import { cn } from '@theguild/components'; +import { cn, HiveLayoutConfig } from '@theguild/components'; import { CaseStudiesHeader } from '../case-studies-header'; import { MoreStoriesSection } from '../more-stories-section'; -import '../case-studies-styles.css'; +import '../../hive-prose-styles.css'; import { LookingToUseHiveUpsellBlock } from '../looking-to-use-hive-upsell-block'; // We can't use CSS Modules together with Tailwind, // because the class responsible for dark mode gets transformed // so `dark:` prefixes don't work. -const ONE_OFF_CLASS_CASE_STUDIES = 'case-studies'; const MAIN_CONTENT = 'main-content'; export default function CaseStudiesLayout({ children }: { children: React.ReactNode }) { return ( -
    +
    + -
    +
    div:first-of-type>:first-child]:hidden')}> {children}
    diff --git a/packages/web/docs/src/app/case-studies/case-studies-styles.css b/packages/web/docs/src/app/hive-prose-styles.css similarity index 89% rename from packages/web/docs/src/app/case-studies/case-studies-styles.css rename to packages/web/docs/src/app/hive-prose-styles.css index 55072657280..35485a26206 100644 --- a/packages/web/docs/src/app/case-studies/case-studies-styles.css +++ b/packages/web/docs/src/app/hive-prose-styles.css @@ -1,4 +1,4 @@ -.case-studies { +.hive-prose { --nextra-content-width: 1208px; --article-max-width: 640px; @@ -7,11 +7,6 @@ width: var(--nextra-content-width); max-width: 100%; - /* hide leftmost column to left-align the case study */ - & > div:first-of-type > :first-child { - @apply hidden; - } - & > div { @apply ml-0 pl-6 md:pl-12; } diff --git a/packages/web/docs/src/app/case-studies/quotemark.svg b/packages/web/docs/src/app/quotemark.svg similarity index 100% rename from packages/web/docs/src/app/case-studies/quotemark.svg rename to packages/web/docs/src/app/quotemark.svg From ed5247ce8e67e319270cd3a5f6f438023c2cd2cd Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:05:52 +0100 Subject: [PATCH 03/23] Add a TODO comment to inconsistent SocialAvatar --- packages/web/docs/src/components/social-avatar.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/web/docs/src/components/social-avatar.tsx b/packages/web/docs/src/components/social-avatar.tsx index 2a653fce039..67ef3004b07 100644 --- a/packages/web/docs/src/components/social-avatar.tsx +++ b/packages/web/docs/src/components/social-avatar.tsx @@ -3,6 +3,10 @@ import { ReactElement } from 'react'; import ReactAvatar from 'react-avatar'; +/** + * TODO: We should drop this and use the avatars defined in authors/index.ts + * for consistency with the team section on the landing page. + */ export const SocialAvatar = ({ author, }: { From 7a3c603a14fbd70825b2b8bbb31fd826ffce0b80 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:06:47 +0100 Subject: [PATCH 04/23] Move useFrontmatter one level up --- .../app/case-studies/case-studies-header.tsx | 6 ++--- .../other-case-studies.tsx | 2 +- .../src/app/case-studies/use-frontmatter.ts | 22 ---------------- .../docs/src/components/use-frontmatter.ts | 26 +++++++++++++++++++ 4 files changed, 30 insertions(+), 26 deletions(-) delete mode 100644 packages/web/docs/src/app/case-studies/use-frontmatter.ts create mode 100644 packages/web/docs/src/components/use-frontmatter.ts diff --git a/packages/web/docs/src/app/case-studies/case-studies-header.tsx b/packages/web/docs/src/app/case-studies/case-studies-header.tsx index 766220ad4a2..c82ca542182 100644 --- a/packages/web/docs/src/app/case-studies/case-studies-header.tsx +++ b/packages/web/docs/src/app/case-studies/case-studies-header.tsx @@ -2,12 +2,12 @@ import { cn, DecorationIsolation, Heading } from '@theguild/components'; import { SmallAvatar } from '../../components/small-avatar'; -import { CaseStudyAuthor } from './case-study-types'; +import { useFrontmatter } from '../../components/use-frontmatter'; +import { CaseStudyAuthor, CaseStudyFrontmatter } from './case-study-types'; import { companyLogos } from './company-logos'; -import { useFrontmatter } from './use-frontmatter'; export function CaseStudiesHeader(props: React.HTMLAttributes) { - const { name, frontmatter } = useFrontmatter(); + const { name, frontmatter } = useFrontmatter(); if (!name) { throw new Error('unexpected'); diff --git a/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx b/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx index 7c2366c5877..c2e9a078c4c 100644 --- a/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx +++ b/packages/web/docs/src/app/case-studies/more-stories-section/other-case-studies.tsx @@ -1,9 +1,9 @@ 'use client'; +import { useFrontmatter } from '../../../components/use-frontmatter'; import { CaseStudyCard } from '../case-study-card'; import { CaseStudyFile } from '../case-study-types'; import { getCompanyLogo } from '../company-logos'; -import { useFrontmatter } from '../use-frontmatter'; export function OtherCaseStudies({ caseStudies }: { caseStudies: CaseStudyFile[] }) { const frontmatter = useFrontmatter(); diff --git a/packages/web/docs/src/app/case-studies/use-frontmatter.ts b/packages/web/docs/src/app/case-studies/use-frontmatter.ts deleted file mode 100644 index 498db9356b0..00000000000 --- a/packages/web/docs/src/app/case-studies/use-frontmatter.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use client'; - -import { useConfig } from '@theguild/components'; -import { CaseStudyFrontmatter } from './case-study-types'; - -/** - * TODO: Lobby Dima to add an imperative way to do this in a server component. - */ -export function useFrontmatter() { - const normalizePagesResult = useConfig().normalizePagesResult; - const frontmatter = normalizePagesResult.activeMetadata as CaseStudyFrontmatter; - const name = normalizePagesResult.activePath.at(-1)?.name; - - if (!name) { - throw new Error('unexpected'); - } - - return { - frontmatter, - name, - }; -} diff --git a/packages/web/docs/src/components/use-frontmatter.ts b/packages/web/docs/src/components/use-frontmatter.ts new file mode 100644 index 00000000000..7551d8e51bc --- /dev/null +++ b/packages/web/docs/src/components/use-frontmatter.ts @@ -0,0 +1,26 @@ +'use client'; + +import { useConfig } from '@theguild/components'; + +/** + * Dima said there's no possible way to access frontmatter imperatively + * from a server component in Nextra, so we have a hook for client components. + */ +export function useFrontmatter< + // this is unsafe, but we're in a blog, and the frontmatter type is + // controlled by us. if it crashes, we can just fix the frontmatter in markdown + TFrontmatter, +>() { + const normalizePagesResult = useConfig().normalizePagesResult; + const frontmatter = normalizePagesResult.activeMetadata as TFrontmatter; + const name = normalizePagesResult.activePath.at(-1)?.name; + + if (!name) { + throw new Error('unexpected'); + } + + return { + frontmatter, + name, + }; +} From d782df4e0531df9ab8b6b973da832b0185497237 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:07:26 +0100 Subject: [PATCH 05/23] Configure Blog page in Nextra --- packages/web/docs/src/app/_meta.ts | 5 ++++- packages/web/docs/src/app/blog/_meta.ts | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 packages/web/docs/src/app/blog/_meta.ts diff --git a/packages/web/docs/src/app/_meta.ts b/packages/web/docs/src/app/_meta.ts index 52a18a6d219..b4e667621d5 100644 --- a/packages/web/docs/src/app/_meta.ts +++ b/packages/web/docs/src/app/_meta.ts @@ -82,7 +82,10 @@ const meta: Record> = { blog: { title: 'Blog', type: 'page', - href: 'https://the-guild.dev/blog', + theme: { + breadcrumb: false, + sidebar: false, + }, }, github: { title: 'GitHub', diff --git a/packages/web/docs/src/app/blog/_meta.ts b/packages/web/docs/src/app/blog/_meta.ts new file mode 100644 index 00000000000..725ff3f30a0 --- /dev/null +++ b/packages/web/docs/src/app/blog/_meta.ts @@ -0,0 +1,5 @@ +export default { + '*': { + display: 'hidden', + }, +}; From cc6b013099f50db1e2d8f171eb8cc69a415e741d Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:14:48 +0100 Subject: [PATCH 06/23] Refactor MDX types out of Case Study files --- .../src/app/case-studies/case-study-types.ts | 16 +--------------- packages/web/docs/src/mdx-types.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 packages/web/docs/src/mdx-types.ts diff --git a/packages/web/docs/src/app/case-studies/case-study-types.ts b/packages/web/docs/src/app/case-studies/case-study-types.ts index 9898db9038f..3b9bf7fccbf 100644 --- a/packages/web/docs/src/app/case-studies/case-study-types.ts +++ b/packages/web/docs/src/app/case-studies/case-study-types.ts @@ -1,4 +1,4 @@ -import type { getPageMap } from '@theguild/components/server'; +import type { MdxFile } from '../../mdx-types'; export type CaseStudyFrontmatter = { title: string; @@ -13,18 +13,4 @@ export type CaseStudyAuthor = { avatar?: string; }; -/** - * TODO: This type should be exported from `nextra` and `@theguild/components` - */ -export type MdxFile = { - name: string; - route: string; - frontMatter?: FrontMatterType; -}; - export type CaseStudyFile = Required>; - -/** - * TODO: This should be exported from `nextra` and `@theguild/components` - */ -export type PageMapItem = Awaited>[number]; diff --git a/packages/web/docs/src/mdx-types.ts b/packages/web/docs/src/mdx-types.ts new file mode 100644 index 00000000000..7edaa158364 --- /dev/null +++ b/packages/web/docs/src/mdx-types.ts @@ -0,0 +1,16 @@ +import type { getPageMap } from '@theguild/components/server'; + +/** + * TODO: This type should be exported from `nextra` and `@theguild/components` + */ +export type MdxFile = { + name: string; + route: string; + frontMatter?: FrontMatterType; +}; + + +/** + * TODO: This should be exported from `nextra` and `@theguild/components` + */ +export type PageMapItem = Awaited>[number]; From 8c680769519a7726a165c2837dbc133c5aa0ed2a Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:18:59 +0100 Subject: [PATCH 07/23] Add blog types based on the posts from the old blog --- packages/web/docs/src/app/blog/blog-types.ts | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/web/docs/src/app/blog/blog-types.ts diff --git a/packages/web/docs/src/app/blog/blog-types.ts b/packages/web/docs/src/app/blog/blog-types.ts new file mode 100644 index 00000000000..af66fb7d7af --- /dev/null +++ b/packages/web/docs/src/app/blog/blog-types.ts @@ -0,0 +1,21 @@ +import type { StaticImageData } from 'next/image'; +import { AuthorId } from '../../authors'; +import { MdxFile, PageMapItem } from '../../mdx-types'; + +export interface BlogFrontmatter { + authors: AuthorId | AuthorId[]; + title: string; + date: string; + tags: string | string[]; + featured?: boolean; + image?: VideoPath | StaticImageData; + thumbnail?: StaticImageData; +} + +type VideoPath = `${string}.${'webm' | 'mp4'}`; + +export type BlogPostFile = Required>; + +export function isBlogPost(item: PageMapItem): item is BlogPostFile { + return item && 'route' in item && 'name' in item && 'frontMatter' in item && !!item.frontMatter; +} From b8df3da4b5d2541918b286c7c7030116343053cd Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:19:22 +0100 Subject: [PATCH 08/23] Migrate Comparison component from The Guild blog --- .../web/docs/src/components/comparison.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/web/docs/src/components/comparison.tsx diff --git a/packages/web/docs/src/components/comparison.tsx b/packages/web/docs/src/components/comparison.tsx new file mode 100644 index 00000000000..04100487c74 --- /dev/null +++ b/packages/web/docs/src/components/comparison.tsx @@ -0,0 +1,26 @@ +import { ReactElement, ReactNode } from 'react'; +import { cn } from '@theguild/components'; + +/** + * This is used in MDX blog posts. + */ +export function Comparison({ + children, + className, +}: { + children: ReactNode; + className: string; +}): ReactElement { + return ( +
    + {children} +
    + ); +} From 539d7b803404eada786c8ab6e1647f4ad44a6944 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:21:10 +0100 Subject: [PATCH 09/23] Update ProductUpdateAuthors to use the type and Hive green --- .../(posts)/product-update-header.tsx | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx b/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx index c9290dd95c8..cf84a895679 100644 --- a/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx +++ b/packages/web/docs/src/app/product-updates/(posts)/product-update-header.tsx @@ -2,29 +2,36 @@ import { format } from 'date-fns'; import { Anchor, useConfig } from '@theguild/components'; -import { authors } from '../../../authors'; +import { AuthorId, authors } from '../../../authors'; import { SocialAvatar } from '../../../components/social-avatar'; type Meta = { - authors: string[]; + authors: AuthorId[]; date: string; title: string; description: string; }; -const Authors = ({ meta }: { meta: Meta }) => { +export const ProductUpdateAuthors = ({ meta }: { meta: Pick }) => { const date = meta.date ? new Date(meta.date) : new Date(); if (meta.authors.length === 1) { - const author = authors[meta.authors[0]]; + const author = authors[meta.authors[0] as AuthorId]; + if (!author) { + throw new Error(`Author ${meta.authors[0]} not found`); + } return ( -
    +
    - + {author.name}
    {meta.authors.map(authorId => { - const author = authors[authorId]; + const author = authors[authorId as AuthorId]; + if (!author) { + throw new Error(`Author ${authorId} not found`); + } + return (
    - + {author.name} @@ -71,7 +86,7 @@ export const ProductUpdateHeader = () => { return (

    {metadata.title}

    - +
    ); }; From ce7022a0fb2c2395b6ea2264092342868d8f63f8 Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 21:23:55 +0100 Subject: [PATCH 10/23] Add Hive Blog index page --- .../src/app/blog/components/blog-card.tsx | 89 +++++++++++++++++++ .../app/blog/components/blog-page-hero.tsx | 53 +++++++++++ .../blog-post-layout/blog-post-header.tsx | 46 ++++++++++ .../blog-post-layout/blog-post-picture.tsx | 25 ++++++ .../src/app/blog/components/blog-tag-chip.tsx | 31 +++++++ .../company-news-and-press-section.tsx | 56 ++++++++++++ .../posts-by-tag/category-filter-link.tsx | 25 ++++++ .../posts-by-tag/category-select.tsx | 22 +++++ .../posts-by-tag/featured-posts.tsx | 34 +++++++ .../blog/components/posts-by-tag/index.tsx | 39 ++++++++ .../components/posts-by-tag/latest-posts.tsx | 31 +++++++ .../app/blog/components/pretty-print-tag.ts | 17 ++++ packages/web/docs/src/app/blog/page.tsx | 22 +++++ packages/web/docs/src/app/blog/tag/layout.tsx | 16 ++++ 14 files changed, 506 insertions(+) create mode 100644 packages/web/docs/src/app/blog/components/blog-card.tsx create mode 100644 packages/web/docs/src/app/blog/components/blog-page-hero.tsx create mode 100644 packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx create mode 100644 packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx create mode 100644 packages/web/docs/src/app/blog/components/blog-tag-chip.tsx create mode 100644 packages/web/docs/src/app/blog/components/company-news-and-press-section.tsx create mode 100644 packages/web/docs/src/app/blog/components/posts-by-tag/category-filter-link.tsx create mode 100644 packages/web/docs/src/app/blog/components/posts-by-tag/category-select.tsx create mode 100644 packages/web/docs/src/app/blog/components/posts-by-tag/featured-posts.tsx create mode 100644 packages/web/docs/src/app/blog/components/posts-by-tag/index.tsx create mode 100644 packages/web/docs/src/app/blog/components/posts-by-tag/latest-posts.tsx create mode 100644 packages/web/docs/src/app/blog/components/pretty-print-tag.ts create mode 100644 packages/web/docs/src/app/blog/page.tsx create mode 100644 packages/web/docs/src/app/blog/tag/layout.tsx diff --git a/packages/web/docs/src/app/blog/components/blog-card.tsx b/packages/web/docs/src/app/blog/components/blog-card.tsx new file mode 100644 index 00000000000..bf26804837e --- /dev/null +++ b/packages/web/docs/src/app/blog/components/blog-card.tsx @@ -0,0 +1,89 @@ +import Image from 'next/image'; +import { Anchor, cn } from '@theguild/components'; +import { Author, AuthorId, authors } from '../../../authors'; +import { BlogPostFile } from '../blog-types'; +import { BlogTagChip } from './blog-tag-chip'; + +export interface BlogCardProps { + post: Pick; + className?: string; + colorScheme?: 'default' | 'featured'; + /** + * The tag to display on the card. If not provided, the first tag will be used. + * Used for tag index page, where we want to show all cards with the same tag. + */ + tag?: string | null; +} + +export function BlogCard({ post, className, colorScheme, tag }: BlogCardProps) { + const frontmatter = post.frontMatter; + const { title, tags } = frontmatter; + const date = new Date(frontmatter.date); + + const postAuthors: Author[] = ( + typeof frontmatter.authors === 'string' + ? [authors[frontmatter.authors as AuthorId]] + : frontmatter.authors.map(author => authors[author as AuthorId]) + ).filter(Boolean); + + if (postAuthors.length === 0) { + console.error('author not found', frontmatter); + throw new Error(`authors ${JSON.stringify(frontmatter.authors)} not found`); + } + + const firstAuthor = postAuthors[0]; + + // todo: show more authors on hover? + const avatarSrc = + firstAuthor.avatar || `https://avatars.githubusercontent.com/${firstAuthor.github}?v=4&s=48`; + + return ( + +
    +
    + + +
    +

    + {title} +

    +
    +
    + {firstAuthor.name} +
    +
    + {firstAuthor.name} +
    +
    +
    + ); +} diff --git a/packages/web/docs/src/app/blog/components/blog-page-hero.tsx b/packages/web/docs/src/app/blog/components/blog-page-hero.tsx new file mode 100644 index 00000000000..f641aec1c2e --- /dev/null +++ b/packages/web/docs/src/app/blog/components/blog-page-hero.tsx @@ -0,0 +1,53 @@ +import { ArchDecoration, cn, DecorationIsolation, Heading } from '@theguild/components'; + +export function BlogPageHero({ className }: { className?: string }) { + return ( +
    + + + + + + + + + + + + + + + + + + GraphQL Stories + +

    + Explore insights on managing and optimizing your GraphQL APIs +

    +
    + ); +} diff --git a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx new file mode 100644 index 00000000000..cbe56d33fd3 --- /dev/null +++ b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { Anchor, cn, Heading } from '@theguild/components'; +import { ArrowIcon } from '../../../../components/arrow-icon'; +import { useFrontmatter } from '../../../../components/use-frontmatter'; +import { ProductUpdateAuthors } from '../../../product-updates/(posts)/product-update-header'; +import type { BlogFrontmatter } from '../../blog-types'; +import { BlogTagChip } from '../blog-tag-chip'; + +export function BlogPostHeader({ className }: { className?: string }) { + const { frontmatter } = useFrontmatter(); + + const tag = frontmatter.tags[0]; + return ( +
    +
    + + + + Blog + {tag && /} + + + {tag && } +
    + + {frontmatter.title} + + +
    + ); +} diff --git a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx new file mode 100644 index 00000000000..c92c9053c88 --- /dev/null +++ b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx @@ -0,0 +1,25 @@ +'use client'; + +import Image from 'next/image'; +import { cn } from '@theguild/components'; +import { useFrontmatter } from '../../../../components/use-frontmatter'; +import type { BlogFrontmatter } from '../../blog-types'; + +export function BlogPostPicture({ className }: { className?: string }) { + const { frontmatter } = useFrontmatter(); + const image = frontmatter.image; + + if (!image) { + return null; + } + + className = cn('h-[324px] rounded-3xl overflow-hidden object-cover [&+*]:-mt-20', className); + + if (typeof image === 'string' && (image.endsWith('.webm') || image.endsWith('.mp4'))) { + return
    ); diff --git a/packages/web/docs/src/components/get-your-api-game-white.tsx b/packages/web/docs/src/components/get-your-api-game-white.tsx index 44ad300100b..0160f41b187 100644 --- a/packages/web/docs/src/components/get-your-api-game-white.tsx +++ b/packages/web/docs/src/components/get-your-api-game-white.tsx @@ -30,7 +30,7 @@ export function GetYourAPIGameWhite(props: React.HTMLAttributes)
    - + diff --git a/packages/web/docs/src/components/landing-page-feature-tabs.tsx b/packages/web/docs/src/components/landing-page-feature-tabs.tsx index 1204c1cc5d9..34c87b1c1ab 100644 --- a/packages/web/docs/src/components/landing-page-feature-tabs.tsx +++ b/packages/web/docs/src/components/landing-page-feature-tabs.tsx @@ -91,7 +91,12 @@ export interface LandingPageFeatureTabsProps { } export function LandingPageFeatureTabs({ className }: LandingPageFeatureTabsProps) { - const icons = [, , ]; + const icons = [ + // The keys here are redundant, but Next.js started false positive linting them even if the ESLint integration is disabled. + , + , + , + ]; return ( Date: Mon, 24 Mar 2025 22:54:52 +0100 Subject: [PATCH 19/23] Fix styles --- .../app/blog/components/posts-by-tag/featured-posts.tsx | 2 +- .../docs/src/app/blog/components/posts-by-tag/index.tsx | 2 +- .../src/app/blog/components/posts-by-tag/latest-posts.tsx | 2 +- packages/web/docs/src/app/hive-prose-styles.css | 8 +++++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/web/docs/src/app/blog/components/posts-by-tag/featured-posts.tsx b/packages/web/docs/src/app/blog/components/posts-by-tag/featured-posts.tsx index 45b93348be5..bde5b369b8f 100644 --- a/packages/web/docs/src/app/blog/components/posts-by-tag/featured-posts.tsx +++ b/packages/web/docs/src/app/blog/components/posts-by-tag/featured-posts.tsx @@ -20,7 +20,7 @@ export function FeaturedPosts({ return (
      diff --git a/packages/web/docs/src/app/blog/components/posts-by-tag/index.tsx b/packages/web/docs/src/app/blog/components/posts-by-tag/index.tsx index 7d5bf025f8c..99b131b16bd 100644 --- a/packages/web/docs/src/app/blog/components/posts-by-tag/index.tsx +++ b/packages/web/docs/src/app/blog/components/posts-by-tag/index.tsx @@ -32,7 +32,7 @@ export function PostsByTag(props: { posts: BlogPostFile[]; tag?: string; classNa return (
      - +
      ); diff --git a/packages/web/docs/src/app/blog/components/posts-by-tag/latest-posts.tsx b/packages/web/docs/src/app/blog/components/posts-by-tag/latest-posts.tsx index 0a6934d0310..3b04afc2e1f 100644 --- a/packages/web/docs/src/app/blog/components/posts-by-tag/latest-posts.tsx +++ b/packages/web/docs/src/app/blog/components/posts-by-tag/latest-posts.tsx @@ -5,7 +5,7 @@ import { prettyPrintTag } from '../pretty-print-tag'; export function LatestPosts({ posts, tag }: { posts: BlogPostFile[]; tag: string | null }) { return ( -
      +
      Latest posts {tag ? ( diff --git a/packages/web/docs/src/app/hive-prose-styles.css b/packages/web/docs/src/app/hive-prose-styles.css index 35485a26206..8e7349aa683 100644 --- a/packages/web/docs/src/app/hive-prose-styles.css +++ b/packages/web/docs/src/app/hive-prose-styles.css @@ -8,7 +8,7 @@ max-width: 100%; & > div { - @apply ml-0 pl-6 md:pl-12; + @apply ml-0 pl-6 max-sm:pr-6 md:pl-12; } & > div > article { @@ -66,3 +66,9 @@ @apply mt-2; } } + +@media (max-width: 768px) { + .hive-prose { + --article-max-width: 100%; + } +} From 5821415fb12789cb37d09a42555fce9f764bde2c Mon Sep 17 00:00:00 2001 From: Piotr Monwid-Olechnowicz Date: Mon, 24 Mar 2025 23:04:21 +0100 Subject: [PATCH 20/23] Render BlogPostPicture inside of header component to make styling simpler --- .../web/docs/src/app/blog/(posts)/layout.tsx | 2 - .../blog-post-layout/blog-post-header.tsx | 72 ++++++++++--------- .../blog-post-layout/blog-post-picture.tsx | 23 +++--- 3 files changed, 49 insertions(+), 48 deletions(-) diff --git a/packages/web/docs/src/app/blog/(posts)/layout.tsx b/packages/web/docs/src/app/blog/(posts)/layout.tsx index 1b573d6f7d2..5f2fa8b6977 100644 --- a/packages/web/docs/src/app/blog/(posts)/layout.tsx +++ b/packages/web/docs/src/app/blog/(posts)/layout.tsx @@ -2,7 +2,6 @@ import { cn, HiveLayoutConfig } from '@theguild/components'; import { LandingPageContainer } from '../../../components/landing-page-container'; import '../../hive-prose-styles.css'; import { BlogPostHeader } from '../components/blog-post-layout/blog-post-header'; -import { BlogPostPicture } from '../components/blog-post-layout/blog-post-picture'; const MAIN_CONTENT = 'main-content'; @@ -10,7 +9,6 @@ export default function BlogPostLayout({ children }: { children: React.ReactNode return ( -
      p:first-of-type]:text-2xl/8')}> {children} diff --git a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx index 89502b0c9ed..c118ba51811 100644 --- a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx +++ b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx @@ -6,42 +6,50 @@ import { useFrontmatter } from '../../../../components/use-frontmatter'; import { ProductUpdateAuthors } from '../../../product-updates/(posts)/product-update-header'; import type { BlogFrontmatter } from '../../blog-types'; import { BlogTagChip } from '../blog-tag-chip'; +import { BlogPostPicture } from './blog-post-picture'; export function BlogPostHeader({ className }: { className?: string }) { - const { frontmatter } = useFrontmatter(); + const { + frontmatter: { image, tags, authors, title, date }, + } = useFrontmatter(); + + const tag = tags[0]; - const tag = frontmatter.tags[0]; return ( -
      -
      - - - - Blog - {tag && /} - - - {tag && } -
      - + {image && } +
      - {frontmatter.title} - - -
      +
      + + + + Blog + {tag && /} + + + {tag && } +
      + + {title} + + +
      + ); } diff --git a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx index c92c9053c88..91d21d94cae 100644 --- a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx +++ b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-picture.tsx @@ -1,19 +1,14 @@ -'use client'; - -import Image from 'next/image'; +import Image, { StaticImageData } from 'next/image'; import { cn } from '@theguild/components'; -import { useFrontmatter } from '../../../../components/use-frontmatter'; -import type { BlogFrontmatter } from '../../blog-types'; - -export function BlogPostPicture({ className }: { className?: string }) { - const { frontmatter } = useFrontmatter(); - const image = frontmatter.image; - - if (!image) { - return null; - } - className = cn('h-[324px] rounded-3xl overflow-hidden object-cover [&+*]:-mt-20', className); +export function BlogPostPicture({ + className, + image, +}: { + className?: string; + image: string | StaticImageData; +}) { + className = cn('h-[324px] rounded-3xl overflow-hidden object-cover', className); if (typeof image === 'string' && (image.endsWith('.webm') || image.endsWith('.mp4'))) { return