diff --git a/registry/components/tabs-slide-indicator/demo.html b/registry/components/tabs-slide-indicator/demo.html index 99c36446a8..d4afca005f 100644 --- a/registry/components/tabs-slide-indicator/demo.html +++ b/registry/components/tabs-slide-indicator/demo.html @@ -54,6 +54,8 @@ background: #18181b; transform: translateX(var(--hf-tab-x)); box-shadow: 0 12px 26px rgba(0, 0, 0, 0.18); + overflow: hidden; + z-index: 2; } .hf-transition-tabs-slide-indicator button { position: relative; @@ -61,10 +63,19 @@ height: 36px; border: 0; background: transparent; - color: #71717a; + color: #6b6b73; font-weight: 600; } - .hf-transition-tabs-slide-indicator button:nth-of-type(2) { + .hf-transition-tabs-slide-indicator .indicator-labels { + position: absolute; + top: 0; + left: 0; + display: grid; + grid-template-columns: repeat(3, 86px); + transform: translateX(calc(var(--hf-tab-x) * -1)); + pointer-events: none; + } + .hf-transition-tabs-slide-indicator .indicator-labels button { color: #fff; } @@ -88,7 +99,12 @@ { "id": "labels", "type": "string", "role": "content", "label": "Labels", "description": "The tab names, separated by commas. Two or more names rebuild the row.", "default": "Plan,Debug,Ask" } ]' > - @@ -117,6 +133,17 @@ return Object.prototype.hasOwnProperty.call(table, value) ? value : fallback; } + // The mirrored copy is decorative: it repeats a label the row + // already carries, so it stays out of the tab order. + function makeTab(text, mirrored) { + var button = document.createElement("button"); + button.textContent = text; + if (mirrored) { + button.tabIndex = -1; + } + return button; + } + var accent = accents[pick(accents, vars.accent, "ink")]; var width = widths[pick(widths, vars.width, "standard")]; var radius = shapes[pick(shapes, vars.shape, "pill")]; @@ -140,14 +167,18 @@ roots[i].style.setProperty("--hf-tab-radius", radius); // A single tab has nowhere to slide to, so it is left as authored. if (labels.length > 1) { + // Every label exists twice: once in the row and once in the + // clipped copy the pill carries, so both are rebuilt together. + var mirror = roots[i].querySelector(".indicator-labels"); var old = roots[i].querySelectorAll("button"); for (var j = 0; j < old.length; j += 1) { old[j].parentNode.removeChild(old[j]); } for (var k = 0; k < labels.length; k += 1) { - var button = document.createElement("button"); - button.textContent = labels[k]; - roots[i].appendChild(button); + roots[i].appendChild(makeTab(labels[k], false)); + if (mirror) { + mirror.appendChild(makeTab(labels[k], true)); + } } roots[i].style.setProperty("--hf-tab-count", String(labels.length)); } @@ -190,6 +221,8 @@ background: var(--hf-tab-fill, #18181b); transform: translateX(calc(var(--hf-tab-x) * var(--hf-tab-step, 1))); box-shadow: 0 12px 26px rgba(0, 0, 0, 0.18); + overflow: hidden; + z-index: 2; } .hf-transition-tabs-slide-indicator button { position: relative; @@ -197,10 +230,19 @@ height: 36px; border: 0; background: transparent; - color: #71717a; + color: #6b6b73; font-weight: 850; } - .hf-transition-tabs-slide-indicator button:nth-of-type(2) { + .hf-transition-tabs-slide-indicator .indicator-labels { + position: absolute; + top: 0; + left: 0; + display: grid; + grid-template-columns: repeat(var(--hf-tab-count, 3), var(--hf-tab-width, 86px)); + transform: translateX(calc(var(--hf-tab-x) * var(--hf-tab-step, 1) * -1)); + pointer-events: none; + } + .hf-transition-tabs-slide-indicator .indicator-labels button { color: #fff; } diff --git a/registry/components/tabs-slide-indicator/tabs-slide-indicator.html b/registry/components/tabs-slide-indicator/tabs-slide-indicator.html index d472843d4f..3512e5eeeb 100644 --- a/registry/components/tabs-slide-indicator/tabs-slide-indicator.html +++ b/registry/components/tabs-slide-indicator/tabs-slide-indicator.html @@ -39,7 +39,12 @@ { "id": "labels", "type": "string", "role": "content", "label": "Labels", "description": "The tab names, separated by commas. Two or more names rebuild the row.", "default": "Plan,Debug,Ask" } ]' > - + @@ -104,6 +124,17 @@ return Object.prototype.hasOwnProperty.call(table, value) ? value : fallback; } + // The mirrored copy is decorative: it repeats a label the row already + // carries, so it stays out of the tab order. + function makeTab(text, mirrored) { + var button = document.createElement("button"); + button.textContent = text; + if (mirrored) { + button.tabIndex = -1; + } + return button; + } + var accent = accents[pick(accents, vars.accent, "ink")]; var width = widths[pick(widths, vars.width, "standard")]; var radius = shapes[pick(shapes, vars.shape, "pill")]; @@ -127,14 +158,18 @@ roots[i].style.setProperty("--hf-tab-radius", radius); // A single tab has nowhere to slide to, so it is left as authored. if (labels.length > 1) { + // Every label exists twice: once in the row and once in the clipped + // copy the pill carries, so both have to be rebuilt together. + var mirror = roots[i].querySelector(".indicator-labels"); var old = roots[i].querySelectorAll("button"); for (var j = 0; j < old.length; j += 1) { old[j].parentNode.removeChild(old[j]); } for (var k = 0; k < labels.length; k += 1) { - var button = document.createElement("button"); - button.textContent = labels[k]; - roots[i].appendChild(button); + roots[i].appendChild(makeTab(labels[k], false)); + if (mirror) { + mirror.appendChild(makeTab(labels[k], true)); + } } roots[i].style.setProperty("--hf-tab-count", String(labels.length)); }