Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning][].

## [Unreleased]

### Added

- `secondary_accent` theme option for the sidebar captions, which `True` sets to the package’s second brand colour from the website (#73)

### Changed

- brand accents are synced from the website’s `accents.json`.
Expand Down
9 changes: 9 additions & 0 deletions docs/api/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ The registry name to look the accent and the dropdown’s current entry up under
CSS colour for decorative surfaces; link and text shades are derived from it.
```

```{confval} secondary_accent
:type: str | bool
:default: none

CSS colour for the sidebar captions; a readable shade is derived from it.
`True` takes the package’s second brand colour from the registry, if it has one.
Without one, the captions keep the text colour.
```

```{confval} show_ecosystem_dropdown
:type: bool
:default: `True`
Expand Down
12 changes: 8 additions & 4 deletions src/scverse_doc/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ def _build_cache(app: Sphinx) -> Path:

#: Brand accents from the website, copied by ``scripts/sync_brand_tokens.py``:
#: each key lists ``[name, [L, C, H]]`` pairs, primary first.
_ACCENTS: dict[str, str] = {
{"scvi": "scvi-tools"}.get(pkg, pkg): "oklch({} {} {})".format(*colors[0][1])
_ACCENTS: dict[str, list[str]] = {
{"scvi": "scvi-tools"}.get(pkg, pkg): ["oklch({} {} {})".format(*lch) for _, lch in colors]
for pkg, colors in json.loads(files(__package__).joinpath("_accents.json").read_text()).items()
}

#: The website’s ``--scverse-deep-blue``, used when a package has no accent of its own.
DEFAULT_ACCENT = _ACCENTS.pop("scverse")
DEFAULT_ACCENT = _ACCENTS.pop("scverse")[0]

#: Upstream ``category`` -> the ``kind`` recorded here.
#: ``core-infrastructure`` is skipped:
Expand Down Expand Up @@ -109,6 +109,9 @@ class Package:
accent: str = DEFAULT_ACCENT
"""The package’s brand accent, falling back to the scverse deep blue."""

secondary_accent: str | None = None
"""The package’s second brand colour, or :data:`None` if it has only one."""


class _Entry(TypedDict):
"""The fields of an upstream listing entry that the registry reads."""
Expand Down Expand Up @@ -152,7 +155,8 @@ def _load() -> Mapping[str, Package]:
inventory=meta.get("inventory"),
repo=meta.get("project_home"),
description=" ".join(meta.get("description", "").split()),
accent=_ACCENTS.get(name, DEFAULT_ACCENT),
accent=accents[0] if (accents := _ACCENTS.get(name, [])) else DEFAULT_ACCENT,
secondary_accent=accents[1] if len(accents) >= 2 else None,
)
# Sorted here, because upstream’s listing order is not stable.
for name, meta in sorted(((meta["name"].casefold(), meta) for meta in _fetch()), key=itemgetter(0))
Expand Down
23 changes: 22 additions & 1 deletion src/scverse_doc/theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@
}}}}
"""

_SECONDARY_ACCENT_CSS = f"""\
:root {{{{
--scverse-color-secondary-accent-text: light-dark(
oklch(from {{accent}} min(l, {_MAX_LIGHTNESS_ON_LIGHT}) c h),
oklch(from {{accent}} max(l, {_MIN_LIGHTNESS_ON_DARK}) c h)
);
}}}}
"""

#: scverse.org itself is linked from the scverse menu beside them.
_ICON_LINKS = ({"name": "Discourse", "url": "https://discourse.scverse.org/", "icon": "fa-brands fa-discourse"},)

Expand All @@ -61,6 +70,15 @@ def _accent(config: Config) -> str:
return DEFAULT_ACCENT


def _secondary_accent(config: Config) -> str | None:
accent: str | bool = config.html_theme_options.get("secondary_accent")
if accent is not True:
return accent or None
if pkg := packages.get(_package_name(config)):
return pkg.secondary_accent
return None


def _add_chrome(config: Config) -> None:
"""Add the brand icon links and the colour mode, alongside whatever :mod:`scverse_doc.source` set."""
options = config.html_theme_options
Expand All @@ -85,9 +103,12 @@ def _configure(app: Sphinx) -> None:

_add_chrome(config)

css = _ACCENT_CSS.format(accent=_accent(config))
if secondary := _secondary_accent(config):
css += _SECONDARY_ACCENT_CSS.format(accent=secondary)
static_dir = _build_cache(app) / "static"
static_dir.mkdir(parents=True, exist_ok=True)
(static_dir / "scverse-accent.css").write_text(_ACCENT_CSS.format(accent=_accent(config)))
(static_dir / "scverse-accent.css").write_text(css)
config.html_static_path = [*config.html_static_path, str(static_dir)]
app.add_css_file("scverse-accent.css")
app.add_js_file("scripts/readthedocs-integration.js", type="module")
Expand Down
7 changes: 7 additions & 0 deletions src/scverse_doc/theme/scverse/static/styles/scverse.css
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ a.dropdown-item {
}
}

nav.bd-links p.caption {
color: var(
--scverse-color-secondary-accent-text,
var(--pst-color-text-base)
);
}

readthedocs-flyout {
--readthedocs-flyout-background-color: transparent;
--readthedocs-flyout-font-family: inherit;
Expand Down
23 changes: 12 additions & 11 deletions src/scverse_doc/theme/scverse/theme.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@ sidebars = ["navbar-logo", "search-button-field", "scverse-sidebar-nav"]
[options]
package = ""
accent = ""
show_ecosystem_dropdown = "True"
secondary_accent = false
show_ecosystem_dropdown = true
# No navbar: its parts live in the primary sidebar and the article header.
navbar_start = ""
navbar_center = ""
navbar_end = ""
navbar_persistent = ""
navbar_start = []
navbar_center = []
navbar_end = []
navbar_persistent = []
article_header_start = ["toggle-primary-sidebar", "breadcrumbs"]
article_header_end = ["theme-switcher", "navbar-icon-links", "scverse-ecosystem", "toggle-secondary-sidebar"]
primary_sidebar_end = ["sidebar-ethical-ads", "sidebar-versions"]
secondary_sidebar_items = ["page-toc", "edit-this-page", "sourcelink"]
footer_start = "scverse-footer"
footer_center = ""
footer_end = ""
show_toc_level = "2"
navigation_depth = "3"
collapse_navigation = "False"
footer_start = ["scverse-footer"]
footer_center = []
footer_end = []
show_toc_level = 2
navigation_depth = 3
collapse_navigation = false
pygments_light_style = "tango"
pygments_dark_style = "monokai"
announcement = "https://scverse.org/announcement.html"
2 changes: 1 addition & 1 deletion tests/roots/minimal/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
extensions = ["scverse_doc", "sphinx.ext.linkcode"]
source_repository = "https://github.com/scverse/pertpy"
source_branch = "main"
html_theme_options = {"announcement": ""}
html_theme_options = {"announcement": "", "secondary_accent": True}
3 changes: 2 additions & 1 deletion tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def test_accent_is_derived_for_both_modes(minimal: tuple[SphinxTestApp, str]) ->
css = (Path(app.outdir) / "_static" / "scverse-accent.css").read_text()
assert "--scverse-color-accent-decorative: oklch(0.5 0.1 249);" in css
assert "oklch(from var(--scverse-color-accent-decorative) min(l, 0.54) c h)" in css
assert "oklch(from oklch(0.595 0.177 14) min(l, 0.54) c h)" in css


@needs_full
Expand Down Expand Up @@ -125,7 +126,7 @@ def test_conf_py_can_pick_another_theme(tmp_path: Path) -> None:
"""A theme that declares none of the source options must not be given any."""
app, _ = build(ROOTS / "minimal", tmp_path, html_theme="alabaster")
assert app.config.html_theme == "alabaster"
assert app.config.html_theme_options.keys() == {"announcement"}
assert app.config.html_theme_options.keys() == {"announcement", "secondary_accent"}


@needs_full
Expand Down
18 changes: 17 additions & 1 deletion tests/test_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
_MAX_LIGHTNESS_ON_LIGHT,
_MIN_LIGHTNESS_ON_DARK,
_accent,
_secondary_accent,
)

#: Minimum contrast ratio for normal-size body text under WCAG 2.1 AA.
WCAG_AA_NORMAL = 4.5

ACCENTS = sorted({pkg.accent for pkg in packages.values()})
ACCENTS = sorted({a for pkg in packages.values() for a in (pkg.accent, pkg.secondary_accent) if a})


def contrast(color: Color, background: str) -> float:
Expand Down Expand Up @@ -60,3 +61,18 @@ def test_bounds_are_load_bearing() -> None:
def test_accent_for_a_package_outside_the_registry(options: dict[str, str], expected: str) -> None:
config = SimpleNamespace(html_theme_options=options, project="not-an-scverse-package")
assert _accent(config) == expected # type: ignore[arg-type]


@pytest.mark.parametrize(
("project", "options", "expected"),
[
pytest.param("pertpy", {}, None, id="unset"),
pytest.param("pertpy", {"secondary_accent": True}, "oklch(0.595 0.177 14)", id="registry"),
pytest.param("anndata", {"secondary_accent": True}, None, id="single-colour"),
pytest.param("not-an-scverse-package", {"secondary_accent": True}, None, id="unknown"),
pytest.param("pertpy", {"secondary_accent": "#c0f"}, "#c0f", id="declared"),
],
)
def test_secondary_accent(project: str, options: dict[str, str], expected: str | None) -> None:
config = SimpleNamespace(html_theme_options=options, project=project)
assert _secondary_accent(config) == expected # type: ignore[arg-type]
Loading