Fix some accessibility issues

This commit is contained in:
Bubka 2022-10-12 11:30:20 +02:00
parent 4511df5764
commit 3fcc2b906b
19 changed files with 353 additions and 227 deletions

View File

@ -7,7 +7,7 @@
<div v-if="this.$root.isTestingApp" class="demo has-background-warning has-text-centered is-size-7-mobile"> <div v-if="this.$root.isTestingApp" class="demo has-background-warning has-text-centered is-size-7-mobile">
{{ $t('commons.testing_do_not_post_sensitive_data') }} {{ $t('commons.testing_do_not_post_sensitive_data') }}
</div> </div>
<notifications id="vueNotification" width="100%" position="top" :duration="4000" :speed="0" :max="1" classes="notification is-radiusless" /> <notifications id="vueNotification" role="alert" width="100%" position="top" :duration="4000" :speed="0" :max="1" classes="notification is-radiusless" />
<main class="main-section"> <main class="main-section">
<router-view></router-view> <router-view></router-view>
</main> </main>

View File

@ -1,5 +1,7 @@
<template> <template>
<p :id="'valError' + field[0].toUpperCase() + field.toLowerCase().slice(1)" class="help is-danger" v-if="form.errors.has(field)" v-html="form.errors.get(field)" /> <div role="alert">
<p :id="'valError' + field[0].toUpperCase() + field.toLowerCase().slice(1)" class="help is-danger" v-if="form.errors.has(field)" v-html="form.errors.get(field)" />
</div>
</template> </template>
<script> <script>

View File

@ -5,7 +5,11 @@
</figure> </figure>
<p class="is-size-4 has-text-grey-light has-ellipsis">{{ internal_service }}</p> <p class="is-size-4 has-text-grey-light has-ellipsis">{{ internal_service }}</p>
<p class="is-size-6 has-text-grey has-ellipsis">{{ internal_account }}</p> <p class="is-size-6 has-text-grey has-ellipsis">{{ internal_account }}</p>
<p tabindex="0" class="is-size-1 has-text-white is-clickable" :title="$t('commons.copy_to_clipboard')" v-clipboard="() => internal_password.replace(/ /g, '')" v-clipboard:success="clipboardSuccessHandler">{{ displayedOtp }}</p> <p>
<span role="log" ref="otp" tabindex="0" class="otp is-size-1 has-text-white is-clickable px-3" @click="copyOTP(internal_password)" @keyup.enter="copyOTP(internal_password)" :title="$t('commons.copy_to_clipboard')">
{{ displayedOtp }}
</span>
</p>
<ul class="dots" v-show="isTimeBased(internal_otp_type)"> <ul class="dots" v-show="isTimeBased(internal_otp_type)">
<li v-for="n in 10" :key="n"></li> <li v-for="n in 10" :key="n"></li>
</ul> </ul>
@ -70,8 +74,34 @@
this.show() this.show()
}, },
// created() {
// },
methods: { methods: {
copyOTP (otp) {
// see https://web.dev/async-clipboard/ for futur Clipboard API usage.
// The API should allow to copy the password on each trip without user interaction.
// For now too many browsers don't support the clipboard-write permission
// (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions#browser_support)
const rawOTP = otp.replace(/ /g, '')
const success = this.$clipboard(rawOTP)
if (success == true) {
if(this.$root.appSettings.kickUserAfter == -1) {
this.appLogout()
}
else if(this.$root.appSettings.closeOtpOnCopy) {
this.$parent.isActive = false
this.clearOTP()
}
this.$notify({ type: 'is-success', text: this.$t('commons.copied_to_clipboard') })
}
},
isTimeBased: function(otp_type) { isTimeBased: function(otp_type) {
return (otp_type === 'totp' || otp_type === 'steamtotp') return (otp_type === 'totp' || otp_type === 'steamtotp')
}, },
@ -133,7 +163,7 @@
else this.$router.push({ name: 'genericError', params: { err: this.$t('errors.not_a_supported_otp_type') } }); else this.$router.push({ name: 'genericError', params: { err: this.$t('errors.not_a_supported_otp_type') } });
this.$parent.isActive = true this.$parent.isActive = true
this.$parent.$refs.closeModalButton.focus() this.focusOnOTP()
} }
catch(error) { catch(error) {
this.clearOTP() this.clearOTP()
@ -181,7 +211,7 @@
await this.axios(request).then(response => { await this.axios(request).then(response => {
if(this.$root.appSettings.copyOtpOnDisplay) { if(this.$root.appSettings.copyOtpOnDisplay) {
this.copyAndNotify(response.data.password) this.copyOTP(response.data.password)
} }
password = response.data password = response.data
}) })
@ -319,35 +349,11 @@
} }
}, },
focusOnOTP() {
clipboardSuccessHandler ({ value, event }) { this.$nextTick(() => {
this.$refs.otp.focus()
if(this.$root.appSettings.kickUserAfter == -1) { })
this.appLogout() }
}
else if(this.$root.appSettings.closeOtpOnCopy) {
this.$parent.isActive = false
this.clearOTP()
}
this.$notify({ type: 'is-success', text: this.$t('commons.copied_to_clipboard') })
},
clipboardErrorHandler ({ value, event }) {
console.log('error', value)
},
copyAndNotify (strToCopy) {
// see https://web.dev/async-clipboard/ for futur Clipboard API usage.
// The API should allow to copy the password on each trip without user interaction.
// For now too many browsers don't support the clipboard-write permission
// (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions#browser_support)
this.$clipboard(strToCopy)
this.$notify({ type: 'is-success', text: this.$t('commons.copied_to_clipboard') })
},
}, },

View File

@ -1,6 +1,6 @@
<template> <template>
<responsive-width-wrapper> <responsive-width-wrapper>
<h1 class="title has-text-grey-dark">{{ $t('commons.about') }}</h1> <h1 class="title has-text-grey-dark">{{ pagetitle }}</h1>
<p class="block"> <p class="block">
<span class="has-text-white"><span class="is-size-5">2FAuth</span> v{{ appVersion }}</span><br /> <span class="has-text-white"><span class="is-size-5">2FAuth</span> v{{ appVersion }}</span><br />
{{ $t('commons.2fauth_teaser')}} {{ $t('commons.2fauth_teaser')}}
@ -13,25 +13,25 @@
{{ $t('commons.resources') }} {{ $t('commons.resources') }}
</h2> </h2>
<div class="buttons"> <div class="buttons">
<a class="button is-dark" href="https://github.com/Bubka/2FAuth"> <a class="button is-dark" href="https://github.com/Bubka/2FAuth" target="_blank">
<span class="icon is-small"> <span class="icon is-small">
<font-awesome-icon :icon="['fab', 'github-alt']" /> <font-awesome-icon :icon="['fab', 'github-alt']" />
</span> </span>
<span>Github</span> <span>Github</span>
</a> </a>
<a class="button is-dark" href="https://docs.2fauth.app/"> <a class="button is-dark" href="https://docs.2fauth.app/" target="_blank">
<span class="icon is-small"> <span class="icon is-small">
<font-awesome-icon :icon="['fas', 'book']" /> <font-awesome-icon :icon="['fas', 'book']" />
</span> </span>
<span>Docs</span> <span>Docs</span>
</a> </a>
<a class="button is-dark" href="https://demo.2fauth.app/"> <a class="button is-dark" href="https://demo.2fauth.app/" target="_blank">
<span class="icon is-small"> <span class="icon is-small">
<font-awesome-icon :icon="['fas', 'flask']" /> <font-awesome-icon :icon="['fas', 'flask']" />
</span> </span>
<span>Demo</span> <span>Demo</span>
</a> </a>
<a class="button is-dark" href="https://docs.2fauth.app/resources/rapidoc.html"> <a class="button is-dark" href="https://docs.2fauth.app/resources/rapidoc.html" target="_blank">
<span class="icon is-small"> <span class="icon is-small">
<font-awesome-icon :icon="['fas', 'code']" /> <font-awesome-icon :icon="['fas', 'code']" />
</span> </span>
@ -52,7 +52,7 @@
{{ $t('commons.environment') }} {{ $t('commons.environment') }}
</h2> </h2>
<div class="box has-background-black-bis is-family-monospace is-size-7"> <div class="box has-background-black-bis is-family-monospace is-size-7">
<button class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listInfos.innerText" v-clipboard:success="clipboardSuccessHandler"> <button :aria-label="$t('commons.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listInfos.innerText" v-clipboard:success="clipboardSuccessHandler">
<font-awesome-icon :icon="['fas', 'copy']" /> <font-awesome-icon :icon="['fas', 'copy']" />
</button> </button>
<ul ref="listInfos"> <ul ref="listInfos">
@ -64,7 +64,7 @@
{{ $t('settings.user_options') }} {{ $t('settings.user_options') }}
</h2> </h2>
<div class="box has-background-black-bis is-family-monospace is-size-7"> <div class="box has-background-black-bis is-family-monospace is-size-7">
<button class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listUserOptions.innerText" v-clipboard:success="clipboardSuccessHandler"> <button :aria-label="$t('commons.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listUserOptions.innerText" v-clipboard:success="clipboardSuccessHandler">
<font-awesome-icon :icon="['fas', 'copy']" /> <font-awesome-icon :icon="['fas', 'copy']" />
</button> </button>
<ul ref="listUserOptions"> <ul ref="listUserOptions">
@ -76,7 +76,7 @@
<vue-footer :showButtons="true"> <vue-footer :showButtons="true">
<!-- close button --> <!-- close button -->
<p class="control"> <p class="control">
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link> <router-link :to="{ name: 'accounts', params: { toRefresh: true } }" role="button" :aria-label="$t('commons.close_the_x_page', {pagetitle: pagetitle})" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
</p> </p>
</vue-footer> </vue-footer>
</responsive-width-wrapper> </responsive-width-wrapper>
@ -86,6 +86,7 @@
export default { export default {
data() { data() {
return { return {
pagetitle: this.$t('commons.about'),
infos : null, infos : null,
options : null, options : null,
showUserOptions: false, showUserOptions: false,

View File

@ -56,12 +56,106 @@
</p> </p>
<!-- Cancel button --> <!-- Cancel button -->
<p class="control"> <p class="control">
<a class="button is-dark is-rounded" @click="showGroupSelector = false">{{ $t('commons.cancel') }}</a> <button class="button is-dark is-rounded" @click="showGroupSelector = false">{{ $t('commons.cancel') }}</button>
</p> </p>
</vue-footer> </vue-footer>
</div> </div>
<!-- header -->
<div class="header has-background-black-ter" v-if="this.showAccounts || this.showGroupSwitch">
<div class="columns is-gapless is-mobile is-centered">
<div v-if="editMode" class="column">
<!-- toolbar -->
<div class="toolbar has-text-centered">
<div class="field is-grouped is-justify-content-center has-text-grey mb-2">
<!-- selected label -->
<p class="control mr-1">
{{ selectedAccounts.length }}&nbsp;{{ $t('commons.selected') }}
</p>
<!-- deselect all -->
<p class="control mr-4">
<button @click="clearSelected" class="clear-selection delete" :style="{visibility: selectedAccounts.length > 0 ? 'visible' : 'hidden'}" :title="$t('commons.clear_selection')"></button>
</p>
<!-- select all button -->
<p class="control mr-5">
<button @click="selectAll" class="button has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.select_all')">
<span>{{ $t('commons.all') }}</span>
<font-awesome-icon class="ml-1" :icon="['fas', 'check-square']" />
</button>
</p>
<!-- sort asc/desc buttons -->
<p class="control">
<button @click="sortAsc" class="button has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.sort_ascending')">
<font-awesome-icon :icon="['fas', 'sort-alpha-down']" />
</button>
</p>
<p class="control">
<button @click="sortDesc" class="button has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.sort_descending')">
<font-awesome-icon :icon="['fas', 'sort-alpha-up']" />
</button>
</p>
</div>
<div class="field is-grouped is-justify-content-center pb-2">
<!-- Change group button -->
<div v-if="selectedAccounts.length > 0" class="control">
<div tabindex="0" role="button" class="tag-button tag-button-link tags are-medium has-addons is-clickable" @click="showGroupSelector = true" @keyup.enter="showGroupSelector = true">
<span class="tag is-dark mb-0">
{{ $t('groups.change_group') }}
</span>
<span class="tag is-link mb-0">
<font-awesome-icon :icon="['fas', 'layer-group']" />
</span>
</div>
</div>
<!-- delete selected button -->
<div v-if="selectedAccounts.length > 0" class="control">
<div tabindex="0" role="button" class="tag-button tag-button-danger tags are-medium has-addons is-clickable" @click="destroyAccounts" @keyup.enter="destroyAccounts">
<span class="tag is-dark mb-0">
{{ $t('commons.delete') }}
</span>
<span class="tag is-danger mb-0">
<font-awesome-icon :icon="['fas', 'trash']" />
</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="column is-three-quarters-mobile is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
<!-- search -->
<div role="search" class="field">
<div class="control has-icons-right">
<input id="txtSearch" type="search" tabindex="1" :aria-label="$t('commons.search')" :title="$t('commons.search')" class="input is-rounded is-search" v-model="search">
<span class="icon is-small is-right">
<font-awesome-icon :icon="['fas', 'search']" v-if="!search" />
<button tabindex="1" :title="$t('commons.clear_search')" class="clear-selection delete" v-if="search" @click="search = '' "></button>
</span>
</div>
</div>
<!-- group switch toggle -->
<div class="has-text-centered">
<div class="columns">
<div class="column" v-if="!showGroupSwitch">
<button :title="$t('groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
{{ activeGroupName }} ({{ filteredAccounts.length }})&nbsp;
<font-awesome-icon :icon="['fas', 'caret-down']" />
</button>
</div>
<div class="column" v-else>
<button :title="$t('groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
{{ $t('groups.select_accounts_to_show') }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- modal -->
<modal v-model="showTwofaccountInModal">
<otp-displayer ref="OtpDisplayer"></otp-displayer>
</modal>
<!-- show accounts list --> <!-- show accounts list -->
<div class="container" v-if="this.showAccounts"> <div class="container" v-if="this.showAccounts" :class="editMode ? 'is-edit-mode' : ''">
<!-- accounts --> <!-- accounts -->
<!-- <vue-pull-refresh :on-refresh="onRefresh" :config="{ <!-- <vue-pull-refresh :on-refresh="onRefresh" :config="{
errorLabel: 'error', errorLabel: 'error',
@ -77,7 +171,7 @@
<div class="tfa-cell tfa-checkbox" v-if="editMode"> <div class="tfa-cell tfa-checkbox" v-if="editMode">
<div class="field"> <div class="field">
<input class="is-checkradio is-small is-white" :id="'ckb_' + account.id" :value="account.id" type="checkbox" :name="'ckb_' + account.id" v-model="selectedAccounts"> <input class="is-checkradio is-small is-white" :id="'ckb_' + account.id" :value="account.id" type="checkbox" :name="'ckb_' + account.id" v-model="selectedAccounts">
<label :for="'ckb_' + account.id"></label> <label tabindex="0" :for="'ckb_' + account.id" v-on:keypress.space.prevent="selectAccount(account.id)"></label>
</div> </div>
</div> </div>
</transition> </transition>
@ -135,94 +229,6 @@
</p> </p>
</vue-footer> </vue-footer>
</div> </div>
<!-- header -->
<div class="header has-background-black-ter" v-if="this.showAccounts || this.showGroupSwitch">
<div class="columns is-gapless is-mobile is-centered">
<div v-if="editMode" class="column">
<!-- toolbar -->
<div class="toolbar has-text-centered">
<div class="field is-grouped is-justify-content-center">
<div class="control">
<div class="tags has-addons are-medium">
<span class="tag is-dark has-background-black-ter has-text-grey">
{{ selectedAccounts.length }}&nbsp;{{ $t('commons.selected') }}
<button @click="clearSelected" :style="{visibility: selectedAccounts.length > 0 ? 'visible' : 'hidden'}" class="delete" :title="$t('commons.clear_selection')"></button>
</span>
<span role="button" @click="selectAll" class="tag is-dark is-clickable has-background-black-ter has-text-grey" :title="$t('commons.select_all')">
{{ $t('commons.all') }}
<font-awesome-icon class="ml-1" :icon="['fas', 'check-square']" />
</span>
</div>
</div>
<div class="control">
<div class="tags has-addons are-medium">
<span role="button" @click="sortAsc" class="tag is-dark is-clickable has-background-black-ter has-text-grey" :title="$t('commons.sort_ascending')">
<font-awesome-icon :icon="['fas', 'sort-alpha-down']" />
</span>
<span role="button" @click="sortDesc" class="tag is-dark is-clickable has-background-black-ter has-text-grey" :title="$t('commons.sort_descending')">
<font-awesome-icon :icon="['fas', 'sort-alpha-up']" />
</span>
</div>
</div>
</div>
<div class="field is-grouped is-justify-content-center pt-1">
<div class="control">
<div role="button" class="tags are-medium has-addons is-clickable" v-if="selectedAccounts.length > 0" @click="showGroupSelector = true">
<span class="tag is-dark">
{{ $t('groups.change_group') }}
</span>
<span class="tag is-link">
<font-awesome-icon :icon="['fas', 'layer-group']" />
</span>
</div>
</div>
<div class="control">
<div role="button" class="tags are-medium has-addons is-clickable" v-if="selectedAccounts.length > 0" @click="destroyAccounts">
<span class="tag is-dark">
{{ $t('commons.delete') }}
</span>
<span class="tag is-danger">
<font-awesome-icon :icon="['fas', 'trash']" />
</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="column is-three-quarters-mobile is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
<!-- search -->
<div class="field">
<div class="control has-icons-right">
<input id="txtSearch" type="search" tabindex="1" :aria-label="$t('commons.search')" :title="$t('commons.search')" class="input is-rounded is-search" v-model="search">
<span class="icon is-small is-right">
<font-awesome-icon :icon="['fas', 'search']" v-if="!search" />
<a class="delete" v-if="search" @click="search = '' "></a>
</span>
</div>
</div>
<!-- group switch toggle -->
<div class="has-text-centered">
<div class="columns">
<div class="column" v-if="!showGroupSwitch">
<button :title="$t('groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
{{ activeGroupName }} ({{ filteredAccounts.length }})&nbsp;
<font-awesome-icon :icon="['fas', 'caret-down']" />
</button>
</div>
<div class="column" v-else>
<button :title="$t('groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
{{ $t('groups.select_accounts_to_show') }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- modal -->
<modal v-model="showTwofaccountInModal">
<otp-displayer ref="OtpDisplayer"></otp-displayer>
</modal>
</div> </div>
</template> </template>
@ -353,7 +359,6 @@
// stop OTP generation on modal close // stop OTP generation on modal close
this.$on('modalClose', function() { this.$on('modalClose', function() {
console.log('modalClose triggered')
this.$refs.OtpDisplayer.clearOTP() this.$refs.OtpDisplayer.clearOTP()
}); });
@ -416,15 +421,7 @@
showAccount(account) { showAccount(account) {
// In Edit mode clicking an account do not show the otpDisplayer but select the account // In Edit mode clicking an account do not show the otpDisplayer but select the account
if(this.editMode) { if(this.editMode) {
selectAccount(account.id)
for (var i=0 ; i<this.selectedAccounts.length ; i++) {
if ( this.selectedAccounts[i] === account.id ) {
this.selectedAccounts.splice(i,1);
return
}
}
this.selectedAccounts.push(account.id)
} }
else { else {
this.$refs.OtpDisplayer.show(account.id) this.$refs.OtpDisplayer.show(account.id)
@ -432,6 +429,20 @@
}, },
/**
* Select an account while in edit mode
*/
selectAccount(accountId) {
for (var i=0 ; i<this.selectedAccounts.length ; i++) {
if ( this.selectedAccounts[i] === accountId ) {
this.selectedAccounts.splice(i,1);
return
}
}
this.selectedAccounts.push(accountId)
},
/** /**
* Get a fresh OTP for the provided account * Get a fresh OTP for the provided account
*/ */

View File

@ -24,9 +24,9 @@
</div> </div>
<div class="fullscreen-footer"> <div class="fullscreen-footer">
<!-- Cancel button --> <!-- Cancel button -->
<label class="button is-large is-warning is-rounded" @click="exitStream()"> <button class="button is-large is-warning is-rounded" @click="exitStream()">
{{ $t('commons.cancel') }} {{ $t('commons.cancel') }}
</label> </button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -19,7 +19,7 @@
{{ $t('commons.delete') }} {{ $t('commons.delete') }}
</button> </button>
<!-- edit link --> <!-- edit link -->
<router-link :to="{ name: 'editGroup', params: { id: group.id, name: group.name }}" class="has-text-grey pl-1" :title="$t('commons.rename')"> <router-link :to="{ name: 'editGroup', params: { id: group.id, name: group.name }}" class="has-text-grey px-1" :title="$t('commons.rename')">
<font-awesome-icon :icon="['fas', 'pen-square']" /> <font-awesome-icon :icon="['fas', 'pen-square']" />
</router-link> </router-link>
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ group.twofaccounts_count }} {{ $t('twofaccounts.accounts') }}</span> <span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ group.twofaccounts_count }} {{ $t('twofaccounts.accounts') }}</span>

View File

@ -11,14 +11,14 @@
<div class="column is-full quick-uploader-button" > <div class="column is-full quick-uploader-button" >
<div class="quick-uploader-centerer"> <div class="quick-uploader-centerer">
<!-- upload a qr code (with basic file field and backend decoding) --> <!-- upload a qr code (with basic file field and backend decoding) -->
<label v-if="$root.appSettings.useBasicQrcodeReader" class="button is-link is-medium is-rounded is-focused" ref="qrcodeInputLabel"> <label role="button" tabindex="0" v-if="$root.appSettings.useBasicQrcodeReader" class="button is-link is-medium is-rounded is-focused" ref="qrcodeInputLabel" @keyup.enter="$refs.qrcodeInputLabel.click()">
<input class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput"> <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
{{ $t('twofaccounts.forms.upload_qrcode') }} {{ $t('twofaccounts.forms.upload_qrcode') }}
</label> </label>
<!-- scan button that launch camera stream --> <!-- scan button that launch camera stream -->
<label v-else class="button is-link is-medium is-rounded is-focused" @click="capture()"> <button v-else class="button is-link is-medium is-rounded is-focused is-double-focused" @click="capture()">
{{ $t('twofaccounts.forms.scan_qrcode') }} {{ $t('twofaccounts.forms.scan_qrcode') }}
</label> </button>
</div> </div>
</div> </div>
<!-- alternative methods --> <!-- alternative methods -->
@ -26,8 +26,8 @@
<div class="block has-text-light">{{ $t('twofaccounts.forms.alternative_methods') }}</div> <div class="block has-text-light">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
<!-- upload a qr code --> <!-- upload a qr code -->
<div class="block has-text-link" v-if="!$root.appSettings.useBasicQrcodeReader"> <div class="block has-text-link" v-if="!$root.appSettings.useBasicQrcodeReader">
<label class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel"> <label role="button" tabindex="0" class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel" @keyup.enter="$refs.qrcodeInputLabel.click()">
<input class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput"> <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
{{ $t('twofaccounts.forms.upload_qrcode') }} {{ $t('twofaccounts.forms.upload_qrcode') }}
</label> </label>
</div> </div>

View File

@ -29,9 +29,9 @@
<p>{{ $t('auth.forms.dont_have_account_yet') }}&nbsp;<router-link id="lnkRegister" :to="{ name: 'register' }" class="is-link">{{ $t('auth.register') }}</router-link></p> <p>{{ $t('auth.forms.dont_have_account_yet') }}&nbsp;<router-link id="lnkRegister" :to="{ name: 'register' }" class="is-link">{{ $t('auth.register') }}</router-link></p>
</div> </div>
<div v-else> <div v-else>
<p>{{ $t('auth.forms.forgot_your_password') }}&nbsp;<router-link id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link">{{ $t('auth.forms.request_password_reset') }}</router-link></p> <p>{{ $t('auth.forms.forgot_your_password') }}&nbsp;<router-link id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('auth.forms.reset_your_password')">{{ $t('auth.forms.request_password_reset') }}</router-link></p>
<p >{{ $t('auth.sign_in_using') }}&nbsp; <p >{{ $t('auth.sign_in_using') }}&nbsp;
<a id="lnkSignWithWebauthn" role="button" class="is-link" @keyup.enter="showWebauthn = true" @click="showWebauthn = true" tabindex="0">{{ $t('auth.webauthn.security_device') }}</a> <a id="lnkSignWithWebauthn" role="button" class="is-link" @keyup.enter="showWebauthn = true" @click="showWebauthn = true" tabindex="0" :aria-label="$t('auth.sign_in_using_security_device')">{{ $t('auth.webauthn.security_device') }}</a>
</p> </p>
</div> </div>
</div> </div>

View File

@ -10,7 +10,9 @@
<div v-else> <div v-else>
<div class="field"> <div class="field">
<input id="unique" name="unique" type="checkbox" class="is-checkradio is-info" v-model="unique" > <input id="unique" name="unique" type="checkbox" class="is-checkradio is-info" v-model="unique" >
<label for="unique" class="label">{{ $t('auth.webauthn.disable_all_other_devices') }}</label> <label tabindex="0" for="unique" class="label" ref="uniqueLabel" v-on:keypress.space.prevent="unique = true">
{{ $t('auth.webauthn.disable_all_other_devices') }}
</label>
</div> </div>
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">

View File

@ -37,9 +37,9 @@
<vue-footer :showButtons="true"> <vue-footer :showButtons="true">
<!-- Cancel button --> <!-- Cancel button -->
<p class="control"> <p class="control">
<a role="button" tabindex="0" class="button is-dark is-rounded" @click.stop="exitSettings"> <button class="button is-dark is-rounded" @click.stop="exitSettings">
{{ $t('commons.close') }} {{ $t('commons.close') }}
</a> </button>
</p> </p>
</vue-footer> </vue-footer>
</div> </div>

View File

@ -47,7 +47,7 @@
<vue-footer :showButtons="true"> <vue-footer :showButtons="true">
<!-- close button --> <!-- close button -->
<p class="control"> <p class="control">
<router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link> <router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded" tabindex="0">{{ $t('commons.close') }}</router-link>
</p> </p>
</vue-footer> </vue-footer>
</form-wrapper> </form-wrapper>

View File

@ -56,9 +56,9 @@
<vue-footer :showButtons="true"> <vue-footer :showButtons="true">
<!-- Cancel button --> <!-- Cancel button -->
<p class="control"> <p class="control">
<a class="button is-dark is-rounded" @click.stop="exitSettings"> <button class="button is-dark is-rounded" @click.stop="exitSettings">
{{ $t('commons.close') }} {{ $t('commons.close') }}
</a> </button>
</p> </p>
</vue-footer> </vue-footer>
</div> </div>

View File

@ -14,8 +14,8 @@
</otp-displayer> </otp-displayer>
</div> </div>
</div> </div>
<div class="columns is-mobile" v-if="form.errors.any()"> <div class="columns is-mobile" role="alert">
<div class="column"> <div v-if="form.errors.any()" class="column">
<p v-for="(field, index) in form.errors.errors" :key="index" class="help is-danger"> <p v-for="(field, index) in form.errors.errors" :key="index" class="help is-danger">
<ul> <ul>
<li v-for="(error, index) in field" :key="index">{{ error }}</li> <li v-for="(error, index) in field" :key="index">{{ error }}</li>
@ -41,17 +41,19 @@
<form-wrapper :title="$t('twofaccounts.forms.new_account')" v-if="showAdvancedForm"> <form-wrapper :title="$t('twofaccounts.forms.new_account')" v-if="showAdvancedForm">
<form @submit.prevent="createAccount" @keydown="form.onKeydown($event)"> <form @submit.prevent="createAccount" @keydown="form.onKeydown($event)">
<!-- qcode fileupload --> <!-- qcode fileupload -->
<div class="field"> <div class="field is-grouped">
<div class="file is-black is-small"> <div class="control">
<label class="file-label" :title="$t('twofaccounts.forms.use_qrcode.title')"> <div role="button" tabindex="0" class="file is-black is-small" @keyup.enter="$refs.qrcodeInputLabel.click()">
<input class="file-input" type="file" accept="image/*" v-on:change="uploadQrcode" ref="qrcodeInput"> <label class="file-label" :title="$t('twofaccounts.forms.use_qrcode.title')" ref="qrcodeInputLabel">
<span class="file-cta"> <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadQrcode" ref="qrcodeInput">
<span class="file-icon"> <span class="file-cta">
<font-awesome-icon :icon="['fas', 'qrcode']" size="lg" /> <span class="file-icon">
<font-awesome-icon :icon="['fas', 'qrcode']" size="lg" />
</span>
<span class="file-label">{{ $t('twofaccounts.forms.prefill_using_qrcode') }}</span>
</span> </span>
<span class="file-label">{{ $t('twofaccounts.forms.prefill_using_qrcode') }}</span> </label>
</span> </div>
</label>
</div> </div>
</div> </div>
<field-error :form="form" field="qrcode" class="help-for-file" /> <field-error :form="form" field="qrcode" class="help-for-file" />
@ -73,9 +75,9 @@
</div> </div>
<!-- upload button --> <!-- upload button -->
<div class="control"> <div class="control">
<div class="file is-dark"> <div role="button" tabindex="0" class="file is-dark" @keyup.enter="$refs.iconInputLabel.click()">
<label class="file-label"> <label class="file-label" ref="iconInputLabel">
<input class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput"> <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
<span class="file-cta"> <span class="file-cta">
<span class="file-icon"> <span class="file-icon">
<font-awesome-icon :icon="['fas', 'upload']" /> <font-awesome-icon :icon="['fas', 'upload']" />
@ -85,7 +87,7 @@
</label> </label>
<span class="tag is-black is-large" v-if="tempIcon"> <span class="tag is-black is-large" v-if="tempIcon">
<img class="icon-preview" :src="'/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')"> <img class="icon-preview" :src="'/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
<button class="delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button> <button class="clear-selection delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
</span> </span>
</div> </div>
</div> </div>

View File

@ -19,9 +19,9 @@
</div> </div>
<!-- upload button --> <!-- upload button -->
<div class="control"> <div class="control">
<div class="file is-dark"> <div role="button" tabindex="0" class="file is-dark" @keyup.enter="$refs.iconInputLabel.click()">
<label class="file-label"> <label class="file-label" ref="iconInputLabel">
<input class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput"> <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
<span class="file-cta"> <span class="file-cta">
<span class="file-icon"> <span class="file-icon">
<font-awesome-icon :icon="['fas', 'upload']" /> <font-awesome-icon :icon="['fas', 'upload']" />
@ -31,7 +31,7 @@
</label> </label>
<span class="tag is-black is-large" v-if="tempIcon"> <span class="tag is-black is-large" v-if="tempIcon">
<img class="icon-preview" :src="'/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')"> <img class="icon-preview" :src="'/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
<button class="delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button> <button class="clear-selection delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
</span> </span>
</div> </div>
</div> </div>
@ -57,18 +57,18 @@
<input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret" :disabled="secretIsLocked"> <input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret" :disabled="secretIsLocked">
</p> </p>
<p class="control" v-if="secretIsLocked"> <p class="control" v-if="secretIsLocked">
<a class="button is-dark field-lock" @click="secretIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')"> <button type="button" class="button is-dark field-lock" @click.stop="secretIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<span class="icon"> <span class="icon">
<font-awesome-icon :icon="['fas', 'lock']" /> <font-awesome-icon :icon="['fas', 'lock']" />
</span> </span>
</a> </button>
</p> </p>
<p class="control" v-else> <p class="control" v-else>
<a class="button is-dark field-unlock" @click="secretIsLocked = true" :title="$t('twofaccounts.forms.lock.title')"> <button type="button" class="button is-dark field-unlock" @click.stop="secretIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<span class="icon has-text-danger"> <span class="icon has-text-danger">
<font-awesome-icon :icon="['fas', 'lock-open']" /> <font-awesome-icon :icon="['fas', 'lock-open']" />
</span> </span>
</a> </button>
</p> </p>
</div> </div>
<div class="field"> <div class="field">
@ -96,18 +96,18 @@
<input class="input" type="text" placeholder="" v-model="form.counter" :disabled="counterIsLocked" /> <input class="input" type="text" placeholder="" v-model="form.counter" :disabled="counterIsLocked" />
</div> </div>
<div class="control" v-if="counterIsLocked"> <div class="control" v-if="counterIsLocked">
<a class="button is-dark field-lock" @click="counterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')"> <button type="button" class="button is-dark field-lock" @click="counterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<span class="icon"> <span class="icon">
<font-awesome-icon :icon="['fas', 'lock']" /> <font-awesome-icon :icon="['fas', 'lock']" />
</span> </span>
</a> </button>
</div> </div>
<div class="control" v-else> <div class="control" v-else>
<a class="button is-dark field-unlock" @click="counterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')"> <button type="button" class="button is-dark field-unlock" @click="counterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<span class="icon has-text-danger"> <span class="icon has-text-danger">
<font-awesome-icon :icon="['fas', 'lock-open']" /> <font-awesome-icon :icon="['fas', 'lock-open']" />
</span> </span>
</a> </button>
</div> </div>
</div> </div>
<field-error :form="form" field="counter" /> <field-error :form="form" field="counter" />

View File

@ -8,9 +8,9 @@
</div> </div>
<div class="fullscreen-footer"> <div class="fullscreen-footer">
<!-- Close button --> <!-- Close button -->
<label class="button is-dark is-rounded" @click.stop="$router.push({name: 'accounts', params: {initialEditMode: true}});"> <button class="button is-dark is-rounded" @click.stop="$router.push({name: 'accounts', params: {initialEditMode: true}});">
{{ $t('commons.close') }} {{ $t('commons.close') }}
</label> </button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -22,6 +22,7 @@
'sign_out' => 'Sign out', 'sign_out' => 'Sign out',
'sign_in' => 'Sign in', 'sign_in' => 'Sign in',
'sign_in_using' => 'Sign in using', 'sign_in_using' => 'Sign in using',
'sign_in_using_security_device' => 'Sign in using a security device',
'login_and_password' => 'login & password', 'login_and_password' => 'login & password',
'register' => 'Register', 'register' => 'Register',
'welcome_back_x' => 'Welcome back {0}', 'welcome_back_x' => 'Welcome back {0}',
@ -89,6 +90,7 @@
'authentication_failed' => 'Authentication failed', 'authentication_failed' => 'Authentication failed',
'forgot_your_password' => 'Forgot your password?', 'forgot_your_password' => 'Forgot your password?',
'request_password_reset' => 'Reset it', 'request_password_reset' => 'Reset it',
'reset_your_password' => 'Reset your password',
'reset_password' => 'Reset password', 'reset_password' => 'Reset password',
'disabled_in_demo' => 'Feature disabled in Demo mode', 'disabled_in_demo' => 'Feature disabled in Demo mode',
'new_password' => 'New password', 'new_password' => 'New password',

View File

@ -27,6 +27,7 @@
'save' => 'Save', 'save' => 'Save',
'close' => 'Close', 'close' => 'Close',
'clear' => 'Clear', 'clear' => 'Clear',
'clear_search' => 'Clear search',
'demo_do_not_post_sensitive_data' => 'This is a demo app, do not post any sensitive data', 'demo_do_not_post_sensitive_data' => 'This is a demo app, do not post any sensitive data',
'testing_do_not_post_sensitive_data' => 'This is a testing app, do not post any sensitive data', 'testing_do_not_post_sensitive_data' => 'This is a testing app, do not post any sensitive data',
'selected' => 'selected', 'selected' => 'selected',
@ -67,4 +68,5 @@
'image_of_qrcode_to_scan' => 'Image of a QR code to scan', 'image_of_qrcode_to_scan' => 'Image of a QR code to scan',
'file' => 'File', 'file' => 'File',
'or' => 'OR', 'or' => 'OR',
'close_the_x_page' => 'Close the {pagetitle} page',
]; ];

View File

@ -35,6 +35,7 @@ a:hover {
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
width: 100%; width: 100%;
z-index: 1000;
} }
@supports (padding-top: env(safe-area-inset-top)) { @supports (padding-top: env(safe-area-inset-top)) {
@ -51,6 +52,19 @@ a:hover {
} }
} }
.modal-otp {
z-index: 2000;
}
.otp:focus-visible {
outline-offset: 3px;
outline: 2px dotted $dark;
border-radius: $radius-large;
}
.otp:focus:not(:focus-visible) {
outline: none;
}
.group-item { .group-item {
border-bottom: 1px solid hsl(0, 0%, 21%); border-bottom: 1px solid hsl(0, 0%, 21%);
padding: 0.75rem; padding: 0.75rem;
@ -65,7 +79,7 @@ a:hover {
} }
.accounts { .accounts {
margin-top: 74px; margin-top: 75px;
} }
.groups { .groups {
@ -82,7 +96,7 @@ a:hover {
@media screen and (min-width: 769px) { @media screen and (min-width: 769px) {
.accounts { .accounts {
margin-top: 98px; margin-top: 99px;
} }
} }
@ -205,16 +219,19 @@ a:hover {
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
} }
.tfa-content:focus, .tfa-content:focus-visible .tfa-content:focus-visible
{ {
outline: 2px solid $grey; outline: 1px solid $grey;
border: none; border: none;
outline-offset: 7px; outline-offset: 7px;
border-radius: 3px; border-radius: 3px;
} }
.tfa-content:focus:not(:focus-visible) {
outline: none;
}
.tfa-list .tfa-content { .is-edit-mode .tfa-list .tfa-content {
padding-right: 1rem; margin-right: 1rem;
} }
.tfa-dots { .tfa-dots {
@ -377,6 +394,14 @@ figure.no-icon {
color: hsl(0, 0%, 21%); color: hsl(0, 0%, 21%);
} }
.button.has-line-height {
height: inherit !important;
}
.button.has-line-height span {
display: inline-block;
line-height: 1rem;
}
.button.is-dark.field-lock, .button.is-dark.field-unlock { .button.is-dark.field-lock, .button.is-dark.field-unlock {
color: hsl(0, 0%, 48%); color: hsl(0, 0%, 48%);
} }
@ -409,14 +434,17 @@ figure.no-icon {
} }
.button:focus-visible, .button.is-focused,
.file[role=button]:focus-visible {
.button:focus, .button:focus-visible, .button.is-focused {
border-color: transparent; border-color: transparent;
outline-offset: 3px; outline-offset: 3px;
outline-style: solid;
outline-width: 2px;
} }
a:focus, a:focus-visible { .button:focus:not(:focus-visible),
outline-offset: 2px; .file[role=button]:focus:not(:focus-visible)
{
outline: none;
} }
.button:focus:not(:active), .button.is-focused:not(:active), .button:focus:not(:active), .button.is-focused:not(:active),
.button.is-white:focus:not(:active), .button.is-white.is-focused:not(:active), .button.is-white:focus:not(:active), .button.is-white.is-focused:not(:active),
@ -434,62 +462,120 @@ a:focus, a:focus-visible {
{ {
box-shadow: none; box-shadow: none;
} }
.button.is-white:focus, .button.is-white:focus-visible, .button.is-white.is-focused .button.is-white:focus, .button.is-white:focus-visible, .button.is-white.is-focused
{ {
outline: 2px solid $white; outline-color: $white;
} }
.button.is-light:focus, .button.is-light:focus-visible, .button.is-light.is-focused .button.is-light:focus, .button.is-light:focus-visible, .button.is-light.is-focused
{ {
outline: 2px solid $grey-lightest; outline-color: $grey-lightest;
} }
.button.is-dark:focus, .button.is-dark:focus-visible, .button.is-dark.is-focused .button.is-dark:focus, .button.is-dark:focus-visible, .button.is-dark.is-focused,
.file[role=button].is-dark:focus, .file[role=button].is-dark:focus-visible
{ {
outline: 2px solid $dark; outline-color: $dark;
} }
.button.is-black:focus, .button.is-black:focus-visible, .button.is-black.is-focused .button.is-black:focus, .button.is-black:focus-visible, .button.is-black.is-focused,
.file[role=button].is-black:focus, .file[role=button].is-black:focus-visible
{ {
outline: 2px solid $black; outline-color: $black;
} }
.button.is-text:focus, .button.is-text:focus-visible, .button.is-text.is-focused .button.is-text:focus, .button.is-text:focus-visible, .button.is-text.is-focused
{ {
outline: 2px solid $text; outline-color: $text;
} }
.button.is-ghost:focus, .button.is-ghost:focus-visible, .button.is-ghost.is-focused .button.is-ghost:focus, .button.is-ghost:focus-visible, .button.is-ghost.is-focused
{ {
outline: 2px solid $text; outline-color: $text;
} }
.button.is-primary:focus, .button.is-primary:focus-visible, .button.is-primary.is-focused .button.is-primary:focus, .button.is-primary:focus-visible, .button.is-primary.is-focused
{ {
outline: 2px solid $primary; outline-color: $primary;
} }
.button.is-link:focus, .button.is-link:focus-visible, .button.is-link.is-focused .button.is-link:focus, .button.is-link:focus-visible, .button.is-link.is-focused
{ {
outline: 2px solid $link; outline-color: $link;
} }
.button.is-info:focus, .button.is-info:focus-visible, .button.is-info.is-focused .button.is-info:focus, .button.is-info:focus-visible, .button.is-info.is-focused
{ {
outline: 2px solid $info; outline-color: $info;
} }
.button.is-success:focus, .button.is-success:focus-visible, .button.is-success.is-focused .button.is-success:focus, .button.is-success:focus-visible, .button.is-success.is-focused
{ {
outline: 2px solid $success; outline-color: $success;
} }
.button.is-warning:focus, .button.is-warning:focus-visible, .button.is-warning.is-focused .button.is-warning:focus, .button.is-warning:focus-visible, .button.is-warning.is-focused
{ {
outline: 2px solid $warning; outline-color: $warning;
} }
.button.is-danger:focus, .button.is-danger:focus-visible, .button.is-danger.is-focused .button.is-danger:focus, .button.is-danger:focus-visible, .button.is-danger.is-focused
{ {
outline: 2px solid $danger; outline-color: $danger;
} }
a:focus, a:focus-visible button.is-double-focused:focus-visible
{ {
outline-style: double !important;
outline-width: 6px !important;
}
button.is-double-focused:focus:not(:focus-visible)
{
outline: none;
}
.file[role=button]:focus-visible {
border-radius: $radius;
}
.file[role=button].is-small:focus-visible {
border-radius: $radius-small;
}
.tag-button:focus-visible
{
border-color: transparent;
border-radius: 3px;
outline-width: 1px;
outline-style: solid;
outline-offset: 3px;
}
.tag-button:focus:not(:focus-visible)
{
outline: none;
}
.tag-button-link:focus-visible
{
outline-color: $link;
}
.tag-button-danger:focus-visible
{
outline-color: $danger;
}
.clear-selection
{
vertical-align: bottom;
}
.clear-selection:focus-visible
{
border-color: transparent;
outline-offset: 1px;
outline: 2px solid $text;
}
.clear-selection:focus:not(:focus-visible)
{
outline: none;
}
a:focus-visible
{
outline-offset: 2px;
border-radius: 3px; border-radius: 3px;
outline: 1px dashed $link; outline: 1px dashed $link;
} }
a:focus:not(:focus-visible)
{
outline: none;
}
a.has-text-black-bis:focus, a.has-text-black-bis:focus-visible { a.has-text-black-bis:focus, a.has-text-black-bis:focus-visible {
outline-color: $black-bis; outline-color: $black-bis;
} }
@ -518,19 +604,36 @@ a.has-text-white-bis:focus, a.has-text-white-bis:focus-visible {
outline-color: $white-bis; outline-color: $white-bis;
} }
.tabs a:focus, .tabs a:focus-visible { a.tag.is-dark.is-rounded:focus-visible
{
outline-offset: 1px;
outline: 1px solid $grey;
}
a.tag.is-dark.is-rounded:focus:not(:focus-visible)
{
outline: none;
}
.tabs a:focus-visible {
outline-offset: -4px; outline-offset: -4px;
} }
.tabs a:focus:not(:focus-visible)
{
outline: none;
}
.control.has-icons-right > span.icon:focus-visible, .control.has-icons-right > span.icon:focus-visible,
.control.has-icons-left > span.icon:focus-visible, .control.has-icons-left > span.icon:focus-visible
.control.has-icons-right > span.icon:focus,
.control.has-icons-left > span.icon:focus
{ {
outline: none; outline: none;
border: 1px solid $input-focus-border-color; border: 1px solid $input-focus-border-color;
box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color; box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color;
} }
.control.has-icons-right > span.icon:focus:not(:focus-visible),
.control.has-icons-left > span.icon:focus:not(:focus-visible)
{
outline: none;
}
.is-checkradio[type="checkbox"] + label:focus, .is-checkradio[type="checkbox"] + label:focus,
.is-checkradio[type="checkbox"] + label:focus-visible .is-checkradio[type="checkbox"] + label:focus-visible
@ -634,10 +737,6 @@ footer .field.is-grouped {
} }
} }
.notification .notification-title {
// Style for title line
}
.notification .notification-content { .notification .notification-content {
text-align: center; text-align: center;
} }
@ -773,7 +872,6 @@ footer .field.is-grouped {
margin: 0 5px; margin: 0 5px;
} }
.fadeInOut-enter-active { .fadeInOut-enter-active {
animation: fadeIn 500ms animation: fadeIn 500ms
} }