♻️(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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user