Skip to content

Commit 63ad6a2

Browse files
committed
feat: add Daily Coding Tip widget with category badges and refresh button
1 parent 73512c6 commit 63ad6a2

3 files changed

Lines changed: 209 additions & 0 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
.container {
2+
background: var(--ifm-card-background-color, #ffffff);
3+
border: 1px solid var(--ifm-color-emphasis-200, #e5e7eb);
4+
border-radius: 12px;
5+
padding: 24px;
6+
max-width: 600px;
7+
margin: 24px auto;
8+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
9+
transition: box-shadow 0.2s ease;
10+
}
11+
12+
.container:hover {
13+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
14+
}
15+
16+
.header {
17+
display: flex;
18+
align-items: center;
19+
gap: 8px;
20+
margin-bottom: 16px;
21+
}
22+
23+
.headerIcon {
24+
font-size: 20px;
25+
}
26+
27+
.headerTitle {
28+
font-size: 18px;
29+
font-weight: 700;
30+
margin: 0;
31+
color: var(--ifm-heading-color);
32+
}
33+
34+
.tipCard {
35+
background: var(--ifm-color-emphasis-100, #f9fafb);
36+
border-radius: 8px;
37+
padding: 16px;
38+
margin-bottom: 16px;
39+
}
40+
41+
.fadeIn {
42+
animation: fadeInUp 0.3s ease forwards;
43+
}
44+
45+
@keyframes fadeInUp {
46+
from {
47+
opacity: 0;
48+
transform: translateY(8px);
49+
}
50+
to {
51+
opacity: 1;
52+
transform: translateY(0);
53+
}
54+
}
55+
56+
.categoryBadge {
57+
display: inline-block;
58+
font-size: 11px;
59+
font-weight: 600;
60+
padding: 3px 10px;
61+
border-radius: 999px;
62+
border: 1px solid;
63+
margin-bottom: 10px;
64+
text-transform: uppercase;
65+
letter-spacing: 0.05em;
66+
}
67+
68+
.tipText {
69+
font-size: 15px;
70+
line-height: 1.6;
71+
color: var(--ifm-font-color-base);
72+
margin: 0;
73+
}
74+
75+
.refreshButton {
76+
background: var(--ifm-color-primary, #2563eb);
77+
color: #ffffff;
78+
border: none;
79+
border-radius: 8px;
80+
padding: 10px 20px;
81+
font-size: 14px;
82+
font-weight: 600;
83+
cursor: pointer;
84+
transition: opacity 0.2s ease, transform 0.1s ease;
85+
width: 100%;
86+
}
87+
88+
.refreshButton:hover {
89+
opacity: 0.9;
90+
transform: translateY(-1px);
91+
}
92+
93+
.refreshButton:active {
94+
transform: translateY(0);
95+
}
96+
97+
@media (max-width: 768px) {
98+
.container {
99+
margin: 16px;
100+
padding: 16px;
101+
}
102+
}

src/components/DailyCodingTip.tsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, { useState, useEffect } from "react";
2+
import styles from "./DailyCodingTip.module.css";
3+
import codingTips from "../data/codingTips.json";
4+
5+
type Tip = {
6+
id: number;
7+
category: string;
8+
tip: string;
9+
};
10+
11+
const CATEGORY_COLORS: Record<string, string> = {
12+
"Git & GitHub Tips": "#f97316",
13+
"JavaScript/React Tips": "#3b82f6",
14+
"Open Source Contribution Tips": "#10b981",
15+
"VS Code Shortcuts": "#8b5cf6",
16+
"Productivity Tricks": "#ec4899",
17+
};
18+
19+
function getTipOfTheDay(): Tip {
20+
try {
21+
const stored = localStorage.getItem("dailyCodingTip");
22+
const today = new Date().toDateString();
23+
if (stored) {
24+
const parsed = JSON.parse(stored);
25+
if (parsed.date === today) return parsed.tip;
26+
}
27+
const randomTip = codingTips[Math.floor(Math.random() * codingTips.length)] as Tip;
28+
localStorage.setItem("dailyCodingTip", JSON.stringify({ date: today, tip: randomTip }));
29+
return randomTip;
30+
} catch {
31+
return codingTips[0] as Tip;
32+
}
33+
}
34+
35+
export default function DailyCodingTip(): React.ReactElement {
36+
const [tip, setTip] = useState<Tip | null>(null);
37+
const [animate, setAnimate] = useState(false);
38+
39+
useEffect(() => {
40+
setTip(getTipOfTheDay());
41+
setAnimate(true);
42+
}, []);
43+
44+
const handleRefresh = () => {
45+
const randomTip = codingTips[Math.floor(Math.random() * codingTips.length)] as Tip;
46+
setAnimate(false);
47+
setTimeout(() => {
48+
setTip(randomTip);
49+
setAnimate(true);
50+
}, 150);
51+
};
52+
53+
if (!tip) return <></>;
54+
55+
const categoryColor = CATEGORY_COLORS[tip.category] || "#6b7280";
56+
57+
return (
58+
<div className={styles.container}>
59+
<div className={styles.header}>
60+
<span className={styles.headerIcon}>💡</span>
61+
<h3 className={styles.headerTitle}>Daily Coding Tip</h3>
62+
</div>
63+
<div className={`${styles.tipCard} ${animate ? styles.fadeIn : ""}`}>
64+
<span
65+
className={styles.categoryBadge}
66+
style={{
67+
backgroundColor: `${categoryColor}20`,
68+
color: categoryColor,
69+
borderColor: `${categoryColor}40`,
70+
}}
71+
>
72+
{tip.category}
73+
</span>
74+
<p className={styles.tipText}>{tip.tip}</p>
75+
</div>
76+
<button
77+
className={styles.refreshButton}
78+
onClick={handleRefresh}
79+
aria-label="Get a new random coding tip"
80+
>
81+
🔀 Get Another Tip
82+
</button>
83+
</div>
84+
);
85+
}

src/data/codingTips.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[
2+
{ "id": 1, "category": "Git & GitHub Tips", "tip": "Use `git stash` to temporarily save uncommitted changes before switching branches." },
3+
{ "id": 2, "category": "Git & GitHub Tips", "tip": "Use `git log --oneline` for a compact view of your commit history." },
4+
{ "id": 3, "category": "Git & GitHub Tips", "tip": "Always write meaningful commit messages — future you will thank present you." },
5+
{ "id": 4, "category": "Git & GitHub Tips", "tip": "Use `git bisect` to find the commit that introduced a bug using binary search." },
6+
{ "id": 5, "category": "JavaScript/React Tips", "tip": "Use optional chaining (`?.`) to safely access deeply nested object properties." },
7+
{ "id": 6, "category": "JavaScript/React Tips", "tip": "Prefer `const` over `let` when the variable won't be reassigned." },
8+
{ "id": 7, "category": "JavaScript/React Tips", "tip": "Use React.memo() to prevent unnecessary re-renders of functional components." },
9+
{ "id": 8, "category": "JavaScript/React Tips", "tip": "Use the nullish coalescing operator (`??`) instead of `||` when 0 or empty string are valid values." },
10+
{ "id": 9, "category": "Open Source Contribution Tips", "tip": "Always read CONTRIBUTING.md before opening a pull request." },
11+
{ "id": 10, "category": "Open Source Contribution Tips", "tip": "Start with 'good first issue' labeled issues when contributing to a new project." },
12+
{ "id": 11, "category": "Open Source Contribution Tips", "tip": "Keep your PRs small and focused — one feature or fix per PR." },
13+
{ "id": 12, "category": "Open Source Contribution Tips", "tip": "Sync your fork with the upstream repo regularly to avoid merge conflicts." },
14+
{ "id": 13, "category": "VS Code Shortcuts", "tip": "Use `Ctrl + P` to quickly open any file in your project." },
15+
{ "id": 14, "category": "VS Code Shortcuts", "tip": "Use `Ctrl + Shift + K` to delete an entire line instantly." },
16+
{ "id": 15, "category": "VS Code Shortcuts", "tip": "Use `Alt + Click` to place multiple cursors for simultaneous editing." },
17+
{ "id": 16, "category": "VS Code Shortcuts", "tip": "Use `Ctrl + backtick` to toggle the integrated terminal." },
18+
{ "id": 17, "category": "Productivity Tricks", "tip": "Use the Pomodoro technique — 25 minutes of focused work followed by a 5-minute break." },
19+
{ "id": 18, "category": "Productivity Tricks", "tip": "Rubber duck debugging: explain your code out loud to find bugs faster." },
20+
{ "id": 19, "category": "Productivity Tricks", "tip": "Write TODO comments in code to track unfinished work." },
21+
{ "id": 20, "category": "Productivity Tricks", "tip": "Take breaks — stepping away from a problem often leads to the solution faster." }
22+
]

0 commit comments

Comments
 (0)