diff --git a/src/calendar-app/calendar/gui/CalendarGuiUtils.ts b/src/calendar-app/calendar/gui/CalendarGuiUtils.ts index 55053f645b4e..a08fec1067cc 100644 --- a/src/calendar-app/calendar/gui/CalendarGuiUtils.ts +++ b/src/calendar-app/calendar/gui/CalendarGuiUtils.ts @@ -1077,7 +1077,7 @@ export function getDisplayEventTitle(title: string): string { export type ColorString = string export function generateRandomColor(): ColorString { - const model = new ColorPickerModel(!isColorLight(theme.content_bg)) + const model = new ColorPickerModel(!isColorLight(theme.surface)) return hslToHex(model.getColor(Math.floor(Math.random() * MAX_HUE_ANGLE), 2)) } diff --git a/src/calendar-app/calendar/gui/day-selector/DaySelector.ts b/src/calendar-app/calendar/gui/day-selector/DaySelector.ts index 1bfb7c85524d..8629bc5fb871 100644 --- a/src/calendar-app/calendar/gui/day-selector/DaySelector.ts +++ b/src/calendar-app/calendar/gui/day-selector/DaySelector.ts @@ -197,7 +197,7 @@ export class DaySelector implements Component { if (highlight) { style = { - backgroundColor: hexToRGBAString(theme.content_accent, 0.2), + backgroundColor: hexToRGBAString(theme.primary, 0.2), height: px(styles.isDesktopLayout() ? 19 : 25), borderRadius: px(styles.isDesktopLayout() ? 6 : 25), width: `calc(100% - ${px(size.hpad_small)})`, diff --git a/src/calendar-app/calendar/gui/eventeditor-view/AttendeeListEditor.ts b/src/calendar-app/calendar/gui/eventeditor-view/AttendeeListEditor.ts index f85a21540c10..43f8c9c408cf 100644 --- a/src/calendar-app/calendar/gui/eventeditor-view/AttendeeListEditor.ts +++ b/src/calendar-app/calendar/gui/eventeditor-view/AttendeeListEditor.ts @@ -53,7 +53,7 @@ export class AttendeeListEditor implements Component { m(".flex-grow.flex.flex-column.gap-vpad.pb.pt.fit-height", { style: { width: px(attrs.width) } }, [ this.renderOrganizer(attrs.model, organizer), m(".flex.flex-column.gap-vpad-s", [ - m("small.uppercase.b.text-ellipsis", { style: { color: theme.navigation_button } }, lang.get("guests_label")), + m("small.uppercase.b.text-ellipsis", { style: { color: theme.on_surface_variant } }, lang.get("guests_label")), whoModel.canModifyGuests ? this.renderGuestsInput(whoModel, attrs.logins, attrs.recipientsSearch) : null, this.renderSendUpdateCheckbox(attrs.model.editModels.whoModel), this.renderGuestList(attrs, organizer), @@ -102,7 +102,7 @@ export class AttendeeListEditor implements Component { m(IconMessageBox, { message: "noEntries_msg", icon: Icons.People, - color: theme.list_message_bg, + color: theme.on_surface_fade, }), ]), ) @@ -181,7 +181,7 @@ export class AttendeeListEditor implements Component { "button.items-center.flex-grow.state-bg.button-content.dropdown-button.pt-s.pb-s.button-min-height", { class: option.selectable === false ? `no-hover` : "", - style: { color: option.value === status ? theme.content_button_selected : undefined }, + style: { color: option.value === status ? theme.primary : undefined }, }, option.name, ), @@ -219,7 +219,7 @@ export class AttendeeListEditor implements Component { const selected = options.find((option) => option.address === address) ?? options[0] return m(".flex.col", [ - m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.navigation_button } }, lang.get("organizer_label")), + m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.on_surface_variant } }, lang.get("organizer_label")), m(Card, { style: { padding: `0` } }, [ m(".flex.flex-column", [ m(".flex.pl-vpad-s.pr-vpad-s", [ @@ -237,7 +237,7 @@ export class AttendeeListEditor implements Component { renderOption: (option) => m( "button.items-center.flex-grow.state-bg.button-content.dropdown-button.pt-s.pb-s.button-min-height", - { style: { color: selected.address === option.address ? theme.content_button_selected : undefined } }, + { style: { color: selected.address === option.address ? theme.primary : undefined } }, option.address, ), renderDisplay: (option) => m("", option.name ? `${option.name} <${option.address}>` : option.address), @@ -270,7 +270,7 @@ export class AttendeeListEditor implements Component { : null, ]), isMe && model.operation !== CalendarOperation.EditThis - ? [m(Divider, { color: theme.button_bubble_bg }), this.renderAttendeeStatus(whoModel, organizer)] + ? [m(Divider, { color: theme.outline_variant }), this.renderAttendeeStatus(whoModel, organizer)] : null, ]), ]), @@ -341,7 +341,7 @@ export class AttendeeListEditor implements Component { }, }, m(Divider, { - color: theme.button_bubble_bg, + color: theme.outline_variant, }), ), this.renderPasswordField(address, password, strength ?? 0, whoModel), @@ -387,7 +387,7 @@ export class AttendeeListEditor implements Component { size: IconSize.Large, class: "mr-s", style: { - fill: theme.content_fg, + fill: theme.on_surface, }, }) } diff --git a/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditDialog.ts b/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditDialog.ts index 5e7ee88dbc35..6e2f8b9346cb 100644 --- a/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditDialog.ts +++ b/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditDialog.ts @@ -146,7 +146,7 @@ export class EventEditorDialog { }, { height: "100%", - "background-color": theme.navigation_bg, + "background-color": theme.surface_container, }, ) .addShortcut({ diff --git a/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditView.ts b/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditView.ts index 432e7070c518..6ae19f646a46 100644 --- a/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditView.ts +++ b/src/calendar-app/calendar/gui/eventeditor-view/CalendarEventEditView.ts @@ -345,7 +345,7 @@ export class CalendarEventEditView implements Component { m(Icon, { icon: Icons.Time, style: { - fill: theme.content_fg, + fill: theme.on_surface, }, title: lang.get("timeSection_label"), size: IconSize.Medium, @@ -55,7 +55,7 @@ export class EventTimeEditor implements Component { ), ]), m(".flex.col.full-width.flex-grow.gap-vpad-s", { style: { paddingLeft: px(size.icon_size_large + size.vpad_small) } }, [ - m(Divider, { color: theme.button_bubble_bg }), + m(Divider, { color: theme.outline_variant }), m(".time-selection-grid.pr-vpad-s", [ m("", lang.get("dateFrom_label")), m( diff --git a/src/calendar-app/calendar/gui/eventeditor-view/RepeatRuleEditor.ts b/src/calendar-app/calendar/gui/eventeditor-view/RepeatRuleEditor.ts index 6342f7b5df6f..2d16dc176556 100644 --- a/src/calendar-app/calendar/gui/eventeditor-view/RepeatRuleEditor.ts +++ b/src/calendar-app/calendar/gui/eventeditor-view/RepeatRuleEditor.ts @@ -96,7 +96,7 @@ export class RepeatRuleEditor implements Component { [ this.hasUnsupportedRules ? this.renderUnsupportedAdvancedRulesWarning() : null, m(".flex.col", [ - m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.navigation_button } }, lang.getTranslationText("frequency_title")), + m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.on_surface_variant } }, lang.getTranslationText("frequency_title")), m( Card, { @@ -137,7 +137,7 @@ export class RepeatRuleEditor implements Component { } return m(".flex.col", [ - m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.navigation_button } }, lang.get("calendarRepeatStopCondition_label")), + m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.on_surface_variant } }, lang.get("calendarRepeatStopCondition_label")), m( Card, { @@ -169,7 +169,7 @@ export class RepeatRuleEditor implements Component { } return m(".flex.col", [ - m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.navigation_button } }, lang.get("interval_title")), + m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.on_surface_variant } }, lang.get("interval_title")), m( Card, { @@ -188,7 +188,7 @@ export class RepeatRuleEditor implements Component { this.renderIntervalPicker(attrs), this.repeatRuleType === RepeatPeriod.WEEKLY ? [ - m(Divider, { color: theme.button_bubble_bg }), + m(Divider, { color: theme.outline_variant }), m(WeekdaySelector, { items: this.weekdayItems, selectedDays: this.byDayRules?.weekdays ?? [], @@ -197,7 +197,7 @@ export class RepeatRuleEditor implements Component { ] : this.repeatRuleType === RepeatPeriod.MONTHLY ? [ - m(Divider, { color: theme.button_bubble_bg }), + m(Divider, { color: theme.outline_variant }), m(WeekRepetitionSelector, { repetitionOptionsAndWeekday: createRepetitionValuesForWeekday( DateTime.fromJSDate(attrs.model.startDate).weekday, diff --git a/src/calendar-app/calendar/gui/eventpopup/ContactPreviewView.ts b/src/calendar-app/calendar/gui/eventpopup/ContactPreviewView.ts index ddc2e04782d6..5b2de87d0da6 100644 --- a/src/calendar-app/calendar/gui/eventpopup/ContactPreviewView.ts +++ b/src/calendar-app/calendar/gui/eventpopup/ContactPreviewView.ts @@ -63,7 +63,7 @@ export class ContactPreviewView implements Component { size: IconSize.Medium, style: Object.assign( { - fill: theme.content_button, + fill: theme.on_surface, display: "block", }, style, @@ -139,12 +139,12 @@ const ActionButtons = pureComponent((contact: Contact) => { }) const emailButtonColors = { - borderColor: theme.content_accent, - color: theme.content_accent, + borderColor: theme.primary, + color: theme.primary, } const phoneButtonColors = { - borderColor: theme.content_button, - color: theme.content_button, + borderColor: theme.on_surface_variant, + color: theme.on_surface_variant, } const singleEmailAdress = contact.mailAddresses.length === 1 diff --git a/src/calendar-app/calendar/gui/eventpopup/EventPreviewView.ts b/src/calendar-app/calendar/gui/eventpopup/EventPreviewView.ts index 899d86f6f475..7a338747e0f1 100644 --- a/src/calendar-app/calendar/gui/eventpopup/EventPreviewView.ts +++ b/src/calendar-app/calendar/gui/eventpopup/EventPreviewView.ts @@ -46,13 +46,13 @@ export type EventPreviewViewAttrs = { * a mail to the organizer. */ export const ReplyButtons = pureComponent((participation: NonNullable) => { const colors = { - borderColor: theme.content_button, - color: theme.content_fg, + borderColor: theme.on_surface_variant, + color: theme.on_surface, } const highlightColors = { - borderColor: theme.content_accent, - color: theme.content_accent, + borderColor: theme.primary, + color: theme.primary, } const makeStatusButtonAttrs = (status: CalendarAttendeeStatus, text: TranslationKey): BannerButtonAttrs => @@ -133,7 +133,7 @@ export class EventPreviewView implements Component { size: IconSize.Medium, style: Object.assign( { - fill: theme.content_button, + fill: theme.on_surface_variant, display: "block", }, style, @@ -210,7 +210,7 @@ export class EventPreviewView implements Component { m(Icon, { icon: iconForAttendeeStatus[status], style: { - fill: theme.content_fg, + fill: theme.on_surface, }, class: "mr-s", }), diff --git a/src/calendar-app/calendar/gui/pickers/DatePicker.ts b/src/calendar-app/calendar/gui/pickers/DatePicker.ts index 83abf9b5e267..1edcab3d1b70 100644 --- a/src/calendar-app/calendar/gui/pickers/DatePicker.ts +++ b/src/calendar-app/calendar/gui/pickers/DatePicker.ts @@ -92,7 +92,7 @@ export class DatePicker implements Component { type: TextFieldType.Date, style: { zIndex: 1, - border: `2px solid ${theme.content_message_bg}`, + border: `2px solid ${theme.outline}`, width: "auto", height: "auto", padding: 0, @@ -676,7 +676,7 @@ export class VisualDatePicker implements Component { height: size, width: size, lineHeight: size, - color: theme.navigation_menu_icon, + color: theme.on_surface_variant, }, }, wd, diff --git a/src/calendar-app/calendar/gui/pickers/GuestPicker.ts b/src/calendar-app/calendar/gui/pickers/GuestPicker.ts index 026854a03a38..ef33b279926d 100644 --- a/src/calendar-app/calendar/gui/pickers/GuestPicker.ts +++ b/src/calendar-app/calendar/gui/pickers/GuestPicker.ts @@ -71,7 +71,7 @@ export class GuestPicker implements ClassComponent { : m(Icon, { icon: Icons.People, style: { - fill: theme.content_fg, + fill: theme.on_surface, "aria-describedby": lang.get("contactListName_label"), }, }) diff --git a/src/calendar-app/calendar/gui/pickers/TimePicker.ts b/src/calendar-app/calendar/gui/pickers/TimePicker.ts index 5d996c07a443..b3c4cf5aacdf 100644 --- a/src/calendar-app/calendar/gui/pickers/TimePicker.ts +++ b/src/calendar-app/calendar/gui/pickers/TimePicker.ts @@ -85,7 +85,7 @@ export class TimePicker implements Component { type: TextFieldType.Time, style: { zIndex: 1, - border: `2px solid ${theme.content_message_bg}`, + border: `2px solid ${theme.outline}`, width: "auto", height: "auto", appearance: "none", diff --git a/src/calendar-app/calendar/search/view/CalendarSearchListView.ts b/src/calendar-app/calendar/search/view/CalendarSearchListView.ts index 520b9251e902..1d02225f3692 100644 --- a/src/calendar-app/calendar/search/view/CalendarSearchListView.ts +++ b/src/calendar-app/calendar/search/view/CalendarSearchListView.ts @@ -46,7 +46,7 @@ export class CalendarSearchListView implements Component { return m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, desktopToolbar: () => m(DesktopListToolbar, [m(".button-height")]), mobileHeader: () => this.renderMobileListHeader(vnode.attrs.header), columnLayout: this.getResultColumnLayout(), @@ -208,7 +208,7 @@ export class CalendarSearchView extends BaseTopLevelView implements TopLevelView const selectedEvent = this.searchViewModel.getSelectedEvents()[0] return m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, desktopToolbar: () => m(DesktopViewerToolbar, []), mobileHeader: () => m(MobileHeader, { @@ -225,8 +225,8 @@ export class CalendarSearchView extends BaseTopLevelView implements TopLevelView ? m(ColumnEmptyMessageBox, { message: "noEventSelect_msg", icon: BootIcons.Calendar, - color: theme.content_message_bg, - backgroundColor: theme.navigation_bg, + color: theme.on_surface_fade, + backgroundColor: theme.surface_container, }) : !this.getSanitizedPreviewData(selectedEvent).isLoaded() ? null diff --git a/src/calendar-app/calendar/settings/CalendarSettingsView.ts b/src/calendar-app/calendar/settings/CalendarSettingsView.ts index 21e286cc94e4..a098584a60d7 100644 --- a/src/calendar-app/calendar/settings/CalendarSettingsView.ts +++ b/src/calendar-app/calendar/settings/CalendarSettingsView.ts @@ -121,7 +121,7 @@ export class CalendarSettingsView extends BaseTopLevelView implements TopLevelVi // are concealed), but there's still room for improvement for scrollbars view: () => m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, classes: this.isTabletView() ? "pr-m pl-vpad-s" : "", columnLayout: m( ".mlr-safe-inset.fill-absolute.content-bg.border-radius-top-left-m.border-radius-top-right-m", @@ -167,7 +167,7 @@ export class CalendarSettingsView extends BaseTopLevelView implements TopLevelVi { view: () => { return m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, columnLayout: m(".flex.flex-grow.col.full-height", [ this.renderSettingsNavigation(this.userFolders, "userSettings_label"), this.renderLoggedInNavigationLinks(), @@ -213,8 +213,8 @@ export class CalendarSettingsView extends BaseTopLevelView implements TopLevelVi class: "flash flex justify-center center-vertically pt-s pb-s plr border-radius", style: { marginInline: "auto", - border: `1px solid ${theme.navigation_button}`, - color: theme.navigation_button, + border: `1px solid ${theme.outline}`, + color: theme.on_surface_variant, }, label: "supportMenu_label", text: m(".pl-s", lang.getTranslation("supportMenu_label").text), @@ -223,7 +223,7 @@ export class CalendarSettingsView extends BaseTopLevelView implements TopLevelVi size: IconSize.Medium, class: "center-h", container: "div", - style: { fill: theme.navigation_button }, + style: { fill: theme.on_surface_variant }, }), onclick: () => { const triggerStage = getSupportUsageTestStage(0) @@ -375,7 +375,7 @@ export class CalendarSettingsView extends BaseTopLevelView implements TopLevelVi class: styles.isSingleColumnLayout() ? "pr-m" : "pr-vpad-s", }, [ - m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.navigation_button } }, lang.getTranslationText(title)), + m("small.uppercase.pb-s.b.text-ellipsis", { style: { color: theme.on_surface_variant } }, lang.getTranslationText(title)), m( ".flex.col.border-radius-m.list-bg", folders @@ -527,7 +527,7 @@ export class CalendarSettingsView extends BaseTopLevelView implements TopLevelVi ".b", { style: { - color: theme.navigation_button_selected, + color: theme.primary, }, }, label, diff --git a/src/calendar-app/calendar/view/CalendarAgendaItemView.ts b/src/calendar-app/calendar/view/CalendarAgendaItemView.ts index f918415e68a1..09ce03268ae7 100644 --- a/src/calendar-app/calendar/view/CalendarAgendaItemView.ts +++ b/src/calendar-app/calendar/view/CalendarAgendaItemView.ts @@ -62,7 +62,7 @@ export class CalendarAgendaItemView implements Component { return m(ColumnEmptyMessageBox, { icon: BootIcons.Calendar, message: "noEntries_msg", - color: theme.list_message_bg, + color: theme.on_surface_fade, }) } else { return m(".flex.mb-s.col", this.renderEventsForDay(events, getTimeZone(), attrs.selectedDate, attrs)) @@ -191,7 +191,7 @@ export class CalendarAgendaView implements Component { return m(ColumnEmptyMessageBox, { icon: BootIcons.Calendar, message: "noEntries_msg", - color: theme.list_message_bg, + color: theme.on_surface_fade, bottomContent: !client.isCalendarApp() ? m(MainCreateButton, { label: "newEvent_action", @@ -263,7 +263,7 @@ export class CalendarAgendaView implements Component { m(ColumnEmptyMessageBox, { icon: BootIcons.Calendar, message: "noEventSelect_msg", - color: theme.list_message_bg, + color: theme.on_surface_fade, }), ) : this.renderEventPreview(attrs), diff --git a/src/calendar-app/calendar/view/CalendarMobileHeader.ts b/src/calendar-app/calendar/view/CalendarMobileHeader.ts index 3f40a28031b8..fdd32f914ee9 100644 --- a/src/calendar-app/calendar/view/CalendarMobileHeader.ts +++ b/src/calendar-app/calendar/view/CalendarMobileHeader.ts @@ -53,7 +53,7 @@ export class CalendarMobileHeader implements Component {}, diff --git a/src/calendar-app/calendar/view/CalendarView.ts b/src/calendar-app/calendar/view/CalendarView.ts index 3a52417e9963..1aafe7518911 100644 --- a/src/calendar-app/calendar/view/CalendarView.ts +++ b/src/calendar-app/calendar/view/CalendarView.ts @@ -270,7 +270,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView this.renderDesktopToolbar(), mobileHeader: () => this.renderMobileHeader(attrs.header), columnLayout: m(CalendarMonthView, { @@ -297,7 +297,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView this.renderDesktopToolbar(), mobileHeader: () => this.renderMobileHeader(attrs.header), columnLayout: m(MultiDayCalendarView, { @@ -330,7 +330,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView this.renderDesktopToolbar(), mobileHeader: () => this.renderMobileHeader(attrs.header), columnLayout: m(MultiDayCalendarView, { @@ -364,7 +364,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView this.renderDesktopToolbar(), mobileHeader: () => this.renderMobileHeader(attrs.header), columnLayout: m(MultiDayCalendarView, { @@ -398,7 +398,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView this.renderDesktopToolbar(), mobileHeader: () => this.renderMobileHeader(attrs.header), columnLayout: m(CalendarAgendaView, { @@ -568,7 +568,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView header, mobileHeader: () => header, columnLayout: children, @@ -1096,7 +1096,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView { svgParameters: { date: new Date().getDate().toString() }, icon: Icons.Today, style: { - fill: theme.content_button, + fill: theme.on_surface_variant, }, }), class: "icon-button state-bg", diff --git a/src/common/desktop/ApplicationWindow.ts b/src/common/desktop/ApplicationWindow.ts index ebeb55213021..efd37cb1eefe 100644 --- a/src/common/desktop/ApplicationWindow.ts +++ b/src/common/desktop/ApplicationWindow.ts @@ -258,7 +258,7 @@ export class ApplicationWindow { const theme = await this.themeFacade.getCurrentThemeWithFallback() if (theme) { - this._browserWindow.setBackgroundColor(theme.navigation_bg) + this._browserWindow.setBackgroundColor(theme.surface_container) } } diff --git a/src/common/desktop/DesktopThemeFacade.ts b/src/common/desktop/DesktopThemeFacade.ts index db0ef20cf3fa..6efe0e55d80a 100644 --- a/src/common/desktop/DesktopThemeFacade.ts +++ b/src/common/desktop/DesktopThemeFacade.ts @@ -7,9 +7,8 @@ import electron from "electron" const LIGHT_FALLBACK_THEME: Partial = { themeId: "light-fallback", - content_bg: "#ffffff", - header_bg: "#ffffff", - navigation_bg: "#f6f6f6", + surface: "#ffffff", + surface_container: "#f6f6f6", } /** diff --git a/src/common/gui/AttachmentBubble.ts b/src/common/gui/AttachmentBubble.ts index 62ecfb3782b2..9c618b246944 100644 --- a/src/common/gui/AttachmentBubble.ts +++ b/src/common/gui/AttachmentBubble.ts @@ -198,7 +198,7 @@ export class AttachmentDetailsPopup implements ModalComponent { icon: getAttachmentIcon(type), class: "pr-s flex items-center", style: { - fill: theme.button_bubble_fg, + fill: theme.on_surface, "background-color": "initial", minHeight: px(bubbleButtonHeight()), }, diff --git a/src/common/gui/CompletenessIndicator.ts b/src/common/gui/CompletenessIndicator.ts index d0a5b6b173ed..9d47118d2e39 100644 --- a/src/common/gui/CompletenessIndicator.ts +++ b/src/common/gui/CompletenessIndicator.ts @@ -14,14 +14,14 @@ export class CompletenessIndicator implements Component { if (attrs.button == null || target.scrollTop === 0) { target.style.borderTop = "" } else { - target.style.borderTop = `1px solid ${theme.content_border}` + target.style.borderTop = `1px solid ${theme.outline}` } }, }, diff --git a/src/common/gui/MailRecipientsTextField.ts b/src/common/gui/MailRecipientsTextField.ts index f70af6d133d3..46c88cf41876 100644 --- a/src/common/gui/MailRecipientsTextField.ts +++ b/src/common/gui/MailRecipientsTextField.ts @@ -80,7 +80,7 @@ export class MailRecipientsTextField implements ClassComponent { onclick: vnode.attrs.click, class: `full-width border-radius-big center b flash ${vnode.attrs.class}`, style: { - border: `2px solid ${theme.content_accent}`, + border: `2px solid ${theme.primary}`, // matching toolbar height: px(size.button_height + size.vpad_xs * 2), - color: theme.content_accent, + color: theme.primary, }, } satisfies BaseButtonAttrs) } diff --git a/src/common/gui/MobileHeader.ts b/src/common/gui/MobileHeader.ts index d5a3f10813ce..18475e36b3c6 100644 --- a/src/common/gui/MobileHeader.ts +++ b/src/common/gui/MobileHeader.ts @@ -109,7 +109,7 @@ export const MobileHeaderMenuButton = pureComponent(({ newsModel, backAction }: right: px(5), }, color: "white", - background: theme.list_accent_fg, + background: theme.primary, }), ]) }) diff --git a/src/common/gui/PasswordInput.ts b/src/common/gui/PasswordInput.ts index a5cd82dc927a..5d58541842e3 100644 --- a/src/common/gui/PasswordInput.ts +++ b/src/common/gui/PasswordInput.ts @@ -27,9 +27,9 @@ export class PasswordInput implements ClassComponent { style: { width: px(size.icon_size_medium), height: px(size.icon_size_medium), - border: `1px solid ${theme.content_button}`, + border: `1px solid ${theme.outline}`, borderRadius: "50%", - background: `conic-gradient(from .25turn, ${theme.content_button} ${scaleToVisualPasswordStrength( + background: `conic-gradient(from .25turn, ${theme.on_surface} ${scaleToVisualPasswordStrength( vnode.attrs.strength, )}%, transparent 0%)`, }, diff --git a/src/common/gui/SelectableRowContainer.ts b/src/common/gui/SelectableRowContainer.ts index dc9d6a8f4beb..fb199d24dae2 100644 --- a/src/common/gui/SelectableRowContainer.ts +++ b/src/common/gui/SelectableRowContainer.ts @@ -51,7 +51,7 @@ export class SelectableRowContainer implements ClassComponent { { "data-testid": `section:${lang.getTestId(name)}`, style: { - color: theme.navigation_button, + color: theme.on_surface_variant, }, }, [ diff --git a/src/common/gui/ThemeController.ts b/src/common/gui/ThemeController.ts index 0adc9f7b1deb..194c56ddbf9d 100644 --- a/src/common/gui/ThemeController.ts +++ b/src/common/gui/ThemeController.ts @@ -5,14 +5,12 @@ import Stream from "mithril/stream" import { assertMainOrNodeBoot, isApp, isDesktop } from "../api/common/Env" import { downcast, findAndRemove, LazyLoaded, mapAndFilterNull, typedValues } from "@tutao/tutanota-utils" import m from "mithril" -import type { BaseThemeId, Theme, ThemeId, ThemePreference } from "./theme" +import { BaseThemeId, Theme, ThemeId, ThemePreference } from "./theme" import { logoDefaultGrey, themes } from "./builtinThemes" -import type { ThemeCustomizations } from "../misc/WhitelabelCustomizations" -import { getWhitelabelCustomizations } from "../misc/WhitelabelCustomizations" +import { getWhitelabelCustomizations, ThemeCustomizations } from "../misc/WhitelabelCustomizations" import { getCalendarLogoSvg, getMailLogoSvg } from "./base/Logo" import { ThemeFacade } from "../native/common/generatedipc/ThemeFacade" import { AppType } from "../misc/ClientConstants.js" -import { locator } from "../api/main/CommonLocator.js" assertMainOrNodeBoot() @@ -70,6 +68,46 @@ export class ThemeController { } } + /** + * Color token mapper from the old to the new. + * If there are colors explicitly defined for the new tokens, they will be respected and the mapped colors will be overwritten. + * Since the number of new tokens is less than the number of old tokens, it is impossible to map all colors. Thus, this is just for mitigation purposes. + * This mapper could be removed after all users who have whitelabel color customization have migrated to the new color tokens. + */ + public static mapOldToNewColorTokens(customizations: ThemeCustomizations | keyof typeof oldToNewColorTokenMap): ThemeCustomizations { + let mappedCustomizations: Record = {} + for (const [oldToken, hex] of Object.entries(customizations)) { + if (!oldToken || !hex) continue + const newToken: keyof Theme | undefined = oldToNewColorTokenMap[oldToken] + if (newToken) { + mappedCustomizations[newToken] = hex + } + mappedCustomizations[oldToken] = hex + } + return mappedCustomizations as ThemeCustomizations + } + + /** + * Color token mapper from the new to the old. + * This mapper could be removed after all users who have whitelabel color customization have migrated to the new color tokens. + */ + public static mapNewToOldColorTokens(customizations: Partial): Record { + let mappedCustomizations: Record = {} + + for (const [newToken, hex] of Object.entries(customizations)) { + if (!newToken || !hex) continue + const mappedOldTokens = newToOldColorTokenMap[newToken as keyof typeof newToOldColorTokenMap] + if (mappedOldTokens) { + for (const oldToken of mappedOldTokens) { + mappedCustomizations[oldToken] = hex + } + } + mappedCustomizations[newToken] = hex + } + + return mappedCustomizations + } + /** * Determines if the current theme is a light theme. * @@ -181,7 +219,7 @@ export class ThemeController { * Apply the custom theme, if permanent === true, then the new theme will be saved */ async applyCustomizations(customizations: ThemeCustomizations, permanent: boolean = true): Promise { - const updatedTheme = this.assembleTheme(customizations) + const updatedTheme = this.assembleTheme(ThemeController.mapOldToNewColorTokens(customizations)) // Set no logo until we sanitize it. const filledWithoutLogo = Object.assign({}, updatedTheme, { logo: "", @@ -329,3 +367,53 @@ export class WebThemeFacade implements ThemeFacade { this.mediaQuery?.addEventListener("change", listener) } } + +const oldToNewColorTokenMap: Record = { + button_bubble_bg: "secondary", + button_bubble_fg: "on_secondary", + content_bg: "surface", + content_fg: "on_surface", + content_button: "on_surface_variant", + content_button_selected: "primary", + content_button_icon: "on_primary", + content_button_icon_selected: "on_primary", + content_accent: "primary", + content_border: "outline", + content_message_bg: "on_surface_fade", + header_bg: "surface", + header_box_shadow_bg: "outline", + header_button: "on_surface_variant", + header_button_selected: "primary", + list_bg: "surface", + list_alternate_bg: "surface_container", + list_accent_fg: "primary", + list_message_bg: "on_surface_fade", + list_border: "outline_variant", + modal_bg: "scrim", + elevated_bg: "surface", + navigation_bg: "surface_container", + navigation_border: "outline_variant", + navigation_button: "on_surface_variant", + navigation_button_icon: "on_primary", + navigation_button_selected: "primary", + navigation_button_icon_selected: "on_primary", + navigation_menu_bg: "secondary", + navigation_menu_icon: "on_secondary", + error_color: "error", +} as const + +const newToOldColorTokenMap: Partial> = { + secondary: ["button_bubble_bg", "navigation_menu_bg"], + on_secondary: ["button_bubble_fg", "navigation_menu_icon"], + surface: ["content_bg", "header_bg", "list_bg", "elevated_bg"], + on_surface: ["content_fg"], + on_surface_variant: ["content_button", "header_button", "navigation_button"], + primary: ["content_accent", "content_button_selected", "header_button_selected", "list_accent_fg", "navigation_button_selected"], + on_primary: ["content_button_icon", "content_button_icon_selected", "navigation_button_icon", "navigation_button_icon_selected"], + outline: ["content_border", "header_box_shadow_bg"], + on_surface_fade: ["content_message_bg", "list_message_bg"], + surface_container: ["list_alternate_bg", "navigation_bg"], + outline_variant: ["list_border", "navigation_border"], + scrim: ["modal_bg"], + error: ["error_color"], +} as const diff --git a/src/common/gui/base/BaseSearchBar.ts b/src/common/gui/base/BaseSearchBar.ts index e5c9089b71c2..51551fa52c19 100644 --- a/src/common/gui/base/BaseSearchBar.ts +++ b/src/common/gui/base/BaseSearchBar.ts @@ -60,7 +60,7 @@ export class BaseSearchBar implements ClassComponent { icon: BootIcons.Search, size: IconSize.Medium, style: { - fill: theme.content_button, + fill: theme.on_surface_variant, }, } satisfies IconAttrs) : null, @@ -87,7 +87,7 @@ export class BaseSearchBar implements ClassComponent { icon: attrs.busy ? BootIcons.Progress : Icons.Close, class: "center-h " + (attrs.busy ? "icon-progress-search icon-progress" : ""), style: { - fill: theme.header_button, + fill: theme.on_surface_variant, }, }), onclick: () => { diff --git a/src/common/gui/base/Button.ts b/src/common/gui/base/Button.ts index bba32f32761c..d4b946d96d84 100644 --- a/src/common/gui/base/Button.ts +++ b/src/common/gui/base/Button.ts @@ -29,33 +29,33 @@ export function getColors(buttonColors: ButtonColor | null | undefined): { switch (buttonColors) { case ButtonColor.Nav: return { - button: theme.navigation_button, - border: theme.navigation_bg, + button: theme.on_surface_variant, + border: theme.surface_container, } case ButtonColor.DrawerNav: return { - button: theme.content_button, + button: theme.on_surface_variant, border: getElevatedBackground(), } case ButtonColor.Elevated: return { - button: theme.content_button, + button: theme.on_surface_variant, border: getElevatedBackground(), } case ButtonColor.Fab: return { - button: theme.content_button_icon_selected, + button: theme.surface, border: getElevatedBackground(), } case ButtonColor.Content: default: return { - button: theme.content_button, - border: theme.content_bg, + button: theme.on_surface_variant, + border: theme.surface, } } } diff --git a/src/common/gui/base/Checkbox.ts b/src/common/gui/base/Checkbox.ts index aa06669b4b37..69b1e72cc396 100644 --- a/src/common/gui/base/Checkbox.ts +++ b/src/common/gui/base/Checkbox.ts @@ -52,7 +52,7 @@ export class Checkbox implements Component { class: getOperatingClasses(a.disabled, "click"), style: { cursor: a.disabled ? "default" : "pointer", - "background-color": theme.content_accent, + "background-color": theme.primary, "mask-image": `url("${a.checked ? Checkbox.checkedIcon : Checkbox.uncheckedIcon}")`, }, disabled: a.disabled, diff --git a/src/common/gui/base/Expander.ts b/src/common/gui/base/Expander.ts index 2a14a29e14d6..8f880c7919b7 100644 --- a/src/common/gui/base/Expander.ts +++ b/src/common/gui/base/Expander.ts @@ -54,7 +54,7 @@ export class ExpanderButton implements Component { ? m(Icon, { icon: Icons.Warning, style: { - fill: a.color ? a.color : theme.content_button, + fill: a.color ? a.color : theme.on_surface_variant, }, }) : null, @@ -62,7 +62,7 @@ export class ExpanderButton implements Component { `${a.isBig ? "span" : "small"}`, { style: { - color: a.color || theme.content_button, + color: a.color || theme.on_surface_variant, }, }, a.isUnformattedLabel ? label : label.toUpperCase(), @@ -72,7 +72,7 @@ export class ExpanderButton implements Component { class: "flex-center items-center", size: a.isBig ? IconSize.Medium : IconSize.Normal, style: { - fill: a.color ? a.color : theme.content_button, + fill: a.color ? a.color : theme.on_surface_variant, "margin-right": px(-4), // icon is has 4px whitespace to the right, transform: `rotateZ(${a.expanded ? 180 : 0}deg)`, diff --git a/src/common/gui/base/FilterChip.ts b/src/common/gui/base/FilterChip.ts index 4ac44242363b..81db20dad9b5 100644 --- a/src/common/gui/base/FilterChip.ts +++ b/src/common/gui/base/FilterChip.ts @@ -32,7 +32,7 @@ export class FilterChip implements Component { selectors += ".pr-vpad-m" } - const contentColor = selected ? on_secondary_fixed : theme.content_fg + const contentColor = selected ? on_secondary_fixed : theme.on_surface return m( selectors, { diff --git a/src/common/gui/base/Icon.ts b/src/common/gui/base/Icon.ts index f384548844a8..0408d7c9fb74 100644 --- a/src/common/gui/base/Icon.ts +++ b/src/common/gui/base/Icon.ts @@ -113,7 +113,7 @@ export class Icon implements Component { style = style ? style : {} if (!style.fill) { - style.fill = theme.content_accent + style.fill = theme.primary } return style as { fill: string } diff --git a/src/common/gui/base/InfoBanner.ts b/src/common/gui/base/InfoBanner.ts index 21e107b144f3..b13babae38cd 100644 --- a/src/common/gui/base/InfoBanner.ts +++ b/src/common/gui/base/InfoBanner.ts @@ -46,7 +46,7 @@ export class InfoBanner implements Component { ".center-vertically.border-bottom.pr-s.pl.border-radius.mt-xs", { style: { - border: `solid 2px ${type === BannerType.Warning ? theme.error_color : theme.content_border}`, + border: `solid 2px ${type === BannerType.Warning ? theme.error : theme.outline}`, // keep the distance to the bottom of the banner the same in the case that buttons aren't present minHeight: buttons.length > 0 ? undefined : px(37), }, @@ -69,7 +69,7 @@ export class InfoBanner implements Component { return m(Icon, { icon, style: { - fill: type === BannerType.Warning ? theme.error_color : theme.content_button, + fill: type === BannerType.Warning ? theme.error : theme.on_surface_variant, display: "block", }, }) diff --git a/src/common/gui/base/InputButton.ts b/src/common/gui/base/InputButton.ts index 7b79f9523d6a..d148e0f7a318 100644 --- a/src/common/gui/base/InputButton.ts +++ b/src/common/gui/base/InputButton.ts @@ -58,7 +58,7 @@ export class InputButton implements ClassComponent { class: this.resolveContainerClasses(attrs.variant, attrs.classes, attrs.disabled), tabIndex: attrs.tabIndex, style: { - borderColor: theme.content_message_bg, + borderColor: theme.outline, padding: 0, ...attrs.containerStyle, }, diff --git a/src/common/gui/base/Label.ts b/src/common/gui/base/Label.ts index dbd8a6e56adc..c70bc1dad4b7 100644 --- a/src/common/gui/base/Label.ts +++ b/src/common/gui/base/Label.ts @@ -8,8 +8,8 @@ import { isColorLight } from "./Color.js" const supportsRelativeHslColors = typeof CSS !== "undefined" ? CSS.supports("color", `hsl(from #ccc h calc(min(50, s)) l)`) : false export function getLabelColor(backgroundColor: string | null): string { - const labelColor = backgroundColor ?? theme.content_accent - const isDarkTheme = !isColorLight(theme.content_bg) + const labelColor = backgroundColor ?? theme.primary + const isDarkTheme = !isColorLight(theme.surface) // make a color have the same hue and lightness with saturation capped to 50 return isDarkTheme ? limitedSaturationColor(labelColor) : labelColor } @@ -32,7 +32,7 @@ export const Label = pureComponent(function Label({ text, color }: { text: strin style: { // in dark theme override saturation to aid readability. This is not relative but absolute saturation. We preserve the hue. backgroundColor: labelColor, - color: colorForBg(color ?? theme.content_accent), + color: colorForBg(color ?? theme.primary), padding: `1px ${size.vpad_xsm}px`, }, }, diff --git a/src/common/gui/base/List.ts b/src/common/gui/base/List.ts index 3c7977b983af..e1304747fd6c 100644 --- a/src/common/gui/base/List.ts +++ b/src/common/gui/base/List.ts @@ -282,7 +282,7 @@ export class List> implements ClassComponent { // The quick change of the background color is to prevent a white background appearing in dark mode - if (row.domElement) row.domElement!.style.background = theme.navigation_bg + if (row.domElement) row.domElement!.style.background = theme.surface_container requestAnimationFrame(() => { if (row.domElement) row.domElement!.style.background = "" }) diff --git a/src/common/gui/base/MessageBox.ts b/src/common/gui/base/MessageBox.ts index 51b2d57a3375..e8a7f2f810e3 100644 --- a/src/common/gui/base/MessageBox.ts +++ b/src/common/gui/base/MessageBox.ts @@ -19,7 +19,7 @@ export class MessageBox implements Component { { "white-space": "pre-wrap", "text-align": "center", - border: `2px solid ${theme.content_border}`, + border: `2px solid ${theme.outline}`, }, attrs.style, ), diff --git a/src/common/gui/base/Modal.ts b/src/common/gui/base/Modal.ts index 21250873869b..377087866e1e 100644 --- a/src/common/gui/base/Modal.ts +++ b/src/common/gui/base/Modal.ts @@ -237,7 +237,7 @@ class Modal implements Component { addAnimation(domLayer: HTMLElement, fadein: boolean): Promise { const start = 0 const end = 0.5 - return animations.add(domLayer, alpha(AlphaEnum.BackgroundColor, theme.modal_bg, fadein ? start : end, fadein ? end : start)) + return animations.add(domLayer, alpha(AlphaEnum.BackgroundColor, theme.scrim, fadein ? start : end, fadein ? end : start)) } } diff --git a/src/common/gui/base/NavButton.ts b/src/common/gui/base/NavButton.ts index 378fd5358194..4992b5de5c01 100644 --- a/src/common/gui/base/NavButton.ts +++ b/src/common/gui/base/NavButton.ts @@ -214,21 +214,21 @@ function getColors(buttonColors: NavButtonColor | null | undefined) { switch (buttonColors) { case NavButtonColor.Header: return { - button: styles.isDesktopLayout() ? theme.header_button : theme.content_accent, - button_selected: styles.isDesktopLayout() ? theme.header_button_selected : theme.content_accent, + button: styles.isDesktopLayout() ? theme.on_surface_variant : theme.primary, + button_selected: styles.isDesktopLayout() ? theme.primary : theme.primary, } case NavButtonColor.Nav: return { - button: theme.navigation_button, - button_selected: theme.navigation_button_selected, + button: theme.on_surface_variant, + button_selected: theme.primary, } default: // for nav buttons in the more dropdown menu return { - button: theme.content_button, - button_selected: theme.content_button_selected, + button: theme.on_surface_variant, + button_selected: theme.primary, } } } diff --git a/src/common/gui/base/RadioSelector.ts b/src/common/gui/base/RadioSelector.ts index 815e79a65296..198e06422ab7 100644 --- a/src/common/gui/base/RadioSelector.ts +++ b/src/common/gui/base/RadioSelector.ts @@ -47,7 +47,7 @@ export class RadioSelector implements Component> { // Make the option the same size as a button if a description is not given class: "button-min-width button-min-height" + attrClasses, style: { - borderColor: isSelected ? theme.content_accent : theme.content_border, + borderColor: isSelected ? theme.primary : theme.outline, borderWidth: "2px", height: "fit-content", }, diff --git a/src/common/gui/base/Select.ts b/src/common/gui/base/Select.ts index d2eb63d0739b..a9728f2040e5 100644 --- a/src/common/gui/base/Select.ts +++ b/src/common/gui/base/Select.ts @@ -566,7 +566,7 @@ class OptionListContainer implements ClassComponent { } private renderNoItem(): Children { - return m("span.placeholder.text-center", { color: theme.list_message_bg }, lang.get("noEntries_msg")) + return m("span.placeholder.text-center", { color: theme.on_surface_fade }, lang.get("noEntries_msg")) } setOrigin(origin: DomRectReadOnlyPolyfilled): this { diff --git a/src/common/gui/base/SidebarSectionRow.ts b/src/common/gui/base/SidebarSectionRow.ts index 2be81a6eee1f..efd93920a368 100644 --- a/src/common/gui/base/SidebarSectionRow.ts +++ b/src/common/gui/base/SidebarSectionRow.ts @@ -71,7 +71,7 @@ export class SidebarSectionRow implements Component { icon: attrs.icon, size: IconSize.Medium, style: { - fill: attrs.iconColor ?? (isNavButtonSelected(navButtonAttrs) ? theme.navigation_button_selected : theme.navigation_button), + fill: attrs.iconColor ?? (isNavButtonSelected(navButtonAttrs) ? theme.primary : theme.on_surface_variant), }, }), ), diff --git a/src/common/gui/base/TextDisplayArea.ts b/src/common/gui/base/TextDisplayArea.ts index 549ba22b46d5..d77d137fa763 100644 --- a/src/common/gui/base/TextDisplayArea.ts +++ b/src/common/gui/base/TextDisplayArea.ts @@ -29,7 +29,7 @@ export class TextDisplayArea implements Component { ".text-pre.flex-grow", { style: { - borderBottom: `1px solid ${theme.content_border}`, + borderBottom: `1px solid ${theme.outline}`, lineHeight: px(inputLineHeight), minHeight: px(inputLineHeight), }, diff --git a/src/common/gui/base/TextField.ts b/src/common/gui/base/TextField.ts index cbb90d918654..ad8e545eacc8 100644 --- a/src/common/gui/base/TextField.ts +++ b/src/common/gui/base/TextField.ts @@ -104,7 +104,7 @@ export class TextField implements ClassComponent { const labelTransitionSpeed = DefaultAnimationTime / 2 const doShowBorder = a.doShowBorder !== false const borderWidth = this.active ? "2px" : "1px" - const borderColor = this.active ? theme.content_accent : theme.content_border + const borderColor = this.active ? theme.primary : theme.outline return m( ".text-field.rel.overflow-hidden", { diff --git a/src/common/gui/base/WizardDialog.ts b/src/common/gui/base/WizardDialog.ts index 7de6cc306b35..851f3b6e6b16 100644 --- a/src/common/gui/base/WizardDialog.ts +++ b/src/common/gui/base/WizardDialog.ts @@ -349,7 +349,7 @@ export class WizardPagingButton { icon: Icons.Checkmark, size: IconSize.Medium, style: { - fill: theme.content_bg, + fill: theme.surface, }, }) : nextIndex, diff --git a/src/common/gui/base/buttons/ArrowButton.ts b/src/common/gui/base/buttons/ArrowButton.ts index 380b5d3b3c05..1af09a00b9f7 100644 --- a/src/common/gui/base/buttons/ArrowButton.ts +++ b/src/common/gui/base/buttons/ArrowButton.ts @@ -17,7 +17,7 @@ export default function renderSwitchMonthArrowIcon(forward: boolean, size: numbe class: "center-h", size: IconSize.Normal, style: { - fill: theme.content_fg, + fill: theme.on_surface, }, }), style: { diff --git a/src/common/gui/base/buttons/BannerButton.ts b/src/common/gui/base/buttons/BannerButton.ts index 6ddaf276b5df..f4eaf05a03f8 100644 --- a/src/common/gui/base/buttons/BannerButton.ts +++ b/src/common/gui/base/buttons/BannerButton.ts @@ -23,8 +23,8 @@ export class BannerButton implements Component { text: lang.getTranslationText(attrs.text), class: `border-radius mr-s center ${attrs.class} ${attrs.disabled ? "disabled" : ""}`, style: { - border: `2px solid ${attrs.disabled ? theme.content_button : attrs.borderColor}`, - color: attrs.disabled ? theme.content_button : attrs.color, + border: `2px solid ${attrs.disabled ? theme.on_surface_variant : attrs.borderColor}`, + color: attrs.disabled ? theme.on_surface_variant : attrs.color, padding: px(size.hpad_button), minWidth: "60px", }, diff --git a/src/common/gui/base/buttons/BubbleButton.ts b/src/common/gui/base/buttons/BubbleButton.ts index 5190efff8939..fbb1cb449623 100644 --- a/src/common/gui/base/buttons/BubbleButton.ts +++ b/src/common/gui/base/buttons/BubbleButton.ts @@ -43,7 +43,7 @@ export class BubbleButton implements Component { icon: attrs.icon, container: "div", class: "mr-xs mb-xs", - style: { fill: theme.button_bubble_fg }, + style: { fill: theme.on_surface }, }), iconWrapperSelector: ".icon.mr-hpad-small", style: { height: px(bubbleButtonHeight()), maxHeight: px(bubbleButtonHeight()) }, diff --git a/src/common/gui/base/buttons/OutlineButton.ts b/src/common/gui/base/buttons/OutlineButton.ts index 4ac6ce0b65c4..7695f66f81a5 100644 --- a/src/common/gui/base/buttons/OutlineButton.ts +++ b/src/common/gui/base/buttons/OutlineButton.ts @@ -30,8 +30,8 @@ export class OutlineButton implements ClassComponent { onclick: attrs.click, disabled: attrs.disabled, style: { - borderColor: theme.content_message_bg, - color: theme.content_button, + borderColor: theme.outline, + color: theme.on_surface_variant, }, class: this.resolveClasses(attrs.expanded, attrs.disabled), } as BaseButtonAttrs) diff --git a/src/common/gui/base/buttons/RowButton.ts b/src/common/gui/base/buttons/RowButton.ts index 756b2d4ebba5..cfd156f0fa4a 100644 --- a/src/common/gui/base/buttons/RowButton.ts +++ b/src/common/gui/base/buttons/RowButton.ts @@ -25,7 +25,7 @@ export class RowButton implements Component { const attrs = vnode.attrs const label = lang.getTranslationText(attrs.label) const text = lang.getTranslationText(attrs.text ?? attrs.label) - const color = attrs.selected ? theme.content_button_selected : theme.content_button + const color = attrs.selected ? theme.primary : theme.on_surface_variant return m(BaseButton, { label: attrs.label, text: m( diff --git a/src/common/gui/base/colorPicker/ColorPickerView.ts b/src/common/gui/base/colorPicker/ColorPickerView.ts index 0962f5900785..6a2fe063acfa 100644 --- a/src/common/gui/base/colorPicker/ColorPickerView.ts +++ b/src/common/gui/base/colorPicker/ColorPickerView.ts @@ -32,7 +32,7 @@ export type ColorPickerViewAttrs = { export class ColorPickerView implements Component { private readonly palette = Array(ColorPickerModel.PALETTE_SIZE).fill(null) - private readonly model: ColorPickerModel = new ColorPickerModel(!isColorLight(theme.content_bg)) + private readonly model: ColorPickerModel = new ColorPickerModel(!isColorLight(theme.surface)) private selectedHueAngle = Math.floor(Math.random() * MAX_HUE_ANGLE) private fallbackVariantIndex: number = PaletteIndex.defaultVariant private isAdvanced = false @@ -91,7 +91,7 @@ export class ColorPickerView implements Component { style: i === 0 ? { - borderRight: `2px solid ${theme.content_border}`, + borderRight: `2px solid ${theme.outline}`, } : undefined, }), @@ -230,7 +230,7 @@ export class ColorPickerView implements Component { padding: "1px", borderWidth: "2px", borderStyle: "solid", - borderColor: isOptionSelected ? theme.content_button_selected : "transparent", + borderColor: isOptionSelected ? theme.primary : "transparent", }, }, m(".border-radius", { @@ -243,8 +243,8 @@ export class ColorPickerView implements Component { height: px(30), borderWidth: "1px", borderStyle: "solid", - borderColor: isOptionSelected ? "transparent" : theme.content_border, - backgroundColor: isColorValid ? color : theme.content_border, + borderColor: isOptionSelected ? "transparent" : theme.outline, + backgroundColor: isColorValid ? color : theme.outline, }, onkeydown: (e: KeyboardEvent) => { if (isKeyPressed(e.key, Keys.SPACE)) { @@ -323,15 +323,15 @@ export class ColorPickerView implements Component { { style: { borderStyle: "solid", - borderColor: theme.content_border, - backgroundColor: theme.content_border, + borderColor: theme.outline, + backgroundColor: theme.outline, borderWidth: px(HUE_GRADIENT_BORDER_WIDTH), height: px(HUE_GRADIENT_HEIGHT), }, }, m("img.block.full-width", { src: `${window.tutao.appState.prefixWithoutFile}/images/color-hue-picker/hue-gradient-${ - !isColorLight(theme.content_bg) ? "dark" : "light" + !isColorLight(theme.surface) ? "dark" : "light" }.png`, alt: "", draggable: false, @@ -396,7 +396,7 @@ export class ColorPickerView implements Component { width: px(24), height: px(24), transform: "translateX(-50%)", - backgroundColor: this.model.getHueWindowColor(this.selectedHueAngle) ?? theme.content_border, + backgroundColor: this.model.getHueWindowColor(this.selectedHueAngle) ?? theme.outline, }, oncreate: (vnode) => { this.hueWindowDom = vnode.dom as HTMLElement @@ -408,7 +408,7 @@ export class ColorPickerView implements Component { width: px(2), height: px(HUE_GRADIENT_HEIGHT), transform: "translateX(-50%)", - backgroundColor: theme.content_border, + backgroundColor: theme.outline, }, }), ], diff --git a/src/common/gui/builtinThemes.ts b/src/common/gui/builtinThemes.ts index aeb2fd63134a..ca8d183a4447 100644 --- a/src/common/gui/builtinThemes.ts +++ b/src/common/gui/builtinThemes.ts @@ -46,7 +46,7 @@ const red = "#850122" const secondary_red = "#FF2222" const red_nota = "#d93951" const dunkel = "#410002" -const blue = "#013E85" +const blue = "#003E85" const secondary_blue = "#4282FF" const blue_nota = "#3964d9" const light_blue = "#ACC7FF" @@ -106,192 +106,129 @@ const getLogo = (isDark: boolean, isDefault: boolean) => { return isDarkOrDefault ? getMailLogoSvg(logo_text_bright_grey, logo_text_bright_grey, logo_text_bright_grey) : getMailLogoSvg(red, secondary_red, black) } +/** + * Color token definitions for each built-in theme. + * We follow the color roles from Material 3 to define the names of the tokens, + * the following link might be helpful to know which color token should be used in which situation. + * https://m3.material.io/styles/color/roles + * + * When a new color role is needed, please follow the instructions below. + * https://m3.material.io/styles/color/advanced/define-new-colors#baed14ce-4be8-46aa-8223-ace5d45af005 + */ export const themes = (): Themes => { const isCalendarApp = client.isCalendarApp() const lightRed = Object.freeze({ themeId: !isCalendarApp ? "light" : "light_secondary", logo: getLogo(false, !isCalendarApp), - button_bubble_bg: grey_lighter_3, - button_bubble_fg: grey_darker_1, - content_fg: grey_darker_1, - content_button: grey_darker_0, - content_button_selected: red, - content_button_icon: light_white, - content_button_icon_selected: light_white, - content_accent: red, + // Campaign colors + tuta_color_nota: red_nota, content_accent_tuta_bday: dark_purple, content_accent_secondary_tuta_bday: light_purple, - content_bg: light_white, content_bg_tuta_bday: dark, - content_border: grey_lighter_1, - content_message_bg: grey_lighter_0, - header_bg: light_white, - header_box_shadow_bg: grey_lighter_1, - header_button: grey_darker_0, - header_button_selected: red, - list_bg: light_white, - list_alternate_bg: grey_lighter_4, - list_accent_fg: red, - list_message_bg: grey_lighter_0, - list_border: grey_lighter_2, - modal_bg: grey_darker_1, - elevated_bg: light_white, - navigation_bg: grey_lighter_4, - navigation_border: grey_lighter_2, - navigation_button: grey_darker_0, - navigation_button_icon: light_white, - navigation_button_selected: red, - navigation_button_icon_selected: light_white, - navigation_menu_bg: grey_lighter_3, - navigation_menu_icon: grey, - error_color: SONNE, - tuta_color_nota: red_nota, + // Basic color tokens + primary: red, + on_primary: light_white, + secondary: grey_lighter_3, + on_secondary: grey_darker_1, + error: SONNE, + surface: light_white, + surface_container: grey_lighter_4, + on_surface_fade: grey_lighter_0, + on_surface: grey_darker_1, + on_surface_variant: grey_darker_0, + outline: grey_lighter_1, + outline_variant: grey_lighter_3, + scrim: grey_darker_1, experimental_primary_container: PEACH, experimental_on_primary_container: RED_DUNKEL, experimental_tertiary: RED_FIGHTER, highlight_bg: highlight, highlight_fg: black, - outline_variant: grey_lighter_1, }) const darkRed = Object.freeze({ themeId: !isCalendarApp ? "dark" : "dark_secondary", logo: getLogo(true, !isCalendarApp), - button_bubble_bg: dark_lighter_2, - button_bubble_fg: light_lighter_1, - content_fg: light_lighter_1, - content_button: light_lighter_0, - content_button_selected: light_red, - content_button_icon_bg: dark_lighter_2, - content_button_icon: light_lighter_1, - content_button_icon_selected: dark_lighter_0, - content_accent: light_red, + // Campaign colors + tuta_color_nota: red_nota, content_accent_tuta_bday: light_purple, content_accent_secondary_tuta_bday: dark_purple, - content_bg: dark_darker_0, content_bg_tuta_bday: light_white, - content_border: dark_lighter_1, - content_message_bg: dark_lighter_2, - header_bg: dark, - header_box_shadow_bg: dark_darker_0, - header_button: light_lighter_0, - header_button_selected: light_red, - list_bg: dark_darker_0, - list_alternate_bg: dark_lighter_0, - list_accent_fg: light_red, - list_message_bg: dark_lighter_2, - list_border: dark_lighter_1, - modal_bg: dark_darker_0, - elevated_bg: dark_lighter_0, - navigation_bg: dark_lighter_0, - navigation_border: dark_lighter_1, - navigation_button: light_lighter_0, - navigation_button_icon_bg: dark_lighter_2, - navigation_button_icon: light_lighter_1, - navigation_button_selected: light_red, - navigation_button_icon_selected: light_lighter_0, - navigation_menu_bg: dark_darker_0, - navigation_menu_icon: light_grey, - error_color: SONNE_70, - tuta_color_nota: red_nota, + // Basic color tokens + primary: light_red, + on_primary: dark_lighter_0, + secondary: dark_lighter_2, + on_secondary: light_lighter_1, + error: SONNE_70, + surface: dark_darker_0, + surface_container: dark_lighter_0, + on_surface_fade: dark_lighter_2, + on_surface: light_lighter_1, + on_surface_variant: light_lighter_0, + outline: dark_lighter_1, + outline_variant: dark_lighter_1, + scrim: dark_darker_0, experimental_primary_container: DARK_PEACH, experimental_on_primary_container: dark_lighter_0, experimental_tertiary: RED_FIGHTER, highlight_bg: highlight, highlight_fg: black, - outline_variant: grey_darker_0, }) const lightBlue = Object.freeze({ themeId: isCalendarApp ? "light" : "light_secondary", // blue is not really our brand color, treat blue like whitelabel color logo: getLogo(false, isCalendarApp), - button_bubble_bg: grey_lighter_3, - button_bubble_fg: grey_darker_1, - content_fg: grey_darker_1, - content_button: grey_darker_0, - content_button_selected: blue, - content_button_icon: light_white, - content_button_icon_selected: light_white, - content_accent: blue, + // Campaign colors + tuta_color_nota: red_nota, content_accent_tuta_bday: dark_purple, content_accent_secondary_tuta_bday: light_purple, - content_bg: light_white, content_bg_tuta_bday: dark, - content_border: grey_lighter_1, - content_message_bg: grey_lighter_0, - header_bg: light_white, - header_box_shadow_bg: grey_lighter_1, - header_button: grey_darker_0, - header_button_selected: blue, - list_bg: light_white, - list_alternate_bg: grey_lighter_4, - list_accent_fg: blue, - list_message_bg: grey_lighter_0, - list_border: grey_lighter_2, - modal_bg: grey_darker_1, - elevated_bg: light_white, - navigation_bg: grey_lighter_4, - navigation_border: grey_lighter_2, - navigation_button: grey_darker_0, - navigation_button_icon: light_white, - navigation_button_selected: blue, - navigation_button_icon_selected: light_white, - navigation_menu_bg: grey_lighter_3, - navigation_menu_icon: grey, - error_color: SONNE, - tuta_color_nota: blue_nota, + // Basic color tokens + primary: blue, + on_primary: light_white, + secondary: grey_lighter_3, + on_secondary: grey_darker_1, + error: SONNE, + surface: light_white, + surface_container: grey_lighter_4, + on_surface_fade: grey_lighter_0, + on_surface: grey_darker_1, + on_surface_variant: grey_darker_0, + outline: grey_lighter_1, + outline_variant: grey_lighter_3, + scrim: grey_darker_1, experimental_primary_container: PEACH, experimental_on_primary_container: BLUE_DUNKEL, experimental_tertiary: BLUE_FIGHTER, highlight_bg: highlight, highlight_fg: black, - outline_variant: grey_lighter_1, }) const darkBlue = Object.freeze({ themeId: isCalendarApp ? "dark" : "dark_secondary", logo: getLogo(true, isCalendarApp), - button_bubble_bg: dark_lighter_2, - button_bubble_fg: light_lighter_1, - content_fg: light_lighter_1, - content_button: light_lighter_0, - content_button_selected: light_blue, - content_button_icon_bg: dark_lighter_2, - content_button_icon: light_lighter_1, - content_button_icon_selected: dark_lighter_0, - content_accent: light_blue, + // Campaign colors + tuta_color_nota: red_nota, content_accent_tuta_bday: light_purple, content_accent_secondary_tuta_bday: dark_purple, - content_bg: dark_darker_0, content_bg_tuta_bday: light_white, - content_border: dark_lighter_1, - content_message_bg: dark_lighter_2, - header_bg: dark, - header_box_shadow_bg: dark_darker_0, - header_button: light_lighter_0, - header_button_selected: light_blue, - list_bg: dark_darker_0, - list_alternate_bg: dark_lighter_0, - list_accent_fg: light_blue, - list_message_bg: dark_lighter_2, - list_border: dark_lighter_1, - modal_bg: dark_darker_0, - elevated_bg: dark_lighter_0, - navigation_bg: dark_lighter_0, - navigation_border: dark_lighter_1, - navigation_button: light_lighter_0, - navigation_button_icon_bg: dark_lighter_2, - navigation_button_icon: light_lighter_1, - navigation_button_selected: light_blue, - navigation_button_icon_selected: light_lighter_0, - navigation_menu_bg: dark_darker_0, - navigation_menu_icon: light_grey, - error_color: SONNE_70, - tuta_color_nota: blue_nota, + // Basic color tokens + primary: light_blue, + on_primary: dark_lighter_0, + secondary: dark_lighter_2, + on_secondary: light_lighter_1, + error: SONNE_70, + surface: dark_darker_0, + surface_container: dark_lighter_0, + on_surface_fade: dark_lighter_2, + on_surface: light_lighter_1, + on_surface_variant: light_lighter_0, + outline: dark_lighter_1, + outline_variant: dark_lighter_1, + scrim: dark_darker_0, experimental_primary_container: DARK_PEACH, experimental_on_primary_container: dark_lighter_0, experimental_tertiary: BLUE_FIGHTER, highlight_bg: highlight, highlight_fg: black, - outline_variant: grey_darker_0, }) return { diff --git a/src/common/gui/dialogs/ImageWithOptionsDialog.ts b/src/common/gui/dialogs/ImageWithOptionsDialog.ts index 9c6d49d0732c..b14eba705a0c 100644 --- a/src/common/gui/dialogs/ImageWithOptionsDialog.ts +++ b/src/common/gui/dialogs/ImageWithOptionsDialog.ts @@ -59,9 +59,9 @@ export class ImageWithOptionsDialog implements Component { }, { height: "100%", - "background-color": theme.navigation_bg, + "background-color": theme.surface_container, }, ) diff --git a/src/common/gui/main-styles.ts b/src/common/gui/main-styles.ts index 826a8f2bf4e4..271748f7f140 100644 --- a/src/common/gui/main-styles.ts +++ b/src/common/gui/main-styles.ts @@ -4,7 +4,7 @@ import { client } from "../misc/ClientDetector" import { lang } from "../misc/LanguageViewModel" import { noselect, position_absolute } from "./mixins" import { assertMainOrNode, isAdminClient, isApp, isElectronClient } from "../api/common/Env" -import { getContentButtonIconBackground, getElevatedBackground, getNavigationMenuBg, theme } from "./theme" +import { getElevatedBackground, getNavigationMenuBg, theme } from "./theme" import { stateBgActive, stateBgFocus, stateBgHover, stateBgLike } from "./builtinThemes.js" import { FontIcons } from "./base/icons/FontIcons.js" import { DefaultAnimationTime } from "./animation/Animations.js" @@ -31,8 +31,9 @@ export function getFonts(): string { return fonts.join(", ") } -export const boxShadow = `0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)` -const searchBarShadow = "0px 2px 4px rgb(0, 0, 0, 0.12)" +export const boxShadowHigh = `0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)` +const boxShadowMedium = "0px 0px 4px 2px rgba(0, 0, 0, 0.22)" +const boxShadowLow = "0px 2px 4px rgb(0, 0, 0, 0.12)" const scrollbarWidthHeight = px(18) styles.registerStyle("main", () => { @@ -49,10 +50,10 @@ styles.registerStyle("main", () => { bottom: px(size.vpad_xs), left: px(size.vpad_xs), "text-align": "center", - color: theme.content_bg, + color: theme.surface, "text-decoration": "none", - "background-color": theme.content_fg, - border: "1px solid " + theme.content_bg, + "background-color": theme.on_surface, + border: "1px solid " + theme.surface, opacity: 0, transition: "opacity .1s linear", "font-family": "monospace", @@ -154,7 +155,7 @@ styles.registerStyle("main", () => { position: "fixed", // Fix body for iOS & Safari // It is inlined to "transparent" in HTML so we have to overwrite it. - "background-color": `${theme.content_bg} !important`, + "background-color": `${theme.surface} !important`, }, "button, textarea": { padding: 0, @@ -173,7 +174,7 @@ styles.registerStyle("main", () => { "font-family": getFonts(), "font-size": px(size.font_size_base), "line-height": size.line_height, - color: theme.content_fg, + color: theme.on_surface, "-webkit-text-size-adjust": "none", // fix for safari browser }, "small, .small": { @@ -257,13 +258,13 @@ styles.registerStyle("main", () => { margin: 0, border: "none", height: "1px", - "background-color": theme.list_border, + "background-color": theme.outline_variant, }, ".border": { - border: `1px solid ${theme.content_border}`, + border: `1px solid ${theme.outline}`, }, ".border-top": { - "border-top": `1px solid ${theme.content_border}`, + "border-top": `1px solid ${theme.outline}`, }, "#shadow-mail-body.break-pre pre": { "white-space": "pre-wrap", @@ -681,10 +682,10 @@ styles.registerStyle("main", () => { }, // borders ".border-bottom": { - "border-bottom": `1px solid ${theme.content_border}`, + "border-bottom": `1px solid ${theme.outline}`, }, ".border-left": { - "border-left": `1px solid ${theme.content_border}`, + "border-left": `1px solid ${theme.outline}`, }, // colors ".bg-transparent": { @@ -703,75 +704,75 @@ styles.registerStyle("main", () => { color: "black", }, ".content-fg": { - color: theme.content_fg, + color: theme.on_surface, }, ".content-accent-fg": { - color: theme.content_accent, + color: theme.primary, }, ".content-accent-accent": { - "accent-color": theme.content_accent, + "accent-color": theme.primary, }, ".icon-accent svg": { - fill: theme.content_accent, + fill: theme.primary, }, ".svg-content-fg path": { - fill: theme.content_fg, + fill: theme.on_surface, }, ".content-bg": { - "background-color": theme.content_bg, + "background-color": theme.surface, }, ".nav-bg": { - "background-color": theme.navigation_bg, + "background-color": theme.surface_container, }, ".content-hover:hover": { - color: theme.content_accent, + color: theme.primary, }, ".no-hover": { "pointer-events": "none", }, ".content-message-bg": { - "background-color": theme.content_message_bg, + "background-color": theme.on_surface_fade, }, ".elevated-bg": { "background-color": getElevatedBackground(), }, ".list-bg": { - "background-color": theme.list_bg, + "background-color": theme.surface, }, ".list-accent-fg": { - color: theme.list_accent_fg, + color: theme.primary, }, ".svg-list-accent-fg path": { - fill: theme.list_accent_fg, + fill: theme.primary, }, ".bg-accent-fg": { - "background-color": theme.list_accent_fg, + "background-color": theme.primary, }, ".list-border-bottom": { - "border-bottom": `1px solid ${theme.list_border}`, + "border-bottom": `1px solid ${theme.outline_variant}`, }, ".accent-bg-translucent": { - background: `${theme.content_accent}2C`, - color: theme.content_accent, + background: `${theme.primary}2C`, + color: theme.primary, }, ".button-bg": { - background: theme.content_button, - color: theme.navigation_bg, + background: theme.on_surface_variant, + color: theme.surface_container, opacity: "0.5", }, ".accent-bg": { - "background-color": theme.content_accent, - color: theme.content_button_icon_selected, + "background-color": theme.primary, + color: theme.on_primary, }, ".accent-bg-cyber-monday": { "background-color": theme.content_accent_tuta_bday, - color: theme.content_button_icon_selected, + color: theme.on_primary, }, ".accent-fg": { - color: theme.content_button_icon, + color: theme.on_primary, }, ".accent-fg path": { - fill: theme.content_button_icon, + fill: theme.on_primary, }, ".red": { "background-color": "#840010", @@ -837,7 +838,7 @@ styles.registerStyle("main", () => { "-webkit-overflow-scrolling": "touch", }, "*": { - "scrollbar-color": `${theme.content_button} transparent`, + "scrollbar-color": `${theme.on_surface_variant} transparent`, "scrollbar-width": "thin", }, "::-webkit-scrollbar": !client.isMobileDevice() @@ -849,7 +850,7 @@ styles.registerStyle("main", () => { : {}, "::-webkit-scrollbar-thumb": !client.isMobileDevice() ? { - background: theme.content_button, + background: theme.on_surface_variant, // reduce the background "border-left": "15px solid transparent", "background-clip": "padding-box", @@ -865,7 +866,7 @@ styles.registerStyle("main", () => { width: "6px", }, ".visible-scrollbar::-webkit-scrollbar-thumb": { - background: theme.content_button, + background: theme.on_surface_variant, "border-radius": "3px", }, // we are trying to handle 3 cases: @@ -891,7 +892,7 @@ styles.registerStyle("main", () => { "padding-right": "16px", }, ".dropdown-info + .dropdown-button": { - "border-top": `1px solid ${theme.content_border}`, + "border-top": `1px solid ${theme.outline}`, }, ".dropdown-info + .dropdown-info": { "padding-top": "0", @@ -909,7 +910,7 @@ styles.registerStyle("main", () => { "text-align": "start", }, ".statusTextColor": { - color: theme.content_accent, + color: theme.primary, }, ".button-height": { height: px(size.button_height), @@ -1206,20 +1207,20 @@ styles.registerStyle("main", () => { "-webkit-tap-highlight-color": "rgba(255, 255, 255, 0)", "padding-bottom": px(size.icon_size_small), "padding-top": px(size.icon_size_small), - "border-bottom": `1px solid ${theme.button_bubble_bg} !important`, + "border-bottom": `1px solid ${theme.outline_variant} !important`, }, ".settings-item:last-child": { "border-bottom": "none !important", }, ".editor-border": { - border: `2px solid ${theme.content_border}`, + border: `2px solid ${theme.outline}`, "padding-top": px(size.vpad_small), "padding-bottom": px(size.vpad_small), "padding-left": px(size.hpad), "padding-right": px(size.hpad), }, ".editor-border-active": { - border: `3px solid ${theme.content_accent}`, + border: `3px solid ${theme.primary}`, "padding-top": px(size.vpad_small - 1), "padding-bottom": px(size.vpad_small - 1), "padding-left": px(size.hpad - 1), @@ -1255,8 +1256,8 @@ styles.registerStyle("main", () => { "background-color": stateBgHover, }, ".search-bar[focused=true]": { - "background-color": theme.content_bg, - "box-shadow": searchBarShadow, + "background-color": theme.surface, + "box-shadow": boxShadowLow, }, ".fab-shadow": { "box-shadow": "0px 8px 12px 6px rgba(0, 0, 0, 0.15), 0px 4px 4px rgba(0, 0, 0, 0.3)", @@ -1347,7 +1348,7 @@ styles.registerStyle("main", () => { "margin-bottom": px(size.vpad), }, ".wizard-breadcrumb": { - border: `1px solid ${getContentButtonIconBackground()}`, + border: `1px solid ${theme.outline}`, color: "inherit", "transition-property": "border-width, border-color, color, background-color", "transition-duration": `${DefaultAnimationTime - 70}ms`, @@ -1355,30 +1356,30 @@ styles.registerStyle("main", () => { "will-change": "border-width, border-color, color", }, ".wizard-breadcrumb-active": { - border: `2px solid ${theme.content_accent}`, - color: theme.content_accent, + border: `2px solid ${theme.primary}`, + color: theme.primary, "transition-property": "border-width, border-color, color, background-color", "transition-duration": `${DefaultAnimationTime - 70}ms`, "transition-timing-function": "ease-out", "will-change": "border-width, color, background-color", }, ".wizard-breadcrumb-previous": { - border: `1px solid ${theme.content_accent}`, + border: `1px solid ${theme.primary}`, color: "inherit", - "background-color": theme.content_accent, + "background-color": theme.primary, "transition-property": "border-width, border-color, color, background-color", "transition-duration": `${DefaultAnimationTime - 70}ms`, "transition-timing-function": "ease-out", "will-change": "border-width, border-color, color, background-color", }, ".wizard-breadcrumb-line": { - "border-top": `3px dotted ${theme.content_border}`, + "border-top": `3px dotted ${theme.outline}`, height: 0, transition: `border-top-color ${DefaultAnimationTime}ms ease-out`, "will-change": "border-top-style, border-top-color", }, ".wizard-breadcrumb-line-active": { - "border-top": `3px solid ${theme.content_accent}`, + "border-top": `3px solid ${theme.primary}`, height: 0, transition: `border-top-color ${DefaultAnimationTime}ms ease-out`, }, @@ -1427,7 +1428,7 @@ styles.registerStyle("main", () => { outline: "none", }, ".state-bg-2::before": { - "background-color": `var(--state-bg-color, ${theme.content_fg})`, + "background-color": `var(--state-bg-color, ${theme.on_surface})`, opacity: "0", transition: "opacity 0.6s", content: "''", @@ -1498,7 +1499,7 @@ styles.registerStyle("main", () => { // header ".header-nav": { height: px(size.navbar_height), - "background-color": theme.navigation_bg, + "background-color": theme.surface_container, "z-index": 2, }, ".bottom-nav": { @@ -1507,9 +1508,9 @@ styles.registerStyle("main", () => { to set all nav elements to border-box, we must make sure to not break any existing styling */ "box-sizing": "border-box", - "border-top": `1px solid ${theme.navigation_border}`, + "border-top": `1px solid ${theme.outline}`, height: `calc(${size.bottom_nav_bar}px + env(safe-area-inset-bottom))`, - background: theme.header_bg, + background: theme.surface, "padding-bottom": "env(safe-area-inset-bottom)", "z-index": 2, }, @@ -1553,7 +1554,7 @@ styles.registerStyle("main", () => { width: "0px", height: "22px", "margin-left": "2px", - "border-color": theme.navigation_border, + "border-color": theme.outline, "border-width": "1px", "border-style": "solid", }, @@ -1574,7 +1575,7 @@ styles.registerStyle("main", () => { "max-width": px(350), }, ".dialog-header": { - "border-bottom": `1px solid ${theme.content_border}`, + "border-bottom": `1px solid ${theme.outline}`, height: px(size.button_height + 1), }, ".dialog-header-line-height": { @@ -1598,13 +1599,13 @@ styles.registerStyle("main", () => { height: "auto", }, ".dialog-buttons": { - "border-top": `1px solid ${theme.content_border}`, + "border-top": `1px solid ${theme.outline}`, }, ".dialog-buttons > button": { flex: "1", }, ".dialog-buttons > button:not(:first-child)": { - "border-left": `1px solid ${theme.content_border}`, + "border-left": `1px solid ${theme.outline}`, "margin-left": "0", }, ".dialog-height-small": { @@ -1619,7 +1620,7 @@ styles.registerStyle("main", () => { "padding-top": "env(safe-area-inset-top)", }, ".list-border-right": { - "border-right": `1px solid ${theme.list_border}`, + "border-right": `1px solid ${theme.outline_variant}`, }, ".folders": { "margin-bottom": px(12), @@ -1645,12 +1646,12 @@ styles.registerStyle("main", () => { "text-align": "center", }, ".row-selected": { - "border-color": `${theme.list_accent_fg} !important`, - color: `${theme.list_accent_fg}`, + "border-color": `${theme.primary} !important`, + color: `${theme.primary}`, }, ".hoverable-list-item:hover": { - "border-color": `${theme.list_accent_fg} !important`, - color: `${theme.list_accent_fg}`, + "border-color": `${theme.primary} !important`, + color: `${theme.primary}`, }, ".expander": { height: px(size.button_height), @@ -1670,7 +1671,7 @@ styles.registerStyle("main", () => { outline: "none", }, "blockquote.tutanota_quote, blockquote[type=cite]": { - "border-left": `1px solid ${theme.content_accent}`, + "border-left": `1px solid ${theme.primary}`, "padding-left": px(size.hpad), "margin-left": px(0), "margin-right": px(0), @@ -1696,15 +1697,15 @@ styles.registerStyle("main", () => { height: px(size.list_row_height), }, ".odd-row": { - "background-color": theme.list_bg, + "background-color": theme.surface, }, ".list-loading": { bottom: 0, }, // mail list ".teamLabel": { - color: theme.list_alternate_bg, - "background-color": theme.list_accent_fg, + color: theme.on_primary, + "background-color": theme.primary, }, ".ion": { display: "inline-block", @@ -1773,26 +1774,26 @@ styles.registerStyle("main", () => { width: "100%", }, ".dropdown-shadow": { - "box-shadow": boxShadow, + "box-shadow": boxShadowHigh, }, ".minimized-shadow": { // shadow params: 1.offset-x 2.offset-y 3.blur 4.spread 5.color - "box-shadow": `0px 0px 4px 2px ${theme.header_box_shadow_bg}`, // similar to header bar shadow + "box-shadow": boxShadowMedium, }, //dropdown filter bar ".dropdown-bar": { "border-style": "solid", "border-width": "0px 0px 1px 0px", - "border-color": theme.content_border, + "border-color": theme.outline, "padding-bottom": "1px", "z-index": 1, "border-radius": `${size.border_radius}px ${size.border_radius}px 0 0`, - color: theme.content_fg, + color: theme.on_surface, }, ".dropdown-bar:focus": { "border-style": "solid", "border-width": "0px 0px 2px 0px", - "border-color": `${theme.content_accent}`, + "border-color": `${theme.primary}`, "padding-bottom": "0px", }, ".dropdown-button": { @@ -1849,32 +1850,30 @@ styles.registerStyle("main", () => { }, ".bubble": { "border-radius": px(size.border_radius), - "background-color": theme.button_bubble_bg, - color: theme.button_bubble_fg, + "background-color": theme.secondary, + color: theme.on_secondary, }, ".keyword-bubble": { "max-width": "300px", "border-radius": px(size.border_radius), "margin-bottom": px(size.vpad_small / 2), "margin-right": px(size.vpad_small / 2), - "background-color": theme.button_bubble_bg, + "background-color": theme.secondary, padding: `${px(size.vpad_small / 2)} ${px(size.vpad_small)} ${px(size.vpad_small / 2)} ${px(size.vpad_small)}`, }, ".keyword-bubble-no-padding": { "max-width": "300px", "border-radius": px(size.border_radius), margin: px(size.vpad_small / 2), - "background-color": theme.button_bubble_bg, + "background-color": theme.secondary, }, ".bubble-color": { - "background-color": theme.button_bubble_bg, - color: theme.button_bubble_fg, + "background-color": theme.secondary, + color: theme.on_secondary, }, mark: { - // 'background-color': theme.content_button, - // 'color': theme.content_button_icon, - "background-color": theme.content_accent, - color: theme.content_button_icon_selected, + "background-color": theme.primary, + color: theme.on_primary, }, ".segmentControl": { // same border as for bubble buttons @@ -1882,14 +1881,14 @@ styles.registerStyle("main", () => { "border-bottom": `${px((size.button_height - size.button_height_bubble) / 2)} solid transparent`, }, ".segmentControl-border": { - border: `1px solid ${theme.content_border}`, + border: `1px solid ${theme.outline}`, "padding-top": px(1), "padding-bottom": px(1), "padding-left": px(1), "padding-right": px(1), }, ".segmentControl-border-active": { - border: `2px solid ${theme.content_accent}`, + border: `2px solid ${theme.primary}`, "padding-top": px(0), "padding-bottom": px(0), "padding-left": px(0), @@ -1990,7 +1989,7 @@ styles.registerStyle("main", () => { background: "transparent", width: "100%", overflow: "hidden", - color: theme.content_fg, + color: theme.on_surface, }, ".input-no-clear::-ms-clear": { // remove the clear (x) button from edge input fields @@ -2006,7 +2005,7 @@ styles.registerStyle("main", () => { width: "100%", }, ".table-header-border tr:first-child": { - "border-bottom": `1px solid ${theme.content_border}`, + "border-bottom": `1px solid ${theme.outline}`, }, ".table td": { "vertical-align": "middle", @@ -2021,7 +2020,7 @@ styles.registerStyle("main", () => { ".buyOptionBox": { position: "relative", display: "inline-block", - border: `1px solid ${theme.content_border}`, + border: `1px solid ${theme.outline}`, width: "100%", padding: px(10), }, @@ -2074,10 +2073,10 @@ styles.registerStyle("main", () => { }, }, ".buyOptionBox.active": { - border: `1px solid ${theme.content_accent}`, + border: `1px solid ${theme.primary}`, }, ".buyOptionBox.highlighted": { - border: `2px solid ${theme.content_accent}`, + border: `2px solid ${theme.primary}`, padding: px(9), }, ".info-badge": { @@ -2089,7 +2088,7 @@ styles.registerStyle("main", () => { height: px(16), "text-align": "center", color: "white", - background: theme.content_button, + background: theme.on_surface_variant, }, ".tooltip": { position: "relative", @@ -2097,8 +2096,8 @@ styles.registerStyle("main", () => { }, ".tooltip .tooltiptext": { visibility: "hidden", - "background-color": theme.content_button, - color: theme.content_bg, + "background-color": theme.on_surface_variant, + color: theme.surface, "text-align": "center", padding: "5px 5px", "border-radius": px(6), @@ -2127,8 +2126,7 @@ styles.registerStyle("main", () => { }, }, ".info-badge:active": { - background: theme.content_bg, - color: theme.content_button, + background: theme.on_surface, }, ".tooltip:hover .tooltiptext, .tooltip[expanded=true] .tooltiptext": { visibility: "visible", @@ -2136,25 +2134,25 @@ styles.registerStyle("main", () => { ".ribbon-horizontal": { position: "absolute", "margin-bottom": "80px", - background: theme.content_accent, + background: theme.primary, top: "69px", left: "-6px", right: "-6px", - color: theme.content_bg, + color: theme.surface, }, ".ribbon-horizontal.nota": { background: theme.tuta_color_nota, }, ".ribbon-horizontal-cyber-monday": { background: theme.content_bg_tuta_bday, - color: theme.content_bg, + color: theme.surface, }, ".ribbon-horizontal:after": { content: '""', position: "absolute", height: 0, width: 0, - "border-left": `6px solid ${theme.content_accent}`, + "border-left": `6px solid ${theme.primary}`, "border-bottom": "6px solid transparent", bottom: "-6px", right: 0, @@ -2170,7 +2168,7 @@ styles.registerStyle("main", () => { position: "absolute", height: 0, width: 0, - "border-right": `6px solid ${theme.content_accent}`, + "border-right": `6px solid ${theme.primary}`, "border-bottom": "6px solid transparent", bottom: "-6px", left: 0, @@ -2207,7 +2205,7 @@ styles.registerStyle("main", () => { display: "block", width: px(size.checkbox_size), height: px(size.checkbox_size), - border: `2px solid ${theme.content_button}`, + border: `2px solid ${theme.on_surface_variant}`, "border-radius": "3px", position: "relative", transition: `border ${DefaultAnimationTime}ms cubic-bezier(.4,.0,.23,1)`, @@ -2217,7 +2215,7 @@ styles.registerStyle("main", () => { opacity: "1", }, ".checkbox:checked": { - border: `7px solid ${theme.content_accent}`, + border: `7px solid ${theme.primary}`, opacity: "1", }, ".checkbox:checked:after": { @@ -2235,7 +2233,7 @@ styles.registerStyle("main", () => { right: 0, bottom: 0, "line-height": "12px", - color: theme.content_bg, + color: theme.surface, "align-items": "center", width: "12px", height: "12px", @@ -2267,10 +2265,10 @@ styles.registerStyle("main", () => { opacity: "0.4", }, ".calendar-alternate-background": { - background: `${theme.list_alternate_bg} !important`, + background: `${theme.surface_container} !important`, }, ".calendar-day:hover": { - background: theme.list_alternate_bg, + background: theme.surface_container, }, ".calendar-day:hover .calendar-day-header-button": { opacity: 1, @@ -2279,15 +2277,15 @@ styles.registerStyle("main", () => { opacity: 0, }, ".calendar-hour": { - "border-bottom": `1px solid ${theme.content_border}`, + "border-bottom": `1px solid ${theme.outline}`, height: px(size.calendar_hour_height), flex: "1 0 auto", }, ".calendar-hour:hover": { - background: theme.list_alternate_bg, + background: theme.surface_container, }, ".calendar-column-border": { - "border-right": `1px solid ${theme.list_border}`, + "border-right": `1px solid ${theme.outline_variant}`, }, ".calendar-column-border:nth-child(7)": { "border-right": "none", @@ -2302,9 +2300,9 @@ styles.registerStyle("main", () => { height: px(size.calendar_days_header_height), }, ".calendar-day": { - "border-top": `1px solid ${theme.list_border}`, + "border-top": `1px solid ${theme.outline_variant}`, transition: "background 0.4s", - background: theme.list_bg, + background: theme.surface, }, ".cursor-pointer": { cursor: "pointer", @@ -2317,7 +2315,7 @@ styles.registerStyle("main", () => { "font-size": "14px", }, ".calendar-day .calendar-day-indicator:hover": { - background: theme.list_message_bg, + background: theme.surface_container, opacity: 0.7, }, ".calendar-day-number": { @@ -2326,33 +2324,33 @@ styles.registerStyle("main", () => { }, ".calendar-event": { "border-radius": px(4), - border: ` ${size.calendar_event_border}px solid ${theme.content_bg}`, + border: ` ${size.calendar_event_border}px solid ${theme.surface}`, "padding-left": "4px", "font-weight": "600", "box-sizing": "content-box", }, ".calendar-current-day-circle": { - "background-color": theme.content_button, + "background-color": theme.on_surface_variant, }, ".calendar-selected-day-circle": { - "background-color": theme.content_accent, + "background-color": theme.primary, }, ".weekday-button-unselected-circle": { - border: `${px(1)} solid ${theme.content_accent}`, + border: `${px(1)} solid ${theme.primary}`, }, ".weekday-button-unselected-text": { - color: theme.content_accent, + color: theme.primary, }, ".weekday-selector": { margin: `${px(size.vpad_small)} 0`, height: "44px", }, ".calendar-current-day-text": { - color: theme.content_bg, + color: theme.surface, "font-weight": "bold", }, ".calendar-selected-day-text": { - color: theme.content_bg, + color: theme.surface, "font-weight": "bold", }, ".animation-reverse": { @@ -2402,7 +2400,7 @@ styles.registerStyle("main", () => { }, }, ".calendar-bubble-more-padding-day .calendar-event": { - border: `1px solid ${theme.list_bg}`, + border: `1px solid ${theme.surface}`, }, ".darker-hover:hover": { filter: "brightness(95%)", @@ -2456,7 +2454,7 @@ styles.registerStyle("main", () => { }, ".calendar-long-events-header": { overflow: "hidden", - "border-bottom": `1px solid ${theme.content_border}`, + "border-bottom": `1px solid ${theme.outline}`, }, ".calendar-month-week-number": { "font-size": "12px", @@ -2490,7 +2488,7 @@ styles.registerStyle("main", () => { ".custom-color-container .inputWrapper:before": { // slash in content is content alt. so that it's ignored by screen readers content: '"#" / ""', - color: theme.content_message_bg, + color: theme.on_surface_fade, }, ".calendar-invite-field": { "min-width": "80px", @@ -2506,7 +2504,7 @@ styles.registerStyle("main", () => { position: "sticky", }, ".text-fade": { - color: theme.content_button, + color: theme.on_surface_variant, }, ".no-appearance input, .no-appearance input::-webkit-outer-spin-button, .no-appearance input::-webkit-inner-spin-button": { "-webkit-appearance": "none", @@ -2540,7 +2538,7 @@ styles.registerStyle("main", () => { cursor: "pointer", }, ".switch-month-button svg": { - fill: theme.navigation_button, + fill: theme.on_surface_variant, }, "drawer-menu": { width: px(size.drawer_menu_width), @@ -2631,8 +2629,8 @@ styles.registerStyle("main", () => { "html, body": { position: "initial", overflow: "visible !important", - color: lightTheme.content_fg, - "background-color": `${lightTheme.content_bg} !important`, + color: lightTheme.on_surface, + "background-color": `${lightTheme.surface} !important`, }, // overwrite position "fixed" otherwise only one page will be printed. ".header-nav": { @@ -2739,8 +2737,8 @@ styles.registerStyle("main", () => { "text-align": "left", }, ".bonus-month": { - background: theme.content_accent, - color: theme.content_bg, + background: theme.primary, + color: theme.surface, width: px(100), "min-width": px(100), height: px(100), @@ -2748,7 +2746,7 @@ styles.registerStyle("main", () => { "border-radius": px(100), }, ".day-events-indicator": { - "background-color": theme.content_accent, + "background-color": theme.primary, "border-radius": "50%", display: "inline-block", height: "5px", @@ -2760,13 +2758,13 @@ styles.registerStyle("main", () => { right: 0, }, ".faded-day": { - color: theme.navigation_menu_icon, + color: theme.on_surface_variant, }, ".faded-text": { - color: theme.content_message_bg, + color: theme.on_surface_fade, }, ".svg-text-content-bg text": { - fill: theme.content_bg, + fill: theme.surface, }, ".overflow-auto": { overflow: "auto", @@ -2786,7 +2784,7 @@ styles.registerStyle("main", () => { }, ".tutaui-card-container": { "box-sizing": "border-box", - "background-color": theme.content_bg, + "background-color": theme.surface, "border-radius": px(size.border_radius_medium), padding: px(size.vpad_small), height: "fit-content", @@ -2796,7 +2794,7 @@ styles.registerStyle("main", () => { }, ".tutaui-card-container-divide > *:not(:last-child)": { "border-radius": "0", - "border-bottom": `1px solid ${theme.button_bubble_bg}`, + "border-bottom": `1px solid ${theme.outline_variant}`, }, ".tutaui-text-field, .child-text-editor [role='textbox']": { display: "block", @@ -2804,26 +2802,26 @@ styles.registerStyle("main", () => { "background-color": "transparent", border: "none", "border-radius": px(size.border_radius_medium), - color: theme.content_fg, + color: theme.on_surface, width: "100%", padding: px(size.vpad_small), transition: `background-color .1s ease-out`, - "caret-color": theme.content_accent, + "caret-color": theme.primary, }, ".child-text-editor [role='textbox']:focus-visible": { outline: "medium invert color", }, ".tutaui-text-field:focus, .child-text-editor [role='textbox']:focus": { - "background-color": theme.button_bubble_bg, + "background-color": theme.secondary, }, ".tutaui-text-field::placeholder": { - color: theme.content_message_bg, + color: theme.on_surface_fade, }, ".text-editor-placeholder": { position: "absolute", top: px(size.vpad_small), left: px(size.vpad_small), - color: theme.content_message_bg, + color: theme.on_surface_fade, }, ".tutaui-switch": { display: "flex", @@ -2835,7 +2833,7 @@ styles.registerStyle("main", () => { display: "block", width: "45.5px", height: "28px", - "background-color": theme.content_message_bg, + "background-color": theme.on_surface_fade, "border-radius": px(size.vpad_small * 4), transition: `background-color ${DefaultAnimationTime}ms ease-out`, }, @@ -2862,7 +2860,7 @@ styles.registerStyle("main", () => { "background-color": "#303030", }, ".tutaui-toggle-pill.checked": { - "background-color": theme.content_accent, + "background-color": theme.primary, }, ".tutaui-toggle-pill.checked:after": { left: "calc(100% - 29px)", @@ -2939,7 +2937,7 @@ styles.registerStyle("main", () => { height: "20px", }, ".outlined": { - border: `2px solid ${theme.content_border}`, + border: `2px solid ${theme.outline}`, "border-radius": px(size.border_radius_medium), }, ".capitalize": { @@ -2955,7 +2953,7 @@ styles.registerStyle("main", () => { "min-height": px(size.vpad_xl * 4), }, ".border-content-message-bg": { - "border-color": theme.content_message_bg, + "border-color": theme.outline, }, ".border-radius-bottom-0": { "border-bottom-right-radius": px(0), diff --git a/src/common/gui/nav/DrawerMenu.ts b/src/common/gui/nav/DrawerMenu.ts index d9dd804c520c..92920522f58b 100644 --- a/src/common/gui/nav/DrawerMenu.ts +++ b/src/common/gui/nav/DrawerMenu.ts @@ -61,7 +61,7 @@ export class DrawerMenu implements Component { right: px(3), }, color: "white", - background: theme.list_accent_fg, + background: theme.primary, }) : null, ]) diff --git a/src/common/gui/nav/ViewSlider.ts b/src/common/gui/nav/ViewSlider.ts index c0b9702e8e0f..6646ee26f540 100644 --- a/src/common/gui/nav/ViewSlider.ts +++ b/src/common/gui/nav/ViewSlider.ts @@ -166,10 +166,10 @@ export class ViewSlider implements Component { zIndex: LayerType.ForegroundMenu, }, oncreate: (vnode) => { - this.busy.then(() => animations.add(vnode.dom as HTMLElement, alpha(AlphaEnum.BackgroundColor, theme.modal_bg, 0, 0.5))) + this.busy.then(() => animations.add(vnode.dom as HTMLElement, alpha(AlphaEnum.BackgroundColor, theme.scrim, 0, 0.5))) }, onbeforeremove: (vnode) => { - return this.busy.then(() => animations.add(vnode.dom as HTMLElement, alpha(AlphaEnum.BackgroundColor, theme.modal_bg, 0.5, 0))) + return this.busy.then(() => animations.add(vnode.dom as HTMLElement, alpha(AlphaEnum.BackgroundColor, theme.scrim, 0.5, 0))) }, onclick: () => { this.focus(this.visibleBackgroundColumns[0]) diff --git a/src/common/gui/theme.ts b/src/common/gui/theme.ts index c9d45cbbe832..7b53d78d4013 100644 --- a/src/common/gui/theme.ts +++ b/src/common/gui/theme.ts @@ -19,42 +19,28 @@ export type ThemePreference = ThemeId | "auto:light|dark" export type Theme = { themeId: ThemeId logo: string - button_bubble_bg: string - button_bubble_fg: string - content_bg: string + // Basic color tokens + primary: string + on_primary: string + secondary: string + on_secondary: string + error: string + surface: string + surface_container: string + /** + * @deprecated This token should not be used. + * It was created temporarily for the purpose of color theme migration. + */ + on_surface_fade: string + on_surface: string + on_surface_variant: string + outline: string + outline_variant: string + scrim: string + // Campaign colors content_bg_tuta_bday: string - content_fg: string - content_button: string - content_button_selected: string - content_button_icon: string - content_button_icon_selected: string - content_button_icon_bg?: string - content_accent: string content_accent_tuta_bday: string content_accent_secondary_tuta_bday: string - content_border: string - content_message_bg: string - header_bg: string - header_box_shadow_bg: string - header_button: string - header_button_selected: string - list_bg: string - list_alternate_bg: string - list_accent_fg: string - list_message_bg: string - list_border: string - modal_bg: string - elevated_bg?: string - navigation_bg: string - navigation_border: string - navigation_button: string - navigation_button_icon_bg?: string - navigation_button_selected: string - navigation_button_icon: string - navigation_button_icon_selected: string - navigation_menu_bg?: string - navigation_menu_icon: string - error_color: string tuta_color_nota: string highlight_bg: string highlight_fg: string @@ -62,7 +48,6 @@ export type Theme = { experimental_primary_container: string experimental_on_primary_container: string experimental_tertiary: string - outline_variant: string } const themeSingleton = {} @@ -98,30 +83,18 @@ export const themeOptions = (isCalendarApp: boolean) => }, ] as const -export function getContentButtonIconBackground(): string { - return theme.content_button_icon_bg || theme.content_button // fallback for the new color content_button_icon_bg -} - -export function getNavButtonIconBackground(): string { - return theme.navigation_button_icon_bg || theme.navigation_button // fallback for the new color content_button_icon_bg -} - export function getElevatedBackground(): string { - return theme.elevated_bg || theme.content_bg + return isColorLight(theme.surface) ? theme.surface : theme.surface_container } export function getNavigationMenuBg(): string { - return theme.navigation_menu_bg || theme.navigation_bg -} - -export function getNavigationMenuIcon(): string { - return theme.navigation_menu_icon || theme.navigation_button_icon + return isColorLight(theme.surface) ? theme.secondary : theme.surface } export function getLightOrDarkTutaLogo(isCalendarApp: boolean): string { // Use tuta logo with our brand colors const isCalendarTheme = (theme.themeId === "light" && isCalendarApp) || (theme.themeId === "light_secondary" && !isCalendarApp) - if (isColorLight(theme.content_bg) && !isCalendarTheme) { + if (isColorLight(theme.surface) && !isCalendarTheme) { return getTutaLogoSvg(tutaRed, tutaDunkel) } else { return getTutaLogoSvg(logoDefaultGrey, logoDefaultGrey) diff --git a/src/common/gui/titles/MenuTitle.ts b/src/common/gui/titles/MenuTitle.ts index 88eea983c5ed..ba3e07415dac 100644 --- a/src/common/gui/titles/MenuTitle.ts +++ b/src/common/gui/titles/MenuTitle.ts @@ -8,6 +8,6 @@ export type MenuTitleAttrsType = { // used in sidebar section title and inside setting view menus export class MenuTitle implements Component { view({ attrs }: Vnode): Children { - return m("small.uppercase.b", { style: { color: theme.navigation_button } }, attrs.content) + return m("small.uppercase.b", { style: { color: theme.on_surface_variant } }, attrs.content) } } diff --git a/src/common/misc/2fa/SecondFactorAuthView.ts b/src/common/misc/2fa/SecondFactorAuthView.ts index 716444b9c41b..4c8e90e8663a 100644 --- a/src/common/misc/2fa/SecondFactorAuthView.ts +++ b/src/common/misc/2fa/SecondFactorAuthView.ts @@ -105,7 +105,7 @@ export class SecondFactorAuthView implements Component { icon: Icons.Cancel, size: IconSize.Medium, style: { - fill: theme.content_accent, + fill: theme.primary, }, }), ), diff --git a/src/common/misc/TranslationKey.ts b/src/common/misc/TranslationKey.ts index 81a78901a3f9..b0d4f06eb7cb 100644 --- a/src/common/misc/TranslationKey.ts +++ b/src/common/misc/TranslationKey.ts @@ -2017,3 +2017,6 @@ export type TranslationKeyType = | "zoomIn_action" | "zoomOut_action" | "emptyString_msg" + | "updateColorCustomizationNews_title" + | "updateColorCustomizationNews_msg" + | "updateColorCustomizationNewsButton_label" diff --git a/src/common/misc/WhitelabelCustomizations.ts b/src/common/misc/WhitelabelCustomizations.ts index 1d410d2a5a94..32269d30dc5e 100644 --- a/src/common/misc/WhitelabelCustomizations.ts +++ b/src/common/misc/WhitelabelCustomizations.ts @@ -1,6 +1,7 @@ -import type { BaseThemeId, Theme } from "../gui/theme" +import { BaseThemeId, theme, Theme } from "../gui/theme" import { assertMainOrNodeBoot } from "../api/common/Env" import type { WhitelabelConfig } from "../api/entities/sys/TypeRefs.js" +import { ThemeController } from "../gui/ThemeController.js" assertMainOrNodeBoot() export type ThemeCustomizations = Partial & { @@ -27,5 +28,5 @@ export function getWhitelabelCustomizations(window: Window): WhitelabelCustomiza } export function getThemeCustomizations(whitelabelConfig: WhitelabelConfig): ThemeCustomizations { - return JSON.parse(whitelabelConfig.jsonTheme, (k, v) => (k === "__proto__" ? undefined : v)) + return ThemeController.mapOldToNewColorTokens(JSON.parse(whitelabelConfig.jsonTheme, (k, v) => (k === "__proto__" ? undefined : v))) } diff --git a/src/common/misc/news/NewsDialog.ts b/src/common/misc/news/NewsDialog.ts index 99377316e33c..7cef5f115dbd 100644 --- a/src/common/misc/news/NewsDialog.ts +++ b/src/common/misc/news/NewsDialog.ts @@ -39,6 +39,7 @@ export function showNewsDialog(newsModel: NewsModel) { ? m(NewsList, { liveNewsIds: newsModel.liveNewsIds, liveNewsListItems: newsModel.liveNewsListItems, + dialog, }) : m( ".flex-center.mt-l", diff --git a/src/common/misc/news/NewsList.ts b/src/common/misc/news/NewsList.ts index 5b651d37c799..e480eab5b569 100644 --- a/src/common/misc/news/NewsList.ts +++ b/src/common/misc/news/NewsList.ts @@ -4,10 +4,12 @@ import { NewsListItem } from "./NewsListItem.js" import ColumnEmptyMessageBox from "../../gui/base/ColumnEmptyMessageBox.js" import { theme } from "../../gui/theme.js" import { Icons } from "../../gui/base/icons/Icons.js" +import { Dialog } from "../../gui/base/Dialog.js" export interface NewsListAttrs { liveNewsListItems: Record liveNewsIds: NewsId[] + dialog: Dialog } /** @@ -19,7 +21,7 @@ export class NewsList implements Component { return m(ColumnEmptyMessageBox, { message: "noNews_msg", icon: Icons.Bulb, - color: theme.content_message_bg, + color: theme.on_surface_fade, }) } @@ -28,7 +30,11 @@ export class NewsList implements Component { vnode.attrs.liveNewsIds.map((liveNewsId) => { const newsListItem = vnode.attrs.liveNewsListItems[liveNewsId.newsItemName] - return m(".pt.pl-l.pr-l.flex.fill.border-grey.left.list-border-bottom", { key: liveNewsId.newsItemId }, newsListItem.render(liveNewsId)) + return m( + ".pt.pl-l.pr-l.flex.fill.border-grey.left.list-border-bottom", + { key: liveNewsId.newsItemId }, + newsListItem.render(liveNewsId, vnode.attrs.dialog), + ) }), ) } diff --git a/src/common/misc/news/NewsListItem.ts b/src/common/misc/news/NewsListItem.ts index ab16bee737de..e8f8514f6a21 100644 --- a/src/common/misc/news/NewsListItem.ts +++ b/src/common/misc/news/NewsListItem.ts @@ -1,5 +1,6 @@ import { Children } from "mithril" import { NewsId } from "../../api/entities/tutanota/TypeRefs.js" +import { Dialog } from "../../gui/base/Dialog.js" /** * News items must implement this interface to be rendered. @@ -8,7 +9,7 @@ export interface NewsListItem { /** * Returns the rendered NewsItem. Should display a button that acknowledges the news via NewsModel.acknowledge(). */ - render(newsId: NewsId): Children + render(newsId: NewsId, dialog?: Dialog): Children /** * Return true iff the news should be shown to the logged-in user. diff --git a/src/common/misc/news/items/UpdateColorCustomizationNews.ts b/src/common/misc/news/items/UpdateColorCustomizationNews.ts new file mode 100644 index 000000000000..20d2e0d0569b --- /dev/null +++ b/src/common/misc/news/items/UpdateColorCustomizationNews.ts @@ -0,0 +1,47 @@ +import { NewsListItem } from "../NewsListItem.js" +import m, { Children } from "mithril" +import { NewsId } from "../../../api/entities/tutanota/TypeRefs.js" +import { lang } from "../../LanguageViewModel.js" +import { Button, ButtonType } from "../../../gui/base/Button.js" +import { NewsModel } from "../NewsModel.js" +import { UserController } from "../../../api/main/UserController.js" +import { Dialog } from "../../../gui/base/Dialog.js" + +/** + * This news item informs admin users that color customization may need updating. + */ +export class UpdateColorCustomizationNews implements NewsListItem { + constructor(private readonly newsModel: NewsModel, private readonly userController: UserController) {} + + async isShown(): Promise { + return this.userController.isGlobalAdmin() && this.userController.isWhitelabelAccount() + } + + render(newsId: NewsId, dialog: Dialog): Children { + const acknowledge = () => { + this.newsModel.acknowledgeNews(newsId.newsItemId).then(m.redraw) + } + + return m(".full-width", [ + m(".h4.pb", lang.get("updateColorCustomizationNews_title")), + m(".pb", lang.get("updateColorCustomizationNews_msg")), + m( + ".flex-end.gap-hpad.flex-no-grow-no-shrink-auto.flex-wrap", + m(Button, { + label: "updateColorCustomizationNewsButton_label", + click: async () => { + m.route.set("/settings/whitelabel") + acknowledge() + dialog.close() + }, + type: ButtonType.Primary, + }), + m(Button, { + label: "close_alt", + click: acknowledge, + type: ButtonType.Secondary, + }), + ), + ]) + } +} diff --git a/src/common/ratings/pages/DissatisfactionPage.ts b/src/common/ratings/pages/DissatisfactionPage.ts index ce0152aacb00..28d7ab97a331 100644 --- a/src/common/ratings/pages/DissatisfactionPage.ts +++ b/src/common/ratings/pages/DissatisfactionPage.ts @@ -54,7 +54,7 @@ export class DissatisfactionPage implements Component leftIcon: { icon: Icons.BulbOutline, title: "ratingSuggestion_label", - fill: theme.content_accent, + fill: theme.primary, }, text: "ratingSuggestion_label", onclick: () => { @@ -65,7 +65,7 @@ export class DissatisfactionPage implements Component leftIcon: { icon: Icons.AlertCircleOutline, title: "ratingNeedUrgentHelp_label", - fill: theme.content_accent, + fill: theme.primary, }, text: "ratingNeedUrgentHelp_label", onclick: () => { diff --git a/src/common/settings/PasswordForm.ts b/src/common/settings/PasswordForm.ts index 0f8afa3cc928..98ffd8100e74 100644 --- a/src/common/settings/PasswordForm.ts +++ b/src/common/settings/PasswordForm.ts @@ -276,7 +276,7 @@ export class PasswordForm implements Component { return m( "button.b.mr-xs.hover.click.darkest-hover.mt-xs", { - style: { display: "inline-block", color: theme.navigation_button_selected }, + style: { display: "inline-block", color: theme.primary }, onclick: async () => { attrs.model.setNewPassword(await showPasswordGeneratorDialog()) m.redraw() diff --git a/src/common/settings/SettingsBannerButton.ts b/src/common/settings/SettingsBannerButton.ts index 48aff2737860..336721e41094 100644 --- a/src/common/settings/SettingsBannerButton.ts +++ b/src/common/settings/SettingsBannerButton.ts @@ -8,8 +8,8 @@ import { theme } from "../gui/theme.js" export function renderSettingsBannerButton(text: TranslationKey, onclick: ClickHandler, isDisabled?: boolean, classes?: string) { return m(BannerButton, { text, - borderColor: theme.content_accent, - color: theme.content_accent, + borderColor: theme.primary, + color: theme.primary, class: "b full-width button-content " + classes, click: (event: MouseEvent, dom: HTMLElement) => { onclick(event, dom) diff --git a/src/common/settings/UserListView.ts b/src/common/settings/UserListView.ts index aee25825e228..8bdfc83c0e06 100644 --- a/src/common/settings/UserListView.ts +++ b/src/common/settings/UserListView.ts @@ -119,7 +119,7 @@ export class UserListView implements UpdatableSettingsViewer { }, this.listModel.isEmptyAndDone() ? m(ColumnEmptyMessageBox, { - color: theme.list_message_bg, + color: theme.on_surface_fade, icon: BootIcons.Contacts, message: "noEntries_msg", }) diff --git a/src/common/settings/keymanagement/KeyManagementSettingsViewer.ts b/src/common/settings/keymanagement/KeyManagementSettingsViewer.ts index a81c8d06953a..fb68e64750f1 100644 --- a/src/common/settings/keymanagement/KeyManagementSettingsViewer.ts +++ b/src/common/settings/keymanagement/KeyManagementSettingsViewer.ts @@ -83,7 +83,7 @@ export class KeyManagementSettingsViewer implements UpdatableSettingsViewer { ".fill-absolute.scroll.plr-l.pb-xl", { style: { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, gap: "16px", display: "flex", flexDirection: "column", diff --git a/src/common/settings/keymanagement/dialogpages/VerificationByManualInputPage.ts b/src/common/settings/keymanagement/dialogpages/VerificationByManualInputPage.ts index d6120134e26c..70a2208d899e 100644 --- a/src/common/settings/keymanagement/dialogpages/VerificationByManualInputPage.ts +++ b/src/common/settings/keymanagement/dialogpages/VerificationByManualInputPage.ts @@ -101,7 +101,7 @@ export class VerificationByManualInputPage implements Component - header: Array - navigation: Array - other: Array -} export const COLOR_PICKER_WIDTH = 400 export const ADVANCED_TEXTFIELD_WIDTH = 344 export const CATEGORY_WIDTH = 750 @@ -44,33 +38,14 @@ export class CustomColorEditor implements Component - renderColorPicker( - (inputEvent) => { - vnode.attrs.model.changeAccentColor(downcast(inputEvent.target).value) - m.redraw() - }, - vnode.attrs.model.accentColor, - ({ dom }) => (this._colorPickerDom = dom as HTMLInputElement), - ), + renderColorPicker((inputEvent) => { + vnode.attrs.model.changeAccentColor(downcast(inputEvent.target).value) + m.redraw() + }, vnode.attrs.model.accentColor), maxWidth: COLOR_PICKER_WIDTH, isReadOnly: true, } - /* - Currently: - Button: - Content: - Elevated: - Header: - List: - Modal: - Navigation: - Then: - Content: Content, List - Header: Header - Navigation: Navigation - Other: Button, Elevated, Modal - */ return m("", [ m("", [ m(".flex", [ @@ -112,52 +87,23 @@ export class CustomColorEditor implements Component { - return m("", [ - m(".h4.mt-l", capitalizeFirstLetterOfString(name)), - m( - ".editor-border.text-break.wrapping-row", - { - style: { - maxWidth: px(CATEGORY_WIDTH), - }, + m("", [ + m( + ".editor-border.text-break.wrapping-row", + { + style: { + maxWidth: px(CATEGORY_WIDTH), }, - [colors.map((c) => renderCustomColorField(model, c))], - ), - ]) - }), + }, + [model.customColors.map((c) => renderCustomColorField(model, c))], + ), + ]), ]), ], ), ]), ]) } - - /** - * - */ - _getGroupedColors(colors: ReadonlyArray): ColorCategories { - const groupedColors: ColorCategories = { - content: [], - header: [], - navigation: [], - other: [], - } - - for (const color of colors) { - if (color.name.startsWith("content") || color.name.startsWith("list")) { - groupedColors.content.push(color) - } else if (color.name.startsWith("header")) { - groupedColors.header.push(color) - } else if (color.name.startsWith("navigation")) { - groupedColors.navigation.push(color) - } else { - groupedColors.other.push(color) - } - } - - return groupedColors - } } function renderCustomColorField(model: CustomColorsEditorViewModel, { name, value, defaultValue, valid }: CustomColor): Child { diff --git a/src/common/settings/whitelabel/CustomColorsEditorViewModel.ts b/src/common/settings/whitelabel/CustomColorsEditorViewModel.ts index c2e61f40c0ab..db2f4ea3e7f6 100644 --- a/src/common/settings/whitelabel/CustomColorsEditorViewModel.ts +++ b/src/common/settings/whitelabel/CustomColorsEditorViewModel.ts @@ -49,7 +49,7 @@ export class CustomColorsEditorViewModel { this.builtTheme = stream() const baseThemeId = themeCustomizations.base ?? "light" - const accentColor = themeCustomizations.content_accent ?? this._themeController.getDefaultTheme().content_accent + const accentColor = themeCustomizations.primary ?? this._themeController.getDefaultTheme().primary this.changeBaseTheme(baseThemeId) this.changeAccentColor(accentColor) @@ -98,12 +98,7 @@ export class CustomColorsEditorViewModel { changeAccentColor(accentColor: string) { this._accentColor = accentColor - this.addCustomization("list_accent_fg", accentColor) - this.addCustomization("content_accent", accentColor) - this.addCustomization("content_button_selected", accentColor) - this.addCustomization("navigation_button_selected", accentColor) - this.addCustomization("header_button_selected", accentColor) - + this.addCustomization("primary", accentColor) this._applyEditedTheme() } @@ -127,7 +122,7 @@ export class CustomColorsEditorViewModel { } this.addCustomization("themeId", this._whitelabelDomainInfo.domain) - this._whitelabelConfig.jsonTheme = JSON.stringify(this.customizations) + this._whitelabelConfig.jsonTheme = JSON.stringify(ThemeController.mapNewToOldColorTokens(this.customizations)) await this._entityClient.update(this._whitelabelConfig) if (!this._loginController.isWhitelabel()) { @@ -159,7 +154,7 @@ export class CustomColorsEditorViewModel { this._applyEditedTheme() } - _isValidColorValue(colorValue: string): boolean { + private _isValidColorValue(colorValue: string): boolean { return isValidColorCode(colorValue.trim()) || colorValue.trim() === "" } @@ -167,24 +162,12 @@ export class CustomColorsEditorViewModel { * These values shall be excluded when rendering the advanced TextFields * @return boolean, true iff provided parameter 'name' shall be excluded */ - _shallBeExcluded(name: CustomizationKey): boolean { - const excludedColors = [ - "logo", - "themeId", - "base", - "list_accent_fg", - "content_button_selected", - "navigation_button_selected", - "header_button_selected", - "content_accent", - "content_accent_tuta_bday", - "content_accent_secondary_tuta_bday", - "content_bg_tuta_bday", - ] + private _shallBeExcluded(name: CustomizationKey): boolean { + const excludedColors = ["logo", "themeId", "base", "content_accent_tuta_bday", "content_accent_secondary_tuta_bday", "content_bg_tuta_bday"] return excludedColors.includes(name) } - _applyEditedTheme: () => void = debounceStart(100, () => { + private _applyEditedTheme: () => void = debounceStart(100, () => { this._removeEmptyCustomizations() this._themeController.applyCustomizations(this._filterAndReturnCustomizations(), false) diff --git a/src/common/subscription/BuyOptionBox.ts b/src/common/subscription/BuyOptionBox.ts index c9e2091445e4..0c0e198bb903 100644 --- a/src/common/subscription/BuyOptionBox.ts +++ b/src/common/subscription/BuyOptionBox.ts @@ -162,7 +162,7 @@ export class BuyOptionBox implements Component { } if (attrs.isFirstMonthForFree && isPersonalPaidPlan && isYearly) { - const isDarkTheme = !isColorLight(theme.content_bg) + const isDarkTheme = !isColorLight(theme.surface) return m( ".ribbon-horizontal.nota", m(".text-center.b", { style: { padding: px(3), color: isDarkTheme ? "#fff" : undefined } }, lang.get("oneMonthTrial_label")), @@ -238,7 +238,7 @@ export class BuyOptionBox implements Component { ".span.strike", { style: { - color: shouldApplyCampaignColor ? theme.content_accent_tuta_bday : theme.content_button, + color: shouldApplyCampaignColor ? theme.content_accent_tuta_bday : theme.on_surface_variant, fontSize: px(size.font_size_base), justifySelf: "end", margin: "auto 0.4em 0 0", diff --git a/src/common/subscription/Captcha.ts b/src/common/subscription/Captcha.ts index a9322829dfed..a820ba4c5ae8 100644 --- a/src/common/subscription/Captcha.ts +++ b/src/common/subscription/Captcha.ts @@ -149,9 +149,9 @@ function showCaptchaDialog(challenge: Uint8Array, token: string): Promise { bottom: 0, left: 0, right: 0, - "background-color": theme.content_bg, + "background-color": theme.surface, "z-index": 1, - "box-shadow": boxShadow, + "box-shadow": boxShadowHigh, }, }, m( diff --git a/src/common/subscription/VariantBSubscriptionPage.ts b/src/common/subscription/VariantBSubscriptionPage.ts index 733ef73945f5..b7d9c82a3d93 100644 --- a/src/common/subscription/VariantBSubscriptionPage.ts +++ b/src/common/subscription/VariantBSubscriptionPage.ts @@ -351,7 +351,7 @@ export function getPrivateBusinessSwitchButton(businessUse: Stream, upd size: IconSize.Large, class: "mr-xsm", style: { - fill: theme.content_accent, + fill: theme.primary, "vertical-align": "sub", }, }), diff --git a/src/common/support/pages/SupportLandingPage.ts b/src/common/support/pages/SupportLandingPage.ts index 8eafa805d37e..2c05b7988bf3 100644 --- a/src/common/support/pages/SupportLandingPage.ts +++ b/src/common/support/pages/SupportLandingPage.ts @@ -45,7 +45,7 @@ export class SupportLandingPage implements Component { ".pb.pt.flex.col.gap-vpad.fit-height.box-content", categories.map((category) => m(SectionButton, { - leftIcon: { icon: category.icon as AllIcons, title: "close_alt", fill: theme.content_accent }, + leftIcon: { icon: category.icon as AllIcons, title: "close_alt", fill: theme.primary }, text: { text: getCategoryName(category, lang.languageTag), testId: "" }, onclick: () => { selectedCategory(category) diff --git a/src/common/support/pages/SupportTopicPage.ts b/src/common/support/pages/SupportTopicPage.ts index 7e21574b7d57..12e20e531419 100644 --- a/src/common/support/pages/SupportTopicPage.ts +++ b/src/common/support/pages/SupportTopicPage.ts @@ -81,7 +81,7 @@ class WasThisHelpful implements Component { view({ attrs: { goToContactSupportPage, goToSolutionWasHelpfulPage, topicName } }: Vnode): Children { return m( ".flex.flex-column.gap-vpad-s", - m("small.uppercase.b.text-ellipsis", { style: { color: theme.navigation_button } }, lang.get("wasThisHelpful_msg")), + m("small.uppercase.b.text-ellipsis", { style: { color: theme.on_surface_variant } }, lang.get("wasThisHelpful_msg")), m(Card, { shouldDivide: true }, [ m(SectionButton, { text: "yes_label", diff --git a/src/mail-app/contacts/view/ContactCardViewer.ts b/src/mail-app/contacts/view/ContactCardViewer.ts index bf698af0b2c1..885498e66aac 100644 --- a/src/mail-app/contacts/view/ContactCardViewer.ts +++ b/src/mail-app/contacts/view/ContactCardViewer.ts @@ -24,7 +24,7 @@ export class ContactCardViewer implements Component { { class: responsiveCardHMargin(), style: { - backgroundColor: theme.content_bg, + backgroundColor: theme.surface, ...attrs.style, }, }, diff --git a/src/mail-app/contacts/view/ContactListEntryViewer.ts b/src/mail-app/contacts/view/ContactListEntryViewer.ts index cb4d97e8cc60..241620d5a6ac 100644 --- a/src/mail-app/contacts/view/ContactListEntryViewer.ts +++ b/src/mail-app/contacts/view/ContactListEntryViewer.ts @@ -26,7 +26,7 @@ export class ContactListEntryViewer implements Component }, listModel == null || listModel.isEmptyAndDone() ? m(ColumnEmptyMessageBox, { - color: theme.list_message_bg, + color: theme.on_surface_fade, message: "noEntries_msg", icon: Icons.People, }) diff --git a/src/mail-app/contacts/view/ContactListView.ts b/src/mail-app/contacts/view/ContactListView.ts index eeb053bbdd51..d024453dc99f 100644 --- a/src/mail-app/contacts/view/ContactListView.ts +++ b/src/mail-app/contacts/view/ContactListView.ts @@ -31,7 +31,7 @@ export class ContactListView implements ClassComponent { }, contactViewModel.listModel.isEmptyAndDone() ? m(ColumnEmptyMessageBox, { - color: theme.list_message_bg, + color: theme.on_surface_fade, message: "noContacts_msg", icon: BootIcons.Contacts, }) diff --git a/src/mail-app/contacts/view/ContactView.ts b/src/mail-app/contacts/view/ContactView.ts index d48a06ec29ef..4e6e8064ca0a 100644 --- a/src/mail-app/contacts/view/ContactView.ts +++ b/src/mail-app/contacts/view/ContactView.ts @@ -143,7 +143,7 @@ export class ContactView extends BaseTopLevelView implements TopLevelView m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, desktopToolbar: () => m(DesktopViewerToolbar, this.detailsViewerActions()), mobileHeader: () => m(MobileHeader, { @@ -184,7 +184,7 @@ export class ContactView extends BaseTopLevelView implements TopLevelView { @@ -218,7 +218,7 @@ export class ContactView extends BaseTopLevelView implements TopLevelView { @@ -406,7 +406,7 @@ export class ContactView extends BaseTopLevelView implements TopLevelView 0 ? m(Button, { @@ -415,7 +415,7 @@ export class ContactView extends BaseTopLevelView implements TopLevelView this.contactListViewModel.listModel?.selectNone(), }) : null, - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, }) : m(ContactListEntryViewer, { entry: getFirstOrThrow(entries), diff --git a/src/mail-app/contacts/view/MultiContactViewer.ts b/src/mail-app/contacts/view/MultiContactViewer.ts index 9b356ad2f02a..101162f1e817 100644 --- a/src/mail-app/contacts/view/MultiContactViewer.ts +++ b/src/mail-app/contacts/view/MultiContactViewer.ts @@ -23,7 +23,7 @@ export class MultiContactViewer implements Component { m(ColumnEmptyMessageBox, { message: getContactSelectionMessage(attrs.selectedEntities.length), icon: BootIcons.Contacts, - color: theme.content_message_bg, + color: theme.on_surface_fade, bottomContent: attrs.selectedEntities.length > 0 ? m(Button, { @@ -32,7 +32,7 @@ export class MultiContactViewer implements Component { click: () => attrs.selectNone(), }) : undefined, - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, }), ] } diff --git a/src/mail-app/mail/view/CollapsedMailView.ts b/src/mail-app/mail/view/CollapsedMailView.ts index adaf002b3ff7..2f215d2cc401 100644 --- a/src/mail-app/mail/view/CollapsedMailView.ts +++ b/src/mail-app/mail/view/CollapsedMailView.ts @@ -31,7 +31,7 @@ export class CollapsedMailView implements Component { "aria-expanded": "false", "data-testid": "collapsed-mail-view", style: { - color: theme.content_button, + color: theme.on_surface_variant, }, onclick: () => viewModel.expandMail(Promise.resolve()), onkeyup: (e: KeyboardEvent) => { @@ -87,7 +87,7 @@ export class CollapsedMailView implements Component { icon, container: "div", style: { - fill: theme.content_button, + fill: theme.on_surface_variant, }, hoverText: hoverText, }) diff --git a/src/mail-app/mail/view/ConversationViewer.ts b/src/mail-app/mail/view/ConversationViewer.ts index 246141803b8c..67a025f15f7d 100644 --- a/src/mail-app/mail/view/ConversationViewer.ts +++ b/src/mail-app/mail/view/ConversationViewer.ts @@ -188,7 +188,7 @@ export class ConversationViewer implements Component { ".font-weight-600.center.mt-l" + "." + responsiveCardHMargin(), { style: { - color: theme.content_button, + color: theme.on_surface_variant, }, }, lang.get("loading_msg"), @@ -211,7 +211,7 @@ export class ConversationViewer implements Component { class: responsiveCardHMargin(), key: elementIdPart(mailViewModel.mail.conversationEntry), style: { - backgroundColor: theme.content_bg, + backgroundColor: theme.surface, marginTop: px(position == null || position === 0 ? 0 : conversationCardMargin), }, }, diff --git a/src/mail-app/mail/view/EditFoldersDialog.ts b/src/mail-app/mail/view/EditFoldersDialog.ts index 301db40a5cd6..bb659b0600b2 100644 --- a/src/mail-app/mail/view/EditFoldersDialog.ts +++ b/src/mail-app/mail/view/EditFoldersDialog.ts @@ -77,7 +77,7 @@ export class EditFoldersDialog implements ModalComponent { this._domDialog = vnode.dom as HTMLElement let animation: AnimationPromise | null = null - const bgcolor = theme.navigation_bg + const bgcolor = theme.surface_container const children = Array.from(this._domDialog.children) as Array for (let child of children) { child.style.opacity = "0" @@ -115,7 +115,7 @@ export class EditFoldersDialog implements ModalComponent { { onscroll: (e: Event) => { const target = e.target as HTMLElement - target.style.borderTop = `1px solid ${theme.content_border}` + target.style.borderTop = `1px solid ${theme.outline}` }, }, this.folderList(), diff --git a/src/mail-app/mail/view/LabelsPopup.ts b/src/mail-app/mail/view/LabelsPopup.ts index 724f6b199c3e..ef91d6e2f7e1 100644 --- a/src/mail-app/mail/view/LabelsPopup.ts +++ b/src/mail-app/mail/view/LabelsPopup.ts @@ -71,7 +71,7 @@ export class LabelsPopup implements ModalComponent { ".pb-s.scroll", this.labels.map((labelState) => { const { label, state } = labelState - const color = theme.content_button + const color = theme.on_surface_variant const canToggleLabel = state === LabelState.Applied || state === LabelState.AppliedToSome || !this.isMaxLabelsReached const opacity = !canToggleLabel ? 0.5 : undefined diff --git a/src/mail-app/mail/view/MailFolderRow.ts b/src/mail-app/mail/view/MailFolderRow.ts index 21143cfbe21a..9e7fe6191ac6 100644 --- a/src/mail-app/mail/view/MailFolderRow.ts +++ b/src/mail-app/mail/view/MailFolderRow.ts @@ -2,7 +2,7 @@ import m, { Children, Component, Vnode } from "mithril" import type { NavButtonAttrs } from "../../../common/gui/base/NavButton.js" import { isNavButtonSelected, NavButton } from "../../../common/gui/base/NavButton.js" import { CounterBadge } from "../../../common/gui/base/CounterBadge" -import { getNavButtonIconBackground, theme } from "../../../common/gui/theme" +import { theme } from "../../../common/gui/theme" import { px, size } from "../../../common/gui/size" import { IconButton, IconButtonAttrs } from "../../../common/gui/base/IconButton.js" import { Icon, IconSize } from "../../../common/gui/base/Icon.js" @@ -81,7 +81,7 @@ export class MailFolderRow implements Component { position: "absolute", bottom: px(9), left: px(5 + indentationMargin + buttonWidth / 2), - fill: isNavButtonSelected(button) ? theme.navigation_button_selected : theme.navigation_button, + fill: isNavButtonSelected(button) ? theme.primary : theme.on_surface_variant, }, icon: Icons.Add, class: "icon-small", @@ -114,7 +114,7 @@ export class MailFolderRow implements Component { icon, size: IconSize.Medium, style: { - fill: isNavButtonSelected(button) ? theme.navigation_button_selected : theme.navigation_button, + fill: isNavButtonSelected(button) ? theme.primary : theme.on_surface_variant, }, }), ), @@ -135,8 +135,8 @@ export class MailFolderRow implements Component { : m("", { style: { marginRight: px(size.hpad_button) } }, [ m(CounterBadge, { count, - color: theme.navigation_button_icon, - background: getNavButtonIconBackground(), + color: theme.surface_container, + background: theme.on_surface_variant, showFullCount: true, }), ]), @@ -146,7 +146,7 @@ export class MailFolderRow implements Component { private renderHierarchyLine({ indentationLevel, numberOfPreviousRows, isLastSibling, onSelectedPath }: MailFolderRowAttrs, indentationMargin: number) { const lineSize = 2 - const border = `${lineSize}px solid ${theme.content_border}` + const border = `${lineSize}px solid ${theme.outline}` const verticalOffsetInsideRow = size.button_height / 2 + 1 const verticalOffsetForParent = (size.button_height - size.icon_size_large) / 2 const lengthOfHorizontalLine = size.hpad - 2 @@ -178,7 +178,7 @@ export class MailFolderRow implements Component { top: px(verticalOffsetInsideRow), left: px(leftOffset), width: px(lengthOfHorizontalLine), - backgroundColor: theme.content_border, + backgroundColor: theme.outline, }, }), ] diff --git a/src/mail-app/mail/view/MailListView.ts b/src/mail-app/mail/view/MailListView.ts index 4c68f2691876..065e621fef1d 100644 --- a/src/mail-app/mail/view/MailListView.ts +++ b/src/mail-app/mail/view/MailListView.ts @@ -358,7 +358,7 @@ export class MailListView implements Component { ? m(ColumnEmptyMessageBox, { icon: BootIcons.Mail, message: "noMails_msg", - color: theme.list_message_bg, + color: theme.on_surface_fade, }) : m(List, { state: listModel.stateStream(), diff --git a/src/mail-app/mail/view/MailRow.ts b/src/mail-app/mail/view/MailRow.ts index 028906ff2265..0163f0647caf 100644 --- a/src/mail-app/mail/view/MailRow.ts +++ b/src/mail-app/mail/view/MailRow.ts @@ -149,7 +149,7 @@ export class MailRow implements VirtualRow { if (label) { element.style.display = "" element.style.backgroundColor = getLabelColor(label.color) - element.style.color = colorForBg(label.color ?? theme.content_accent) + element.style.color = colorForBg(label.color ?? theme.primary) element.textContent = label.name } else { element.style.display = "none" @@ -341,8 +341,8 @@ export class MailRow implements VirtualRow { { style: { // in dark theme override saturation to aid readability. This is not relative but absolute saturation. We preserve the hue. - border: `2px solid ${getLabelColor(theme.content_button)}`, - color: getLabelColor(theme.content_button), + border: `2px solid ${getLabelColor(theme.on_surface_variant)}`, + color: getLabelColor(theme.on_surface_variant), padding: `0px ${size.vpad_xsm}px 1px`, marginRight: px(size.vpad_xsm), minWidth: px(16), diff --git a/src/mail-app/mail/view/MailView.ts b/src/mail-app/mail/view/MailView.ts index d6b4aa07f8e0..9fccf94a4d00 100644 --- a/src/mail-app/mail/view/MailView.ts +++ b/src/mail-app/mail/view/MailView.ts @@ -133,7 +133,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView { const folder = this.mailViewModel.getFolder() return m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, desktopToolbar: () => m(DesktopListToolbar, m(SelectAllCheckbox, selectionAttrsForList(this.mailViewModel)), this.renderFilterButton()), columnLayout: folder ? m( @@ -308,7 +308,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView m(DesktopViewerToolbar, this.mailViewerSingleActions(viewModel)), mobileHeader: () => m(MobileHeader, { @@ -376,7 +376,7 @@ export class MailView extends BaseTopLevelView implements TopLevelView m(DesktopViewerToolbar, this.mailViewerMultiActions()), mobileHeader: () => m(MobileHeader, { diff --git a/src/mail-app/mail/view/MailViewer.ts b/src/mail-app/mail/view/MailViewer.ts index 4e62bf005b2e..f582bf7a8977 100644 --- a/src/mail-app/mail/view/MailViewer.ts +++ b/src/mail-app/mail/view/MailViewer.ts @@ -217,8 +217,8 @@ export class MailViewer implements Component { { style: { borderRadius: "25%", - border: `1px solid ${theme.list_border}`, - backgroundColor: theme.content_bg, + border: `1px solid ${theme.outline_variant}`, + backgroundColor: theme.surface, }, }, m(ToggleButton, { @@ -276,7 +276,7 @@ export class MailViewer implements Component { return m(IconMessageBox, { message: "corrupted_msg", icon: Icons.Warning, - color: theme.content_message_bg, + color: theme.on_surface_fade, }) } @@ -453,7 +453,7 @@ export class MailViewer implements Component { const quoteIndicator = document.createElement("div") quoteIndicator.classList.add("flex") - quoteIndicator.style.borderLeft = `2px solid ${theme.content_border}` + quoteIndicator.style.borderLeft = `2px solid ${theme.outline}` quoteIndicator.style.display = expanded ? "none" : "" m.render( @@ -463,7 +463,7 @@ export class MailViewer implements Component { class: "icon-xl mlr", container: "div", style: { - fill: theme.navigation_menu_icon, + fill: theme.on_surface_variant, }, }), ) diff --git a/src/mail-app/mail/view/MailViewerHeader.ts b/src/mail-app/mail/view/MailViewerHeader.ts index 98dd8de769d1..3bd94ea2d3ae 100644 --- a/src/mail-app/mail/view/MailViewerHeader.ts +++ b/src/mail-app/mail/view/MailViewerHeader.ts @@ -114,7 +114,7 @@ export class MailViewerHeader implements Component { icon, container: "div", style: { - fill: theme.content_button, + fill: theme.on_surface_variant, marginLeft: margin, }, }), @@ -124,7 +124,7 @@ export class MailViewerHeader implements Component { labels.map((label) => m(Label, { text: label.name, - color: label.color ?? theme.content_accent, + color: label.color ?? theme.primary, }), ), ], @@ -174,7 +174,7 @@ export class MailViewerHeader implements Component { icon: getConfidentialIcon(viewModel.mail), container: "div", style: { - fill: theme.content_button, + fill: theme.on_surface_variant, }, hoverText: lang.get("confidential_label"), }) @@ -184,11 +184,11 @@ export class MailViewerHeader implements Component { icon: getFolderIconByType(folderInfo.folderType), container: "div", style: { - fill: theme.content_button, + fill: theme.on_surface_variant, }, hoverText: folderInfo.name, }), - m(".small.font-weight-600.selectable.no-wrap", { style: { color: theme.content_button } }, [ + m(".small.font-weight-600.selectable.no-wrap", { style: { color: theme.on_surface_variant } }, [ m(".noprint", dateTime), // show the short date when viewing m(".noscreen", dateTimeFull), // show the date with year when printing ]), @@ -245,7 +245,7 @@ export class MailViewerHeader implements Component { icon: Icons.Edit, container: "div", style: { - fill: theme.content_button, + fill: theme.on_surface_variant, }, hoverText: lang.get("draft_label"), }), @@ -564,7 +564,7 @@ export class MailViewerHeader implements Component { "font-weight": "normal", }, expanded: this.filesExpanded, - color: theme.content_fg, + color: theme.on_surface, isBig: true, isUnformattedLabel: true, onExpandedChange: (change) => { @@ -868,7 +868,7 @@ export class MailViewerHeader implements Component { icon: BootIcons.Expand, container: "div", style: { - fill: theme.content_fg, + fill: theme.on_surface, transform: this.detailsExpanded ? "rotate(180deg)" : "", }, }), diff --git a/src/mail-app/mail/view/MinimizedEditorOverlay.ts b/src/mail-app/mail/view/MinimizedEditorOverlay.ts index b63958854300..a26f73b3ac80 100644 --- a/src/mail-app/mail/view/MinimizedEditorOverlay.ts +++ b/src/mail-app/mail/view/MinimizedEditorOverlay.ts @@ -1,6 +1,6 @@ import m, { Children, Component, Vnode } from "mithril" import { CounterBadge } from "../../../common/gui/base/CounterBadge" -import { getNavButtonIconBackground, theme } from "../../../common/gui/theme" +import { theme } from "../../../common/gui/theme" import { lang } from "../../../common/misc/LanguageViewModel" import type { MinimizedEditor, MinimizedMailEditorViewModel } from "../model/MinimizedMailEditorViewModel" import { SaveErrorReason, SaveStatus, SaveStatusEnum } from "../model/MinimizedMailEditorViewModel" @@ -61,8 +61,8 @@ export class MinimizedEditorOverlay implements Component implements Component> { m(ColumnEmptyMessageBox, { message: attrs.getSelectionMessage(selectedEntities), icon: BootIcons.Mail, - color: theme.content_message_bg, - backgroundColor: theme.navigation_bg, + color: theme.on_surface_fade, + backgroundColor: theme.surface_container, bottomContent: this.renderEmptyMessageButtons(attrs), }), ), diff --git a/src/mail-app/mailLocator.ts b/src/mail-app/mailLocator.ts index d22d02e6a244..2ba2fc17b11d 100644 --- a/src/mail-app/mailLocator.ts +++ b/src/mail-app/mailLocator.ts @@ -944,6 +944,10 @@ class MailLocator implements CommonLocator { const { RichNotificationsNews } = await import("../common/misc/news/items/RichNotificationsNews.js") return new RichNotificationsNews(this.newsModel, isApp() || isDesktop() ? this.pushService : null) } + case "colorCustomizationUpdate": { + const { UpdateColorCustomizationNews } = await import("../common/misc/news/items/UpdateColorCustomizationNews.js") + return new UpdateColorCustomizationNews(this.newsModel, this.logins.getUserController()) + } default: console.log(`No implementation for news named '${name}'`) return null diff --git a/src/mail-app/search/SearchBarOverlay.ts b/src/mail-app/search/SearchBarOverlay.ts index 5b1c26432a6b..5f4bb1eeb2bf 100644 --- a/src/mail-app/search/SearchBarOverlay.ts +++ b/src/mail-app/search/SearchBarOverlay.ts @@ -132,7 +132,7 @@ export class SearchBarOverlay implements Component { ), m(".abs", { style: { - backgroundColor: theme.content_accent, + backgroundColor: theme.primary, height: "2px", width: state.indexState.progress + "%", bottom: 0, diff --git a/src/mail-app/search/view/SearchListView.ts b/src/mail-app/search/view/SearchListView.ts index 4ec23b35d698..b9f14aada90e 100644 --- a/src/mail-app/search/view/SearchListView.ts +++ b/src/mail-app/search/view/SearchListView.ts @@ -56,7 +56,7 @@ export class SearchListView implements Component { ? m(ColumnEmptyMessageBox, { icon, message: "searchNoResults_msg", - color: theme.list_message_bg, + color: theme.on_surface_fade, }) : m(List, { state: attrs.listModel.state, diff --git a/src/mail-app/search/view/SearchView.ts b/src/mail-app/search/view/SearchView.ts index b29b3d3090c6..a1b1c2fc3633 100644 --- a/src/mail-app/search/view/SearchView.ts +++ b/src/mail-app/search/view/SearchView.ts @@ -192,7 +192,7 @@ export class SearchView extends BaseTopLevelView implements TopLevelView { return m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, desktopToolbar: () => m(DesktopListToolbar, [ this.searchViewModel.listModel && getCurrentSearchMode() !== SearchCategoryTypes.calendar @@ -569,7 +569,7 @@ export class SearchView extends BaseTopLevelView implements TopLevelView m(DesktopViewerToolbar, actions), mobileHeader: () => m(MobileHeader, { @@ -616,7 +616,7 @@ export class SearchView extends BaseTopLevelView implements TopLevelView m(DesktopViewerToolbar, actions), mobileHeader: () => m(MobileHeader, { @@ -663,7 +663,7 @@ export class SearchView extends BaseTopLevelView implements TopLevelView m(DesktopViewerToolbar, actions), mobileHeader: () => m(MobileHeader, { @@ -702,7 +702,7 @@ export class SearchView extends BaseTopLevelView implements TopLevelView m(DesktopViewerToolbar, []), mobileHeader: () => m(MobileHeader, { @@ -719,8 +719,8 @@ export class SearchView extends BaseTopLevelView implements TopLevelView m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, columnLayout: m( ".mlr-safe-inset.fill-absolute.content-bg", { @@ -356,7 +356,7 @@ export class SettingsView extends BaseTopLevelView implements TopLevelView m(BackgroundColumnLayout, { - backgroundColor: theme.navigation_bg, + backgroundColor: theme.surface_container, columnLayout: m( `.mlr-safe-inset.fill-absolute${this.detailsViewer ? ".content-bg" : ""}`, this.detailsViewer ? this.detailsViewer.renderView() : m(""), @@ -787,8 +787,8 @@ export class SettingsView extends BaseTopLevelView implements TopLevelView { const triggerStage = getSupportUsageTestStage(0) @@ -850,7 +850,7 @@ export class SettingsView extends BaseTopLevelView implements TopLevelView { ".text-break.smaller.b.text-center", { style: { - "border-bottom": `1px solid ${theme.content_border}`, + "border-bottom": `1px solid ${theme.outline}`, }, }, template.title, diff --git a/src/mail-app/templates/view/TemplateSearchBar.ts b/src/mail-app/templates/view/TemplateSearchBar.ts index 6649a9f813f2..afda90f2fca4 100644 --- a/src/mail-app/templates/view/TemplateSearchBar.ts +++ b/src/mail-app/templates/view/TemplateSearchBar.ts @@ -23,7 +23,7 @@ export class TemplateSearchBar implements ClassComponent ".inputWrapper.pt-xs.pb-xs", { style: { - "border-bottom": `1px solid ${theme.content_border}`, + "border-bottom": `1px solid ${theme.outline}`, }, }, this._getInputField(a), diff --git a/src/mail-app/translations/de.ts b/src/mail-app/translations/de.ts index 6de6bef8ad69..a9c5ebcbee7b 100644 --- a/src/mail-app/translations/de.ts +++ b/src/mail-app/translations/de.ts @@ -2036,6 +2036,6 @@ export default { "yourMessage_label": "Deine Nachricht", "you_label": "Du", "zoomIn_action": "Hereinzoomen", - "zoomOut_action": "Herauszoomen" + "zoomOut_action": "Herauszoomen", } } diff --git a/src/mail-app/translations/de_sie.ts b/src/mail-app/translations/de_sie.ts index 716f8e47d35b..dbf851940cd6 100644 --- a/src/mail-app/translations/de_sie.ts +++ b/src/mail-app/translations/de_sie.ts @@ -2036,6 +2036,6 @@ export default { "yourMessage_label": "Ihre Nachricht", "you_label": "Sie", "zoomIn_action": "Hereinzoomen", - "zoomOut_action": "Herauszoomen" + "zoomOut_action": "Herauszoomen", } } diff --git a/src/mail-app/translations/en.ts b/src/mail-app/translations/en.ts index 1bcbc157642f..ad8f9d9b354a 100644 --- a/src/mail-app/translations/en.ts +++ b/src/mail-app/translations/en.ts @@ -2032,6 +2032,6 @@ export default { "yourMessage_label": "Your message", "you_label": "You", "zoomIn_action": "Zoom In", - "zoomOut_action": "Zoom Out" + "zoomOut_action": "Zoom Out", } } diff --git a/test/tests/desktop/ApplicationWindowTest.ts b/test/tests/desktop/ApplicationWindowTest.ts index bf5c5f9a8be0..de0d7f452d47 100644 --- a/test/tests/desktop/ApplicationWindowTest.ts +++ b/test/tests/desktop/ApplicationWindowTest.ts @@ -75,8 +75,7 @@ o.spec("ApplicationWindow Test", function () { if (theme == null) { theme = { themeId: "light-fallback", - content_bg: "#ffffff", - header_bg: "#ffffff", + surface: "#ffffff", } as Theme } diff --git a/test/tests/gui/ThemeControllerTest.ts b/test/tests/gui/ThemeControllerTest.ts index 05897ce97fb3..729c0e396146 100644 --- a/test/tests/gui/ThemeControllerTest.ts +++ b/test/tests/gui/ThemeControllerTest.ts @@ -35,7 +35,7 @@ o.spec("ThemeController", function () { o("updateCustomTheme", async function () { const theme: ThemeCustomizations = downcast({ themeId: "HelloFancyId", - content_bg: "#fffeee", + surface: "#fffeee", logo: "unsanitized_logo", base: "light", }) @@ -46,12 +46,73 @@ o.spec("ThemeController", function () { verify(themeFacadeMock.setThemes(captor.capture())) const savedTheme = captor.values![0][4] o(savedTheme.themeId).equals("HelloFancyId") - o(savedTheme.content_bg).equals("#fffeee") + o(savedTheme.surface).equals("#fffeee") o(savedTheme.logo).equals("sanitized") - o(savedTheme.content_fg).equals(themeManager.getDefaultTheme().content_fg) + o(savedTheme.on_surface).equals(themeManager.getDefaultTheme().on_surface) o(themeManager.getCurrentTheme().logo).equals("sanitized") }) + o("Mapping the new color tokens to the old tokens", async function () { + const newTokenTheme: ThemeCustomizations = downcast({ + themeId: "HelloFancyId", + primary: "#abcdef", + surface: "#fffeee", + base: "light", + }) + + o(ThemeController.mapNewToOldColorTokens(newTokenTheme)).deepEquals( + downcast({ + themeId: "HelloFancyId", + primary: "#abcdef", + surface: "#fffeee", + base: "light", + content_accent: "#abcdef", + content_button_selected: "#abcdef", + header_button_selected: "#abcdef", + list_accent_fg: "#abcdef", + navigation_button_selected: "#abcdef", + content_bg: "#fffeee", + header_bg: "#fffeee", + list_bg: "#fffeee", + elevated_bg: "#fffeee", + }), + ) + }) + + o("Mapping the old color tokens to the new tokens", async function () { + const oldTokenTheme: ThemeCustomizations = downcast({ + themeId: "HelloFancyId", + base: "light", + content_accent: "#abcdef", + content_button_selected: "#abcdef", + header_button_selected: "#abcdef", + list_accent_fg: "#abcdef", + navigation_button_selected: "#abcdef", + content_bg: "#fffeee", + header_bg: "#fffeee", + list_bg: "#fffeee", + elevated_bg: "#fffeee", + }) + + o(ThemeController.mapOldToNewColorTokens(oldTokenTheme)).deepEquals( + downcast({ + themeId: "HelloFancyId", + primary: "#abcdef", + surface: "#fffeee", + base: "light", + content_accent: "#abcdef", + content_button_selected: "#abcdef", + header_button_selected: "#abcdef", + list_accent_fg: "#abcdef", + navigation_button_selected: "#abcdef", + content_bg: "#fffeee", + header_bg: "#fffeee", + list_bg: "#fffeee", + elevated_bg: "#fffeee", + }), + ) + }) + o("when using automatic theme and preferring dark, dark theme is applied, and themeId is automatic", async function () { when(themeFacadeMock.getThemePreference()).thenResolve("auto:light|dark") when(themeFacadeMock.prefersDark()).thenResolve(true) diff --git a/test/tests/settings/whitelabel/CustomColorEditorTest.ts b/test/tests/settings/whitelabel/CustomColorEditorTest.ts index 5077f7454bab..84a753664129 100644 --- a/test/tests/settings/whitelabel/CustomColorEditorTest.ts +++ b/test/tests/settings/whitelabel/CustomColorEditorTest.ts @@ -18,11 +18,7 @@ o.spec("SimpleColorEditor", function () { let defaultTheme // These customizations should always be set if no changes are made const defaultCustomizations: ThemeCustomizations = downcast({ - list_accent_fg: "#850122", - content_accent: "#850122", - content_button_selected: "#850122", - navigation_button_selected: "#850122", - header_button_selected: "#850122", + primary: "#850122", base: "light", }) let entityClient: EntityClient @@ -68,14 +64,10 @@ o.spec("SimpleColorEditor", function () { o("open Editor with custom theme, all customizations should be applied", async function () { const customizations: ThemeCustomizations = downcast({ themeId: "test.domain.com", - list_accent_fg: "#ee051f", - content_accent: "#ee051f", - content_button_selected: "#ee051f", - navigation_button_selected: "#ee051f", - header_button_selected: "#ee051f", + primary: "#ee051f", base: "dark", - content_bg: "#1df3ed", - modal_bg: "#1aa1aa", + surface: "#1df3ed", + scrim: "#1aa1aa", }) model = new CustomColorsEditorViewModel( defaultTheme, @@ -86,11 +78,11 @@ o.spec("SimpleColorEditor", function () { entityClient, loginController, ) - o(model.accentColor).equals(customizations.content_accent!) + o(model.accentColor).equals(customizations.primary!) o(model.baseThemeId).equals(customizations.base!) await model.save() o(entityClient.update.callCount).equals(1) - o(JSON.parse(entityClient.update.args[0].jsonTheme)).deepEquals(customizations) + o(JSON.parse(entityClient.update.args[0].jsonTheme)).deepEquals(ThemeController.mapNewToOldColorTokens(customizations)) }) }) o.spec("addCustomization", function () { @@ -105,14 +97,14 @@ o.spec("SimpleColorEditor", function () { entityClient, loginController, ) - model.addCustomization("content_button", "#abcdef") - model.addCustomization("modal_bg", "#fedcba") + model.addCustomization("on_surface_variant", "#abcdef") + model.addCustomization("scrim", "#fedcba") // should only contain 2 customizations here o(model.customizations).deepEquals( downcast( Object.assign({}, defaultCustomizations, { - content_button: "#abcdef", - modal_bg: "#fedcba", + on_surface_variant: "#abcdef", + scrim: "#fedcba", }), ), ) @@ -120,11 +112,13 @@ o.spec("SimpleColorEditor", function () { o(entityClient.update.callCount).equals(1) // should now equal the themeCustomizations including accentColor and baseTheme o(JSON.parse(entityClient.update.args[0].jsonTheme)).deepEquals( - Object.assign({}, defaultCustomizations, { - content_button: "#abcdef", - modal_bg: "#fedcba", - themeId: "test.domain.com", - }), + ThemeController.mapNewToOldColorTokens( + Object.assign({}, defaultCustomizations, { + on_surface_variant: "#abcdef", + scrim: "#fedcba", + themeId: "test.domain.com", + }), + ), ) }) // Invalid practically means 'empty' as well but I created a separate test just for that @@ -139,18 +133,18 @@ o.spec("SimpleColorEditor", function () { entityClient, loginController, ) - model.addCustomization("modal_bg", "#false") - model.addCustomization("button_bubble_bg", "#69") - model.addCustomization("content_fg", "#zzzzzz") - model.addCustomization("navigation_bg", "#abcdefghi") + model.addCustomization("scrim", "#false") + model.addCustomization("secondary", "#69") + model.addCustomization("on_surface", "#zzzzzz") + model.addCustomization("surface_container", "#abcdefghi") // Customizations should hold the wrong key o(model.customizations).deepEquals( downcast( Object.assign({}, defaultCustomizations, { - modal_bg: "#false", - button_bubble_bg: "#69", - content_fg: "#zzzzzz", - navigation_bg: "#abcdefghi", + scrim: "#false", + secondary: "#69", + on_surface: "#zzzzzz", + surface_container: "#abcdefghi", }), ), ) @@ -168,27 +162,27 @@ o.spec("SimpleColorEditor", function () { entityClient, loginController, ) - model.addCustomization("modal_bg", "#fedcba") - model.addCustomization("button_bubble_bg", "#69") - model.addCustomization("content_fg", "#deffed") - model.addCustomization("navigation_bg", "#abcdefghi") - model.addCustomization("content_button", "#abcdef") + model.addCustomization("scrim", "#fedcba") + model.addCustomization("secondary", "#69") + model.addCustomization("on_surface", "#deffed") + model.addCustomization("surface_container", "#abcdefghi") + model.addCustomization("on_surface_variant", "#abcdef") o(model.customizations).deepEquals( downcast( Object.assign({}, defaultCustomizations, { - modal_bg: "#fedcba", - button_bubble_bg: "#69", - content_fg: "#deffed", - navigation_bg: "#abcdefghi", - content_button: "#abcdef", + scrim: "#fedcba", + secondary: "#69", + on_surface: "#deffed", + surface_container: "#abcdefghi", + on_surface_variant: "#abcdef", }), ), ) o(model._filterAndReturnCustomizations()).deepEquals( Object.assign({}, defaultCustomizations, { - modal_bg: "#fedcba", - content_fg: "#deffed", - content_button: "#abcdef", + scrim: "#fedcba", + on_surface: "#deffed", + on_surface_variant: "#abcdef", }), ) }) @@ -203,9 +197,9 @@ o.spec("SimpleColorEditor", function () { entityClient, loginController, ) - model.addCustomization("modal_bg", "") - model.addCustomization("content_fg", "") - model.addCustomization("content_button", "") + model.addCustomization("scrim", "") + model.addCustomization("on_surface", "") + model.addCustomization("on_surface_variant", "") // usually this is called on its own, since theres a debounce however it doesnt work in this test model._removeEmptyCustomizations() @@ -245,24 +239,22 @@ o.spec("SimpleColorEditor", function () { entityClient, loginController, ) - model.addCustomization("modal_bg", "#fedcba") - model.addCustomization("content_fg", "#deffed") + model.addCustomization("scrim", "#fedcba") + model.addCustomization("on_surface", "#deffed") model.changeBaseTheme("dark") model.changeAccentColor("#aaaaaa") await model.save() o(entityClient.update.callCount).equals(1) o(JSON.parse(entityClient.update.args[0].jsonTheme)).deepEquals( - Object.assign({}, defaultCustomizations, { - modal_bg: "#fedcba", - content_fg: "#deffed", - base: "dark", - list_accent_fg: "#aaaaaa", - content_accent: "#aaaaaa", - content_button_selected: "#aaaaaa", - navigation_button_selected: "#aaaaaa", - header_button_selected: "#aaaaaa", - themeId: "test.domain.com", - }), + ThemeController.mapNewToOldColorTokens( + Object.assign({}, defaultCustomizations, { + scrim: "#fedcba", + on_surface: "#deffed", + base: "dark", + primary: "#aaaaaa", + themeId: "test.domain.com", + }), + ), ) }) o("pressed save when on whitelabelDomain, should not revert back to initial theme", async function () { @@ -286,11 +278,7 @@ o.spec("SimpleColorEditor", function () { o("changing accent changed preview", async function () { const customizations: ThemeCustomizations = downcast({}) const expectedCustomizations = { - list_accent_fg: "#ff00f2", - content_accent: "#ff00f2", - content_button_selected: "#ff00f2", - navigation_button_selected: "#ff00f2", - header_button_selected: "#ff00f2", + primary: "#ff00f2", themeId: "test.domain.com", } model = new CustomColorsEditorViewModel( @@ -307,7 +295,7 @@ o.spec("SimpleColorEditor", function () { await model.save() o(entityClient.update.callCount).equals(1) o(JSON.parse(entityClient.update.args[0].jsonTheme)).deepEquals( - Object.assign({}, expectedCustomizations, { + Object.assign({}, ThemeController.mapNewToOldColorTokens(expectedCustomizations), { base: "light", }), ) @@ -334,7 +322,9 @@ o.spec("SimpleColorEditor", function () { await model.save() o(entityClient.update.callCount).equals(1) // We have to do base -> customizations because otherwise we would overwrite the 'base' key - o(JSON.parse(entityClient.update.args[0].jsonTheme)).deepEquals(Object.assign({}, defaultCustomizations, expectedCustomizations)) + o(JSON.parse(entityClient.update.args[0].jsonTheme)).deepEquals( + ThemeController.mapNewToOldColorTokens(Object.assign({}, defaultCustomizations, expectedCustomizations)), + ) }) }) })