133 lines
3.7 KiB
Svelte
133 lines
3.7 KiB
Svelte
|
|
<script lang="ts">
|
||
|
|
import { createEventDispatcher } from 'svelte';
|
||
|
|
|
||
|
|
export let activeTab: 'profiles' | 'triggers' | 'settings' = 'profiles';
|
||
|
|
|
||
|
|
const dispatch = createEventDispatcher<{
|
||
|
|
tabChange: { tab: 'profiles' | 'triggers' | 'settings' }
|
||
|
|
}>();
|
||
|
|
|
||
|
|
// Handle keyboard navigation for sidebar tabs
|
||
|
|
function handleSidebarTabKeydown(event: KeyboardEvent) {
|
||
|
|
// Define the tab order
|
||
|
|
const tabs = ['profiles', 'triggers', 'settings'];
|
||
|
|
const currentIndex = tabs.indexOf(activeTab);
|
||
|
|
|
||
|
|
switch (event.key) {
|
||
|
|
case 'ArrowRight':
|
||
|
|
case 'ArrowDown':
|
||
|
|
// Move to next tab
|
||
|
|
event.preventDefault();
|
||
|
|
const nextIndex = (currentIndex + 1) % tabs.length;
|
||
|
|
dispatch('tabChange', { tab: tabs[nextIndex] as any });
|
||
|
|
document.getElementById(`tab-${tabs[nextIndex]}`)?.focus();
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'ArrowLeft':
|
||
|
|
case 'ArrowUp':
|
||
|
|
// Move to previous tab
|
||
|
|
event.preventDefault();
|
||
|
|
const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
||
|
|
dispatch('tabChange', { tab: tabs[prevIndex] as any });
|
||
|
|
document.getElementById(`tab-${tabs[prevIndex]}`)?.focus();
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'Home':
|
||
|
|
// Move to first tab
|
||
|
|
event.preventDefault();
|
||
|
|
dispatch('tabChange', { tab: tabs[0] as any });
|
||
|
|
document.getElementById(`tab-${tabs[0]}`)?.focus();
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'End':
|
||
|
|
// Move to last tab
|
||
|
|
event.preventDefault();
|
||
|
|
dispatch('tabChange', { tab: tabs[tabs.length - 1] as any });
|
||
|
|
document.getElementById(`tab-${tabs[tabs.length - 1]}`)?.focus();
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'Enter':
|
||
|
|
case ' ':
|
||
|
|
// Activate current tab
|
||
|
|
event.preventDefault();
|
||
|
|
// The click handler will handle the tab change
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
// Let other keys function normally
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<div class="sidebar-tabs" role="tablist" aria-label="Sidebar tabs">
|
||
|
|
<button
|
||
|
|
class="sidebar-tab"
|
||
|
|
id="tab-profiles"
|
||
|
|
role="tab"
|
||
|
|
aria-controls="panel-profiles"
|
||
|
|
aria-selected={activeTab === 'profiles'}
|
||
|
|
class:active={activeTab === 'profiles'}
|
||
|
|
tabindex={activeTab === 'profiles' ? "0" : "-1"}
|
||
|
|
on:click={() => dispatch('tabChange', { tab: 'profiles' })}
|
||
|
|
on:keydown={handleSidebarTabKeydown}>
|
||
|
|
Profiles
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
class="sidebar-tab"
|
||
|
|
id="tab-triggers"
|
||
|
|
role="tab"
|
||
|
|
aria-controls="panel-triggers"
|
||
|
|
aria-selected={activeTab === 'triggers'}
|
||
|
|
class:active={activeTab === 'triggers'}
|
||
|
|
tabindex={activeTab === 'triggers' ? "0" : "-1"}
|
||
|
|
on:click={() => dispatch('tabChange', { tab: 'triggers' })}
|
||
|
|
on:keydown={handleSidebarTabKeydown}>
|
||
|
|
Triggers
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
class="sidebar-tab"
|
||
|
|
id="tab-settings"
|
||
|
|
role="tab"
|
||
|
|
aria-controls="panel-settings"
|
||
|
|
aria-selected={activeTab === 'settings'}
|
||
|
|
class:active={activeTab === 'settings'}
|
||
|
|
tabindex={activeTab === 'settings' ? "0" : "-1"}
|
||
|
|
on:click={() => dispatch('tabChange', { tab: 'settings' })}
|
||
|
|
on:keydown={handleSidebarTabKeydown}>
|
||
|
|
Settings
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.sidebar-tabs {
|
||
|
|
display: flex;
|
||
|
|
border-bottom: 1px solid var(--color-border);
|
||
|
|
}
|
||
|
|
|
||
|
|
.sidebar-tab {
|
||
|
|
flex: 1;
|
||
|
|
padding: 0.5rem;
|
||
|
|
text-align: center;
|
||
|
|
background: none;
|
||
|
|
border: none;
|
||
|
|
cursor: pointer;
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.sidebar-tab.active {
|
||
|
|
color: var(--color-text);
|
||
|
|
border-bottom: 2px solid var(--color-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.sidebar-tab:focus {
|
||
|
|
outline: 2px solid var(--color-primary);
|
||
|
|
outline-offset: -2px;
|
||
|
|
position: relative;
|
||
|
|
z-index: 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
.sidebar-tab:focus:not(:focus-visible) {
|
||
|
|
outline: none;
|
||
|
|
}
|
||
|
|
</style>
|