From ccb63d60f1961739563e8e043c8bef433b3eee49 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 11 Sep 2025 00:54:27 +0200 Subject: [PATCH] feat(tabs): revamp tabs (#815) --- docs/content/docs/guide/shortcodes/tabs.md | 81 ++++++++++++---------- layouts/_partials/shortcodes/tabs.html | 69 ++++++++++++++++++ layouts/_shortcodes/tab.html | 30 ++++---- layouts/_shortcodes/tabs.html | 62 +++++++---------- 4 files changed, 158 insertions(+), 84 deletions(-) create mode 100644 layouts/_partials/shortcodes/tabs.html diff --git a/docs/content/docs/guide/shortcodes/tabs.md b/docs/content/docs/guide/shortcodes/tabs.md index 1d37cc5..d8a9be4 100644 --- a/docs/content/docs/guide/shortcodes/tabs.md +++ b/docs/content/docs/guide/shortcodes/tabs.md @@ -5,12 +5,10 @@ next: /docs/guide/deploy-site ## Example -{{< tabs items="macOS,Linux,Windows" >}} - - {{< 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 >}} + {{< tab name="JSON" >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}} + {{< tab name="YAML" >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}} + {{< tab name="TOML" >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}} {{< /tabs >}} ## Usage @@ -18,37 +16,35 @@ next: /docs/guide/deploy-site ### Default ``` -{{}} +{{}} - {{}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{}} - {{}}**YAML**: YAML is a human-readable data serialization language.{{}} - {{}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{}} + {{}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{}} + {{}}**YAML**: YAML is a human-readable data serialization language.{{}} + {{}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{}} {{}} ``` -### Specify Selected Index +### Specify Selected Tab -Use `defaultIndex` property to specify the selected tab. The index starts from 0. +Use `selected` property to specify the selected tab. ``` -{{}} +{{}} - {{}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{}} - {{}}**YAML**: YAML is a human-readable data serialization language.{{}} - {{}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{}} + {{}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{}} + {{}}**YAML**: YAML is a human-readable data serialization language.{{}} + {{}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{}} {{}} ``` The `YAML` tab will be selected by default. -{{< tabs items="JSON,YAML,TOML" defaultIndex="1" >}} - -{{< 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 >}} - +{{< tabs >}} + {{< tab name="JSON" >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}} + {{< tab name="YAML" selected=true >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}} + {{< tab name="TOML" >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}} {{< /tabs >}} @@ -57,9 +53,9 @@ The `YAML` tab will be selected by default. Markdown syntax including code block is also supported: ```` -{{}} +{{}} - {{}} + {{}} ```json { "hello": "world" } ``` @@ -70,21 +66,21 @@ Markdown syntax including code block is also supported: {{}} ```` -{{< tabs items="JSON,YAML,TOML" >}} +{{< tabs >}} - {{< tab >}} + {{< tab name="JSON" >}} ```json { "hello": "world" } ``` {{< /tab >}} - {{< tab >}} + {{< tab name="YAML" >}} ```yaml hello: world ``` {{< /tab >}} - {{< tab >}} + {{< tab name="TOML" >}} ```toml hello = "world" ``` @@ -97,7 +93,7 @@ Markdown syntax including code block is also supported: 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: +Enable/disable globally in your `hugo.yaml` under the `page` section: ```yaml {filename="hugo.yaml"} params: @@ -106,20 +102,33 @@ params: sync: true ``` -With this enabled the following two tab blocks will always display the same selected item: +Enable/disable per page inside the front matter: + +```yaml {filename="my_page.md"} +--- +title: My page +params: + tabs: + sync: true +--- + +Example content. +``` + +With this enabled, the following two tab blocks will always display the same selected item: ```markdown -{{}} +{{}} - {{}}A content{{}} - {{}}B content{{}} + {{}}A content{{}} + {{}}B content{{}} {{}} -{{}} +{{}} - {{}}Second A content{{}} - {{}}Second B content{{}} + {{}}Second A content{{}} + {{}}Second B content{{}} {{}} ``` diff --git a/layouts/_partials/shortcodes/tabs.html b/layouts/_partials/shortcodes/tabs.html new file mode 100644 index 0000000..a9e9dd7 --- /dev/null +++ b/layouts/_partials/shortcodes/tabs.html @@ -0,0 +1,69 @@ +{{- $tabsID := .id }} + +{{- /* +The `tabs` parameter is a list of dict with the following keys: + - `id`: (int) the ID of the tab (the Ordinal of the tab shortcode). + - `name`: (string) the name of the tab (the title). + - `content`: (string) the content of the tab. + - `selected`: (bool) whether the tab is selected. +*/ -}} +{{- $tabs := .tabs }} + +{{- if eq (len $tabs) 0 -}} + {{ errorf "tabs must have at least one tab" }} +{{- end -}} + +{{- $enableSync := .enableSync }} + +{{- /* Create group data for syncing and select the first tab if none is selected. */ -}} +{{- $selectedIndex := 0 -}} +{{ $dataTabGroup := slice -}} + +{{- range $i, $item := $tabs -}} + {{- $dataTabGroup = $dataTabGroup | append ($item.name) -}} + + {{- if $item.selected -}} + {{- $selectedIndex = $i -}} + {{- end -}} +{{- end -}} + +{{- /* Generate a unique ID for each tab group. */ -}} +{{- $globalID := printf "tabs-%02v" $tabsID -}} + +
+
+ {{- range $i, $item := $tabs -}} + + {{- end -}} +
+
+
+ {{- range $i, $item := $tabs -}} +
+ {{- $item.content | markdownify -}} +
+ {{- end -}} +
diff --git a/layouts/_shortcodes/tab.html b/layouts/_shortcodes/tab.html index f2f4233..4d46d6e 100644 --- a/layouts/_shortcodes/tab.html +++ b/layouts/_shortcodes/tab.html @@ -1,18 +1,24 @@ {{- /* Create a tab. -@example {{< tab >}}content{{< /tab >}} +@param {string} name The name of the tab. +@param {string} selected Whether the tab is selected. + +@example {{< tab name="Foo" selected=true >}}content{{< /tab >}} */ -}} -{{- $defaultIndex := int ((.Parent.Get "defaultIndex") | default "0") -}} +{{- $name := .Get "name" | default (printf "Tab %d" .Ordinal) -}} -
- {{- .InnerDeindent | markdownify -}} -
-{{- /* Drop trailing newlines */ -}} +{{- $selected := .Get "selected" -}} +{{- if .Parent.Get "defaultIndex" -}} + {{- $selected = eq .Ordinal (int (.Parent.Get "defaultIndex")) -}} +{{- end -}} + +{{- $tabs := .Parent.Store.Get "tabs" | default slice -}} +{{ .Parent.Store.Set "tabs" ($tabs | append (dict + "id" .Ordinal + "name" $name + "content" .InnerDeindent + "selected" $selected + )) +-}} diff --git a/layouts/_shortcodes/tabs.html b/layouts/_shortcodes/tabs.html index 6e129f4..efdf5a6 100644 --- a/layouts/_shortcodes/tabs.html +++ b/layouts/_shortcodes/tabs.html @@ -1,49 +1,39 @@ {{- /* Create a tabbed interface with the given items. -@param {string} items The items to display in the tabs. -@param {string} defaultIndex The index of the default tab. - -@example {{< tabs items="JSON,YAML,TOML" >}}{{< /tabs >}} +@example {{< tabs >}}...{{< /tabs >}} */ -}} -{{- $items := split (.Get "items") "," -}} -{{- $defaultIndex := int ((.Get "defaultIndex") | default "0") -}} +{{- /* Unused, but required for the shortcode to work. */ -}} +{{- .Inner -}} -{{- $enableSync := site.Params.page.tabs.sync | default false -}} - -{{- if not (.Get "items") -}} - {{ errorf "tabs shortcode: 'items' parameter is required" }} +{{- /* Enable syncing of tabs across the page. */ -}} +{{- $enableSync := false -}} +{{- if or (eq .Page.Params.tabs.sync false) (eq .Page.Params.tabs.sync true) -}} + {{- $enableSync = .Page.Params.tabs.sync -}} +{{- else -}} + {{- $enableSync = site.Params.page.tabs.sync | default false -}} {{- end -}} -{{- if not $items -}} - {{ errorf "tabs shortcode: 'items' parameter cannot be empty" }} +{{- $tabs := ($.Store.Get "tabs") | default slice -}} + +{{- /* Compatibility with previous parameter "items". */ -}} +{{- if .Get "defaultIndex" -}} + {{- warnf "The 'defaultIndex' parameter of the 'tabs' shortcode is deprecated. Please use 'selected' on 'tab' instead." -}} {{- end -}} -{{- range $items -}} - {{- if eq (trim . " ") "" -}} - {{ errorf "tabs shortcode: empty item found in 'items' parameter" }} +{{- if .Get "items" -}} + {{- warnf "The 'items' parameter of the 'tabs' shortcode is deprecated. Please use 'name' on 'tab' instead." -}} + + {{- $items := split (.Get "items") "," -}} + + {{- $temp := slice -}} + {{- range $i, $item := $items -}} + {{- $tab := index $tabs $i -}} + {{- $temp = $temp | append (merge $tab (dict "name" $item)) -}} {{- end -}} + + {{- $tabs = $temp -}} {{- end -}} -
-
- {{- range $i, $item := $items -}} - - {{- end -}} -
-
-
- {{- .Inner -}} -
-{{- /* Drop trailing newlines */ -}} +{{- partial "shortcodes/tabs" (dict "tabs" $tabs "enableSync" $enableSync "id" .Ordinal) -}}