feat: add option to show week numbers in mini-calendar

This commit is contained in:
Linus Rath
2026-03-21 20:45:16 +01:00
parent 09eda86d3f
commit c73940e22a
12 changed files with 86 additions and 28 deletions
+46 -19
View File
@@ -1,12 +1,12 @@
"use client";
import { useState, useMemo } from "react";
import { useState, useMemo, Fragment } from "react";
import { useTranslations, useFormatter } from "next-intl";
import { ChevronLeft, ChevronRight, ChevronDown } from "lucide-react";
import {
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
addMonths, subMonths, addYears, subYears, setMonth, setYear,
eachDayOfInterval, getMonth, getYear,
eachDayOfInterval, getMonth, getYear, getISOWeek, getWeek,
isSameDay, isSameMonth, isToday, format,
} from "date-fns";
import { cn } from "@/lib/utils";
@@ -26,6 +26,7 @@ interface MiniCalendarProps {
onChangeMonth: (date: Date) => void;
events?: CalendarEvent[];
firstDayOfWeek?: number;
showWeekNumbers?: boolean;
}
export function MiniCalendar({
@@ -35,6 +36,7 @@ export function MiniCalendar({
onChangeMonth,
events = [],
firstDayOfWeek = 1,
showWeekNumbers = false,
}: MiniCalendarProps) {
const t = useTranslations("calendar");
const intlFormatter = useFormatter();
@@ -61,6 +63,17 @@ export function MiniCalendar({
? ["sun", "mon", "tue", "wed", "thu", "fri", "sat"] as const
: ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] as const;
// Compute week numbers for each row (one per 7-day chunk)
const weekNumbers = useMemo(() => {
if (!showWeekNumbers) return [];
const nums: number[] = [];
for (let i = 0; i < days.length; i += 7) {
// Use the first day of each row to determine the week number
nums.push(weekStart === 1 ? getISOWeek(days[i]) : getWeek(days[i], { weekStartsOn: 0 }));
}
return nums;
}, [days, showWeekNumbers, weekStart]);
const currentYear = getYear(displayMonth);
const currentMonth = getMonth(displayMonth);
const decadeStart = Math.floor(currentYear / 10) * 10;
@@ -135,35 +148,49 @@ export function MiniCalendar({
</div>
{pickerView === "days" && (
<div className="grid grid-cols-7 gap-0">
<div className={cn("grid gap-0", showWeekNumbers ? "grid-cols-[auto_repeat(7,1fr)]" : "grid-cols-7")}>
{showWeekNumbers && (
<div className="text-center text-[10px] font-medium text-muted-foreground py-1 w-5" />
)}
{dayHeaders.map((d) => (
<div key={d} className="text-center text-[10px] font-medium text-muted-foreground py-1">
{t(`days.${d}`)}
</div>
))}
{days.map((day) => {
{days.map((day, index) => {
const inMonth = isSameMonth(day, displayMonth);
const selected = isSameDay(day, selectedDate);
const today = isToday(day);
const hasEvent = eventDates.has(format(day, "yyyy-MM-dd"));
const isFirstDayOfRow = index % 7 === 0;
return (
<button
key={day.toISOString()}
onClick={() => onSelectDate(day)}
className={cn(
"relative flex items-center justify-center w-7 h-7 text-xs rounded-full transition-colors",
!inMonth && "text-muted-foreground/40",
inMonth && !selected && "hover:bg-muted",
today && !selected && "font-bold text-primary",
selected && "bg-primary text-primary-foreground"
<Fragment key={day.toISOString()}>
{showWeekNumbers && isFirstDayOfRow && (
<div
key={`wk-${index}`}
className="flex items-center justify-center w-5 text-[9px] text-muted-foreground/60 font-medium"
>
{weekNumbers[index / 7]}
</div>
)}
>
{format(day, "d")}
{hasEvent && !selected && (
<span className="absolute bottom-0.5 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-primary" />
)}
</button>
<button
key={`day-${day.toISOString()}`}
onClick={() => onSelectDate(day)}
className={cn(
"relative flex items-center justify-center w-7 h-7 text-xs rounded-full transition-colors",
!inMonth && "text-muted-foreground/40",
inMonth && !selected && "hover:bg-muted",
today && !selected && "font-bold text-primary",
selected && "bg-primary text-primary-foreground"
)}
>
{format(day, "d")}
{hasEvent && !selected && (
<span className="absolute bottom-0.5 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-primary" />
)}
</button>
</Fragment>
);
})}
</div>
+11
View File
@@ -15,6 +15,7 @@ export function CalendarSettings() {
timeFormat,
firstDayOfWeek,
showTimeInMonthView,
showWeekNumbers,
calendarNotificationsEnabled,
calendarNotificationSound,
calendarInvitationParsingEnabled,
@@ -68,6 +69,16 @@ export function CalendarSettings() {
/>
</SettingItem>
<SettingItem
label={t('show_week_numbers')}
description={t('show_week_numbers_desc')}
>
<ToggleSwitch
checked={showWeekNumbers}
onChange={(checked) => updateSetting('showWeekNumbers', checked)}
/>
</SettingItem>
<SettingItem
label={t('notifications_enabled')}
description={t('notifications_enabled_desc')}