From adea2a91bf59f9bb143718925bdff70f86f5ab47 Mon Sep 17 00:00:00 2001 From: Condition00 Date: Thu, 2 Oct 2025 17:33:10 +0530 Subject: [PATCH] fixed issue#659 --- .../FloatingContributors.css | 107 ++++++++-------- src/components/FloatingContributors/index.tsx | 118 +++++++++--------- 2 files changed, 114 insertions(+), 111 deletions(-) diff --git a/src/components/FloatingContributors/FloatingContributors.css b/src/components/FloatingContributors/FloatingContributors.css index 755b56bb..b4d2bfa6 100644 --- a/src/components/FloatingContributors/FloatingContributors.css +++ b/src/components/FloatingContributors/FloatingContributors.css @@ -30,7 +30,7 @@ -webkit-backdrop-filter: blur(20px); border: none; border-radius: 20px; - padding: 18px; + padding: 20px; box-shadow: 0 15px 35px rgba(108, 74, 232, 0.15), 0 5px 15px rgba(0, 0, 0, 0.05); @@ -40,12 +40,12 @@ color: #1a202c; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; min-width: 330px; - width: 330px; /* Set fixed width */ - height: 290px; /* Reduced fixed height */ + width: 330px; + min-height: 290px; /* Increased min-height for better content fit */ transition: all 0.4s ease; display: flex; flex-direction: column; - justify-content: space-between; /* Distribute space evenly */ + gap: 12px; } /* New activity feed styles */ @@ -210,13 +210,14 @@ /* Header embedded version - larger size */ .floating-contributors-container.header-embedded .floating-contributors-card { min-width: 450px; - width: 450px; /* Set fixed width */ - height: 370px; /* Updated height as requested */ - padding: 28px; + width: 450px; + min-height: 370px; /* content fit */ + padding: 24px; border-radius: 24px; box-shadow: 0 15px 35px rgba(108, 74, 232, 0.12), 0 5px 15px rgba(0, 0, 0, 0.03); + gap: 16px; } @@ -378,11 +379,10 @@ /* Header */ .floating-contributors-header { - margin-bottom: 10px; padding-right: 30px; display: flex; flex-direction: column; - gap: 2px; + gap: 4px; } .floating-contributors-title { @@ -424,18 +424,17 @@ .floating-contributors-activity { display: flex; align-items: center; - gap: 10px; - padding: 10px 14px; + gap: 12px; + padding: 12px 16px; background: rgba(255, 255, 255, 0.4); border-radius: 14px; - margin-bottom: 10px; border: none; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; position: relative; overflow: hidden; - height: 60px; /* Slightly reduced height */ - box-sizing: border-box; /* Include padding in height calculation */ + min-height: 64px; + box-sizing: border-box; box-shadow: 0 2px 8px rgba(108, 74, 232, 0.08); } @@ -540,17 +539,18 @@ .activity-details { flex: 1; min-width: 0; - overflow: hidden; /* Hide overflow */ - max-width: calc(100% - 60px); /* Account for avatar + padding */ + overflow: hidden; + max-width: calc(100% - 60px); display: flex; flex-direction: column; + gap: 2px; } .activity-user { display: flex; align-items: center; - gap: 6px; - margin-bottom: 3px; + gap: 8px; + flex-wrap: wrap; } .activity-username { @@ -605,13 +605,12 @@ .activity-message { font-size: 13px; - margin: 2px 0; color: rgba(60, 60, 60, 0.8); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; - line-height: 1.3; + line-height: 1.4; } [data-theme='light'] .activity-message { @@ -620,22 +619,21 @@ /* Contributors grid */ .floating-contributors-grid { - margin-bottom: 0px; - flex: 0 0 auto; /* Don't grow, don't shrink, use auto height */ + flex: 0 0 auto; display: flex; flex-direction: column; - overflow: hidden; /* Hide overflow */ + overflow: visible; + gap: 8px; } .contributors-grid-header { display: flex; align-items: center; justify-content: space-between; - margin-bottom: 10px; font-size: 13px; font-weight: 600; color: #333; - padding: 3%; + padding: 8px 12px; background: rgba(205, 205, 205, 0.562); border-radius: 12px; box-shadow: none; @@ -663,19 +661,23 @@ .contributors-avatars { display: flex; flex-wrap: wrap; - gap: 10px; + gap: 8px; align-items: center; - max-height: 50px; /* Set fixed height */ - padding: 10px; /* Add padding around all avatars */ + padding: 12px; background: rgba(255, 255, 255, 0.3); border-radius: 12px; - margin-top: 8px; box-shadow: inset 0 2px 6px rgba(108, 74, 232, 0.05); + overflow: visible; } .contributor-avatar-wrapper { position: relative; cursor: pointer; + z-index: 1; +} + +.contributor-avatar-wrapper:hover { + z-index: 10; } .contributor-avatar { @@ -730,22 +732,22 @@ .contributor-tooltip { position: absolute; - bottom: 100%; + bottom: calc(100% + 8px); left: 50%; - transform: translateX(-50%) translateY(4px); - background: rgba(0, 0, 0, 0.9); + transform: translateX(-50%); + background: rgba(0, 0, 0, 0.92); color: white; - padding: 8px 12px; - border-radius: 8px; + padding: 6px 10px; + border-radius: 6px; font-size: 11px; white-space: nowrap; opacity: 0; visibility: hidden; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - margin-bottom: 8px; - z-index: 20; + transition: opacity 0.2s ease, visibility 0.2s ease, transform 0.2s ease; + z-index: 100; pointer-events: none; backdrop-filter: blur(8px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } .contributor-tooltip::after { @@ -754,14 +756,14 @@ top: 100%; left: 50%; transform: translateX(-50%); - border: 4px solid transparent; - border-top-color: rgba(0, 0, 0, 0.9); + border: 5px solid transparent; + border-top-color: rgba(0, 0, 0, 0.92); } .contributor-avatar-wrapper:hover .contributor-tooltip { opacity: 1; visibility: visible; - transform: translateX(-50%) translateY(-8px); + transform: translateX(-50%) translateY(-4px); } .tooltip-name, @@ -812,9 +814,7 @@ /* Footer */ .floating-contributors-footer { - padding-top: 8px; - margin-top: auto; /* Push to bottom of flex container */ - margin-bottom: 0; /* Remove bottom spacing */ + margin-top: auto; } [data-theme='light'] .floating-contributors-footer { @@ -827,7 +827,7 @@ justify-content: center; gap: 8px; width: 100%; - padding: 10px 0; + padding: 12px 16px; background: linear-gradient(90deg, #4f46e5 0%, #6366f1 100%); color: white; text-decoration: none; @@ -838,7 +838,7 @@ position: relative; overflow: hidden; box-shadow: 0 4px 12px rgba(79, 70, 229, 0.25); - height: 42px; /* Fixed height */ + min-height: 44px; border: none; } @@ -954,8 +954,9 @@ } .floating-contributors-card { - padding: 16px; + padding: 18px; border-radius: 16px; + gap: 10px; } .floating-contributors-title { @@ -1012,12 +1013,14 @@ } .floating-contributors-card { - padding: 14px; + padding: 16px; border-radius: 14px; + gap: 8px; } .floating-contributors-activity { - padding: 25px; + padding: 10px 12px; + min-height: 56px; } .activity-details { @@ -1095,7 +1098,7 @@ .status-dot { animation: none !important; } - + .floating-contributors-card { transform: none !important; } @@ -1107,11 +1110,11 @@ border-width: 2px; border-color: rgba(255, 255, 255, 0.8); } - + .activity-avatar { border-width: 3px; } - + .contributor-avatar { border-width: 3px; } @@ -1216,4 +1219,4 @@ .contributor-avatar, .activity-avatar { transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} \ No newline at end of file +} diff --git a/src/components/FloatingContributors/index.tsx b/src/components/FloatingContributors/index.tsx index df362f80..e6f4855d 100644 --- a/src/components/FloatingContributors/index.tsx +++ b/src/components/FloatingContributors/index.tsx @@ -5,25 +5,25 @@ import './FloatingContributors.css'; // Format relative time (e.g., "2 hours ago") const formatTimeAgo = (date: Date): string => { const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000); - + let interval = Math.floor(seconds / 31536000); if (interval > 1) return `${interval} years ago`; - + interval = Math.floor(seconds / 2592000); if (interval > 1) return `${interval} months ago`; - + interval = Math.floor(seconds / 86400); if (interval > 1) return `${interval} days ago`; if (interval === 1) return `1 day ago`; - + interval = Math.floor(seconds / 3600); if (interval > 1) return `${interval} hours ago`; if (interval === 1) return `1 hour ago`; - + interval = Math.floor(seconds / 60); if (interval > 1) return `${interval} minutes ago`; if (interval === 1) return `1 minute ago`; - + return `just now`; }; @@ -115,7 +115,7 @@ const FloatingContributors: React.FC = ({ headerEmbed }, { login: 'recodehive-team', - avatar_url: 'https://avatars.githubusercontent.com/u/150000000?v=4', + avatar_url: 'https://avatars.githubusercontent.com/u/150000000?v=4', html_url: 'https://github.com/recodehive', }, { @@ -130,11 +130,11 @@ const FloatingContributors: React.FC = ({ headerEmbed }, { login: 'coder', - avatar_url: 'https://avatars.githubusercontent.com/u/6154722?v=4', + avatar_url: 'https://avatars.githubusercontent.com/u/6154722?v=4', html_url: 'https://github.com/coder', }, ]; - + const actions: ContributorActivity['action'][] = ['pushed', 'created', 'merged', 'opened', 'commented']; const timeOffsets = [5, 10, 30, 60, 120, 240, 480]; // minutes const messages = [ @@ -146,11 +146,11 @@ const FloatingContributors: React.FC = ({ headerEmbed 'Updated dependencies', 'Fixed typo in README' ]; - + return fallbackContributors.map((contributor, index) => { const now = new Date(); const timestamp = new Date(now.getTime() - (timeOffsets[index % timeOffsets.length] * 60 * 1000)); - + return { id: `fallback-${index}`, contributor: { @@ -165,21 +165,21 @@ const FloatingContributors: React.FC = ({ headerEmbed }; }); }, []); - + // Fetch live data from GitHub const fetchLiveData = useCallback(async () => { try { // Use specific cache key for this repository's events const CACHE_KEY = 'recodehive_website_events'; const CACHE_DURATION = 2 * 60 * 1000; // 2 minutes - short for "live" data - + // Check if we have recent data already const now = Date.now(); if (lastFetched && now - lastFetched < 30000) { // Don't fetch more than once every 30 seconds return; } - + // Check for cached events let events: GitHubEvent[] = []; if (typeof window !== 'undefined') { @@ -195,20 +195,20 @@ const FloatingContributors: React.FC = ({ headerEmbed console.warn('Error retrieving cached events', e); } } - + // If no valid cache, fetch fresh data if (events.length === 0) { setLoading(true); - + // Fetch repository events from GitHub API const eventsResponse = await fetch('https://api.github.com/repos/recodehive/recode-website/events?per_page=30'); - + if (!eventsResponse.ok) { throw new Error(`GitHub API error: ${eventsResponse.status}`); } - + events = await eventsResponse.json(); - + // Save to cache if (typeof window !== 'undefined' && Array.isArray(events)) { try { @@ -221,7 +221,7 @@ const FloatingContributors: React.FC = ({ headerEmbed } } } - + // Process events into activities if (Array.isArray(events) && events.length > 0) { // Convert GitHub events to our activity format @@ -229,7 +229,7 @@ const FloatingContributors: React.FC = ({ headerEmbed // Map GitHub event types to our action types let action: ContributorActivity['action'] = 'other'; let message: string | undefined; - + switch (event.type) { case 'PushEvent': action = 'pushed'; @@ -252,9 +252,9 @@ const FloatingContributors: React.FC = ({ headerEmbed default: action = 'other'; } - + const timestamp = new Date(event.created_at); - + return { id: event.id, contributor: { @@ -268,21 +268,21 @@ const FloatingContributors: React.FC = ({ headerEmbed timeAgo: formatTimeAgo(timestamp), }; }); - + // Update only if we have events if (newActivities.length > 0) { setActivities(newActivities); - + // Extract contributors from these events const contributorsMap = new Map(); - + // Also fetch contributors directly for contribution counts try { const contributorsResponse = await fetch('https://api.github.com/repos/recodehive/recode-website/contributors?per_page=100'); - + if (contributorsResponse.ok) { const contributorsData = await contributorsResponse.json(); - + if (Array.isArray(contributorsData)) { contributorsData.forEach(contributor => { if (contributor.login && contributor.type === 'User') { @@ -299,7 +299,7 @@ const FloatingContributors: React.FC = ({ headerEmbed } } catch (error) { console.warn('Error fetching contributors:', error); - + // If we couldn't get contributors data, at least use actors from events events.forEach(event => { const login = event.actor.login; @@ -314,17 +314,17 @@ const FloatingContributors: React.FC = ({ headerEmbed } }); } - + // Update contributors if we found any if (contributorsMap.size > 0) { setContributors(Array.from(contributorsMap.values())); } } } - + setLastFetched(now); setLoading(false); - + // Set up next refresh if (refreshTimerRef.current) { clearTimeout(refreshTimerRef.current); @@ -332,15 +332,15 @@ const FloatingContributors: React.FC = ({ headerEmbed refreshTimerRef.current = setTimeout(() => { fetchLiveData(); }, 60000); // Refresh every minute - + } catch (error) { console.warn('Error fetching GitHub events:', error); - + // Use fallback data if we have no activities yet if (activities.length === 0) { const fallbackActivities = createFallbackActivities(); setActivities(fallbackActivities); - + // Create fallback contributors const contributorsMap = new Map(); fallbackActivities.forEach(activity => { @@ -355,22 +355,22 @@ const FloatingContributors: React.FC = ({ headerEmbed }); } }); - + setContributors(Array.from(contributorsMap.values())); } - + setLoading(false); } }, [activities.length, createFallbackActivities, lastFetched]); - + // Initialize component and start data fetching useEffect(() => { // Set loading state setLoading(true); - + // Fetch data immediately fetchLiveData(); - + // Clean up on unmount return () => { if (refreshTimerRef.current) { @@ -378,22 +378,22 @@ const FloatingContributors: React.FC = ({ headerEmbed } }; }, [fetchLiveData]); - + // Cycle through activities useEffect(() => { if (activities.length <= 1) return; - + const interval = setInterval(() => { setCurrentActivityIndex((prev) => (prev + 1) % activities.length); }, 4000); - + return () => clearInterval(interval); }, [activities.length]); - + // Get GitHub URL for event const getGitHubEventUrl = (activity: ContributorActivity): string => { const repoUrl = 'https://github.com/recodehive/recode-website'; - + switch (activity.action) { case 'pushed': return `${repoUrl}/commits`; @@ -409,7 +409,7 @@ const FloatingContributors: React.FC = ({ headerEmbed return repoUrl; } }; - + // Get icon for action type const getActionIcon = (action: ContributorActivity['action']): string => { switch (action) { @@ -422,7 +422,7 @@ const FloatingContributors: React.FC = ({ headerEmbed default: return '💻'; } }; - + // Get text for action type const getActionText = (action: ContributorActivity['action']): string => { switch (action) { @@ -435,15 +435,15 @@ const FloatingContributors: React.FC = ({ headerEmbed default: return 'ACTIVE'; } }; - + // Don't render anything while initial loading if (loading && activities.length === 0) { return null; } - + // Get current activity to display const currentActivity = activities[currentActivityIndex]; - + return ( {isVisible && ( @@ -519,7 +519,7 @@ const FloatingContributors: React.FC = ({ headerEmbed - +
@{currentActivity.contributor.login} @@ -543,7 +543,7 @@ const FloatingContributors: React.FC = ({ headerEmbed Recent Contributors {contributors.length}
- +
{contributors .sort((a, b) => b.contributions - a.contributions) // Sort contributors by contributions in descending order @@ -554,7 +554,7 @@ const FloatingContributors: React.FC = ({ headerEmbed className="contributor-avatar-wrapper" initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 1, scale: 1 }} - transition={{ + transition={{ delay: index * 0.05, type: "spring", stiffness: 300, @@ -562,9 +562,9 @@ const FloatingContributors: React.FC = ({ headerEmbed }} whileHover={{ scale: 1.1, zIndex: 5 }} > - = ({ headerEmbed ))} - - {contributors.length > 12 && ( + + {contributors.length > 5 && (
- +{contributors.length - 12} more + +{contributors.length - 5}
)}