Skip to content

Commit 43d772e

Browse files
committed
feat: enhance OpenSource page layout and functionality with improved styling and error handling
1 parent 016b328 commit 43d772e

10 files changed

Lines changed: 241 additions & 278 deletions

File tree

src/components/common/StatusShowingGroup.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,21 @@ const StatusContainer = styled.div`
88
display: flex;
99
justify-content: center;
1010
align-items: center;
11+
gap: 10px;
12+
padding: 48px 0;
13+
color: var(--text-secondary);
1114
12-
i {
13-
background-color: #feffe6 !important;
15+
.anticon {
16+
color: var(--color-primary);
1417
}
1518
16-
span {
17-
margin-left: 10px;
18-
color: #feffe6;
19+
.ant-spin-dot-item {
20+
background-color: var(--color-primary);
21+
}
22+
23+
.ant-typography {
24+
color: inherit;
25+
margin-bottom: 0;
1926
}
2027
`;
2128

@@ -34,7 +41,7 @@ export const StatusShowingGroup: React.FC<StatusShowingGroupProps> = ({
3441
<>
3542
{error && (
3643
<StatusContainer>
37-
<WarningOutlined style={{ fontSize: '24px', color: '#feffe6' }} />
44+
<WarningOutlined style={{ fontSize: '24px' }} />
3845
<Typography.Text>{t('error')}</Typography.Text>
3946
</StatusContainer>
4047
)}

src/components/openSource/HeroSection.tsx

Lines changed: 77 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { useEffect, useRef } from 'react';
21
import styled from 'styled-components';
32
import { useTranslation } from 'react-i18next';
3+
import { DatabaseOutlined, FireOutlined, StarOutlined } from '@ant-design/icons';
44

55
const HeroCard = styled.div`
66
display: flex;
7-
flex-direction: column;
8-
gap: 40px;
7+
justify-content: center;
98
margin-bottom: 64px;
109
padding: 48px;
1110
border-radius: var(--radius-xl);
1211
border: 1px solid var(--border-color);
13-
background: var(--bg-elevated);
12+
background: linear-gradient(145deg, var(--bg-elevated), var(--bg-primary));
1413
box-shadow: var(--shadow-sm);
1514
position: relative;
1615
overflow: hidden;
@@ -21,152 +20,135 @@ const HeroCard = styled.div`
2120
top: 0;
2221
left: 0;
2322
right: 0;
24-
height: 4px;
23+
height: 6px;
2524
background: var(--gradient-accent);
25+
opacity: 0.8;
2626
}
2727
2828
@media (min-width: 900px) {
29-
flex-direction: row;
3029
align-items: center;
31-
justify-content: space-between;
3230
}
3331
3432
@media (max-width: 768px) {
3533
padding: 32px 24px;
36-
gap: 32px;
3734
}
3835
`;
3936

40-
const HeroText = styled.div`
41-
display: flex;
42-
flex-direction: column;
43-
gap: 16px;
44-
max-width: 600px;
45-
`;
46-
47-
const Eyebrow = styled.span`
48-
font-size: 0.85rem;
49-
font-weight: 600;
50-
letter-spacing: 0.1em;
51-
text-transform: uppercase;
52-
color: var(--color-primary);
53-
`;
54-
55-
const Title = styled.h2`
56-
margin: 0;
57-
font-family: var(--font-heading);
58-
font-size: clamp(2.5rem, 4vw, 3.5rem);
59-
font-weight: 700;
60-
line-height: 1.1;
61-
color: var(--text-primary);
62-
`;
63-
64-
const Description = styled.p`
65-
margin: 0;
66-
color: var(--text-secondary);
67-
font-size: 1.05rem;
68-
line-height: 1.7;
69-
`;
70-
7137
const MetaGrid = styled.div`
7238
display: flex;
73-
gap: 32px;
39+
justify-content: center;
40+
gap: 48px;
7441
flex-wrap: wrap;
7542
7643
@media (max-width: 640px) {
7744
width: 100%;
78-
justify-content: space-between;
45+
flex-direction: column;
46+
justify-content: center;
7947
gap: 24px;
8048
}
8149
`;
8250

8351
const MetaCard = styled.div`
8452
display: flex;
8553
flex-direction: column;
86-
gap: 4px;
54+
align-items: center;
55+
gap: 12px;
56+
padding: 24px 40px;
57+
border-radius: var(--radius-lg);
58+
background: var(--bg-secondary);
59+
border: 1px solid transparent;
60+
transition: all 0.3s ease;
61+
62+
&:hover {
63+
transform: translateY(-4px);
64+
box-shadow: var(--shadow-md);
65+
border-color: var(--border-color);
66+
background: var(--bg-elevated);
67+
68+
.anticon {
69+
color: var(--color-accent);
70+
transform: scale(1.1);
71+
}
72+
}
73+
74+
@media (max-width: 640px) {
75+
width: 100%;
76+
padding: 24px;
77+
}
8778
`;
8879

8980
const MetaLabel = styled.div`
81+
display: flex;
82+
align-items: center;
83+
gap: 8px;
9084
color: var(--text-tertiary);
91-
font-size: 0.85rem;
92-
font-weight: 500;
85+
font-size: 0.95rem;
86+
font-weight: 600;
9387
text-transform: uppercase;
94-
letter-spacing: 0.05em;
88+
letter-spacing: 0.1em;
89+
90+
.anticon {
91+
font-size: 1.1rem;
92+
transition: all 0.3s ease;
93+
}
9594
`;
9695

9796
const MetaValue = styled.div`
9897
font-family: var(--font-heading);
99-
font-size: 2.5rem;
100-
font-weight: 700;
98+
font-size: 3.2rem;
99+
font-weight: 800;
101100
color: var(--text-primary);
102101
line-height: 1;
102+
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
103103
`;
104104

105-
// Direct DOM mutation ticker, constant speed (linear)
106-
const useNumberTicker = (end: number, duration: number = 1000) => {
107-
const nodeRef = useRef<HTMLDivElement>(null);
108-
109-
useEffect(() => {
110-
let startTime: number | null = null;
111-
let animationFrame: number;
112-
113-
const step = (timestamp: number) => {
114-
if (!startTime) startTime = timestamp;
115-
const progress = Math.min((timestamp - startTime) / duration, 1);
116-
117-
// Linear progression (勻速)
118-
const currentValue = Math.floor(progress * end);
119-
120-
if (nodeRef.current) {
121-
nodeRef.current.textContent = currentValue.toString();
122-
}
123-
124-
if (progress < 1) {
125-
animationFrame = window.requestAnimationFrame(step);
126-
}
127-
};
128-
129-
animationFrame = window.requestAnimationFrame(step);
130-
return () => window.cancelAnimationFrame(animationFrame);
131-
}, [end, duration]);
132-
133-
return nodeRef;
134-
};
135-
136105
interface HeroSectionProps {
106+
loading: boolean;
137107
stats: {
138108
totalCount: number;
139109
activeCount: number;
140110
totalStars: number;
141111
};
142112
}
143113

144-
const HeroSection = ({ stats }: HeroSectionProps) => {
145-
const { t } = useTranslation();
146-
147-
const totalRef = useNumberTicker(stats.totalCount);
148-
const activeRef = useNumberTicker(stats.activeCount);
149-
const starsRef = useNumberTicker(stats.totalStars);
114+
const getNumberLocale = (language: string) => {
115+
const normalizedLocale = language.replace('_', '-');
116+
117+
return Intl.NumberFormat.supportedLocalesOf([normalizedLocale])[0] ?? 'en-US';
118+
};
119+
120+
const HeroSection = ({ loading, stats }: HeroSectionProps) => {
121+
const { t, i18n } = useTranslation();
122+
const numberLocale = getNumberLocale(i18n.resolvedLanguage ?? i18n.language);
123+
124+
const formatValue = (value: number) => {
125+
if (loading) {
126+
return '--';
127+
}
128+
129+
return value.toLocaleString(numberLocale);
130+
};
150131

151132
return (
152133
<HeroCard>
153-
<HeroText>
154-
<Eyebrow>{t('opensource.eyebrow')}</Eyebrow>
155-
<Title>{t('opensource.title')}</Title>
156-
<Description>{t('opensource.intro')}</Description>
157-
</HeroText>
158134
<MetaGrid>
159135
<MetaCard>
160-
<MetaLabel>{t('opensource.stats.total')}</MetaLabel>
161-
<MetaValue ref={totalRef}>0</MetaValue>
136+
<MetaLabel>
137+
<DatabaseOutlined /> {t('opensource.stats.total')}
138+
</MetaLabel>
139+
<MetaValue>{formatValue(stats.totalCount)}</MetaValue>
162140
</MetaCard>
163141
<MetaCard>
164-
<MetaLabel>{t('opensource.stats.active')}</MetaLabel>
165-
<MetaValue ref={activeRef}>0</MetaValue>
142+
<MetaLabel>
143+
<FireOutlined /> {t('opensource.stats.active')}
144+
</MetaLabel>
145+
<MetaValue>{formatValue(stats.activeCount)}</MetaValue>
166146
</MetaCard>
167147
<MetaCard>
168-
<MetaLabel>{t('opensource.stats.stars')}</MetaLabel>
169-
<MetaValue ref={starsRef}>0</MetaValue>
148+
<MetaLabel>
149+
<StarOutlined /> {t('opensource.stats.stars')}
150+
</MetaLabel>
151+
<MetaValue>{formatValue(stats.totalStars)}</MetaValue>
170152
</MetaCard>
171153
</MetaGrid>
172154
</HeroCard>

src/components/openSource/OpenSourceStyles.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@ import styled from 'styled-components';
22

33
export const RepoSection = styled.section`
44
background: var(--bg-primary);
5+
background-image: radial-gradient(circle at top right, rgba(0, 0, 0, 0.02) 0%, transparent 60%),
6+
radial-gradient(circle at bottom left, rgba(0, 0, 0, 0.02) 0%, transparent 60%);
57
padding: 80px 24px 100px;
8+
position: relative;
9+
10+
[data-theme='dark'] & {
11+
background-image: radial-gradient(circle at top right, rgba(255, 255, 255, 0.03) 0%, transparent 60%),
12+
radial-gradient(circle at bottom left, rgba(255, 255, 255, 0.03) 0%, transparent 60%);
13+
}
614
715
@media (max-width: 768px) {
816
padding: 56px 16px 80px;
@@ -25,12 +33,20 @@ export const Grid = styled.div`
2533
}
2634
`;
2735

28-
export const StatusContainer = styled.div`
36+
export const EmptyState = styled.div`
2937
display: flex;
38+
flex-direction: column;
3039
justify-content: center;
31-
padding: 80px 0;
40+
align-items: center;
41+
padding: 80px 24px;
3242
color: var(--text-secondary);
3343
font-size: 1.1rem;
44+
text-align: center;
45+
background: var(--bg-elevated);
46+
border-radius: var(--radius-xl);
47+
border: 1px dashed var(--border-color);
48+
margin-top: 32px;
49+
backdrop-filter: blur(8px);
3450
`;
3551

3652
const languageColors: Record<string, string> = {

0 commit comments

Comments
 (0)