Sliding Tabs
Navigation @bhaskar Stars:0 Views:0
"use client";
import { useLayoutEffect, useRef, useState, type KeyboardEvent } from "react";
const css = `
.tb{position:relative;display:inline-flex;gap:2px;padding:4px;border:1px solid rgba(127,127,127,.3);border-radius:999px;background:rgba(127,127,127,.08)}
.tb button{position:relative;z-index:1;padding:9px 16px;border:0;border-radius:999px;background:none;color:#5c627c;font:600 14px/1 system-ui,sans-serif;cursor:pointer;transition:color .25s}
.tb button[aria-selected=true]{color:#0b1020}
.tb button:focus-visible{outline:2px solid #1659f5;outline-offset:2px}
.tb__pill{position:absolute;top:4px;bottom:4px;left:0;border-radius:999px;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.15);transition:transform .4s cubic-bezier(.3,1.3,.5,1),width .4s cubic-bezier(.3,1.3,.5,1)}
@media (prefers-reduced-motion:reduce){.tb__pill{transition:none}}`;
export function SlidingTabs({ tabs, value, onChange, label = "Views" }: { tabs: string[]; value?: number; onChange?: (i: number) => void; label?: string }) {
const [inner, setInner] = useState(0);
const active = value ?? inner;
const refs = useRef<(HTMLButtonElement | null)[]>([]);
const [pill, setPill] = useState({ x: 0, w: 0 });
useLayoutEffect(() => {
const t = refs.current[active];
if (t) setPill({ x: t.offsetLeft, w: t.offsetWidth });
}, [active, tabs]);
const pick = (i: number, focus = false) => { setInner(i); onChange?.(i); if (focus) refs.current[i]?.focus(); };
const key = (e: KeyboardEvent, i: number) => {
const d = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0;
if (d) { e.preventDefault(); pick((i + d + tabs.length) % tabs.length, true); }
};
return (
<>
<style>{css}</style>
<div className="tb" role="tablist" aria-label={label}>
<span className="tb__pill" aria-hidden="true" style={{ width: pill.w, transform: `translateX(${pill.x}px)` }} />
{tabs.map((t, i) => (
<button key={t} ref={(el) => { refs.current[i] = el; }} role="tab" aria-selected={i === active} tabIndex={i === active ? 0 : -1} onClick={() => pick(i)} onKeyDown={(e) => key(e, i)}>{t}</button>
))}
</div>
</>
);
}
export default SlidingTabs;