Handle dark/light/contrast theme modes in common CSS (#37095)

This commit is contained in:
diondiondion
2025-12-04 16:56:35 +01:00
committed by GitHub
parent 0dac31dfd5
commit 65b216353e
17 changed files with 148 additions and 127 deletions

View File

@@ -1,2 +1,2 @@
<html class="no-reduce-motion"> <html class="no-reduce-motion theme-light">
</html> </html>

View File

@@ -153,10 +153,8 @@ module ApplicationHelper
tag.meta(content: content, property: property) tag.meta(content: content, property: property)
end end
def body_classes def html_classes
output = [] output = []
output << content_for(:body_classes)
output << "theme-#{current_theme.parameterize}"
output << 'system-font' if current_account&.user&.setting_system_font_ui output << 'system-font' if current_account&.user&.setting_system_font_ui
output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion') output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
@@ -164,6 +162,12 @@ module ApplicationHelper
output.compact_blank.join(' ') output.compact_blank.join(' ')
end end
def body_classes
output = []
output << content_for(:body_classes)
output.compact_blank.join(' ')
end
def cdn_host def cdn_host
Rails.configuration.action_controller.asset_host Rails.configuration.action_controller.asset_host
end end

View File

@@ -1,7 +1,2 @@
@use 'mastodon/css_variables';
@use 'mastodon/variables'; @use 'mastodon/variables';
@use 'common'; @use 'common';
html {
color-scheme: dark;
}

View File

@@ -3,6 +3,7 @@
@use 'fonts/roboto-mono'; @use 'fonts/roboto-mono';
@use 'mastodon/reset'; @use 'mastodon/reset';
@use 'mastodon/theme';
@use 'mastodon/basics'; @use 'mastodon/basics';
@use 'mastodon/branding'; @use 'mastodon/branding';
@use 'mastodon/containers'; @use 'mastodon/containers';

View File

@@ -1,8 +1,3 @@
@use 'mastodon/css_variables';
@use 'mastodon/variables'; @use 'mastodon/variables';
@use 'common'; @use 'common';
@use 'contrast/diff'; @use 'mastodon/high-contrast';
html {
color-scheme: dark;
}

View File

@@ -1,9 +1,4 @@
@use 'mastodon-light/css_variables';
@use 'mastodon/variables' with ( @use 'mastodon/variables' with (
$emojis-requiring-inversion: 'chains' $emojis-requiring-inversion: 'chains'
); );
@use 'common'; @use 'common';
html {
color-scheme: light;
}

View File

@@ -3,6 +3,20 @@
html { html {
color: var(--color-text-primary); color: var(--color-text-primary);
background: var(--color-bg-ambient); background: var(--color-bg-ambient);
&.custom-scrollbars {
scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
}
--outline-focus-default: 2px solid var(--color-text-brand);
--avatar-border-radius: 8px;
// Variable for easily inverting directional UI elements,
--text-x-direction: 1;
&.rtl {
--text-x-direction: -1;
}
} }
html.has-modal { html.has-modal {
@@ -37,7 +51,7 @@ body {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0%); -webkit-tap-highlight-color: rgba(0, 0, 0, 0%);
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
&.system-font { .system-font & {
// system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+) // system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+)
// -apple-system => Safari <11 specific // -apple-system => Safari <11 specific
// BlinkMacSystemFont => Chrome <56 on macOS specific // BlinkMacSystemFont => Chrome <56 on macOS specific

View File

@@ -1,17 +1,3 @@
:root {
/* TEXT TOKENS */
--color-text-primary: var(--color-grey-50);
--color-text-secondary: var(--color-grey-300);
--color-text-tertiary: var(--color-grey-400);
--color-text-brand: var(--color-indigo-300);
--color-text-status-links: var(--color-text-brand);
/* BORDER TOKENS */
--border-strength-primary: 18%;
}
.status__content a, .status__content a,
.reply-indicator__content a, .reply-indicator__content a,
.edit-indicator__content a, .edit-indicator__content a,

View File

@@ -52,7 +52,3 @@ table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
} }
html:has(body.custom-scrollbars) {
scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
}

View File

@@ -1,6 +1,6 @@
@use 'variables' as *; @use 'variables' as *;
body.rtl { html.rtl {
direction: rtl; direction: rtl;
.reactions-bar { .reactions-bar {

View File

@@ -0,0 +1,27 @@
@mixin palette {
--color-black: #000;
--color-grey-950: #181821;
--color-grey-800: #292938;
--color-grey-700: #444664;
--color-grey-600: #545778;
--color-grey-500: #696d91;
--color-grey-400: #8b8dac;
--color-grey-300: #b4b6cb;
--color-grey-200: #d8d9e3;
--color-grey-100: #f0f0f5;
--color-grey-50: #f0f1ff;
--color-white: #fff;
--color-indigo-600: #6147e6;
--color-indigo-400: #8886ff;
--color-indigo-300: #a5abfd;
--color-indigo-200: #c8cdfe;
--color-indigo-100: #e0e3ff;
--color-indigo-50: #f0f1ff;
--color-red-500: #ff637e;
--color-red-600: #ec003f;
--color-yellow-400: #ffb900;
--color-yellow-600: #e17100;
--color-yellow-700: #bb4d00;
--color-green-400: #05df72;
--color-green-600: #00a63e;
}

View File

@@ -1,31 +1,6 @@
@use 'theme_utils' as utils; @use 'utils';
:root {
--color-black: #000;
--color-grey-950: #181821;
--color-grey-800: #292938;
--color-grey-700: #444664;
--color-grey-600: #545778;
--color-grey-500: #696d91;
--color-grey-400: #8b8dac;
--color-grey-300: #b4b6cb;
--color-grey-200: #d8d9e3;
--color-grey-100: #f0f0f5;
--color-grey-50: #f0f1ff;
--color-white: #fff;
--color-indigo-600: #6147e6;
--color-indigo-400: #8886ff;
--color-indigo-300: #a5abfd;
--color-indigo-200: #c8cdfe;
--color-indigo-100: #e0e3ff;
--color-indigo-50: #f0f1ff;
--color-red-500: #ff637e;
--color-red-600: #ec003f;
--color-yellow-400: #ffb900;
--color-yellow-600: #e17100;
--color-green-400: #05df72;
--color-green-600: #00a63e;
@mixin tokens {
/* TEXT TOKENS */ /* TEXT TOKENS */
--color-text-primary: var(--color-grey-50); --color-text-primary: var(--color-grey-50);
@@ -127,7 +102,7 @@
// Warning // Warning
--overlay-strength-warning: 10%; --overlay-strength-warning: 10%;
--color-bg-warning-base: var(--color-yellow-600); --color-bg-warning-base: var(--color-yellow-700);
--color-bg-warning-base-hover: color-mix( --color-bg-warning-base-hover: color-mix(
in oklab, in oklab,
var(--color-bg-warning-base), var(--color-bg-warning-base),
@@ -212,18 +187,18 @@
--rich-text-container-color: rgb(87 24 60 / 100%); --rich-text-container-color: rgb(87 24 60 / 100%);
--rich-text-text-color: rgb(255 175 212 / 100%); --rich-text-text-color: rgb(255 175 212 / 100%);
--rich-text-decorations-color: rgb(128 58 95 / 100%); --rich-text-decorations-color: rgb(128 58 95 / 100%);
/* MISCELLANEOUS */
--outline-focus-default: 2px solid var(--color-text-brand);
--avatar-border-radius: 8px;
} }
body { @mixin contrast-overrides {
// Variable for easily inverting directional UI elements, /* TEXT TOKENS */
--text-x-direction: 1;
&.rtl { --color-text-primary: var(--color-grey-50);
--text-x-direction: -1; --color-text-secondary: var(--color-grey-300);
} --color-text-tertiary: var(--color-grey-400);
--color-text-brand: var(--color-indigo-300);
--color-text-status-links: var(--color-text-brand);
/* BORDER TOKENS */
--border-strength-primary: 18%;
} }

View File

@@ -1,31 +1,6 @@
@use '../mastodon/theme_utils' as utils; @use 'utils';
:root {
--color-black: #000;
--color-grey-950: #181821;
--color-grey-800: #292938;
--color-grey-700: #444664;
--color-grey-600: #545778;
--color-grey-500: #696d91;
--color-grey-400: #8b8dac;
--color-grey-300: #b4b6cb;
--color-grey-200: #d8d9e3;
--color-grey-100: #f0f0f5;
--color-grey-50: #f0f1ff;
--color-white: #fff;
--color-indigo-600: #6147e6;
--color-indigo-400: #8886ff;
--color-indigo-300: #a5abfd;
--color-indigo-200: #c8cdfe;
--color-indigo-100: #e0e3ff;
--color-indigo-50: #f0f1ff;
--color-red-500: #ff637e;
--color-red-600: #ec003f;
--color-yellow-400: #ffb900;
--color-yellow-600: #e17100;
--color-green-400: #05df72;
--color-green-600: #00a63e;
@mixin tokens {
/* TEXT TOKENS */ /* TEXT TOKENS */
--color-text-primary: var(--color-grey-950); --color-text-primary: var(--color-grey-950);
@@ -124,7 +99,7 @@
// Warning // Warning
--overlay-strength-warning: 10%; --overlay-strength-warning: 10%;
--color-bg-warning-base: var(--color-yellow-600); --color-bg-warning-base: var(--color-yellow-700);
--color-bg-warning-base-hover: color-mix( --color-bg-warning-base-hover: color-mix(
in oklab, in oklab,
var(--color-bg-warning-base), var(--color-bg-warning-base),
@@ -207,9 +182,18 @@
--rich-text-container-color: rgb(255 216 231 / 100%); --rich-text-container-color: rgb(255 216 231 / 100%);
--rich-text-text-color: rgb(114 47 83 / 100%); --rich-text-text-color: rgb(114 47 83 / 100%);
--rich-text-decorations-color: rgb(255 175 212 / 100%); --rich-text-decorations-color: rgb(255 175 212 / 100%);
}
/* MISCELLANEOUS */
@mixin contrast-overrides {
--outline-focus-default: 2px solid var(--color-text-brand); /* TEXT TOKENS */
--avatar-border-radius: 8px;
--color-text-primary: var(--color-black);
--color-text-secondary: var(--color-grey-800);
--color-text-tertiary: var(--color-grey-700);
--color-text-brand: var(--color-indigo-600);
/* BORDER TOKENS */
--border-strength-primary: 30%;
--color-border-on-bg-secondary: var(--color-grey-300);
} }

View File

@@ -0,0 +1,48 @@
@use 'base';
@use 'dark';
@use 'light';
html {
@include base.palette;
&[data-user-theme='system'] {
color-scheme: dark light;
@media (prefers-color-scheme: dark) {
@include dark.tokens;
@media (prefers-contrast: more) {
@include dark.contrast-overrides;
}
}
@media (prefers-color-scheme: light) {
@include light.tokens;
@media (prefers-contrast: more) {
@include light.contrast-overrides;
}
}
}
}
.theme-dark,
html:where(
:not([data-user-theme='mastodon-light'], [data-user-theme='system'])
) {
color-scheme: dark;
@include dark.tokens;
}
html[data-user-theme='contrast'],
html[data-user-theme='contrast'] .theme-dark {
@include dark.contrast-overrides;
}
.theme-light,
html:where([data-user-theme='mastodon-light']) {
color-scheme: light;
@include light.tokens;
}

View File

@@ -1,5 +1,5 @@
!!! 5 !!! 5
%html{ lang: I18n.locale } %html{ lang: I18n.locale, class: html_classes, 'data-user-theme': current_theme.parameterize }
%head %head
%meta{ charset: 'utf-8' }/ %meta{ charset: 'utf-8' }/
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover' }/ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover' }/

View File

@@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ApplicationHelper do RSpec.describe ApplicationHelper do
describe 'body_classes' do describe 'html_classes' do
context 'with a body class string from a controller' do context 'with non-default user settings' do
before do before do
user = Fabricate :user user = Fabricate :user
user.settings['web.use_system_font'] = true user.settings['web.use_system_font'] = true
@@ -15,19 +15,11 @@ RSpec.describe ApplicationHelper do
end end
it 'uses the current theme and user settings classes in the result' do it 'uses the current theme and user settings classes in the result' do
expect(helper.body_classes) expect(helper.html_classes)
.to match(/theme-default/) .to match(/system-font/)
.and match(/system-font/)
.and match(/reduce-motion/) .and match(/reduce-motion/)
end end
it 'includes values set via content_for' do
helper.content_for(:body_classes) { 'admin' }
expect(helper.body_classes)
.to match(/admin/)
end
private private
def controller_helpers def controller_helpers
@@ -35,13 +27,22 @@ RSpec.describe ApplicationHelper do
def current_account def current_account
@current_account ||= Fabricate(:account, user: User.last) @current_account ||= Fabricate(:account, user: User.last)
end end
def current_theme = 'default'
end end
end end
end end
end end
describe 'body_classes' do
context 'with a body class string from a controller' do
it 'includes values set via content_for' do
helper.content_for(:body_classes) { 'admin' }
expect(helper.body_classes)
.to match(/admin/)
end
end
end
describe 'locale_direction' do describe 'locale_direction' do
it 'adds rtl body class if locale is Arabic' do it 'adds rtl body class if locale is Arabic' do
I18n.with_locale(:ar) do I18n.with_locale(:ar) do