mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +00:00
feat: draw calendar colours in the colour-blind-safe palette when that setting is on
This commit is contained in:
@@ -14,7 +14,7 @@ import { format } from 'date-fns';
|
|||||||
import { ArrowLeft, MapPin, Repeat, Search, X } from '../icons';
|
import { ArrowLeft, MapPin, Repeat, Search, X } from '../icons';
|
||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { useCalendarStore, searchCalendarEvents } from '../../stores/calendar-store';
|
import { useCalendarStore, searchCalendarEvents } from '../../stores/calendar-store';
|
||||||
import { useCalendarLocale } from '../../lib/calendar-locale';
|
import { useCalendarLocale } from '../../lib/calendar-locale';
|
||||||
import { formatCalendarDay } from '../../lib/calendar-system';
|
import { formatCalendarDay } from '../../lib/calendar-system';
|
||||||
@@ -62,6 +62,7 @@ export function CalendarSearchSheet({
|
|||||||
onClose,
|
onClose,
|
||||||
}: CalendarSearchSheetProps) {
|
}: CalendarSearchSheetProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const { locale, t, system } = useCalendarLocale();
|
const { locale, t, system } = useCalendarLocale();
|
||||||
const loaded = useCalendarStore((s) => s.events);
|
const loaded = useCalendarStore((s) => s.events);
|
||||||
@@ -134,7 +135,7 @@ export function CalendarSearchSheet({
|
|||||||
const start = getEventStartDate(event);
|
const start = getEventStartDate(event);
|
||||||
const end = getEventEndDate(event);
|
const end = getEventEndDate(event);
|
||||||
const calendar = calendars.find((cal) => cal.id === getPrimaryCalendarId(event));
|
const calendar = calendars.find((cal) => cal.id === getPrimaryCalendarId(event));
|
||||||
const color = getEventColor(event, calendars);
|
const color = paint(getEventColor(event, calendars));
|
||||||
const inactive = isInactiveEvent(event, currentUserEmails);
|
const inactive = isInactiveEvent(event, currentUserEmails);
|
||||||
const place = event.locations ? Object.values(event.locations)[0]?.name : undefined;
|
const place = event.locations ? Object.values(event.locations)[0]?.name : undefined;
|
||||||
const meta = [place, calendar?.name].filter(Boolean).join(' · ');
|
const meta = [place, calendar?.name].filter(Boolean).join(' · ');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from '../icons';
|
} from '../icons';
|
||||||
import type { Calendar } from '../../api/types';
|
import type { Calendar } from '../../api/types';
|
||||||
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { useAnimDuration } from '../../theme/dynamic';
|
import { useAnimDuration } from '../../theme/dynamic';
|
||||||
import { CALENDAR_COLOR_PALETTE, calendarColorName, getCalendarColor } from '../../lib/calendar-utils';
|
import { CALENDAR_COLOR_PALETTE, calendarColorName, getCalendarColor } from '../../lib/calendar-utils';
|
||||||
import { BIRTHDAY_CALENDAR_ID } from '../../lib/birthday-calendar';
|
import { BIRTHDAY_CALENDAR_ID } from '../../lib/birthday-calendar';
|
||||||
@@ -309,6 +309,7 @@ function Section({
|
|||||||
isSubscriptionCalendar?: (calendarId: string) => boolean;
|
isSubscriptionCalendar?: (calendarId: string) => boolean;
|
||||||
}) {
|
}) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const t = useLocaleStore((s) => s.t);
|
const t = useLocaleStore((s) => s.t);
|
||||||
// Neighbours a calendar can move past: the server calendars of this section.
|
// Neighbours a calendar can move past: the server calendars of this section.
|
||||||
@@ -352,8 +353,8 @@ function Section({
|
|||||||
style={[
|
style={[
|
||||||
styles.swatch,
|
styles.swatch,
|
||||||
{
|
{
|
||||||
backgroundColor: visible ? getCalendarColor(cal) : 'transparent',
|
backgroundColor: visible ? paint(getCalendarColor(cal)) : 'transparent',
|
||||||
borderColor: getCalendarColor(cal),
|
borderColor: paint(getCalendarColor(cal)),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Clock, MapPin, Users } from '../icons';
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import {
|
import {
|
||||||
eventTimeRange,
|
eventTimeRange,
|
||||||
getEventColor,
|
getEventColor,
|
||||||
@@ -38,9 +38,10 @@ function participantCount(event: CalendarEvent): number {
|
|||||||
*/
|
*/
|
||||||
export function EventCard({ event, calendars, timeFormat, currentUserEmails, onPress, onLongPress, day }: EventCardProps) {
|
export function EventCard({ event, calendars, timeFormat, currentUserEmails, onPress, onLongPress, day }: EventCardProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
const color = getEventColor(event, calendars);
|
const color = paint(getEventColor(event, calendars));
|
||||||
const range = eventTimeRange(event);
|
const range = eventTimeRange(event);
|
||||||
const { start, end } = range;
|
const { start, end } = range;
|
||||||
const allDay = range.allDay || (!!day && isTimedEventFullDayOnDate(event, day));
|
const allDay = range.allDay || (!!day && isTimedEventFullDayOnDate(event, day));
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import {
|
|||||||
import { format, type Locale } from 'date-fns';
|
import { format, type Locale } from 'date-fns';
|
||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import {
|
import {
|
||||||
eventTimeRange,
|
eventTimeRange,
|
||||||
getEventColor,
|
getEventColor,
|
||||||
@@ -140,6 +140,7 @@ export function EventDetailSheet({
|
|||||||
onSaveNote,
|
onSaveNote,
|
||||||
}: EventDetailSheetProps) {
|
}: EventDetailSheetProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
const [rsvpBusy, setRsvpBusy] = React.useState(false);
|
const [rsvpBusy, setRsvpBusy] = React.useState(false);
|
||||||
const [noteOpen, setNoteOpen] = React.useState(false);
|
const [noteOpen, setNoteOpen] = React.useState(false);
|
||||||
@@ -200,7 +201,7 @@ export function EventDetailSheet({
|
|||||||
|
|
||||||
if (!event) return null;
|
if (!event) return null;
|
||||||
|
|
||||||
const color = getEventColor(event, calendars);
|
const color = paint(getEventColor(event, calendars));
|
||||||
const calendarId = getPrimaryCalendarId(event);
|
const calendarId = getPrimaryCalendarId(event);
|
||||||
const calendar = calendars.find((c) => c.id === calendarId);
|
const calendar = calendars.find((c) => c.id === calendarId);
|
||||||
const range = formatRange(event, timeFormat, locale);
|
const range = formatRange(event, timeFormat, locale);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import type {
|
|||||||
VirtualLocation,
|
VirtualLocation,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import {
|
import {
|
||||||
getCalendarColor,
|
getCalendarColor,
|
||||||
getPrimaryCalendarId,
|
getPrimaryCalendarId,
|
||||||
@@ -189,6 +189,7 @@ export function EventModal({
|
|||||||
onClose,
|
onClose,
|
||||||
}: EventModalProps) {
|
}: EventModalProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
const timeFormat = useSettingsStore((s) => s.calendarTimeFormat);
|
const timeFormat = useSettingsStore((s) => s.calendarTimeFormat);
|
||||||
@@ -588,7 +589,7 @@ export function EventModal({
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.calendarSwatch,
|
styles.calendarSwatch,
|
||||||
{ backgroundColor: getCalendarColor(selectedCalendar) },
|
{ backgroundColor: paint(getCalendarColor(selectedCalendar)) },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -614,7 +615,7 @@ export function EventModal({
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.calendarSwatch,
|
styles.calendarSwatch,
|
||||||
{ backgroundColor: getCalendarColor(c) },
|
{ backgroundColor: paint(getCalendarColor(c)) },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.popoverRowText}>{c.name}</Text>
|
<Text style={styles.popoverRowText}>{c.name}</Text>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { uploadBlob } from '../../api/blob';
|
|||||||
import { parseCalendarBlob } from '../../api/calendar';
|
import { parseCalendarBlob } from '../../api/calendar';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { getCalendarColor } from '../../lib/calendar-utils';
|
import { getCalendarColor } from '../../lib/calendar-utils';
|
||||||
import { isWritableCalendar } from '../../lib/calendar-editability';
|
import { isWritableCalendar } from '../../lib/calendar-editability';
|
||||||
import { ImportRefusedError, type ImportResult } from '../../stores/calendar-store';
|
import { ImportRefusedError, type ImportResult } from '../../stores/calendar-store';
|
||||||
@@ -58,6 +58,7 @@ function eventDateLabel(event: Partial<CalendarEvent>, locale: Locale): string |
|
|||||||
|
|
||||||
export function ICalImportSheet({ visible, onClose, calendars, onImport, onImported }: Props) {
|
export function ICalImportSheet({ visible, onClose, calendars, onImport, onImported }: Props) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const t = useLocaleStore((s) => s.t);
|
const t = useLocaleStore((s) => s.t);
|
||||||
@@ -308,7 +309,7 @@ export function ICalImportSheet({ visible, onClose, calendars, onImport, onImpor
|
|||||||
onPress={() => setCalendarId(cal.id)}
|
onPress={() => setCalendarId(cal.id)}
|
||||||
style={[styles.calChip, cal.id === calendarId && styles.calChipActive]}
|
style={[styles.calChip, cal.id === calendarId && styles.calChipActive]}
|
||||||
>
|
>
|
||||||
<View style={[styles.calSwatch, { backgroundColor: getCalendarColor(cal) }]} />
|
<View style={[styles.calSwatch, { backgroundColor: paint(getCalendarColor(cal)) }]} />
|
||||||
<Text style={styles.calChipText} numberOfLines={1}>{cal.name}</Text>
|
<Text style={styles.calChipText} numberOfLines={1}>{cal.name}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { X, Plus, RefreshCw, Trash2, Rss, AlertTriangle } from '../icons';
|
|||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { CALENDAR_COLOR_PALETTE, calendarColorName } from '../../lib/calendar-utils';
|
import { CALENDAR_COLOR_PALETTE, calendarColorName } from '../../lib/calendar-utils';
|
||||||
import { useCalendarLocale } from '../../lib/calendar-locale';
|
import { useCalendarLocale } from '../../lib/calendar-locale';
|
||||||
import {
|
import {
|
||||||
@@ -37,6 +37,7 @@ interface Props {
|
|||||||
|
|
||||||
export function ICalSubscriptionSheet({ visible, onClose }: Props) {
|
export function ICalSubscriptionSheet({ visible, onClose }: Props) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
@@ -257,7 +258,7 @@ export function ICalSubscriptionSheet({ visible, onClose }: Props) {
|
|||||||
const busy = !!syncing[sub.id];
|
const busy = !!syncing[sub.id];
|
||||||
return (
|
return (
|
||||||
<View key={sub.id} style={styles.subRow}>
|
<View key={sub.id} style={styles.subRow}>
|
||||||
<View style={[styles.subSwatch, { backgroundColor: sub.color || c.primary }]} />
|
<View style={[styles.subSwatch, { backgroundColor: sub.color ? paint(sub.color) : c.primary }]} />
|
||||||
<Pressable style={{ flex: 1, minWidth: 0 }} onPress={() => startEdit(sub)}>
|
<Pressable style={{ flex: 1, minWidth: 0 }} onPress={() => startEdit(sub)}>
|
||||||
<Text style={styles.subName} numberOfLines={1}>{sub.name}</Text>
|
<Text style={styles.subName} numberOfLines={1}>{sub.name}</Text>
|
||||||
<Text style={styles.subMeta} numberOfLines={1}>
|
<Text style={styles.subMeta} numberOfLines={1}>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { componentSizes, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { componentSizes, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { useSettingsStore } from '../../stores/settings-store';
|
import { useSettingsStore } from '../../stores/settings-store';
|
||||||
import {
|
import {
|
||||||
buildEventDayIndex,
|
buildEventDayIndex,
|
||||||
@@ -157,6 +157,7 @@ function MonthWeekRowInner({
|
|||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
// "Light event colors" (webmail calendarTintedEvents).
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
|
const paint = useCvdColor();
|
||||||
const chipTime = monthChipShowsTime(true, width / 7);
|
const chipTime = monthChipShowsTime(true, width / 7);
|
||||||
if (variant === 'full') {
|
if (variant === 'full') {
|
||||||
return (
|
return (
|
||||||
@@ -241,7 +242,7 @@ function MonthWeekRowInner({
|
|||||||
{visible.map((event, idx) => (
|
{visible.map((event, idx) => (
|
||||||
<View
|
<View
|
||||||
key={`${event.id}-${idx}`}
|
key={`${event.id}-${idx}`}
|
||||||
style={[styles.dot, { backgroundColor: getEventColor(event, calendars) }]}
|
style={[styles.dot, { backgroundColor: paint(getEventColor(event, calendars)) }]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{overflow > 0 && (
|
{overflow > 0 && (
|
||||||
@@ -255,7 +256,7 @@ function MonthWeekRowInner({
|
|||||||
// Solid bars in the calendar colour; declined and
|
// Solid bars in the calendar colour; declined and
|
||||||
// cancelled events outlined (repos/branding/APP.md).
|
// cancelled events outlined (repos/branding/APP.md).
|
||||||
const inactive = isInactiveEvent(event, currentUserEmails);
|
const inactive = isInactiveEvent(event, currentUserEmails);
|
||||||
const colors = eventBlockColors(getEventColor(event, calendars), inactive, c, tinted);
|
const colors = eventBlockColors(paint(getEventColor(event, calendars)), inactive, c, tinted);
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={`${event.id}-${idx}`}
|
key={`${event.id}-${idx}`}
|
||||||
@@ -331,6 +332,7 @@ function FullWeekRow({
|
|||||||
const { t, system } = useCalendarLocale();
|
const { t, system } = useCalendarLocale();
|
||||||
// "Light event colors" (webmail calendarTintedEvents).
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
|
const paint = useCvdColor();
|
||||||
const showTime = monthChipShowsTime(showTimeInMonthView, width / 7);
|
const showTime = monthChipShowsTime(showTimeInMonthView, width / 7);
|
||||||
return (
|
return (
|
||||||
<View style={[styles.fullWeekRow, { height }]}>
|
<View style={[styles.fullWeekRow, { height }]}>
|
||||||
@@ -371,7 +373,7 @@ function FullWeekRow({
|
|||||||
</View>
|
</View>
|
||||||
{dayEvents.slice(0, shown).map((event, idx) => {
|
{dayEvents.slice(0, shown).map((event, idx) => {
|
||||||
const inactive = isInactiveEvent(event, currentUserEmails);
|
const inactive = isInactiveEvent(event, currentUserEmails);
|
||||||
const colors = eventBlockColors(getEventColor(event, calendars), inactive, c, tinted);
|
const colors = eventBlockColors(paint(getEventColor(event, calendars)), inactive, c, tinted);
|
||||||
// No time on events that fill the whole day.
|
// No time on events that fill the whole day.
|
||||||
const time = showTime && !event.showWithoutTime && !isTimedEventFullDayOnDate(event, d)
|
const time = showTime && !event.showWithoutTime && !isTimedEventFullDayOnDate(event, d)
|
||||||
? `${format(getEventStartDate(event), timePattern(timeFormat), { locale })} `
|
? `${format(getEventStartDate(event), timePattern(timeFormat), { locale })} `
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
import { format, isBefore, startOfDay } from 'date-fns';
|
import { format, isBefore, startOfDay } from 'date-fns';
|
||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { getCalendarColor, getTaskDueDisplayDate, timePattern, type TimeFormat } from '../../lib/calendar-utils';
|
import { getCalendarColor, getTaskDueDisplayDate, timePattern, type TimeFormat } from '../../lib/calendar-utils';
|
||||||
import { displayNow, isDisplayToday } from '../../lib/calendar-timezone';
|
import { displayNow, isDisplayToday } from '../../lib/calendar-timezone';
|
||||||
import {
|
import {
|
||||||
@@ -109,6 +109,7 @@ export function TasksSheet({
|
|||||||
onDelete,
|
onDelete,
|
||||||
}: TasksSheetProps) {
|
}: TasksSheetProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
const slideY = React.useRef(new Animated.Value(Dimensions.get('window').height)).current;
|
const slideY = React.useRef(new Animated.Value(Dimensions.get('window').height)).current;
|
||||||
@@ -373,7 +374,7 @@ export function TasksSheet({
|
|||||||
onPress={() => setEditor((e) => ({ ...e, calendarId: cal.id }))}
|
onPress={() => setEditor((e) => ({ ...e, calendarId: cal.id }))}
|
||||||
style={[styles.calChip, cal.id === editor.calendarId && styles.calChipActive]}
|
style={[styles.calChip, cal.id === editor.calendarId && styles.calChipActive]}
|
||||||
>
|
>
|
||||||
<View style={[styles.calSwatch, { backgroundColor: getCalendarColor(cal) }]} />
|
<View style={[styles.calSwatch, { backgroundColor: paint(getCalendarColor(cal)) }]} />
|
||||||
<Text style={styles.calChipText} numberOfLines={1}>{cal.name}</Text>
|
<Text style={styles.calChipText} numberOfLines={1}>{cal.name}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import { differenceInCalendarDays, format, type Locale } from 'date-fns';
|
import { differenceInCalendarDays, format, type Locale } from 'date-fns';
|
||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { useSettingsStore } from '../../stores/settings-store';
|
import { useSettingsStore } from '../../stores/settings-store';
|
||||||
import { useCalendarLocale } from '../../lib/calendar-locale';
|
import { useCalendarLocale } from '../../lib/calendar-locale';
|
||||||
import { dayNumberLabel } from '../../lib/calendar-system';
|
import { dayNumberLabel } from '../../lib/calendar-system';
|
||||||
@@ -125,6 +125,7 @@ function TimeGridScrollViewInner({
|
|||||||
draftResetKey = 0,
|
draftResetKey = 0,
|
||||||
}: TimeGridScrollViewProps) {
|
}: TimeGridScrollViewProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
// "Light event colors" (webmail calendarTintedEvents).
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
@@ -455,7 +456,7 @@ function TimeGridScrollViewInner({
|
|||||||
key={`${segment.event.id}:${segment.startIndex}:${segment.row}`}
|
key={`${segment.event.id}:${segment.startIndex}:${segment.row}`}
|
||||||
title={segment.event.title || t('calendar.events.no_title', '(No title)')}
|
title={segment.event.title || t('calendar.events.no_title', '(No title)')}
|
||||||
colors={eventBlockColors(
|
colors={eventBlockColors(
|
||||||
getEventColor(segment.event, calendars),
|
paint(getEventColor(segment.event, calendars)),
|
||||||
isInactiveEvent(segment.event, currentUserEmails),
|
isInactiveEvent(segment.event, currentUserEmails),
|
||||||
c,
|
c,
|
||||||
tinted,
|
tinted,
|
||||||
@@ -620,6 +621,7 @@ const DayColumn = React.memo(function DayColumn({
|
|||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
// "Light event colors" (webmail calendarTintedEvents).
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
|
const paint = useCvdColor();
|
||||||
const handleLongPress = React.useCallback(
|
const handleLongPress = React.useCallback(
|
||||||
(e: GestureResponderEvent) => {
|
(e: GestureResponderEvent) => {
|
||||||
onLongPressAt?.(day, hourAtOffset(e.nativeEvent.locationY, HOUR_HEIGHT));
|
onLongPressAt?.(day, hourAtOffset(e.nativeEvent.locationY, HOUR_HEIGHT));
|
||||||
@@ -650,7 +652,7 @@ const DayColumn = React.memo(function DayColumn({
|
|||||||
? minutesToTimeLabel(startMinutes, timeFormat)
|
? minutesToTimeLabel(startMinutes, timeFormat)
|
||||||
: null}
|
: null}
|
||||||
colors={eventBlockColors(
|
colors={eventBlockColors(
|
||||||
getEventColor(event, calendars),
|
paint(getEventColor(event, calendars)),
|
||||||
isInactiveEvent(event, currentUserEmails),
|
isInactiveEvent(event, currentUserEmails),
|
||||||
colors,
|
colors,
|
||||||
tinted,
|
tinted,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { addDays, format, isSameDay, startOfWeek } from 'date-fns';
|
|||||||
import { displayNow, displayNowMinutes, isDisplayToday } from '../../lib/calendar-timezone';
|
import { displayNow, displayNowMinutes, isDisplayToday } from '../../lib/calendar-timezone';
|
||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { useSettingsStore } from '../../stores/settings-store';
|
import { useSettingsStore } from '../../stores/settings-store';
|
||||||
import { useCalendarLocale } from '../../lib/calendar-locale';
|
import { useCalendarLocale } from '../../lib/calendar-locale';
|
||||||
import { dayNumberLabel } from '../../lib/calendar-system';
|
import { dayNumberLabel } from '../../lib/calendar-system';
|
||||||
@@ -68,6 +68,7 @@ function WeekViewInner({
|
|||||||
currentUserEmails,
|
currentUserEmails,
|
||||||
}: WeekViewProps) {
|
}: WeekViewProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
// "Light event colors" (webmail calendarTintedEvents).
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
@@ -213,7 +214,7 @@ function WeekViewInner({
|
|||||||
key={`${segment.event.id}:${segment.startIndex}:${segment.row}`}
|
key={`${segment.event.id}:${segment.startIndex}:${segment.row}`}
|
||||||
title={segment.event.title || t('calendar.events.no_title', '(No title)')}
|
title={segment.event.title || t('calendar.events.no_title', '(No title)')}
|
||||||
colors={eventBlockColors(
|
colors={eventBlockColors(
|
||||||
getEventColor(segment.event, calendars),
|
paint(getEventColor(segment.event, calendars)),
|
||||||
isInactiveEvent(segment.event, currentUserEmails),
|
isInactiveEvent(segment.event, currentUserEmails),
|
||||||
c,
|
c,
|
||||||
tinted,
|
tinted,
|
||||||
@@ -289,7 +290,7 @@ function WeekViewInner({
|
|||||||
? minutesToTimeLabel(startMinutes, timeFormat)
|
? minutesToTimeLabel(startMinutes, timeFormat)
|
||||||
: null}
|
: null}
|
||||||
colors={eventBlockColors(
|
colors={eventBlockColors(
|
||||||
getEventColor(event, calendars),
|
paint(getEventColor(event, calendars)),
|
||||||
isInactiveEvent(event, currentUserEmails),
|
isInactiveEvent(event, currentUserEmails),
|
||||||
c,
|
c,
|
||||||
tinted,
|
tinted,
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { cvdColor } from '../../../lib/cvd-palette';
|
||||||
|
|
||||||
|
// Checked on the source: the suite has no React Native renderer. Every place
|
||||||
|
// that draws a calendar's colour runs it through useCvdColor() first, so the
|
||||||
|
// colour-blind-safe setting recolours calendars as its description promises
|
||||||
|
// (webmail cf4d02b49). The colour pickers keep the raw colours.
|
||||||
|
const src = path.resolve(__dirname, '../../..');
|
||||||
|
const DRAWERS = [
|
||||||
|
'components/calendar/EventCard.tsx',
|
||||||
|
'components/calendar/EventDetailSheet.tsx',
|
||||||
|
'components/calendar/MonthView.tsx',
|
||||||
|
'components/calendar/WeekView.tsx',
|
||||||
|
'components/calendar/TimeGridScrollView.tsx',
|
||||||
|
'components/calendar/CalendarSearchSheet.tsx',
|
||||||
|
'components/calendar/CalendarSidebarDrawer.tsx',
|
||||||
|
'components/calendar/EventModal.tsx',
|
||||||
|
'components/calendar/TasksSheet.tsx',
|
||||||
|
'components/calendar/ICalImportSheet.tsx',
|
||||||
|
'components/contacts/ContactActivity.tsx',
|
||||||
|
'components/email/CalendarInvitationBanner.tsx',
|
||||||
|
'components/email/structured-mail/booking-calendar-actions.tsx',
|
||||||
|
];
|
||||||
|
|
||||||
|
function colourCalls(source: string): string[] {
|
||||||
|
return source
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.filter((line) => !/^\s*import\b/.test(line) && /\bget(Event|Calendar)Color\(/.test(line))
|
||||||
|
.map((line) => line.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('calendar colours in the colour-blind-safe palette', () => {
|
||||||
|
it.each(DRAWERS)('%s paints every calendar colour it draws', (file) => {
|
||||||
|
const source = fs.readFileSync(path.join(src, file), 'utf8');
|
||||||
|
expect(source).toMatch(/const paint = useCvdColor\(\);/);
|
||||||
|
const calls = colourCalls(source);
|
||||||
|
expect(calls.length).toBeGreaterThan(0);
|
||||||
|
for (const line of calls) {
|
||||||
|
expect(line).toMatch(/paint\(get(Event|Calendar)Color\(/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the raw colour in the calendar editor, which is a colour picker', () => {
|
||||||
|
const source = fs.readFileSync(path.join(src, 'components/calendar/CalendarEditSheet.tsx'), 'utf8');
|
||||||
|
expect(source).not.toMatch(/useCvdColor/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('paints a subscription swatch through the hook', () => {
|
||||||
|
const source = fs.readFileSync(path.join(src, 'components/calendar/ICalSubscriptionSheet.tsx'), 'utf8');
|
||||||
|
expect(source).toMatch(/paint\(sub\.color\)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps a calendar green to the palette green only when the setting is on', () => {
|
||||||
|
expect(cvdColor('#22c55e', false)).toBe('#22c55e');
|
||||||
|
expect(cvdColor('#22c55e', true).toLowerCase()).not.toBe('#22c55e');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -17,7 +17,7 @@ import { singleLine } from '../../lib/single-line';
|
|||||||
import { prefetchMessage } from '../../lib/email-detail-cache';
|
import { prefetchMessage } from '../../lib/email-detail-cache';
|
||||||
import SenderAvatar from '../SenderAvatar';
|
import SenderAvatar from '../SenderAvatar';
|
||||||
import { radius, spacing, typography, componentSizes, type ThemePalette } from '../../theme/tokens';
|
import { radius, spacing, typography, componentSizes, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
|
|
||||||
const EMAIL_LIMIT = 5;
|
const EMAIL_LIMIT = 5;
|
||||||
const EVENT_LIMIT = 5;
|
const EVENT_LIMIT = 5;
|
||||||
@@ -92,6 +92,7 @@ interface Props {
|
|||||||
|
|
||||||
export function ContactActivity({ contact }: Props) {
|
export function ContactActivity({ contact }: Props) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const navigation = useNavigation<Nav>();
|
const navigation = useNavigation<Nav>();
|
||||||
const t = useLocaleStore((s) => s.t);
|
const t = useLocaleStore((s) => s.t);
|
||||||
@@ -254,7 +255,7 @@ export function ContactActivity({ contact }: Props) {
|
|||||||
upcomingEvents.map((event) => (
|
upcomingEvents.map((event) => (
|
||||||
<View key={event.id} style={styles.eventRow}>
|
<View key={event.id} style={styles.eventRow}>
|
||||||
<View
|
<View
|
||||||
style={[styles.eventDot, { backgroundColor: getEventColor(event, calendars) }]}
|
style={[styles.eventDot, { backgroundColor: paint(getEventColor(event, calendars)) }]}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.eventTime}>{formatEventTime(event, t)}</Text>
|
<Text style={styles.eventTime}>{formatEventTime(event, t)}</Text>
|
||||||
<View style={styles.eventInfo}>
|
<View style={styles.eventInfo}>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { format, isSameDay } from 'date-fns';
|
import { format, isSameDay } from 'date-fns';
|
||||||
import type { Calendar, Email, CalendarEvent } from '../../api/types';
|
import type { Calendar, Email, CalendarEvent } from '../../api/types';
|
||||||
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors, useCvdColor } from '../../theme/colors';
|
||||||
import { fetchCalendarBlobText, findEventsByUid, parseCalendarBlob } from '../../api/calendar';
|
import { fetchCalendarBlobText, findEventsByUid, parseCalendarBlob } from '../../api/calendar';
|
||||||
import { useCalendarStore } from '../../stores/calendar-store';
|
import { useCalendarStore } from '../../stores/calendar-store';
|
||||||
import { useSettingsStore } from '../../stores/settings-store';
|
import { useSettingsStore } from '../../stores/settings-store';
|
||||||
@@ -72,6 +72,7 @@ function trustReasonText(
|
|||||||
|
|
||||||
export function CalendarInvitationBanner({ email, jmapAccountId }: Props) {
|
export function CalendarInvitationBanner({ email, jmapAccountId }: Props) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const t = useLocaleStore((s) => s.t);
|
const t = useLocaleStore((s) => s.t);
|
||||||
const locale = useLocaleStore((s) => s.locale);
|
const locale = useLocaleStore((s) => s.locale);
|
||||||
@@ -390,7 +391,7 @@ export function CalendarInvitationBanner({ email, jmapAccountId }: Props) {
|
|||||||
{state !== 'done' && !existing && targetCalendar && candidates.length > 1 && (
|
{state !== 'done' && !existing && targetCalendar && candidates.length > 1 && (
|
||||||
<View>
|
<View>
|
||||||
<Pressable style={styles.calendarPicker} onPress={() => setPickerOpen((v) => !v)}>
|
<Pressable style={styles.calendarPicker} onPress={() => setPickerOpen((v) => !v)}>
|
||||||
<View style={[styles.calendarSwatch, { backgroundColor: getCalendarColor(targetCalendar) }]} />
|
<View style={[styles.calendarSwatch, { backgroundColor: paint(getCalendarColor(targetCalendar)) }]} />
|
||||||
<Text style={styles.calendarPickerText} numberOfLines={1}>{targetCalendar.name}</Text>
|
<Text style={styles.calendarPickerText} numberOfLines={1}>{targetCalendar.name}</Text>
|
||||||
<ChevronDown size={14} color={c.textMuted} />
|
<ChevronDown size={14} color={c.textMuted} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
@@ -402,7 +403,7 @@ export function CalendarInvitationBanner({ email, jmapAccountId }: Props) {
|
|||||||
style={[styles.calendarRow, cal.id === targetCalendar.id && styles.calendarRowActive]}
|
style={[styles.calendarRow, cal.id === targetCalendar.id && styles.calendarRowActive]}
|
||||||
onPress={() => { setCalendarId(cal.id); setPickerOpen(false); }}
|
onPress={() => { setCalendarId(cal.id); setPickerOpen(false); }}
|
||||||
>
|
>
|
||||||
<View style={[styles.calendarSwatch, { backgroundColor: getCalendarColor(cal) }]} />
|
<View style={[styles.calendarSwatch, { backgroundColor: paint(getCalendarColor(cal)) }]} />
|
||||||
<Text style={styles.calendarPickerText} numberOfLines={1}>{cal.name}</Text>
|
<Text style={styles.calendarPickerText} numberOfLines={1}>{cal.name}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { getCalendarColor } from '../../../lib/calendar-utils';
|
|||||||
import type { StructuredCard } from '../../../lib/structured-mail';
|
import type { StructuredCard } from '../../../lib/structured-mail';
|
||||||
import type { RootStackParamList } from '../../../navigation/types';
|
import type { RootStackParamList } from '../../../navigation/types';
|
||||||
import { useLocaleStore } from '../../../stores/locale-store';
|
import { useLocaleStore } from '../../../stores/locale-store';
|
||||||
import { useColors } from '../../../theme/colors';
|
import { useColors, useCvdColor } from '../../../theme/colors';
|
||||||
import { radius, spacing, typography, type ThemePalette } from '../../../theme/tokens';
|
import { radius, spacing, typography, type ThemePalette } from '../../../theme/tokens';
|
||||||
import { ArrowRight, CalendarCheck, CalendarDays, CalendarPlus, CalendarX, ChevronDown } from '../../icons';
|
import { ArrowRight, CalendarCheck, CalendarDays, CalendarPlus, CalendarX, ChevronDown } from '../../icons';
|
||||||
import { useBookingCalendar } from './use-booking-calendar';
|
import { useBookingCalendar } from './use-booking-calendar';
|
||||||
@@ -27,6 +27,7 @@ type Nav = NativeStackNavigationProp<RootStackParamList>;
|
|||||||
*/
|
*/
|
||||||
export function BookingCalendarActions({ booking, email }: { booking: StructuredCard; email: Pick<Email, 'from' | 'subject'> }) {
|
export function BookingCalendarActions({ booking, email }: { booking: StructuredCard; email: Pick<Email, 'from' | 'subject'> }) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
const paint = useCvdColor();
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const t = useLocaleStore((s) => s.t);
|
const t = useLocaleStore((s) => s.t);
|
||||||
const navigation = useNavigation<Nav>();
|
const navigation = useNavigation<Nav>();
|
||||||
@@ -144,7 +145,7 @@ export function BookingCalendarActions({ booking, email }: { booking: Structured
|
|||||||
accessibilityLabel={cal.name}
|
accessibilityLabel={cal.name}
|
||||||
testID="booking-calendar-choice"
|
testID="booking-calendar-choice"
|
||||||
>
|
>
|
||||||
<View style={[styles.swatch, { backgroundColor: getCalendarColor(cal) }]} />
|
<View style={[styles.swatch, { backgroundColor: paint(getCalendarColor(cal)) }]} />
|
||||||
<Text style={styles.pickerText} numberOfLines={1}>{cal.name}</Text>
|
<Text style={styles.pickerText} numberOfLines={1}>{cal.name}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user