Skip to content

Commit 66b9a93

Browse files
authored
SITE: replace TCE tab control with a pull-down menu (#2194)
1 parent b504480 commit 66b9a93

File tree

4 files changed

+185
-78
lines changed

4 files changed

+185
-78
lines changed

assets/css/index.css

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -395,34 +395,30 @@ select {
395395
@apply text-redis-red-500;
396396
}
397397

398-
.codetabs .label {
399-
min-width: 2rem;
400-
line-height: 0.852rem;
401-
color: #8CA0AD;
402-
}
403-
404-
.codetabs .label:active {
405-
color: white;
398+
/* Language selector dropdown styling */
399+
.codetabs .lang-selector {
400+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
401+
background-repeat: no-repeat;
402+
background-position: right 0.5rem center;
403+
background-size: 1.5em 1.5em;
404+
padding-right: 2.5rem;
406405
}
407406

408-
409-
.codetabs .radiotab:focus + .label {
410-
@apply bg-transparent text-indigo-600;
407+
.codetabs .codetabs-header {
408+
border-bottom: 1px solid #475569;
411409
}
412410

413-
.codetabs .radiotab:checked + .label {
414-
@apply border-slate-900 not-italic;
415-
@apply bg-slate-600 text-white pt-1 mt-2.5;
416-
411+
/* Panel visibility controlled by JavaScript */
412+
.codetabs .panel.panel-hidden {
413+
display: none !important;
417414
}
418415

419-
420-
.codetabs .radiotab:checked + .label + .panel {
421-
@apply block;
416+
.codetabs .panel:not(.panel-hidden) {
417+
display: block;
422418
}
423419

424420
.codetabs > .panel .highlight > .chroma {
425-
@apply rounded-tl-none m-0;
421+
@apply rounded-t-none m-0;
426422
font-size: 0.875rem;
427423
line-height: 1.25rem;
428424
}

layouts/partials/scripts.html

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
<script>
33
document.addEventListener("DOMContentLoaded", function () {
44

5-
// don't run script for tabbed code examples
6-
if (!document.querySelector('.codetabs.cli.group.flex.justify-start.items-center.flex-wrap.box-border.rounded-lg.mt-0.mb-0.mx-auto.bg-slate-900')) {
7-
document.querySelectorAll('div.highlight').forEach(block => {
5+
// Add copy buttons to code blocks (but skip those inside codetabs)
6+
document.querySelectorAll('div.highlight').forEach(block => {
7+
// Skip if this code block is inside a codetabs container
8+
if (block.closest('.codetabs')) {
9+
return;
10+
}
811
// Create a wrapper for the button and message
912
const wrapper = document.createElement('div');
1013

@@ -80,10 +83,9 @@
8083
wrapper.appendChild(downloadButton);
8184
}
8285

83-
block.style.position = 'relative';
84-
block.appendChild(wrapper);
85-
});
86-
}
86+
block.style.position = 'relative';
87+
block.appendChild(wrapper);
88+
});
8789
});
8890
</script>
8991

layouts/partials/tabs/wrapper.html

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,67 @@
88
{{ $showFooter := (.showFooter) }}
99

1010
<!-- spellchecker-disable -->
11-
<div class="codetabs cli group flex justify-start items-center flex-wrap box-border rounded-lg mt-0 mb-0 mx-auto bg-slate-900" id="{{ $id }}">
12-
{{ range $i, $tab := $tabs }}
13-
{{ $tid := printf "%s_%s" (replace (replace (index $tab "title") "#" "sharp") "." "") $id }}
14-
{{ $pid := printf "panel_%s" $tid }}
15-
{{ $dataLang := replace (or (index $tab "language") "redis-cli") "C#" "dotnet" }}
16-
{{ $dataLang := replace $dataLang "." "-" }}
17-
<input tabindex="1" type="radio" name="{{ $id }}" id="{{ $tid }}" data-lang="{{ $dataLang }}" class="radiotab w-0 h-0"
18-
{{ if eq $i 0 }}checked{{ end }}
19-
/>
20-
<label class="justify-left label ml-0.5 pt-3.5 px-1 pb-1 cursor-pointer text-sm text-center bg-redis-ink-900
21-
hover:text-redis-red-600 rounded rounded-mx transition duration-150 ease-in-out"
22-
title="Open example" for="{{ $tid }}">
23-
{{ if eq (index $tab "title") "redis-cli" }}
24-
{{ or (index $tab "displayName") $cliName }}
25-
{{ else }}
26-
{{ index $tab "title" }}
27-
{{ end }}
28-
</label>
29-
30-
<div class="panel order-last hidden w-full mt-0 {{ if not $showFooter}}pb-8{{end}} relative" id="{{ $pid }}" role="tabpanel" tabindex="0" aria-labelledby="tab-{{ $id }}">
31-
{{ index $tab "content" }}
32-
<button tabindex="1" class="clipboard-button text-neutral-400 hover:text-slate-100 bg-slate-600 absolute -top-8 right-10 h-7 w-7 mr-2 mt-2 p-1 rounded rounded-mx"
33-
title="Copy to clipboard" onclick="copyCodeToClipboard('#{{ $pid }}')">
11+
<div class="codetabs cli group box-border rounded-lg mt-0 mb-0 mx-auto bg-slate-900" id="{{ $id }}">
12+
<!-- Language selector dropdown with controls -->
13+
<div class="codetabs-header flex items-center justify-between px-4 py-2 bg-slate-900 rounded-t-lg">
14+
<div class="flex items-center flex-1">
15+
<label for="lang-select-{{ $id }}" class="text-xs text-slate-400 mr-3 whitespace-nowrap">Language:</label>
16+
<select id="lang-select-{{ $id }}"
17+
class="lang-selector max-w-xs px-3 py-2 text-sm bg-slate-700 text-white border border-slate-600 rounded-md cursor-pointer
18+
hover:bg-slate-600 focus:outline-none
19+
transition duration-150 ease-in-out appearance-none"
20+
data-codetabs-id="{{ $id }}">
21+
{{ range $i, $tab := $tabs }}
22+
{{ $dataLang := replace (or (index $tab "language") "redis-cli") "C#" "dotnet" }}
23+
{{ $dataLang := replace $dataLang "." "-" }}
24+
{{ $displayName := "" }}
25+
{{ if eq (index $tab "title") "redis-cli" }}
26+
{{ $displayName = or (index $tab "displayName") $cliName }}
27+
{{ else }}
28+
{{ $displayName = index $tab "title" }}
29+
{{ end }}
30+
<option value="{{ $dataLang }}" data-index="{{ $i }}" {{ if eq $i 0 }}selected{{ end }}>
31+
{{ $displayName }}
32+
</option>
33+
{{ end }}
34+
</select>
35+
</div>
36+
<div class="flex items-center gap-2">
37+
<button tabindex="1" class="visibility-button text-neutral-400 hover:text-slate-100 bg-slate-600 h-7 w-7 p-1 rounded rounded-mx"
38+
title="Toggle visibility" data-codetabs-id="{{ $id }}" onclick="toggleVisibleLinesForCodetabs(this)">
39+
<svg class="visibility-icon-on" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
40+
<path d="M24 31.5q3.55 0 6.025-2.475Q32.5 26.55 32.5 23q0-3.55-2.475-6.025Q27.55 14.5 24 14.5q-3.55 0-6.025 2.475Q15.5 19.45 15.5 23q0 3.55 2.475 6.025Q20.45 31.5 24 31.5Zm0-2.9q-2.35 0-3.975-1.625T18.4 23q0-2.35 1.625-3.975T24 17.4q2.35 0 3.975 1.625T29.6 23q0 2.35-1.625 3.975T24 28.6Zm0 9.4q-7.3 0-13.2-4.15Q4.9 29.7 2 23q2.9-6.7 8.8-10.85Q16.7 8 24 8q7.3 0 13.2 4.15Q43.1 16.3 46 23q-2.9 6.7-8.8 10.85Q31.3 38 24 38Zm0-15Zm0 12q6.05 0 11.125-3.275T42.85 23q-2.65-5.45-7.725-8.725Q30.05 11 24 11t-11.125 3.275Q7.8 17.55 5.1 23q2.7 5.45 7.775 8.725Q17.95 35 24 35Z"/>
41+
</svg>
42+
<svg class="visibility-icon-off hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
43+
<path d="m31.45 27.05-2.2-2.2q1.3-3.55-1.35-5.9-2.65-2.35-5.75-1.2l-2.2-2.2q.85-.55 1.9-.8 1.05-.25 2.15-.25 3.55 0 6.025 2.475Q32.5 19.45 32.5 23q0 1.1-.275 2.175-.275 1.075-.775 1.875Zm6.45 6.45-2-2q2.45-1.8 4.275-4.025Q42 25.25 42.85 23q-2.5-5.55-7.5-8.775Q30.35 11 24.5 11q-2.1 0-4.3.4-2.2.4-3.45.95L14.45 10q1.75-.8 4.475-1.4Q21.65 8 24.25 8q7.15 0 13.075 4.075Q43.25 16.15 46 23q-1.3 3.2-3.35 5.85-2.05 2.65-4.75 4.65Zm2.9 11.3-8.4-8.25q-1.75.7-3.95 1.075T24 38q-7.3 0-13.25-4.075T2 23q1-2.6 2.775-5.075T9.1 13.2L2.8 6.9l2.1-2.15L42.75 42.6ZM11.15 15.3q-1.85 1.35-3.575 3.55Q5.85 21.05 5.1 23q2.55 5.55 7.675 8.775Q17.9 35 24.4 35q1.65 0 3.25-.2t2.4-.6l-3.2-3.2q-.55.25-1.35.375T24 31.5q-3.5 0-6-2.45T15.5 23q0-.75.125-1.5T16 20.15Zm15.25 7.1Zm-5.8 2.9Z"/>
44+
</svg>
45+
</button>
46+
<button tabindex="1" class="clipboard-button text-neutral-400 hover:text-slate-100 bg-slate-600 h-7 w-7 p-1 rounded rounded-mx"
47+
title="Copy to clipboard" data-codetabs-id="{{ $id }}" onclick="copyCodeToClipboardForCodetabs(this)">
3448
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
3549
<path d="M9 43.95q-1.2 0-2.1-.9-.9-.9-.9-2.1V10.8h3v30.15h23.7v3Zm6-6q-1.2 0-2.1-.9-.9-.9-.9-2.1v-28q0-1.2.9-2.1.9-.9 2.1-.9h22q1.2 0 2.1.9.9.9.9 2.1v28q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h22v-28H15v28Zm0 0v-28 28Z"/>
3650
</svg>
3751
<div class="tooltip relative inline-block">
3852
<span class="tooltiptext hidden bg-slate-200 rounded rounded-mx text-slate-800 text-center text-xs p-1 absolute right-6 bottom-4">Copied!</span>
3953
</div>
4054
</button>
41-
{{ if or (ne (index $tab "title") "redis-cli") (lt (index $tab "limit") 100) }}
42-
<button tabindex="1" class="visibility-button text-neutral-400 hover:text-slate-100 bg-slate-600 absolute -top-8 right-2 h-7 w-7 mr-2 mt-2 p-1 rounded rounded-mx"
43-
title="Toggle visibility" aria-controls="{{ $pid }}" onclick="toggleVisibleLines(this)">
44-
<svg id="visibility-off" class="hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
45-
<path d="m31.45 27.05-2.2-2.2q1.3-3.55-1.35-5.9-2.65-2.35-5.75-1.2l-2.2-2.2q.85-.55 1.9-.8 1.05-.25 2.15-.25 3.55 0 6.025 2.475Q32.5 19.45 32.5 23q0 1.1-.275 2.175-.275 1.075-.775 1.875Zm6.45 6.45-2-2q2.45-1.8 4.275-4.025Q42 25.25 42.85 23q-2.5-5.55-7.5-8.775Q30.35 11 24.5 11q-2.1 0-4.3.4-2.2.4-3.45.95L14.45 10q1.75-.8 4.475-1.4Q21.65 8 24.25 8q7.15 0 13.075 4.075Q43.25 16.15 46 23q-1.3 3.2-3.35 5.85-2.05 2.65-4.75 4.65Zm2.9 11.3-8.4-8.25q-1.75.7-3.95 1.075T24 38q-7.3 0-13.25-4.075T2 23q1-2.6 2.775-5.075T9.1 13.2L2.8 6.9l2.1-2.15L42.75 42.6ZM11.15 15.3q-1.85 1.35-3.575 3.55Q5.85 21.05 5.1 23q2.55 5.55 7.675 8.775Q17.9 35 24.4 35q1.65 0 3.25-.2t2.4-.6l-3.2-3.2q-.55.25-1.35.375T24 31.5q-3.5 0-6-2.45T15.5 23q0-.75.125-1.5T16 20.15Zm15.25 7.1Zm-5.8 2.9Z"/>
46-
</svg>
47-
<svg id="visibility-on" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
48-
<path d="M24 31.5q3.55 0 6.025-2.475Q32.5 26.55 32.5 23q0-3.55-2.475-6.025Q27.55 14.5 24 14.5q-3.55 0-6.025 2.475Q15.5 19.45 15.5 23q0 3.55 2.475 6.025Q20.45 31.5 24 31.5Zm0-2.9q-2.35 0-3.975-1.625T18.4 23q0-2.35 1.625-3.975T24 17.4q2.35 0 3.975 1.625T29.6 23q0 2.35-1.625 3.975T24 28.6Zm0 9.4q-7.3 0-13.2-4.15Q4.9 29.7 2 23q2.9-6.7 8.8-10.85Q16.7 8 24 8q7.3 0 13.2 4.15Q43.1 16.3 46 23q-2.9 6.7-8.8 10.85Q31.3 38 24 38Zm0-15Zm0 12q6.05 0 11.125-3.275T42.85 23q-2.65-5.45-7.725-8.725Q30.05 11 24 11t-11.125 3.275Q7.8 17.55 5.1 23q2.7 5.45 7.775 8.725Q17.95 35 24 35Z"/>
49-
</svg>
50-
</button>
51-
{{ end }}
55+
</div>
56+
</div>
57+
58+
<!-- Tab panels -->
59+
{{ range $i, $tab := $tabs }}
60+
{{ $tid := printf "%s_%s" (replace (replace (index $tab "title") "#" "sharp") "." "") $id }}
61+
{{ $pid := printf "panel_%s" $tid }}
62+
{{ $dataLang := replace (or (index $tab "language") "redis-cli") "C#" "dotnet" }}
63+
{{ $dataLang := replace $dataLang "." "-" }}
64+
65+
<div class="panel {{ if ne $i 0 }}panel-hidden{{ end }} w-full mt-0 {{ if not $showFooter}}pb-8{{end}}"
66+
id="{{ $pid }}"
67+
data-lang="{{ $dataLang }}"
68+
role="tabpanel"
69+
tabindex="0"
70+
aria-labelledby="lang-select-{{ $id }}">
71+
{{ index $tab "content" }}
5272

5373
{{ if $showFooter }}
5474
<div class="cli-footer flex items-center justify-between rounded-b-md bg-slate-900 mt-0 px-4 pt-0 mb-0 transition-opacity ease-in-out duration-300 opacity-0 invisible

static/js/codetabs.js

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,71 @@ function copyCodeToClipboard(panelId) {
1111
setTimeout(() => tooltip.style.display = 'none', 1000);
1212
}
1313

14+
function copyCodeToClipboardForCodetabs(button) {
15+
const codetabsId = button.getAttribute('data-codetabs-id');
16+
const codetabsContainer = document.getElementById(codetabsId);
17+
18+
if (!codetabsContainer) return;
19+
20+
// Find the visible panel
21+
const visiblePanel = codetabsContainer.querySelector('.panel:not(.panel-hidden)');
22+
if (!visiblePanel) return;
23+
24+
// Get the code from the visible panel
25+
const code = [...visiblePanel.querySelectorAll('code')].pop().textContent;
26+
navigator.clipboard.writeText(code);
27+
28+
// Toggle tooltip
29+
const tooltip = button.querySelector('.tooltiptext');
30+
if (tooltip) {
31+
tooltip.style.display = 'block';
32+
setTimeout(() => tooltip.style.display = 'none', 1000);
33+
}
34+
}
35+
1436
function toggleVisibleLines(evt) {
1537
document.getElementById(evt.getAttribute('aria-controls'))
1638
.toggleAttribute('aria-expanded');
1739
}
1840

19-
function switchCodeTab(selectedTabInput, tabLang) {
20-
// Synchronize tab selection to relevant page tabs
21-
const trg = document.querySelectorAll(`.codetabs > input[data-lang=${tabLang}]`);
22-
trg.forEach((element) => {
23-
if (element === selectedTabInput) return;
24-
element.checked = true;
41+
function toggleVisibleLinesForCodetabs(button) {
42+
const codetabsId = button.getAttribute('data-codetabs-id');
43+
const codetabsContainer = document.getElementById(codetabsId);
44+
45+
if (!codetabsContainer) return;
46+
47+
// Find the visible panel
48+
const visiblePanel = codetabsContainer.querySelector('.panel:not(.panel-hidden)');
49+
if (!visiblePanel) return;
50+
51+
// Toggle aria-expanded attribute
52+
visiblePanel.toggleAttribute('aria-expanded');
53+
54+
// Toggle visibility icons
55+
const iconOn = button.querySelector('.visibility-icon-on');
56+
const iconOff = button.querySelector('.visibility-icon-off');
57+
58+
if (iconOn && iconOff) {
59+
iconOn.classList.toggle('panel-hidden');
60+
iconOff.classList.toggle('panel-hidden');
61+
}
62+
}
63+
64+
function switchCodeTab(selectedDropdown, tabLang) {
65+
// Synchronize dropdown selection to all code tab dropdowns on the page
66+
const allDropdowns = document.querySelectorAll('.codetabs .lang-selector');
67+
allDropdowns.forEach((dropdown) => {
68+
if (dropdown === selectedDropdown) return;
69+
70+
// Find the option with matching data-lang and select it
71+
const options = dropdown.querySelectorAll('option');
72+
options.forEach((option) => {
73+
if (option.value === tabLang) {
74+
dropdown.value = tabLang;
75+
// Trigger panel visibility update for this dropdown's codetabs
76+
updatePanelVisibility(dropdown);
77+
}
78+
});
2579
});
2680

2781
// Persist tab selection
@@ -30,35 +84,70 @@ function switchCodeTab(selectedTabInput, tabLang) {
3084
}
3185
}
3286

87+
function updatePanelVisibility(dropdown) {
88+
const selectedLang = dropdown.value;
89+
const codetabsId = dropdown.getAttribute('data-codetabs-id');
90+
const codetabsContainer = document.getElementById(codetabsId);
91+
92+
if (!codetabsContainer) return;
93+
94+
// Hide all panels in this codetabs container
95+
const panels = codetabsContainer.querySelectorAll('.panel');
96+
panels.forEach((panel) => {
97+
panel.classList.add('panel-hidden');
98+
});
99+
100+
// Show the selected panel
101+
const selectedPanel = Array.from(panels).find(panel =>
102+
panel.getAttribute('data-lang') === selectedLang
103+
);
104+
105+
if (selectedPanel) {
106+
selectedPanel.classList.remove('panel-hidden');
107+
}
108+
}
109+
33110
function onchangeCodeTab(e) {
34-
const selectedTabInput = e.target;
35-
const tabLang = e.target.dataset.lang;
111+
const selectedDropdown = e.target;
112+
const tabLang = e.target.value;
36113
const yPos = e.target.getBoundingClientRect().top;
37114

38-
switchCodeTab(selectedTabInput, tabLang);
115+
// Update visibility for this dropdown's panels
116+
updatePanelVisibility(selectedDropdown);
117+
118+
// Synchronize with other dropdowns
119+
switchCodeTab(selectedDropdown, tabLang);
39120

40121
// Scroll to the source element if it jumped
41122
const yDiff = e.target.getBoundingClientRect().top - yPos;
42123
window.scrollTo(window.scrollX, window.scrollY + yDiff);
43124
}
44125

45-
document.addEventListener('DOMContentLoaded', () => {
46-
// Register tab switch listeners
47-
for (const tvr of document.querySelectorAll('.codetabs > input[type="radio"]')) {
48-
tvr.addEventListener("change", (e) => onchangeCodeTab(e));
49-
}
126+
// Initialize codetabs - script is deferred so DOM is already ready
127+
(function initCodetabs() {
128+
// Register dropdown change listeners
129+
const dropdowns = document.querySelectorAll('.codetabs .lang-selector');
130+
dropdowns.forEach((dropdown) => {
131+
dropdown.addEventListener("change", (e) => onchangeCodeTab(e));
132+
});
50133

51-
// Restore selection
134+
// Restore selection from localStorage
52135
if (window.localStorage) {
53136
const selectedTab = window.localStorage.getItem("selectedCodeTab");
54137
if (selectedTab) {
55-
switchCodeTab(null, selectedTab);
138+
dropdowns.forEach((dropdown) => {
139+
const options = dropdown.querySelectorAll('option');
140+
const matchingOption = Array.from(options).find(opt => opt.value === selectedTab);
141+
if (matchingOption) {
142+
dropdown.value = selectedTab;
143+
updatePanelVisibility(dropdown);
144+
}
145+
});
56146
}
57147
}
58148

59149
// Work around Chroma's tabindex: https://github.com/alecthomas/chroma/issues/731
60150
for (const pre of document.querySelectorAll('.highlight pre')) {
61151
pre.removeAttribute('tabindex');
62152
}
63-
64-
});
153+
})();

0 commit comments

Comments
 (0)