diff --git a/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx b/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx index 04d5565..7348095 100644 --- a/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx +++ b/__test__/features/Calendars/services/getCalendarsListAsync.test.tsx @@ -10,6 +10,13 @@ jest.mock("@/features/Calendars/services/helpers"); jest.mock("@/features/Calendars/CalendarApi"); jest.mock("@/utils/errorUtils"); jest.mock("@/features/Calendars/utils/normalizeCalendar"); +jest.mock("@/utils/getAccessiblePair", () => ({ + getAccessiblePair: jest.fn().mockReturnValue("#FFF"), +})); + +jest.mock("@mui/material/styles", () => ({ + createTheme: jest.fn().mockReturnValue({}), +})); const mockedGetOpenPaasUser = getOpenPaasUser as jest.Mock; const mockedFetchOwnerData = fetchOwnerData as jest.Mock; diff --git a/src/components/Attendees/PeopleSearch.tsx b/src/components/Attendees/PeopleSearch.tsx index 9eb309c..77cec0c 100644 --- a/src/components/Attendees/PeopleSearch.tsx +++ b/src/components/Attendees/PeopleSearch.tsx @@ -1,4 +1,4 @@ -import { getAccessiblePair } from "@/components/Calendar/utils/calendarColorsUtils"; +import { getAccessiblePair } from "@/utils/getAccessiblePair"; import { stringAvatar } from "@/components/Event/utils/eventUtils"; import { useUserSearch } from "./useUserSearch"; import { SnackbarAlert } from "@/components/Loading/SnackBarAlert"; diff --git a/src/components/Attendees/ResourceSearch.tsx b/src/components/Attendees/ResourceSearch.tsx index aa78874..2bf5178 100644 --- a/src/components/Attendees/ResourceSearch.tsx +++ b/src/components/Attendees/ResourceSearch.tsx @@ -1,4 +1,4 @@ -import { getAccessiblePair } from "@/components/Calendar/utils/calendarColorsUtils"; +import { getAccessiblePair } from "@/utils/getAccessiblePair"; import { useUserSearch } from "./useUserSearch"; import { ResourceIcon } from "./ResourceIcon"; import { SnackbarAlert } from "@/components/Loading/SnackBarAlert"; diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index 12077f9..33ff844 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -20,7 +20,7 @@ import interactionPlugin from "@fullcalendar/interaction"; import momentTimezonePlugin from "@fullcalendar/moment-timezone"; import FullCalendar from "@fullcalendar/react"; import timeGridPlugin from "@fullcalendar/timegrid"; -import { Box, Button, radius, useTheme } from "@linagora/twake-mui"; +import { Box, Button, radius } from "@linagora/twake-mui"; import AddIcon from "@mui/icons-material/Add"; import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react"; import { useI18n } from "twake-i18n"; @@ -38,7 +38,6 @@ import { useCalendarViewHandlers } from "./hooks/useCalendarViewHandlers"; import { MiniCalendar } from "./MiniCalendar"; import { TempCalendarsInput } from "./TempCalendarsInput"; import { TimezoneSelector } from "./TimezoneSelector"; -import { updateDarkColor } from "./utils/calendarColorsUtils"; import { eventToFullCalendarFormat, extractEvents, @@ -75,7 +74,6 @@ export default function CalendarApp({ const userId = useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; const dispatch = useAppDispatch(); - const theme = useTheme(); const view = useAppSelector((state) => state.settings.view); const userData = useAppSelector((state) => state.user.userData); const workingDays = useAppSelector( @@ -103,13 +101,6 @@ export default function CalendarApp({ const [selectedCalendars, setSelectedCalendars] = useState(storedCalendars); - const calendarLightSignature = useMemo(() => { - return Object.values(calendars || {}) - .map((cal) => `${cal.id}:${cal.color?.light ?? ""}`) - .sort() - .join("|"); - }, [calendars]); - const calendarIdsString = useMemo( () => Object.keys(calendars || {}) @@ -167,22 +158,6 @@ export default function CalendarApp({ } }, [selectedCalendars, calendarIds.length]); - const prevCalendarLightSignature = useRef(null); - - useEffect(() => { - if (!calendarLightSignature) { - prevCalendarLightSignature.current = calendarLightSignature; - return; - } - - if (prevCalendarLightSignature.current === calendarLightSignature) { - return; - } - - prevCalendarLightSignature.current = calendarLightSignature; - updateDarkColor(calendars || {}, theme, dispatch); - }, [calendarLightSignature, calendars, theme, dispatch]); - useEffect(() => { if (calendarIds.length === 0) return; const validCalendarIds = new Set(calendarIds); diff --git a/src/components/Calendar/CalendarColorPicker.tsx b/src/components/Calendar/CalendarColorPicker.tsx index eba4f83..08cd40e 100644 --- a/src/components/Calendar/CalendarColorPicker.tsx +++ b/src/components/Calendar/CalendarColorPicker.tsx @@ -11,7 +11,7 @@ import { import { useState, useEffect } from "react"; import { HexColorPicker } from "react-colorful"; import { useI18n } from "twake-i18n"; -import { getAccessiblePair } from "./utils/calendarColorsUtils"; +import { getAccessiblePair } from "@/utils/getAccessiblePair"; import { defaultColors } from "@/utils/defaultColors"; export function ColorPicker({ diff --git a/src/components/Calendar/CalendarResources.tsx b/src/components/Calendar/CalendarResources.tsx index a295ad2..8ea8232 100644 --- a/src/components/Calendar/CalendarResources.tsx +++ b/src/components/Calendar/CalendarResources.tsx @@ -16,7 +16,7 @@ import { useRef, useState } from "react"; import { useI18n } from "twake-i18n"; import { ResponsiveDialog } from "../Dialog"; import { ColorPicker } from "./CalendarColorPicker"; -import { getAccessiblePair } from "./utils/calendarColorsUtils"; +import { getAccessiblePair } from "@/utils/getAccessiblePair"; import { defaultColors } from "@/utils/defaultColors"; import { addCalendarResourceAsync } from "@/features/Calendars/api/addCalendarResourceAsync"; import { Resource, ResourceSearch } from "../Attendees/ResourceSearch"; diff --git a/src/components/Calendar/CalendarSearch.tsx b/src/components/Calendar/CalendarSearch.tsx index 11045ad..20548df 100644 --- a/src/components/Calendar/CalendarSearch.tsx +++ b/src/components/Calendar/CalendarSearch.tsx @@ -20,7 +20,7 @@ import { PeopleSearch, User } from "../Attendees/PeopleSearch"; import { ResponsiveDialog } from "../Dialog"; import { stringAvatar } from "../Event/utils/eventUtils"; import { ColorPicker } from "./CalendarColorPicker"; -import { getAccessiblePair } from "./utils/calendarColorsUtils"; +import { getAccessiblePair } from "@/utils/getAccessiblePair"; import { defaultColors } from "@/utils/defaultColors"; interface CalendarWithOwner { diff --git a/src/components/Calendar/TempCalendarsInput.tsx b/src/components/Calendar/TempCalendarsInput.tsx index 92aca2a..fd6ac53 100644 --- a/src/components/Calendar/TempCalendarsInput.tsx +++ b/src/components/Calendar/TempCalendarsInput.tsx @@ -7,7 +7,7 @@ import { TextField, useTheme } from "@linagora/twake-mui"; import { useRef } from "react"; import { useI18n } from "twake-i18n"; import { PeopleSearch, User } from "../Attendees/PeopleSearch"; -import { getAccessiblePair } from "./utils/calendarColorsUtils"; +import { getAccessiblePair } from "@/utils/getAccessiblePair"; const requestControllers = new Map(); diff --git a/src/components/Calendar/utils/calendarColorsUtils.tsx b/src/components/Calendar/utils/calendarColorsUtils.tsx deleted file mode 100644 index 9ca27cd..0000000 --- a/src/components/Calendar/utils/calendarColorsUtils.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { AppDispatch } from "@/app/store"; -import { updateCalColor } from "@/features/Calendars/CalendarSlice"; -import { Calendar } from "@/features/Calendars/CalendarTypes"; -import { darken, getContrastRatio, lighten, Theme } from "@linagora/twake-mui"; -import { defaultColors } from "@/utils/defaultColors"; - -export function updateDarkColor( - calendars: Record, - theme: Theme, - dispatch: AppDispatch -) { - Object.values(calendars).forEach((cal) => { - if (!cal?.color?.light || typeof cal.color.light !== "string") return; - const baseColor = cal.color.light; - - const isDefault = Object.values(defaultColors).find( - (c) => c.light === baseColor - ); - const darkColor = isDefault - ? isDefault.dark - : getAccessiblePair(baseColor, theme); - - if (cal.color?.dark === darkColor) { - return; - } - - dispatch( - updateCalColor({ - id: cal.id, - color: { light: baseColor, dark: darkColor }, - }) - ); - }); -} - -export function getAccessiblePair(baseColor: string, theme: Theme): string { - if (typeof baseColor !== "string") { - return theme.palette.getContrastText("#000"); - } - - const contrastToBlack = getContrastRatio(baseColor, "#000"); - const contrastToWhite = getContrastRatio(baseColor, "#fff"); - const isLight = contrastToBlack > contrastToWhite; - - const adjusted = isLight ? darken(baseColor, 0.6) : lighten(baseColor, 0.7); - - // Check if contrast meets 4.5 - const contrast = getContrastRatio(baseColor, adjusted); - if (contrast >= 4.5) return adjusted; - - if (isLight) { - return "#ffffffff"; - } - - return theme.palette.getContrastText(baseColor); -} diff --git a/src/features/Calendars/services/getCalendarDetailAsync.ts b/src/features/Calendars/services/getCalendarDetailAsync.ts index ed685ac..6f0cc04 100644 --- a/src/features/Calendars/services/getCalendarDetailAsync.ts +++ b/src/features/Calendars/services/getCalendarDetailAsync.ts @@ -8,7 +8,6 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import { getCalendar } from "../CalendarApi"; import { RejectedError } from "../types/RejectedError"; import { extractCalendarEvents } from "../utils/extractCalendarEvents"; -import { defaultColors } from "@/utils/defaultColors"; import { type RootState } from "@/app/store"; export const getCalendarDetailAsync = createAsyncThunk< @@ -42,16 +41,15 @@ export const getCalendarDetailAsync = createAsyncThunk< match, signal )) as CalendarData; - - const color = calendar["apple:color"] - ? { light: calendar["apple:color"], dark: calendar["apple:color"] } - : defaultColors[0]; const syncToken = calendar._embedded?.["sync-token"]; const items = calendar._embedded?.["dav:item"]; const events: CalendarEvent[] = Array.isArray(items) ? items.flatMap((item: CalendarItem) => - extractCalendarEvents(item, { cal: calendarStored, color }) + extractCalendarEvents(item, { + cal: calendarStored, + color: calendarStored.color, + }) ) : []; diff --git a/src/features/Calendars/services/getCalendarsListAsync.ts b/src/features/Calendars/services/getCalendarsListAsync.ts index 0736e4a..b1363a6 100644 --- a/src/features/Calendars/services/getCalendarsListAsync.ts +++ b/src/features/Calendars/services/getCalendarsListAsync.ts @@ -10,6 +10,10 @@ import { CalendarData } from "../types/CalendarData"; import { RejectedError } from "../types/RejectedError"; import { normalizeCalendar } from "../utils/normalizeCalendar"; import { fetchOwnerData } from "./helpers"; +import { createTheme } from "@mui/material/styles"; +import { getAccessiblePair } from "@/utils/getAccessiblePair"; + +const theme = createTheme(); export const getCalendarsListAsync = createAsyncThunk< { importedCalendars: Record; errors: string }, @@ -77,10 +81,11 @@ export const getCalendarsListAsync = createAsyncThunk< emails: [], }; - const color = cal["apple:color"] + const rawColor = cal["apple:color"]; + const color = rawColor ? { - light: cal["apple:color"], - dark: "#fff", + light: rawColor, + dark: getAccessiblePair(rawColor, theme), } : defaultColors[0]; @@ -126,10 +131,6 @@ export const getCalendarsListAsync = createAsyncThunk< if (fetchedCal) { importedCalendars[id] = { ...fetchedCal, - color: { - ...existingCal.color, - light: fetchedCal.color?.light, - }, events: existingCal.events || {}, }; } diff --git a/src/utils/getAccessiblePair.tsx b/src/utils/getAccessiblePair.tsx new file mode 100644 index 0000000..acdc7b7 --- /dev/null +++ b/src/utils/getAccessiblePair.tsx @@ -0,0 +1,23 @@ +import { darken, getContrastRatio, lighten, Theme } from "@linagora/twake-mui"; + +export function getAccessiblePair(baseColor: string, theme: Theme): string { + if (typeof baseColor !== "string") { + return theme.palette.getContrastText("#000"); + } + + const contrastToBlack = getContrastRatio(baseColor, "#000"); + const contrastToWhite = getContrastRatio(baseColor, "#fff"); + const isLight = contrastToBlack > contrastToWhite; + + const adjusted = isLight ? darken(baseColor, 0.6) : lighten(baseColor, 0.7); + + // Check if contrast meets 4.5 + const contrast = getContrastRatio(baseColor, adjusted); + if (contrast >= 4.5) return adjusted; + + if (isLight) { + return "#ffffffff"; + } + + return theme.palette.getContrastText(baseColor); +}