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:
2025-06-06 23:36:00 +02:00
parent bbbcb96905
commit aa37cb23cf
3 changed files with 125 additions and 40 deletions

View 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>
);
}

View File

@@ -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>

View File

@@ -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>