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
1 change: 1 addition & 0 deletions _config_theme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ site-css:
site-js:
- href: "https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2026.812.190302/dist/crowdin.js"
- "/assets/js/crowdin-init.js"
- "/assets/js/tabs.js"
theme-switcher: true
theme-switcher-style: 'button'
gtag: "G-SSW90X5YZX"
Expand Down
16 changes: 9 additions & 7 deletions _includes/tabs.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
<div class="tabs">
<div class="nav nav-underline" role="tablist">
{% capture tabs_id %}tabs-{% increment tabs_counter %}{% endcapture %}
<div class="tabs" id="{{ tabs_id }}">
<div class="nav" role="tablist">
{% for tab in include.tabs %}
<div class="nav-item">
<button
class="nav-link {% if forloop.first %}active{% endif %}"
id="{{ tab.name | slugify }}{% if include.index and include.index >= 0 %}-{{ include.index }}{% endif %}-tab"
id="{{ tabs_id }}-tab-{{ forloop.index0 }}"
data-tab-name="{{ tab.name | escape }}"
data-bs-toggle="tab"
data-bs-target="#{{ tab.name | slugify }}{% if include.index and include.index >= 0 %}-{{ include.index }}{% endif %}-pane"
data-bs-target="#{{ tabs_id }}-pane-{{ forloop.index0 }}"
type="button"
role="tab"
aria-controls="{{ tab.name | slugify }}{% if include.index and include.index >= 0 %}-{{ include.index }}{% endif %}-pane"
aria-controls="{{ tabs_id }}-pane-{{ forloop.index0 }}"
aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"
>{{ tab.name }}</button>
</div>
Expand All @@ -19,9 +21,9 @@
{% for tab in include.tabs %}
<div
class="tab-pane fade {% if forloop.first %}show active{% endif %}"
id="{{ tab.name | slugify }}{% if include.index and include.index >= 0 %}-{{ include.index }}{% endif %}-pane"
id="{{ tabs_id }}-pane-{{ forloop.index0 }}"
role="tabpanel"
aria-labelledby="{{ tab.name | slugify }}{% if include.index and include.index >= 0 %}-{{ include.index }}{% endif %}-tab"
aria-labelledby="{{ tabs_id }}-tab-{{ forloop.index0 }}"
>
{% if include.index and include.index >= 0 %}
{{ tab.content[include.index] | markdownify }}
Expand Down
1 change: 1 addition & 0 deletions _posts/guides/1970-01-01-blog-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ You can use indexed tabs with the following syntax:

The tabs object must have a `name` and `content` field.
The `content` field should be an array of strings when using indexed tabs.
Groups with the same tab names in the same order are linked, so selecting a tab in one group selects it in all of them.

### Examples

Expand Down
48 changes: 39 additions & 9 deletions _sass/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
--admonition-text: #212529;
--circular-progress-border: #dee2e6;
--circular-progress-bg: #e9ecef;
--tabs-hover-bg: rgba(40, 169, 230, 0.1);
--tabs-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.08);

/* Light theme commit activity colors */
--commit-activity-empty: rgba(0, 0, 0, 0.08);
Expand All @@ -40,6 +42,8 @@
--admonition-text: #18191a;
--circular-progress-border: #2b3035;
--circular-progress-bg: #2b3035;
--tabs-hover-bg: rgba(40, 169, 230, 0.16);
--tabs-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.24);

/* Dark theme commit activity colors */
--commit-activity-empty: rgba(255, 255, 255, 0.1);
Expand Down Expand Up @@ -162,24 +166,50 @@ img.invert {
/* CSS for tabs */
.tabs {
background-color: var(--footer-col);
padding: 1rem;
border: 1px solid var(--navbar-border-col);
border-radius: 0.75rem;
box-shadow: var(--tabs-shadow);
margin: 1.5rem 0;
overflow: hidden;
}
.tabs > .nav {
border-bottom: 1px solid var(--navbar-border-col);
flex-wrap: nowrap;
gap: 0.25rem;
overflow-x: auto;
padding: 0.5rem 0.5rem 0;
}
.tabs .nav-link {
border: 1px solid transparent;
border-bottom: 0;
border-radius: 0.5rem 0.5rem 0 0;
color: var(--link-col);
font-weight: 600;
padding: 0.625rem 1rem;
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
white-space: nowrap;
}
.tabs .nav-link:hover {
background-color: var(--tabs-hover-bg);
color: var(--hover-col);
}
.tabs .nav-link.active {
.tabs .nav-link:focus-visible {
outline: 0.1875rem solid var(--link-col);
outline-offset: -0.1875rem;
}
.tabs .nav-link.active,
.tabs .nav-link.active:hover {
background-color: var(--page-col);
border-color: var(--navbar-border-col);
color: var(--text-col);
border-color: var(--text-col);
margin-bottom: -1px;
}
.tab-content {
background-color: var(--navbar-col);
padding: 1rem;
margin-top: 1rem;
border: 1px solid var(--page-col);
border-radius: 0.5rem;
.tabs > .tab-content {
background-color: var(--page-col);
padding: 1.25rem;
}
.tabs > .tab-content > .tab-pane > :last-child {
margin-bottom: 0;
}

/* tables */
Expand Down
57 changes: 57 additions & 0 deletions assets/js/tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Keep matching groups of tabs in sync.
*/
(function() {
'use strict';

let synchronizing = false;

function getTabButtons(group) {
const tabList = Array.from(group.children).find((child) => child.getAttribute('role') === 'tablist');

if (!tabList) {
return [];
}

return Array.from(tabList.querySelectorAll('[role="tab"][data-tab-name]'));
}

function haveMatchingNames(firstButtons, secondButtons) {
return firstButtons.length === secondButtons.length && firstButtons.every(
(button, index) => button.dataset.tabName === secondButtons[index].dataset.tabName
);
}

function synchronizeTabs(event) {
const selectedTab = event.target;

if (synchronizing || !selectedTab.matches('.tabs [role="tab"][data-tab-name]')) {
return;
}

const selectedGroup = selectedTab.closest('.tabs');
const selectedButtons = getTabButtons(selectedGroup);
const selectedIndex = selectedButtons.indexOf(selectedTab);

synchronizing = true;

try {
document.querySelectorAll('.tabs').forEach((group) => {
const groupButtons = getTabButtons(group);
const matchingTab = groupButtons[selectedIndex];

if (
group !== selectedGroup &&
haveMatchingNames(selectedButtons, groupButtons) &&
!matchingTab.classList.contains('active')
) {
bootstrap.Tab.getOrCreateInstance(matchingTab).show();
}
});
} finally {
synchronizing = false;
}
}

document.addEventListener('shown.bs.tab', synchronizeTabs);
})();