Files
Musify/web-player/src/lib/components/ui/primitives/Tabs.svelte
T
Admin a081b85efc
CI/CD / BackEnd (push) Successful in 1m45s
CI/CD / WebPlayer (push) Successful in 52s
CI/CD / Deploy (push) Skipped
Restore the invitations tab, render library tracks on first paint and re-measure the tab indicator
2026-09-25 02:40:50 +02:00

81 lines
2.1 KiB
Svelte

<script lang="ts">
interface Tab {
href: string;
label: string;
active: boolean;
count?: number;
}
interface Props {
label: string;
tabs: Tab[];
class?: string;
}
let { label, tabs, class: klass = '' }: Props = $props();
let links = $state<Record<string, HTMLAnchorElement | undefined>>({});
let indicator = $state({ x: 0, width: 0 });
let measured = $state(false);
let animated = $state(false);
const activeHref = $derived(tabs.find((tab) => tab.active)?.href);
let nav = $state<HTMLElement>();
function measure() {
const link = activeHref ? links[activeHref] : undefined;
if (!link || link.offsetWidth === 0) return;
indicator = { x: link.offsetLeft, width: link.offsetWidth };
measured = true;
if (!animated) requestAnimationFrame(() => (animated = true));
}
$effect(measure);
$effect(() => {
if (!nav) return;
const observer = new ResizeObserver(() => measure());
observer.observe(nav);
for (const link of Object.values(links)) if (link) observer.observe(link);
return () => observer.disconnect();
});
</script>
<nav
bind:this={nav}
aria-label={label}
class="relative inline-flex max-w-full rounded-full bg-surface-2 p-1 {klass}"
>
{#if measured}
<div
class="absolute inset-y-1 left-0 rounded-full bg-fg ease-snappy {animated
? 'transition-[transform,width] duration-200'
: ''}"
style="width:{indicator.width}px; transform:translateX({indicator.x}px)"
></div>
{/if}
{#each tabs as tab (tab.href)}
<a
bind:this={links[tab.href]}
href={tab.href}
aria-current={tab.active ? 'page' : undefined}
class="relative z-(--z-raised) rounded-full px-4 py-1.5 text-xs font-medium focus-ring transition-colors {tab.active
? 'text-ink'
: 'text-fg-2 hover:text-fg'}"
>
{tab.label}
{#if tab.count}
<span
class="ml-1.5 inline-grid min-w-5 place-items-center rounded-full px-1.5 text-xs tabular-nums {tab.active
? 'bg-ink/15 text-ink'
: 'bg-accent-tint text-accent-soft'}"
aria-label="{tab.count} pendientes"
>
{tab.count}
</span>
{/if}
</a>
{/each}
</nav>