Skip to content

Commit 61c9638

Browse files
committed
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
1 parent 24fc8ab commit 61c9638

File tree

10 files changed

+54
-59
lines changed

10 files changed

+54
-59
lines changed

web/src/components/ActivityCalendar/CalendarCell.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
33
import { cn } from "@/lib/utils";
44
import { DEFAULT_CELL_SIZE, SMALL_CELL_SIZE } from "./constants";
55
import type { CalendarDayCell, CalendarSize } from "./types";
6-
import { getCellIntensityClass } from "./utils";
6+
import { getCalendarCellStateClass, getCellIntensityClass } from "./utils";
77

88
export interface CalendarCellProps {
99
day: CalendarDayCell;
@@ -44,8 +44,7 @@ export const CalendarCell = memo((props: CalendarCellProps) => {
4444
const buttonClasses = cn(
4545
baseClasses,
4646
intensityClass,
47-
day.isToday && "ring-2 ring-primary/30 ring-offset-1 font-semibold z-10",
48-
day.isSelected && "ring-2 ring-primary ring-offset-1 font-bold z-10",
47+
getCalendarCellStateClass(day),
4948
isInteractive ? "cursor-pointer hover:bg-muted/40 hover:border-border/30" : "cursor-default",
5049
);
5150

web/src/components/ActivityCalendar/MonthCalendar.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const WeekdayHeader = memo(({ weekDays, size }: WeekdayHeaderProps) => (
2323
{weekDays.map((label, index) => (
2424
<div
2525
key={index}
26-
className="flex h-4 items-center justify-center font-medium uppercase tracking-wide text-muted-foreground/60"
26+
className="flex h-4 items-center justify-center uppercase tracking-wide text-muted-foreground/60"
2727
role="columnheader"
2828
aria-label={label}
2929
>
@@ -35,19 +35,20 @@ const WeekdayHeader = memo(({ weekDays, size }: WeekdayHeaderProps) => (
3535
WeekdayHeader.displayName = "WeekdayHeader";
3636

3737
export const MonthCalendar = memo((props: MonthCalendarProps) => {
38-
const { month, data, maxCount, size = "default", onClick, className, disableTooltips = false } = props;
38+
const { month, data, maxCount, size = "default", onClick, selectedDate, className, disableTooltips = false } = props;
3939
const t = useTranslate();
4040
const { generalSetting } = useInstance();
4141
const today = useTodayDate();
4242
const weekDays = useWeekdayLabels();
43+
const gridStyle = GRID_STYLES[size];
4344

4445
const { weeks, weekDays: rotatedWeekDays } = useCalendarMatrix({
4546
month,
4647
data,
4748
weekDays,
4849
weekStartDayOffset: generalSetting.weekStartDayOffset,
4950
today,
50-
selectedDate: "",
51+
selectedDate: selectedDate ?? "",
5152
});
5253

5354
const flatDays = useMemo(() => weeks.flatMap((week) => week.days), [weeks]);
@@ -56,7 +57,7 @@ export const MonthCalendar = memo((props: MonthCalendarProps) => {
5657
<div className={cn("flex flex-col", className)} role="grid" aria-label={`Calendar for ${month}`}>
5758
<WeekdayHeader weekDays={rotatedWeekDays} size={size} />
5859

59-
<div className={cn("grid grid-cols-7", GRID_STYLES[size].gap)} role="rowgroup">
60+
<div className={cn("grid grid-cols-7", gridStyle.gap)} role="rowgroup">
6061
{flatDays.map((day) => (
6162
<CalendarCell
6263
key={day.date}

web/src/components/ActivityCalendar/YearCalendar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { cn } from "@/lib/utils";
55
import { useTranslate } from "@/utils/i18n";
66
import { getMaxYear, MIN_YEAR } from "./constants";
77
import { MonthCalendar } from "./MonthCalendar";
8-
import type { YearCalendarProps } from "./types";
9-
import { calculateYearMaxCount, filterDataByYear, generateMonthsForYear, getMonthLabel } from "./utils";
8+
import type { CalendarData, YearCalendarProps } from "./types";
9+
import { calculateMaxCount, filterDataByYear, generateMonthsForYear, getMonthLabel } from "./utils";
1010

1111
interface YearNavigationProps {
1212
selectedYear: number;
@@ -70,7 +70,7 @@ YearNavigation.displayName = "YearNavigation";
7070

7171
interface MonthCardProps {
7272
month: string;
73-
data: Record<string, number>;
73+
data: CalendarData;
7474
maxCount: number;
7575
onDateClick: (date: string) => void;
7676
}
@@ -87,7 +87,7 @@ export const YearCalendar = memo(({ selectedYear, data, onYearChange, onDateClic
8787
const currentYear = useMemo(() => new Date().getFullYear(), []);
8888
const yearData = useMemo(() => filterDataByYear(data, selectedYear), [data, selectedYear]);
8989
const months = useMemo(() => generateMonthsForYear(selectedYear), [selectedYear]);
90-
const yearMaxCount = useMemo(() => calculateYearMaxCount(yearData), [yearData]);
90+
const yearMaxCount = useMemo(() => calculateMaxCount(yearData), [yearData]);
9191

9292
const canGoPrev = selectedYear > MIN_YEAR;
9393
const canGoNext = selectedYear < getMaxYear();

web/src/components/ActivityCalendar/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export const DAYS_IN_WEEK = 7;
22
export const MONTHS_IN_YEAR = 12;
3-
export const WEEKEND_DAYS = [0, 6] as const;
43
export const MIN_COUNT = 1;
54

65
export const MIN_YEAR = 1970;

web/src/components/ActivityCalendar/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type CalendarSize = "default" | "small";
2+
export type CalendarData = Record<string, number>;
23

34
export interface CalendarDayCell {
45
date: string;
@@ -7,7 +8,6 @@ export interface CalendarDayCell {
78
isCurrentMonth: boolean;
89
isToday: boolean;
910
isSelected: boolean;
10-
isWeekend: boolean;
1111
}
1212

1313
export interface CalendarDayRow {
@@ -17,22 +17,22 @@ export interface CalendarDayRow {
1717
export interface CalendarMatrixResult {
1818
weeks: CalendarDayRow[];
1919
weekDays: string[];
20-
maxCount: number;
2120
}
2221

2322
export interface MonthCalendarProps {
2423
month: string;
25-
data: Record<string, number>;
24+
data: CalendarData;
2625
maxCount: number;
2726
size?: CalendarSize;
2827
onClick?: (date: string) => void;
28+
selectedDate?: string;
2929
className?: string;
3030
disableTooltips?: boolean;
3131
}
3232

3333
export interface YearCalendarProps {
3434
selectedYear: number;
35-
data: Record<string, number>;
35+
data: CalendarData;
3636
onYearChange: (year: number) => void;
3737
onDateClick: (date: string) => void;
3838
className?: string;

web/src/components/ActivityCalendar/useCalendar.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import dayjs from "dayjs";
22
import { useMemo } from "react";
3-
import { DAYS_IN_WEEK, MIN_COUNT, WEEKEND_DAYS } from "./constants";
4-
import type { CalendarDayCell, CalendarMatrixResult } from "./types";
3+
import { DAYS_IN_WEEK } from "./constants";
4+
import type { CalendarData, CalendarDayCell, CalendarMatrixResult } from "./types";
55

66
export interface UseCalendarMatrixParams {
77
month: string;
8-
data: Record<string, number>;
8+
data: CalendarData;
99
weekDays: string[];
1010
weekStartDayOffset: number;
1111
today: string;
@@ -15,7 +15,7 @@ export interface UseCalendarMatrixParams {
1515
const createCalendarDayCell = (
1616
current: dayjs.Dayjs,
1717
monthKey: string,
18-
data: Record<string, number>,
18+
data: CalendarData,
1919
today: string,
2020
selectedDate: string,
2121
): CalendarDayCell => {
@@ -30,7 +30,6 @@ const createCalendarDayCell = (
3030
isCurrentMonth,
3131
isToday: isoDate === today,
3232
isSelected: isoDate === selectedDate,
33-
isWeekend: WEEKEND_DAYS.includes(current.day() as 0 | 6),
3433
};
3534
};
3635

@@ -68,7 +67,6 @@ export const useCalendarMatrix = ({
6867
const { calendarStart, dayCount } = calculateCalendarBoundaries(monthStart, weekStartDayOffset);
6968

7069
const weeks: CalendarMatrixResult["weeks"] = [];
71-
let maxCount = 0;
7270

7371
// Iterate through each day in the calendar grid
7472
for (let index = 0; index < dayCount; index += 1) {
@@ -82,13 +80,11 @@ export const useCalendarMatrix = ({
8280
// Create the day cell object with data and status flags
8381
const dayCell = createCalendarDayCell(current, monthKey, data, today, selectedDate);
8482
weeks[weekIndex].days.push(dayCell);
85-
maxCount = Math.max(maxCount, dayCell.count);
8683
}
8784

8885
return {
8986
weeks,
9087
weekDays: rotatedWeekDays,
91-
maxCount: Math.max(maxCount, MIN_COUNT),
9288
};
9389
}, [month, data, weekDays, weekStartDayOffset, today, selectedDate]);
9490
};

web/src/components/ActivityCalendar/utils.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import dayjs from "dayjs";
22
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
33
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
4+
import { cn } from "@/lib/utils";
45
import { useTranslate } from "@/utils/i18n";
56
import { CELL_STYLES, INTENSITY_THRESHOLDS, MIN_COUNT, MONTHS_IN_YEAR } from "./constants";
6-
import type { CalendarDayCell } from "./types";
7+
import type { CalendarData, CalendarDayCell } from "./types";
78

89
dayjs.extend(isSameOrAfter);
910
dayjs.extend(isSameOrBefore);
@@ -22,11 +23,15 @@ export const getCellIntensityClass = (day: CalendarDayCell, maxCount: number): s
2223
return CELL_STYLES.MINIMAL;
2324
};
2425

26+
export const getCalendarCellStateClass = (day: Pick<CalendarDayCell, "isToday" | "isSelected">): string => {
27+
return cn(day.isToday && "font-semibold z-10", day.isSelected && "font-bold z-10");
28+
};
29+
2530
export const generateMonthsForYear = (year: number): string[] => {
2631
return Array.from({ length: MONTHS_IN_YEAR }, (_, i) => dayjs(`${year}-01-01`).add(i, "month").format("YYYY-MM"));
2732
};
2833

29-
export const calculateYearMaxCount = (data: Record<string, number>): number => {
34+
export const calculateMaxCount = (data: CalendarData): number => {
3035
let max = 0;
3136
for (const count of Object.values(data)) {
3237
max = Math.max(max, count);
@@ -55,10 +60,6 @@ export const filterDataByYear = (data: Record<string, number>, year: number): Re
5560
return filtered;
5661
};
5762

58-
export const hasActivityData = (data: Record<string, number>): boolean => {
59-
return Object.values(data).some((count) => count > 0);
60-
};
61-
6263
export const getTooltipText = (count: number, date: string, t: TranslateFunction): string => {
6364
if (count === 0) {
6465
return date;

web/src/components/MemoEditor/components/AudioRecorderPanel.tsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,29 @@ export const AudioRecorderPanel: FC<AudioRecorderPanelProps> = ({ audioRecorder,
1515
return (
1616
<div className="w-full rounded-lg border border-border/60 bg-muted/20 px-2.5 py-2">
1717
<div className="flex items-center gap-2">
18-
<div className="min-w-0 flex-1">
18+
<div className="min-w-0 flex flex-1 gap-2">
1919
<div className="truncate text-sm font-medium text-foreground">
2020
{isRequestingPermission ? t("editor.audio-recorder.requesting-permission") : t("editor.audio-recorder.recording")}
2121
</div>
22-
</div>
23-
24-
<div
25-
className={cn(
26-
"inline-flex shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium",
27-
isRequestingPermission
28-
? "border border-border/60 bg-background text-muted-foreground"
29-
: "border border-destructive/20 bg-destructive/[0.08] text-destructive",
30-
)}
31-
>
32-
{isRequestingPermission ? (
33-
<LoaderCircleIcon className="size-3 animate-spin" />
34-
) : (
35-
<span className="size-2 rounded-full bg-destructive" />
36-
)}
37-
{formatAudioTime(elapsedSeconds)}
22+
<div
23+
className={cn(
24+
"inline-flex shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium",
25+
isRequestingPermission
26+
? "border border-border/60 bg-background text-muted-foreground"
27+
: "border border-destructive/20 bg-destructive/[0.08] text-destructive",
28+
)}
29+
>
30+
{isRequestingPermission ? (
31+
<LoaderCircleIcon className="size-3 animate-spin" />
32+
) : (
33+
<span className="size-2 rounded-full bg-destructive" />
34+
)}
35+
{formatAudioTime(elapsedSeconds)}
36+
</div>
3837
</div>
3938

4039
<div className="ml-auto flex shrink-0 items-center gap-1">
41-
<Button variant="ghost" size="icon" onClick={onCancel} aria-label={t("common.cancel")}>
40+
<Button variant="ghost" size="sm" onClick={onCancel} aria-label={t("common.cancel")}>
4241
<XIcon className="size-4" />
4342
</Button>
4443
<Button size="sm" className="gap-1.5" onClick={onStop} disabled={isRequestingPermission}>

web/src/components/StatisticsView/StatisticsView.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import dayjs from "dayjs";
2-
import { useMemo, useState } from "react";
3-
import { MonthCalendar } from "@/components/ActivityCalendar";
2+
import { useState } from "react";
3+
import { calculateMaxCount, MonthCalendar } from "@/components/ActivityCalendar";
44
import { useDateFilterNavigation } from "@/hooks";
55
import type { StatisticsData } from "@/types/statistics";
66
import { MonthNavigator } from "./MonthNavigator";
@@ -15,17 +15,17 @@ const StatisticsView = (props: Props) => {
1515
const navigateToDateFilter = useDateFilterNavigation();
1616
const [visibleMonthString, setVisibleMonthString] = useState(dayjs().format("YYYY-MM"));
1717

18-
const maxCount = useMemo(() => {
19-
const counts = Object.values(activityStats);
20-
return Math.max(...counts, 1);
21-
}, [activityStats]);
22-
2318
return (
2419
<div className="group w-full mt-2 flex flex-col text-muted-foreground animate-fade-in">
2520
<MonthNavigator visibleMonth={visibleMonthString} onMonthChange={setVisibleMonthString} activityStats={activityStats} />
2621

2722
<div className="w-full animate-scale-in">
28-
<MonthCalendar month={visibleMonthString} data={activityStats} maxCount={maxCount} onClick={navigateToDateFilter} />
23+
<MonthCalendar
24+
month={visibleMonthString}
25+
data={activityStats}
26+
maxCount={calculateMaxCount(activityStats)}
27+
onClick={navigateToDateFilter}
28+
/>
2929
</div>
3030
</div>
3131
);

web/src/themes/default-dark.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
--popover: oklch(0.17 0.006 265);
1111
--popover-foreground: oklch(0.82 0.005 265);
1212

13-
/* Primary — bright blue, clearly interactive in dark context */
14-
--primary: oklch(0.65 0.15 250);
13+
/* Primary — subdued blue that sits back on dark surfaces */
14+
--primary: oklch(0.42 0.08 250);
1515
--primary-foreground: oklch(0.98 0.003 265);
1616

1717
/* Secondary — subtle elevated surface for secondary buttons */
@@ -33,7 +33,7 @@
3333
/* Borders — --border for layout dividers, --input for form field borders */
3434
--border: oklch(0.21 0.007 265);
3535
--input: oklch(0.25 0.007 265);
36-
--ring: oklch(0.65 0.15 250);
36+
--ring: oklch(0.34 0.06 250);
3737

3838
/* Sidebar — darkest surface, distinct from background */
3939
--sidebar: oklch(0.07 0.005 265);

0 commit comments

Comments
 (0)