Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions assets/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -395,34 +395,30 @@ select {
@apply text-redis-red-500;
}

.codetabs .label {
min-width: 2rem;
line-height: 0.852rem;
color: #8CA0AD;
}

.codetabs .label:active {
color: white;
/* Language selector dropdown styling */
.codetabs .lang-selector {
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");
background-repeat: no-repeat;
background-position: right 0.5rem center;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}


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

.codetabs .radiotab:checked + .label {
@apply border-slate-900 not-italic;
@apply bg-slate-600 text-white pt-1 mt-2.5;

/* Panel visibility controlled by JavaScript */
.codetabs .panel.panel-hidden {
display: none !important;
}


.codetabs .radiotab:checked + .label + .panel {
@apply block;
.codetabs .panel:not(.panel-hidden) {
display: block;
}

.codetabs > .panel .highlight > .chroma {
@apply rounded-tl-none m-0;
@apply rounded-t-none m-0;
font-size: 0.875rem;
line-height: 1.25rem;
}
Expand Down
16 changes: 9 additions & 7 deletions layouts/partials/scripts.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
<script>
document.addEventListener("DOMContentLoaded", function () {

// don't run script for tabbed code examples
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')) {
document.querySelectorAll('div.highlight').forEach(block => {
// Add copy buttons to code blocks (but skip those inside codetabs)
document.querySelectorAll('div.highlight').forEach(block => {
// Skip if this code block is inside a codetabs container
if (block.closest('.codetabs')) {
return;
}
// Create a wrapper for the button and message
const wrapper = document.createElement('div');

Expand Down Expand Up @@ -80,10 +83,9 @@
wrapper.appendChild(downloadButton);
}

block.style.position = 'relative';
block.appendChild(wrapper);
});
}
block.style.position = 'relative';
block.appendChild(wrapper);
});
});
</script>

Expand Down
88 changes: 54 additions & 34 deletions layouts/partials/tabs/wrapper.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,67 @@
{{ $showFooter := (.showFooter) }}

<!-- spellchecker-disable -->
<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 }}">
{{ range $i, $tab := $tabs }}
{{ $tid := printf "%s_%s" (replace (replace (index $tab "title") "#" "sharp") "." "") $id }}
{{ $pid := printf "panel_%s" $tid }}
{{ $dataLang := replace (or (index $tab "language") "redis-cli") "C#" "dotnet" }}
{{ $dataLang := replace $dataLang "." "-" }}
<input tabindex="1" type="radio" name="{{ $id }}" id="{{ $tid }}" data-lang="{{ $dataLang }}" class="radiotab w-0 h-0"
{{ if eq $i 0 }}checked{{ end }}
/>
<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
hover:text-redis-red-600 rounded rounded-mx transition duration-150 ease-in-out"
title="Open example" for="{{ $tid }}">
{{ if eq (index $tab "title") "redis-cli" }}
{{ or (index $tab "displayName") $cliName }}
{{ else }}
{{ index $tab "title" }}
{{ end }}
</label>

<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 }}">
{{ index $tab "content" }}
<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"
title="Copy to clipboard" onclick="copyCodeToClipboard('#{{ $pid }}')">
<div class="codetabs cli group box-border rounded-lg mt-0 mb-0 mx-auto bg-slate-900" id="{{ $id }}">
<!-- Language selector dropdown with controls -->
<div class="codetabs-header flex items-center justify-between px-4 py-2 bg-slate-900 rounded-t-lg">
<div class="flex items-center flex-1">
<label for="lang-select-{{ $id }}" class="text-xs text-slate-400 mr-3 whitespace-nowrap">Language:</label>
<select id="lang-select-{{ $id }}"
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
hover:bg-slate-600 focus:outline-none
transition duration-150 ease-in-out appearance-none"
data-codetabs-id="{{ $id }}">
{{ range $i, $tab := $tabs }}
{{ $dataLang := replace (or (index $tab "language") "redis-cli") "C#" "dotnet" }}
{{ $dataLang := replace $dataLang "." "-" }}
{{ $displayName := "" }}
{{ if eq (index $tab "title") "redis-cli" }}
{{ $displayName = or (index $tab "displayName") $cliName }}
{{ else }}
{{ $displayName = index $tab "title" }}
{{ end }}
<option value="{{ $dataLang }}" data-index="{{ $i }}" {{ if eq $i 0 }}selected{{ end }}>
{{ $displayName }}
</option>
{{ end }}
</select>
</div>
<div class="flex items-center gap-2">
<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"
title="Toggle visibility" data-codetabs-id="{{ $id }}" onclick="toggleVisibleLinesForCodetabs(this)">
<svg class="visibility-icon-on" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
<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"/>
</svg>
<svg class="visibility-icon-off hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
<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"/>
</svg>
</button>
<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"
title="Copy to clipboard" data-codetabs-id="{{ $id }}" onclick="copyCodeToClipboardForCodetabs(this)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
<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"/>
</svg>
<div class="tooltip relative inline-block">
<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>
</div>
</button>
{{ if or (ne (index $tab "title") "redis-cli") (lt (index $tab "limit") 100) }}
<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"
title="Toggle visibility" aria-controls="{{ $pid }}" onclick="toggleVisibleLines(this)">
<svg id="visibility-off" class="hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
<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"/>
</svg>
<svg id="visibility-on" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
<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"/>
</svg>
</button>
{{ end }}
</div>
</div>

<!-- Tab panels -->
{{ range $i, $tab := $tabs }}
{{ $tid := printf "%s_%s" (replace (replace (index $tab "title") "#" "sharp") "." "") $id }}
{{ $pid := printf "panel_%s" $tid }}
{{ $dataLang := replace (or (index $tab "language") "redis-cli") "C#" "dotnet" }}
{{ $dataLang := replace $dataLang "." "-" }}

<div class="panel {{ if ne $i 0 }}panel-hidden{{ end }} w-full mt-0 {{ if not $showFooter}}pb-8{{end}}"
id="{{ $pid }}"
data-lang="{{ $dataLang }}"
role="tabpanel"
tabindex="0"
aria-labelledby="lang-select-{{ $id }}">
{{ index $tab "content" }}

{{ if $showFooter }}
<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
Expand Down
125 changes: 107 additions & 18 deletions static/js/codetabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,71 @@ function copyCodeToClipboard(panelId) {
setTimeout(() => tooltip.style.display = 'none', 1000);
}

function copyCodeToClipboardForCodetabs(button) {
const codetabsId = button.getAttribute('data-codetabs-id');
const codetabsContainer = document.getElementById(codetabsId);

if (!codetabsContainer) return;

// Find the visible panel
const visiblePanel = codetabsContainer.querySelector('.panel:not(.panel-hidden)');
if (!visiblePanel) return;

// Get the code from the visible panel
const code = [...visiblePanel.querySelectorAll('code')].pop().textContent;
navigator.clipboard.writeText(code);

// Toggle tooltip
const tooltip = button.querySelector('.tooltiptext');
if (tooltip) {
tooltip.style.display = 'block';
setTimeout(() => tooltip.style.display = 'none', 1000);
}
}

function toggleVisibleLines(evt) {
document.getElementById(evt.getAttribute('aria-controls'))
.toggleAttribute('aria-expanded');
}

function switchCodeTab(selectedTabInput, tabLang) {
// Synchronize tab selection to relevant page tabs
const trg = document.querySelectorAll(`.codetabs > input[data-lang=${tabLang}]`);
trg.forEach((element) => {
if (element === selectedTabInput) return;
element.checked = true;
function toggleVisibleLinesForCodetabs(button) {
const codetabsId = button.getAttribute('data-codetabs-id');
const codetabsContainer = document.getElementById(codetabsId);

if (!codetabsContainer) return;

// Find the visible panel
const visiblePanel = codetabsContainer.querySelector('.panel:not(.panel-hidden)');
if (!visiblePanel) return;

// Toggle aria-expanded attribute
visiblePanel.toggleAttribute('aria-expanded');

// Toggle visibility icons
const iconOn = button.querySelector('.visibility-icon-on');
const iconOff = button.querySelector('.visibility-icon-off');

if (iconOn && iconOff) {
iconOn.classList.toggle('panel-hidden');
iconOff.classList.toggle('panel-hidden');
}
}

function switchCodeTab(selectedDropdown, tabLang) {
// Synchronize dropdown selection to all code tab dropdowns on the page
const allDropdowns = document.querySelectorAll('.codetabs .lang-selector');
allDropdowns.forEach((dropdown) => {
if (dropdown === selectedDropdown) return;

// Find the option with matching data-lang and select it
const options = dropdown.querySelectorAll('option');
options.forEach((option) => {
if (option.value === tabLang) {
dropdown.value = tabLang;
// Trigger panel visibility update for this dropdown's codetabs
updatePanelVisibility(dropdown);
}
});
});

// Persist tab selection
Expand All @@ -30,35 +84,70 @@ function switchCodeTab(selectedTabInput, tabLang) {
}
}

function updatePanelVisibility(dropdown) {
const selectedLang = dropdown.value;
const codetabsId = dropdown.getAttribute('data-codetabs-id');
const codetabsContainer = document.getElementById(codetabsId);

if (!codetabsContainer) return;

// Hide all panels in this codetabs container
const panels = codetabsContainer.querySelectorAll('.panel');
panels.forEach((panel) => {
panel.classList.add('panel-hidden');
});

// Show the selected panel
const selectedPanel = Array.from(panels).find(panel =>
panel.getAttribute('data-lang') === selectedLang
);

if (selectedPanel) {
selectedPanel.classList.remove('panel-hidden');
}
}

function onchangeCodeTab(e) {
const selectedTabInput = e.target;
const tabLang = e.target.dataset.lang;
const selectedDropdown = e.target;
const tabLang = e.target.value;
const yPos = e.target.getBoundingClientRect().top;

switchCodeTab(selectedTabInput, tabLang);
// Update visibility for this dropdown's panels
updatePanelVisibility(selectedDropdown);

// Synchronize with other dropdowns
switchCodeTab(selectedDropdown, tabLang);

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

document.addEventListener('DOMContentLoaded', () => {
// Register tab switch listeners
for (const tvr of document.querySelectorAll('.codetabs > input[type="radio"]')) {
tvr.addEventListener("change", (e) => onchangeCodeTab(e));
}
// Initialize codetabs - script is deferred so DOM is already ready
(function initCodetabs() {
// Register dropdown change listeners
const dropdowns = document.querySelectorAll('.codetabs .lang-selector');
dropdowns.forEach((dropdown) => {
dropdown.addEventListener("change", (e) => onchangeCodeTab(e));
});

// Restore selection
// Restore selection from localStorage
if (window.localStorage) {
const selectedTab = window.localStorage.getItem("selectedCodeTab");
if (selectedTab) {
switchCodeTab(null, selectedTab);
dropdowns.forEach((dropdown) => {
const options = dropdown.querySelectorAll('option');
const matchingOption = Array.from(options).find(opt => opt.value === selectedTab);
if (matchingOption) {
dropdown.value = selectedTab;
updatePanelVisibility(dropdown);
}
});
}
}

// Work around Chroma's tabindex: https://github.com/alecthomas/chroma/issues/731
for (const pre of document.querySelectorAll('.highlight pre')) {
pre.removeAttribute('tabindex');
}

});
})();