mirror of
https://github.com/imfing/hextra.git
synced 2025-09-14 07:07:17 -04:00
feat(tabs): revamp tabs (#815)
This commit is contained in:

committed by
GitHub

parent
3bc454bbf6
commit
ccb63d60f1
@@ -5,12 +5,10 @@ next: /docs/guide/deploy-site
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
{{< tabs items="macOS,Linux,Windows" >}}
|
{{< 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 >}}**macOS**: A desktop operating system by Apple.{{< /tab >}}
|
{{< tab name="YAML" >}}**YAML**: YAML is a human-readable data serialization language.{{< /tab >}}
|
||||||
{{< tab >}}**Linux**: An open-source operating system.{{< /tab >}}
|
{{< tab name="TOML" >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
|
||||||
{{< tab >}}**Windows**: A desktop operating system by Microsoft.{{< /tab >}}
|
|
||||||
|
|
||||||
{{< /tabs >}}
|
{{< /tabs >}}
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -18,37 +16,35 @@ next: /docs/guide/deploy-site
|
|||||||
### Default
|
### Default
|
||||||
|
|
||||||
```
|
```
|
||||||
{{</* tabs items="JSON,YAML,TOML" */>}}
|
{{</* tabs */>}}
|
||||||
|
|
||||||
{{</* tab */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
|
{{</* 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 */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
|
{{</* tab name="YAML" */>}}**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 name="TOML" */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
|
||||||
|
|
||||||
{{</* /tabs */>}}
|
{{</* /tabs */>}}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 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.
|
||||||
|
|
||||||
```
|
```
|
||||||
{{</* tabs items="JSON,YAML,TOML" defaultIndex="1" */>}}
|
{{</* tabs */>}}
|
||||||
|
|
||||||
{{</* tab */>}}**JSON**: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax.{{</* /tab */>}}
|
{{</* 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 */>}}**YAML**: YAML is a human-readable data serialization language.{{</* /tab */>}}
|
{{</* tab name="YAML" selected=true */>}}**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 name="TOML" */>}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{</* /tab */>}}
|
||||||
|
|
||||||
{{</* /tabs */>}}
|
{{</* /tabs */>}}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `YAML` tab will be selected by default.
|
The `YAML` tab will be selected by default.
|
||||||
|
|
||||||
{{< tabs items="JSON,YAML,TOML" defaultIndex="1" >}}
|
{{< 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 >}}**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 >}}**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 >}}
|
||||||
{{< tab >}}**TOML**: TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics.{{< /tab >}}
|
|
||||||
|
|
||||||
{{< /tabs >}}
|
{{< /tabs >}}
|
||||||
|
|
||||||
|
|
||||||
@@ -57,9 +53,9 @@ The `YAML` tab will be selected by default.
|
|||||||
Markdown syntax including code block is also supported:
|
Markdown syntax including code block is also supported:
|
||||||
|
|
||||||
````
|
````
|
||||||
{{</* tabs items="JSON,YAML,TOML" */>}}
|
{{</* tabs */>}}
|
||||||
|
|
||||||
{{</* tab */>}}
|
{{</* tab name="JSON" */>}}
|
||||||
```json
|
```json
|
||||||
{ "hello": "world" }
|
{ "hello": "world" }
|
||||||
```
|
```
|
||||||
@@ -70,21 +66,21 @@ Markdown syntax including code block is also supported:
|
|||||||
{{</* /tabs */>}}
|
{{</* /tabs */>}}
|
||||||
````
|
````
|
||||||
|
|
||||||
{{< tabs items="JSON,YAML,TOML" >}}
|
{{< tabs >}}
|
||||||
|
|
||||||
{{< tab >}}
|
{{< tab name="JSON" >}}
|
||||||
```json
|
```json
|
||||||
{ "hello": "world" }
|
{ "hello": "world" }
|
||||||
```
|
```
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab >}}
|
{{< tab name="YAML" >}}
|
||||||
```yaml
|
```yaml
|
||||||
hello: world
|
hello: world
|
||||||
```
|
```
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab >}}
|
{{< tab name="TOML" >}}
|
||||||
```toml
|
```toml
|
||||||
hello = "world"
|
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.
|
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"}
|
```yaml {filename="hugo.yaml"}
|
||||||
params:
|
params:
|
||||||
@@ -106,20 +102,33 @@ params:
|
|||||||
sync: true
|
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
|
```markdown
|
||||||
{{</* tabs items="A,B" */>}}
|
{{</* tabs */>}}
|
||||||
|
|
||||||
{{</* tab */>}}A content{{</* /tab */>}}
|
{{</* tab name="A" */>}}A content{{</* /tab */>}}
|
||||||
{{</* tab */>}}B content{{</* /tab */>}}
|
{{</* tab name="B" */>}}B content{{</* /tab */>}}
|
||||||
|
|
||||||
{{</* /tabs */>}}
|
{{</* /tabs */>}}
|
||||||
|
|
||||||
{{</* tabs items="A,B" */>}}
|
{{</* tabs */>}}
|
||||||
|
|
||||||
{{</* tab */>}}Second A content{{</* /tab */>}}
|
{{</* tab name="A" */>}}Second A content{{</* /tab */>}}
|
||||||
{{</* tab */>}}Second B content{{</* /tab */>}}
|
{{</* tab name="B" */>}}Second B content{{</* /tab */>}}
|
||||||
|
|
||||||
{{</* /tabs */>}}
|
{{</* /tabs */>}}
|
||||||
```
|
```
|
||||||
|
69
layouts/_partials/shortcodes/tabs.html
Normal file
69
layouts/_partials/shortcodes/tabs.html
Normal file
@@ -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 -}}
|
||||||
|
|
||||||
|
<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"
|
||||||
|
{{ if $enableSync }} data-tab-group="{{ delimit $dataTabGroup `,` }}"{{ end }}
|
||||||
|
>
|
||||||
|
{{- range $i, $item := $tabs -}}
|
||||||
|
<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"
|
||||||
|
role="tab"
|
||||||
|
type="button"
|
||||||
|
aria-controls="tabs-panel-{{ $globalID }}-{{ $item.id }}"
|
||||||
|
{{- if eq $i $selectedIndex -}}
|
||||||
|
aria-selected="true"
|
||||||
|
tabindex="0"
|
||||||
|
data-state="selected"
|
||||||
|
{{- end }}
|
||||||
|
>
|
||||||
|
{{- $item.name -}}
|
||||||
|
</button>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{- range $i, $item := $tabs -}}
|
||||||
|
<div
|
||||||
|
class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block"
|
||||||
|
id="tabs-panel-{{ $globalID }}-{{ $item.id }}"
|
||||||
|
role="tabpanel"
|
||||||
|
{{- if eq $i $selectedIndex -}}
|
||||||
|
tabindex="0"
|
||||||
|
data-state="selected"
|
||||||
|
{{ end -}}
|
||||||
|
>
|
||||||
|
{{- $item.content | markdownify -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
@@ -1,18 +1,24 @@
|
|||||||
{{- /*
|
{{- /*
|
||||||
Create a tab.
|
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) -}}
|
||||||
|
|
||||||
<div
|
{{- $selected := .Get "selected" -}}
|
||||||
class="hextra-tabs-panel hx:rounded-sm hx:pt-6 hx:hidden hx:data-[state=selected]:block"
|
{{- if .Parent.Get "defaultIndex" -}}
|
||||||
id="tabs-panel-{{ .Ordinal }}"
|
{{- $selected = eq .Ordinal (int (.Parent.Get "defaultIndex")) -}}
|
||||||
role="tabpanel"
|
{{- end -}}
|
||||||
{{- if eq .Ordinal $defaultIndex }} tabindex="0" {{ end -}}
|
|
||||||
{{- if eq .Ordinal $defaultIndex }} data-state="selected" {{ end -}}
|
{{- $tabs := .Parent.Store.Get "tabs" | default slice -}}
|
||||||
>
|
{{ .Parent.Store.Set "tabs" ($tabs | append (dict
|
||||||
{{- .InnerDeindent | markdownify -}}
|
"id" .Ordinal
|
||||||
</div>
|
"name" $name
|
||||||
{{- /* Drop trailing newlines */ -}}
|
"content" .InnerDeindent
|
||||||
|
"selected" $selected
|
||||||
|
))
|
||||||
|
-}}
|
||||||
|
@@ -1,49 +1,39 @@
|
|||||||
{{- /*
|
{{- /*
|
||||||
Create a tabbed interface with the given items.
|
Create a tabbed interface with the given items.
|
||||||
|
|
||||||
@param {string} items The items to display in the tabs.
|
@example {{< tabs >}}...{{< /tabs >}}
|
||||||
@param {string} defaultIndex The index of the default tab.
|
|
||||||
|
|
||||||
@example {{< tabs items="JSON,YAML,TOML" >}}{{< /tabs >}}
|
|
||||||
*/ -}}
|
*/ -}}
|
||||||
|
|
||||||
{{- $items := split (.Get "items") "," -}}
|
{{- /* Unused, but required for the shortcode to work. */ -}}
|
||||||
{{- $defaultIndex := int ((.Get "defaultIndex") | default "0") -}}
|
{{- .Inner -}}
|
||||||
|
|
||||||
{{- $enableSync := site.Params.page.tabs.sync | default false -}}
|
{{- /* Enable syncing of tabs across the page. */ -}}
|
||||||
|
{{- $enableSync := false -}}
|
||||||
{{- if not (.Get "items") -}}
|
{{- if or (eq .Page.Params.tabs.sync false) (eq .Page.Params.tabs.sync true) -}}
|
||||||
{{ errorf "tabs shortcode: 'items' parameter is required" }}
|
{{- $enableSync = .Page.Params.tabs.sync -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $enableSync = site.Params.page.tabs.sync | default false -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{- if not $items -}}
|
{{- $tabs := ($.Store.Get "tabs") | default slice -}}
|
||||||
{{ errorf "tabs shortcode: 'items' parameter cannot be empty" }}
|
|
||||||
|
{{- /* 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 -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{- range $items -}}
|
{{- if .Get "items" -}}
|
||||||
{{- if eq (trim . " ") "" -}}
|
{{- warnf "The 'items' parameter of the 'tabs' shortcode is deprecated. Please use 'name' on 'tab' instead." -}}
|
||||||
{{ errorf "tabs shortcode: empty item found in 'items' parameter" }}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
<div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain">
|
{{- $items := split (.Get "items") "," -}}
|
||||||
<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 }}>
|
|
||||||
|
{{- $temp := slice -}}
|
||||||
{{- range $i, $item := $items -}}
|
{{- range $i, $item := $items -}}
|
||||||
<button
|
{{- $tab := index $tabs $i -}}
|
||||||
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"
|
{{- $temp = $temp | append (merge $tab (dict "name" $item)) -}}
|
||||||
role="tab"
|
|
||||||
type="button"
|
|
||||||
aria-controls="tabs-panel-{{ $i }}"
|
|
||||||
{{- if eq $i $defaultIndex }} aria-selected="true" {{ end -}}
|
|
||||||
{{- if eq $i $defaultIndex }} tabindex="0" {{ end -}}
|
|
||||||
{{- if eq $i $defaultIndex }} data-state="selected"{{ end -}}
|
|
||||||
>
|
|
||||||
{{- $item -}}
|
|
||||||
</button>
|
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</div>
|
|
||||||
</div>
|
{{- $tabs = $temp -}}
|
||||||
<div>
|
{{- end -}}
|
||||||
{{- .Inner -}}
|
|
||||||
</div>
|
{{- partial "shortcodes/tabs" (dict "tabs" $tabs "enableSync" $enableSync "id" .Ordinal) -}}
|
||||||
{{- /* Drop trailing newlines */ -}}
|
|
||||||
|
Reference in New Issue
Block a user