diff --git a/res/css/_components.scss b/res/css/_components.scss index 26e36b8cdd..73e25d314f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -200,10 +200,10 @@ @import "./views/right_panel/_EncryptionInfo.scss"; @import "./views/right_panel/_PinnedMessagesCard.scss"; @import "./views/right_panel/_RoomSummaryCard.scss"; +@import "./views/right_panel/_ThreadPanel.scss"; @import "./views/right_panel/_UserInfo.scss"; @import "./views/right_panel/_VerificationPanel.scss"; @import "./views/right_panel/_WidgetCard.scss"; -@import "./views/right_panel/_ThreadPanel.scss"; @import "./views/room_settings/_AliasSettings.scss"; @import "./views/rooms/_AppsDrawer.scss"; @import "./views/rooms/_Autocomplete.scss"; diff --git a/res/themes/light-high-contrast/css/_light-high-contrast.scss b/res/themes/light-high-contrast/css/_light-high-contrast.scss index bb5fa16056..f1f4387a06 100644 --- a/res/themes/light-high-contrast/css/_light-high-contrast.scss +++ b/res/themes/light-high-contrast/css/_light-high-contrast.scss @@ -1,11 +1,12 @@ //// Reference: https://www.figma.com/file/RnLKnv09glhxGIZtn8zfmh/UI-Themes-%26-Accessibility?node-id=321%3A65847 $accent: #268075; $alert: #D62C25; -$notice-primary-color: #D61C25; $links: #0A6ECA; $secondary-content: #5E6266; -$tertiary-content: #5E6266; // Same as secondary -$quaternary-content: #5E6266; // Same as secondary +$tertiary-content: $secondary-content; +$quaternary-content: $secondary-content; +$quinary-content: $secondary-content; +$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); $username-variant1-color: #0A6ECA; $username-variant2-color: #AC3BA8; @@ -18,9 +19,13 @@ $username-variant8-color: #3E810A; $accent-color: $accent; $accent-color-50pct: rgba($accent-color, 0.5); +$accent-color-alt: $links; +$input-border-color: $secondary-content; $input-darker-bg-color: $quinary-content; +$input-darker-fg-color: $secondary-content; $input-lighter-fg-color: $input-darker-fg-color; $input-valid-border-color: $accent-color; +$input-focused-border-color: $accent-color; $button-bg-color: $accent-color; $resend-button-divider-color: $input-darker-bg-color; $icon-button-color: $quaternary-content; @@ -41,12 +46,14 @@ $voice-record-stop-border-color: $quinary-content; $voice-record-icon-color: $tertiary-content; $appearance-tab-border-color: $input-darker-bg-color; $eventbubble-reply-color: $quaternary-content; +$notice-primary-color: $alert; $warning-color: $notice-primary-color; // red $pinned-unread-color: $notice-primary-color; $button-danger-bg-color: $notice-primary-color; $mention-user-pill-bg-color: $warning-color; $input-invalid-border-color: $warning-color; $event-highlight-fg-color: $warning-color; +$roomtopic-color: $secondary-content; @define-mixin mx_DialogButton_danger { background-color: $accent-color; @@ -64,3 +71,38 @@ $event-highlight-fg-color: $warning-color; color: $accent-color; text-decoration: none; } + +.mx_AccessibleButton { + margin-left: 4px; +} + +.mx_AccessibleButton:focus { + outline: 2px solid $accent-color; + outline-offset: 2px; +} + +.mx_BasicMessageComposer .mx_BasicMessageComposer_inputEmpty > :first-child::before { + color: $secondary-content; + opacity: 1 !important; +} + +.mx_TextualEvent { + color: $secondary-content; + opacity: 1 !important; +} + +.mx_Dialog, .mx_MatrixChat_wrapper { + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text]::placeholder, + :not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search]::placeholder, + .mx_textinput input::placeholder { + color: $input-darker-fg-color !important; + } +} + +.mx_UserMenu_contextMenu .mx_UserMenu_contextMenu_header .mx_UserMenu_contextMenu_themeButton { + background-color: $roomlist-button-bg-color !important; +} + +.mx_FontScalingPanel_fontSlider { + background-color: $roomlist-button-bg-color !important; +} diff --git a/src/components/views/settings/ThemeChoicePanel.tsx b/src/components/views/settings/ThemeChoicePanel.tsx index caa07bd0ad..feb9552230 100644 --- a/src/components/views/settings/ThemeChoicePanel.tsx +++ b/src/components/views/settings/ThemeChoicePanel.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import { _t } from "../../../languageHandler"; import SettingsStore from "../../../settings/SettingsStore"; -import { enumerateThemes } from "../../../theme"; +import { enumerateThemes, findHighContrastTheme, findNonHighContrastTheme, isHighContrastTheme } from "../../../theme"; import ThemeWatcher from "../../../settings/watchers/ThemeWatcher"; import AccessibleButton from "../elements/AccessibleButton"; import dis from "../../../dispatcher/dispatcher"; @@ -159,7 +159,37 @@ export default class ThemeChoicePanel extends React.Component { this.setState({ customThemeUrl: e.target.value }); }; - public render() { + private renderHighContrastCheckbox(): React.ReactElement { + if ( + !this.state.useSystemTheme && ( + findHighContrastTheme(this.state.theme) || + isHighContrastTheme(this.state.theme) + ) + ) { + return
+ this.highContrastThemeChanged(e.target.checked)} + > + { _t( "Use high contrast" ) } + +
; + } + } + + private highContrastThemeChanged(checked: boolean): void { + let newTheme: string; + if (checked) { + newTheme = findHighContrastTheme(this.state.theme); + } else { + newTheme = findNonHighContrastTheme(this.state.theme); + } + if (newTheme) { + this.onThemeChange(newTheme); + } + } + + public render(): React.ReactElement { const themeWatcher = new ThemeWatcher(); let systemThemeSection: JSX.Element; if (themeWatcher.isSystemThemeSupported()) { @@ -210,7 +240,8 @@ export default class ThemeChoicePanel extends React.Component { // XXX: replace any type here const themes = Object.entries(enumerateThemes()) - .map(p => ({ id: p[0], name: p[1] })); // convert pairs to objects for code readability + .map(p => ({ id: p[0], name: p[1] })) // convert pairs to objects for code readability + .filter(p => !isHighContrastTheme(p.id)); const builtInThemes = themes.filter(p => !p.id.startsWith("custom-")); const customThemes = themes.filter(p => !builtInThemes.includes(p)) .sort((a, b) => compare(a.name, b.name)); @@ -229,12 +260,21 @@ export default class ThemeChoicePanel extends React.Component { className: "mx_ThemeSelector_" + t.id, }))} onChange={this.onThemeChange} - value={this.state.useSystemTheme ? undefined : this.state.theme} + value={this.apparentSelectedThemeId()} outlined /> + { this.renderHighContrastCheckbox() } { customThemeForm } ); } + + apparentSelectedThemeId() { + if (this.state.useSystemTheme) { + return undefined; + } + const nonHighContrast = findNonHighContrastTheme(this.state.theme); + return nonHighContrast ? nonHighContrast : this.state.theme; + } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 47242cd402..0596f61b44 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -579,6 +579,7 @@ "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", "Light": "Light", + "Light high contrast": "Light high contrast", "Dark": "Dark", "%(displayName)s is typing …": "%(displayName)s is typing …", "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", @@ -1293,6 +1294,7 @@ "Invalid theme schema.": "Invalid theme schema.", "Error downloading theme information.": "Error downloading theme information.", "Theme added!": "Theme added!", + "Use high contrast": "Use high contrast", "Custom theme URL": "Custom theme URL", "Add theme": "Add theme", "Theme": "Theme", diff --git a/src/theme.ts b/src/theme.ts index aaebe3746d..b1eec5aced 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -21,6 +21,9 @@ import SettingsStore from "./settings/SettingsStore"; import ThemeWatcher from "./settings/watchers/ThemeWatcher"; export const DEFAULT_THEME = "light"; +const HIGH_CONTRAST_THEMES = { + "light": "light-high-contrast", +}; interface IFontFaces { src: { @@ -42,9 +45,37 @@ interface ICustomTheme { is_dark?: boolean; // eslint-disable-line camelcase } +/** + * Given a non-high-contrast theme, find the corresponding high-contrast one + * if it exists, or return undefined if not. + */ +export function findHighContrastTheme(theme: string) { + return HIGH_CONTRAST_THEMES[theme]; +} + +/** + * Given a high-contrast theme, find the corresponding non-high-contrast one + * if it exists, or return undefined if not. + */ +export function findNonHighContrastTheme(hcTheme: string) { + for (const theme in HIGH_CONTRAST_THEMES) { + if (HIGH_CONTRAST_THEMES[theme] === hcTheme) { + return theme; + } + } +} + +/** + * Decide whether the supplied theme is high contrast. + */ +export function isHighContrastTheme(theme: string) { + return Object.values(HIGH_CONTRAST_THEMES).includes(theme); +} + export function enumerateThemes(): {[key: string]: string} { const BUILTIN_THEMES = { "light": _t("Light"), + "light-high-contrast": _t("Light high contrast"), "dark": _t("Dark"), }; const customThemes = SettingsStore.getValue("custom_themes");