diff --git a/core/context_processors.py b/core/context_processors.py index 7d55f5537..b962d5bd4 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -2,6 +2,7 @@ from enum import StrEnum from django.conf import settings +from django.template.defaultfilters import capfirst from django.urls import NoReverseMatch, reverse from libraries.constants import LATEST_RELEASE_URL_PATH_STR @@ -109,7 +110,9 @@ def selected_version(request): version = header_data.most_recent is_non_latest = _is_non_latest_version(url_version_slug, cookie_slug) - label = version.display_name if (is_non_latest and version) else "Latest" + # capfirst so the "master"/"develop" branch names render as "Master"/"Develop"; + # numeric release names are unaffected. + label = capfirst(version.display_name) if (is_non_latest and version) else "Latest" latest_href = "" if is_url_driven and resolver_match and resolver_match.view_name: diff --git a/core/tests/test_context_processors.py b/core/tests/test_context_processors.py index e153a8b41..58151c17d 100644 --- a/core/tests/test_context_processors.py +++ b/core/tests/test_context_processors.py @@ -3,6 +3,7 @@ import pytest from django.test import RequestFactory from django.urls import ResolverMatch +from model_bakery import baker from core.context_processors import ( active_nav_item, @@ -147,3 +148,26 @@ def test_selected_version_no_resolver_match(rf): ctx = selected_version(request) assert ctx["selected_version_is_url_driven"] is False assert ctx["selected_version_label"] == "Latest" + + +@pytest.mark.parametrize( + "branch,expected_label", + [("master", "Master"), ("develop", "Develop")], +) +def test_selected_version_label_capitalizes_branch_names( + db, rf, branch, expected_label +): + """Branch versions are excluded from the header dropdown options, so the + label comes from the DB fallback — it must still read "Master"/"Develop".""" + baker.make("versions.Version", name=branch, slug=branch, fully_imported=True) + request = rf.get(f"/library/{branch}/foo/") + _stub_header_data(request) + _attach_resolver( + request, + route="library///", + kwargs={"version_slug": branch, "library_slug": "foo"}, + view_name="library-detail", + ) + ctx = selected_version(request) + assert ctx["selected_version_is_non_latest"] is True + assert ctx["selected_version_label"] == expected_label diff --git a/libraries/templatetags/branch_url_tag.py b/libraries/templatetags/branch_url_tag.py index baf8e97db..1433561be 100644 --- a/libraries/templatetags/branch_url_tag.py +++ b/libraries/templatetags/branch_url_tag.py @@ -6,5 +6,4 @@ @register.simple_tag() def branch_url_tag(view: str, branch: str, kwargs: dict): - kwargs["version_slug"] = branch - return reverse(view, kwargs=kwargs) + return reverse(view, kwargs={**kwargs, "version_slug": branch}) diff --git a/static/css/v3/branch-buttons.css b/static/css/v3/branch-buttons.css new file mode 100644 index 000000000..164c0a2ff --- /dev/null +++ b/static/css/v3/branch-buttons.css @@ -0,0 +1,86 @@ +/* + Branch Buttons — small Master ("M") / Develop ("D") version-switch links. + Depends on: foundations.css, semantics.css, spacing.css, border-radius.css +*/ + +.branch-buttons { + display: flex; + align-items: center; + gap: var(--space-s); +} + +.branch-button { + position: relative; + display: inline-block; + flex-shrink: 0; + width: 29px; + height: 28px; + color: var(--color-text-primary); + text-decoration: none; +} + +.branch-button__square { + position: absolute; + top: 4px; + left: 5px; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + background-color: var(--color-surface-weak); + border: 1px solid var(--color-stroke-weak); + border-radius: var(--border-radius-s); + transition: border-color 0.15s ease; +} + +.branch-button__icon { + width: 16px; + height: 16px; + color: var(--color-text-primary); +} + +.branch-button__badge { + position: absolute; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + min-width: 12px; + padding: 2px; + border-radius: 60px; + background-color: var(--color-button-accent); + color: var(--color-text-on-accent); + font-size: 8px; + line-height: 1; + letter-spacing: -0.08px; + text-align: center; +} + +.branch-button:hover .branch-button__square { + border-color: var(--color-stroke-brand-accent); +} + +.branch-button:focus-visible { + outline: 2px solid var(--color-stroke-brand-accent); + outline-offset: 2px; + border-radius: var(--border-radius-s); +} + +/* Sub-page hero: far right, bottom-aligned with the actions row; right edge + inset to line up with the content cards below. */ +.hero__block > .branch-buttons { + align-self: flex-end; + flex-shrink: 0; + margin-right: var(--space-large); +} + +/* Mobile: stacks below the actions row, left-aligned with the cards. */ +@media (max-width: 767px) { + .hero__block > .branch-buttons { + align-self: flex-start; + margin-right: 0; + margin-top: var(--space-large); + } +} diff --git a/static/css/v3/components.css b/static/css/v3/components.css index af9ded68e..b48fe5077 100644 --- a/static/css/v3/components.css +++ b/static/css/v3/components.css @@ -43,6 +43,7 @@ @import "./user-card.css"; @import "./post-filter.css"; @import "./library-filter.css"; +@import "./branch-buttons.css"; @import "./contributors-list.css"; @import "./card-group.css"; @import "./post-card.css"; diff --git a/static/css/v3/library-page.css b/static/css/v3/library-page.css index c1f8b41e0..8abc60cb3 100644 --- a/static/css/v3/library-page.css +++ b/static/css/v3/library-page.css @@ -18,6 +18,23 @@ padding: 0 var(--space-large); } +/* Filter toolbar row: filters fill the row, branch buttons sit at the far right, + bottom-aligned with the filter dropdowns. */ +.library-page__filter-row { + display: flex; + align-items: flex-end; + gap: var(--space-medium); +} + +.library-page__filter-row .library-filter { + flex: 1 1 auto; + min-width: 0; +} + +.library-page__filter-row .branch-buttons { + flex: 0 0 auto; +} + /* === Hero === */ .library-page-hero { diff --git a/templates/includes/icon.html b/templates/includes/icon.html index 012a292a0..1e64059d6 100644 --- a/templates/includes/icon.html +++ b/templates/includes/icon.html @@ -153,6 +153,8 @@ {% elif icon_name == "pixel-share" %} + {% elif icon_name == "git-branch" %} + {% elif icon_name == "file-multiple" %} {% elif icon_name == "rss" %} diff --git a/templates/v3/includes/_branch_buttons.html b/templates/v3/includes/_branch_buttons.html new file mode 100644 index 000000000..e48849f5e --- /dev/null +++ b/templates/v3/includes/_branch_buttons.html @@ -0,0 +1,23 @@ +{% load branch_url_tag %} +{% comment %} + V3 Branch Buttons + + Links that switch the current page to the Master / Develop Boost version. + URLs are derived from the current view via branch_url_tag, which swaps the + version_slug — so this partial is page-agnostic and takes no variables. + Requires the `request` context processor (project default). +{% endcomment %} + diff --git a/templates/v3/includes/_hero_library.html b/templates/v3/includes/_hero_library.html index 6627bf2e8..56fae2c05 100644 --- a/templates/v3/includes/_hero_library.html +++ b/templates/v3/includes/_hero_library.html @@ -18,6 +18,7 @@ hero_background_image_url (optional) — Background image URL for the hero container. extra_classes (optional) — Extra modifier classes for the hero
, e.g. "hero--community" (community) or "hero--release" (release detail). is_flagship_lib (optional) — Boolean value stating whether or not this is the hero for a flagship library. If it isn't, styles are a bit different. + show_branch_buttons (optional) — Boolean. If truthy, renders the small Master/Develop version-switch buttons (v3/includes/_branch_buttons.html) at the far right of the actions/links row. Off by default so the buttons only appear on library sub-pages, not other heroes (community, release, examples). If none of category_tags/version_tag/first_boost_version are passed, the tags row is hidden. If none of doc_url/source_url/slack_url/github_url are passed, @@ -97,6 +98,9 @@

{% endwith %} {% endif %} + {% if show_branch_buttons %} + {% include "v3/includes/_branch_buttons.html" %} + {% endif %} {% include "v3/includes/_version_alert.html" %}

diff --git a/templates/v3/libraries/library-subpage.html b/templates/v3/libraries/library-subpage.html index 5656fe6ce..03c4722c2 100644 --- a/templates/v3/libraries/library-subpage.html +++ b/templates/v3/libraries/library-subpage.html @@ -19,9 +19,9 @@

No library records available for this vers {% else %} {% with cpp_ver=library_version.get_cpp_standard_minimum_display|default:"C++03" %} {% if is_flagship_lib %} - {% include "v3/includes/_hero_library.html" with title=object.display_name description=library_version.description|default:object.description category_tags=category_tags_v3 version_tag=cpp_ver first_boost_version=object.first_boost_version.display_name doc_url=documentation_url source_url=github_url slack_url=slack_url github_url=object.github_issues_url is_flagship_lib=True hero_image_url_light=library_hero_image_url_light hero_image_url_dark=library_hero_image_url_dark %} + {% include "v3/includes/_hero_library.html" with title=object.display_name description=library_version.description|default:object.description category_tags=category_tags_v3 version_tag=cpp_ver first_boost_version=object.first_boost_version.display_name doc_url=documentation_url source_url=github_url slack_url=slack_url github_url=object.github_issues_url is_flagship_lib=True hero_image_url_light=library_hero_image_url_light hero_image_url_dark=library_hero_image_url_dark show_branch_buttons=True %} {% else %} - {% include "v3/includes/_hero_library.html" with title=object.display_name description=library_version.description|default:object.description category_tags=category_tags_v3 version_tag=cpp_ver first_boost_version=object.first_boost_version.display_name doc_url=documentation_url source_url=github_url slack_url=slack_url github_url=object.github_issues_url %} + {% include "v3/includes/_hero_library.html" with title=object.display_name description=library_version.description|default:object.description category_tags=category_tags_v3 version_tag=cpp_ver first_boost_version=object.first_boost_version.display_name doc_url=documentation_url source_url=github_url slack_url=slack_url github_url=object.github_issues_url show_branch_buttons=True %} {% endif %} {% endwith %} diff --git a/templates/v3/library_page.html b/templates/v3/library_page.html index 6ebba3c53..9f71909ea 100644 --- a/templates/v3/library_page.html +++ b/templates/v3/library_page.html @@ -393,8 +393,9 @@

Find a battle-tested library for the problem you're solvi
-
+
{% include 'v3/includes/_library_filter.html' with filter_id='library-page-filter' fields=library_filter_fields clear_url=library_filter_clear_url %} + {% include 'v3/includes/_branch_buttons.html' %}
{% if library_view_str == 'list' %}