feat: 為列表元件添加角色屬性以改善可訪問性

This commit is contained in:
skytek_xinliang
2026-03-31 15:03:22 +08:00
parent 8dbae6c614
commit da96d64f75
5 changed files with 144 additions and 136 deletions
@@ -136,7 +136,7 @@
</template>
</v-tooltip>
</template>
<v-list density="compact" width="180">
<v-list role="none" density="compact" width="180">
<v-list-subheader class="text-subtitle-1 py-2">顯示設定</v-list-subheader>
<v-list-item>
<v-switch
@@ -1,154 +1,162 @@
<template>
<v-list v-model:opened="openedModel" color="primary" density="compact" prepend-gap="8">
<template v-for="item in menuItems" :key="item.path ?? item.title">
<v-list-group
v-if="item.subItems?.length"
:id="getGroupId(item)"
:value="getGroupValue(item)"
>
<template #activator="{ props: activatorProps }">
<v-list-item
v-bind="isShrink ? undefined : activatorProps"
role="listitem"
:aria-selected="undefined"
:class="{ 'px-0': isShrink }"
:link="isNavigable(item) && !!item.path"
:to="isNavigable(item) ? item.path : undefined"
@click="emitSelect(item)"
>
<template #prepend>
<v-icon v-if="item.icon" size="20" :icon="item.icon" />
<v-btn
v-if="isShrink && !item.icon"
class=""
rounded
size="36"
spaced="start"
variant="text"
>{{ item.title?.charAt(0) }}</v-btn
>
</template>
<template #title>
<span v-if="!isShrink" class="text-body-2 nav-text-overflow">{{ item.title }}</span>
</template>
<template #append>
<v-chip
v-if="!isShrink && getItemCount(item) > 0"
class="menu-count"
color="secondary"
size="x-small"
variant="tonal"
>
{{ getItemCount(item) }}
</v-chip>
</template>
</v-list-item>
</template>
<nav aria-label="sidebar navigation">
<v-list
role="none"
v-model:opened="openedModel"
color="primary"
density="compact"
prepend-gap="8"
>
<template v-for="item in menuItems" :key="item.path ?? item.title">
<v-list-group
v-if="item.subItems?.length"
:id="getGroupId(item)"
:value="getGroupValue(item)"
>
<template #activator="{ props: activatorProps }">
<v-list-item
v-bind="isShrink ? undefined : activatorProps"
role="button"
:aria-selected="undefined"
:class="{ 'px-0': isShrink }"
:link="isNavigable(item) && !!item.path"
:to="isNavigable(item) ? item.path : undefined"
@click="emitSelect(item)"
>
<template #prepend>
<v-icon v-if="item.icon" size="20" :icon="item.icon" />
<v-btn
v-if="isShrink && !item.icon"
class=""
rounded
size="36"
spaced="start"
variant="text"
>{{ item.title?.charAt(0) }}</v-btn
>
</template>
<template #title>
<span v-if="!isShrink" class="text-body-2 nav-text-overflow">{{ item.title }}</span>
</template>
<template #append>
<v-chip
v-if="!isShrink && getItemCount(item) > 0"
class="menu-count"
color="secondary"
size="x-small"
variant="tonal"
>
{{ getItemCount(item) }}
</v-chip>
</template>
</v-list-item>
</template>
<template v-for="subItem in item.subItems" :key="subItem.path ?? subItem.title">
<v-list-group
v-if="subItem.subItems?.length"
:id="getGroupId(subItem, getGroupId(item))"
:value="getGroupValue(subItem, getGroupValue(item))"
>
<template #activator="{ props: subProps }">
<v-list-item
v-bind="subProps"
role="button"
:aria-selected="undefined"
:link="isNavigable(subItem)"
:prepend-icon="subItem.icon || mdiMenuRight"
:to="isNavigable(subItem) ? subItem.path : undefined"
@click="emitSelect(subItem)"
>
<template #title>
<span class="text-body-2 nav-text-overflow">{{ subItem.title }}</span>
</template>
<template #append>
<v-chip
v-if="getItemCount(subItem) > 0"
class="menu-count"
color="secondary"
size="x-small"
variant="tonal"
>
{{ getItemCount(subItem) }}
</v-chip>
</template>
</v-list-item>
</template>
<template v-for="subItem in item.subItems" :key="subItem.path ?? subItem.title">
<v-list-group
v-if="subItem.subItems?.length"
:id="getGroupId(subItem, getGroupId(item))"
:value="getGroupValue(subItem, getGroupValue(item))"
>
<template #activator="{ props: subProps }">
<v-list-item
v-bind="subProps"
role="listitem"
:aria-selected="undefined"
:link="isNavigable(subItem)"
:prepend-icon="subItem.icon || mdiMenuRight"
:to="isNavigable(subItem) ? subItem.path : undefined"
@click="emitSelect(subItem)"
v-for="subSubItem in subItem.subItems"
:key="subSubItem.path ?? subSubItem.title"
:link="!!subSubItem.path"
:prepend-icon="mdiCircleSmall"
:to="subSubItem.path"
@click="emitSelect(subSubItem)"
>
<template #title>
<span class="text-body-2 nav-text-overflow">{{ subItem.title }}</span>
</template>
<template #append>
<v-chip
v-if="getItemCount(subItem) > 0"
class="menu-count"
color="secondary"
size="x-small"
variant="tonal"
>
{{ getItemCount(subItem) }}
</v-chip>
<v-tooltip location="end" :text="subSubItem.title" :aria-label="subSubItem.title">
<template #activator="{ props: tooltipProps }">
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
subSubItem.title
}}</span>
</template>
</v-tooltip>
</template>
</v-list-item>
</template>
</v-list-group>
<v-list-item
v-for="subSubItem in subItem.subItems"
:key="subSubItem.path ?? subSubItem.title"
:link="!!subSubItem.path"
:prepend-icon="mdiCircleSmall"
:to="subSubItem.path"
@click="emitSelect(subSubItem)"
v-else
:link="!!subItem.path"
:prepend-icon="subItem.icon || mdiMenuRight"
:to="subItem.path"
@click="emitSelect(subItem)"
>
<template #title>
<v-tooltip location="end" :text="subSubItem.title" :aria-label="subSubItem.title">
<v-tooltip location="end" :text="subItem.title" :aria-label="subItem.title">
<template #activator="{ props: tooltipProps }">
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
subSubItem.title
subItem.title
}}</span>
</template>
</v-tooltip>
</template>
</v-list-item>
</v-list-group>
</template>
</v-list-group>
<v-list-item
v-else
:link="!!subItem.path"
:prepend-icon="subItem.icon || mdiMenuRight"
:to="subItem.path"
@click="emitSelect(subItem)"
>
<template #title>
<v-tooltip location="end" :text="subItem.title" :aria-label="subItem.title">
<template #activator="{ props: tooltipProps }">
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
subItem.title
}}</span>
</template>
</v-tooltip>
</template>
</v-list-item>
</template>
</v-list-group>
<v-list-item
v-else
:class="{ 'px-0': isShrink }"
:link="!!item.path"
:to="item.path"
@click="emitSelect(item)"
>
<template #prepend>
<v-icon v-if="item.icon" size="20" :icon="item.icon" />
<v-btn
v-if="isShrink && !item.icon"
class=""
rounded
size="36"
spaced="start"
variant="text"
>{{ item.title?.charAt(0) }}</v-btn
>
</template>
<template #title>
<v-tooltip v-if="!isShrink" location="end" :text="item.title" :aria-label="item.title">
<template #activator="{ props: tooltipProps }">
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
item.title
}}</span>
</template>
</v-tooltip>
</template>
</v-list-item>
</template>
</v-list>
<v-list-item
v-else
:class="{ 'px-0': isShrink }"
:link="!!item.path"
:to="item.path"
@click="emitSelect(item)"
>
<template #prepend>
<v-icon v-if="item.icon" size="20" :icon="item.icon" />
<v-btn
v-if="isShrink && !item.icon"
class=""
rounded
size="36"
spaced="start"
variant="text"
>{{ item.title?.charAt(0) }}</v-btn
>
</template>
<template #title>
<v-tooltip v-if="!isShrink" location="end" :text="item.title" :aria-label="item.title">
<template #activator="{ props: tooltipProps }">
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
item.title
}}</span>
</template>
</v-tooltip>
</template>
</v-list-item>
</template>
</v-list>
</nav>
</template>
<script setup lang="ts">
@@ -1,6 +1,6 @@
<template>
<v-sheet class="mobile-favorites-panel d-flex flex-column" color="surface">
<v-list class="px-2 py-2 flex-grow-1 overflow-auto" density="comfortable">
<v-list role="none" class="px-2 py-2 flex-grow-1 overflow-auto" density="comfortable">
<v-list-item
v-for="item in favoriteItems"
:key="item.path ?? item.title"
@@ -1,6 +1,6 @@
<template>
<v-sheet class="mobile-menu-panel d-flex flex-column" color="surface">
<v-list class="px-2 py-2 flex-grow-1 overflow-auto" density="comfortable">
<v-list role="none" class="px-2 py-2 flex-grow-1 overflow-auto" density="comfortable">
<v-list-item
v-for="item in mobileCurrentItems"
:key="item.path ?? item.title"