Skip to content

Commit 9503ce8

Browse files
committed
feat(achievements): add inline SVG BadgeIcon before titles; theme-aware via currentColor; accessible (aria-hidden, not focusable) (#23)
1 parent a69cb0e commit 9503ce8

File tree

3 files changed

+97
-17
lines changed

3 files changed

+97
-17
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/BadgeIcon.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as React from "react";
2+
3+
export type BadgeKey =
4+
| "first-challenge"
5+
| "design-master"
6+
| "code-warrior"
7+
| "community-helper"
8+
| "streak-master"
9+
| "all-rounder"; // optional fallback
10+
11+
type Props = { kind: BadgeKey; className?: string };
12+
13+
export const BadgeIcon: React.FC<Props> = ({ kind, className = "h-4 w-4" }) => {
14+
// IMPORTANT: boolean values + proper typing
15+
const common: React.SVGProps<SVGSVGElement> = {
16+
className,
17+
viewBox: "0 0 24 24",
18+
"aria-hidden": true,
19+
focusable: false,
20+
fill: "none",
21+
stroke: "currentColor",
22+
strokeWidth: 1.75,
23+
strokeLinecap: "round",
24+
strokeLinejoin: "round",
25+
};
26+
27+
switch (kind) {
28+
case "first-challenge": // 🥇 medal
29+
return (
30+
<svg {...common}>
31+
<circle cx="12" cy="13" r="4" />
32+
<path d="M9 3l3 4 3-4M9 3h6" />
33+
</svg>
34+
);
35+
case "design-master": // 🎨 palette
36+
return (
37+
<svg {...common}>
38+
<path d="M12 21a9 9 0 1 1 0-18 4 4 0 0 1 0 8h-1a2 2 0 0 0 0 4h1" />
39+
<circle cx="8.5" cy="9" r="1" />
40+
<circle cx="10.5" cy="6.5" r="1" />
41+
<circle cx="14.5" cy="7" r="1" />
42+
<circle cx="16" cy="10" r="1" />
43+
</svg>
44+
);
45+
case "code-warrior": // ⟨/⟩
46+
return (
47+
<svg {...common}>
48+
<path d="M8 7l-4 5 4 5M16 7l4 5-4 5" />
49+
</svg>
50+
);
51+
case "community-helper": // 💬
52+
return (
53+
<svg {...common}>
54+
<path d="M7 17l-3 3V7a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v6a4 4 0 0 1-4 4H7z" />
55+
<path d="M8.5 10.5h7" />
56+
</svg>
57+
);
58+
case "streak-master": // 🔥
59+
return (
60+
<svg {...common}>
61+
<path d="M12 3s2 3 2 5a3 3 0 0 1-6 0c0-2 2-5 2-5" />
62+
<path d="M8 13a4 4 0 1 0 8 0c0-1.5-.6-2.3-1.6-3.4" />
63+
</svg>
64+
);
65+
default: // all-rounder fallback
66+
return (
67+
<svg {...common}>
68+
<circle cx="12" cy="12" r="8" />
69+
<path d="M9 12h6M12 9v6" />
70+
</svg>
71+
);
72+
}
73+
};

src/components/Profile/Profile.tsx

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
2-
import { Award, BookOpen, Star, TrendingUp, Calendar, Target } from 'lucide-react';
2+
import { BookOpen, Star, TrendingUp, Calendar, Target } from 'lucide-react';
3+
import { BadgeIcon, type BadgeKey } from "../BadgeIcon";
34

45
export const Profile: React.FC = () => {
56
const completedChallenges = [
@@ -39,13 +40,13 @@ export const Profile: React.FC = () => {
3940
{ label: 'Avg Rating', value: '4.6', icon: Star, color: 'text-yellow-600 bg-yellow-100 dark:text-yellow-400 dark:bg-yellow-900' }
4041
];
4142

42-
const achievements = [
43-
{ title: 'First Challenge', description: 'Completed your first challenge', earned: true },
44-
{ title: 'Design Master', description: 'Completed 10 design challenges', earned: true },
45-
{ title: 'Code Warrior', description: 'Completed 10 development challenges', earned: true },
46-
{ title: 'Community Helper', description: 'Provided feedback on 25 submissions', earned: false },
47-
{ title: 'Streak Master', description: 'Completed challenges for 7 days straight', earned: false },
48-
{ title: 'All Rounder', description: 'Completed challenges in all domains', earned: false }
43+
const achievements: Array<{ key: BadgeKey; title: string; description: string; earned: boolean }> = [
44+
{ key: 'first-challenge', title: 'First Challenge', description: 'Completed your first challenge', earned: true },
45+
{ key: 'design-master', title: 'Design Master', description: 'Completed 10 design challenges', earned: true },
46+
{ key: 'code-warrior', title: 'Code Warrior', description: 'Completed 10 development challenges', earned: true },
47+
{ key: 'community-helper', title: 'Community Helper', description: 'Provided feedback on 25 submissions', earned: false },
48+
{ key: 'streak-master', title: 'Streak Master', description: 'Completed challenges for 7 days straight', earned: false },
49+
{ key: 'all-rounder', title: 'All Rounder', description: 'Completed challenges in all domains', earned: false },
4950
];
5051

5152
return (
@@ -154,14 +155,19 @@ export const Profile: React.FC = () => {
154155
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">Achievements</h3>
155156
<div className="space-y-3">
156157
{achievements.map((achievement, index) => (
157-
<div key={index} className={`flex items-center space-x-3 p-3 rounded-lg ${
158-
achievement.earned ? 'bg-green-50 dark:bg-green-950 border border-green-200 dark:border-green-800' : 'bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600'
159-
}`}>
160-
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
161-
achievement.earned ? 'bg-green-500 text-white' : 'bg-gray-300 dark:bg-gray-600 text-gray-500 dark:text-gray-400'
162-
}`}>
163-
<Award className="h-4 w-4" />
158+
<div
159+
key={index}
160+
className={`flex items-start gap-2 p-3 rounded-lg ${
161+
achievement.earned
162+
? 'bg-green-50 dark:bg-green-950 border border-green-200 dark:border-green-800'
163+
: 'bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600'
164+
}`}
165+
>
166+
{/* Icon inherits current text color for light/dark */}
167+
<div className={`${achievement.earned ? 'text-green-900 dark:text-green-300' : 'text-gray-700 dark:text-gray-300'}`}>
168+
<BadgeIcon kind={achievement.key} className="h-4 w-4 mt-0.5" />
164169
</div>
170+
165171
<div className="flex-1">
166172
<p className={`text-sm font-medium ${achievement.earned ? 'text-green-900 dark:text-green-300' : 'text-gray-700 dark:text-gray-300'}`}>
167173
{achievement.title}
@@ -192,8 +198,8 @@ export const Profile: React.FC = () => {
192198
<span className="text-sm text-gray-500 dark:text-gray-400">{item.completed}</span>
193199
</div>
194200
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
195-
<div
196-
className={`${item.color} h-2 rounded-full`}
201+
<div
202+
className={`${item.color} h-2 rounded-full`}
197203
style={{ width: `${(item.completed / 10) * 100}%` }}
198204
></div>
199205
</div>

0 commit comments

Comments
 (0)