Skip to content

Commit 914fc14

Browse files
Merge branch 'main' into main
2 parents 6a358d7 + 74af637 commit 914fc14

14 files changed

Lines changed: 849 additions & 248 deletions

File tree

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Assign owners for all files in the repository
2+
* @sanjay-kv @iitzIrFan @Adez017

.github/CODEOWNERS.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

blog/google-icon-update/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,14 @@ Google I/O 2026 is happening this week. The timing of this rollout is not accide
126126

127127
The new icons do one thing that the 2020 redesign refused to do: they let each app look different from the others.
128128

129-
The strict four-color rule is gone. Instead of forcing every icon to carry all four brand colors, the new design gives each app its own dominant color identity with gradients replacing the flat hard-edged color blocks.
129+
The previous requirement that each icon display all four brand colors has been removed, the new design gives each app its own dominant color identity with gradients replacing the flat hard-edged color blocks.
130130

131131
Looking at the new icons directly, here is what stands out:
132132
<BrowserWindow url="https://github.com/recodehive/recode-website/issues" bodyStyle={{padding: 0}}>
133133
![Google 2026 new icon design showing per-app gradient color identity](./assets/09-google-new-design.png)
134134
</BrowserWindow>
135135

136-
The icons are also physically larger in the launcher. Google removed the Workspace page container that previously boxed each icon in, giving more visual real estate to the icon shapes themselves.
136+
The icons are also physically larger in the launcher.Google also did away with the Workspace page-shaped container that surrounded the icons, allowing the designs to take up more space and become more visually prominent.
137137

138138
---
139139

package-lock.json

Lines changed: 103 additions & 172 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
"@mdx-js/react": "^3.0.0",
4343
"@monaco-editor/react": "^4.7.0",
4444
"@popperjs/core": "^2.11.8",
45-
"@radix-ui/react-avatar": "^1.1.7",
46-
"@radix-ui/react-collapsible": "^1.1.12",
45+
"@radix-ui/react-avatar": "^1.1.12",
46+
"@radix-ui/react-collapsible": "^1.1.13",
4747
"@radix-ui/react-slot": "^1.2.3",
4848
"@tsparticles/engine": "^4.0.5",
4949
"@tsparticles/react": "^4.1.0",
@@ -81,14 +81,14 @@
8181
"@types/react": "^19.1.9",
8282
"@types/react-dom": "^19.1.7",
8383
"@typescript-eslint/eslint-plugin": "^8.60.1",
84-
"@typescript-eslint/parser": "^8.60.1",
84+
"@typescript-eslint/parser": "^8.61.0",
8585
"autoprefixer": "^10.5.0",
8686
"eslint": "^10.4.1",
8787
"eslint-plugin-react": "^7.37.5",
8888
"husky": "^9.1.7",
8989
"lint-staged": "^17.0.7",
9090
"postcss": "^8.5.15",
91-
"prettier": "^3.8.3",
91+
"prettier": "^3.8.4",
9292
"prettier-plugin-tailwindcss": "^0.6.14",
9393
"tailwindcss": "^4.1.4",
9494
"typescript": "~5.3"

src/components/blogCarousel/blogCard.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
import React from "react";
33
import Link from "@docusaurus/Link";
44
import { getAuthorProfiles } from "../../utils/authors";
5-
import { usePluginData } from "@docusaurus/useGlobalData";
5+
66

77
interface BlogCardProps {
88
type: string;
99
date?: string;
1010
title: string;
11-
content: string;
1211
imageUrl: string;
1312
id: string;
1413
authors?: string[];
@@ -50,7 +49,6 @@ const BlogCard = ({
5049
type,
5150
date,
5251
title,
53-
content,
5452
imageUrl,
5553
id,
5654
authors,
@@ -80,7 +78,9 @@ const BlogCard = ({
8078
<div className="article-card">
8179
{/* Image */}
8280
<div className="card-image">
83-
<img src={imageUrl} alt={title} loading="lazy" />
81+
<Link to={`/blog/${id}`} className="block" aria-label={`Read ${title}`}>
82+
<img src={imageUrl} alt={title} loading="lazy" />
83+
</Link>
8484
</div>
8585

8686
{/* Content */}
@@ -202,4 +202,4 @@ const BlogCard = ({
202202
);
203203
};
204204

205-
export default BlogCard;
205+
export default BlogCard;

src/components/blogCarousel/blogCarousel.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@
102102
flex-shrink: 0;
103103
}
104104

105+
.card-image a {
106+
display: block;
107+
width: 100%;
108+
height: 100%;
109+
}
110+
105111
.card-image::after {
106112
content: "";
107113
position: absolute;
@@ -112,13 +118,15 @@
112118
opacity: 0;
113119
transition: opacity 0.35s ease;
114120
z-index: 1;
121+
pointer-events: none;
115122
}
116123

117124
.article-card:hover .card-image::after {
118125
opacity: 1;
119126
}
120127

121128
.card-image img {
129+
display: block;
122130
width: 100%;
123131
height: 100%;
124132
object-fit: cover;
@@ -157,6 +165,7 @@
157165
transition: color 0.2s ease;
158166
display: -webkit-box;
159167
-webkit-line-clamp: 2;
168+
line-clamp: 2;
160169
-webkit-box-orient: vertical;
161170
overflow: hidden;
162171
}
@@ -195,6 +204,7 @@
195204
margin: 0 0 16px 0;
196205
display: -webkit-box;
197206
-webkit-line-clamp: 3;
207+
line-clamp: 3;
198208
-webkit-box-orient: vertical;
199209
overflow: hidden;
200210
flex: 1;
@@ -635,6 +645,7 @@
635645
transform: translateY(-20px);
636646
transition: opacity 0.4s ease-in, transform 0.4s ease-in;
637647
}
648+
638649
/* ── Reading time ── */
639650
.card-reading-time {
640651
display: inline-flex;

src/components/dashboard/LeaderBoard/BadgeModal.tsx

Lines changed: 125 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// src/components/dashboard/LeaderBoard/BadgeModal.tsx
2-
import React, { useEffect } from "react";
2+
import React, { useEffect, useState } from "react";
33
import { motion, AnimatePresence } from "framer-motion";
4-
import { FaTimes } from "react-icons/fa";
4+
import { FaTimes, FaShareAlt, FaDownload } from "react-icons/fa";
55
import { useSafeColorMode } from "@site/src/utils/useSafeColorMode";
6+
import { Contributor } from "./leaderboard";
7+
import { generateShareCard } from "../../../utils/cardGenerator";
68

7-
interface BadgeConfig {
9+
export interface BadgeConfig {
810
image: string;
911
name: string;
1012
criteria: (prs: number, points: number) => boolean;
@@ -15,17 +17,94 @@ interface BadgeModalProps {
1517
onClose: () => void;
1618
earnedBadges: string[];
1719
allBadges: BadgeConfig[];
18-
contributorName?: string;
20+
contributor: Contributor;
21+
rank: number;
1922
}
2023

2124
export default function BadgeModal({
2225
isOpen,
2326
onClose,
2427
earnedBadges,
2528
allBadges,
26-
contributorName,
29+
contributor,
30+
rank,
2731
}: BadgeModalProps): JSX.Element | null {
2832
const { isDark } = useSafeColorMode();
33+
const [isSharing, setIsSharing] = useState(false);
34+
const [isDownloading, setIsDownloading] = useState(false);
35+
const [shareError, setShareError] = useState<string | null>(null);
36+
37+
const getCardBlob = async () => {
38+
return await generateShareCard({
39+
username: contributor.username,
40+
avatarUrl: contributor.avatar,
41+
prs: contributor.prs,
42+
points: contributor.points,
43+
rank,
44+
earnedBadges,
45+
allBadges,
46+
});
47+
};
48+
49+
const handleDownloadCard = async () => {
50+
setIsDownloading(true);
51+
setShareError(null);
52+
try {
53+
const cardBlob = await getCardBlob();
54+
const downloadUrl = URL.createObjectURL(cardBlob);
55+
const link = document.createElement("a");
56+
link.href = downloadUrl;
57+
link.download = `recodehive-${contributor.username}-achievements.png`;
58+
document.body.appendChild(link);
59+
link.click();
60+
document.body.removeChild(link);
61+
URL.revokeObjectURL(downloadUrl);
62+
} catch (err) {
63+
console.error("Error generating or downloading card: ", err);
64+
setShareError("Could not download achievements card. Please try again.");
65+
} finally {
66+
setIsDownloading(false);
67+
}
68+
};
69+
70+
const handleShareCard = async () => {
71+
setIsSharing(true);
72+
setShareError(null);
73+
try {
74+
const cardBlob = await getCardBlob();
75+
const file = new File(
76+
[cardBlob],
77+
`recodehive-${contributor.username}-achievements.png`,
78+
{ type: "image/png" }
79+
);
80+
81+
if (
82+
navigator.share &&
83+
navigator.canShare &&
84+
navigator.canShare({ files: [file] })
85+
) {
86+
await navigator.share({
87+
files: [file],
88+
title: "My Recode Hive Open Source Achievements",
89+
text: `Check out my open-source contribution achievements on Recode Hive! I am ranked #${rank} with ${contributor.prs} merged PRs and ${contributor.points} points. 🚀`,
90+
});
91+
} else {
92+
const downloadUrl = URL.createObjectURL(cardBlob);
93+
const link = document.createElement("a");
94+
link.href = downloadUrl;
95+
link.download = `recodehive-${contributor.username}-achievements.png`;
96+
document.body.appendChild(link);
97+
link.click();
98+
document.body.removeChild(link);
99+
URL.revokeObjectURL(downloadUrl);
100+
}
101+
} catch (err) {
102+
console.error("Error generating or sharing card: ", err);
103+
setShareError("Could not share achievements card. Please try again.");
104+
} finally {
105+
setIsSharing(false);
106+
}
107+
};
29108

30109
// Close modal on Escape key press
31110
useEffect(() => {
@@ -79,27 +158,58 @@ export default function BadgeModal({
79158
id="badge-modal-title"
80159
className={`badge-modal-title ${isDark ? "dark" : "light"}`}
81160
>
82-
{contributorName
83-
? `${contributorName}'s Badges`
84-
: "Achievement Badges"}
161+
{contributor.username ? `${contributor.username}'s Badges` : "Achievement Badges"}
85162
</h2>
86163
<p
87164
className={`badge-modal-subtitle ${isDark ? "dark" : "light"}`}
88165
>
89166
{earnedBadges.length} of {allBadges.length} badges earned
90167
</p>
91168
</div>
92-
<button
93-
className={`badge-modal-close ${isDark ? "dark" : "light"}`}
94-
onClick={onClose}
95-
aria-label="Close modal"
96-
>
97-
<FaTimes />
98-
</button>
169+
<div className="badge-modal-header-actions">
170+
<button
171+
className={`badge-modal-download-btn ${isDark ? "dark" : "light"}`}
172+
onClick={handleDownloadCard}
173+
disabled={isDownloading || isSharing}
174+
aria-label="Download achievements card"
175+
title="Download Card"
176+
>
177+
{isDownloading ? (
178+
<span className="badge-modal-spinner" />
179+
) : (
180+
<FaDownload />
181+
)}
182+
</button>
183+
<button
184+
className={`badge-modal-share-btn ${isDark ? "dark" : "light"}`}
185+
onClick={handleShareCard}
186+
disabled={isDownloading || isSharing}
187+
aria-label="Share achievements card"
188+
>
189+
{isSharing ? (
190+
<span className="badge-modal-spinner" />
191+
) : (
192+
<FaShareAlt style={{ marginRight: 6 }} />
193+
)}
194+
{isSharing ? "Generating..." : "Share Card"}
195+
</button>
196+
<button
197+
className={`badge-modal-close ${isDark ? "dark" : "light"}`}
198+
onClick={onClose}
199+
aria-label="Close modal"
200+
>
201+
<FaTimes />
202+
</button>
203+
</div>
99204
</div>
100205

101206
{/* Modal Body */}
102207
<div className={`badge-modal-body ${isDark ? "dark" : "light"}`}>
208+
{shareError && (
209+
<div className={`badge-modal-error-banner ${isDark ? "dark" : "light"}`}>
210+
{shareError}
211+
</div>
212+
)}
103213
<div className="badge-grid">
104214
{allBadges.map((badge, index) => {
105215
const isEarned = earnedBadges.includes(badge.image);

0 commit comments

Comments
 (0)