feat: change the colour of the birthdays calendar

This commit is contained in:
Linus Rath
2026-10-31 04:31:14 +01:00
parent b6995bb638
commit 574db42652
5 changed files with 30 additions and 6 deletions
@@ -322,8 +322,9 @@ function Section({
const isSubscription = !!isSubscriptionCalendar?.(cal.id); const isSubscription = !!isSubscriptionCalendar?.(cal.id);
const isOwn = !cal.isShared && !isBirthday && !isSubscription; const isOwn = !cal.isShared && !isBirthday && !isSubscription;
const canSetDefault = !!onSetDefault && isOwn && !cal.isDefault; const canSetDefault = !!onSetDefault && isOwn && !cal.isDefault;
// Own calendars recolour server-side; shared ones locally (#345). // Own calendars recolour server-side; shared ones and the birthdays
const canRecolor = !!onSetColor && (!!cal.isShared || isOwn); // locally (#345).
const canRecolor = !!onSetColor && (!!cal.isShared || isOwn || isBirthday);
const canRename = !!onRename && isOwn; const canRename = !!onRename && isOwn;
const canShare = !!onShare && isOwn && (!cal.myRights || cal.myRights.mayShare !== false); const canShare = !!onShare && isOwn && (!cal.myRights || cal.myRights.mayShare !== false);
const canClear = !!onClear && isOwn && isWritableCalendar(cal); const canClear = !!onClear && isOwn && isWritableCalendar(cal);
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';
import { BIRTHDAY_CALENDAR_COLOR, createBirthdayCalendar } from '../birthday-calendar';
import { useSettingsStore } from '../../stores/settings-store';
describe('birthday calendar colour', () => {
it('takes the colour the user picked and keeps yellow until then', () => {
expect(createBirthdayCalendar('Birthdays', '#ef4444').color).toBe('#ef4444');
expect(createBirthdayCalendar('Birthdays').color).toBe(BIRTHDAY_CALENDAR_COLOR);
expect(useSettingsStore.getState().birthdayCalendarColor).toBe(BIRTHDAY_CALENDAR_COLOR);
});
});
+2 -2
View File
@@ -6,11 +6,11 @@ export const BIRTHDAY_CALENDAR_ID = '__birthday-calendar__';
export const BIRTHDAY_CALENDAR_COLOR = '#eab308'; // yellow export const BIRTHDAY_CALENDAR_COLOR = '#eab308'; // yellow
// A read-only virtual calendar holding generated birthday events. // A read-only virtual calendar holding generated birthday events.
export function createBirthdayCalendar(name = 'Birthdays'): Calendar { export function createBirthdayCalendar(name = 'Birthdays', color?: string): Calendar {
return { return {
id: BIRTHDAY_CALENDAR_ID, id: BIRTHDAY_CALENDAR_ID,
name, name,
color: BIRTHDAY_CALENDAR_COLOR, color: color || BIRTHDAY_CALENDAR_COLOR,
sortOrder: 999, sortOrder: 999,
isSubscribed: true, isSubscribed: true,
isVisible: true, isVisible: true,
+7 -2
View File
@@ -165,6 +165,7 @@ export default function CalendarScreen() {
const calendarFreeScroll = useSettingsStore((s) => s.calendarFreeScroll); const calendarFreeScroll = useSettingsStore((s) => s.calendarFreeScroll);
const calendarTimeFormat = useSettingsStore((s) => s.calendarTimeFormat); const calendarTimeFormat = useSettingsStore((s) => s.calendarTimeFormat);
const showBirthdayCalendar = useSettingsStore((s) => s.showBirthdayCalendar); const showBirthdayCalendar = useSettingsStore((s) => s.showBirthdayCalendar);
const birthdayCalendarColor = useSettingsStore((s) => s.birthdayCalendarColor);
const enableCalendarTasks = useSettingsStore((s) => s.enableCalendarTasks); const enableCalendarTasks = useSettingsStore((s) => s.enableCalendarTasks);
const sharedCalendarColors = useSettingsStore((s) => s.sharedCalendarColors); const sharedCalendarColors = useSettingsStore((s) => s.sharedCalendarColors);
const setSharedCalendarColor = useSettingsStore((s) => s.setSharedCalendarColor); const setSharedCalendarColor = useSettingsStore((s) => s.setSharedCalendarColor);
@@ -318,8 +319,8 @@ export default function CalendarScreen() {
}, [storeCalendars, sharedCalendarColors, setSharedCalendarColor]); }, [storeCalendars, sharedCalendarColors, setSharedCalendarColor]);
const allCalendars = React.useMemo( const allCalendars = React.useMemo(
() => (showBirthdayCalendar ? [...displayCalendars, createBirthdayCalendar()] : displayCalendars), () => (showBirthdayCalendar ? [...displayCalendars, createBirthdayCalendar(undefined, birthdayCalendarColor)] : displayCalendars),
[displayCalendars, showBirthdayCalendar], [displayCalendars, showBirthdayCalendar, birthdayCalendarColor],
); );
// Tasks with a due date are overlaid on the grid as chips (webmail's // Tasks with a due date are overlaid on the grid as chips (webmail's
// showTasksOnCalendar); tapping one opens the tasks sheet on that task. // showTasksOnCalendar); tapping one opens the tasks sheet on that task.
@@ -884,6 +885,10 @@ export default function CalendarScreen() {
const handleSetCalendarColor = React.useCallback( const handleSetCalendarColor = React.useCallback(
(cal: Calendar, color: string) => { (cal: Calendar, color: string) => {
if (cal.id === BIRTHDAY_CALENDAR_ID) {
useSettingsStore.getState().updateSetting('birthdayCalendarColor', color);
return;
}
if (cal.isShared) { if (cal.isShared) {
// Per-viewer recolor (#345): the owner's colour is left alone. // Per-viewer recolor (#345): the owner's colour is left alone.
setSharedCalendarColor(sharedCalendarColorKey(cal), color); setSharedCalendarColor(sharedCalendarColorKey(cal), color);
+7
View File
@@ -309,6 +309,10 @@ interface PersistedSettings {
// Hard cap on the on-disk body cache, in megabytes. When a sync pushes the // Hard cap on the on-disk body cache, in megabytes. When a sync pushes the
// cache past this, the oldest messages are evicted to fit. // cache past this, the oldest messages are evicted to fit.
offlineCacheMaxMB: number; offlineCacheMaxMB: number;
// calendar: the colour of the virtual Birthdays calendar (webmail
// birthdayCalendarColor).
birthdayCalendarColor: string;
} }
const DEFAULT_PERSISTED: PersistedSettings = { const DEFAULT_PERSISTED: PersistedSettings = {
@@ -463,6 +467,9 @@ const DEFAULT_PERSISTED: PersistedSettings = {
offlineCacheEnabled: false, offlineCacheEnabled: false,
offlineCacheDays: 7, offlineCacheDays: 7,
offlineCacheMaxMB: 50, offlineCacheMaxMB: 50,
// calendar:
birthdayCalendarColor: '#eab308',
}; };
export interface SettingsState extends PersistedSettings { export interface SettingsState extends PersistedSettings {