Skip to content

Commit 794d081

Browse files
authored
Story 2394: Version-aware Homepage Install Card (#2460)
1 parent 5c690f2 commit 794d081

7 files changed

Lines changed: 158 additions & 1 deletion

File tree

static/css/v3/install-card.css

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,47 @@
170170

171171
/* Per-option visibility rules are generated by Django in _install_card.html */
172172

173+
/* ── Latest/older variant toggle ─────────────────────────── */
174+
/* Both variants are always rendered server-side; the wrapper's data-active-variant
175+
attribute controls which one is visible. JS flips it on version-dropdown change;
176+
with JS disabled, the server-rendered value stays and a full request swaps it. */
177+
[data-install-card-wrapper][data-active-variant="latest"] .install-card--older,
178+
[data-install-card-wrapper][data-active-variant="older"] .install-card:not(.install-card--older) {
179+
display: none;
180+
}
181+
182+
@keyframes install-card-fade-in {
183+
from { opacity: 0; transform: translateY(4px); }
184+
to { opacity: 1; transform: translateY(0); }
185+
}
186+
187+
.install-card--entering {
188+
animation: install-card-fade-in 200ms ease-out;
189+
}
190+
191+
/* ── Older-version variant ───────────────────────────────── */
192+
.install-card--older {
193+
gap: var(--space-large);
194+
}
195+
196+
.install-card__older-message {
197+
margin: 0;
198+
/* Override the site-wide `p { @apply py-5 }` from frontend/styles.css */
199+
padding-top: 0;
200+
padding-bottom: 0;
201+
color: var(--color-text-secondary);
202+
font-size: var(--font-size-base);
203+
line-height: var(--line-height-loose);
204+
}
205+
206+
.install-card__older-cta {
207+
display: flex;
208+
}
209+
210+
.install-card__older-cta .btn {
211+
flex: 1 0 0;
212+
}
213+
173214
/* ── Responsive ───────────────────────────────────────────── */
174215
@media (max-width: 767px) {
175216
.install-card__dropdown {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Install Card — In-place version sync
3+
*
4+
* Listens for the `boost:version-changed` CustomEvent dispatched by the header
5+
* version-dropdown JS (see _header_v3.html) and swaps the install card between
6+
* its "latest" and "older" variants without a page reload.
7+
*
8+
* Progressive enhancement: with JS disabled, the dropdown's form submits
9+
* normally and the server's POST + 302 + GET roundtrip swaps the card.
10+
*
11+
* Requires:
12+
* - Wrapper element [data-install-card-wrapper][data-active-variant]
13+
* around each install card render (see _install_card.html).
14+
* - The header JS to dispatch `boost:version-changed` with detail
15+
* { slug, label, isLatest }.
16+
*/
17+
18+
function _getOrCreateLiveRegion() {
19+
var el = document.getElementById("install-card-live");
20+
if (el) return el;
21+
el = document.createElement("span");
22+
el.id = "install-card-live";
23+
el.setAttribute("aria-live", "polite");
24+
el.style.cssText =
25+
"position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;";
26+
document.body.appendChild(el);
27+
return el;
28+
}
29+
30+
document.addEventListener("boost:version-changed", function (e) {
31+
var detail = e.detail || {};
32+
var isLatest = detail.isLatest === true;
33+
var label = detail.label || "";
34+
35+
document
36+
.querySelectorAll("[data-install-card-wrapper]")
37+
.forEach(function (wrapper) {
38+
var prev = wrapper.getAttribute("data-active-variant");
39+
var next = isLatest ? "latest" : "older";
40+
41+
wrapper.setAttribute("data-active-variant", next);
42+
43+
if (prev !== next) {
44+
var visible = isLatest
45+
? wrapper.querySelector(".install-card:not(.install-card--older)")
46+
: wrapper.querySelector(".install-card--older");
47+
if (visible) {
48+
visible.classList.remove("install-card--entering");
49+
void visible.offsetWidth;
50+
visible.classList.add("install-card--entering");
51+
visible.addEventListener(
52+
"animationend",
53+
function () {
54+
visible.classList.remove("install-card--entering");
55+
},
56+
{ once: true }
57+
);
58+
}
59+
}
60+
});
61+
62+
var liveRegion = _getOrCreateLiveRegion();
63+
liveRegion.textContent = isLatest
64+
? "Showing install steps for the latest version."
65+
: "Showing documentation link for Boost " + label + ".";
66+
});

templates/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<script src="{% static 'js/code-block.js' %}" defer></script>
5050
<script src="{% static 'js/boost-gecko/main.CsWsx5SC.js' %}" defer></script>
5151
<script src="{% static 'js/install-card.js' %}" defer></script>
52+
<script src="{% static 'js/install-card-version-sync.js' %}" defer></script>
5253
{% else %}
5354
<script src="{% static 'js/boost-gecko/main.D1bdgn0u.js' %}" defer></script>
5455
{% endflag %}

templates/v3/includes/_code_block_card.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
button_text (optional) — if set, a button is shown
1212
button_url (optional) — button href, default "#"
1313
button_aria_label (optional) — accessible name for the button; defaults to button_text if omitted
14+
button_style (optional) — forwarded to _button.html style (e.g. "teal", "yellow"); defaults to primary
1415
{% endcomment %}
1516
<div class="code-block-card code-block-card--{{ card_variant|default:'neutral' }}">
1617
<h2 class="card__title code-block-card__heading">{{ heading }}</h2>
1718
{% if description %}<p class="code-block-card__description">{{ description }}</p>{% endif %}
1819
{% if code or code_html %}{% include "v3/includes/_code_block.html" with code=code code_html=code_html language=language variant=block_variant|default:"grey-bg" %}{% endif %}
1920
{% if button_text %}
2021
<div class="card__cta_section">
21-
{% include "v3/includes/_button.html" with label=button_text url=button_url|default:'#' aria_label=button_aria_label|default:button_text %}
22+
{% include "v3/includes/_button.html" with label=button_text url=button_url|default:'#' aria_label=button_aria_label|default:button_text style=button_style %}
2223
</div>
2324
{% endif %}
2425
</div>

templates/v3/includes/_header_v3.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@
136136
* Progressive enhancement for the cookie-mode version dropdown: send the
137137
* POST via fetch and update the UI in place, avoiding a full page reload.
138138
* Falls back to a normal form submit on any error.
139+
*
140+
* After a successful fetch, dispatch a `boost:version-changed` CustomEvent
141+
* on document so other components (e.g. the install card) can react without
142+
* a page reload. Detail: { slug, label, isLatest, docUrl }.
139143
*/
140144
document.addEventListener('submit', function (e) {
141145
var form = e.target;
@@ -149,6 +153,8 @@
149153

150154
var selectedValue = submitter.value;
151155
var newLabel = submitter.textContent.trim();
156+
var isLatest = submitter.dataset.isLatest === 'true';
157+
var docUrl = submitter.dataset.docUrl || '';
152158
var formData = new FormData(form);
153159
formData.set('version', selectedValue);
154160

@@ -177,6 +183,10 @@
177183
var toggle = menu.querySelector('.header__version-toggle');
178184
if (toggle) toggle.checked = false;
179185
});
186+
187+
document.dispatchEvent(new CustomEvent('boost:version-changed', {
188+
detail: { slug: selectedValue, label: newLabel, isLatest: isLatest, docUrl: docUrl }
189+
}));
180190
}).catch(function () {
181191
form.submit();
182192
});

templates/v3/includes/_install_card.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
are visible. Dynamic :checked ~ rules are generated in the inline <style>
1313
block below because the number of options is data-driven.
1414

15+
Both the latest-version and older-version variants are always rendered inside
16+
a wrapper with data-active-variant="latest|older". CSS hides the inactive
17+
one. install-card-version-sync.js (cookie-mode pages only) flips the
18+
attribute on version-dropdown change so the swap happens without a page
19+
refresh; with JS disabled, the server-rendered variant stays and the
20+
dropdown's POST + redirect path handles the swap on the next request.
21+
1522
Variables:
1623
title (optional) — card heading text; defaults to "Install Boost and get started in your terminal."
1724
card_id (IMPORTANT) — unique identifier to scope IDs/names when multiple cards appear on the same page; defaults to slugified title, then "install"
@@ -26,6 +33,7 @@
2633
Dependencies:
2734
CSS: card.css, tab.css, install-card.css, code-block.css, forms.css (.dropdown)
2835
JS: install-card.js (keyboard nav + ARIA updates; optional — core works without it)
36+
install-card-version-sync.js (in-place version swap; optional — server roundtrip works without it)
2937

3038
Accessibility:
3139
- role="tablist" on tab container, role="tab" on labels, role="tabpanel" on panels
@@ -37,6 +45,25 @@
3745
{% endcomment %}
3846
{% with resolved_title=title|slugify|default:"install" %}
3947
{% with card_id=card_id|default:resolved_title %}
48+
<div class="install-card-wrapper"
49+
data-install-card-wrapper
50+
data-active-variant="{% if selected_version_is_non_latest %}older{% else %}latest{% endif %}">
51+
<section class="install-card install-card--older card"
52+
aria-labelledby="{{ card_id }}-older-heading">
53+
<div class="card__header">
54+
<h2 id="{{ card_id }}-older-heading" class="card__title install-card__title">
55+
{{ title|default:"Install Boost and get started in your terminal." }}
56+
</h2>
57+
</div>
58+
<div class="install-card__body px-large">
59+
<p class="install-card__older-message">
60+
You are viewing an older version. See the documentation for installation steps.
61+
</p>
62+
<div class="install-card__older-cta">
63+
{% include "v3/includes/_button.html" with label="See Documentation" url="/doc/user-guide/getting-started.html" style="teal" aria_label="See documentation for installation steps" only %}
64+
</div>
65+
</div>
66+
</section>
4067
{# Per-option visibility rules (generated from data, complements static rules in install-card.css) #}
4168
<style>
4269
{% for opt in install_card_pkg_managers %}
@@ -131,5 +158,6 @@ <h2 id="{{ card_id }}-heading" class="card__title install-card__title">
131158
</div>
132159
</div>
133160
</section>
161+
</div>
134162
{% endwith %}
135163
{% endwith %}

templates/v3/includes/header/_header_version_dropdown.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,17 @@
4242
{% if latest_href %}
4343
<a href="{{ latest_href }}"
4444
class="header__version-option"
45+
data-version-option
46+
data-is-latest="true"
4547
{% if not selected_version_is_non_latest %}aria-current="true"{% endif %}>Latest</a>
4648
{% endif %}
4749
{% for v in version_dropdown_options %}
4850
{% if v.href %}
4951
<a href="{{ v.href }}"
5052
class="header__version-option"
53+
data-version-option
54+
data-is-latest="false"
55+
data-doc-url="{{ v.documentation_url }}"
5156
{% if selected_version_is_non_latest and selected_version and v.slug == selected_version.slug %}aria-current="true"{% endif %}>
5257
{{ v.display_name }}
5358
</a>
@@ -62,12 +67,17 @@
6267
name="version"
6368
value="latest"
6469
class="header__version-option"
70+
data-version-option
71+
data-is-latest="true"
6572
{% if not selected_version_is_non_latest %}aria-current="true"{% endif %}>Latest</button>
6673
{% for v in version_dropdown_options %}
6774
<button type="submit"
6875
name="version"
6976
value="{{ v.slug }}"
7077
class="header__version-option"
78+
data-version-option
79+
data-is-latest="false"
80+
data-doc-url="{{ v.documentation_url }}"
7181
{% if selected_version_is_non_latest and selected_version and v.slug == selected_version.slug %}aria-current="true"{% endif %}>
7282
{{ v.display_name }}
7383
</button>

0 commit comments

Comments
 (0)