fix: a11y — tab panels v-show, radio roving-tabindex, table header label
This commit is contained in:
parent
2070b4c3b0
commit
789291e912
3 changed files with 31 additions and 5 deletions
|
|
@ -70,7 +70,9 @@
|
|||
<caption class="sr-only">Source health — last 24 hours</caption>
|
||||
<thead class="bg-surface-raised border-b border-surface-border">
|
||||
<tr>
|
||||
<th scope="col" class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider w-4"></th>
|
||||
<th scope="col" class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider w-4">
|
||||
<span class="sr-only">Status</span>
|
||||
</th>
|
||||
<th scope="col" class="text-left px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider">Source</th>
|
||||
<th scope="col" class="text-right px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider">Events</th>
|
||||
<th scope="col" class="text-right px-4 py-2.5 text-text-dim font-medium text-xs uppercase tracking-wider">Errors</th>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
:aria-selected="activeTab === t.key"
|
||||
:id="`tab-${t.key}`"
|
||||
:aria-controls="`tabpanel-${t.key}`"
|
||||
:tabindex="activeTab === t.key ? 0 : -1"
|
||||
@click="activeTab = t.key"
|
||||
:class="[
|
||||
'px-4 py-2 text-sm transition-colors border-b-2 -mb-px',
|
||||
|
|
@ -29,20 +30,22 @@
|
|||
|
||||
<!-- Quick tab panel -->
|
||||
<div
|
||||
v-if="activeTab === 'quick'"
|
||||
v-show="activeTab === 'quick'"
|
||||
role="tabpanel"
|
||||
id="tabpanel-quick"
|
||||
aria-labelledby="tab-quick"
|
||||
tabindex="0"
|
||||
>
|
||||
<QuickCapture />
|
||||
</div>
|
||||
|
||||
<!-- Structured tab panel -->
|
||||
<div
|
||||
v-else
|
||||
v-show="activeTab === 'structured'"
|
||||
role="tabpanel"
|
||||
id="tabpanel-structured"
|
||||
aria-labelledby="tab-structured"
|
||||
tabindex="0"
|
||||
>
|
||||
<IncidentForm @created="onCreated" />
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@
|
|||
</p>
|
||||
<div role="radiogroup" aria-labelledby="entry-point-label" class="flex gap-3">
|
||||
<button
|
||||
v-for="opt in entryPointOptions"
|
||||
v-for="(opt, idx) in entryPointOptions"
|
||||
:key="opt.value"
|
||||
:ref="(el) => collectEntryRef(el, idx)"
|
||||
role="radio"
|
||||
:aria-checked="prefs.entry_point_style === opt.value"
|
||||
:tabindex="prefs.entry_point_style === opt.value ? 0 : -1"
|
||||
@click="setEntryPoint(opt.value as 'topbar' | 'fab')"
|
||||
@keydown="handleEntryPointKey($event, idx)"
|
||||
:class="[
|
||||
'flex-1 px-4 py-3 rounded border text-sm transition-colors text-left',
|
||||
prefs.entry_point_style === opt.value
|
||||
|
|
@ -186,7 +189,8 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
|
||||
const BASE = import.meta.env.BASE_URL.replace(/\/$/, '')
|
||||
|
||||
|
|
@ -210,12 +214,29 @@ const saveStatus = ref<{ ok: boolean; msg: string } | null>(null)
|
|||
const showAddOverride = ref(false)
|
||||
const showApiKey = ref(false)
|
||||
const newRule = ref<SeverityOverride>({ name: '', pattern: '', override_severity: 'WARN', enabled: true })
|
||||
const entryPointBtnRefs = ref<HTMLButtonElement[]>([])
|
||||
|
||||
const entryPointOptions = [
|
||||
{ value: 'topbar', label: 'Top bar', desc: 'Persistent input bar below the nav on every page' },
|
||||
{ value: 'fab', label: 'FAB', desc: 'Floating action button in the bottom-right corner' },
|
||||
]
|
||||
|
||||
function collectEntryRef(el: any, idx: number) {
|
||||
if (el instanceof HTMLButtonElement) entryPointBtnRefs.value[idx] = el
|
||||
}
|
||||
|
||||
function handleEntryPointKey(e: KeyboardEvent, idx: number) {
|
||||
let next = idx
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') next = idx + 1
|
||||
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') next = idx - 1
|
||||
else return
|
||||
e.preventDefault()
|
||||
const clamped = Math.max(0, Math.min(entryPointOptions.length - 1, next))
|
||||
setEntryPoint(entryPointOptions[clamped]!.value as 'topbar' | 'fab')
|
||||
const nextBtn = entryPointBtnRefs.value[clamped]
|
||||
if (nextBtn) nextBtn.focus()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE}/api/settings`)
|
||||
|
|
|
|||
Loading…
Reference in a new issue