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";
}
}