Client - handle keyboard navigation on workouts displayed on calendar
This commit is contained in:
parent
f94f0073b5
commit
ebb3fb91de
@ -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') }}
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user