Skip to content

Commit e967edc

Browse files
authored
Merge pull request #135 from yashksaini-coder/clickable-cards
(feat): Clickable cards, with real-time stats of RecodeHive Org
2 parents ae0dddf + 03b654b commit e967edc

File tree

6 files changed

+592
-107
lines changed

6 files changed

+592
-107
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"react": "^18.0.0",
4545
"react-dom": "^18.0.0",
4646
"react-icons": "^5.5.0",
47+
"react-slot-counter": "^3.3.1",
4748
"rehype-katex": "^7.0.1",
4849
"remark-math": "^6.0.0",
4950
"styled-components": "^6.1.18",

src/components/Community/LandingCommunity.css

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
max-width: 100%;
1111
margin-bottom: 1rem;
1212
padding: auto 1rem;
13+
flex-wrap: wrap;
1314
}
1415

1516
.landing-community .landing-community__header .landing-community__title {
@@ -26,6 +27,12 @@
2627
text-shadow: 0 0 1px var(--ifm-color-primary);
2728
}
2829

30+
.landing-community .landing-community__header .landing-community__error {
31+
color: var(--ifm-color-warning);
32+
font-size: 0.9rem;
33+
margin-top: 0.5rem;
34+
}
35+
2936
.landing-community .landing-community__content {
3037
display: grid;
3138
grid-template-columns: 1fr 1fr;
@@ -48,6 +55,23 @@
4855
border-radius: 1rem;
4956
background-color: var(--ifm-color-background);
5057
box-shadow: 0 0 1px var(--ifm-color-primary);
58+
transition: all 0.3s ease;
59+
position: relative;
60+
}
61+
62+
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable {
63+
cursor: pointer;
64+
}
65+
66+
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable:hover,
67+
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable:focus {
68+
transform: scale(1.02);
69+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
70+
outline: none;
71+
}
72+
73+
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.loading {
74+
opacity: 0.7;
5175
}
5276

5377
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item:hover {
@@ -62,6 +86,49 @@
6286
color: var(--ifm-color-primary);
6387
text-shadow: 0 0 1px var(--ifm-color-primary);
6488
padding-bottom: 0.5rem;
89+
position: relative;
90+
}
91+
92+
/* SlotCounter styling */
93+
.landing-community .slot-counter-number {
94+
font-size: inherit;
95+
font-weight: inherit;
96+
color: inherit;
97+
text-shadow: inherit;
98+
}
99+
100+
.landing-community .slot-counter-separator {
101+
font-size: inherit;
102+
font-weight: inherit;
103+
color: inherit;
104+
text-shadow: inherit;
105+
}
106+
107+
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item .landing-community__loading {
108+
display: flex;
109+
align-items: center;
110+
justify-content: center;
111+
font-size: 2rem;
112+
}
113+
114+
.landing-community .loading-spinner {
115+
animation: spin 2s linear infinite;
116+
}
117+
118+
@keyframes spin {
119+
0% { transform: rotate(0deg); }
120+
100% { transform: rotate(360deg); }
121+
}
122+
123+
.landing-community .external-link-icon {
124+
font-size: 0.8em;
125+
margin-left: 0.3rem;
126+
opacity: 0.7;
127+
transition: opacity 0.2s ease;
128+
}
129+
130+
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item.clickable:hover .external-link-icon {
131+
opacity: 1;
65132
}
66133

67134
.landing-community .landing-community__content .landing-community__stats .landing-community__stat-item .landing-community__stat-description {
@@ -75,12 +142,19 @@
75142
border-radius: 1rem;
76143
background-color: var(--ifm-color-background);
77144
box-shadow: 0 0 1px var(--ifm-color-primary);
145+
transition: all 0.3s ease;
146+
position: relative;
78147
}
79148

80-
.landing-community .landing-community__content .landing-community__info:hover {
149+
.landing-community .landing-community__content .landing-community__info.clickable {
81150
cursor: pointer;
82-
transform: scale(1.01);
83-
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4);
151+
}
152+
153+
.landing-community .landing-community__content .landing-community__info.clickable:hover,
154+
.landing-community .landing-community__content .landing-community__info.clickable:focus {
155+
transform: scale(1.01);
156+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
157+
outline: none;
84158
}
85159

86160
.landing-community .landing-community__content .landing-community__info .landing-community__image {
@@ -100,12 +174,27 @@
100174
color: var(--ifm-color-primary);
101175
text-shadow: 0 0 1px var(--ifm-color-primary);
102176
text-decoration: none;
177+
font-weight: 600;
103178
}
104179

105180
.landing-community .landing-community__content .landing-community__info .landing-community__info-text .landing-community__link:hover {
106181
text-decoration: underline;
107182
}
108183

184+
.landing-community .external-link-indicator {
185+
display: flex;
186+
align-items: center;
187+
justify-content: center;
188+
margin-top: 0.5rem;
189+
gap: 0.5rem;
190+
opacity: 0.7;
191+
transition: opacity 0.2s ease;
192+
}
193+
194+
.landing-community .landing-community__content .landing-community__info.clickable:hover .external-link-indicator {
195+
opacity: 1;
196+
}
197+
109198
@media screen and (max-width: 768px) {
110199
.landing-community .landing-community__content {
111200
grid-template-columns: 1fr;

src/components/Community/index.tsx

Lines changed: 101 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { type FC, useEffect, useState, useMemo } from "react";
2+
import SlotCounter from 'react-slot-counter';
23
import "./LandingCommunity.css";
34
import { useCommunityStatsContext } from "@site/src/lib/statsProvider";
45

@@ -7,121 +8,150 @@ type Props = {
78
};
89

910
export const LandingCommunity: FC<Props> = ({ className }) => {
10-
const { githubStarCountText, githubContributorsCount, githubForksCount } = useCommunityStatsContext();
11-
const [state, setState] = useState({
12-
stat0: 0,
13-
stat1: 0,
14-
stat2: 0,
15-
stat3: 0,
16-
});
17-
11+
const {
12+
githubStarCountText,
13+
githubContributorsCountText,
14+
githubForksCountText,
15+
githubReposCountText,
16+
githubStarCount,
17+
githubContributorsCount,
18+
githubForksCount,
19+
githubReposCount,
20+
loading,
21+
error
22+
} = useCommunityStatsContext();
23+
24+
1825
const generateList = useMemo(() => [
1926
{
20-
stat: githubStarCountText,
21-
description: "Stars on our GitHub repository, showcase the support and contribution, we recieved from the community.",
22-
href: "https://github.com/recodehive",
23-
// https://github.com/CodeHarborHub/codeharborhub.github.io/stargazers
27+
stat: githubStarCount,
28+
statText: githubStarCountText,
29+
description: "Stars across all our GitHub repositories, showcasing the support and appreciation from the community.",
30+
href: "https://github.com/recodehive/Support",
31+
label: "GitHub Stars"
2432
},
2533
{
26-
stat: 20,
27-
description: "Live projects on recodehive, demonstrating the power of open-source collaboration.",
34+
stat: githubReposCount,
35+
statText: githubReposCountText,
36+
description: "Live public projects on RecodHive, demonstrating the power of open-source collaboration.",
37+
href: "https://github.com/orgs/recodehive/repositories?q=visibility%3Apublic+archived%3Afalse",
38+
label: "Public Repositories"
2839
},
2940
{
3041
stat: githubContributorsCount,
31-
description: "List of Contributors who have made our repository better.",
32-
href: "https://github.com/recodehive",
33-
// https://github.com/CodeHarborHub/codeharborhub.github.io/graphs/contributors
42+
statText: githubContributorsCountText,
43+
description: "Amazing contributors who have made our repositories better and helped build our community.",
44+
href: "https://github.com/orgs/recodehive/people",
45+
label: "Contributors"
3446
},
3547
{
3648
stat: githubForksCount,
37-
description: "Forks of our repository, showing how our community extends our work.",
38-
href: "https://github.com/recodehive",
39-
40-
//https://github.com/CodeHarborHub/codeharborhub.github.io/network/members
49+
statText: githubForksCountText,
50+
description: "Forks of our repositories, showing how our community extends and builds upon our work.",
51+
href: "https://github.com/orgs/recodehive/discussions",
52+
label: "Community Forks"
4153
},
42-
], [githubStarCountText, githubContributorsCount, githubForksCount]);
54+
], [githubStarCount, githubStarCountText, githubReposCount, githubReposCountText, githubContributorsCount, githubContributorsCountText, githubForksCount, githubForksCountText]);
4355

44-
const handleDynamicChange = (target: number, index: number) => {
45-
let count = 0;
46-
const increment = target / 100;
47-
const interval = setInterval(() => {
48-
count += increment;
49-
setState(prev => ({ ...prev, [`stat${index}`]: Math.round(count) }));
50-
if (count >= target) {
51-
setState(prev => ({ ...prev, [`stat${index}`]: target }));
52-
clearInterval(interval);
53-
}
54-
}, 20);
56+
const handleCardClick = (href: string) => {
57+
if (href) {
58+
window.open(href, '_blank', 'noopener,noreferrer');
59+
}
5560
};
5661

57-
useEffect(() => {
58-
generateList.forEach((item, index) => {
59-
handleDynamicChange(Number(item.stat), index);
60-
});
61-
}, [generateList]);
62-
6362
return (
6463
<div className={`landing-community ${className || ""}`}>
6564
<div className="landing-community__header">
6665
<h2 className="landing-community__title">
6766
Discover the strength of our{" "}
6867
<span className="landing-community__highlight">amazing community</span>.
6968
</h2>
69+
{error && (
70+
<div className="landing-community__error">
71+
<small>⚠️ Stats may be cached or incomplete</small>
72+
</div>
73+
)}
7074
</div>
7175

7276
<div className="landing-community__content">
7377
<div className="landing-community__stats">
7478
{generateList.map((item, index) => (
75-
<span key={index} className="landing-community__stat-item">
79+
<div
80+
key={index}
81+
className={`landing-community__stat-item ${item.href ? 'clickable' : ''} ${loading ? 'loading' : ''}`}
82+
onClick={() => handleCardClick(item.href)}
83+
role={item.href ? "button" : "presentation"}
84+
tabIndex={item.href ? 0 : -1}
85+
onKeyDown={(e) => {
86+
if (item.href && (e.key === 'Enter' || e.key === ' ')) {
87+
e.preventDefault();
88+
handleCardClick(item.href);
89+
}
90+
}}
91+
title={item.href ? `Click to visit ${item.label}` : item.label}
92+
>
7693
<div className="landing-community__stat-value">
77-
{item.href ? (
78-
<a
79-
href={item.href}
80-
target="_blank"
81-
rel="noopener noreferrer"
82-
>
83-
{`${state[`stat${index}`]}${index !== 1 ? "" : ""}`}
84-
</a>
94+
{loading ? (
95+
<div className="landing-community__loading">
96+
<span className="loading-spinner"></span>
97+
</div>
8598
) : (
86-
`${state[`stat${index}`]}`
99+
<span>
100+
<SlotCounter
101+
value={item.stat}
102+
duration={2}
103+
animateOnVisible={{
104+
triggerOnce: true,
105+
rootMargin: '0px 0px -100px 0px'
106+
}}
107+
numberSlotClassName="slot-counter-number"
108+
separatorClassName="slot-counter-separator"
109+
/>
110+
{item.href && <span className="external-link-icon"></span>}
111+
</span>
87112
)}
88113
</div>
89114
<div className="landing-community__stat-description">
90115
{item.description}
91116
</div>
92-
</span>
117+
</div>
93118
))}
94119
</div>
95120

96-
<div className="landing-community__info">
121+
<div
122+
className="landing-community__info clickable"
123+
onClick={() => handleCardClick("https://github.com/recodehive")}
124+
role="button"
125+
tabIndex={0}
126+
onKeyDown={(e) => {
127+
if (e.key === 'Enter' || e.key === ' ') {
128+
e.preventDefault();
129+
handleCardClick("https://github.com/recodehive");
130+
}
131+
}}
132+
title="Click to visit RecodHive GitHub Organization"
133+
>
97134
<img
98135
className="landing-community__image"
99136
src="/community.png"
100137
alt="team collaboration"
101138
loading="lazy"
102139
/>
103140
<div className="landing-community__info-text">
104-
Our developers are the core of Hive community. We take pride in
141+
Our developers are the core of RecodHive community. We take pride in
105142
our{" "}
106-
<a
107-
href="https://github.com/recodehive"
108-
//https://github.com/CodeHarborHub/codeharborhub.github.io/graphs/contributors
109-
target="_blank"
110-
rel="noopener noreferrer"
111-
className="landing-community__link"
112-
>
113-
GitHub community
114-
</a>{" "}
115-
with over{" "}
116-
<a
117-
href="https://github.com/recodehive"
118-
target="_blank"
119-
rel="noopener noreferrer"
120-
className="landing-community__link"
121-
>
122-
500+ contributors
123-
</a>{" "}
124-
powering recodehive.
143+
<span className="landing-community__link">
144+
GitHub organization
145+
</span>{" "}
146+
with amazing{" "}
147+
<span className="landing-community__link">
148+
contributors and maintainers
149+
</span>{" "}
150+
powering RecodHive's growth.
151+
<div className="external-link-indicator">
152+
<span className="external-link-icon"></span>
153+
<small>Click to explore our GitHub</small>
154+
</div>
125155
</div>
126156
</div>
127157
</div>

0 commit comments

Comments
 (0)