fix(client): daily challenge calendar (#66202)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Tom
2026-03-03 07:28:54 -06:00
committed by GitHub
parent b9307e19b6
commit 5d10ce3fb4
4 changed files with 102 additions and 128 deletions
@@ -1,12 +1,11 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Spacer } from '@freecodecamp/ui';
import { Link } from '../helpers';
import GreenPass from '../../assets/icons/green-pass';
import GreenNotCompleted from '../../assets/icons/green-not-completed';
import JavaScriptIcon from '../../assets/icons/javascript';
import PythonIcon from '../../assets/icons/python';
import { formatDisplayDate } from './helpers';
import { formatDisplayDate, truncate } from './helpers';
interface CalendarDayProps {
dayNumber: number;
@@ -17,33 +16,33 @@ interface CalendarDayProps {
title?: string;
}
function Checkmark({ completed }: { completed: boolean }): JSX.Element {
return completed ? (
function Checkmark({ isCompleted }: { isCompleted: boolean }) {
return isCompleted ? (
<span
className='dc-checkmark dc-small-checkmark completed'
className='dc-checkmark completed'
data-playwright-test-label='calendar-day-completed'
>
<GreenPass />
</span>
) : (
<span
className='dc-checkmark dc-small-checkmark not-completed'
className='dc-checkmark not-completed'
data-playwright-test-label='calendar-day-not-completed'
>
<GreenNotCompleted />
</span>
);
}
function DailyCodingChallengeCalendarDay({
dayNumber,
date,
isAvailable = false,
title,
title = '',
completedLanguages = [],
challengeNumber
}: CalendarDayProps): JSX.Element {
const { t } = useTranslation();
const completed = completedLanguages.length > 0;
// dayNumber = 0 -> render nothing
if (dayNumber === 0) return <div></div>;
@@ -74,40 +73,29 @@ function DailyCodingChallengeCalendarDay({
{dayNumber}
</span>
<div className='dc-number'>#{challengeNumber}</div>
<span className='dc-number'>#{challengeNumber}</span>
<div className='dc-info'>
<div className='dc-title'>{title}</div>
<div className='dc-title-wrap'>
<div className='dc-title'>{truncate(title)}</div>
</div>
{completedLanguages.length === 2 ? (
<span className='dc-checkmark dc-big-checkmark completed'>
<span className='dc-spacer'>
<Spacer size='s' />
</span>
<GreenPass />
</span>
) : (
<div className='dc-languages'>
<hr />
<div className='dc-language'>
<div className='dc-language-icon'>
<JavaScriptIcon />
</div>
<div className='dc-language-name'>JavaScript</div>
<Checkmark
completed={completedLanguages.includes('javascript')}
/>
</div>
<Checkmark isCompleted={completed} />
<div className='dc-language'>
<div className='dc-language-icon'>
<PythonIcon />
</div>
<div className='dc-language-name'>Python</div>
<Checkmark completed={completedLanguages.includes('python')} />
<div className='dc-languages'>
{completedLanguages.includes('javascript') && (
<div className='dc-language-icon'>
<JavaScriptIcon />
<span className='sr-only'>JavaScript</span>
</div>
</div>
)}
)}
{completedLanguages.includes('python') && (
<div className='dc-language-icon'>
<PythonIcon />
<span className='sr-only'>Python</span>
</div>
)}
</div>
</div>
</Link>
);
@@ -28,7 +28,7 @@
.calendar-day {
position: relative;
padding: 10px 10px 20px;
padding: 5px;
min-height: 200px;
border: 1px solid var(--tertiary-color);
background-color: var(--primary-background);
@@ -37,41 +37,61 @@
.calendar-day-number {
position: absolute;
font-size: 0.8em;
top: 0;
left: 5px;
right: 5px;
color: var(--gray-45);
}
.dc-number {
position: absolute;
top: 0;
left: 5px;
font-size: 0.8em;
font-style: italic;
color: var(--gray-45);
text-align: center;
}
.dc-info {
position: relative;
padding-top: 15px;
display: flex;
flex-direction: column;
height: 100%;
}
.dc-title {
.dc-title-wrap {
width: 100%;
min-height: 55px;
display: flex;
align-items: center;
justify-content: center;
padding: 2px 10px;
}
.dc-title {
text-align: center;
align-self: center;
margin-top: 5px;
font-size: 0.9em;
}
.dc-checkmark {
width: 100%;
position: absolute;
top: 50%;
}
.dc-checkmark svg {
width: 50px;
height: 50px;
margin-bottom: -5px;
margin: 0 auto;
}
.dc-languages {
padding: 0 15px;
}
.dc-language {
font-size: 0.8rem;
position: absolute;
bottom: 0;
left: 0;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
gap: 2px;
}
.dc-language-icon {
@@ -83,25 +103,6 @@
height: 25px;
}
.dc-small-checkmark {
position: relative;
/* top: -5px; */
}
.dc-small-checkmark svg {
width: 20px;
height: 20px;
}
.dc-big-checkmark {
margin: 0 auto;
}
.dc-big-checkmark svg {
width: 50px;
height: 50px;
}
.not-available:hover,
.not-available:active {
cursor: not-allowed;
@@ -132,6 +133,17 @@
stroke: var(--primary-background);
}
.dc-language-icon svg path {
fill: var(--gray-45);
stroke: var(--gray-45);
}
.available:hover .dc-language-icon svg path,
.available:active .dc-language-icon svg path {
fill: var(--quaternary-background);
stroke: var(--quaternary-background);
}
.available:hover .not-completed svg circle,
.available:active .not-completed svg circle {
fill: var(--primary-color);
@@ -162,74 +174,41 @@
}
}
@media (max-width: 1345px) {
.calendar-day-number,
.dc-number,
.dc-title {
font-size: 0.8rem;
}
.dc-info hr {
margin: 10px 0;
}
.calendar-day {
min-height: 170px;
}
.dc-language {
justify-content: center;
}
.dc-language-name {
display: none;
}
}
@media (max-width: 1115px) {
.calendar-grid {
max-width: unset;
}
.calendar-day {
min-height: 125px;
padding: 10px;
}
.dc-title,
.dc-info hr {
display: none;
}
.dc-info {
display: flex;
justify-content: center;
align-items: center;
height: 80%;
}
.dc-spacer {
display: none;
}
}
@media (max-width: 815px) {
.calendar-day {
min-height: 100px;
padding: 0;
}
.dc-number {
text-align: right;
padding-right: 5px;
.calendar-day-number {
display: none;
}
.dc-languages {
.dc-info {
padding: 0;
justify-content: center;
}
.dc-big-checkmark svg {
.dc-title-wrap {
display: none;
}
.dc-checkmark {
top: unset;
}
.dc-checkmark svg {
width: 40px;
height: 40px;
}
}
@media (max-width: 1300px) {
.dc-title-wrap {
padding: 0;
}
.dc-title {
font-size: 0.8em;
}
}
@@ -33,3 +33,10 @@ export function formatDisplayDate(dateString: string) {
}
return format(parsedDate, 'MMMM d, yyyy');
}
export function truncate(str: string, maxLength = 35) {
if (str.length <= maxLength) {
return str;
}
return str.slice(0, maxLength) + '...';
}