feat : improve language picker component (#2040)

Signed-off-by: Shubham Oulkar <oulkarshubhu@gmail.com>
Co-authored-by: Sebastian Beltran <bjohansebas@gmail.com>
This commit is contained in:
shubham oulkar
2025-09-14 20:02:41 +05:30
committed by GitHub
parent 72ff6baa31
commit 00e199ad99
8 changed files with 74 additions and 177 deletions

View File

@@ -29,12 +29,12 @@ jobs:
run: |
PREVIEW_URL="https://deploy-preview-${{ github.event.pull_request.number }}--expressjscom-preview.netlify.app"
echo "PREVIEW_URL=$PREVIEW_URL" >> "$GITHUB_ENV"
MAX_RETRIES=12
MAX_RETRIES=10
DELAY=10
echo "Checking Netlify preview: $PREVIEW_URL"
for i in $(seq 1 $MAX_RETRIES); do
if curl -s --head --max-time 5 "$PREVIEW_URL" | grep "200 OK" > /dev/null; then
if curl -s -I "$PREVIEW_URL" | grep "HTTP/.* 200" > /dev/null; then
echo "✅ Preview is live!"
echo "skip_lighthouse=false" >> "$GITHUB_ENV"
exit 0

View File

@@ -1,6 +1,3 @@
# Home
home: Home
# Getting started
getting_started: Getting started
installing: Installing

View File

@@ -12,11 +12,6 @@
<nav id="navbar" aria-label="primary">
<input id="q" placeholder="🔎 search">
<ul id="navmenu">
<li>
<a href="/" id="home-menu" {% if page.menu=='home' %} class="active" {% endif %}>
{{ site.data[page.lang].menu.home }}
</a>
</li>
<li id="getting-started-menu" class="submenu">
<a href="/{{ page.lang }}/starter/installing.html" {% if page.menu=='starter' %} class="active" {% endif%}>
{{ site.data[page.lang].menu.getting_started }}
@@ -252,6 +247,5 @@
<span id="icon-sun">{% include icons/sun.svg %}</span>
</button>
{% include language-picker.html %}
{% include language-picker-mobile.html %}
</div>
</header>

View File

@@ -1,20 +0,0 @@
<div id="mobile-menu">
<div id="language-picker-button" class="header-btn">
{% include icons/i18n.svg %}
</div>
</div>
<div id="language-picker-menu">
<div id="navbar">
<ul id="navmenu">
{% assign url_parts = page.url | split: '/' %}
{% assign url_remainder = url_parts | slice: 2, url_parts.size | join: '/' %}
{% for lang in site.data.languages %}
<li class="submenu">
<a href="/{{ lang.code }}/{{ url_remainder }}">{{ lang.name }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>

View File

@@ -1,22 +1,21 @@
<div class="desktop-lang-switcher">
{% assign url_parts = page.url | split: '/' %}
{% assign url_remainder = url_parts | slice: 2, url_parts.size | join: '/' %}
{% assign current_lang = page.lang %}
<button class="lang-btn" type="button" aria-haspopup="menu" aria-label="Change language">
<span id="current-lang"></span>
</button>
<ul class="lang-list submenu-content" aria-labelledby="current-lang" >
{% for lang in site.data.languages %}
<li>
<a href="/{{ lang.code }}/{{ url_remainder }}">
{% if lang.code == current_lang %}
<div class="desktop-lang-switcher">
<button id="langBtn" class="lang-btn" type="button" aria-expanded="false" aria-haspopup="listbox" aria-label="Change language">
{% include icons/i18n.svg %}
</button>
<ul id="langList" class="lang-list" role="listbox">
{% for lang in site.data.languages %}
<li>
<a href="/{{ lang.code }}/{{ url_remainder }}">
{% if lang.code == current_lang %}
<strong>{{ lang.name }}</strong>
{% else %}
{% else %}
{{ lang.name }}
{% endif %}
</a>
</li>
{% endfor %}
</ul>
<div id="languageData" data-languages='{{ site.data.languages | jsonify }}' style="display:none;"></div>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
</div>

View File

@@ -802,42 +802,12 @@ button.lang-btn {
cursor: pointer;
color: var(--card-fg);
padding: 0.2rem;
font-size: inherit;
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
&::after {
content: "";
display: block;
width: 0.8em;
height: 0.5em;
background-color: var(--card-fg);
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
cursor: pointer;
pointer-events: none;
transition: transform 0.2s ease-in-out;
}
width: fit-content;
aspect-ratio: 1;
}
div.desktop-lang-switcher {
position: relative;
> button.lang-btn {
&:is(:focus, :hover) {
& + ul {
display: block;
opacity: 1;
visibility: visible;
}
}
}
/* enable lang list tabbing on keyboard focus */
&:focus-within .lang-list {
display: block;
opacity: 1;
visibility: visible;
}
> ul.lang-list {
display: none;
@@ -845,11 +815,14 @@ div.desktop-lang-switcher {
position: absolute;
list-style: none;
visibility: hidden;
&:is(:hover, :focus) {
display: block;
opacity: 1;
visibility: visible;
}
left: -75px;
z-index: 100;
background-color: var(--card-bg);
border: 1px solid;
border-radius: 10px;
padding: 0px;
min-width: max-content;
li a {
display: block;
padding: 5px 20px 5px 20px;
@@ -859,11 +832,22 @@ div.desktop-lang-switcher {
background: var(--hover-bg);
}
}
> li:first-child > a {
border-start-start-radius: 10px;
border-start-end-radius: 10px;
}
> li:last-child > a {
border-end-start-radius: 10px;
border-end-end-radius: 10px;
}
}
/* rotate arrow */
&:is(:hover,:focus-within) button.lang-btn::after {
transform: rotate(180deg);
> ul.lang-list.open {
display: block;
opacity: 1;
visibility: visible;
}
}
@@ -953,9 +937,6 @@ div.desktop-lang-switcher {
text-decoration: none;
}
#language-picker-menu {
display: none;
}
/* TOC side menu */
@@ -1320,26 +1301,6 @@ h2 a {
display: flex;
gap: 8px;
}
#language-picker-menu #navmenu>li:first-child {
display: flex;
}
#language-picker-menu #navmenu {
max-height: 70vh;
overflow-y: auto;
scrollbar-width: thin;
text-align: center;
}
#language-picker-menu {
display: block;
position: absolute;
top: 0;
right: 0;
width: 100%;
z-index: 1000;
}
}
/* TOC responsive */
@@ -1448,10 +1409,6 @@ h2 a {
header {
position: absolute;
}
#mobile-menu {
display:none;
}
}
/* For image callouts in writing-middleware.md */
@@ -1589,9 +1546,6 @@ h2 a {
margin-right: 0;
padding-right: 10px;
}
#blog-doc .blog-details + p > img {
margin-bottom: 15px;
}
@@ -1661,18 +1615,14 @@ blockquote {
}
@media all and (max-width: 1110px) {
.desktop-lang-switcher {
display: none;
.algolia-autocomplete {
display: none !important;
}
#mobile-menu {
display: block;
}
.algolia-autocomplete {
display: none !important;
}
#navbar {
padding: 0;
top: 1px;

View File

@@ -1,15 +1,5 @@
const languageElement = document.getElementById('languageData');
const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : [];
const langDisplay = document.getElementById('current-lang');
const i18nMsgBox = document.getElementById("i18n-notice-box");
// display current language in language picker component
if (langDisplay) {
const currentLanguage = window.location.pathname.split('/')[1];
const matchedLang = languagesData.find(lang => lang.code === currentLanguage);
langDisplay.textContent = matchedLang ? matchedLang.name : 'English';
}
// add/remove class 'scroll' on scroll by 5px
const scrollTarget = document.querySelector('.logo-container');
const scrollObserver = new IntersectionObserver(

View File

@@ -65,15 +65,35 @@ for (const el of itemsMenu) {
}
// Mobile Menu and Language Picker
const langBtn = document.getElementById("langBtn");
const langList = document.getElementById("langList");
const linkItemsMenu = document.querySelectorAll(".submenu > a");
const languageItems = document.querySelectorAll("#language-picker-menu > #navbar > #navmenu > .submenu > a");
const languagePickerMenu = document.querySelector("#language-picker-menu > #navbar > #navmenu");
const menu = document.querySelector("#navmenu");
const overlay = document.querySelector("#overlay");
const navButton = document.querySelector("#nav-button");
const languagePickerButton = document.querySelector("#language-picker-button");
function closeLangList() {
langList.classList.remove("open");
langBtn.setAttribute("aria-expanded", false);
}
function toggleLangList() {
const isOpen = langList.classList.toggle("open");
langBtn.setAttribute("aria-expanded", isOpen);
}
// toggle on button click
langBtn.addEventListener("click", (e) => {
e.stopPropagation();
toggleLangList();
});
// close on outside click except for lang btn
document.body.addEventListener("click", (e) => {
if (!langList.contains(e.target)) {
closeLangList();
}
});
for (const el of linkItemsMenu) {
el.addEventListener("click", (e) => {
@@ -85,56 +105,23 @@ for (const el of linkItemsMenu) {
e.preventDefault();
}
});
}
for (const el of languageItems) {
el.addEventListener("click", (e) => {
const href = el.getAttribute("href");
if (href && href !== "#") {
languagePickerMenu?.classList.remove("opens");
overlay?.classList.remove("blurs");
document.body.classList.remove("no-scroll");
window.location.href = href;
}
});
}
navButton?.addEventListener("click", () => {
const isLanguageMenuOpen = languagePickerMenu?.classList.contains("opens");
if (isLanguageMenuOpen) {
languagePickerMenu?.classList.remove("opens");
menu?.classList.toggle("opens");
} else {
menu?.classList.toggle("opens");
overlay?.classList.toggle("blurs");
document.body.classList.toggle("no-scroll");
}
});
languagePickerButton?.addEventListener("click", () => {
const isMenuOpen = menu?.classList.contains("opens");
if (isMenuOpen) {
menu?.classList.remove("opens");
languagePickerMenu?.classList.toggle("opens");
} else {
languagePickerMenu?.classList.toggle("opens");
overlay?.classList.toggle("blurs");
document.body.classList.toggle("no-scroll");
}
menu?.classList.toggle("opens");
overlay?.classList.toggle("blurs");
document.body.classList.toggle("no-scroll");
});
overlay?.addEventListener("click", () => {
if (menu?.classList.contains("opens")) {
menu.classList.remove("opens");
}
if (languagePickerMenu?.classList.contains("opens")) {
languagePickerMenu.classList.remove("opens");
}
overlay.classList.remove("blurs");
document.body.classList.remove("no-scroll");
// TODO : write helper function
const isOpen = langList.classList.toggle("open");
langBtn.setAttribute("aria-expanded", isOpen);
});
document