Enhance ContributionCalendar and development.astro for dark mode support and improved commit display
- Refactor ContributionCalendar to support light and dark color schemes based on user preference. - Implement dark mode detection using a MutationObserver to dynamically adjust styles. - Update development.astro to include a new CollapsibleIntro component for better user experience. - Improve commit display logic to format messages with bullet points and enhance layout for clarity.
This commit is contained in:
45
src/components/CollapsibleIntro.jsx
Normal file
45
src/components/CollapsibleIntro.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function CollapsibleIntro({ text }) {
|
||||
const STORAGE_KEY = 'devnet-intro-collapsed';
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored === 'true') setCollapsed(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, collapsed ? 'true' : 'false');
|
||||
}, [collapsed]);
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:underline focus:outline-none mb-2"
|
||||
onClick={() => setCollapsed((c) => !c)}
|
||||
aria-expanded={!collapsed}
|
||||
aria-controls="devnet-intro-text"
|
||||
>
|
||||
<span>{collapsed ? 'Show more' : 'Show less'}</span>
|
||||
<svg
|
||||
className={`transition-transform duration-200 w-4 h-4 ${collapsed ? 'rotate-0' : 'rotate-180'}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
id="devnet-intro-text"
|
||||
style={{ display: collapsed ? 'none' : 'block' }}
|
||||
className="text-lg text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
// Helper to get all days in the last 52 weeks, starting on Monday
|
||||
function getCalendarDays() {
|
||||
@@ -38,23 +38,45 @@ function getMonthLabels(days) {
|
||||
// Day labels for rows (Monday to Sunday)
|
||||
const DAY_LABELS = ['Mon', '', 'Wed', '', 'Fri', '', 'Sun'];
|
||||
|
||||
// Color scale (GitHub-like)
|
||||
const COLORS = [
|
||||
// Color scales
|
||||
const COLORS_LIGHT = [
|
||||
'#ebedf0', // 0
|
||||
'#c6e48b', // 1
|
||||
'#7bc96f', // 2
|
||||
'#239a3b', // 3
|
||||
'#196127', // 4+
|
||||
];
|
||||
function getColor(count) {
|
||||
if (!count) return COLORS[0];
|
||||
if (count >= 4) return COLORS[4];
|
||||
return COLORS[count];
|
||||
const COLORS_DARK = [
|
||||
'#23272e', // 0
|
||||
'#3c4d36', // 1
|
||||
'#4e7c4e', // 2
|
||||
'#399150', // 3
|
||||
'#6ee7b7', // 4+
|
||||
];
|
||||
|
||||
function getColor(count, isDark) {
|
||||
const palette = isDark ? COLORS_DARK : COLORS_LIGHT;
|
||||
if (!count) return palette[0];
|
||||
if (count >= 4) return palette[4];
|
||||
return palette[count];
|
||||
}
|
||||
|
||||
export default function ContributionCalendar({ data }) {
|
||||
const days = getCalendarDays();
|
||||
const monthLabels = getMonthLabels(days);
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Detect dark mode by checking for 'dark' class on <html>
|
||||
const checkDark = () => {
|
||||
setIsDark(document.documentElement.classList.contains('dark'));
|
||||
};
|
||||
checkDark();
|
||||
const observer = new MutationObserver(checkDark);
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
// Get max count for scaling (optional, for more dynamic color)
|
||||
// const max = Math.max(...Object.values(data));
|
||||
|
||||
@@ -66,11 +88,11 @@ export default function ContributionCalendar({ data }) {
|
||||
margin: '0 auto',
|
||||
padding: 0,
|
||||
borderRadius: '1rem',
|
||||
background: 'rgba(255,255,255,0.7)',
|
||||
boxShadow: '0 4px 24px 0 rgba(0,0,0,0.08)',
|
||||
background: isDark ? 'rgba(30,41,59,0.85)' : 'rgba(255,255,255,0.7)',
|
||||
boxShadow: isDark ? '0 4px 24px 0 rgba(0,0,0,0.32)' : '0 4px 24px 0 rgba(0,0,0,0.08)',
|
||||
backdropFilter: 'blur(12px)',
|
||||
WebkitBackdropFilter: 'blur(12px)',
|
||||
border: '1px solid rgba(255,255,255,0.3)',
|
||||
border: isDark ? '1px solid rgba(51,65,85,0.5)' : '1px solid rgba(255,255,255,0.3)',
|
||||
overflowX: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -92,7 +114,7 @@ export default function ContributionCalendar({ data }) {
|
||||
flex: '0 0 14px',
|
||||
textAlign: 'center',
|
||||
fontSize: 12,
|
||||
color: '#888',
|
||||
color: isDark ? '#b6c2d1' : '#888',
|
||||
fontWeight: 500,
|
||||
minWidth: 14,
|
||||
}}
|
||||
@@ -111,7 +133,7 @@ export default function ContributionCalendar({ data }) {
|
||||
style={{
|
||||
height: 14,
|
||||
fontSize: 12,
|
||||
color: '#888',
|
||||
color: isDark ? '#b6c2d1' : '#888',
|
||||
textAlign: 'right',
|
||||
lineHeight: '14px',
|
||||
marginBottom: 1,
|
||||
@@ -141,7 +163,7 @@ export default function ContributionCalendar({ data }) {
|
||||
width: 12,
|
||||
height: 12,
|
||||
margin: 1,
|
||||
background: getColor(count),
|
||||
background: getColor(count, isDark),
|
||||
borderRadius: 2,
|
||||
}}
|
||||
/>
|
||||
@@ -151,9 +173,9 @@ export default function ContributionCalendar({ data }) {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: '#888', marginTop: 4, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 12, color: isDark ? '#b6c2d1' : '#888', marginTop: 4, textAlign: 'center' }}>
|
||||
<span>Less</span>
|
||||
{COLORS.map((color, i) => (
|
||||
{(isDark ? COLORS_DARK : COLORS_LIGHT).map((color, i) => (
|
||||
<span key={i} style={{ display: 'inline-block', width: 12, height: 12, background: color, margin: '0 2px', borderRadius: 2 }} />
|
||||
))}
|
||||
<span>More</span>
|
||||
|
@@ -5,6 +5,7 @@ import { getTranslation } from '../../i18n/translations';
|
||||
import React, { useMemo } from 'react';
|
||||
import dynamic from 'astro/dynamic';
|
||||
import ContributionCalendar from '../../components/ContributionCalendar.jsx';
|
||||
import CollapsibleIntro from '../../components/CollapsibleIntro.jsx';
|
||||
|
||||
const metadata = {
|
||||
title: 'Development Progress | 365DevNet',
|
||||
@@ -42,11 +43,11 @@ try {
|
||||
|
||||
// Format date to a readable format
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString(lang, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
const date = new Date(dateString);
|
||||
const day = date.toLocaleString(lang, { day: '2-digit' });
|
||||
const month = date.toLocaleString(lang, { month: 'short' }).toLowerCase().replace('.', '');
|
||||
const year = date.getFullYear();
|
||||
return `${day}-${month}-${year}`;
|
||||
};
|
||||
|
||||
// Helper: Group commits by date (YYYY-MM-DD)
|
||||
@@ -67,9 +68,8 @@ const contributionData = getContributionData(commits);
|
||||
<Layout metadata={metadata}>
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<h1 class="text-4xl font-bold mb-4">{t.development.title || 'Development Progress'}</h1>
|
||||
<p class="mb-8 text-lg text-gray-700 dark:text-gray-300">
|
||||
{t.development.intro}
|
||||
</p>
|
||||
{/* Collapsible Intro */}
|
||||
<CollapsibleIntro text={t.development.intro} client:only="react" />
|
||||
{/* Contribution Calendar */}
|
||||
<div class="mb-8">
|
||||
<ContributionCalendar data={contributionData} client:only="react" />
|
||||
@@ -80,26 +80,44 @@ const contributionData = getContributionData(commits);
|
||||
{Array.isArray(commits) && commits.length > 0 ? (
|
||||
<div class="space-y-4">
|
||||
{commits.slice(0, 10).map((commit) => (
|
||||
<div class="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="font-medium">{commit.commit?.message?.split('\n')[0] || 'No message'}</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{commit.commit?.message || ''}</p>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">{commit.commit?.author?.date ? formatDate(commit.commit.author.date) : ''}</span>
|
||||
<div class="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg flex flex-col md:flex-row md:items-stretch md:gap-8">
|
||||
<div class="flex-1">
|
||||
<h3 class="font-medium">{commit.commit?.message?.split('\n')[0] || 'No message'}</h3>
|
||||
{/* Format commit description with bullet points on new lines */}
|
||||
{commit.commit?.message ? (
|
||||
(() => {
|
||||
const lines = commit.commit.message.split('\n');
|
||||
const bullets = lines.filter(line => line.trim().startsWith('- '));
|
||||
if (bullets.length > 0) {
|
||||
// Render as a list if there are bullet points
|
||||
return (
|
||||
<ul class="text-sm text-gray-600 dark:text-gray-400 list-disc ml-5">
|
||||
{lines.map((line, idx) =>
|
||||
line.trim().startsWith('- ')
|
||||
? <li key={idx}>{line.trim().slice(2)}</li>
|
||||
: line.trim() !== '' && <li key={idx} class="list-none pl-0">{line}</li>
|
||||
)}
|
||||
</ul>
|
||||
);
|
||||
} else {
|
||||
// Render as a paragraph if no bullet points
|
||||
return <p class="text-sm text-gray-600 dark:text-gray-400">{commit.commit.message}</p>;
|
||||
}
|
||||
})()
|
||||
) : null}
|
||||
</div>
|
||||
<div class="mt-2 text-sm">
|
||||
<span class="text-gray-500">Commit:</span>{' '}
|
||||
{commit.html_url ? (
|
||||
<a href={commit.html_url} target="_blank" rel="noopener">
|
||||
<code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
|
||||
{commit.sha ? commit.sha.slice(0, 7) : ''}
|
||||
<div class="flex flex-col items-end min-w-[140px] bg-gray-100 dark:bg-gray-700 rounded-lg px-4 py-2 mt-4 md:mt-0 md:ml-8 shadow-sm border border-gray-200 dark:border-gray-600">
|
||||
<span class="text-base font-semibold text-gray-800 dark:text-gray-200 mb-1">{commit.commit?.author?.date ? formatDate(commit.commit.author.date) : ''}</span>
|
||||
{commit.commit?.author?.name && (
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 mb-2">{commit.commit.author.name}</span>
|
||||
)}
|
||||
{commit.sha && (
|
||||
<a href={commit.html_url} target="_blank" rel="noopener" class="block mt-1 text-right">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-300 block">Commit:</span>
|
||||
<code class="mt-1 px-2 py-1 rounded bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 font-mono text-xs text-blue-700 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-gray-900 transition-colors cursor-pointer block">
|
||||
{commit.sha.slice(0, 7)}
|
||||
</code>
|
||||
</a>
|
||||
) : (
|
||||
<code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
|
||||
{commit.sha ? commit.sha.slice(0, 7) : ''}
|
||||
</code>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user