From e11f9a5dedd1c4233159369c32bc590e272fd79a Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Wed, 7 May 2025 23:23:35 +0100 Subject: [PATCH] badge colour variants --- frontend/src/app/globals.css | 24 +++++++++++++ frontend/src/app/questions/[slug]/page.tsx | 12 +++---- .../components/questions/question-card.tsx | 23 ++++++++----- frontend/src/components/ui/badge.tsx | 34 ++++++++++++++++--- frontend/src/lib/utils.test.ts | 19 +++++------ frontend/src/lib/utils.ts | 10 +++--- 6 files changed, 87 insertions(+), 35 deletions(-) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 200c64b..3a4db4b 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -49,6 +49,18 @@ --approach-correct-bg: #f0fdf4; --approach-correct-border: #bbf7d0; --approach-correct-fg: #16a34a; + + /* Badge colors - Category (teal) */ + --badge-category: #0d9488; + --badge-category-bg: #ccfbf1; + + /* Badge colors - Pattern (indigo) */ + --badge-pattern: #4f46e5; + --badge-pattern-bg: #e0e7ff; + + /* Badge colors - Optimal (green) */ + --badge-optimal: #16a34a; + --badge-optimal-bg: #dcfce7; } @media (prefers-color-scheme: dark) { @@ -101,6 +113,18 @@ --approach-correct-bg: rgba(22, 163, 74, 0.15); --approach-correct-border: rgba(22, 163, 74, 0.4); --approach-correct-fg: #4ade80; + + /* Badge colors - Category (teal, dark mode) */ + --badge-category: #2dd4bf; + --badge-category-bg: rgba(20, 184, 166, 0.2); + + /* Badge colors - Pattern (indigo, dark mode) */ + --badge-pattern: #a5b4fc; + --badge-pattern-bg: rgba(99, 102, 241, 0.2); + + /* Badge colors - Optimal (green, dark mode) */ + --badge-optimal: #4ade80; + --badge-optimal-bg: rgba(34, 197, 94, 0.2); } } diff --git a/frontend/src/app/questions/[slug]/page.tsx b/frontend/src/app/questions/[slug]/page.tsx index 768d61f..b61a5d1 100644 --- a/frontend/src/app/questions/[slug]/page.tsx +++ b/frontend/src/app/questions/[slug]/page.tsx @@ -5,7 +5,7 @@ import { CodeBlock } from "@/components/ui/code-block"; import { Markdown } from "@/components/ui/markdown"; import { Callout, ApproachBox } from "@/components/ui/callout"; import { Collapsible } from "@/components/ui/collapsible"; -import { getDifficultyColor, getDifficultyLabel, capitalize } from "@/lib/utils"; +import { getDifficultyVariant, getDifficultyLabel, capitalize } from "@/lib/utils"; import { FileText, AlertCircle, @@ -40,7 +40,7 @@ export default async function QuestionDetailPage({

{question.title}

{capitalize(question.difficulty)} @@ -50,12 +50,12 @@ export default async function QuestionDetailPage({
{question.categories.map((cat) => ( - {cat.name} + {cat.name} ))} {question.patterns.map((pat) => ( - {pat.name} + {pat.name} ))}
@@ -239,9 +239,7 @@ export default async function QuestionDetailPage({

{solution.approach_name}

- - Optimal - + Optimal
{solution.explanation && (
diff --git a/frontend/src/components/questions/question-card.tsx b/frontend/src/components/questions/question-card.tsx index 6ba37f7..fffba35 100644 --- a/frontend/src/components/questions/question-card.tsx +++ b/frontend/src/components/questions/question-card.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import { Badge } from "@/components/ui/badge"; -import { getDifficultyColor, capitalize } from "@/lib/utils"; +import { getDifficultyVariant, getDifficultyLabel, capitalize } from "@/lib/utils"; import type { QuestionListItem } from "@/types"; interface QuestionCardProps { @@ -15,25 +15,32 @@ export function QuestionCard({ question }: QuestionCardProps) { >

{question.title}

- + {capitalize(question.difficulty)}
{question.categories.map((cat) => ( - + {cat.name} ))}
-
+
+ {question.patterns.map((p) => ( + + {p.name} + + ))} {question.leetcode_id && ( - LeetCode #{question.leetcode_id} - )} - {question.patterns.length > 0 && ( - {question.patterns.map((p) => p.name).join(", ")} + + LeetCode #{question.leetcode_id} + )}
diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx index 87fc31a..7a3a680 100644 --- a/frontend/src/components/ui/badge.tsx +++ b/frontend/src/components/ui/badge.tsx @@ -1,26 +1,50 @@ import { cn } from "@/lib/utils"; +type BadgeVariant = + | "default" + | "outline" + | "difficulty-easy" + | "difficulty-medium" + | "difficulty-hard" + | "category" + | "pattern" + | "optimal"; + interface BadgeProps { children: React.ReactNode; className?: string; - variant?: "default" | "outline"; + variant?: BadgeVariant; + "aria-label"?: string; } +const variantClasses: Record = { + default: "bg-[var(--secondary)] text-[var(--secondary-foreground)]", + outline: "border border-[var(--border)] text-[var(--muted-foreground)]", + "difficulty-easy": + "text-[var(--difficulty-easy)] bg-[var(--difficulty-easy-bg)]", + "difficulty-medium": + "text-[var(--difficulty-medium)] bg-[var(--difficulty-medium-bg)]", + "difficulty-hard": + "text-[var(--difficulty-hard)] bg-[var(--difficulty-hard-bg)]", + category: "text-[var(--badge-category)] bg-[var(--badge-category-bg)]", + pattern: "text-[var(--badge-pattern)] bg-[var(--badge-pattern-bg)]", + optimal: "text-[var(--badge-optimal)] bg-[var(--badge-optimal-bg)]", +}; + export function Badge({ children, className, variant = "default", + "aria-label": ariaLabel, }: BadgeProps) { return ( {children} diff --git a/frontend/src/lib/utils.test.ts b/frontend/src/lib/utils.test.ts index 7e0e9f1..8c9776f 100644 --- a/frontend/src/lib/utils.test.ts +++ b/frontend/src/lib/utils.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { cn, getDifficultyColor, getDifficultyLabel, capitalize } from "./utils"; +import { cn, getDifficultyVariant, getDifficultyLabel, capitalize } from "./utils"; describe("cn", () => { it("joins multiple class names", () => { @@ -15,20 +15,17 @@ describe("cn", () => { }); }); -describe("getDifficultyColor", () => { - it("returns easy difficulty CSS variable classes", () => { - const result = getDifficultyColor("easy"); - expect(result).toContain("--difficulty-easy"); +describe("getDifficultyVariant", () => { + it("returns difficulty-easy variant for easy", () => { + expect(getDifficultyVariant("easy")).toBe("difficulty-easy"); }); - it("returns medium difficulty CSS variable classes", () => { - const result = getDifficultyColor("medium"); - expect(result).toContain("--difficulty-medium"); + it("returns difficulty-medium variant for medium", () => { + expect(getDifficultyVariant("medium")).toBe("difficulty-medium"); }); - it("returns hard difficulty CSS variable classes", () => { - const result = getDifficultyColor("hard"); - expect(result).toContain("--difficulty-hard"); + it("returns difficulty-hard variant for hard", () => { + expect(getDifficultyVariant("hard")).toBe("difficulty-hard"); }); }); diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 8d76c53..3eb2b78 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -4,14 +4,16 @@ export function cn(...classes: (string | undefined | false)[]): string { return classes.filter(Boolean).join(" "); } -export function getDifficultyColor(difficulty: Difficulty): string { +export function getDifficultyVariant( + difficulty: Difficulty +): "difficulty-easy" | "difficulty-medium" | "difficulty-hard" { switch (difficulty) { case "easy": - return "text-[var(--difficulty-easy)] bg-[var(--difficulty-easy-bg)]"; + return "difficulty-easy"; case "medium": - return "text-[var(--difficulty-medium)] bg-[var(--difficulty-medium-bg)]"; + return "difficulty-medium"; case "hard": - return "text-[var(--difficulty-hard)] bg-[var(--difficulty-hard-bg)]"; + return "difficulty-hard"; } }