Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 21 additions & 24 deletions components/home/upstream-proof-band.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { getTranslations } from "next-intl/server";
import { ArrowRight } from "lucide-react";
import { Link } from "@/i18n/navigation";
import { aggregates, findRepo, type UpstreamRepo } from "@/lib/upstream";
import { BrandIcon } from "@/components/upstream/brand-icon";
import { aggregates, findRepo, prsForRepo } from "@/lib/upstream";
import {
UpstreamProofMarquee,
type MarqueeBrand,
} from "@/components/home/upstream-proof-marquee";

/**
* Home trust band — promotes the /upstream OSS proof (merged PRs into the
Expand Down Expand Up @@ -31,14 +34,21 @@ const MARQUEE: { slug: string; label: string }[] = [

export async function UpstreamProofBand({ locale }: { locale: string }) {
const t = await getTranslations({ locale, namespace: "home.upstream" });
const tu = await getTranslations({ locale, namespace: "upstream" });
const agg = aggregates();

const brands = MARQUEE.map((m) => {
const repo = findRepo(m.slug);
return repo ? { ...m, repo } : null;
}).filter(
(b): b is { slug: string; label: string; repo: UpstreamRepo } => b !== null,
);
if (!repo) return null;
const prs = prsForRepo(m.slug);
return {
slug: m.slug,
label: m.label,
repo,
prs,
prsCountLabel: tu("tile.prsCount", { count: prs.length }),
};
}).filter((b): b is MarqueeBrand => b !== null);

return (
<section className="relative border-t border-fg/5 px-6 py-20 md:py-28">
Expand All @@ -61,24 +71,11 @@ export async function UpstreamProofBand({ locale }: { locale: string }) {
{t("sub", { prs: agg.totalPrs, repos: agg.totalRepos })}
</p>

<ul className="mt-12 grid grid-cols-3 gap-x-6 gap-y-8 sm:grid-cols-4 md:grid-cols-6">
{brands.map(({ slug, label, repo }) => (
<li
key={slug}
className="flex flex-col items-center gap-2 text-center"
>
<BrandIcon
iconSlug={repo.iconSlug}
fallbackName={label}
customLogoKey={repo.slug}
size={40}
/>
<span className="font-mono text-2xs uppercase tracking-wider text-fg-subtle">
{label}
</span>
</li>
))}
</ul>
<UpstreamProofMarquee
brands={brands}
viewPrLabel={tu("pr.viewPr")}
locale={locale}
/>

<div className="mt-12">
<Link
Expand Down
138 changes: 138 additions & 0 deletions components/home/upstream-proof-marquee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"use client";

import { useState } from "react";
import type { UpstreamPr, UpstreamRepo } from "@/lib/upstream-schema";
import { BrandIcon } from "@/components/upstream/brand-icon";
import { UpstreamPrList } from "@/components/upstream/pr-list";
import { requestLenisResize } from "@/components/scroll/smooth-scroll";
import { cn } from "@/lib/cn";

export type MarqueeBrand = {
slug: string;
label: string;
repo: UpstreamRepo;
prs: UpstreamPr[];
prsCountLabel: string;
};

/**
* Home proof-band logos — clickable. A click expands that repo's merged-PR
* card **in place** (a single panel below the grid), with no navigation and
* no scroll, so the visitor stays exactly where they were. Deliberately
* mirrors the /upstream tile's expand, reusing <UpstreamPrList>.
*
* `displayedSlug` lags `openSlug` on close so the panel content stays mounted
* through the collapse transition (an empty grid-rows row would snap shut).
*/
export function UpstreamProofMarquee({
brands,
viewPrLabel,
locale,
panelId = "upstream-proof-panel",
}: {
brands: MarqueeBrand[];
viewPrLabel: string;
locale: string;
panelId?: string;
}) {
const [openSlug, setOpenSlug] = useState<string | null>(null);
const [displayedSlug, setDisplayedSlug] = useState<string | null>(null);

function toggle(slug: string) {
if (openSlug === slug) {
setOpenSlug(null); // keep displayedSlug until the collapse finishes
} else {
setDisplayedSlug(slug);
setOpenSlug(slug);
}
}

const isOpen = openSlug !== null;
const displayed = brands.find((b) => b.slug === displayedSlug) ?? null;

return (
<div className="mt-12">
<ul className="grid grid-cols-3 gap-x-6 gap-y-8 sm:grid-cols-4 md:grid-cols-6">
{brands.map(({ slug, label, repo }) => {
const active = slug === openSlug;
return (
<li key={slug}>
<button
type="button"
onClick={() => toggle(slug)}
aria-expanded={active}
aria-controls={panelId}
className="group flex w-full cursor-pointer flex-col items-center gap-2 rounded-xl py-2 text-center focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
<span
className={cn(
"flex size-14 items-center justify-center rounded-2xl border transition-all duration-300",
active
? "border-accent/50 bg-accent/10 shadow-[0_0_28px_-8px_oklch(0.65_0.25_295_/0.5)]"
: "border-transparent group-hover:border-fg/10 group-hover:bg-bg-elev-1/60",
)}
>
<BrandIcon
iconSlug={repo.iconSlug}
fallbackName={label}
customLogoKey={repo.slug}
size={40}
/>
</span>
<span
className={cn(
"font-mono text-2xs uppercase tracking-wider transition-colors",
active
? "text-accent"
: "text-fg-subtle group-hover:text-fg",
)}
>
{label}
</span>
</button>
</li>
);
})}
</ul>

<div
id={panelId}
role="region"
onTransitionEnd={(e) => {
if (e.propertyName !== "grid-template-rows") return;
requestLenisResize();
if (openSlug === null) setDisplayedSlug(null);
}}
className={cn(
"grid overflow-hidden transition-[grid-template-rows,opacity] duration-300 ease-out",
isOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0",
)}
>
<div className="min-h-0">
{displayed && (
<div className="pt-6">
<div className="rounded-2xl border border-accent/25 bg-bg-elev-1/60 backdrop-blur-sm">
<div className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1 px-5 pt-4">
<h3 className="font-display text-base font-semibold text-fg">
{displayed.label}
<span className="ml-2 font-mono text-2xs uppercase tracking-wider text-fg-subtle">
{displayed.repo.owner}/{displayed.repo.name}
</span>
</h3>
<span className="font-mono text-2xs text-accent">
{displayed.prsCountLabel}
</span>
</div>
<UpstreamPrList
prs={displayed.prs}
locale={locale}
viewPrLabel={viewPrLabel}
/>
</div>
</div>
)}
</div>
</div>
</div>
);
}
39 changes: 7 additions & 32 deletions components/upstream/brand-tile.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import { useId, useRef } from "react";
import { ChevronDown, ExternalLink } from "lucide-react";
import { ChevronDown } from "lucide-react";
import type { UpstreamPr, UpstreamRepo } from "@/lib/upstream-schema";
import { BrandIcon } from "@/components/upstream/brand-icon";
import { UpstreamPrList } from "@/components/upstream/pr-list";
import { requestLenisResize } from "@/components/scroll/smooth-scroll";
import { cn } from "@/lib/cn";

Expand Down Expand Up @@ -32,7 +33,6 @@ export function BrandTile({ repo, prs, open, onToggle, locale, labels }: Props)
notation: "compact",
maximumFractionDigits: 1,
});
const df = new Intl.DateTimeFormat(locale, { dateStyle: "medium" });

const starsCompact = nf.format(repo.stars);
const downloadsCompact =
Expand Down Expand Up @@ -120,36 +120,11 @@ export function BrandTile({ repo, prs, open, onToggle, locale, labels }: Props)
)}
>
<div className="min-h-0">
<ul className="border-t border-fg/8 px-5 py-4">
{prs.map((pr) => (
<li key={pr.number} className="group/pr py-2 first:pt-0 last:pb-0">
<a
href={pr.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-start justify-between gap-3 rounded-lg -mx-2 px-2 py-1.5 hover:bg-bg-elev-2/50 transition-colors focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 font-mono text-2xs uppercase tracking-wider">
<span className="text-accent">#{pr.number}</span>
<span className="text-fg-subtle">
{df.format(new Date(pr.mergedAt))}
</span>
</div>
<p className="mt-1 text-sm text-fg-muted group-hover/pr:text-fg transition-colors">
{pr.title}
</p>
</div>
<ExternalLink
aria-hidden
size={14}
className="mt-1 shrink-0 text-fg-subtle group-hover/pr:text-accent transition-colors"
/>
<span className="sr-only">{labels.viewPr}</span>
</a>
</li>
))}
</ul>
<UpstreamPrList
prs={prs}
locale={locale}
viewPrLabel={labels.viewPr}
/>
</div>
</div>
</article>
Expand Down
52 changes: 52 additions & 0 deletions components/upstream/pr-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ExternalLink } from "lucide-react";
import type { UpstreamPr } from "@/lib/upstream-schema";

/**
* Shared merged-PR list — the expandable body used by both the `/upstream`
* brand tiles and the home proof-band panel. One source of truth for the
* PR-row styling so the two never drift.
*/
export function UpstreamPrList({
prs,
locale,
viewPrLabel,
}: {
prs: readonly UpstreamPr[];
locale: string;
viewPrLabel: string;
}) {
const df = new Intl.DateTimeFormat(locale, { dateStyle: "medium" });

return (
<ul className="border-t border-fg/8 px-5 py-4">
{prs.map((pr) => (
<li key={pr.number} className="group/pr py-2 first:pt-0 last:pb-0">
<a
href={pr.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-start justify-between gap-3 rounded-lg -mx-2 px-2 py-1.5 hover:bg-bg-elev-2/50 transition-colors focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 font-mono text-2xs uppercase tracking-wider">
<span className="text-accent">#{pr.number}</span>
<span className="text-fg-subtle">
{df.format(new Date(pr.mergedAt))}
</span>
</div>
<p className="mt-1 text-sm text-fg-muted group-hover/pr:text-fg transition-colors">
{pr.title}
</p>
</div>
<ExternalLink
aria-hidden
size={14}
className="mt-1 shrink-0 text-fg-subtle group-hover/pr:text-accent transition-colors"
/>
<span className="sr-only">{viewPrLabel}</span>
</a>
</li>
))}
</ul>
);
}
Loading