mirror of
https://github.com/discourse/discourse.git
synced 2025-05-29 01:31:35 +08:00
FEATURE: Dark/light mode selector (#31086)
This commit makes the [color-scheme-toggle](https://github.com/discourse/discourse-color-scheme-toggle) theme component a core feature with improvements and bug fixes. The theme component will be updated to become a no-op if the core feature is enabled. Noteworthy changes: * the color mode selector has a new "Auto" option that makes the site render in the same color mode as the user's system preference * the splash screen respects the color mode selected by the user * dark/light variants of category logos and background images are now picked correctly based on the selected color mode * a new `interface_color_selector` site setting to disable the selector or choose its location between the sidebar footer or header Internal topic: t/139465. --------- Co-authored-by: Ella <ella.estigoy@gmail.com>
This commit is contained in:
@ -672,6 +672,105 @@ RSpec.describe ApplicationController do
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.body).not_to include("d-splash")
|
||||
end
|
||||
|
||||
context "with color schemes" do
|
||||
let!(:light_scheme) { ColorScheme.find_by(base_scheme_id: "Solarized Light") }
|
||||
let!(:dark_scheme) { ColorScheme.find_by(base_scheme_id: "Dark") }
|
||||
|
||||
before do
|
||||
SiteSetting.default_dark_mode_color_scheme_id = dark_scheme.id
|
||||
SiteSetting.interface_color_selector = "sidebar_footer"
|
||||
Theme.find_by(id: SiteSetting.default_theme_id).update!(color_scheme_id: light_scheme.id)
|
||||
end
|
||||
|
||||
context "when light mode is forced" do
|
||||
before { cookies[:forced_color_mode] = "light" }
|
||||
|
||||
it "uses the light scheme colors and doesn't include the prefers-color-scheme media query" do
|
||||
get "/"
|
||||
|
||||
style = css_select("#d-splash style").to_s
|
||||
expect(style).not_to include("prefers-color-scheme")
|
||||
|
||||
secondary = light_scheme.colors.find { |color| color.name == "secondary" }.hex
|
||||
tertiary = light_scheme.colors.find { |color| color.name == "tertiary" }.hex
|
||||
expect(style).to include(<<~CSS.indent(6))
|
||||
html {
|
||||
background-color: ##{secondary};
|
||||
}
|
||||
CSS
|
||||
expect(style).to include(<<~CSS.indent(6))
|
||||
#d-splash {
|
||||
--dot-color: ##{tertiary};
|
||||
}
|
||||
CSS
|
||||
end
|
||||
end
|
||||
|
||||
context "when dark mode is forced" do
|
||||
before { cookies[:forced_color_mode] = "dark" }
|
||||
|
||||
it "uses the dark scheme colors and doesn't include the prefers-color-scheme media query" do
|
||||
get "/"
|
||||
|
||||
style = css_select("#d-splash style").to_s
|
||||
expect(style).not_to include("prefers-color-scheme")
|
||||
|
||||
secondary = dark_scheme.colors.find { |color| color.name == "secondary" }.hex
|
||||
tertiary = dark_scheme.colors.find { |color| color.name == "tertiary" }.hex
|
||||
expect(style).to include(<<~CSS.indent(6))
|
||||
html {
|
||||
background-color: ##{secondary};
|
||||
}
|
||||
CSS
|
||||
expect(style).to include(<<~CSS.indent(6))
|
||||
#d-splash {
|
||||
--dot-color: ##{tertiary};
|
||||
}
|
||||
CSS
|
||||
end
|
||||
end
|
||||
|
||||
context "when no color mode is forced" do
|
||||
before { cookies[:forced_color_mode] = nil }
|
||||
|
||||
it "includes both dark and light colors inside prefers-color-scheme media queries" do
|
||||
get "/"
|
||||
|
||||
style = css_select("#d-splash style").to_s
|
||||
|
||||
light_secondary = light_scheme.colors.find { |color| color.name == "secondary" }.hex
|
||||
light_tertiary = light_scheme.colors.find { |color| color.name == "tertiary" }.hex
|
||||
|
||||
dark_secondary = dark_scheme.colors.find { |color| color.name == "secondary" }.hex
|
||||
dark_tertiary = dark_scheme.colors.find { |color| color.name == "tertiary" }.hex
|
||||
|
||||
expect(style).to include(<<~CSS.indent(6))
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background-color: ##{light_secondary};
|
||||
}
|
||||
|
||||
#d-splash {
|
||||
--dot-color: ##{light_tertiary};
|
||||
}
|
||||
}
|
||||
CSS
|
||||
|
||||
expect(style).to include(<<~CSS.indent(6))
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: ##{dark_secondary};
|
||||
}
|
||||
|
||||
#d-splash {
|
||||
--dot-color: ##{dark_tertiary};
|
||||
}
|
||||
}
|
||||
CSS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Delegated auth" do
|
||||
@ -1526,4 +1625,193 @@ RSpec.describe ApplicationController do
|
||||
expect(response.headers["X-Discourse-Route"]).to eq("users/show")
|
||||
end
|
||||
end
|
||||
|
||||
describe "color definition stylesheets" do
|
||||
let!(:dark_scheme) { ColorScheme.find_by(base_scheme_id: "Dark") }
|
||||
|
||||
before do
|
||||
SiteSetting.default_dark_mode_color_scheme_id = dark_scheme.id
|
||||
SiteSetting.interface_color_selector = "sidebar_footer"
|
||||
end
|
||||
|
||||
context "with early hints" do
|
||||
before { global_setting :early_hint_header_mode, "preload" }
|
||||
|
||||
it "includes stylesheet links in the header" do
|
||||
get "/"
|
||||
|
||||
expect(response.headers["Link"]).to include("color_definitions_base")
|
||||
expect(response.headers["Link"]).to include("color_definitions_dark")
|
||||
end
|
||||
end
|
||||
|
||||
context "when the default theme's scheme is the same as the site's default dark scheme" do
|
||||
before { Theme.find(SiteSetting.default_theme_id).update!(color_scheme_id: dark_scheme.id) }
|
||||
|
||||
it "includes a single color stylesheet that has media=all" do
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(1)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
end
|
||||
|
||||
context "when light mode is forced" do
|
||||
before { cookies[:forced_color_mode] = "light" }
|
||||
|
||||
it "includes a light stylesheet with media=all and a dark stylesheet with media=none" do
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(2)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
dark_stylesheet = color_stylesheets.find { |tag| tag[:class] == "dark-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
expect(dark_stylesheet[:media]).to eq("none")
|
||||
end
|
||||
|
||||
context "when the dark scheme no longer exists" do
|
||||
it "includes only a light stylesheet with media=all" do
|
||||
dark_scheme.destroy!
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(1)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
end
|
||||
|
||||
context "when all schemes are deleted" do
|
||||
it "includes only a light stylesheet with media=all" do
|
||||
ColorScheme.destroy_all
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(1)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when dark mode is forced" do
|
||||
before { cookies[:forced_color_mode] = "dark" }
|
||||
|
||||
it "includes a light stylesheet with media=none and a dark stylesheet with media=all" do
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(2)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
dark_stylesheet = color_stylesheets.find { |tag| tag[:class] == "dark-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("none")
|
||||
expect(dark_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
|
||||
context "when the dark scheme no longer exists" do
|
||||
it "includes only a light stylesheet with media=all" do
|
||||
dark_scheme.destroy!
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(1)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
end
|
||||
|
||||
context "when all schemes are deleted" do
|
||||
it "includes only a light stylesheet with media=all" do
|
||||
ColorScheme.destroy_all
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(1)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when color mode is automatic" do
|
||||
before { cookies[:forced_color_mode] = nil }
|
||||
|
||||
it "includes a light stylesheet with media=(prefers-color-scheme: light) and a dark stylesheet with media=(prefers-color-scheme: dark)" do
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(2)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
dark_stylesheet = color_stylesheets.find { |tag| tag[:class] == "dark-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("(prefers-color-scheme: light)")
|
||||
expect(dark_stylesheet[:media]).to eq("(prefers-color-scheme: dark)")
|
||||
end
|
||||
|
||||
context "when the dark scheme no longer exists" do
|
||||
it "includes only a light stylesheet with media=all" do
|
||||
dark_scheme.destroy!
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(1)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
end
|
||||
|
||||
context "when all schemes are deleted" do
|
||||
it "includes only a light stylesheet with media=all" do
|
||||
ColorScheme.destroy_all
|
||||
get "/"
|
||||
|
||||
color_stylesheets =
|
||||
css_select("link").select { |tag| tag[:href].include?("color_definitions") }
|
||||
|
||||
expect(color_stylesheets.size).to eq(1)
|
||||
|
||||
light_stylesheet = color_stylesheets.find { |tag| tag[:class] == "light-scheme" }
|
||||
|
||||
expect(light_stylesheet[:media]).to eq("all")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user