Client - handle keyboard navigation on workouts displayed on calendar

This commit is contained in:
Sam 2024-02-04 13:40:52 +01:00
parent f94f0073b5
commit ebb3fb91de
5 changed files with 84 additions and 18 deletions

View File

@ -15,6 +15,7 @@
:workouts="filterWorkouts(day, workouts)" :workouts="filterWorkouts(day, workouts)"
:sports="sports" :sports="sports"
:displayHARecord="displayHARecord" :displayHARecord="displayHARecord"
:index="i"
/> />
<div class="calendar-cell-day"> <div class="calendar-cell-day">
{{ format(day, 'd') }} {{ format(day, 'd') }}

View File

@ -1,9 +1,7 @@
<template> <template>
<div <router-link
class="calendar-workout" class="calendar-workout"
@click=" :to="{ name: 'Workout', params: { workoutId: workout.id } }"
$router.push({ name: 'Workout', params: { workoutId: workout.id } })
"
> >
<SportImage <SportImage
:sport-label="sportLabel" :sport-label="sportLabel"
@ -26,7 +24,7 @@
" "
/> />
</sup> </sup>
</div> </router-link>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -21,6 +21,7 @@
:datasets="chartDatasets" :datasets="chartDatasets"
:colors="colors" :colors="colors"
:displayHARecord="displayHARecord" :displayHARecord="displayHARecord"
:index="index"
/> />
</div> </div>
</div> </div>
@ -32,6 +33,7 @@
:datasets="chartDatasets" :datasets="chartDatasets"
:colors="colors" :colors="colors"
:displayHARecord="displayHARecord" :displayHARecord="displayHARecord"
:index="index"
/> />
</div> </div>
</div> </div>
@ -52,10 +54,11 @@
displayHARecord: boolean displayHARecord: boolean
workouts: IWorkout[] workouts: IWorkout[]
sports: ISport[] sports: ISport[]
index: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const { displayHARecord, workouts, sports } = toRefs(props) const { displayHARecord, index, sports, workouts } = toRefs(props)
const chartDatasets = computed(() => getDonutDatasets(props.workouts)) const chartDatasets = computed(() => getDonutDatasets(props.workouts))
const colors = computed(() => sportIdColors(props.sports)) const colors = computed(() => sportIdColors(props.sports))
const displayedWorkoutCount = 6 const displayedWorkoutCount = 6

View File

@ -1,16 +1,22 @@
<template> <template>
<div class="calendar-workouts-chart"> <div class="calendar-workouts-chart">
<div class="workouts-chart" @click="togglePane"> <button
class="workouts-chart transparent"
:id="`workouts-donut-${index}`"
@click="togglePane"
>
<div class="workouts-count">{{ workouts.length }}</div> <div class="workouts-count">{{ workouts.length }}</div>
<DonutChart :datasets="datasets" :colors="colors" /> <DonutChart :datasets="datasets" :colors="colors" />
</div> </button>
<div class="workouts-pane" v-if="!isHidden"> <div class="workouts-pane" v-if="!isHidden">
<div class="more-workouts" v-click-outside="togglePane"> <div
<i class="more-workouts"
class="fa fa-times calendar-more" :id="`workouts-pane-${index}`"
aria-hidden="true" v-click-outside="togglePane"
@click="togglePane" >
/> <button class="calendar-more-close transparent" @click="togglePane">
<i class="fa fa-times" aria-hidden="true" />
</button>
<CalendarWorkout <CalendarWorkout
v-for="(workout, index) in workouts" v-for="(workout, index) in workouts"
:key="index" :key="index"
@ -25,7 +31,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, toRefs } from 'vue' import { nextTick, onMounted, onUnmounted, ref, toRefs } from 'vue'
import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue' import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue'
import DonutChart from '@/components/Dashboard/UserCalendar/DonutChart.vue' import DonutChart from '@/components/Dashboard/UserCalendar/DonutChart.vue'
@ -39,16 +45,71 @@
sports: ISport[] sports: ISport[]
workouts: IWorkout[] workouts: IWorkout[]
displayHARecord: boolean displayHARecord: boolean
index: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()
let tabbableElementIndex = 0
const { colors, datasets, sports, workouts } = toRefs(props) const { colors, datasets, index, sports, workouts } = toRefs(props)
const isHidden = ref(true) const isHidden = ref(true)
function togglePane(event: Event) { function isWorkoutsMorePaneDisplayed() {
const pane = document.getElementById(`workouts-pane-${index.value}`)
return pane?.children && pane?.children.length > 0 ? pane : null
}
async function togglePane(event: Event) {
event.preventDefault()
event.stopPropagation() event.stopPropagation()
isHidden.value = !isHidden.value isHidden.value = !isHidden.value
await nextTick()
const pane = isWorkoutsMorePaneDisplayed()
if (!isHidden.value) {
;(pane?.children[0] as HTMLButtonElement).focus()
} else {
document.getElementById(`workouts-donut-${index.value}`)?.focus()
}
} }
function handleKey(e: KeyboardEvent) {
if (!isHidden.value) {
// focusTrap
if (!isHidden.value && (e.key === 'Tab' || e.keyCode === 9)) {
e.preventDefault()
e.stopPropagation()
const pane = isWorkoutsMorePaneDisplayed()
if (pane) {
if (e.shiftKey) {
tabbableElementIndex -= 1
if (tabbableElementIndex < 0) {
tabbableElementIndex = pane.children.length - 1
}
} else {
tabbableElementIndex += 1
if (tabbableElementIndex >= pane.children.length) {
tabbableElementIndex = 0
}
}
// children are only links or buttons
;(
pane.children[tabbableElementIndex] as
| HTMLButtonElement
| HTMLLinkElement
).focus()
}
}
// close pane on Escape
if (e.key === 'Escape') {
togglePane(e)
}
}
}
onMounted(() => {
document.addEventListener('keydown', handleKey)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKey)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -58,6 +119,7 @@
.workouts-chart { .workouts-chart {
position: relative; position: relative;
padding: 0;
.workouts-count { .workouts-count {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -108,11 +170,12 @@
flex-wrap: wrap; flex-wrap: wrap;
z-index: 1000; z-index: 1000;
.calendar-more { .calendar-more-close {
position: absolute; position: absolute;
font-size: 0.9em; font-size: 0.9em;
top: 5px; top: 5px;
right: 5px; right: 5px;
padding: 0;
} }
} }
} }

View File

@ -95,6 +95,7 @@ button {
&.transparent { &.transparent {
font-family: 'PT Sans', Helvetica, Arial, sans-serif; font-family: 'PT Sans', Helvetica, Arial, sans-serif;
font-size: 1em; font-size: 1em;
background: transparent;
border-color: transparent; border-color: transparent;
box-shadow: none; box-shadow: none;