♻️(front) refactor MiniCalendar component

Update MiniCalendar for integration with CalendarContext
and improved locale support for date navigation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Nathan Panchout
2026-01-25 20:34:57 +01:00
parent 8be9b1db52
commit 717029bd96
2 changed files with 140 additions and 58 deletions

View File

@@ -6,16 +6,23 @@
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
margin-bottom: 1rem;
padding: 0 0.25rem;
}
&__month-title {
font-size: 0.875rem;
font-size: 1rem;
font-weight: 500;
text-transform: capitalize;
color: var(--c--theme--colors--greyscale-800);
}
&__nav {
display: flex;
align-items: center;
gap: 0.25rem;
}
&__nav-btn {
display: flex;
align-items: center;
@@ -26,11 +33,12 @@
background: transparent;
border-radius: 50%;
cursor: pointer;
color: var(--c--theme--colors--greyscale-600);
transition: background-color 0.15s;
color: var(--c--theme--colors--greyscale-500);
transition: background-color 0.15s, color 0.15s;
&:hover {
background-color: var(--c--theme--colors--greyscale-100);
color: var(--c--theme--colors--greyscale-700);
}
.material-icons {
@@ -38,9 +46,14 @@
}
}
&__grid {
display: flex;
flex-direction: column;
}
&__weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-template-columns: 2rem repeat(7, 1fr);
gap: 0;
margin-bottom: 0.25rem;
}
@@ -50,16 +63,37 @@
align-items: center;
justify-content: center;
height: 1.5rem;
font-size: 0.625rem;
font-weight: 500;
color: var(--c--theme--colors--greyscale-500);
text-transform: uppercase;
font-size: 0.75rem;
font-weight: 600;
color: var(--c--theme--colors--greyscale-800);
text-transform: lowercase;
&--week-num {
color: var(--c--theme--colors--greyscale-500);
font-weight: 500;
font-size: 0.7rem;
}
}
&__days {
&__body {
display: flex;
flex-direction: column;
gap: 2px;
}
&__week {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
grid-template-columns: 2rem repeat(7, 1fr);
gap: 0;
}
&__week-number {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
color: var(--c--theme--colors--greyscale-400);
font-weight: 400;
}
&__day {
@@ -70,10 +104,11 @@
aspect-ratio: 1;
border: none;
background: transparent;
border-radius: 50%;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
font-size: 0.8rem;
color: var(--c--theme--colors--greyscale-800);
font-weight: 500;
transition: background-color 0.15s;
&:hover {
@@ -82,21 +117,22 @@
&--outside {
color: var(--c--theme--colors--greyscale-400);
font-weight: 400;
}
&--today {
background-color: var(--c--theme--colors--primary-100);
color: var(--c--theme--colors--primary-600);
font-weight: 600;
}
&--selected {
background-color: var(--c--theme--colors--primary-500);
background-color: #8b4513;
color: white;
font-weight: 600;
border-radius: 4px;
&:hover {
background-color: var(--c--theme--colors--primary-600);
background-color: #7a3d11;
}
}
}

View File

@@ -2,7 +2,7 @@
* MiniCalendar component - A small month calendar for date navigation.
*/
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import {
addMonths,
@@ -10,6 +10,7 @@ import {
endOfMonth,
endOfWeek,
format,
getWeek,
isSameDay,
isSameMonth,
startOfMonth,
@@ -17,18 +18,42 @@ import {
subMonths,
} from "date-fns";
import { fr } from "date-fns/locale";
import { useTranslation } from "react-i18next";
import { useCalendarContext } from "../contexts";
interface MiniCalendarProps {
selectedDate: Date;
onDateSelect: (date: Date) => void;
}
// Helper to chunk array into groups of n
const chunkArray = <T,>(arr: T[], size: number): T[][] => {
const result: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
};
export const MiniCalendar = ({
selectedDate,
onDateSelect,
}: MiniCalendarProps) => {
const { t } = useTranslation();
const { goToDate, currentDate } = useCalendarContext();
const [viewDate, setViewDate] = useState(selectedDate);
// Sync viewDate when main calendar navigates (via prev/next buttons)
useEffect(() => {
setViewDate((prevViewDate) => {
// Only update if the month changed to avoid unnecessary re-renders
if (!isSameMonth(prevViewDate, currentDate)) {
return currentDate;
}
return prevViewDate;
});
}, [currentDate]);
const days = useMemo(() => {
const monthStart = startOfMonth(viewDate);
const monthEnd = endOfMonth(viewDate);
@@ -38,7 +63,10 @@ export const MiniCalendar = ({
return eachDayOfInterval({ start: calendarStart, end: calendarEnd });
}, [viewDate]);
const weekDays = ["L", "M", "M", "J", "V", "S", "D"];
// Group days by weeks
const weeks = useMemo(() => chunkArray(days, 7), [days]);
const weekDays = ["lu", "ma", "me", "je", "ve", "sa", "di"];
const handlePrevMonth = () => {
setViewDate(subMonths(viewDate, 1));
@@ -50,58 +78,76 @@ export const MiniCalendar = ({
const handleDayClick = (day: Date) => {
onDateSelect(day);
goToDate(day);
};
return (
<div className="mini-calendar">
<div className="mini-calendar__header">
<button
className="mini-calendar__nav-btn"
onClick={handlePrevMonth}
aria-label="Mois précédent"
>
<span className="material-icons">chevron_left</span>
</button>
<span className="mini-calendar__month-title">
{format(viewDate, "MMMM yyyy", { locale: fr })}
</span>
<button
className="mini-calendar__nav-btn"
onClick={handleNextMonth}
aria-label="Mois suivant"
>
<span className="material-icons">chevron_right</span>
</button>
<div className="mini-calendar__nav">
<button
className="mini-calendar__nav-btn"
onClick={handlePrevMonth}
aria-label={t("calendar.miniCalendar.previousMonth")}
>
<span className="material-icons">chevron_left</span>
</button>
<button
className="mini-calendar__nav-btn"
onClick={handleNextMonth}
aria-label={t("calendar.miniCalendar.nextMonth")}
>
<span className="material-icons">chevron_right</span>
</button>
</div>
</div>
<div className="mini-calendar__weekdays">
{weekDays.map((day, index) => (
<div key={index} className="mini-calendar__weekday">
{day}
<div className="mini-calendar__grid">
{/* Header row with week number column and days */}
<div className="mini-calendar__weekdays">
<div className="mini-calendar__weekday mini-calendar__weekday--week-num">
Sem.
</div>
))}
</div>
{weekDays.map((day, index) => (
<div key={index} className="mini-calendar__weekday">
{day}
</div>
))}
</div>
<div className="mini-calendar__days">
{days.map((day, index) => {
const isCurrentMonth = isSameMonth(day, viewDate);
const isSelected = isSameDay(day, selectedDate);
const isToday = isSameDay(day, new Date());
{/* Calendar body with weeks */}
<div className="mini-calendar__body">
{weeks.map((week, weekIndex) => {
const weekNumber = getWeek(week[0], { weekStartsOn: 1 });
return (
<div key={weekIndex} className="mini-calendar__week">
<div className="mini-calendar__week-number">{weekNumber}</div>
{week.map((day, dayIndex) => {
const isCurrentMonth = isSameMonth(day, viewDate);
const isSelected = isSameDay(day, selectedDate);
const isToday = isSameDay(day, new Date());
return (
<button
key={index}
className={`mini-calendar__day ${
!isCurrentMonth ? "mini-calendar__day--outside" : ""
} ${isSelected ? "mini-calendar__day--selected" : ""} ${
isToday ? "mini-calendar__day--today" : ""
}`}
onClick={() => handleDayClick(day)}
>
{format(day, "d")}
</button>
);
})}
return (
<button
key={dayIndex}
className={`mini-calendar__day ${
!isCurrentMonth ? "mini-calendar__day--outside" : ""
} ${isSelected ? "mini-calendar__day--selected" : ""} ${
isToday && !isSelected ? "mini-calendar__day--today" : ""
}`}
onClick={() => handleDayClick(day)}
>
{format(day, "d")}
</button>
);
})}
</div>
);
})}
</div>
</div>
</div>
);