Merge pull request #489 from SamR1/calendar-keyboard-navigation

handle keyboard navigation on calendar
This commit is contained in:
Sam 2024-02-04 14:24:43 +01:00 committed by GitHub
commit 37faf06205
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 113 additions and 47 deletions

View File

@ -7,11 +7,11 @@
<link rel="stylesheet" href="/static/css/fork-awesome.min.css"/> <link rel="stylesheet" href="/static/css/fork-awesome.min.css"/>
<link rel="stylesheet" href="/static/css/leaflet.css"/> <link rel="stylesheet" href="/static/css/leaflet.css"/>
<title>FitTrackee</title> <title>FitTrackee</title>
<script type="module" crossorigin src="/static/index-QmYZ8SSf.js"></script> <script type="module" crossorigin src="/static/index-SWko0ePt.js"></script>
<link rel="modulepreload" crossorigin href="/static/charts-_RwsDDkL.js"> <link rel="modulepreload" crossorigin href="/static/charts-_RwsDDkL.js">
<link rel="modulepreload" crossorigin href="/static/maps-ZyuCPqes.js"> <link rel="modulepreload" crossorigin href="/static/maps-ZyuCPqes.js">
<link rel="stylesheet" crossorigin href="/static/css/maps-B7qTrBCW.css"> <link rel="stylesheet" crossorigin href="/static/css/maps-B7qTrBCW.css">
<link rel="stylesheet" crossorigin href="/static/css/index-ZuVoSwX6.css"> <link rel="stylesheet" crossorigin href="/static/css/index-tZL8_Oa6.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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;