feat: create BaseButton component (#1123)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/1123
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
Dominik Pschenitschni 2022-01-04 18:58:06 +00:00
parent cb37fd773d
commit cdbd1c2ac4
39 changed files with 254 additions and 146 deletions

View file

@ -101,7 +101,7 @@ describe('Lists', () => {
.click() .click()
cy.url() cy.url()
.should('contain', '/settings/delete') .should('contain', '/settings/delete')
cy.get('.modal-mask .modal-container .modal-content .actions a.button') cy.get('[data-cy="modalPrimary"]')
.contains('Do it') .contains('Do it')
.click() .click()
@ -392,7 +392,7 @@ describe('Lists', () => {
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input') cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
.first() .first()
.type(3) .type(3)
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field a.button.is-primary') cy.get('[data-cy="setBucketLimit"]')
.first() .first()
.click() .click()

View file

@ -89,7 +89,7 @@ describe('Namepaces', () => {
.click() .click()
cy.url() cy.url()
.should('contain', '/settings/delete') .should('contain', '/settings/delete')
cy.get('.modal-mask .modal-container .modal-content .actions a.button') cy.get('[data-cy="modalPrimary"]')
.contains('Do it') .contains('Do it')
.click() .click()

View file

@ -168,7 +168,7 @@ describe('Task', () => {
.click() .click()
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll') cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
.type('{selectall}New Description') .type('{selectall}New Description')
cy.get('.task-view .details.content.description .editor a') cy.get('[data-cy="saveEditor"]')
.contains('Save') .contains('Save')
.click() .click()
@ -404,7 +404,7 @@ describe('Task', () => {
cy.get('.datepicker .datepicker-popup a') cy.get('.datepicker .datepicker-popup a')
.contains('Tomorrow') .contains('Tomorrow')
.click() .click()
cy.get('.datepicker .datepicker-popup a.button') cy.get('[data-cy="closeDatepicker"]')
.contains('Confirm') .contains('Confirm')
.click() .click()

View file

@ -18,7 +18,7 @@ describe('User Settings', () => {
.trigger('mousedown', {which: 1}) .trigger('mousedown', {which: 1})
.trigger('mousemove', {clientY: 100}) .trigger('mousemove', {clientY: 100})
.trigger('mouseup') .trigger('mouseup')
cy.get('a.button.is-primary') cy.get('[data-cy="uploadAvatar"]')
.contains('Upload Avatar') .contains('Upload Avatar')
.click() .click()
@ -33,7 +33,7 @@ describe('User Settings', () => {
cy.get('.general-settings .control input.input') cy.get('.general-settings .control input.input')
.first() .first()
.type('Lorem Ipsum') .type('Lorem Ipsum')
cy.get('.card.general-settings .button.is-primary') cy.get('[data-cy="saveGeneralSettings"]')
.contains('Save') .contains('Save')
.click() .click()

View file

@ -1,5 +1,6 @@
<template> <template>
<ready :class="{'is-touch': isTouch}"> <ready>
<div :class="{'is-touch': isTouch}">
<div :class="{'is-hidden': !online}"> <div :class="{'is-hidden': !online}">
<template v-if="authUser"> <template v-if="authUser">
<top-navigation/> <top-navigation/>
@ -15,6 +16,7 @@
<transition name="fade"> <transition name="fade">
<keyboard-shortcuts v-if="keyboardShortcutsActive"/> <keyboard-shortcuts v-if="keyboardShortcutsActive"/>
</transition> </transition>
</div>
</ready> </ready>
</template> </template>

View file

@ -0,0 +1,118 @@
<template>
<component
:is="componentNodeName"
class="base-button"
:class="{ 'base-button--type-button': isButton }"
v-bind="elementBindings"
:disabled="disabled || undefined"
>
<slot />
</component>
</template>
<script lang="ts">
// see https://v3.vuejs.org/api/sfc-script-setup.html#usage-alongside-normal-script
export default {
inheritAttrs: false,
}
</script>
<script lang="ts" setup>
// this component removes styling differences between links / vue-router links and button elements
// by doing so we make it easy abstract the functionality from style and enable easier and semantic
// correct button and link usage. Also see: https://css-tricks.com/a-complete-guide-to-links-and-buttons/#accessibility-considerations
// the component tries to heuristically determine what it should be checking the props (see the
// componentNodeName and elementBindings ref for this).
// NOTE: Do NOT use buttons with @click to push routes. => Use router-links instead!
import { ref, watchEffect, computed, useAttrs, PropType } from 'vue'
const BASE_BUTTON_TYPES_MAP = Object.freeze({
button: 'button',
submit: 'submit',
})
type BaseButtonTypes = keyof typeof BASE_BUTTON_TYPES_MAP
const props = defineProps({
type: {
type: String as PropType<BaseButtonTypes>,
default: 'button',
},
disabled: {
type: Boolean,
default: false,
},
})
const componentNodeName = ref<Node['nodeName']>('button')
interface ElementBindings {
type?: string;
rel?: string,
}
const elementBindings = ref({})
const attrs = useAttrs()
watchEffect(() => {
// by default this component is a button element with the attribute of the type "button" (default prop value)
let nodeName = 'button'
let bindings: ElementBindings = {type: props.type}
// if we find a "to" prop we set it as router-link
if ('to' in attrs) {
nodeName = 'router-link'
bindings = {}
}
// if there is a href we assume the user wants an external link via a link element
// we also set the attribute rel to "noopener" but make it possible to overwrite this by the user.
if ('href' in attrs) {
nodeName = 'a'
bindings = {rel: 'noopener'}
}
componentNodeName.value = nodeName
elementBindings.value = {
...bindings,
...attrs,
}
})
const isButton = computed(() => componentNodeName.value === 'button')
</script>
<style lang="scss">
// NOTE: we do not use scoped styles to reduce specifity and make it easy to overwrite
// We reset the default styles of a button element to enable easier styling
:where(.base-button--type-button) {
border: 0;
margin: 0;
padding: 0;
text-decoration: none;
background-color: transparent;
text-align: center;
appearance: none;
}
:where(.base-button) {
cursor: pointer;
display: block;
color: inherit;
font: inherit;
user-select: none;
pointer-events: auto; // disable possible resets
&:focus {
outline: transparent;
}
&[disabled] {
cursor: default;
}
}
</style>

View file

@ -37,7 +37,7 @@
<dropdown class="is-right" ref="usernameDropdown"> <dropdown class="is-right" ref="usernameDropdown">
<template #trigger> <template #trigger>
<x-button <x-button
type="secondary" variant="secondary"
:shadow="false"> :shadow="false">
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span> <span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
<span class="icon is-small"> <span class="icon is-small">

View file

@ -1,41 +1,45 @@
<template> <template>
<a <BaseButton
class="button" class="button"
:class="{ :class="[
variantClass,
{
'is-loading': loading, 'is-loading': loading,
'has-no-shadow': !shadow, 'has-no-shadow': !shadow || variant === 'tertiary',
'is-primary': type === 'primary', }
'is-outlined': type === 'secondary', ]"
'is-text is-inverted has-no-shadow underline-none':
type === 'tertary',
}"
:disabled="disabled || null"
@click="click"
:href="href !== '' ? href : null"
> >
<icon :icon="icon" v-if="showIconOnly"/> <icon :icon="icon" v-if="showIconOnly"/>
<span class="icon is-small" v-else-if="icon !== ''"> <span class="icon is-small" v-else-if="icon !== ''">
<icon :icon="icon"/> <icon :icon="icon"/>
</span> </span>
<slot></slot> <slot />
</a> </BaseButton>
</template> </template>
<script> <script lang="ts">
export default { export default {
name: 'x-button', name: 'x-button',
props: { }
type: { </script>
type: String,
<script setup lang="ts">
import {computed, useSlots, PropType} from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
const BUTTON_TYPES_MAP = Object.freeze({
primary: 'is-primary',
secondary: 'is-outlined',
tertiary: 'is-text is-inverted underline-none',
})
type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
const props = defineProps({
variant: {
type: String as PropType<ButtonTypes>,
default: 'primary', default: 'primary',
}, },
href: {
type: String,
default: '',
},
to: {
default: false,
},
icon: { icon: {
default: '', default: '',
}, },
@ -47,31 +51,12 @@ export default {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
disabled: { })
type: Boolean,
default: false,
},
},
emits: ['click'],
computed: {
showIconOnly() {
return this.icon !== '' && typeof this.$slots.default === 'undefined'
},
},
methods: {
click(e) {
if (this.disabled) {
return
}
if (this.to !== false) { const variantClass = computed(() => BUTTON_TYPES_MAP[props.variant])
this.$router.push(this.to)
}
this.$emit('click', e) const slots = useSlots()
}, const showIconOnly = computed(() => props.icon !== '' && typeof slots.default === 'undefined')
},
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -83,8 +68,8 @@ export default {
font-weight: bold; font-weight: bold;
height: $button-height; height: $button-height;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
display: inline-flex;
&.is-hovered,
&:hover { &:hover {
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
} }
@ -106,9 +91,10 @@ export default {
color: var(--white); color: var(--white);
} }
&.is-small { }
.is-small {
border-radius: $radius; border-radius: $radius;
}
} }
.underline-none { .underline-none {

View file

@ -27,7 +27,7 @@
@click="reset" @click="reset"
class="is-small ml-2" class="is-small ml-2"
:shadow="false" :shadow="false"
type="secondary" variant="secondary"
> >
{{ $t('input.resetColor') }} {{ $t('input.resetColor') }}
</x-button> </x-button>

View file

@ -101,6 +101,7 @@
class="is-fullwidth" class="is-fullwidth"
:shadow="false" :shadow="false"
@click="close" @click="close"
v-cy="'closeDatepicker'"
> >
{{ $t('misc.confirm') }} {{ $t('misc.confirm') }}
</x-button> </x-button>

View file

@ -35,7 +35,7 @@
<a @click="toggleEdit">{{ $t('input.editor.edit') }}</a> <a @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
</li> </li>
</ul> </ul>
<x-button v-else-if="isEditActive" @click="toggleEdit" type="secondary" :shadow="false"> <x-button v-else-if="isEditActive" @click="toggleEdit" variant="secondary" :shadow="false" v-cy="'saveEditor'">
{{ $t('misc.save') }} {{ $t('misc.save') }}
</x-button> </x-button>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<x-button <x-button
v-if="hasFilters" v-if="hasFilters"
type="secondary" variant="secondary"
@click="clearFilters" @click="clearFilters"
> >
{{ $t('filters.clear') }} {{ $t('filters.clear') }}
@ -10,7 +10,7 @@
<template #trigger="{toggle}"> <template #trigger="{toggle}">
<x-button <x-button
@click.prevent.stop="toggle()" @click.prevent.stop="toggle()"
type="secondary" variant="secondary"
icon="filter" icon="filter"
> >
{{ $t('filters.title') }} {{ $t('filters.title') }}

View file

@ -14,25 +14,25 @@
</div> </div>
<footer class="modal-card-foot is-flex is-justify-content-flex-end"> <footer class="modal-card-foot is-flex is-justify-content-flex-end">
<x-button <x-button
v-if="tertiary !== ''"
:shadow="false" :shadow="false"
type="tertary" variant="tertiary"
@click.prevent.stop="$emit('tertary')" @click.prevent.stop="$emit('tertiary')"
v-if="tertary !== ''"
> >
{{ tertary }} {{ tertiary }}
</x-button> </x-button>
<x-button <x-button
type="secondary" variant="secondary"
@click.prevent.stop="$router.back()" @click.prevent.stop="$router.back()"
> >
{{ $t('misc.cancel') }} {{ $t('misc.cancel') }}
</x-button> </x-button>
<x-button <x-button
type="primary" v-if="primaryLabel !== ''"
variant="primary"
@click.prevent.stop="primary" @click.prevent.stop="primary"
:icon="primaryIcon" :icon="primaryIcon"
:disabled="primaryDisabled" :disabled="primaryDisabled"
v-if="primaryLabel !== ''"
> >
{{ primaryLabel }} {{ primaryLabel }}
</x-button> </x-button>
@ -65,7 +65,7 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
tertary: { tertiary: {
type: String, type: String,
default: '', default: '',
}, },
@ -78,7 +78,7 @@ export default {
default: false, default: false,
}, },
}, },
emits: ['create', 'primary', 'tertary'], emits: ['create', 'primary', 'tertiary'],
methods: { methods: {
primary() { primary() {
this.$emit('create') this.$emit('create')

View file

@ -26,7 +26,7 @@
@click="action.callback" @click="action.callback"
:shadow="false" :shadow="false"
class="is-small" class="is-small"
type="secondary" variant="secondary"
v-for="(action, i) in item.data.actions" v-for="(action, i) in item.data.actions"
> >
{{ action.title }} {{ action.title }}

View file

@ -1,6 +1,6 @@
<template> <template>
<x-button <x-button
type="secondary" variant="secondary"
:icon="icon" :icon="icon"
v-tooltip="tooltipText" v-tooltip="tooltipText"
@click="changeSubscription" @click="changeSubscription"

View file

@ -31,14 +31,15 @@
<div class="actions"> <div class="actions">
<x-button <x-button
@click="$emit('close')" @click="$emit('close')"
type="tertary" variant="tertiary"
class="has-text-danger" class="has-text-danger"
> >
{{ $t('misc.cancel') }} {{ $t('misc.cancel') }}
</x-button> </x-button>
<x-button <x-button
@click="$emit('submit')" @click="$emit('submit')"
type="primary" variant="primary"
v-cy="'modalPrimary'"
:shadow="false" :shadow="false"
> >
{{ $t('misc.doit') }} {{ $t('misc.doit') }}

View file

@ -83,7 +83,7 @@
@click="$refs.files.click()" @click="$refs.files.click()"
class="mb-4" class="mb-4"
icon="cloud-upload-alt" icon="cloud-upload-alt"
type="secondary" variant="secondary"
:shadow="false" :shadow="false"
> >
{{ $t('task.attachment.upload') }} {{ $t('task.attachment.upload') }}

View file

@ -8,21 +8,21 @@
<x-button <x-button
@click.prevent.stop="() => deferDays(1)" @click.prevent.stop="() => deferDays(1)"
:shadow="false" :shadow="false"
type="secondary" variant="secondary"
> >
{{ $t('task.deferDueDate.1day') }} {{ $t('task.deferDueDate.1day') }}
</x-button> </x-button>
<x-button <x-button
@click.prevent.stop="() => deferDays(3)" @click.prevent.stop="() => deferDays(3)"
:shadow="false" :shadow="false"
type="secondary" variant="secondary"
> >
{{ $t('task.deferDueDate.3days') }} {{ $t('task.deferDueDate.3days') }}
</x-button> </x-button>
<x-button <x-button
@click.prevent.stop="() => deferDays(7)" @click.prevent.stop="() => deferDays(7)"
:shadow="false" :shadow="false"
type="secondary" variant="secondary"
> >
{{ $t('task.deferDueDate.1week') }} {{ $t('task.deferDueDate.1week') }}
</x-button> </x-button>

View file

@ -6,7 +6,7 @@
class="is-pulled-right add-task-relation-button" class="is-pulled-right add-task-relation-button"
:class="{'is-active': showNewRelationForm}" :class="{'is-active': showNewRelationForm}"
v-tooltip="$t('task.relation.add')" v-tooltip="$t('task.relation.add')"
type="secondary" variant="secondary"
icon="plus" icon="plus"
:shadow="false" :shadow="false"
/> />

View file

@ -1,9 +1,9 @@
<template> <template>
<div class="control repeat-after-input"> <div class="control repeat-after-input">
<div class="buttons has-addons is-centered mt-2"> <div class="buttons has-addons is-centered mt-2">
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'days')">{{ $t('task.repeat.everyDay') }}</x-button> <x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'days')">{{ $t('task.repeat.everyDay') }}</x-button>
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'weeks')">{{ $t('task.repeat.everyWeek') }}</x-button> <x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'weeks')">{{ $t('task.repeat.everyWeek') }}</x-button>
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'months')">{{ $t('task.repeat.everyMonth') }}</x-button> <x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'months')">{{ $t('task.repeat.everyMonth') }}</x-button>
</div> </div>
<div class="is-flex is-align-items-center mb-2"> <div class="is-flex is-align-items-center mb-2">
<label for="repeatMode" class="is-fullwidth"> <label for="repeatMode" class="is-fullwidth">

View file

@ -1,11 +1,8 @@
import {createApp, configureCompat} from 'vue' import {createApp, configureCompat} from 'vue'
// default everything to Vue 3 behavior
configureCompat({ configureCompat({
COMPONENT_V_MODEL: false, MODE: 3,
COMPONENT_ASYNC: false,
RENDER_FUNCTION: false,
WATCH_ARRAY: false, // TODO: check this again; this might lead to some problemes
TRANSITION_GROUP_ROOT: false,
}) })
import App from './App.vue' import App from './App.vue'

View file

@ -21,7 +21,7 @@
</div> </div>
<footer class="modal-card-foot is-flex is-justify-content-flex-end"> <footer class="modal-card-foot is-flex is-justify-content-flex-end">
<x-button <x-button
type="secondary" variant="secondary"
@click.prevent.stop="$router.back()" @click.prevent.stop="$router.back()"
> >
{{ $t('misc.close') }} {{ $t('misc.close') }}

View file

@ -4,8 +4,8 @@
primary-icon="" primary-icon=""
:primary-label="$t('misc.save')" :primary-label="$t('misc.save')"
@primary="saveSavedFilter" @primary="saveSavedFilter"
:tertary="$t('misc.delete')" :tertiary="$t('misc.delete')"
@tertary="$router.push({ name: 'filter.settings.delete', params: { id: $route.params.listId } })" @tertiary="$router.push({ name: 'filter.settings.delete', params: { id: $route.params.listId } })"
> >
<form @submit.prevent="saveSavedFilter()"> <form @submit.prevent="saveSavedFilter()">
<div class="field"> <div class="field">

View file

@ -6,8 +6,8 @@
class="list-background-setting" class="list-background-setting"
:wide="true" :wide="true"
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled" v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
:tertary="hasBackground ? $t('list.background.remove') : ''" :tertiary="hasBackground ? $t('list.background.remove') : ''"
@tertary="removeBackground()" @tertiary="removeBackground()"
> >
<div class="mb-4" v-if="uploadBackgroundEnabled"> <div class="mb-4" v-if="uploadBackgroundEnabled">
<input <input
@ -20,7 +20,7 @@
<x-button <x-button
:loading="backgroundUploadService.loading" :loading="backgroundUploadService.loading"
@click="$refs.backgroundUploadInput.click()" @click="$refs.backgroundUploadInput.click()"
type="primary" variant="primary"
> >
{{ $t('list.background.upload') }} {{ $t('list.background.upload') }}
</x-button> </x-button>
@ -54,7 +54,7 @@
@click="() => searchBackgrounds(currentPage + 1)" @click="() => searchBackgrounds(currentPage + 1)"
class="is-load-more-button mt-4" class="is-load-more-button mt-4"
:shadow="false" :shadow="false"
type="secondary" variant="secondary"
v-if="backgroundSearchResult.length > 0" v-if="backgroundSearchResult.length > 0"
> >
{{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }} {{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }}

View file

@ -4,8 +4,8 @@
primary-icon="" primary-icon=""
:primary-label="$t('misc.save')" :primary-label="$t('misc.save')"
@primary="save" @primary="save"
:tertary="$t('misc.delete')" :tertiary="$t('misc.delete')"
@tertary="$router.push({ name: 'list.list.settings.delete', params: { id: $route.params.listId } })" @tertiary="$router.push({ name: 'list.list.settings.delete', params: { id: $route.params.listId } })"
> >
<div class="field"> <div class="field">
<label class="label" for="title">{{ $t('list.title') }}</label> <label class="label" for="title">{{ $t('list.title') }}</label>

View file

@ -79,6 +79,7 @@
:disabled="bucket.limit < 0" :disabled="bucket.limit < 0"
:icon="['far', 'save']" :icon="['far', 'save']"
:shadow="false" :shadow="false"
v-cy="'setBucketLimit'"
/> />
</div> </div>
</div> </div>
@ -165,7 +166,7 @@
:shadow="false" :shadow="false"
v-if="!showNewTaskInput[bucket.id]" v-if="!showNewTaskInput[bucket.id]"
icon="plus" icon="plus"
type="secondary" variant="secondary"
> >
{{ {{
bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask') bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask')
@ -195,7 +196,7 @@
:shadow="false" :shadow="false"
class="is-transparent is-fullwidth has-text-centered" class="is-transparent is-fullwidth has-text-centered"
v-else v-else
type="secondary" variant="secondary"
icon="plus" icon="plus"
> >
{{ $t('list.kanban.addBucket') }} {{ $t('list.kanban.addBucket') }}

View file

@ -37,7 +37,7 @@
<x-button <x-button
@click="showTaskSearch = !showTaskSearch" @click="showTaskSearch = !showTaskSearch"
icon="search" icon="search"
type="secondary" variant="secondary"
v-if="!showTaskSearch" v-if="!showTaskSearch"
/> />
</div> </div>

View file

@ -7,7 +7,7 @@
<x-button <x-button
@click.prevent.stop="toggle()" @click.prevent.stop="toggle()"
icon="th" icon="th"
type="secondary" variant="secondary"
> >
{{ $t('list.table.columns') }} {{ $t('list.table.columns') }}
</x-button> </x-button>

View file

@ -53,7 +53,7 @@
</p> </p>
<div class="buttons"> <div class="buttons">
<x-button @click="migrate">{{ $t('migrate.confirm') }}</x-button> <x-button @click="migrate">{{ $t('migrate.confirm') }}</x-button>
<x-button :to="{name: 'home'}" type="tertary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button> <x-button :to="{name: 'home'}" variant="tertiary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button>
</div> </div>
</div> </div>
<div v-else> <div v-else>

View file

@ -22,7 +22,7 @@
<x-button <x-button
:to="{name: 'list.create', params: {id: n.id}}" :to="{name: 'list.create', params: {id: n.id}}"
class="is-pulled-right" class="is-pulled-right"
type="secondary" variant="secondary"
v-if="n.id > 0 && n.lists.length > 0" v-if="n.id > 0 && n.lists.length > 0"
icon="plus" icon="plus"
> >
@ -31,7 +31,7 @@
<x-button <x-button
:to="{name: 'namespace.settings.archive', params: {id: n.id}}" :to="{name: 'namespace.settings.archive', params: {id: n.id}}"
class="is-pulled-right mr-4" class="is-pulled-right mr-4"
type="secondary" variant="secondary"
v-if="n.isArchived" v-if="n.isArchived"
icon="archive" icon="archive"
> >

View file

@ -4,8 +4,8 @@
primary-icon="" primary-icon=""
:primary-label="$t('misc.save')" :primary-label="$t('misc.save')"
@primary="save" @primary="save"
:tertary="$t('misc.delete')" :tertiary="$t('misc.delete')"
@tertary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })" @tertiary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })"
> >
<form @submit.prevent="save()"> <form @submit.prevent="save()">
<div class="field"> <div class="field">

View file

@ -32,9 +32,9 @@
/> />
</h3> </h3>
<div v-if="!showAll" class="mb-4"> <div v-if="!showAll" class="mb-4">
<x-button type="secondary" @click="showTodaysTasks()" class="mr-2">{{ $t('task.show.today') }}</x-button> <x-button variant="secondary" @click="showTodaysTasks()" class="mr-2">{{ $t('task.show.today') }}</x-button>
<x-button type="secondary" @click="setDatesToNextWeek()" class="mr-2">{{ $t('task.show.nextWeek') }}</x-button> <x-button variant="secondary" @click="setDatesToNextWeek()" class="mr-2">{{ $t('task.show.nextWeek') }}</x-button>
<x-button type="secondary" @click="setDatesToNextMonth()">{{ $t('task.show.nextMonth') }}</x-button> <x-button variant="secondary" @click="setDatesToNextMonth()">{{ $t('task.show.nextMonth') }}</x-button>
</div> </div>
<template v-if="!loading && (!tasks || tasks.length === 0) && showNothingToDo"> <template v-if="!loading && (!tasks || tasks.length === 0) && showNothingToDo">
<h3 class="nothing">{{ $t('task.show.noTasks') }}</h3> <h3 class="nothing">{{ $t('task.show.noTasks') }}</h3>

View file

@ -258,7 +258,7 @@
@click="toggleTaskDone()" @click="toggleTaskDone()"
class="is-outlined has-no-border" class="is-outlined has-no-border"
icon="check-double" icon="check-double"
type="secondary" variant="secondary"
> >
{{ task.done ? $t('task.detail.undone') : $t('task.detail.done') }} {{ task.done ? $t('task.detail.undone') : $t('task.detail.done') }}
</x-button> </x-button>
@ -270,7 +270,7 @@
/> />
<x-button <x-button
@click="setFieldActive('assignees')" @click="setFieldActive('assignees')"
type="secondary" variant="secondary"
v-shortcut="'a'" v-shortcut="'a'"
v-cy="'taskDetail.assign'" v-cy="'taskDetail.assign'"
> >
@ -279,7 +279,7 @@
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('labels')" @click="setFieldActive('labels')"
type="secondary" variant="secondary"
icon="tags" icon="tags"
v-shortcut="'l'" v-shortcut="'l'"
> >
@ -287,14 +287,14 @@
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('priority')" @click="setFieldActive('priority')"
type="secondary" variant="secondary"
icon="exclamation" icon="exclamation"
> >
{{ $t('task.detail.actions.priority') }} {{ $t('task.detail.actions.priority') }}
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('dueDate')" @click="setFieldActive('dueDate')"
type="secondary" variant="secondary"
icon="calendar" icon="calendar"
v-shortcut="'d'" v-shortcut="'d'"
> >
@ -302,42 +302,42 @@
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('startDate')" @click="setFieldActive('startDate')"
type="secondary" variant="secondary"
icon="play" icon="play"
> >
{{ $t('task.detail.actions.startDate') }} {{ $t('task.detail.actions.startDate') }}
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('endDate')" @click="setFieldActive('endDate')"
type="secondary" variant="secondary"
icon="stop" icon="stop"
> >
{{ $t('task.detail.actions.endDate') }} {{ $t('task.detail.actions.endDate') }}
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('reminders')" @click="setFieldActive('reminders')"
type="secondary" variant="secondary"
:icon="['far', 'clock']" :icon="['far', 'clock']"
> >
{{ $t('task.detail.actions.reminders') }} {{ $t('task.detail.actions.reminders') }}
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('repeatAfter')" @click="setFieldActive('repeatAfter')"
type="secondary" variant="secondary"
icon="history" icon="history"
> >
{{ $t('task.detail.actions.repeatAfter') }} {{ $t('task.detail.actions.repeatAfter') }}
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('percentDone')" @click="setFieldActive('percentDone')"
type="secondary" variant="secondary"
icon="percent" icon="percent"
> >
{{ $t('task.detail.actions.percentDone') }} {{ $t('task.detail.actions.percentDone') }}
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('attachments')" @click="setFieldActive('attachments')"
type="secondary" variant="secondary"
icon="paperclip" icon="paperclip"
v-shortcut="'f'" v-shortcut="'f'"
> >
@ -345,7 +345,7 @@
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('relatedTasks')" @click="setFieldActive('relatedTasks')"
type="secondary" variant="secondary"
icon="sitemap" icon="sitemap"
v-shortcut="'r'" v-shortcut="'r'"
> >
@ -353,21 +353,21 @@
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('moveList')" @click="setFieldActive('moveList')"
type="secondary" variant="secondary"
icon="list" icon="list"
> >
{{ $t('task.detail.actions.moveList') }} {{ $t('task.detail.actions.moveList') }}
</x-button> </x-button>
<x-button <x-button
@click="setFieldActive('color')" @click="setFieldActive('color')"
type="secondary" variant="secondary"
icon="fill-drip" icon="fill-drip"
> >
{{ $t('task.detail.actions.color') }} {{ $t('task.detail.actions.color') }}
</x-button> </x-button>
<x-button <x-button
@click="toggleFavorite" @click="toggleFavorite"
type="secondary" variant="secondary"
:icon="task.isFavorite ? 'star' : ['far', 'star']" :icon="task.isFavorite ? 'star' : ['far', 'star']"
> >
{{ {{

View file

@ -67,7 +67,7 @@
<x-button <x-button
:to="{ name: 'user.register' }" :to="{ name: 'user.register' }"
v-if="registrationEnabled" v-if="registrationEnabled"
type="secondary" variant="secondary"
> >
{{ $t('user.auth.register') }} {{ $t('user.auth.register') }}
</x-button> </x-button>
@ -87,7 +87,7 @@
@click="redirectToProvider(p)" @click="redirectToProvider(p)"
v-for="(p, k) in openidConnect.providers" v-for="(p, k) in openidConnect.providers"
:key="k" :key="k"
type="secondary" variant="secondary"
class="is-fullwidth mt-2" class="is-fullwidth mt-2"
> >
{{ $t('user.auth.loginWith', {provider: p.name}) }} {{ $t('user.auth.loginWith', {provider: p.name}) }}

View file

@ -79,7 +79,7 @@
> >
{{ $t('user.auth.register') }} {{ $t('user.auth.register') }}
</x-button> </x-button>
<x-button :to="{ name: 'user.login' }" type="secondary"> <x-button :to="{ name: 'user.login' }" variant="secondary">
{{ $t('user.auth.login') }} {{ $t('user.auth.login') }}
</x-button> </x-button>
</div> </div>

View file

@ -35,7 +35,7 @@
> >
{{ $t('user.auth.resetPasswordAction') }} {{ $t('user.auth.resetPasswordAction') }}
</x-button> </x-button>
<x-button :to="{ name: 'user.login' }" type="secondary"> <x-button :to="{ name: 'user.login' }" variant="secondary">
{{ $t('user.auth.login') }} {{ $t('user.auth.login') }}
</x-button> </x-button>
</div> </div>

View file

@ -48,6 +48,7 @@
<x-button <x-button
:loading="avatarService.loading || loading" :loading="avatarService.loading || loading"
@click="uploadAvatar" @click="uploadAvatar"
v-cy="'uploadAvatar'"
> >
{{ $t('user.settings.avatar.uploadAvatar') }} {{ $t('user.settings.avatar.uploadAvatar') }}
</x-button> </x-button>

View file

@ -110,6 +110,7 @@
:loading="loading" :loading="loading"
@click="updateSettings()" @click="updateSettings()"
class="is-fullwidth mt-4" class="is-fullwidth mt-4"
v-cy="'saveGeneralSettings'"
> >
{{ $t('misc.save') }} {{ $t('misc.save') }}
</x-button> </x-button>

View file

@ -55,7 +55,7 @@
<x-button @click="totpDisable" class="is-danger"> <x-button @click="totpDisable" class="is-danger">
{{ $t('user.settings.totp.disable') }} {{ $t('user.settings.totp.disable') }}
</x-button> </x-button>
<x-button @click="totpDisableForm = false" type="tertary" class="ml-2"> <x-button @click="totpDisableForm = false" variant="tertiary" class="ml-2">
{{ $t('misc.cancel') }} {{ $t('misc.cancel') }}
</x-button> </x-button>
</div> </div>