Compare commits

...

3 Commits

Author SHA1 Message Date
Xin
54032eb238 Move tabs sync setting under page params 2025-06-08 11:51:57 +01:00
Xin
cf4d334da3 feat(tabs): add optional synchronization 2025-06-08 11:51:20 +01:00
Xin
43c930f1ac Sync tabs across groups 2025-06-04 00:52:20 +01:00
4 changed files with 83 additions and 22 deletions

View File

@ -1,20 +1,50 @@
document.querySelectorAll('.hextra-tabs-toggle').forEach(function (button) { (function () {
button.addEventListener('click', function (e) { function updateGroup(container, index) {
// set parent tabs to unselected const tabs = Array.from(container.querySelectorAll('.hextra-tabs-toggle'));
const tabs = Array.from(e.target.parentElement.querySelectorAll('.hextra-tabs-toggle')); tabs.forEach((tab, i) => {
tabs.map(tab => tab.dataset.state = ''); tab.dataset.state = i === index ? 'selected' : '';
if (i === index) {
// set current tab to selected tab.setAttribute('aria-selected', 'true');
e.target.dataset.state = 'selected'; tab.tabIndex = 0;
} else {
// set all panels to unselected tab.removeAttribute('aria-selected');
const panelsContainer = e.target.parentElement.parentElement.nextElementSibling; tab.removeAttribute('tabindex');
Array.from(panelsContainer.children).forEach(function (panel) { }
panel.dataset.state = '';
}); });
const panelsContainer = container.parentElement.nextElementSibling;
Array.from(panelsContainer.children).forEach((panel, i) => {
panel.dataset.state = i === index ? 'selected' : '';
if (i === index) {
panel.tabIndex = 0;
} else {
panel.removeAttribute('tabindex');
}
});
}
const panelId = e.target.getAttribute('aria-controls'); const groups = document.querySelectorAll('[data-tab-group]');
const panel = panelsContainer.querySelector(`#${panelId}`);
panel.dataset.state = 'selected'; groups.forEach((group) => {
const key = group.dataset.tabGroup;
const saved = localStorage.getItem('hextra-tab-' + key);
if (saved !== null) {
updateGroup(group, parseInt(saved, 10));
}
}); });
});
document.querySelectorAll('.hextra-tabs-toggle').forEach((button) => {
button.addEventListener('click', function (e) {
const container = e.target.parentElement;
const index = Array.from(container.querySelectorAll('.hextra-tabs-toggle')).indexOf(
e.target
);
const key = container.dataset.tabGroup;
document
.querySelectorAll('[data-tab-group="' + key + '"]')
.forEach((grp) => updateGroup(grp, index));
if (key) {
localStorage.setItem('hextra-tab-' + key, index.toString());
}
});
});
})();

View File

@ -13,6 +13,33 @@ next: /docs/guide/deploy-site
{{< /tabs >}} {{< /tabs >}}
### Sync Tabs
Tabs with the same list of `items` can be synchronized. When enabled, selecting a tab updates all other tabs with the same `items` and remembers the selection across pages.
Enable globally in your `hugo.yaml` under the `page` section:
```yaml {filename="hugo.yaml"}
params:
page:
tabs:
sync: true
```
With this enabled the following two tab blocks will always display the same selected item:
```markdown
{{</* tabs items="A,B" */>}}
{{</* tab */>}}A content{{</* /tab */>}}
{{</* tab */>}}B content{{</* /tab */>}}
{{</* /tabs */>}}
{{</* tabs items="A,B" */>}}
{{</* tab */>}}Second A content{{</* /tab */>}}
{{</* tab */>}}Second B content{{</* /tab */>}}
{{</* /tabs */>}}
```
## Usage ## Usage
### Default ### Default

View File

@ -132,10 +132,6 @@ params:
# link: / # link: /
width: wide width: wide
page:
# full (100%), wide (90rem), normal (80rem)
width: normal
theme: theme:
# light | dark | system # light | dark | system
default: system default: system
@ -184,6 +180,12 @@ params:
# hover | always # hover | always
display: hover display: hover
page:
# full (100%), wide (90rem), normal (80rem)
width: normal
tabs:
sync: true
comments: comments:
enable: false enable: false
type: giscus type: giscus

View File

@ -1,12 +1,14 @@
{{- $items := split (.Get "items") "," -}} {{- $items := split (.Get "items") "," -}}
{{- $defaultIndex := int ((.Get "defaultIndex") | default "0") -}} {{- $defaultIndex := int ((.Get "defaultIndex") | default "0") -}}
{{- $enableSync := site.Params.page.tabs.sync | default false -}}
{{- if not $items -}} {{- if not $items -}}
{{ errorf "no items provided" }} {{ errorf "no items provided" }}
{{- end -}} {{- end -}}
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain"> <div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
<div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800"> <div class="hx:mt-4 hx:flex hx:w-max hx:min-w-full hx:border-b hx:border-gray-200 hx:pb-px hx:dark:border-neutral-800"{{ if $enableSync }} data-tab-group="{{ delimit $items "," }}"{{ end }}>
{{- range $i, $item := $items -}} {{- range $i, $item := $items -}}
<button <button
class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white" class="hextra-tabs-toggle hx:cursor-pointer hx:data-[state=selected]:border-primary-500 hx:data-[state=selected]:text-primary-600 hx:data-[state=selected]:dark:border-primary-500 hx:data-[state=selected]:dark:text-primary-600 hx:mr-2 hx:rounded-t hx:p-2 hx:font-medium hx:leading-5 hx:transition-colors hx:-mb-0.5 hx:select-none hx:border-b-2 hx:border-transparent hx:text-gray-600 hx:hover:border-gray-200 hx:hover:text-black hx:dark:text-gray-200 hx:dark:hover:border-neutral-800 hx:dark:hover:text-white"