Color picker rework (#232)
Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -10,13 +10,14 @@ import { searchUsers } from "../../features/User/userAPI";
|
||||
import PeopleOutlineOutlinedIcon from "@mui/icons-material/PeopleOutlineOutlined";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { getAccessiblePair } from "../Calendar/utils/calendarColorsUtils";
|
||||
|
||||
export interface User {
|
||||
email: string;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
openpaasId: string;
|
||||
color?: string;
|
||||
color?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function PeopleSearch({
|
||||
@@ -150,8 +151,8 @@ export function PeopleSearch({
|
||||
const label = isString ? option : option.displayName || option.email;
|
||||
const chipColor = isString
|
||||
? theme.palette.grey[300]
|
||||
: (option.color ?? theme.palette.grey[300]);
|
||||
const textColor = theme.palette.getContrastText(chipColor);
|
||||
: (option.color?.light ?? theme.palette.grey[300]);
|
||||
const textColor = getAccessiblePair(chipColor, theme);
|
||||
|
||||
return (
|
||||
<Chip
|
||||
|
||||
@@ -40,6 +40,8 @@ import momentTimezonePlugin from "@fullcalendar/moment-timezone";
|
||||
import { TimezoneSelector } from "./TimezoneSelector";
|
||||
import { MiniCalendar } from "./MiniCalendar";
|
||||
import { User } from "../Attendees/PeopleSearch";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { updateDarkColor } from "./utils/calendarColorsUtils";
|
||||
|
||||
interface CalendarAppProps {
|
||||
calendarRef: React.RefObject<CalendarApi | null>;
|
||||
@@ -58,7 +60,7 @@ export default function CalendarApp({
|
||||
const userId =
|
||||
useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const theme = useTheme();
|
||||
useEffect(() => {
|
||||
if (!tokens || !userId) {
|
||||
dispatch(push("/"));
|
||||
@@ -108,6 +110,15 @@ export default function CalendarApp({
|
||||
}
|
||||
}, [calendars, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
updateDarkColor(calendars, theme, dispatch);
|
||||
}, [
|
||||
theme,
|
||||
Object.values(calendars)
|
||||
.map((c) => c.color?.dark)
|
||||
.join(","),
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const validCalendarIds = new Set(Object.keys(calendars));
|
||||
setSelectedCalendars((prev) =>
|
||||
|
||||
@@ -1,36 +1,241 @@
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const defaultColors = ["#34d399", "#fbbf24", "#f87171", "#60a5fa", "#f472b6"];
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Popover,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils";
|
||||
|
||||
export function ColorPicker({
|
||||
selectedColor,
|
||||
colors = defaultColors,
|
||||
onChange,
|
||||
}: {
|
||||
selectedColor?: string;
|
||||
colors?: string[];
|
||||
onChange: (color: string) => void;
|
||||
selectedColor: Record<string, string>;
|
||||
colors?: Record<string, string>[];
|
||||
onChange: (color: Record<string, string>) => void;
|
||||
}) {
|
||||
const [customColor, setCustomColor] = useState(
|
||||
!colors.find((c) => c.light === selectedColor?.light)
|
||||
? selectedColor
|
||||
: undefined
|
||||
);
|
||||
return (
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
{colors.map((c) => (
|
||||
<Box
|
||||
key={c}
|
||||
role="button"
|
||||
aria-label={`select color ${c}`}
|
||||
onClick={() => onChange(c)}
|
||||
style={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: c,
|
||||
cursor: "pointer",
|
||||
border:
|
||||
selectedColor === c ? "2px solid black" : "2px solid transparent",
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
<ColorBox
|
||||
key={c.light}
|
||||
color={c}
|
||||
onChange={onChange}
|
||||
selectedColor={selectedColor}
|
||||
/>
|
||||
))}
|
||||
{customColor && (
|
||||
<ColorBox
|
||||
color={customColor ?? {}}
|
||||
onChange={onChange}
|
||||
selectedColor={selectedColor}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ColorPickerBox
|
||||
onChange={(c) => {
|
||||
onChange(c);
|
||||
setCustomColor(c);
|
||||
}}
|
||||
selectedColor={selectedColor}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
function ColorBox({
|
||||
color,
|
||||
onChange,
|
||||
selectedColor,
|
||||
}: {
|
||||
color: Record<string, string>;
|
||||
onChange: (color: Record<string, string>) => void;
|
||||
selectedColor: Record<string, string>;
|
||||
}) {
|
||||
return (
|
||||
<Box
|
||||
role="button"
|
||||
aria-label={`select color ${color.light}`}
|
||||
onClick={() => onChange(color)}
|
||||
style={{
|
||||
width: "46px",
|
||||
height: "32px",
|
||||
padding: 0,
|
||||
borderRadius: "4px",
|
||||
backgroundColor: color.light,
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
height: "7px",
|
||||
width: "100%",
|
||||
borderRadius: "4px 4px 0px 0px",
|
||||
backgroundColor: color.dark,
|
||||
}}
|
||||
></Box>
|
||||
<CheckIcon
|
||||
style={{
|
||||
visibility:
|
||||
selectedColor?.light === color.light ? "visible" : "hidden",
|
||||
color: color.dark,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ColorPickerBox({
|
||||
onChange,
|
||||
selectedColor,
|
||||
}: {
|
||||
onChange: (color: Record<string, string>) => void;
|
||||
selectedColor: Record<string, string>;
|
||||
}) {
|
||||
const [oldColor] = useState(
|
||||
selectedColor ?? { light: "#ffffff", dark: "#808080" }
|
||||
);
|
||||
const [color, setColor] = useState(oldColor);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const theme = useTheme();
|
||||
|
||||
const handleClick = (event: any) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onChange(oldColor);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
onChange(color);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleColorChange = (c: string) => {
|
||||
const newLight = c;
|
||||
const newColor = {
|
||||
light: newLight,
|
||||
dark: getAccessiblePair(newLight, theme),
|
||||
};
|
||||
setColor(newColor);
|
||||
onChange(newColor);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
key={"colorPicker"}
|
||||
role="button"
|
||||
aria-label={`select custom color`}
|
||||
onClick={handleClick}
|
||||
style={{
|
||||
width: "46px",
|
||||
height: "32px",
|
||||
padding: 0,
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #CBD2E0",
|
||||
backgroundColor: "#FFF",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
height: "7px",
|
||||
width: "100%",
|
||||
borderRadius: "4px 4px 0px 0px",
|
||||
backgroundColor: "#CBD2E0",
|
||||
}}
|
||||
></Box>
|
||||
<AddIcon
|
||||
style={{
|
||||
color: "#CBD2E0",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: "center",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
style: {
|
||||
padding: "24px",
|
||||
width: "294px",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0px 1px 3px #3C404326",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1" fontWeight="600">
|
||||
Choose custom colour
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 2, color: "text.secondary" }}>
|
||||
Choose a background colour for this calendar
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<HexColorPicker
|
||||
color={color.light}
|
||||
onChange={handleColorChange}
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", alignItems: "center", mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ mr: 1 }}>
|
||||
Hex
|
||||
</Typography>
|
||||
<TextField
|
||||
value={color.light?.toUpperCase()}
|
||||
onChange={(e) => handleColorChange(e.target.value)}
|
||||
variant="standard"
|
||||
size="small"
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", gap: 1 }}>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
sx={{ textTransform: "none" }}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Box>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ResponsiveDialog } from "../Dialog";
|
||||
import { AccessTab } from "./AccessTab";
|
||||
import { ImportTab } from "./ImportTab";
|
||||
import { SettingsTab } from "./SettingsTab";
|
||||
import { defaultColors } from "./utils/calendarColorsUtils";
|
||||
|
||||
function CalendarPopover({
|
||||
open,
|
||||
@@ -34,7 +35,7 @@ function CalendarPopover({
|
||||
// existing calendar params
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [color, setColor] = useState("");
|
||||
const [color, setColor] = useState<Record<string, string>>(defaultColors[0]);
|
||||
const [visibility, setVisibility] = useState<"private" | "public">("public");
|
||||
|
||||
// import tab state
|
||||
@@ -45,7 +46,7 @@ function CalendarPopover({
|
||||
// new calendar params (for import new)
|
||||
const [newCalName, setNewCalName] = useState("");
|
||||
const [newCalDescription, setNewCalDescription] = useState("");
|
||||
const [newCalColor, setNewCalColor] = useState("");
|
||||
const [newCalColor, setNewCalColor] = useState(defaultColors[0]);
|
||||
const [newCalVisibility, setNewCalVisibility] = useState<
|
||||
"public" | "private"
|
||||
>("public");
|
||||
@@ -55,13 +56,13 @@ function CalendarPopover({
|
||||
if (calendar) {
|
||||
setName(calendar.name);
|
||||
setDescription(calendar.description ?? "");
|
||||
setColor(calendar.color ?? "");
|
||||
setColor(calendar.color ?? defaultColors[0]);
|
||||
setVisibility(calendar.visibility ?? "public");
|
||||
setImportTarget(calendar.id ?? "new");
|
||||
} else {
|
||||
setName("");
|
||||
setDescription("");
|
||||
setColor("");
|
||||
setColor(defaultColors[0]);
|
||||
setVisibility("public");
|
||||
setImportTarget("new");
|
||||
}
|
||||
@@ -90,7 +91,7 @@ function CalendarPopover({
|
||||
calId: string,
|
||||
name: string,
|
||||
desc: string,
|
||||
color: string,
|
||||
color: Record<string, string>,
|
||||
visibility: string
|
||||
) => {
|
||||
await dispatch(
|
||||
@@ -160,7 +161,7 @@ function CalendarPopover({
|
||||
onClose(e, reason);
|
||||
setName("");
|
||||
setDescription("");
|
||||
setColor("");
|
||||
setColor(defaultColors[0]);
|
||||
setTab("settings");
|
||||
setVisibility("public");
|
||||
setImportTarget("new");
|
||||
@@ -168,7 +169,7 @@ function CalendarPopover({
|
||||
|
||||
setNewCalName("");
|
||||
setNewCalDescription("");
|
||||
setNewCalColor("");
|
||||
setNewCalColor(defaultColors[0]);
|
||||
setNewCalVisibility("public");
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ import { addSharedCalendarAsync } from "../../features/Calendars/CalendarSlice";
|
||||
import { ColorPicker } from "./CalendarColorPicker";
|
||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||
import { User, PeopleSearch } from "../Attendees/PeopleSearch";
|
||||
import { getAccessiblePair } from "./utils/calendarColorsUtils";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { ResponsiveDialog } from "../Dialog";
|
||||
|
||||
interface CalendarWithOwner {
|
||||
cal: Record<string, any>;
|
||||
@@ -29,8 +32,10 @@ function CalendarItem({
|
||||
}: {
|
||||
cal: CalendarWithOwner;
|
||||
onRemove: () => void;
|
||||
onColorChange: (color: string) => void;
|
||||
onColorChange: (color: Record<string, string>) => void;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={cal.owner.email + cal.cal["dav:name"]}
|
||||
@@ -65,7 +70,10 @@ function CalendarItem({
|
||||
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<ColorPicker
|
||||
selectedColor={cal.cal["apple:color"]}
|
||||
selectedColor={{
|
||||
light: cal.cal["apple:color"],
|
||||
dark: getAccessiblePair(cal.cal["apple:color"], theme),
|
||||
}}
|
||||
onChange={onColorChange}
|
||||
/>
|
||||
<IconButton size="small" onClick={onRemove}>
|
||||
@@ -85,7 +93,10 @@ function SelectedCalendarsList({
|
||||
calendars: Record<string, Calendars>;
|
||||
selectedCal: CalendarWithOwner[];
|
||||
onRemove: (cal: CalendarWithOwner) => void;
|
||||
onColorChange: (cal: CalendarWithOwner, color: string) => void;
|
||||
onColorChange: (
|
||||
cal: CalendarWithOwner,
|
||||
color: Record<string, string>
|
||||
) => void;
|
||||
}) {
|
||||
if (selectedCal.length === 0) return null;
|
||||
|
||||
@@ -194,7 +205,13 @@ export default function CalendarSearch({
|
||||
addSharedCalendarAsync({
|
||||
userId: openpaasId,
|
||||
calId,
|
||||
cal: { ...cal, color: cal.cal["apple:color"] },
|
||||
cal: {
|
||||
...cal,
|
||||
color: {
|
||||
light: cal.cal["apple:color"],
|
||||
dark: cal.cal["X-TWAKE-Dark-theme-color"],
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
return cal.cal._links.self.href
|
||||
@@ -212,122 +229,92 @@ export default function CalendarSearch({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<ResponsiveDialog
|
||||
open={open}
|
||||
contentSx={{ paddingTop: "8px !important" }}
|
||||
onClose={() => {
|
||||
onClose({}, "backdropClick");
|
||||
setSelectedCalendars([]);
|
||||
setSelectedUsers([]);
|
||||
}}
|
||||
title="Browse other calendars"
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
Add
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
top: "20%",
|
||||
transform: "translate( -50%)",
|
||||
width: "50vw",
|
||||
maxHeight: "80vh",
|
||||
<PeopleSearch
|
||||
objectTypes={["user"]}
|
||||
selectedUsers={selectedUsers}
|
||||
onChange={async (event: any, value: User[]) => {
|
||||
setSelectedUsers(value);
|
||||
|
||||
const cals = await Promise.all(
|
||||
value.map(async (user: User) => {
|
||||
const cals = (await getCalendars(
|
||||
user.openpaasId,
|
||||
"sharedPublic=true&withRights=true"
|
||||
)) as Record<string, any>;
|
||||
return cals._embedded?.["dav:calendar"]
|
||||
? cals._embedded["dav:calendar"].map(
|
||||
(cal: Record<string, any>) => ({ cal, owner: user })
|
||||
)
|
||||
: { cal: undefined, owner: user };
|
||||
})
|
||||
);
|
||||
|
||||
setSelectedCalendars(cals.flat());
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
maxHeight: "80vh",
|
||||
}}
|
||||
>
|
||||
<Box style={{ position: "absolute", top: 8, right: 8 }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
/>
|
||||
|
||||
<CardHeader
|
||||
title="Browse other calendars"
|
||||
style={{ paddingBottom: 0 }}
|
||||
/>
|
||||
<CardContent style={{ flex: 1, overflow: "auto" }}>
|
||||
<PeopleSearch
|
||||
objectTypes={["user"]}
|
||||
selectedUsers={selectedUsers}
|
||||
onChange={async (event: any, value: User[]) => {
|
||||
setSelectedUsers(value);
|
||||
|
||||
const cals = await Promise.all(
|
||||
value.map(async (user: User) => {
|
||||
const cals = (await getCalendars(
|
||||
user.openpaasId,
|
||||
"sharedPublic=true&withRights=true"
|
||||
)) as Record<string, any>;
|
||||
return cals._embedded?.["dav:calendar"]
|
||||
? cals._embedded["dav:calendar"].map(
|
||||
(cal: Record<string, any>) => ({ cal, owner: user })
|
||||
)
|
||||
: { cal: undefined, owner: user };
|
||||
})
|
||||
);
|
||||
|
||||
setSelectedCalendars(cals.flat());
|
||||
}}
|
||||
/>
|
||||
|
||||
<SelectedCalendarsList
|
||||
calendars={calendars}
|
||||
selectedCal={selectedCal}
|
||||
onRemove={(cal) => {
|
||||
setSelectedCalendars((prev) =>
|
||||
prev.filter(
|
||||
(c) => c.cal._links.self.href !== cal.cal._links.self.href
|
||||
)
|
||||
);
|
||||
if (
|
||||
!selectedCal.find(
|
||||
(c) =>
|
||||
cal.owner.email === c.owner.email &&
|
||||
c.cal._links.self.href !== cal.cal._links.self.href
|
||||
)
|
||||
) {
|
||||
setSelectedUsers((prev) =>
|
||||
prev.filter((u) => u.email !== cal.owner.email)
|
||||
);
|
||||
}
|
||||
}}
|
||||
onColorChange={(cal, color) =>
|
||||
setSelectedCalendars((prev) =>
|
||||
prev.map((prevcal) =>
|
||||
prevcal.owner.email === cal.owner.email &&
|
||||
prevcal.cal._links.self.href === cal.cal._links.self.href
|
||||
? {
|
||||
...prevcal,
|
||||
cal: { ...prevcal.cal, "apple:color": color },
|
||||
}
|
||||
: prevcal
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<CardActions style={{ justifyContent: "flex-end", gap: 8 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
Add
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Box>
|
||||
</Modal>
|
||||
<SelectedCalendarsList
|
||||
calendars={calendars}
|
||||
selectedCal={selectedCal}
|
||||
onRemove={(cal) => {
|
||||
setSelectedCalendars((prev) =>
|
||||
prev.filter(
|
||||
(c) => c.cal._links.self.href !== cal.cal._links.self.href
|
||||
)
|
||||
);
|
||||
if (
|
||||
!selectedCal.find(
|
||||
(c) =>
|
||||
cal.owner.email === c.owner.email &&
|
||||
c.cal._links.self.href !== cal.cal._links.self.href
|
||||
)
|
||||
) {
|
||||
setSelectedUsers((prev) =>
|
||||
prev.filter((u) => u.email !== cal.owner.email)
|
||||
);
|
||||
}
|
||||
}}
|
||||
onColorChange={(cal, color) =>
|
||||
setSelectedCalendars((prev) =>
|
||||
prev.map((prevcal) =>
|
||||
prevcal.owner.email === cal.owner.email &&
|
||||
prevcal.cal._links.self.href === cal.cal._links.self.href
|
||||
? {
|
||||
...prevcal,
|
||||
cal: {
|
||||
...prevcal.cal,
|
||||
"apple:color": color.light,
|
||||
"X-TWAKE-Dark-theme-color": color.dark,
|
||||
},
|
||||
}
|
||||
: prevcal
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -218,8 +218,8 @@ function CalendarSelector({
|
||||
<label>
|
||||
<Checkbox
|
||||
sx={{
|
||||
color: calendars[id].color,
|
||||
"&.Mui-checked": { color: calendars[id].color },
|
||||
color: calendars[id].color?.light,
|
||||
"&.Mui-checked": { color: calendars[id].color?.light },
|
||||
}}
|
||||
size="small"
|
||||
checked={selectedCalendars.includes(id)}
|
||||
@@ -232,7 +232,14 @@ function CalendarSelector({
|
||||
</IconButton>
|
||||
</div>
|
||||
<Menu id={id} anchorEl={anchorEl} open={open} onClose={handleClose}>
|
||||
<MenuItem onClick={() => setOpen()}>Modify</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setOpen();
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
Modify
|
||||
</MenuItem>
|
||||
{!isDefault && <Divider />}
|
||||
{!isDefault && (
|
||||
<MenuItem onClick={() => setDeletePopupOpen(!deletePopupOpen)}>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function ImportTab({
|
||||
setName: Function;
|
||||
description: string;
|
||||
setDescription: Function;
|
||||
color: string;
|
||||
color: Record<string, string>;
|
||||
setColor: Function;
|
||||
visibility: "public" | "private";
|
||||
setVisibility: Function;
|
||||
|
||||
@@ -29,7 +29,7 @@ export function SettingsTab({
|
||||
setName: Function;
|
||||
description: string;
|
||||
setDescription: Function;
|
||||
color: string;
|
||||
color: Record<string, string>;
|
||||
setColor: Function;
|
||||
visibility: "public" | "private";
|
||||
setVisibility: Function;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useRef } from "react";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import {
|
||||
@@ -6,6 +7,7 @@ import {
|
||||
} from "../../features/Calendars/CalendarSlice";
|
||||
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||
import { User, PeopleSearch } from "../Attendees/PeopleSearch";
|
||||
import { getAccessiblePair } from "./utils/calendarColorsUtils";
|
||||
|
||||
const requestControllers = new Map<string, AbortController>();
|
||||
|
||||
@@ -26,6 +28,7 @@ export function TempCalendarsInput({
|
||||
const tempcalendars =
|
||||
useAppSelector((state) => state.calendars.templist) ?? {};
|
||||
const calendars = useAppSelector((state) => state.calendars.list);
|
||||
const theme = useTheme();
|
||||
|
||||
const prevUsersRef = useRef<User[]>([]);
|
||||
const userColors = new Map<string, string>();
|
||||
@@ -59,8 +62,12 @@ export function TempCalendarsInput({
|
||||
const existingColors = new Set(userColors.values());
|
||||
userColors.set(user.email, generateUserColor(existingColors));
|
||||
}
|
||||
const lightColor = userColors.get(user.email)!;
|
||||
|
||||
user.color = userColors.get(user.email)!;
|
||||
user.color = {
|
||||
light: lightColor,
|
||||
dark: getAccessiblePair(lightColor, theme),
|
||||
};
|
||||
|
||||
dispatch(
|
||||
getTempCalendarsListAsync(user, { signal: controller.signal })
|
||||
|
||||
@@ -127,7 +127,9 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
|
||||
);
|
||||
const delegated = calendar.delegated;
|
||||
let Icon = null;
|
||||
let titleStyle: React.CSSProperties = {};
|
||||
let titleStyle: React.CSSProperties = {
|
||||
color: event._def.extendedProps.colors?.dark,
|
||||
};
|
||||
|
||||
const showSpecialDisplay = attendees.filter((att: userAttendee) =>
|
||||
ownerEmails.has(att.cal_address.toLowerCase())
|
||||
@@ -265,7 +267,7 @@ function RenderEventTitle(
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: calendar.color,
|
||||
backgroundColor: calendar.color?.light,
|
||||
color: "white",
|
||||
borderRadius: "4px",
|
||||
border: "1px",
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { darken, getContrastRatio, lighten, Theme } from "@mui/material";
|
||||
import { ThunkDispatch } from "@reduxjs/toolkit";
|
||||
import { updateCalColor } from "../../../features/Calendars/CalendarSlice";
|
||||
import { Calendars } from "../../../features/Calendars/CalendarTypes";
|
||||
|
||||
export function updateDarkColor(
|
||||
calendars: Record<string, Calendars>,
|
||||
theme: Theme,
|
||||
dispatch: ThunkDispatch<any, any, any>
|
||||
) {
|
||||
Object.values(calendars).forEach((cal) => {
|
||||
if (!cal?.color?.light) return;
|
||||
const baseColor = cal.color.light;
|
||||
|
||||
const isDefault = Object.values(defaultColors).find(
|
||||
(c) => c.light === baseColor
|
||||
);
|
||||
const darkColor = isDefault
|
||||
? isDefault.dark
|
||||
: getAccessiblePair(baseColor, theme);
|
||||
|
||||
dispatch(
|
||||
updateCalColor({
|
||||
id: cal.id,
|
||||
color: { light: baseColor, dark: darkColor },
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function getAccessiblePair(baseColor: string, theme: Theme): string {
|
||||
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);
|
||||
console.log(isLight, contrast);
|
||||
if (contrast >= 4.5) return adjusted;
|
||||
|
||||
if (isLight) {
|
||||
return "#ffffffff";
|
||||
}
|
||||
|
||||
return theme.palette.getContrastText(baseColor);
|
||||
}
|
||||
|
||||
export const defaultColors = [
|
||||
{ light: "#D0ECDA", dark: "#329655" },
|
||||
{ light: "#FAE3CE", dark: "#E15300" },
|
||||
{ light: "#F5CFD0", dark: "#BE0103" },
|
||||
{ light: "#AFCBEF", dark: "#0654B1" },
|
||||
];
|
||||
@@ -62,9 +62,9 @@ export const eventToFullCalendarFormat = (
|
||||
.concat(filteredTempEvents.map((e) => ({ ...e, temp: true })))
|
||||
.map((e) => {
|
||||
if (e.calId.split("/")[0] === userId) {
|
||||
return { ...e, editable: true };
|
||||
return { ...e, colors: e.color, color: e.color?.light, editable: true };
|
||||
}
|
||||
return { ...e, editable: false };
|
||||
return { ...e, colors: e.color, color: e.color?.light, editable: false };
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user