Skip to content

Commit 42183dc

Browse files
committed
feat: add comparison preset suggestions
1 parent 8d7f385 commit 42183dc

5 files changed

Lines changed: 108 additions & 0 deletions

File tree

‎src/features/comparison/components/home-page-client.tsx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { AppFooter } from "@/components/layout/app-footer";
1111
import { useTranslation } from "@/components/providers/language-provider";
1212
import { cn } from "@/utils/cn";
1313
import { useComparisonController } from "../hooks";
14+
import { type ComparisonPreset } from "../config/comparison-presets";
15+
import { PresetPills } from "./preset-pills";
1416

1517
export function HomePageClient() {
1618
const { t } = useTranslation();
@@ -34,6 +36,13 @@ export function HomePageClient() {
3436
usernameErrors,
3537
} = useComparisonController();
3638

39+
const handlePresetClick = (preset: ComparisonPreset) => {
40+
handleUsername1Change(preset.username1);
41+
handleUsername2Change(preset.username2);
42+
43+
handleCompare(preset.username1, preset.username2, { selectedLanguages });
44+
};
45+
3746
const skeleton = useMemo(() => <DashboardSkeleton />, []);
3847

3948
return (
@@ -57,6 +66,14 @@ export function HomePageClient() {
5766
username1Error={usernameErrors.username1}
5867
username2Error={usernameErrors.username2}
5968
/>
69+
{!displayData && (
70+
<PresetPills
71+
onSelect={handlePresetClick}
72+
loading={loading}
73+
username1={username1}
74+
username2={username2}
75+
/>
76+
)}
6077

6178
<div className="relative min-h-[28rem]" aria-live="polite">
6279
{displayData ? (
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client";
2+
3+
import { useId } from "react";
4+
import { useTranslation } from "@/components/providers/language-provider";
5+
import { cn } from "@/utils/cn";
6+
import { COMPARISON_PRESETS } from "../config/comparison-presets";
7+
8+
type Preset = (typeof COMPARISON_PRESETS)[number];
9+
10+
interface PresetPillsProps {
11+
onSelect: (preset: Preset) => void;
12+
loading: boolean;
13+
username1: string;
14+
username2: string;
15+
}
16+
17+
export function PresetPills({ onSelect, loading, username1, username2 }: PresetPillsProps) {
18+
const { t } = useTranslation();
19+
const headingId = useId();
20+
21+
return (
22+
<section
23+
aria-labelledby={headingId}
24+
className="flex animate-fadeIn flex-col items-center gap-3"
25+
>
26+
<h2 id={headingId} className="text-sm text-muted-foreground">
27+
{t("presets.title")}
28+
</h2>
29+
30+
<ul className="flex flex-wrap justify-center gap-2" aria-busy={loading}>
31+
{COMPARISON_PRESETS.map((preset) => {
32+
const isActive =
33+
loading && username1 === preset.username1 && username2 === preset.username2;
34+
35+
return (
36+
<li key={preset.labelKey}>
37+
{/* Native <button>: Tab to focus, Enter/Space to activate */}
38+
<button
39+
type="button"
40+
disabled={loading}
41+
onClick={() => onSelect(preset)}
42+
className={cn(
43+
"inline-flex items-center gap-2 rounded-full border border-border bg-card px-4 py-2 text-sm text-foreground",
44+
"transition-all duration-200 hover:bg-accent",
45+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
46+
"disabled:cursor-not-allowed disabled:opacity-50 motion-reduce:transition-none",
47+
isActive && "opacity-100 ring-2 ring-ring",
48+
)}
49+
>
50+
{isActive && (
51+
<span
52+
aria-hidden="true"
53+
className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-current border-t-transparent"
54+
/>
55+
)}
56+
{t(preset.labelKey)}
57+
</button>
58+
</li>
59+
);
60+
})}
61+
</ul>
62+
</section>
63+
);
64+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const COMPARISON_PRESETS = [
2+
{
3+
username1: "yyx990803",
4+
username2: "antfu",
5+
labelKey: "presets.evanVsAnthony",
6+
},
7+
{
8+
username1: "rauchg",
9+
username2: "leerob",
10+
labelKey: "presets.guillermoVsLee",
11+
},
12+
{
13+
username1: "kentcdodds",
14+
username2: "t3dotgg",
15+
labelKey: "presets.kentVsTheo",
16+
},
17+
] as const;
18+
19+
export type ComparisonPreset = (typeof COMPARISON_PRESETS)[number];

‎src/locales/ar.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@
281281
"profile.signalShare": "{pct}% من الإشارة النهائية",
282282
"profile.title": "تأثير وإحصائيات المطور",
283283
"profile.topWork": "أبرز الأعمال والمساهمات",
284+
"presets.title": "لا تعرف من تقارن؟ جرّب أحد هذه الأمثلة:",
285+
"presets.evanVsAnthony": "إيفان يو ضد أنتوني فو",
286+
"presets.guillermoVsLee": "غييرمو راوتش ضد لي روبنسون",
287+
"presets.kentVsTheo": "كينت سي. دودز ضد ثيو براون",
284288
"profile.transparencyTitle": "إشارات شفافية التقييم",
285289
"profile.viewFullStats": "عرض الملف الشخصي للمطور",
286290
"profile.viewOnGithub": "عرض الملف الشخصي على GitHub"

‎src/locales/en.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@
272272
"profile.location": "Location",
273273
"profile.metaDescription": "Explore {name}'s (@{username}) open-source developer impact score, top repositories, merged pull requests, and community contributions on DevImpact.",
274274
"profile.metaTitle": "{name} (@{username}) - GitHub Impact & Developer Stats",
275+
"presets.title": "Not sure who to compare? Try one of these:",
276+
"presets.evanVsAnthony": "Evan You vs Anthony Fu",
277+
"presets.guillermoVsLee": "Guillermo Rauch vs Lee Robinson",
278+
"presets.kentVsTheo": "Kent C. Dodds vs Theo Browne",
275279
"profile.methodologyCta": "Learn how this developer score was calculated",
276280
"profile.notFound.description": "We couldn't find GitHub data for @{username}. Please check the username and try again.",
277281
"profile.notFound.title": "Developer Not Found",

0 commit comments

Comments
 (0)