diff --git a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss index 7e4e9b8e73..4de12ec2c2 100644 --- a/src/pydata_sphinx_theme/assets/styles/variables/_color.scss +++ b/src/pydata_sphinx_theme/assets/styles/variables/_color.scss @@ -80,52 +80,70 @@ $pst-semantic-colors: ( /******************************************************************************* * write the color rules for each theme (light/dark) -* -* NOTE: @each {...} is like a for-loop -* https://sass-lang.com/documentation/at-rules/control/each -* and #{...} inserts a variable into a CSS selector or property name -* https://sass-lang.com/documentation/interpolation */ -@each $mode in (light, dark) { - html[data-theme="#{$mode}"] { - @each $name, $value in $pst-semantic-colors { - // check if this color is defined differently for light/dark - @if type-of($value) == map { - $value: map-get($value, $mode); - } + +/* NOTE: + * Mixins enable us to reuse the same definitions for the different modes + * https://sass-lang.com/documentation/at-rules/mixin + * #{...} inserts a variable into a CSS selector or property name + * https://sass-lang.com/documentation/interpolation + */ +@mixin theme-colors($mode) { + // check if this color is defined differently for light/dark + @each $name, $value in $pst-semantic-colors { + @if type-of($value) == map { + $value: map-get($value, $mode); + } + & { --pst-color-#{$name}: #{$value}; } - // assign the "duplicate" colors (ones that just reference other variables) + } + // assign the "duplicate" colors (ones that just reference other variables) + & { --pst-color-link: var(--pst-color-primary); --pst-color-link-hover: var(--pst-color-warning); - // adapt to light/dark-specific content - @if $mode == "light" { - .only-dark { - display: none !important; - } - } @else { - .only-light { - display: none !important; - } - /* Adjust images in dark mode (unless they have class .only-dark or - * .dark-light, in which case assume they're already optimized for dark - * mode). - */ - img:not(.only-dark):not(.dark-light) { - filter: brightness(0.8) contrast(1.2); - } - /* Give images a light background in dark mode in case they have - * transparency and black text (unless they have class .only-dark or .dark-light, in - * which case assume they're already optimized for dark mode). - */ - .bd-content img:not(.only-dark):not(.dark-light) { - background: rgb(255, 255, 255); - border-radius: 0.25rem; - } - // MathJax SVG outputs should be filled to same color as text. - .MathJax_SVG * { - fill: var(--pst-color-text-base); - } + } + // adapt to light/dark-specific content + @if $mode == "light" { + .only-dark { + display: none !important; + } + } @else { + .only-light { + display: none !important; + } + /* Adjust images in dark mode (unless they have class .only-dark or + * .dark-light, in which case assume they're already optimized for dark + * mode). + */ + img:not(.only-dark):not(.dark-light) { + filter: brightness(0.8) contrast(1.2); } + /* Give images a light background in dark mode in case they have + * transparency and black text (unless they have class .only-dark or .dark-light, in + * which case assume they're already optimized for dark mode). + */ + .bd-content img:not(.only-dark):not(.dark-light) { + background: rgb(255, 255, 255); + border-radius: 0.25rem; + } + // MathJax SVG outputs should be filled to same color as text. + .MathJax_SVG * { + fill: var(--pst-color-text-base); + } + } +} + +/* Defaults to light mode if data-theme is not set */ +html:not([data-theme]) { + @include theme-colors("light"); +} + +/* NOTE: @each {...} is like a for-loop + * https://sass-lang.com/documentation/at-rules/control/each + */ +@each $mode in (light, dark) { + html[data-theme="#{$mode}"] { + @include theme-colors($mode); } } diff --git a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-logo.html b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-logo.html index c963997886..6336e13716 100644 --- a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-logo.html +++ b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-logo.html @@ -13,8 +13,16 @@ {% set is_logo = "light" in theme_logo["image_relative"] %} {% set alt = theme_logo.get("alt_text", "Logo image") %} {% if is_logo %} - - + {# Theme switching is only available when JavaScript is enabled. + # Thus we should add the extra image using JavaScript, defaulting + # depending on the value of default_mode; and light if unset. + #} + {% if default_mode is undefined %} + {% set default_mode = "light" %} + {% endif %} + {% set js_mode = "light" if default_mode == "dark" else "dark" %} + + {% endif %} {% if not is_logo or theme_logo.get("text") %}
{{ theme_logo.get("text") or docstitle }}
diff --git a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/search-button.html b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/search-button.html index 13262852b6..76fc231f91 100644 --- a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/search-button.html +++ b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/search-button.html @@ -1,4 +1,11 @@ -{# A button that, when clicked, will trigger a search popup overlay #} - +{# A button that, when clicked, will trigger a search popup overlay. + # + # As this function will only work when JavaScript is enabled, we add it through JavaScript. + #} + diff --git a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-switcher.html b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-switcher.html index 65c4831668..63c8cd239d 100644 --- a/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-switcher.html +++ b/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-switcher.html @@ -1,5 +1,11 @@ - for "basic/layout.html" to add a default `data-theme` attribute when + # a default mode has been set. This also improves compatibility when JavaScript is disabled. + #} +{% set html_tag %} + +{% endset %} {%- extends "basic/layout.html" %} {%- import "static/webpack-macros.html" as _webpack with context %} {# Metadata and asset linking #} diff --git a/tests/test_build.py b/tests/test_build.py index e653102dac..b23fb568d0 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -201,6 +201,51 @@ def test_logo_two_images(sphinx_build_factory): assert "Foo Title" in index_str +def test_primary_logo_is_light_when_no_default_mode(sphinx_build_factory): + """Test that the primary logo image is light + (and secondary, written through JavaScript, is dark) + when no default mode is set.""" + # Ensure no default mode is set + confoverrides = { + "html_context": {}, + } + sphinx_build = sphinx_build_factory("base", confoverrides=confoverrides).build() + index_html = sphinx_build.html_tree("index.html") + navbar_brand = index_html.select(".navbar-brand")[0] + assert navbar_brand.find("img", class_="only-light") is not None + assert navbar_brand.find("script", string=re.compile("only-dark")) is not None + + +def test_primary_logo_is_light_when_default_mode_is_light(sphinx_build_factory): + """Test that the primary logo image is light + (and secondary, written through JavaScript, is dark) + when default mode is set to light.""" + # Ensure no default mode is set + confoverrides = { + "html_context": {"default_mode": "light"}, + } + sphinx_build = sphinx_build_factory("base", confoverrides=confoverrides).build() + index_html = sphinx_build.html_tree("index.html") + navbar_brand = index_html.select(".navbar-brand")[0] + assert navbar_brand.find("img", class_="only-light") is not None + assert navbar_brand.find("script", string=re.compile("only-dark")) is not None + + +def test_primary_logo_is_dark_when_default_mode_is_dark(sphinx_build_factory): + """Test that the primary logo image is dark + (and secondary, written through JavaScript, is light) + when default mode is set to dark.""" + # Ensure no default mode is set + confoverrides = { + "html_context": {"default_mode": "dark"}, + } + sphinx_build = sphinx_build_factory("base", confoverrides=confoverrides).build() + index_html = sphinx_build.html_tree("index.html") + navbar_brand = index_html.select(".navbar-brand")[0] + assert navbar_brand.find("img", class_="only-dark") is not None + assert navbar_brand.find("script", string=re.compile("only-light")) is not None + + def test_logo_missing_image(sphinx_build_factory): """Test that a missing image will raise a warning.""" # Test with a specified title and a dark logo @@ -683,7 +728,11 @@ def test_theme_switcher(sphinx_build_factory, file_regression): """Regression test the theme switcher btn HTML""" sphinx_build = sphinx_build_factory("base").build() - switcher = sphinx_build.html_tree("index.html").select(".theme-switch-button")[0] + switcher = ( + sphinx_build.html_tree("index.html") + .find(string=re.compile("theme-switch-button")) + .find_parent("script") + ) file_regression.check( switcher.prettify(), basename="navbar_theme", extension=".html" ) diff --git a/tests/test_build/navbar_theme.html b/tests/test_build/navbar_theme.html index 61345ebd22..e3dfce411d 100644 --- a/tests/test_build/navbar_theme.html +++ b/tests/test_build/navbar_theme.html @@ -1,14 +1,9 @@ - + diff --git a/tests/test_build/sidebar_subpage.html b/tests/test_build/sidebar_subpage.html index 89c4b9d92f..ff7fd99103 100644 --- a/tests/test_build/sidebar_subpage.html +++ b/tests/test_build/sidebar_subpage.html @@ -36,20 +36,15 @@