mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
Issue 11450 Adding keyboard accessibility to task board controls and purchases (#12363)
* intial draft adding keyboard accessibility to task board controls * cleanup * finish adding keyboard accessibility for task dropdown, rewards * add notEnough conditions to disable purchase button * fix(lint): remove console.log from buy modal * add missing comma, use focus-within instead of focus-visible * missed one more focus visible * override browser default focus styling * move focus styling to tasks only * add rounded border * fix element height on focus, rounded borders * fix dropdown margin to avoid element resizing * styling updates on focus * fix spacing around task checklist * fix border around dropdown item * remove spacing that made tasks with notes jump * keep disabled habit control styling when not focused * revert unintended spacing Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
This commit is contained in:
@@ -295,7 +295,10 @@
|
||||
&-control {
|
||||
&-bg { background: $gray-600; }
|
||||
&-inner {
|
||||
border: 1px solid $gray-300;
|
||||
&:not(:focus) {
|
||||
border: 1px solid $gray-300 !important;
|
||||
}
|
||||
|
||||
opacity: 0.75;
|
||||
|
||||
.negative, .positive {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
v-if="withPin"
|
||||
class="badge-dialog"
|
||||
@click.prevent.stop="togglePinned()"
|
||||
@keypress.enter.prevent.stop="togglePinned()"
|
||||
tabindex="0"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isPinned"
|
||||
@@ -18,6 +20,8 @@
|
||||
class="svg-icon icon-12 close-icon"
|
||||
aria-hidden="true"
|
||||
@click="hideDialog()"
|
||||
@keypress.enter="hideDialog()"
|
||||
tabindex="0"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
</div>
|
||||
@@ -145,10 +149,13 @@
|
||||
v-else
|
||||
class="btn btn-primary"
|
||||
:disabled="item.key === 'gem' && gemsLeft === 0 ||
|
||||
attemptingToPurchaseMoreGemsThanAreLeft || numberInvalid || item.locked"
|
||||
attemptingToPurchaseMoreGemsThanAreLeft || numberInvalid || item.locked ||
|
||||
!preventHealthPotion ||
|
||||
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
:class="{'notEnough': !preventHealthPotion ||
|
||||
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||
@click="buyItem()"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ $t('buyNow') }}
|
||||
</button>
|
||||
@@ -289,6 +296,10 @@
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
min-width: 6rem;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
.balance {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
:id="itemId"
|
||||
class="item-wrapper"
|
||||
@click="click()"
|
||||
@keypress.enter="click()"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="item"
|
||||
@@ -63,7 +65,7 @@
|
||||
<b-popover
|
||||
v-if="showPopover"
|
||||
:target="itemId"
|
||||
triggers="hover"
|
||||
triggers="hover, focus"
|
||||
:placement="popoverPosition"
|
||||
>
|
||||
<slot
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
class="filter small-text"
|
||||
:class="{active: activeFilter.label === filter}"
|
||||
@click="activateFilter(type, filter)"
|
||||
@keypress.enter="activateFilter(type, filter)"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ $t(filter) }}
|
||||
</div>
|
||||
@@ -128,6 +130,7 @@
|
||||
<span
|
||||
class="badge-top"
|
||||
@click.prevent.stop="togglePinned(ctx.item)"
|
||||
@keypress.enter.prevent.stop="togglePinned(ctx.item)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="ctx.item.pinned"
|
||||
@@ -156,6 +159,10 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.item:focus-within .badge-pin {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tasks-column {
|
||||
min-height: 556px;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
}, controlClass.up.inner]"
|
||||
@click="(isUser && task.up && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('up') : null"
|
||||
@keypress.enter="(isUser && task.up && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('up') : null"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
v-if="!isUser"
|
||||
@@ -65,6 +68,9 @@
|
||||
:class="controlClass.inner"
|
||||
@click="isUser && !task.group.approval.requested
|
||||
? score(task.completed ? 'down' : 'up' ) : null"
|
||||
@keypress.enter="isUser && !task.group.approval.requested
|
||||
? score(task.completed ? 'down' : 'up' ) : null"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
v-if="!isUser"
|
||||
@@ -92,6 +98,8 @@
|
||||
class="task-clickable-area"
|
||||
:class="{'task-clickable-area-user': isUser}"
|
||||
@click="edit($event, task)"
|
||||
@keypress.enter="edit($event, task)"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="d-flex justify-content-between">
|
||||
<h3
|
||||
@@ -103,6 +111,7 @@
|
||||
v-if="!isRunningYesterdailies && showOptions"
|
||||
ref="taskDropdown"
|
||||
v-b-tooltip.hover.top="$t('options')"
|
||||
tabindex="0"
|
||||
class="task-dropdown"
|
||||
:right="task.type === 'reward'"
|
||||
>
|
||||
@@ -117,6 +126,8 @@
|
||||
v-if="showEdit"
|
||||
ref="editTaskItem"
|
||||
class="dropdown-item edit-task-item"
|
||||
tabindex="0"
|
||||
@keypress.enter="edit($event, task)"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
@@ -130,6 +141,8 @@
|
||||
v-if="isUser"
|
||||
class="dropdown-item"
|
||||
@click="moveToTop"
|
||||
tabindex="0"
|
||||
@keypress.enter="moveToTop"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
@@ -143,6 +156,8 @@
|
||||
v-if="isUser"
|
||||
class="dropdown-item"
|
||||
@click="moveToBottom"
|
||||
tabindex="0"
|
||||
@keypress.enter="moveToBottom"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
@@ -156,6 +171,8 @@
|
||||
v-if="showDelete"
|
||||
class="dropdown-item"
|
||||
@click="destroy"
|
||||
tabindex="0"
|
||||
@keypress.enter="destroy"
|
||||
>
|
||||
<span class="dropdown-icon-item delete-task-item">
|
||||
<span
|
||||
@@ -185,7 +202,9 @@
|
||||
? 'expand': 'collapse'}Checklist`)"
|
||||
class="collapse-checklist mb-2 d-flex align-items-center expand-toggle"
|
||||
:class="{open: !task.collapseChecklist}"
|
||||
tabindex="0"
|
||||
@click="collapseChecklist(task)"
|
||||
@keypress.enter="collapseChecklist(task)"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
@@ -207,10 +226,12 @@
|
||||
<input
|
||||
:id="`checklist-${item.id}-${random}`"
|
||||
class="custom-control-input"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
:checked="item.completed"
|
||||
:disabled="castingSpell || !isUser"
|
||||
@change="toggleChecklistItem(item)"
|
||||
@keypress.enter="toggleChecklistItem(item)"
|
||||
>
|
||||
<label
|
||||
v-markdown="item.text"
|
||||
@@ -330,6 +351,9 @@
|
||||
}, controlClass.down.inner]"
|
||||
@click="(isUser && task.down && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('down') : null"
|
||||
@keypress.enter="(isUser && task.down && (!task.group.approval.requested
|
||||
|| task.group.approval.approved)) ? score('down') : null"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
v-if="!isUser"
|
||||
@@ -350,6 +374,8 @@
|
||||
class="right-control d-flex align-items-center justify-content-center reward-control"
|
||||
:class="controlClass.bg"
|
||||
@click="isUser ? score('down') : null"
|
||||
@keypress.enter="isUser ? score('down') : null"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="svg-icon"
|
||||
@@ -373,6 +399,19 @@
|
||||
<!-- eslint-disable max-len -->
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
.task-best-control-inner-habit:focus {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
transition: none;
|
||||
border: $purple-400 solid 1px;
|
||||
|
||||
:not(task-best-control-inner-habit) { // round icon
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.control-bottom-box {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
@@ -395,14 +434,16 @@
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
|
||||
&:hover:not(.task-not-editable) {
|
||||
&:hover:not(.task-not-editable),
|
||||
&:focus-within:not(.task-not-editable) {
|
||||
box-shadow: 0 1px 8px 0 rgba($black, 0.12), 0 4px 4px 0 rgba($black, 0.16);
|
||||
z-index: 11;
|
||||
}
|
||||
}
|
||||
|
||||
.task:not(.groupTask) {
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
.left-control, .right-control, .task-content {
|
||||
border-color: $purple-400;
|
||||
}
|
||||
@@ -410,7 +451,8 @@
|
||||
}
|
||||
|
||||
.task.groupTask {
|
||||
&:hover:not(.task-not-editable) {
|
||||
&:hover:not(.task-not-editable),
|
||||
&:focus-within:not(.task-not-editable) {
|
||||
border: $purple-400 solid 1px;
|
||||
border-radius: 3px;
|
||||
margin: -1px; // to counter the border width
|
||||
@@ -455,10 +497,16 @@
|
||||
.task-clickable-area {
|
||||
padding: 7px 8px;
|
||||
padding-bottom: 0px;
|
||||
border: transparent solid 1px;
|
||||
|
||||
&-user {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-radius: 2px;
|
||||
border: $purple-400 solid 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-title + .task-dropdown ::v-deep .dropdown-menu {
|
||||
@@ -482,6 +530,20 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.task:focus-within ::v-deep .habitica-menu-dropdown .habitica-menu-dropdown-toggle {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.task ::v-deep .habitica-menu-dropdown:focus-within {
|
||||
opacity: 1;
|
||||
border: $purple-400 solid 1px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.task ::v-deep .habitica-menu-dropdown {
|
||||
border: transparent solid 1px;
|
||||
}
|
||||
|
||||
.task-clickable-area ::v-deep .habitica-menu-dropdown.open .habitica-menu-dropdown-toggle {
|
||||
opacity: 1;
|
||||
|
||||
@@ -494,20 +556,26 @@
|
||||
color: $purple-400 !important;
|
||||
}
|
||||
|
||||
.task-clickable-area ::v-deep .habitica-menu-dropdown .habitica-menu-dropdown-toggle:focus-within .svg-icon {
|
||||
color: $purple-400 !important;
|
||||
}
|
||||
|
||||
.task-dropdown {
|
||||
max-height: 16px;
|
||||
max-height: 18px;
|
||||
}
|
||||
|
||||
.task-dropdown ::v-deep .dropdown-menu {
|
||||
.dropdown-item {
|
||||
cursor: pointer !important;
|
||||
transition: none;
|
||||
border: transparent solid 1px;
|
||||
|
||||
* {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $purple-300;
|
||||
|
||||
.svg-icon.push-to-top, .svg-icon.push-to-bottom {
|
||||
@@ -516,6 +584,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-radius: 2px;
|
||||
border: $purple-400 solid 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,12 +624,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.checklist {
|
||||
&.isOpen {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
margin-top: -3px;
|
||||
.checklist.isOpen {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.collapse-checklist {
|
||||
@@ -567,8 +636,10 @@
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
color: $gray-200;
|
||||
border: transparent solid 1px;
|
||||
|
||||
&.open {
|
||||
&:focus {
|
||||
border: $purple-400 solid 1px;
|
||||
}
|
||||
|
||||
span {
|
||||
@@ -706,6 +777,11 @@
|
||||
transition-duration: 0.15s;
|
||||
transition-property: border-color, background, color;
|
||||
transition-timing-function: ease-in;
|
||||
border: transparent solid 1px;
|
||||
|
||||
&:focus {
|
||||
border: $purple-400 solid 1px;
|
||||
}
|
||||
}
|
||||
.left-control {
|
||||
border-top-left-radius: 2px;
|
||||
@@ -1012,7 +1088,6 @@ export default {
|
||||
},
|
||||
edit (e, task) {
|
||||
if (this.isRunningYesterdailies || !this.showEdit) return;
|
||||
|
||||
const target = e.target || e.srcElement;
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user