Skip to content

Commit ccb63d6

Browse files
authored
feat(tabs): revamp tabs (#815)
1 parent 3bc454b commit ccb63d6

File tree

4 files changed

+158
-84
lines changed

4 files changed

+158
-84
lines changed

docs/content/docs/guide/shortcodes/tabs.md

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,46 @@ next: /docs/guide/deploy-site
55

66
## Example
77

8-
{{< tabs items="macOS,Linux,Windows" >}}
9-
10-
{{< tab >}}**macOS**: A desktop operating system by Apple.{{< /tab >}}
11-
{{< tab >}}**Linux**: An open-source operating system.{{< /tab >}}
12-
{{< tab >}}**Windows**: A desktop operating system by Microsoft.{{< /tab >}}
13-
8+
{{< tabs >}}
9+
{{< tab name="JSON" >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}}
10+
{{< tab name="YAML" >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}}
11+
{{< tab name="TOML" >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
1412
{{< /tabs >}}
1513

1614
## Usage
1715

1816
### Default
1917

2018
```
21-
{{</* tabs items="JSON,YAML,TOML" */>}}
19+
{{</* tabs */>}}
2220
23-
{{</* tab */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
24-
{{</* tab */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
25-
{{</* tab */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
21+
{{</* tab name="JSON" */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
22+
{{</* tab name="YAML" */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
23+
{{</* tab name="TOML" */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
2624
2725
{{</* /tabs */>}}
2826
```
2927

30-
### Specify Selected Index
28+
### Specify Selected Tab
3129

32-
Use `defaultIndex` property to specify the selected tab. The index starts from 0.
30+
Use `selected` property to specify the selected tab.
3331

3432
```
35-
{{</* tabs items="JSON,YAML,TOML" defaultIndex="1" */>}}
33+
{{</* tabs */>}}
3634
37-
{{</* tab */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
38-
{{</* tab */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
39-
{{</* tab */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
35+
{{</* tab name="JSON" */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
36+
{{</* tab name="YAML" selected=true */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
37+
{{</* tab name="TOML" */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
4038
4139
{{</* /tabs */>}}
4240
```
4341

4442
The `YAML` tab will be selected by default.
4543

46-
{{< tabs items="JSON,YAML,TOML" defaultIndex="1" >}}
47-
48-
{{< tab >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}}
49-
{{< tab >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}}
50-
{{< tab >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
51-
44+
{{< tabs >}}
45+
{{< tab name="JSON" >}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{< /tab >}}
46+
{{< tab name="YAML" selected=true >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}}
47+
{{< tab name="TOML" >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
5248
{{< /tabs >}}
5349

5450

@@ -57,9 +53,9 @@ The `YAML` tab will be selected by default.
5753
Markdown syntax including code block is also supported:
5854

5955
````
60-
{{</* tabs items="JSON,YAML,TOML" */>}}
56+
{{</* tabs */>}}
6157
62-
{{</* tab */>}}
58+
{{</* tab name="JSON" */>}}
6359
```json
6460
{ "hello": "world" }
6561
```
@@ -70,21 +66,21 @@ Markdown syntax including code block is also supported:
7066
{{</* /tabs */>}}
7167
````
7268

73-
{{< tabs items="JSON,YAML,TOML" >}}
69+
{{< tabs >}}
7470

75-
{{< tab >}}
71+
{{< tab name="JSON" >}}
7672
```json
7773
{ "hello": "world" }
7874
```
7975
{{< /tab >}}
8076

81-
{{< tab >}}
77+
{{< tab name="YAML" >}}
8278
```yaml
8379
hello: world
8480
```
8581
{{< /tab >}}
8682
87-
{{< tab >}}
83+
{{< tab name="TOML" >}}
8884
```toml
8985
hello = "world"
9086
```
@@ -97,7 +93,7 @@ Markdown syntax including code block is also supported:
9793

9894
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.
9995

100-
Enable globally in your `hugo.yaml` under the `page` section:
96+
Enable/disable globally in your `hugo.yaml` under the `page` section:
10197

10298
```yaml {filename="hugo.yaml"}
10399
params:
@@ -106,20 +102,33 @@ params:
106102
sync: true
107103
```
108104
109-
With this enabled the following two tab blocks will always display the same selected item:
105+
Enable/disable per page inside the front matter:
106+
107+
```yaml {filename="my_page.md"}
108+
---
109+
title: My page
110+
params:
111+
tabs:
112+
sync: true
113+
---
114+
115+
Example content.
116+
```
117+
118+
With this enabled, the following two tab blocks will always display the same selected item:
110119

111120
```markdown
112-
{{</* tabs items="A,B" */>}}
121+
{{</* tabs */>}}
113122

114-
{{</* tab */>}}A content{{</* /tab */>}}
115-
{{</* tab */>}}B content{{</* /tab */>}}
123+
{{</* tab name="A" */>}}A content{{</* /tab */>}}
124+
{{</* tab name="B" */>}}B content{{</* /tab */>}}
116125

117126
{{</* /tabs */>}}
118127

119-
{{</* tabs items="A,B" */>}}
128+
{{</* tabs */>}}
120129

121-
{{</* tab */>}}Second A content{{</* /tab */>}}
122-
{{</* tab */>}}Second B content{{</* /tab */>}}
130+
{{</* tab name="A" */>}}Second A content{{</* /tab */>}}
131+
{{</* tab name="B" */>}}Second B content{{</* /tab */>}}
123132

124133
{{</* /tabs */>}}
125134
```
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{{- $tabsID := .id }}
2+
3+
{{- /*
4+
The `tabs` parameter is a list of dict with the following keys:
5+
- `id`: (int) the ID of the tab (the Ordinal of the tab shortcode).
6+
- `name`: (string) the name of the tab (the title).
7+
- `content`: (string) the content of the tab.
8+
- `selected`: (bool) whether the tab is selected.
9+
*/ -}}
10+
{{- $tabs := .tabs }}
11+
12+
{{- if eq (len $tabs) 0 -}}
13+
{{ errorf "tabs must have at least one tab" }}
14+
{{- end -}}
15+
16+
{{- $enableSync := .enableSync }}
17+
18+
{{- /* Create group data for syncing and select the first tab if none is selected. */ -}}
19+
{{- $selectedIndex := 0 -}}
20+
{{ $dataTabGroup := slice -}}
21+
22+
{{- range $i, $item := $tabs -}}
23+
{{- $dataTabGroup = $dataTabGroup | append ($item.name) -}}
24+
25+
{{- if $item.selected -}}
26+
{{- $selectedIndex = $i -}}
27+
{{- end -}}
28+
{{- end -}}
29+
30+
{{- /* Generate a unique ID for each tab group. */ -}}
31+
{{- $globalID := printf "tabs-%02v" $tabsID -}}
32+
33+
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
34+
<div
35+
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"
36+
{{ if $enableSync }} data-tab-group="{{ delimit $dataTabGroup `,` }}"{{ end }}
37+
>
38+
{{- range $i, $item := $tabs -}}
39+
<button
40+
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"
41+
role="tab"
42+
type="button"
43+
aria-controls="tabs-panel-{{ $globalID }}-{{ $item.id }}"
44+
{{- if eq $i $selectedIndex -}}
45+
aria-selected="true"
46+
tabindex="0"
47+
data-state="selected"
48+
{{- end }}
49+
>
50+
{{- $item.name -}}
51+
</button>
52+
{{- end -}}
53+
</div>
54+
</div>
55+
<div>
56+
{{- range $i, $item := $tabs -}}
57+
<div
58+
class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block"
59+
id="tabs-panel-{{ $globalID }}-{{ $item.id }}"
60+
role="tabpanel"
61+
{{- if eq $i $selectedIndex -}}
62+
tabindex="0"
63+
data-state="selected"
64+
{{ end -}}
65+
>
66+
{{- $item.content | markdownify -}}
67+
</div>
68+
{{- end -}}
69+
</div>

layouts/_shortcodes/tab.html

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
{{- /*
22
Create a tab.
33

4-
@example {{< tab >}}content{{< /tab >}}
4+
@param {string} name The name of the tab.
5+
@param {string} selected Whether the tab is selected.
6+
7+
@example {{< tab name="Foo" selected=true >}}content{{< /tab >}}
58
*/ -}}
69

7-
{{- $defaultIndex := int ((.Parent.Get "defaultIndex") | default "0") -}}
10+
{{- $name := .Get "name" | default (printf "Tab %d" .Ordinal) -}}
11+
12+
{{- $selected := .Get "selected" -}}
13+
{{- if .Parent.Get "defaultIndex" -}}
14+
{{- $selected = eq .Ordinal (int (.Parent.Get "defaultIndex")) -}}
15+
{{- end -}}
816

9-
<div
10-
class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block"
11-
id="tabs-panel-{{ .Ordinal }}"
12-
role="tabpanel"
13-
{{- if eq .Ordinal $defaultIndex }} tabindex="0" {{ end -}}
14-
{{- if eq .Ordinal $defaultIndex }} data-state="selected" {{ end -}}
15-
>
16-
{{- .InnerDeindent | markdownify -}}
17-
</div>
18-
{{- /* Drop trailing newlines */ -}}
17+
{{- $tabs := .Parent.Store.Get "tabs" | default slice -}}
18+
{{ .Parent.Store.Set "tabs" ($tabs | append (dict
19+
"id" .Ordinal
20+
"name" $name
21+
"content" .InnerDeindent
22+
"selected" $selected
23+
))
24+
-}}

layouts/_shortcodes/tabs.html

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,39 @@
11
{{- /*
22
Create a tabbed interface with the given items.
33

4-
@param {string} items The items to display in the tabs.
5-
@param {string} defaultIndex The index of the default tab.
6-
7-
@example {{< tabs items="JSON,YAML,TOML" >}}{{< /tabs >}}
4+
@example {{< tabs >}}...{{< /tabs >}}
85
*/ -}}
96

10-
{{- $items := split (.Get "items") "," -}}
11-
{{- $defaultIndex := int ((.Get "defaultIndex") | default "0") -}}
12-
13-
{{- $enableSync := site.Params.page.tabs.sync | default false -}}
7+
{{- /* Unused, but required for the shortcode to work. */ -}}
8+
{{- .Inner -}}
149

15-
{{- if not (.Get "items") -}}
16-
{{ errorf "tabs shortcode: 'items' parameter is required" }}
10+
{{- /* Enable syncing of tabs across the page. */ -}}
11+
{{- $enableSync := false -}}
12+
{{- if or (eq .Page.Params.tabs.sync false) (eq .Page.Params.tabs.sync true) -}}
13+
{{- $enableSync = .Page.Params.tabs.sync -}}
14+
{{- else -}}
15+
{{- $enableSync = site.Params.page.tabs.sync | default false -}}
1716
{{- end -}}
1817

19-
{{- if not $items -}}
20-
{{ errorf "tabs shortcode: 'items' parameter cannot be empty" }}
18+
{{- $tabs := ($.Store.Get "tabs") | default slice -}}
19+
20+
{{- /* Compatibility with previous parameter "items". */ -}}
21+
{{- if .Get "defaultIndex" -}}
22+
{{- warnf "The 'defaultIndex' parameter of the 'tabs' shortcode is deprecated. Please use 'selected' on 'tab' instead." -}}
2123
{{- end -}}
2224

23-
{{- range $items -}}
24-
{{- if eq (trim . " ") "" -}}
25-
{{ errorf "tabs shortcode: empty item found in 'items' parameter" }}
25+
{{- if .Get "items" -}}
26+
{{- warnf "The 'items' parameter of the 'tabs' shortcode is deprecated. Please use 'name' on 'tab' instead." -}}
27+
28+
{{- $items := split (.Get "items") "," -}}
29+
30+
{{- $temp := slice -}}
31+
{{- range $i, $item := $items -}}
32+
{{- $tab := index $tabs $i -}}
33+
{{- $temp = $temp | append (merge $tab (dict "name" $item)) -}}
2634
{{- end -}}
35+
36+
{{- $tabs = $temp -}}
2737
{{- end -}}
2838

29-
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
30-
<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 }}>
31-
{{- range $i, $item := $items -}}
32-
<button
33-
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"
34-
role="tab"
35-
type="button"
36-
aria-controls="tabs-panel-{{ $i }}"
37-
{{- if eq $i $defaultIndex }} aria-selected="true" {{ end -}}
38-
{{- if eq $i $defaultIndex }} tabindex="0" {{ end -}}
39-
{{- if eq $i $defaultIndex }} data-state="selected"{{ end -}}
40-
>
41-
{{- $item -}}
42-
</button>
43-
{{- end -}}
44-
</div>
45-
</div>
46-
<div>
47-
{{- .Inner -}}
48-
</div>
49-
{{- /* Drop trailing newlines */ -}}
39+
{{- partial "shortcodes/tabs" (dict "tabs" $tabs "enableSync" $enableSync "id" .Ordinal) -}}

0 commit comments

Comments
 (0)