Client - improve keyboard navigation

This commit is contained in:
Sam 2023-07-13 12:54:45 +02:00
parent 6653648422
commit 3f672b5e90
11 changed files with 165 additions and 47 deletions

View File

@ -7,7 +7,7 @@
<div class="admin-menu description-list">
<dl>
<dt>
<router-link to="/admin/application">
<router-link id="adminLink" to="/admin/application">
{{ $t('admin.APPLICATION') }}
</router-link>
</dt>
@ -54,7 +54,7 @@
</template>
<script setup lang="ts">
import { capitalize, toRefs, withDefaults } from 'vue'
import { capitalize, onMounted, toRefs, withDefaults } from 'vue'
import AppStatsCards from '@/components/Administration/AppStatsCards.vue'
import Card from '@/components/Common/Card.vue'
@ -69,6 +69,13 @@
})
const { appConfig, appStatistics } = toRefs(props)
onMounted(() => {
const applicationLink = document.getElementById('adminLink')
if (applicationLink) {
applicationLink.focus()
}
})
</script>
<style lang="scss" scoped>

View File

@ -22,8 +22,8 @@
{{ $t('buttons.YES') }}
</button>
<button
tabindex="0"
id="cancel-button"
:tabindex="0"
:id="`${name}-cancel-button`"
class="cancel"
@click="emit('cancelAction')"
>
@ -46,9 +46,11 @@
title: string
message: string
strongMessage?: string | null
name?: string | null
}
const props = withDefaults(defineProps<Props>(), {
strongMessage: () => null,
name: 'modal',
})
const emit = defineEmits(['cancelAction', 'confirmAction'])

View File

@ -138,7 +138,7 @@
function updateDisplayModal(display: boolean) {
displayModal.value = display
if (display) {
const button = document.getElementById('cancel-button')
const button = document.getElementById('modal-cancel-button')
if (button) {
button.focus()
}

View File

@ -1,12 +1,12 @@
<template>
<div class="chart-menu">
<div class="chart-arrow">
<i
class="fa fa-chevron-left"
aria-hidden="true"
@click="emit('arrowClick', true)"
/>
</div>
<button
class="chart-arrow transparent"
@click="emit('arrowClick', true)"
@keydown.enter="emit('arrowClick', true)"
>
<i class="fa fa-chevron-left" aria-hidden="true" />
</button>
<div class="time-frames custom-checkboxes-group">
<div class="time-frames-checkboxes custom-checkboxes">
<div
@ -22,23 +22,30 @@
:checked="selectedTimeFrame === frame"
@input="onUpdateTimeFrame(frame)"
/>
<span>{{ $t(`statistics.TIME_FRAMES.${frame}`) }}</span>
<span
:id="`frame-${frame}`"
:tabindex="0"
role="button"
@keydown.enter="onUpdateTimeFrame(frame)"
>
{{ $t(`statistics.TIME_FRAMES.${frame}`) }}
</span>
</label>
</div>
</div>
</div>
<div class="chart-arrow">
<i
class="fa fa-chevron-right"
aria-hidden="true"
@click="emit('arrowClick', false)"
/>
</div>
<button
class="chart-arrow transparent"
@click="emit('arrowClick', false)"
@keydown.enter="emit('arrowClick', false)"
>
<i class="fa fa-chevron-right" aria-hidden="true" />
</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onMounted, ref } from 'vue'
const emit = defineEmits(['arrowClick', 'timeFrameUpdate'])
@ -49,11 +56,19 @@
selectedTimeFrame.value = timeFrame
emit('timeFrameUpdate', timeFrame)
}
onMounted(() => {
const input = document.getElementById('frame-month')
if (input) {
input.focus()
}
})
</script>
<style lang="scss" scoped>
.chart-menu {
display: flex;
align-items: center;
.chart-arrow,
.time-frames {

View File

@ -11,7 +11,14 @@
:disabled="disabled"
@input="$router.push(getPath(tab))"
/>
<span>{{ $t(`user.PROFILE.TABS.${tab}`) }}</span>
<span
:id="`tab-${tab}`"
:tabindex="0"
role="button"
@keydown.enter="$router.push(getPath(tab))"
>
{{ $t(`user.PROFILE.TABS.${tab}`) }}
</span>
</label>
</div>
</div>
@ -19,7 +26,7 @@
</template>
<script setup lang="ts">
import { toRefs, withDefaults } from 'vue'
import { onMounted, toRefs, withDefaults } from 'vue'
interface Props {
tabs: string[]
@ -33,6 +40,13 @@
const { tabs, selectedTab, disabled } = toRefs(props)
onMounted(() => {
const input = document.getElementById(`tab-${tabs.value[0]}`)
if (input) {
input.focus()
}
})
function getPath(tab: string) {
switch (tab) {
case 'ACCOUNT':

View File

@ -1,8 +1,9 @@
<template>
<div id="workout-card-title">
<div
class="workout-previous workout-arrow"
<button
class="workout-previous workout-arrow transparent"
:class="{ inactive: !workoutObject.previousUrl }"
:disabled="!workoutObject.previousUrl"
:title="
workoutObject.previousUrl
? $t(`workouts.PREVIOUS_${workoutObject.type}`)
@ -15,33 +16,37 @@
"
>
<i class="fa fa-chevron-left" aria-hidden="true" />
</div>
</button>
<div class="workout-card-title">
<SportImage :sport-label="sport.label" :color="sport.color" />
<div class="workout-title-date">
<div class="workout-title" v-if="workoutObject.type === 'WORKOUT'">
<span>{{ workoutObject.title }}</span>
<i
class="fa fa-edit"
aria-hidden="true"
<button
class="transparent icon-button"
@click="
$router.push({
name: 'EditWorkout',
params: { workoutId: workoutObject.workoutId },
})
"
/>
<i
>
<i class="fa fa-edit" aria-hidden="true" />
</button>
<button
v-if="workoutObject.with_gpx"
class="fa fa-download"
aria-hidden="true"
class="transparent icon-button"
@click.prevent="downloadGpx(workoutObject.workoutId)"
/>
<i
class="fa fa-trash"
aria-hidden="true"
@click="emit('displayModal', true)"
/>
>
<i class="fa fa-download" aria-hidden="true" />
</button>
<button
id="delete-workout-button"
class="transparent icon-button"
@click="displayDeleteModal"
>
<i class="fa fa-trash" aria-hidden="true" />
</button>
</div>
<div class="workout-title" v-else>
{{ workoutObject.title }}
@ -69,9 +74,10 @@
</div>
</div>
</div>
<div
class="workout-next workout-arrow"
<button
class="workout-next workout-arrow transparent"
:class="{ inactive: !workoutObject.nextUrl }"
:disabled="!workoutObject.nextUrl"
:title="
workoutObject.nextUrl
? $t(`workouts.NEXT_${workoutObject.type}`)
@ -82,7 +88,7 @@
"
>
<i class="fa fa-chevron-right" aria-hidden="true" />
</div>
</button>
</div>
</template>
@ -119,6 +125,10 @@
gpxLink.click()
})
}
function displayDeleteModal(event: Event & { target: HTMLInputElement }) {
event.target.blur()
emit('displayModal', true)
}
</script>
<style lang="scss" scoped>
@ -166,9 +176,13 @@
}
.fa {
cursor: pointer;
padding: 0 $default-padding * 0.3;
}
.icon-button {
cursor: pointer;
padding: 0;
margin-left: 2px;
}
}
@media screen and (max-width: $small-limit) {

View File

@ -18,12 +18,20 @@
@ready="fitBounds(bounds)"
>
<LControlLayers />
<LControl position="topleft" class="map-control" @click="resetZoom">
<LControl
position="topleft"
class="map-control"
tabindex="0"
role="button"
@click="resetZoom"
>
<i class="fa fa-refresh" aria-hidden="true" />
</LControl>
<LControl
position="topleft"
class="map-control"
tabindex="0"
role="button"
@click="toggleFullscreen"
>
<i

View File

@ -2,10 +2,12 @@
<div class="workout-detail">
<Modal
v-if="displayModal"
name="workout"
:title="$t('common.CONFIRMATION')"
:message="$t('workouts.WORKOUT_DELETION_CONFIRMATION')"
@confirmAction="deleteWorkout(workoutObject.workoutId)"
@cancelAction="updateDisplayModal(false)"
@cancelAction="cancelDelete"
@keydown.esc="cancelDelete"
/>
<Card>
<template #title>
@ -35,6 +37,7 @@
ComputedRef,
Ref,
computed,
nextTick,
ref,
toRefs,
watch,
@ -161,18 +164,50 @@
}
function updateDisplayModal(value: boolean) {
displayModal.value = value
if (displayModal.value) {
nextTick(() => {
const button = document.getElementById('workout-cancel-button')
if (button) {
button.focus()
}
})
}
}
function cancelDelete() {
updateDisplayModal(false)
const button = document.getElementById('delete-workout-button')
if (button) {
button.focus()
}
}
function deleteWorkout(workoutId: string) {
updateDisplayModal(false)
store.dispatch(WORKOUTS_STORE.ACTIONS.DELETE_WORKOUT, {
workoutId: workoutId,
})
}
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
watch(
() => route.params.segmentId,
async (newSegmentId) => {
if (newSegmentId) {
segmentId.value = +newSegmentId
scrollToTop()
}
}
)
watch(
() => route.params.workoutId,
async (workoutId) => {
if (workoutId) {
displayModal.value = false
scrollToTop()
}
}
)

View File

@ -352,8 +352,15 @@
const payloadErrorMessages: Ref<string[]> = ref([])
onMounted(() => {
let element
if (props.workout.id) {
formatWorkoutForm(props.workout)
element = document.getElementById('sport')
} else {
element = document.getElementById('withGpx')
}
if (element) {
element.focus()
}
})

View File

@ -7,6 +7,7 @@
<div class="form-item">
<label> {{ $t('workouts.FROM') }}: </label>
<input
id="from"
name="from"
type="date"
:value="$route.query.from"
@ -185,7 +186,7 @@
</template>
<script setup lang="ts">
import { ComputedRef, computed, toRefs, watch } from 'vue'
import { ComputedRef, computed, toRefs, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { LocationQuery, useRoute, useRouter } from 'vue-router'
@ -216,6 +217,13 @@
)
let params: LocationQuery = Object.assign({}, route.query)
onMounted(() => {
const filter = document.getElementById('from')
if (filter) {
filter.focus()
}
})
function handleFilterChange(event: Event & { target: HTMLInputElement }) {
if (event.target.value === '') {
delete params[event.target.name]

View File

@ -85,14 +85,22 @@ button {
border-color: transparent;
box-shadow: none;
&:hover {
&:hover, &:disabled {
background: transparent;
}
&:hover {
color: var(--app-color);
}
&:enabled:active {
box-shadow: none;
}
&:disabled, &.confirm:disabled {
border-color: transparent;
color: var(--disabled-color);
}
}
&:hover {