-
Notifications
You must be signed in to change notification settings - Fork 211
DEV: add generic-ish tab control #1651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
--- | ||
title: "Multi-Tabs Test" | ||
description: "Testing the simpler multi-tab syntax" | ||
weight: 995 | ||
--- | ||
|
||
# Multi-Tabs Shortcode Test | ||
|
||
This page tests a simpler approach to multi-tab syntax that works reliably with Hugo. | ||
|
||
## Multi-Tab Example | ||
|
||
{{< multitabs id="example-tabs" tab1="Getting Started" tab2="Features" tab3="Usage Guide" >}} | ||
Welcome to the **Getting Started** tab! This demonstrates the simpler multi-tab syntax. | ||
|
||
### Quick Setup | ||
1. Include the tab component files | ||
2. Use the `multitabs` shortcode with tab parameters | ||
3. Separate content with a divider: `- tab - sep - | ||
|
||
This approach avoids Hugo's nested shortcode parsing issues while still providing clean multi-tab functionality. | ||
|
||
-tab-sep- | ||
|
||
## Key Features | ||
|
||
The tab control includes: | ||
|
||
- **GitHub-style design**: Clean, professional appearance | ||
- **Accessibility**: Full keyboard navigation and ARIA support | ||
- **Responsive**: Works on all screen sizes | ||
- **Markdown support**: Full markdown rendering within tabs | ||
- **Simple syntax**: Uses parameter-based tab titles and content separators | ||
|
||
### Code Example | ||
```javascript | ||
// Example of tab initialization | ||
document.addEventListener('DOMContentLoaded', () => { | ||
const tabs = document.querySelectorAll('.generic-tabs'); | ||
tabs.forEach(tab => new GenericTabs(tab)); | ||
}); | ||
``` | ||
|
||
-tab-sep- | ||
|
||
## How to Use | ||
|
||
The multi-tab syntax uses parameters for tab titles and separates content with triple dashes. | ||
|
||
**Syntax structure:** | ||
1. Define tab titles as parameters: `tab1="Title 1" tab2="Title 2"` | ||
2. Separate content sections with `---` on its own line | ||
3. Each section becomes the content for the corresponding tab | ||
|
||
### Benefits | ||
- **Reliable parsing**: No nested shortcode issues | ||
- **Clean syntax**: Easy to read and write | ||
- **Flexible content**: Any markdown content works | ||
- **Maintainable**: Clear separation between tabs | ||
- **Accessible**: Proper semantic structure | ||
|
||
Perfect for organizing documentation, tutorials, and reference materials! | ||
{{< /multitabs >}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{{/* | ||
Generic GitHub-style tabs component | ||
|
||
Usage: | ||
{{ partial "components/generic-tabs.html" (dict "id" "my-tabs" "tabs" $tabs) }} | ||
|
||
Where $tabs is an array of dictionaries with "title" and "content" keys: | ||
$tabs := slice | ||
(dict "title" "Tab 1" "content" "Content for tab 1") | ||
(dict "title" "Tab 2" "content" "Content for tab 2") | ||
*/}} | ||
|
||
{{ $id := .id | default (printf "tabs-%s" (substr (.tabs | jsonify | md5) 0 8)) }} | ||
{{ $tabs := .tabs | default (slice (dict "title" "Error" "content" "No tabs provided")) }} | ||
|
||
<div class="generic-tabs" id="{{ $id }}"> | ||
<!-- Tab Navigation --> | ||
<div class="tab-nav" role="tablist" aria-label="Tab navigation"> | ||
{{ range $index, $tab := $tabs }} | ||
{{ $tabId := printf "%s-tab-%d" $id $index }} | ||
{{ $panelId := printf "%s-panel-%d" $id $index }} | ||
|
||
<input | ||
type="radio" | ||
name="{{ $id }}" | ||
id="{{ $tabId }}" | ||
class="tab-radio" | ||
{{ if eq $index 0 }}checked{{ end }} | ||
aria-controls="{{ $panelId }}" | ||
data-tab-index="{{ $index }}" | ||
/> | ||
<label | ||
for="{{ $tabId }}" | ||
class="tab-label" | ||
role="tab" | ||
aria-selected="{{ if eq $index 0 }}true{{ else }}false{{ end }}" | ||
aria-controls="{{ $panelId }}" | ||
tabindex="{{ if eq $index 0 }}0{{ else }}-1{{ end }}" | ||
> | ||
{{ $tab.title }} | ||
</label> | ||
{{ end }} | ||
</div> | ||
|
||
<!-- Tab Content --> | ||
<div class="tab-content-container"> | ||
{{ range $index, $tab := $tabs }} | ||
{{ $tabId := printf "%s-tab-%d" $id $index }} | ||
{{ $panelId := printf "%s-panel-%d" $id $index }} | ||
|
||
<div | ||
id="{{ $panelId }}" | ||
class="tab-content {{ if eq $index 0 }}active{{ end }}" | ||
role="tabpanel" | ||
aria-labelledby="{{ $tabId }}" | ||
tabindex="0" | ||
data-tab-index="{{ $index }}" | ||
{{ if ne $index 0 }}aria-hidden="true"{{ end }} | ||
> | ||
{{ $tab.content | safeHTML }} | ||
</div> | ||
{{ end }} | ||
</div> | ||
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
{{/* | ||
Multi-tabs shortcode with simpler syntax | ||
|
||
Usage: | ||
{{< multitabs id="my-tabs" | ||
tab1="Tab Title 1" | ||
tab2="Tab Title 2" | ||
tab3="Tab Title 3" >}} | ||
|
||
Content for tab 1 | ||
|
||
-tab-sep- | ||
|
||
Content for tab 2 | ||
|
||
-tab-sep- | ||
|
||
Content for tab 3 | ||
{{< /multitabs >}} | ||
*/}} | ||
|
||
{{ $id := .Get "id" | default (printf "tabs-%s" (substr (.Inner | md5) 0 8)) }} | ||
{{ $tabs := slice }} | ||
|
||
{{/* Split content by -tab-sep- separator */}} | ||
{{ $sections := split .Inner "-tab-sep-" }} | ||
|
||
{{/* Get tab titles from parameters */}} | ||
{{ $tabTitles := slice }} | ||
{{ range $i := seq 1 10 }} | ||
{{ $tabParam := printf "tab%d" $i }} | ||
{{ $title := $.Get $tabParam }} | ||
{{ if $title }} | ||
{{ $tabTitles = $tabTitles | append $title }} | ||
{{ end }} | ||
{{ end }} | ||
|
||
{{/* Create tabs from sections and titles */}} | ||
{{ range $index, $section := $sections }} | ||
{{ $title := "Tab" }} | ||
{{ if lt $index (len $tabTitles) }} | ||
{{ $title = index $tabTitles $index }} | ||
{{ else }} | ||
{{ $title = printf "Tab %d" (add $index 1) }} | ||
{{ end }} | ||
|
||
{{ $content := $section | strings.TrimSpace | markdownify }} | ||
{{ if ne $content "" }} | ||
{{ $tabs = $tabs | append (dict "title" $title "content" $content) }} | ||
{{ end }} | ||
{{ end }} | ||
|
||
{{/* Render tabs if we have any */}} | ||
{{ if gt (len $tabs) 0 }} | ||
{{ partial "components/generic-tabs.html" (dict "id" $id "tabs" $tabs) }} | ||
{{ else }} | ||
{{/* Fallback to single content box */}} | ||
<div class="generic-tabs-single mb-6"> | ||
<div class="tab-content-single p-6 bg-white border border-redis-pen-300 rounded-md shadow-sm"> | ||
{{ .Inner | markdownify }} | ||
</div> | ||
</div> | ||
{{ end }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/** | ||
* Generic GitHub-style tabs functionality | ||
* Handles tab switching, keyboard navigation, and accessibility | ||
*/ | ||
|
||
class GenericTabs { | ||
constructor(container) { | ||
this.container = container; | ||
this.tabRadios = container.querySelectorAll('.tab-radio'); | ||
this.tabLabels = container.querySelectorAll('.tab-label'); | ||
this.tabPanels = container.querySelectorAll('.tab-content'); | ||
|
||
this.init(); | ||
} | ||
|
||
init() { | ||
// Add event listeners for radio button changes | ||
this.tabRadios.forEach((radio, index) => { | ||
radio.addEventListener('change', (e) => { | ||
if (e.target.checked) { | ||
this.switchToTab(index); | ||
} | ||
}); | ||
}); | ||
|
||
// Add keyboard navigation for tab labels | ||
this.tabLabels.forEach((label, index) => { | ||
label.addEventListener('keydown', (e) => { | ||
this.handleKeydown(e, index); | ||
}); | ||
}); | ||
|
||
// Set initial state | ||
const checkedRadio = this.container.querySelector('.tab-radio:checked'); | ||
if (checkedRadio) { | ||
const index = parseInt(checkedRadio.dataset.tabIndex); | ||
this.switchToTab(index); | ||
} | ||
} | ||
|
||
switchToTab(index) { | ||
// Update radio buttons | ||
this.tabRadios.forEach((radio, i) => { | ||
radio.checked = i === index; | ||
}); | ||
|
||
// Update tab labels | ||
this.tabLabels.forEach((label, i) => { | ||
const isSelected = i === index; | ||
label.setAttribute('aria-selected', isSelected); | ||
label.setAttribute('tabindex', isSelected ? '0' : '-1'); | ||
}); | ||
|
||
// Update tab panels | ||
this.tabPanels.forEach((panel, i) => { | ||
const isActive = i === index; | ||
panel.classList.toggle('active', isActive); | ||
panel.setAttribute('aria-hidden', !isActive); | ||
}); | ||
} | ||
|
||
handleKeydown(event, currentIndex) { | ||
let newIndex = currentIndex; | ||
|
||
switch (event.key) { | ||
case 'ArrowLeft': | ||
event.preventDefault(); | ||
newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabLabels.length - 1; | ||
break; | ||
case 'ArrowRight': | ||
event.preventDefault(); | ||
newIndex = currentIndex < this.tabLabels.length - 1 ? currentIndex + 1 : 0; | ||
break; | ||
case 'Home': | ||
event.preventDefault(); | ||
newIndex = 0; | ||
break; | ||
case 'End': | ||
event.preventDefault(); | ||
newIndex = this.tabLabels.length - 1; | ||
break; | ||
case 'Enter': | ||
case ' ': | ||
event.preventDefault(); | ||
this.tabRadios[currentIndex].checked = true; | ||
this.switchToTab(currentIndex); | ||
return; | ||
default: | ||
return; | ||
} | ||
|
||
// Focus and activate the new tab | ||
this.tabLabels[newIndex].focus(); | ||
this.tabRadios[newIndex].checked = true; | ||
this.switchToTab(newIndex); | ||
} | ||
} | ||
|
||
// Initialize all generic tabs on page load | ||
document.addEventListener('DOMContentLoaded', () => { | ||
const tabContainers = document.querySelectorAll('.generic-tabs'); | ||
tabContainers.forEach(container => { | ||
new GenericTabs(container); | ||
}); | ||
}); | ||
|
||
// Export for potential external use | ||
window.GenericTabs = GenericTabs; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spacing here doesn't look great. Does removing the blank line between the heading and the -tab-sep- line fix it, or is this just a hazard of heading padding?

Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @cmilesb! I appreciate the review.
I'm pretty sure the spacing issue is caused by heading padding. I removed the blank lines before and after -tab-sep- and the spacing stayed the same. Maybe don't use any headings in the tabs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was some extra padding being added in the CSS. I fixed that, but headings still cause a lot of extra top whitespace. I can't really do anything about that without really messing up our site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, thanks for taking a look anyway.