diff --git a/assets/js/tabs.js b/assets/js/tabs.js index b8d7937..12a7d64 100644 --- a/assets/js/tabs.js +++ b/assets/js/tabs.js @@ -1,20 +1,51 @@ -document.querySelectorAll('.hextra-tabs-toggle').forEach(function (button) { - button.addEventListener('click', function (e) { - // set parent tabs to unselected - const tabs = Array.from(e.target.parentElement.querySelectorAll('.hextra-tabs-toggle')); - tabs.map(tab => tab.dataset.state = ''); - - // set current tab to selected - e.target.dataset.state = 'selected'; - - // set all panels to unselected - const panelsContainer = e.target.parentElement.parentElement.nextElementSibling; - Array.from(panelsContainer.children).forEach(function (panel) { - panel.dataset.state = ''; +(function () { + function updateGroup(container, index) { + const tabs = Array.from(container.querySelectorAll('.hextra-tabs-toggle')); + tabs.forEach((tab, i) => { + tab.dataset.state = i === index ? 'selected' : ''; + if (i === index) { + tab.setAttribute('aria-selected', 'true'); + tab.tabIndex = 0; + } else { + tab.removeAttribute('aria-selected'); + tab.removeAttribute('tabindex'); + } }); + const panelsContainer = container.parentElement.nextElementSibling; + if (!panelsContainer) return; + 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 panel = panelsContainer.querySelector(`#${panelId}`); - panel.dataset.state = 'selected'; + const groups = document.querySelectorAll('[data-tab-group]'); + + groups.forEach((group) => { + const key = encodeURIComponent(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 = encodeURIComponent(container.dataset.tabGroup); + document + .querySelectorAll('[data-tab-group="' + container.dataset.tabGroup + '"]') + .forEach((grp) => updateGroup(grp, index)); + if (key) { + localStorage.setItem('hextra-tab-' + key, index.toString()); + } + }); + }); +})(); diff --git a/exampleSite/content/docs/guide/shortcodes/tabs.md b/exampleSite/content/docs/guide/shortcodes/tabs.md index c63547a..1d37cc5 100644 --- a/exampleSite/content/docs/guide/shortcodes/tabs.md +++ b/exampleSite/content/docs/guide/shortcodes/tabs.md @@ -5,11 +5,11 @@ next: /docs/guide/deploy-site ## Example -{{< tabs items="JSON,YAML,TOML" >}} +{{< tabs items="macOS,Linux,Windows" >}} -{{< tab >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}} -{{< tab >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}} -{{< tab >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}} + {{< tab >}}**macOS**: A desktop operating system by Apple.{{< /tab >}} + {{< tab >}}**Linux**: An open-source operating system.{{< /tab >}} + {{< tab >}}**Windows**: A desktop operating system by Microsoft.{{< /tab >}} {{< /tabs >}} @@ -91,3 +91,35 @@ Markdown syntax including code block is also supported: {{< /tab >}} {{< /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 +{{}} + + {{}}A content{{}} + {{}}B content{{}} + +{{}} + +{{}} + + {{}}Second A content{{}} + {{}}Second B content{{}} + +{{}} +``` diff --git a/exampleSite/hugo.yaml b/exampleSite/hugo.yaml index 453dc90..25a3d5b 100644 --- a/exampleSite/hugo.yaml +++ b/exampleSite/hugo.yaml @@ -132,10 +132,6 @@ params: # link: / width: wide - page: - # full (100%), wide (90rem), normal (80rem) - width: normal - theme: # light | dark | system default: system @@ -187,6 +183,12 @@ params: # hover | always display: hover + page: + # full (100%), wide (90rem), normal (80rem) + width: normal + # tabs: + # sync: true + comments: enable: false type: giscus diff --git a/layouts/_shortcodes/tabs.html b/layouts/_shortcodes/tabs.html index b068790..22992be 100644 --- a/layouts/_shortcodes/tabs.html +++ b/layouts/_shortcodes/tabs.html @@ -1,12 +1,24 @@ {{- $items := split (.Get "items") "," -}} {{- $defaultIndex := int ((.Get "defaultIndex") | default "0") -}} +{{- $enableSync := site.Params.page.tabs.sync | default false -}} + +{{- if not (.Get "items") -}} + {{ errorf "tabs shortcode: 'items' parameter is required" }} +{{- end -}} + {{- if not $items -}} - {{ errorf "no items provided" }} + {{ errorf "tabs shortcode: 'items' parameter cannot be empty" }} +{{- end -}} + +{{- range $items -}} + {{- if eq (trim . " ") "" -}} + {{ errorf "tabs shortcode: empty item found in 'items' parameter" }} + {{- end -}} {{- end -}}
-
+
{{- range $i, $item := $items -}}