From 61c963841b2e27333290da9456077a196a1a6913 Mon Sep 17 00:00:00 2001 From: boojack Date: Mon, 6 Apr 2026 22:44:11 +0800 Subject: [PATCH] chore(web): polish dark theme and calendar UI - simplify ActivityCalendar state handling and shared max-count utilities - remove calendar ring styling and darken the default dark theme primary colors - tighten the audio recorder panel layout and action sizing --- .../ActivityCalendar/CalendarCell.tsx | 5 ++- .../ActivityCalendar/MonthCalendar.tsx | 9 ++--- .../ActivityCalendar/YearCalendar.tsx | 8 ++--- .../components/ActivityCalendar/constants.ts | 1 - web/src/components/ActivityCalendar/types.ts | 8 ++--- .../ActivityCalendar/useCalendar.ts | 12 +++---- web/src/components/ActivityCalendar/utils.ts | 13 +++---- .../components/AudioRecorderPanel.tsx | 35 +++++++++---------- .../StatisticsView/StatisticsView.tsx | 16 ++++----- web/src/themes/default-dark.css | 6 ++-- 10 files changed, 54 insertions(+), 59 deletions(-) diff --git a/web/src/components/ActivityCalendar/CalendarCell.tsx b/web/src/components/ActivityCalendar/CalendarCell.tsx index 5d47dc56..175b5ddc 100644 --- a/web/src/components/ActivityCalendar/CalendarCell.tsx +++ b/web/src/components/ActivityCalendar/CalendarCell.tsx @@ -3,7 +3,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip import { cn } from "@/lib/utils"; import { DEFAULT_CELL_SIZE, SMALL_CELL_SIZE } from "./constants"; import type { CalendarDayCell, CalendarSize } from "./types"; -import { getCellIntensityClass } from "./utils"; +import { getCalendarCellStateClass, getCellIntensityClass } from "./utils"; export interface CalendarCellProps { day: CalendarDayCell; @@ -44,8 +44,7 @@ export const CalendarCell = memo((props: CalendarCellProps) => { const buttonClasses = cn( baseClasses, intensityClass, - day.isToday && "ring-2 ring-primary/30 ring-offset-1 font-semibold z-10", - day.isSelected && "ring-2 ring-primary ring-offset-1 font-bold z-10", + getCalendarCellStateClass(day), isInteractive ? "cursor-pointer hover:bg-muted/40 hover:border-border/30" : "cursor-default", ); diff --git a/web/src/components/ActivityCalendar/MonthCalendar.tsx b/web/src/components/ActivityCalendar/MonthCalendar.tsx index 891a285b..3c3e9176 100644 --- a/web/src/components/ActivityCalendar/MonthCalendar.tsx +++ b/web/src/components/ActivityCalendar/MonthCalendar.tsx @@ -23,7 +23,7 @@ const WeekdayHeader = memo(({ weekDays, size }: WeekdayHeaderProps) => ( {weekDays.map((label, index) => (
@@ -35,11 +35,12 @@ const WeekdayHeader = memo(({ weekDays, size }: WeekdayHeaderProps) => ( WeekdayHeader.displayName = "WeekdayHeader"; export const MonthCalendar = memo((props: MonthCalendarProps) => { - const { month, data, maxCount, size = "default", onClick, className, disableTooltips = false } = props; + const { month, data, maxCount, size = "default", onClick, selectedDate, className, disableTooltips = false } = props; const t = useTranslate(); const { generalSetting } = useInstance(); const today = useTodayDate(); const weekDays = useWeekdayLabels(); + const gridStyle = GRID_STYLES[size]; const { weeks, weekDays: rotatedWeekDays } = useCalendarMatrix({ month, @@ -47,7 +48,7 @@ export const MonthCalendar = memo((props: MonthCalendarProps) => { weekDays, weekStartDayOffset: generalSetting.weekStartDayOffset, today, - selectedDate: "", + selectedDate: selectedDate ?? "", }); const flatDays = useMemo(() => weeks.flatMap((week) => week.days), [weeks]); @@ -56,7 +57,7 @@ export const MonthCalendar = memo((props: MonthCalendarProps) => {
-
+
{flatDays.map((day) => ( ; + data: CalendarData; maxCount: number; onDateClick: (date: string) => void; } @@ -87,7 +87,7 @@ export const YearCalendar = memo(({ selectedYear, data, onYearChange, onDateClic const currentYear = useMemo(() => new Date().getFullYear(), []); const yearData = useMemo(() => filterDataByYear(data, selectedYear), [data, selectedYear]); const months = useMemo(() => generateMonthsForYear(selectedYear), [selectedYear]); - const yearMaxCount = useMemo(() => calculateYearMaxCount(yearData), [yearData]); + const yearMaxCount = useMemo(() => calculateMaxCount(yearData), [yearData]); const canGoPrev = selectedYear > MIN_YEAR; const canGoNext = selectedYear < getMaxYear(); diff --git a/web/src/components/ActivityCalendar/constants.ts b/web/src/components/ActivityCalendar/constants.ts index c0913b96..de457971 100644 --- a/web/src/components/ActivityCalendar/constants.ts +++ b/web/src/components/ActivityCalendar/constants.ts @@ -1,6 +1,5 @@ export const DAYS_IN_WEEK = 7; export const MONTHS_IN_YEAR = 12; -export const WEEKEND_DAYS = [0, 6] as const; export const MIN_COUNT = 1; export const MIN_YEAR = 1970; diff --git a/web/src/components/ActivityCalendar/types.ts b/web/src/components/ActivityCalendar/types.ts index 306b21b9..119b0d07 100644 --- a/web/src/components/ActivityCalendar/types.ts +++ b/web/src/components/ActivityCalendar/types.ts @@ -1,4 +1,5 @@ export type CalendarSize = "default" | "small"; +export type CalendarData = Record; export interface CalendarDayCell { date: string; @@ -7,7 +8,6 @@ export interface CalendarDayCell { isCurrentMonth: boolean; isToday: boolean; isSelected: boolean; - isWeekend: boolean; } export interface CalendarDayRow { @@ -17,22 +17,22 @@ export interface CalendarDayRow { export interface CalendarMatrixResult { weeks: CalendarDayRow[]; weekDays: string[]; - maxCount: number; } export interface MonthCalendarProps { month: string; - data: Record; + data: CalendarData; maxCount: number; size?: CalendarSize; onClick?: (date: string) => void; + selectedDate?: string; className?: string; disableTooltips?: boolean; } export interface YearCalendarProps { selectedYear: number; - data: Record; + data: CalendarData; onYearChange: (year: number) => void; onDateClick: (date: string) => void; className?: string; diff --git a/web/src/components/ActivityCalendar/useCalendar.ts b/web/src/components/ActivityCalendar/useCalendar.ts index 783767e4..cf45dbee 100644 --- a/web/src/components/ActivityCalendar/useCalendar.ts +++ b/web/src/components/ActivityCalendar/useCalendar.ts @@ -1,11 +1,11 @@ import dayjs from "dayjs"; import { useMemo } from "react"; -import { DAYS_IN_WEEK, MIN_COUNT, WEEKEND_DAYS } from "./constants"; -import type { CalendarDayCell, CalendarMatrixResult } from "./types"; +import { DAYS_IN_WEEK } from "./constants"; +import type { CalendarData, CalendarDayCell, CalendarMatrixResult } from "./types"; export interface UseCalendarMatrixParams { month: string; - data: Record; + data: CalendarData; weekDays: string[]; weekStartDayOffset: number; today: string; @@ -15,7 +15,7 @@ export interface UseCalendarMatrixParams { const createCalendarDayCell = ( current: dayjs.Dayjs, monthKey: string, - data: Record, + data: CalendarData, today: string, selectedDate: string, ): CalendarDayCell => { @@ -30,7 +30,6 @@ const createCalendarDayCell = ( isCurrentMonth, isToday: isoDate === today, isSelected: isoDate === selectedDate, - isWeekend: WEEKEND_DAYS.includes(current.day() as 0 | 6), }; }; @@ -68,7 +67,6 @@ export const useCalendarMatrix = ({ const { calendarStart, dayCount } = calculateCalendarBoundaries(monthStart, weekStartDayOffset); const weeks: CalendarMatrixResult["weeks"] = []; - let maxCount = 0; // Iterate through each day in the calendar grid for (let index = 0; index < dayCount; index += 1) { @@ -82,13 +80,11 @@ export const useCalendarMatrix = ({ // Create the day cell object with data and status flags const dayCell = createCalendarDayCell(current, monthKey, data, today, selectedDate); weeks[weekIndex].days.push(dayCell); - maxCount = Math.max(maxCount, dayCell.count); } return { weeks, weekDays: rotatedWeekDays, - maxCount: Math.max(maxCount, MIN_COUNT), }; }, [month, data, weekDays, weekStartDayOffset, today, selectedDate]); }; diff --git a/web/src/components/ActivityCalendar/utils.ts b/web/src/components/ActivityCalendar/utils.ts index 0ce1029d..368c26ab 100644 --- a/web/src/components/ActivityCalendar/utils.ts +++ b/web/src/components/ActivityCalendar/utils.ts @@ -1,9 +1,10 @@ import dayjs from "dayjs"; import isSameOrAfter from "dayjs/plugin/isSameOrAfter"; import isSameOrBefore from "dayjs/plugin/isSameOrBefore"; +import { cn } from "@/lib/utils"; import { useTranslate } from "@/utils/i18n"; import { CELL_STYLES, INTENSITY_THRESHOLDS, MIN_COUNT, MONTHS_IN_YEAR } from "./constants"; -import type { CalendarDayCell } from "./types"; +import type { CalendarData, CalendarDayCell } from "./types"; dayjs.extend(isSameOrAfter); dayjs.extend(isSameOrBefore); @@ -22,11 +23,15 @@ export const getCellIntensityClass = (day: CalendarDayCell, maxCount: number): s return CELL_STYLES.MINIMAL; }; +export const getCalendarCellStateClass = (day: Pick): string => { + return cn(day.isToday && "font-semibold z-10", day.isSelected && "font-bold z-10"); +}; + export const generateMonthsForYear = (year: number): string[] => { return Array.from({ length: MONTHS_IN_YEAR }, (_, i) => dayjs(`${year}-01-01`).add(i, "month").format("YYYY-MM")); }; -export const calculateYearMaxCount = (data: Record): number => { +export const calculateMaxCount = (data: CalendarData): number => { let max = 0; for (const count of Object.values(data)) { max = Math.max(max, count); @@ -55,10 +60,6 @@ export const filterDataByYear = (data: Record, year: number): Re return filtered; }; -export const hasActivityData = (data: Record): boolean => { - return Object.values(data).some((count) => count > 0); -}; - export const getTooltipText = (count: number, date: string, t: TranslateFunction): string => { if (count === 0) { return date; diff --git a/web/src/components/MemoEditor/components/AudioRecorderPanel.tsx b/web/src/components/MemoEditor/components/AudioRecorderPanel.tsx index 6b7c49fb..ae95722a 100644 --- a/web/src/components/MemoEditor/components/AudioRecorderPanel.tsx +++ b/web/src/components/MemoEditor/components/AudioRecorderPanel.tsx @@ -15,30 +15,29 @@ export const AudioRecorderPanel: FC = ({ audioRecorder, return (
-
+
{isRequestingPermission ? t("editor.audio-recorder.requesting-permission") : t("editor.audio-recorder.recording")}
-
- -
- {isRequestingPermission ? ( - - ) : ( - - )} - {formatAudioTime(elapsedSeconds)} +
+ {isRequestingPermission ? ( + + ) : ( + + )} + {formatAudioTime(elapsedSeconds)} +
-