Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dark theme and theme switcher #540

Merged
merged 59 commits into from Apr 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
07e416b
docs: exclude files created by jupyter
12rambau Jan 5, 2022
932844e
set up js for theme change
12rambau Jan 5, 2022
2469f07
demo with only background color change
12rambau Jan 5, 2022
f0cccbe
set-up all the dark theme colors
12rambau Jan 7, 2022
9850073
overwrite the pygment css file
12rambau Jan 7, 2022
338a1b5
add default_theme parameter
12rambau Jan 7, 2022
a1d9865
small update to documentation
12rambau Jan 7, 2022
6f50e5a
color support for ethical add
12rambau Jan 7, 2022
df23c55
missing alpha channel in css
12rambau Jan 7, 2022
84338de
docs: fix badly written css comments
12rambau Jan 18, 2022
5e3f3a5
Merge branch 'master' into dark-theme
12rambau Jan 18, 2022
81a2117
Update src/pydata_sphinx_theme/__init__.py
12rambau Feb 11, 2022
bf6044b
use typescript comment structure
12rambau Feb 11, 2022
c59735c
make the switch as a btn
12rambau Feb 11, 2022
25a8e1e
use lighter section headers
12rambau Feb 12, 2022
03a84ee
replace all colors with rgba defined colors
12rambau Feb 13, 2022
951a316
add comments on pygments theme switch
12rambau Feb 13, 2022
7342f64
move ethical-ads to its own scss file
12rambau Feb 13, 2022
06ab359
use theme options to control pygments styling
12rambau Feb 14, 2022
3b36764
change theme switch color
12rambau Feb 14, 2022
9ecd5e4
remind that pygments_style is overwritten
12rambau Feb 14, 2022
31f7c5e
add comment to js theme management functions
12rambau Feb 14, 2022
5b1452d
refactor cycleTheme
12rambau Feb 14, 2022
ecdf638
prevent flickering when switching theme
12rambau Feb 14, 2022
8eb2c1e
set the theme-switcher as a template
12rambau Feb 14, 2022
36445bc
document the css trick
12rambau Feb 16, 2022
3e07469
document the css trick
12rambau Feb 16, 2022
a474db1
change colors to improve accecibility
12rambau Feb 16, 2022
c822508
change colors to improve accecibility
12rambau Feb 16, 2022
2c28826
Merge branch 'master' into dark-theme
12rambau Feb 16, 2022
e107a14
apply pre-commit checks
12rambau Feb 16, 2022
5bb58c3
Merge branch 'dark-theme' of github.com:sepal-contrib/pydata-sphinx-t…
12rambau Feb 16, 2022
fec703b
add the theme switcher to the tests
12rambau Feb 16, 2022
06c7e13
quote Furo theme in comments
12rambau Feb 17, 2022
34b3934
add the css trick to all version modified widgets
12rambau Feb 17, 2022
17409bf
include sidebar and topic in the color scheme
12rambau Feb 17, 2022
9b13e31
refactor navbar-toggler
12rambau Feb 17, 2022
8413144
support for version btn
12rambau Feb 18, 2022
1406d1f
fix blockquote colors
12rambau Feb 18, 2022
ba3e59a
listen to the span instead of the a tag
12rambau Feb 18, 2022
82c2e79
use the same color for hov and focus
12rambau Feb 18, 2022
a2aaf68
typo
12rambau Mar 9, 2022
0f9f477
typo
12rambau Mar 9, 2022
80cb20e
set the theme switcher in the navbar-end
12rambau Mar 9, 2022
e4c68a4
set back the theme-switcher in pydata documentation
12rambau Mar 9, 2022
43ceb27
Merge branch 'master' into dark-theme
12rambau Mar 12, 2022
4b6f568
replace orange and green
12rambau Mar 12, 2022
1bff226
use color in the gihub action report
12rambau Mar 12, 2022
5220fb9
test the theme switcher
12rambau Mar 12, 2022
ba04a68
remove the theme switcher container from the test
12rambau Mar 12, 2022
48d3d6b
typo
12rambau Mar 12, 2022
1a45cc7
docs: disclaimer on colors
12rambau Mar 12, 2022
02f185c
docs: typo
12rambau Mar 12, 2022
df22fc2
Merge pull request #1 from sepal-contrib/master
12rambau Mar 27, 2022
27e7bd6
reduce minscore for lighthouse SEO expectations
12rambau Mar 28, 2022
5ef0ac3
Cleaning up the colors for accessibility
choldgraf Mar 29, 2022
45860d7
Warning about stability
choldgraf Mar 29, 2022
decac66
Merge pull request #2 from pydata/dark-theme
12rambau Mar 30, 2022
45d65c3
Update src/pydata_sphinx_theme/assets/styles/_navbar.scss
12rambau Apr 8, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lighthouserc.json
Expand Up @@ -11,7 +11,7 @@
"categories:performance": ["error", { "minScore": 0.5 }],
"categories:accessibility": ["error", { "minScore": 0.8 }],
"categories:best-practices": ["error", { "minScore": 0.8 }],
"categories:seo": ["error", { "minScore": 0.8 }]
"categories:seo": ["error", { "minScore": 0.7 }]
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Expand Up @@ -66,7 +66,7 @@ jobs:

# Run tests under coverage
- name: Run the tests
run: pytest --cov pydata_sphinx_theme --cov-branch --cov-report term-missing:skip-covered --cov-fail-under ${{ env.COVERAGE_THRESHOLD }}
run: pytest --color=yes --cov pydata_sphinx_theme --cov-branch --cov-report term-missing:skip-covered --cov-fail-under ${{ env.COVERAGE_THRESHOLD }}

- name: Upload coverage
if: ${{ always() }}
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Expand Up @@ -61,7 +61,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"]

# -- Extension options -------------------------------------------------------

Expand Down Expand Up @@ -126,7 +126,7 @@
# "navbar_align": "left", # [left, content, right] For testing that the navbar items align properly
# "navbar_start": ["navbar-logo", "navbar-version"],
# "navbar_center": ["navbar-nav", "navbar-version"], # Just for testing
"navbar_end": ["version-switcher", "navbar-icon-links"],
"navbar_end": ["theme-switcher", "version-switcher", "navbar-icon-links"],
# "left_sidebar_end": ["custom-template.html", "sidebar-ethical-ads.html"],
# "footer_items": ["copyright", "sphinx-version", ""]
"switcher": {
Expand Down
31 changes: 30 additions & 1 deletion docs/user_guide/configuring.rst
Expand Up @@ -10,7 +10,7 @@ in your ``conf.py`` file. This is a dictionary with ``key: val`` pairs that
you can configure in various ways. This page describes the options available to you.

Configure project logo
==============================
======================

To add a logo that's placed at the left of your nav bar, put a logo file under your
doc path's _static folder, and use the following configuration:
Expand All @@ -31,6 +31,35 @@ If you'd like it to link to another page or use an external link instead, use th

.. _icon-links:

Configure default theme
=======================

The theme mode can be changed by the user. By default landing on the documentation will switch the mode to ``auto``. You can specified this value to be one of ``auto``, ``dark``, ``light``.

.. code-block:: python

html_context = {
...
"default_mode": "auto"
}

Configure pygment theme
=======================

As the Sphinx theme supports multiple modes, the code highlighting colors can be modified for each one of them by modifying the `pygment_light_style`and `pygment_style_style`. You can check available Pygments colors on this `page <https://help.farbox.com/pygments.html>`__.

.. code-block:: python

html_contexts = {
...
"pygment_light_style": "tango",
"pygment_dark_style": "native"
}

.. danger::

The native Sphinx option `pygments_style` will be overwritten by this theme.

Configure icon links
====================

Expand Down
46 changes: 45 additions & 1 deletion docs/user_guide/customizing.rst
Expand Up @@ -7,6 +7,13 @@ Customizing the theme
In addition to the configuration options detailed at :ref:`configuration`, it
is also possible to customize the HTML layout and CSS style of the theme.

.. danger::

This theme is still under active development, and we make no promises
about the stability of any specific HTML structure, CSS variables, etc.
Make these customizations at your own risk, and pin versions if you're
worried about breaking changes!

.. _custom-css:

Custom CSS Stylesheets
Expand All @@ -30,6 +37,41 @@ To add a custom stylesheet, follow these steps:

When you build your documentation, this stylesheet should now be activated.

.. _manage-themes:

Manage themes
=============

.. danger::

Theming is still a beta feature so the variables related to the theme switch are likely to change in the future. No backward compatibily is guaranteed when customization is done.

Pydata sphinx theme embed 3 different theming mode:

- ``auto``: the documentation theme will follow the one provided by your computer
- ``dark``: the documentation is displayed with the dark theme
- ``light``: the documentation is displayed with the light theme

In order to customize the display of any of the theme element you need to encaspulate your modifications in the approriate css rules:

.. code-block:: css

/* anything related to the light theme */
html[data-theme="light"] {

/* whatever you want to change */
background: white;
}

/* anything related to the dark theme */
html[data-theme="dark"] {

/* whatever you want to change */
background: black;
}

A complete list of the used colors for this theme can be found in the `pydata default css colors file <pydata-css-colors_>`__.

.. _css-variables:

CSS Theme variables
Expand Down Expand Up @@ -58,7 +100,8 @@ In order to change a variable, follow these steps:

Note that these are `CSS variables <css-variable-help_>`_ and not
`SASS variables <https://sass-lang.com/documentation/variables>`_.
The theme is defined with CSS variables, not SASS variables!
The theme is defined with CSS variables, not SASS variables! Refer to the previous section if
you desire a different behavior between the light and dark theme.

For a complete list of the theme variables that you may override, see the
`theme variables defaults CSS file <pydata-css-variables_>`_:
Expand Down Expand Up @@ -126,6 +169,7 @@ The default body and header fonts can be changed as follows:
before waiting for the CSS to be parsed, but should be used with care.

.. _pydata-css-variables: https://github.com/pydata/pydata-sphinx-theme/blob/master/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/static/styles/theme.css
.. _pydata-css-colors: https://github.com/pydata/pydata-sphinx-theme/blob/master/src/pydata_sphinx_theme/assets/style/color.scss
.. _css-variable-help: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties

.. meta::
Expand Down
1 change: 1 addition & 0 deletions docs/user_guide/sections.rst
Expand Up @@ -138,6 +138,7 @@ will be named accordingly).
- ``sidebar-nav-bs.html``
- ``sphinx-version.html``
- ``version-switcher.html``
- ``theme-switcher.html``

Add your own HTML templates to theme sections
=============================================
Expand Down
84 changes: 84 additions & 0 deletions src/pydata_sphinx_theme/__init__.py
Expand Up @@ -10,6 +10,8 @@
from sphinx.environment.adapters.toctree import TocTree
from sphinx.errors import ExtensionError
from sphinx.util import logging
from pygments.formatters import HtmlFormatter
from pygments.styles import get_all_styles

from .bootstrap_html_translator import BootstrapHTML5Translator

Expand Down Expand Up @@ -547,6 +549,87 @@ def get_edit_url():
context["theme_show_toc_level"] = int(context.get("theme_show_toc_level", 1))


# ------------------------------------------------------------------------------
# handle pygment css
# ------------------------------------------------------------------------------

# inspired by the Furo theme
# https://github.com/pradyunsg/furo/blob/main/src/furo/__init__.py


def _get_styles(formatter, prefix):
"""
Get styles out of a formatter, where everything has the correct prefix.
"""

for line in formatter.get_linenos_style_defs():
yield f"{prefix} {line}"
yield from formatter.get_background_style_defs(prefix)
yield from formatter.get_token_style_defs(prefix)


def get_pygments_stylesheet(light_style, dark_style):
"""
Generate the theme-specific pygments.css.
There is no way to tell Sphinx how the theme handles modes
"""
light_formatter = HtmlFormatter(style=light_style)
dark_formatter = HtmlFormatter(style=dark_style)

lines = []

light_prefix = 'html[data-theme="light"] .highlight'
lines.extend(_get_styles(light_formatter, prefix=light_prefix))

dark_prefix = 'html[data-theme="dark"] .highlight'
lines.extend(_get_styles(dark_formatter, prefix=dark_prefix))

return "\n".join(lines)
12rambau marked this conversation as resolved.
Show resolved Hide resolved


def _overwrite_pygments_css(app, exception=None):
"""
Sphinx is not build to host multiple sphinx formatter and there is no way
to tell which one to use and when.
So yes, at build time we overwrite the pygment.css file so that it embeds
2 versions:
- the light theme prefixed with "[data-theme="light"]" using tango
- the dark theme prefixed with "[data-theme="dark"]" using native

When the theme is switched, Pygments will be using one of the preset css
style.
"""
default_light_theme = "tango"
default_dark_theme = "native"

if exception is not None:
return

assert app.builder

# check the theme specified in the theme options
theme_options = app.config["html_theme_options"]
pygments_styles = list(get_all_styles())
light_theme = theme_options.get("pygment_light_style", default_light_theme)
if light_theme not in pygments_styles:
logger.warn(
f"{light_theme}, is not part of the available pygments style,"
f' defaulting to "{default_light_theme}".'
)
light_theme = default_light_theme
dark_theme = theme_options.get("pygment_dark_style", default_dark_theme)
if dark_theme not in pygments_styles:
logger.warn(
f"{dark_theme}, is not part of the available pygments style,"
f' defaulting to "{default_dark_theme}".'
)
dark_theme = default_dark_theme

pygment_css = Path(app.builder.outdir) / "_static" / "pygments.css"
with pygment_css.open("w") as f:
f.write(get_pygments_stylesheet(light_theme, dark_theme))


# -----------------------------------------------------------------------------


Expand All @@ -567,6 +650,7 @@ def setup(app):
app.connect("html-page-context", setup_edit_url)
app.connect("html-page-context", add_toctree_functions)
app.connect("html-page-context", update_templates)
app.connect("build-finished", _overwrite_pygments_css)

# Include templates for sidebar
app.config.templates_path.append(str(theme_path / "_templates"))
Expand Down
88 changes: 88 additions & 0 deletions src/pydata_sphinx_theme/assets/scripts/index.js
Expand Up @@ -9,6 +9,89 @@ import "bootstrap";

import "../styles/index.scss";

/*******************************************************************************
* Theme interaction
*/

var prefersDark = window.matchMedia("(prefers-color-scheme: dark)");

/**
* set the the body theme to the one specified by the user browser
* @param {event} e
*/
function autoTheme(e) {
document.documentElement.dataset.theme = prefersDark.matches
? "dark"
: "light";
}

/**
* Set the theme using the specified mode.
* It can be one of ["auto", "dark", "light"]
* @param {str} mode
*/
function setTheme(mode) {
if (mode !== "light" && mode !== "dark" && mode !== "auto") {
console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the future, how do you envision new light and dark modes using new themes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no more than 2 themes at the time should be used in documentation.
If we want to add extra themes in the lib (high contrast, color-blind friendly etc...) I think we should select a different theming file at build time. For this purpose, we could split dark and light default theme in 2 files and add 2 parameters (or 1 dict) in the conf.py file.

mode = "auto";
}

// get the theme
var colorScheme = prefersDark.matches ? "dark" : "light";
document.documentElement.dataset.mode = mode;
var theme = mode == "auto" ? colorScheme : mode;
document.documentElement.dataset.theme = theme;

// save mode and theme
localStorage.setItem("mode", mode);
localStorage.setItem("theme", theme);
console.log(`Changed to ${mode} mode using the ${theme} theme.`);

// add a listener if set on auto
prefersDark.onchange = mode == "auto" ? autoTheme : "";
}

/**
* Change the theme option order so that clicking on the btn is always a change
* from "auto"
*/
function cycleMode() {
const defaultMode = document.documentElement.dataset.defaultMode || "auto";
const currentMode = localStorage.getItem("mode") || defaultMode;

var loopArray = (arr, current) => {
var nextPosition = arr.indexOf(current) + 1;
if (nextPosition === arr.length) {
nextPosition = 0;
}
return arr[nextPosition];
};

// make sure the next theme after auto is always a change
var modeList = prefersDark.matches
? ["auto", "light", "dark"]
: ["auto", "dark", "light"];
var newMode = loopArray(modeList, currentMode);
setTheme(newMode);
}

/**
* add the theme listener on the btns of the navbar
*/
function addModeListener() {
// the theme was set a first time using the initial mini-script
// running setMode will ensure the use of the dark mode if auto is selected
setTheme(document.documentElement.dataset.mode);

// Attach event handlers for toggling themes colors
const btn = document.getElementById("theme-switch");
btn.addEventListener("click", cycleMode);
}

/*******************************************************************************
* TOC interactivity
*/

function addTOCInteractivity() {
// TOC sidebar - add "active" class to parent list
//
Expand All @@ -31,6 +114,10 @@ function addTOCInteractivity() {
});
}

/*******************************************************************************
* Scroll
*/

// Navigation sidebar scrolling to active page
function scrollToActive() {
var sidebar = document.querySelector("div.bd-sidebar");
Expand Down Expand Up @@ -73,5 +160,6 @@ function scrollToActive() {

// This is equivalent to the .ready() function as described in
// https://api.jquery.com/ready/
$(addModeListener);
$(scrollToActive);
$(addTOCInteractivity);