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
+2 -1
View File
@@ -63,7 +63,7 @@ export default function CalendarPage() {
setSelectedDate, setViewMode, toggleCalendarVisibility, updateCalendar,
refreshAllSubscriptions,
} = useCalendarStore();
const { firstDayOfWeek, timeFormat } = useSettingsStore();
const { firstDayOfWeek, timeFormat, showWeekNumbers } = useSettingsStore();
const { identities } = useIdentityStore();
const normalizedViewMode = isCalendarViewMode(viewMode) ? viewMode : "month";
@@ -751,6 +751,7 @@ export default function CalendarPage() {
onChangeMonth={handleMiniMonthChange}
events={events}
firstDayOfWeek={firstDayOfWeek}
showWeekNumbers={showWeekNumbers}
/>
<CalendarSidebarPanel
calendars={calendars}
+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')}
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "E-Mail-Einladungen verarbeiten",
"invitation_parsing_desc": "Kalendereinladungen in E-Mail-Anhängen erkennen und Kalenderaktionen anzeigen",
"show_time_in_month_view": "Zeit in Monatsansicht anzeigen",
"show_time_in_month_view_desc": "Ereigniszeiten in der Monatskalenderansicht anzeigen"
"show_time_in_month_view_desc": "Ereigniszeiten in der Monatskalenderansicht anzeigen",
"show_week_numbers": "Kalenderwochen anzeigen",
"show_week_numbers_desc": "Kalenderwochen im Minikalender anzeigen"
},
"days": {
"monday": "Montag",
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "Parse email invitations",
"invitation_parsing_desc": "Detect calendar invitations in email attachments and show calendar actions",
"show_time_in_month_view": "Show time in month view",
"show_time_in_month_view_desc": "Display event times in the month calendar view"
"show_time_in_month_view_desc": "Display event times in the month calendar view",
"show_week_numbers": "Show week numbers",
"show_week_numbers_desc": "Display week numbers in the mini-calendar"
},
"days": {
"monday": "Monday",
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "Analizar invitaciones por correo",
"invitation_parsing_desc": "Detectar invitaciones de calendario en archivos adjuntos del correo y mostrar acciones del calendario",
"show_time_in_month_view": "Mostrar hora en vista mensual",
"show_time_in_month_view_desc": "Mostrar las horas de los eventos en la vista mensual del calendario"
"show_time_in_month_view_desc": "Mostrar las horas de los eventos en la vista mensual del calendario",
"show_week_numbers": "Mostrar números de semana",
"show_week_numbers_desc": "Mostrar los números de semana en el minicalendario"
},
"days": {
"monday": "Lunes",
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "Analyser les invitations par e-mail",
"invitation_parsing_desc": "Détecter les invitations de calendrier dans les pièces jointes des e-mails et afficher les actions du calendrier",
"show_time_in_month_view": "Afficher l'heure dans la vue mensuelle",
"show_time_in_month_view_desc": "Afficher les heures des événements dans la vue mensuelle du calendrier"
"show_time_in_month_view_desc": "Afficher les heures des événements dans la vue mensuelle du calendrier",
"show_week_numbers": "Afficher les numéros de semaine",
"show_week_numbers_desc": "Afficher les numéros de semaine dans le mini-calendrier"
},
"days": {
"monday": "Lundi",
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "Analizza gli inviti email",
"invitation_parsing_desc": "Rileva gli inviti del calendario negli allegati email e mostra le azioni del calendario",
"show_time_in_month_view": "Mostra orario nella vista mensile",
"show_time_in_month_view_desc": "Visualizza gli orari degli eventi nella vista mensile del calendario"
"show_time_in_month_view_desc": "Visualizza gli orari degli eventi nella vista mensile del calendario",
"show_week_numbers": "Mostra numeri di settimana",
"show_week_numbers_desc": "Mostra i numeri di settimana nel mini-calendario"
},
"days": {
"monday": "Lunedì",
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "メール招待を解析する",
"invitation_parsing_desc": "メール添付のカレンダー招待を検出してカレンダー操作を表示する",
"show_time_in_month_view": "月表示で時刻を表示",
"show_time_in_month_view_desc": "月カレンダー表示でイベントの時刻を表示する"
"show_time_in_month_view_desc": "月カレンダー表示でイベントの時刻を表示する",
"show_week_numbers": "週番号を表示",
"show_week_numbers_desc": "ミニカレンダーに週番号を表示する"
},
"days": {
"monday": "月曜日",
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "E-mailuitnodigingen verwerken",
"invitation_parsing_desc": "Kalenderuitnodigingen in e-mailbijlagen detecteren en kalenderacties tonen",
"show_time_in_month_view": "Tijd weergeven in maandweergave",
"show_time_in_month_view_desc": "Evenementtijden weergeven in de maandkalenderweergave"
"show_time_in_month_view_desc": "Evenementtijden weergeven in de maandkalenderweergave",
"show_week_numbers": "Weeknummers weergeven",
"show_week_numbers_desc": "Weeknummers weergeven in de minikalender"
},
"days": {
"monday": "Maandag",
+3 -1
View File
@@ -1858,7 +1858,9 @@
"invitation_parsing": "Analisar convites por e-mail",
"invitation_parsing_desc": "Detectar convites de calendário em anexos de e-mail e mostrar ações do calendário",
"show_time_in_month_view": "Mostrar horário na visualização mensal",
"show_time_in_month_view_desc": "Exibir horários dos eventos na visualização mensal do calendário"
"show_time_in_month_view_desc": "Exibir horários dos eventos na visualização mensal do calendário",
"show_week_numbers": "Mostrar números da semana",
"show_week_numbers_desc": "Exibir números da semana no minicalendário"
},
"days": {
"monday": "Segunda-feira",
+3
View File
@@ -121,6 +121,7 @@ interface SettingsState {
// Calendar
showTimeInMonthView: boolean;
showWeekNumbers: boolean;
// Calendar Notifications
calendarNotificationsEnabled: boolean;
@@ -221,6 +222,7 @@ const DEFAULT_SETTINGS = {
// Calendar
showTimeInMonthView: false,
showWeekNumbers: false,
// Calendar Notifications
calendarNotificationsEnabled: true,
@@ -307,6 +309,7 @@ export const useSettingsStore = create<SettingsState>()(
calendarNotificationSound: state.calendarNotificationSound,
calendarInvitationParsingEnabled: state.calendarInvitationParsingEnabled,
showTimeInMonthView: state.showTimeInMonthView,
showWeekNumbers: state.showWeekNumbers,
toolbarPosition: state.toolbarPosition,
senderFavicons: state.senderFavicons,
folderIcons: state.folderIcons,