103 lines
2.5 KiB
Svelte
103 lines
2.5 KiB
Svelte
<script lang="ts">
|
|
import { twMerge } from 'tailwind-merge';
|
|
import { setTabsContext, type TabAbstract } from './context';
|
|
import { writable, type Writable } from 'svelte/store';
|
|
|
|
export let buttonClass = '';
|
|
|
|
let tabs: TabAbstract[] = [];
|
|
let selectedTab: Writable<string | null> = writable(null);
|
|
|
|
function setTab(id: string): void {
|
|
const tab = tabs.find((x) => x.id == id);
|
|
if (tab) {
|
|
$selectedTab = tab.id;
|
|
}
|
|
}
|
|
|
|
function registerTab(tab: TabAbstract): void {
|
|
tabs.push(tab);
|
|
tabs = tabs;
|
|
|
|
if (tabs.length === 1) {
|
|
$selectedTab = tabs[0].id;
|
|
}
|
|
}
|
|
|
|
function unregisterTab(id: string): void {
|
|
tabs = tabs.filter((x) => x.id !== id);
|
|
}
|
|
|
|
function setFocus(tab: TabAbstract) {
|
|
const tabEl = document.getElementById(tab.id);
|
|
tabEl?.focus();
|
|
}
|
|
|
|
function onKeyDown(e: KeyboardEvent, tab: TabAbstract) {
|
|
const index = tabs.findIndex((x) => x.id === tab.id);
|
|
if (index === -1) return;
|
|
|
|
switch (e.key) {
|
|
case 'ArrowRight':
|
|
e.preventDefault();
|
|
if (tabs.length - 1 === index) {
|
|
setFocus(tabs[0]);
|
|
} else {
|
|
setFocus(tabs[index + 1]);
|
|
}
|
|
break;
|
|
case 'ArrowLeft':
|
|
e.preventDefault();
|
|
if (index === 0) {
|
|
setFocus(tabs[tabs.length - 1]);
|
|
} else {
|
|
setFocus(tabs[index - 1]);
|
|
}
|
|
break;
|
|
case 'Home':
|
|
e.preventDefault();
|
|
setFocus(tabs[0]);
|
|
break;
|
|
case 'End':
|
|
e.preventDefault();
|
|
setFocus(tabs[tabs.length - 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
setTabsContext({
|
|
register: registerTab,
|
|
unregister: unregisterTab,
|
|
selectedTab: selectedTab
|
|
});
|
|
</script>
|
|
|
|
<div {...$$restProps} class={twMerge('flex flex-col gap-4', $$restProps['class'])}>
|
|
<div
|
|
role="tablist"
|
|
aria-labelledby="tablist"
|
|
class="flex gap-4 overflow-auto rounded-md bg-primary-300 p-4 shadow-inner"
|
|
>
|
|
{#each tabs as tab, i}
|
|
<button
|
|
role="tab"
|
|
type="button"
|
|
id={tab.id}
|
|
tabindex={$selectedTab === tab.id || ($selectedTab === null && i === 0) ? 0 : -1}
|
|
aria-selected={$selectedTab === tab.id}
|
|
aria-controls={tab.id}
|
|
class={twMerge(
|
|
`rounded-md border-2 border-contrast-200 bg-primary-100 px-4 py-2 hover:border-contrast-900 dark:hover:border-accent ${$selectedTab === tab.id ? 'border-contrast-900 dark:border-accent' : ''}`,
|
|
buttonClass
|
|
)}
|
|
on:click={() => setTab(tab.id)}
|
|
on:keydown={(e) => onKeyDown(e, tab)}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<slot />
|
|
</div>
|