feat: add option to show week numbers in mini-calendar
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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')}
|
||||
|
||||
Reference in New Issue
Block a user