...
This commit is contained in:
102
src/lib/components/tabs/Tabs.svelte
Normal file
102
src/lib/components/tabs/Tabs.svelte
Normal file
@@ -0,0 +1,102 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user