♻️(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:
@@ -6,16 +6,23 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 1rem;
|
||||||
|
padding: 0 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__month-title {
|
&__month-title {
|
||||||
font-size: 0.875rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
color: var(--c--theme--colors--greyscale-800);
|
color: var(--c--theme--colors--greyscale-800);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
&__nav-btn {
|
&__nav-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -26,11 +33,12 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--c--theme--colors--greyscale-600);
|
color: var(--c--theme--colors--greyscale-500);
|
||||||
transition: background-color 0.15s;
|
transition: background-color 0.15s, color 0.15s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--c--theme--colors--greyscale-100);
|
background-color: var(--c--theme--colors--greyscale-100);
|
||||||
|
color: var(--c--theme--colors--greyscale-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
@@ -38,9 +46,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
&__weekdays {
|
&__weekdays {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: 2rem repeat(7, 1fr);
|
||||||
gap: 0;
|
gap: 0;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -50,16 +63,37 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
font-size: 0.625rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
color: var(--c--theme--colors--greyscale-500);
|
color: var(--c--theme--colors--greyscale-800);
|
||||||
text-transform: uppercase;
|
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;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: 2rem repeat(7, 1fr);
|
||||||
gap: 1px;
|
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 {
|
&__day {
|
||||||
@@ -70,10 +104,11 @@
|
|||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 50%;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.75rem;
|
font-size: 0.8rem;
|
||||||
color: var(--c--theme--colors--greyscale-800);
|
color: var(--c--theme--colors--greyscale-800);
|
||||||
|
font-weight: 500;
|
||||||
transition: background-color 0.15s;
|
transition: background-color 0.15s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -82,21 +117,22 @@
|
|||||||
|
|
||||||
&--outside {
|
&--outside {
|
||||||
color: var(--c--theme--colors--greyscale-400);
|
color: var(--c--theme--colors--greyscale-400);
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--today {
|
&--today {
|
||||||
background-color: var(--c--theme--colors--primary-100);
|
|
||||||
color: var(--c--theme--colors--primary-600);
|
color: var(--c--theme--colors--primary-600);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
background-color: var(--c--theme--colors--primary-500);
|
background-color: #8b4513;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--c--theme--colors--primary-600);
|
background-color: #7a3d11;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* MiniCalendar component - A small month calendar for date navigation.
|
* MiniCalendar component - A small month calendar for date navigation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addMonths,
|
addMonths,
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
endOfMonth,
|
endOfMonth,
|
||||||
endOfWeek,
|
endOfWeek,
|
||||||
format,
|
format,
|
||||||
|
getWeek,
|
||||||
isSameDay,
|
isSameDay,
|
||||||
isSameMonth,
|
isSameMonth,
|
||||||
startOfMonth,
|
startOfMonth,
|
||||||
@@ -17,18 +18,42 @@ import {
|
|||||||
subMonths,
|
subMonths,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { fr } from "date-fns/locale";
|
import { fr } from "date-fns/locale";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useCalendarContext } from "../contexts";
|
||||||
|
|
||||||
interface MiniCalendarProps {
|
interface MiniCalendarProps {
|
||||||
selectedDate: Date;
|
selectedDate: Date;
|
||||||
onDateSelect: (date: Date) => void;
|
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 = ({
|
export const MiniCalendar = ({
|
||||||
selectedDate,
|
selectedDate,
|
||||||
onDateSelect,
|
onDateSelect,
|
||||||
}: MiniCalendarProps) => {
|
}: MiniCalendarProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { goToDate, currentDate } = useCalendarContext();
|
||||||
const [viewDate, setViewDate] = useState(selectedDate);
|
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 days = useMemo(() => {
|
||||||
const monthStart = startOfMonth(viewDate);
|
const monthStart = startOfMonth(viewDate);
|
||||||
const monthEnd = endOfMonth(viewDate);
|
const monthEnd = endOfMonth(viewDate);
|
||||||
@@ -38,7 +63,10 @@ export const MiniCalendar = ({
|
|||||||
return eachDayOfInterval({ start: calendarStart, end: calendarEnd });
|
return eachDayOfInterval({ start: calendarStart, end: calendarEnd });
|
||||||
}, [viewDate]);
|
}, [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 = () => {
|
const handlePrevMonth = () => {
|
||||||
setViewDate(subMonths(viewDate, 1));
|
setViewDate(subMonths(viewDate, 1));
|
||||||
@@ -50,58 +78,76 @@ export const MiniCalendar = ({
|
|||||||
|
|
||||||
const handleDayClick = (day: Date) => {
|
const handleDayClick = (day: Date) => {
|
||||||
onDateSelect(day);
|
onDateSelect(day);
|
||||||
|
goToDate(day);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mini-calendar">
|
<div className="mini-calendar">
|
||||||
<div className="mini-calendar__header">
|
<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">
|
<span className="mini-calendar__month-title">
|
||||||
{format(viewDate, "MMMM yyyy", { locale: fr })}
|
{format(viewDate, "MMMM yyyy", { locale: fr })}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<div className="mini-calendar__nav">
|
||||||
className="mini-calendar__nav-btn"
|
<button
|
||||||
onClick={handleNextMonth}
|
className="mini-calendar__nav-btn"
|
||||||
aria-label="Mois suivant"
|
onClick={handlePrevMonth}
|
||||||
>
|
aria-label={t("calendar.miniCalendar.previousMonth")}
|
||||||
<span className="material-icons">chevron_right</span>
|
>
|
||||||
</button>
|
<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>
|
||||||
|
|
||||||
<div className="mini-calendar__weekdays">
|
<div className="mini-calendar__grid">
|
||||||
{weekDays.map((day, index) => (
|
{/* Header row with week number column and days */}
|
||||||
<div key={index} className="mini-calendar__weekday">
|
<div className="mini-calendar__weekdays">
|
||||||
{day}
|
<div className="mini-calendar__weekday mini-calendar__weekday--week-num">
|
||||||
|
Sem.
|
||||||
</div>
|
</div>
|
||||||
))}
|
{weekDays.map((day, index) => (
|
||||||
</div>
|
<div key={index} className="mini-calendar__weekday">
|
||||||
|
{day}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mini-calendar__days">
|
{/* Calendar body with weeks */}
|
||||||
{days.map((day, index) => {
|
<div className="mini-calendar__body">
|
||||||
const isCurrentMonth = isSameMonth(day, viewDate);
|
{weeks.map((week, weekIndex) => {
|
||||||
const isSelected = isSameDay(day, selectedDate);
|
const weekNumber = getWeek(week[0], { weekStartsOn: 1 });
|
||||||
const isToday = isSameDay(day, new Date());
|
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 (
|
return (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={dayIndex}
|
||||||
className={`mini-calendar__day ${
|
className={`mini-calendar__day ${
|
||||||
!isCurrentMonth ? "mini-calendar__day--outside" : ""
|
!isCurrentMonth ? "mini-calendar__day--outside" : ""
|
||||||
} ${isSelected ? "mini-calendar__day--selected" : ""} ${
|
} ${isSelected ? "mini-calendar__day--selected" : ""} ${
|
||||||
isToday ? "mini-calendar__day--today" : ""
|
isToday && !isSelected ? "mini-calendar__day--today" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleDayClick(day)}
|
onClick={() => handleDayClick(day)}
|
||||||
>
|
>
|
||||||
{format(day, "d")}
|
{format(day, "d")}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user