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> </template>
</v-tooltip> </v-tooltip>
</template> </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-subheader class="text-subtitle-1 py-2">顯示設定</v-list-subheader>
<v-list-item> <v-list-item>
<v-switch <v-switch
@@ -1,154 +1,162 @@
<template> <template>
<v-list v-model:opened="openedModel" color="primary" density="compact" prepend-gap="8"> <nav aria-label="sidebar navigation">
<template v-for="item in menuItems" :key="item.path ?? item.title"> <v-list
<v-list-group role="none"
v-if="item.subItems?.length" v-model:opened="openedModel"
:id="getGroupId(item)" color="primary"
:value="getGroupValue(item)" density="compact"
> prepend-gap="8"
<template #activator="{ props: activatorProps }"> >
<v-list-item <template v-for="item in menuItems" :key="item.path ?? item.title">
v-bind="isShrink ? undefined : activatorProps" <v-list-group
role="listitem" v-if="item.subItems?.length"
:aria-selected="undefined" :id="getGroupId(item)"
:class="{ 'px-0': isShrink }" :value="getGroupValue(item)"
:link="isNavigable(item) && !!item.path" >
:to="isNavigable(item) ? item.path : undefined" <template #activator="{ props: activatorProps }">
@click="emitSelect(item)" <v-list-item
> v-bind="isShrink ? undefined : activatorProps"
<template #prepend> role="button"
<v-icon v-if="item.icon" size="20" :icon="item.icon" /> :aria-selected="undefined"
<v-btn :class="{ 'px-0': isShrink }"
v-if="isShrink && !item.icon" :link="isNavigable(item) && !!item.path"
class="" :to="isNavigable(item) ? item.path : undefined"
rounded @click="emitSelect(item)"
size="36" >
spaced="start" <template #prepend>
variant="text" <v-icon v-if="item.icon" size="20" :icon="item.icon" />
>{{ item.title?.charAt(0) }}</v-btn <v-btn
> v-if="isShrink && !item.icon"
</template> class=""
<template #title> rounded
<span v-if="!isShrink" class="text-body-2 nav-text-overflow">{{ item.title }}</span> size="36"
</template> spaced="start"
<template #append> variant="text"
<v-chip >{{ item.title?.charAt(0) }}</v-btn
v-if="!isShrink && getItemCount(item) > 0" >
class="menu-count" </template>
color="secondary" <template #title>
size="x-small" <span v-if="!isShrink" class="text-body-2 nav-text-overflow">{{ item.title }}</span>
variant="tonal" </template>
> <template #append>
{{ getItemCount(item) }} <v-chip
</v-chip> v-if="!isShrink && getItemCount(item) > 0"
</template> class="menu-count"
</v-list-item> color="secondary"
</template> 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-list-item
v-bind="subProps" v-for="subSubItem in subItem.subItems"
role="listitem" :key="subSubItem.path ?? subSubItem.title"
:aria-selected="undefined" :link="!!subSubItem.path"
:link="isNavigable(subItem)" :prepend-icon="mdiCircleSmall"
:prepend-icon="subItem.icon || mdiMenuRight" :to="subSubItem.path"
:to="isNavigable(subItem) ? subItem.path : undefined" @click="emitSelect(subSubItem)"
@click="emitSelect(subItem)"
> >
<template #title> <template #title>
<span class="text-body-2 nav-text-overflow">{{ subItem.title }}</span> <v-tooltip location="end" :text="subSubItem.title" :aria-label="subSubItem.title">
</template> <template #activator="{ props: tooltipProps }">
<template #append> <span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
<v-chip subSubItem.title
v-if="getItemCount(subItem) > 0" }}</span>
class="menu-count" </template>
color="secondary" </v-tooltip>
size="x-small"
variant="tonal"
>
{{ getItemCount(subItem) }}
</v-chip>
</template> </template>
</v-list-item> </v-list-item>
</template> </v-list-group>
<v-list-item <v-list-item
v-for="subSubItem in subItem.subItems" v-else
:key="subSubItem.path ?? subSubItem.title" :link="!!subItem.path"
:link="!!subSubItem.path" :prepend-icon="subItem.icon || mdiMenuRight"
:prepend-icon="mdiCircleSmall" :to="subItem.path"
:to="subSubItem.path" @click="emitSelect(subItem)"
@click="emitSelect(subSubItem)"
> >
<template #title> <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 }"> <template #activator="{ props: tooltipProps }">
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{ <span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
subSubItem.title subItem.title
}}</span> }}</span>
</template> </template>
</v-tooltip> </v-tooltip>
</template> </template>
</v-list-item> </v-list-item>
</v-list-group> </template>
</v-list-group>
<v-list-item <v-list-item
v-else v-else
:link="!!subItem.path" :class="{ 'px-0': isShrink }"
:prepend-icon="subItem.icon || mdiMenuRight" :link="!!item.path"
:to="subItem.path" :to="item.path"
@click="emitSelect(subItem)" @click="emitSelect(item)"
> >
<template #title> <template #prepend>
<v-tooltip location="end" :text="subItem.title" :aria-label="subItem.title"> <v-icon v-if="item.icon" size="20" :icon="item.icon" />
<template #activator="{ props: tooltipProps }"> <v-btn
<span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{ v-if="isShrink && !item.icon"
subItem.title class=""
}}</span> rounded
</template> size="36"
</v-tooltip> spaced="start"
</template> variant="text"
</v-list-item> >{{ item.title?.charAt(0) }}</v-btn
</template> >
</v-list-group> </template>
<template #title>
<v-list-item <v-tooltip v-if="!isShrink" location="end" :text="item.title" :aria-label="item.title">
v-else <template #activator="{ props: tooltipProps }">
:class="{ 'px-0': isShrink }" <span v-bind="tooltipProps" class="text-body-2 nav-text-overflow">{{
:link="!!item.path" item.title
:to="item.path" }}</span>
@click="emitSelect(item)" </template>
> </v-tooltip>
<template #prepend> </template>
<v-icon v-if="item.icon" size="20" :icon="item.icon" /> </v-list-item>
<v-btn </template>
v-if="isShrink && !item.icon" </v-list>
class="" </nav>
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>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -1,6 +1,6 @@
<template> <template>
<v-sheet class="mobile-favorites-panel d-flex flex-column" color="surface"> <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-list-item
v-for="item in favoriteItems" v-for="item in favoriteItems"
:key="item.path ?? item.title" :key="item.path ?? item.title"
@@ -1,6 +1,6 @@
<template> <template>
<v-sheet class="mobile-menu-panel d-flex flex-column" color="surface"> <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-list-item
v-for="item in mobileCurrentItems" v-for="item in mobileCurrentItems"
:key="item.path ?? item.title" :key="item.path ?? item.title"
+1 -1
View File
@@ -20,7 +20,7 @@
variant="text" variant="text"
></v-btn> ></v-btn>
</template> </template>
<v-list density="compact"> <v-list role="none" density="compact">
<v-list-item <v-list-item
v-for="localeOption in localeOptions" v-for="localeOption in localeOptions"
:key="localeOption" :key="localeOption"