From e61e23e4175dd92c19461b7d3c33ea7f40d1a073 Mon Sep 17 00:00:00 2001 From: "David W. Dougherty" Date: Mon, 2 Jun 2025 14:10:27 -0700 Subject: [PATCH 1/5] DEV: add generic-ish tab control --- assets/css/index.css | 52 +++++++++ content/develop/multitabs-demo.md | 63 ++++++++++ layouts/partials/components/generic-tabs.html | 64 +++++++++++ layouts/partials/scripts.html | 5 +- layouts/shortcodes/multitabs.html | 63 ++++++++++ static/js/generic-tabs.js | 108 ++++++++++++++++++ 6 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 content/develop/multitabs-demo.md create mode 100644 layouts/partials/components/generic-tabs.html create mode 100644 layouts/shortcodes/multitabs.html create mode 100644 static/js/generic-tabs.js diff --git a/assets/css/index.css b/assets/css/index.css index 28ae78149a..e612924d38 100644 --- a/assets/css/index.css +++ b/assets/css/index.css @@ -739,6 +739,58 @@ input[type="radio"] { appearance: none; } +/* Generic GitHub-style tabs */ +.generic-tabs { + @apply w-full mb-6; +} + +.generic-tabs .tab-nav { + @apply flex border-b border-redis-pen-300 bg-redis-neutral-200 rounded-t-md; +} + +.generic-tabs .tab-radio { + @apply sr-only; +} + +.generic-tabs .tab-label { + @apply px-4 py-3 cursor-pointer text-sm font-medium text-redis-pen-600 + bg-redis-neutral-200 border-r border-redis-pen-300 + hover:bg-white hover:text-redis-ink-900 + transition-colors duration-150 ease-in-out + focus:outline-none focus:ring-2 focus:ring-redis-red-500 focus:ring-inset + first:rounded-tl-md select-none; +} + +.generic-tabs .tab-label:last-child { + @apply border-r-0 rounded-tr-md; +} + +.generic-tabs .tab-radio:checked + .tab-label { + @apply bg-white text-redis-ink-900 border-b-2 border-b-redis-red-500 -mb-px relative z-10; +} + +.generic-tabs .tab-radio:focus + .tab-label { + @apply ring-2 ring-redis-red-500 ring-inset; +} + +.generic-tabs .tab-content { + @apply hidden p-6 bg-white border border-t-0 border-redis-pen-300 rounded-b-md shadow-sm; +} + +.generic-tabs .tab-content.active { + @apply block; +} + +/* Ensure proper stacking and borders */ +.generic-tabs .tab-content-container { + @apply relative -mt-px; +} + +/* Single content box styling (when no explicit tabs are provided) */ +.generic-tabs-single .tab-content-single { + @apply prose prose-lg max-w-none; +} + .stack-logo-inline { display: inline; max-height: 1em; diff --git a/content/develop/multitabs-demo.md b/content/develop/multitabs-demo.md new file mode 100644 index 0000000000..fd385144ec --- /dev/null +++ b/content/develop/multitabs-demo.md @@ -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 `---` dividers + +This approach avoids Hugo's nested shortcode parsing issues while still providing clean multi-tab functionality. + +- - - + +## 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)); +}); +``` + +- - - + +## 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 >}} diff --git a/layouts/partials/components/generic-tabs.html b/layouts/partials/components/generic-tabs.html new file mode 100644 index 0000000000..ae1dcd84a1 --- /dev/null +++ b/layouts/partials/components/generic-tabs.html @@ -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")) }} + +
+ +
+ {{ range $index, $tab := $tabs }} + {{ $tabId := printf "%s-tab-%d" $id $index }} + {{ $panelId := printf "%s-panel-%d" $id $index }} + + + + {{ end }} +
+ + +
+ {{ range $index, $tab := $tabs }} + {{ $tabId := printf "%s-tab-%d" $id $index }} + {{ $panelId := printf "%s-panel-%d" $id $index }} + +
+ {{ $tab.content | safeHTML }} +
+ {{ end }} +
+
diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html index caaee46967..243916f8e9 100644 --- a/layouts/partials/scripts.html +++ b/layouts/partials/scripts.html @@ -141,4 +141,7 @@ } } } - \ No newline at end of file + + + + \ No newline at end of file diff --git a/layouts/shortcodes/multitabs.html b/layouts/shortcodes/multitabs.html new file mode 100644 index 0000000000..6f0c55c42b --- /dev/null +++ b/layouts/shortcodes/multitabs.html @@ -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 + + - - - + + Content for tab 2 + + - - - + + Content for tab 3 + {{< /multitabs >}} +*/}} + +{{ $id := .Get "id" | default (printf "tabs-%s" (substr (.Inner | md5) 0 8)) }} +{{ $tabs := slice }} + +{{/* Split content by --- separator */}} +{{ $sections := split .Inner "- - -" }} + +{{/* 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 */}} +
+
+ {{ .Inner | markdownify }} +
+
+{{ end }} diff --git a/static/js/generic-tabs.js b/static/js/generic-tabs.js new file mode 100644 index 0000000000..3d37d9b07c --- /dev/null +++ b/static/js/generic-tabs.js @@ -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; From 5a16e8512f46970253c379010193dba5fce78189 Mon Sep 17 00:00:00 2001 From: "David W. Dougherty" Date: Tue, 3 Jun 2025 07:38:43 -0700 Subject: [PATCH 2/5] Minor modifications. --- content/develop/multitabs-demo.md | 6 +++--- layouts/shortcodes/multitabs.html | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/content/develop/multitabs-demo.md b/content/develop/multitabs-demo.md index fd385144ec..a332f6253d 100644 --- a/content/develop/multitabs-demo.md +++ b/content/develop/multitabs-demo.md @@ -16,11 +16,11 @@ Welcome to the **Getting Started** tab! This demonstrates the simpler multi-tab ### Quick Setup 1. Include the tab component files 2. Use the `multitabs` shortcode with tab parameters -3. Separate content with `---` dividers +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 @@ -41,7 +41,7 @@ document.addEventListener('DOMContentLoaded', () => { }); ``` -- - - +-tab-sep- ## How to Use diff --git a/layouts/shortcodes/multitabs.html b/layouts/shortcodes/multitabs.html index 6f0c55c42b..8a9a1904bf 100644 --- a/layouts/shortcodes/multitabs.html +++ b/layouts/shortcodes/multitabs.html @@ -9,11 +9,11 @@ Content for tab 1 - - - - + -tab-sep- Content for tab 2 - - - - + -tab-sep- Content for tab 3 {{< /multitabs >}} @@ -22,8 +22,8 @@ {{ $id := .Get "id" | default (printf "tabs-%s" (substr (.Inner | md5) 0 8)) }} {{ $tabs := slice }} -{{/* Split content by --- separator */}} -{{ $sections := split .Inner "- - -" }} +{{/* Split content by -tab-sep- separator */}} +{{ $sections := split .Inner "-tab-sep-" }} {{/* Get tab titles from parameters */}} {{ $tabTitles := slice }} From c72b5c447f5c6752fb7ee98c8ad86193e9addd91 Mon Sep 17 00:00:00 2001 From: "David W. Dougherty" Date: Tue, 3 Jun 2025 08:08:33 -0700 Subject: [PATCH 3/5] Minor styling change. --- assets/css/index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/css/index.css b/assets/css/index.css index e612924d38..64d728c8f6 100644 --- a/assets/css/index.css +++ b/assets/css/index.css @@ -770,7 +770,7 @@ input[type="radio"] { } .generic-tabs .tab-radio:focus + .tab-label { - @apply ring-2 ring-redis-red-500 ring-inset; + @apply border-b-2 border-b-redis-red-500 -mb-px; } .generic-tabs .tab-content { From cd45f511216e8816f873bf0383cc83b7faf488fa Mon Sep 17 00:00:00 2001 From: "David W. Dougherty" Date: Tue, 3 Jun 2025 08:59:59 -0700 Subject: [PATCH 4/5] CSS fix --- assets/css/index.css | 2 +- content/develop/multitabs-demo.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/css/index.css b/assets/css/index.css index 64d728c8f6..01872d7ae1 100644 --- a/assets/css/index.css +++ b/assets/css/index.css @@ -774,7 +774,7 @@ input[type="radio"] { } .generic-tabs .tab-content { - @apply hidden p-6 bg-white border border-t-0 border-redis-pen-300 rounded-b-md shadow-sm; + @apply hidden px-6 pb-6 pt-3 bg-white border border-t-0 border-redis-pen-300 rounded-b-md shadow-sm; } .generic-tabs .tab-content.active { diff --git a/content/develop/multitabs-demo.md b/content/develop/multitabs-demo.md index a332f6253d..2f22f1d0f3 100644 --- a/content/develop/multitabs-demo.md +++ b/content/develop/multitabs-demo.md @@ -13,7 +13,7 @@ This page tests a simpler approach to multi-tab syntax that works reliably with {{< 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 +**Quick Setup** 1. Include the tab component files 2. Use the `multitabs` shortcode with tab parameters 3. Separate content with a divider: `- tab - sep - @@ -22,7 +22,7 @@ This approach avoids Hugo's nested shortcode parsing issues while still providin -tab-sep- -## Key Features +**Key Features** The tab control includes: @@ -32,7 +32,7 @@ The tab control includes: - **Markdown support**: Full markdown rendering within tabs - **Simple syntax**: Uses parameter-based tab titles and content separators -### Code Example +**Code Example** ```javascript // Example of tab initialization document.addEventListener('DOMContentLoaded', () => { @@ -43,7 +43,7 @@ document.addEventListener('DOMContentLoaded', () => { -tab-sep- -## How to Use +**How to Use** The multi-tab syntax uses parameters for tab titles and separates content with triple dashes. @@ -52,7 +52,7 @@ The multi-tab syntax uses parameters for tab titles and separates content with t 2. Separate content sections with `---` on its own line 3. Each section becomes the content for the corresponding tab -### Benefits +**Benefits** - **Reliable parsing**: No nested shortcode issues - **Clean syntax**: Easy to read and write - **Flexible content**: Any markdown content works From b104d88d4d20b22a890f7d64f5a24e35147892a6 Mon Sep 17 00:00:00 2001 From: "David W. Dougherty" Date: Tue, 3 Jun 2025 09:29:26 -0700 Subject: [PATCH 5/5] Remove demo file before merge. --- content/develop/multitabs-demo.md | 63 ------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 content/develop/multitabs-demo.md diff --git a/content/develop/multitabs-demo.md b/content/develop/multitabs-demo.md deleted file mode 100644 index 2f22f1d0f3..0000000000 --- a/content/develop/multitabs-demo.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -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 >}}