mirror of
				https://github.com/imfing/hextra.git
				synced 2025-10-31 12:54:51 -04:00 
			
		
		
		
	Merge branch 'main' into image-zoom
This commit is contained in:
		| @@ -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" }} |  | ||||||
|  |   {{- $items := split (.Get "items") "," -}} | ||||||
|  |  | ||||||
|  |   {{- $temp := slice -}} | ||||||
|  |   {{- range $i, $item := $items -}} | ||||||
|  |     {{- $tab := index $tabs $i -}} | ||||||
|  |     {{- $temp = $temp | append (merge $tab (dict "name" $item)) -}} | ||||||
|   {{- end -}} |   {{- end -}} | ||||||
|  |  | ||||||
|  |   {{- $tabs = $temp -}} | ||||||
| {{- end -}} | {{- end -}} | ||||||
|  |  | ||||||
| <div class="hextra-scrollbar hx:overflow-x-auto hx:overflow-y-hidden hx:overscroll-x-contain"> | {{- partial "shortcodes/tabs" (dict "tabs" $tabs "enableSync" $enableSync "id" .Ordinal) -}} | ||||||
|   <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 -}} |  | ||||||
|       <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-{{ $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 -}} |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
| <div> |  | ||||||
|   {{- .Inner -}} |  | ||||||
| </div> |  | ||||||
| {{- /* Drop trailing newlines */ -}} |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Xin
					Xin