chore: use the <dropdown> and <dropdown-item> components everywhere
Resolves https://kolaente.dev/vikunja/frontend/issues/2176
This commit is contained in:
parent
d6a10b01dd
commit
cdb63b578d
11 changed files with 146 additions and 76 deletions
|
@ -554,8 +554,4 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
.namespaces-list.loader-container.is-loading {
|
.namespaces-list.loader-container.is-loading {
|
||||||
min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
|
min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
|
||||||
}
|
}
|
||||||
|
|
||||||
a.dropdown-item:hover {
|
|
||||||
background: var(--dropdown-item-hover-background-color) !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -56,12 +56,13 @@
|
||||||
{{ $t('menu.archive') }}
|
{{ $t('menu.archive') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
<task-subscription
|
<task-subscription
|
||||||
class="dropdown-item has-no-shadow"
|
class="has-no-shadow"
|
||||||
:is-button="false"
|
:is-button="false"
|
||||||
entity="list"
|
entity="list"
|
||||||
:entity-id="list.id"
|
:entity-id="list.id"
|
||||||
:subscription="list.subscription"
|
:subscription="list.subscription"
|
||||||
@change="sub => subscription = sub"
|
@change="sub => subscription = sub"
|
||||||
|
type="dropdown"
|
||||||
/>
|
/>
|
||||||
<dropdown-item
|
<dropdown-item
|
||||||
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
||||||
|
|
|
@ -1,25 +1,95 @@
|
||||||
<template>
|
<template>
|
||||||
<router-link
|
<component
|
||||||
|
:is="componentNodeName"
|
||||||
|
v-bind="elementBindings"
|
||||||
:to="to"
|
:to="to"
|
||||||
class="dropdown-item">
|
class="dropdown-item">
|
||||||
<span class="icon" v-if="icon !== ''">
|
<span class="icon" v-if="icon">
|
||||||
<icon :icon="icon"/>
|
<icon :icon="icon"/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps({
|
import {ref, useAttrs, watchEffect} from 'vue'
|
||||||
to: {
|
|
||||||
required: true,
|
const props = defineProps<{
|
||||||
},
|
to?: object,
|
||||||
icon: {
|
icon?: string | string[],
|
||||||
type: String,
|
}>()
|
||||||
required: false,
|
|
||||||
default: '',
|
const componentNodeName = ref<Node['nodeName']>('a')
|
||||||
},
|
const elementBindings = ref({})
|
||||||
|
|
||||||
|
const attrs = useAttrs()
|
||||||
|
watchEffect(() => {
|
||||||
|
let nodeName = 'a'
|
||||||
|
|
||||||
|
if (props.to) {
|
||||||
|
nodeName = 'router-link'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('href' in attrs) {
|
||||||
|
nodeName = 'BaseButton'
|
||||||
|
}
|
||||||
|
|
||||||
|
componentNodeName.value = nodeName
|
||||||
|
elementBindings.value = {
|
||||||
|
...attrs,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dropdown-item {
|
||||||
|
color: var(--text);
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: $item-padding;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.dropdown-item,
|
||||||
|
button.dropdown-item {
|
||||||
|
text-align: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: left !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--grey-100) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: var(--link);
|
||||||
|
color: var(--link-invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
padding-right: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon:not(.has-text-success) {
|
||||||
|
color: var(--grey-300) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-text-danger .icon {
|
||||||
|
color: var(--danger) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown is-right is-active" ref="dropdown">
|
<div class="dropdown" ref="dropdown">
|
||||||
<slot name="trigger" :close="close" :toggleOpen="toggleOpen">
|
<slot name="trigger" :close="close" :toggleOpen="toggleOpen">
|
||||||
<BaseButton class="dropdown-trigger is-flex" @click="toggleOpen">
|
<BaseButton class="dropdown-trigger is-flex" @click="toggleOpen">
|
||||||
<icon :icon="triggerIcon" class="icon"/>
|
<icon :icon="triggerIcon" class="icon"/>
|
||||||
|
@ -51,7 +51,36 @@ onClickOutside(dropdown, (e: Event) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.dropdown-menu .dropdown-content {
|
.dropdown {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
min-width: 12rem;
|
||||||
|
padding-top: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 20;
|
||||||
|
display: block;
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
background-color: var(--scheme-main);
|
||||||
|
border-radius: $radius;
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
padding-top: .5rem;
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
.dropdown-divider {
|
||||||
|
background-color: var(--border-light);
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<x-button
|
<x-button
|
||||||
v-if="isButton"
|
v-if="type === 'button'"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
:icon="iconName"
|
:icon="iconName"
|
||||||
v-tooltip="tooltipText"
|
v-tooltip="tooltipText"
|
||||||
|
@ -9,6 +9,15 @@
|
||||||
>
|
>
|
||||||
{{ buttonText }}
|
{{ buttonText }}
|
||||||
</x-button>
|
</x-button>
|
||||||
|
<DropdownItem
|
||||||
|
v-else-if="type === 'dropdown'"
|
||||||
|
v-tooltip="tooltipText"
|
||||||
|
@click="changeSubscription"
|
||||||
|
:class="{'is-disabled': disabled}"
|
||||||
|
:icon="iconName"
|
||||||
|
>
|
||||||
|
{{ buttonText }}
|
||||||
|
</DropdownItem>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-else
|
v-else
|
||||||
v-tooltip="tooltipText"
|
v-tooltip="tooltipText"
|
||||||
|
@ -27,6 +36,7 @@ import {computed, shallowRef} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||||
|
|
||||||
import SubscriptionService from '@/services/subscription'
|
import SubscriptionService from '@/services/subscription'
|
||||||
import SubscriptionModel from '@/models/subscription'
|
import SubscriptionModel from '@/models/subscription'
|
||||||
|
@ -34,15 +44,15 @@ import SubscriptionModel from '@/models/subscription'
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
entity: string
|
entity: string
|
||||||
entityId: number
|
entityId: number
|
||||||
subscription: SubscriptionModel | null
|
subscription: SubscriptionModel | null
|
||||||
isButton?: boolean
|
type?: 'button' | 'dropdown' | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
isButton: true,
|
|
||||||
subscription: null,
|
subscription: null,
|
||||||
|
type: 'button',
|
||||||
})
|
})
|
||||||
|
|
||||||
const subscriptionEntity = computed<string | null>(() => props.subscription?.entity ?? null)
|
const subscriptionEntity = computed<string | null>(() => props.subscription?.entity ?? null)
|
||||||
|
|
|
@ -34,12 +34,13 @@
|
||||||
{{ $t('menu.archive') }}
|
{{ $t('menu.archive') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
<task-subscription
|
<task-subscription
|
||||||
class="dropdown-item has-no-shadow"
|
class="has-no-shadow"
|
||||||
:is-button="false"
|
:is-button="false"
|
||||||
entity="namespace"
|
entity="namespace"
|
||||||
:entity-id="namespace.id"
|
:entity-id="namespace.id"
|
||||||
:subscription="subscription"
|
:subscription="subscription"
|
||||||
@change="sub => subscription = sub"
|
@change="sub => subscription = sub"
|
||||||
|
type="dropdown"
|
||||||
/>
|
/>
|
||||||
<dropdown-item
|
<dropdown-item
|
||||||
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
||||||
|
|
|
@ -50,7 +50,6 @@ const props = defineProps({
|
||||||
|
|
||||||
for (const e of prop) {
|
for (const e of prop) {
|
||||||
if (!isDate(e) && !isString(e)) {
|
if (!isDate(e) && !isString(e)) {
|
||||||
console.log('validation failed', e, e instanceof Date)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,6 @@ $vikunja-font: 'Quicksand', sans-serif;
|
||||||
$pagination-current-border: var(--primary);
|
$pagination-current-border: var(--primary);
|
||||||
$navbar-item-active-color: var(--primary);
|
$navbar-item-active-color: var(--primary);
|
||||||
|
|
||||||
$dropdown-content-shadow: none;
|
|
||||||
$dropdown-item-hover-background-color: var(--grey-100);
|
|
||||||
|
|
||||||
$site-background: var(--grey-100);
|
$site-background: var(--grey-100);
|
||||||
|
|
||||||
$transition-duration: 150ms;
|
$transition-duration: 150ms;
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
// imports from "bulma-css-variables/sass/components/_all";
|
// imports from "bulma-css-variables/sass/components/_all";
|
||||||
// @import "bulma-css-variables/sass/components/breadcrumb"; // not used
|
// @import "bulma-css-variables/sass/components/breadcrumb"; // not used
|
||||||
@import "bulma-css-variables/sass/components/card";
|
@import "bulma-css-variables/sass/components/card";
|
||||||
@import "bulma-css-variables/sass/components/dropdown";
|
// @import "bulma-css-variables/sass/components/dropdown"; // moved to component
|
||||||
// @import "bulma-css-variables/sass/components/level"; // not used
|
// @import "bulma-css-variables/sass/components/level"; // not used
|
||||||
@import "bulma-css-variables/sass/components/media";
|
@import "bulma-css-variables/sass/components/media";
|
||||||
@import "bulma-css-variables/sass/components/menu";
|
@import "bulma-css-variables/sass/components/menu";
|
||||||
|
|
|
@ -91,34 +91,6 @@ button.table {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: merge with dropdown-item.vue
|
|
||||||
// for this to happen the component has to be used everywhere
|
|
||||||
.dropdown-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left !important;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
padding-right: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon:not(.has-text-success) {
|
|
||||||
color: var(--grey-300) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-text-danger .icon {
|
|
||||||
color: var(--danger) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-max-width-desktop {
|
.is-max-width-desktop {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: $desktop;
|
max-width: $desktop;
|
||||||
|
|
|
@ -62,9 +62,8 @@
|
||||||
trigger-icon="ellipsis-v"
|
trigger-icon="ellipsis-v"
|
||||||
@close="() => showSetLimitInput = false"
|
@close="() => showSetLimitInput = false"
|
||||||
>
|
>
|
||||||
<a
|
<dropdown-item
|
||||||
@click.stop="showSetLimitInput = true"
|
@click.stop="showSetLimitInput = true"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
<div class="field has-addons" v-if="showSetLimitInput">
|
<div class="field has-addons" v-if="showSetLimitInput">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
@ -93,34 +92,32 @@
|
||||||
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</dropdown-item>
|
||||||
<a
|
<dropdown-item
|
||||||
@click.stop="toggleDoneBucket(bucket)"
|
@click.stop="toggleDoneBucket(bucket)"
|
||||||
class="dropdown-item"
|
|
||||||
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
||||||
>
|
>
|
||||||
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}">
|
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}">
|
||||||
<icon icon="check-double"/>
|
<icon icon="check-double"/>
|
||||||
</span>
|
</span>
|
||||||
{{ $t('list.kanban.doneBucket') }}
|
{{ $t('list.kanban.doneBucket') }}
|
||||||
</a>
|
</dropdown-item>
|
||||||
<a
|
<dropdown-item
|
||||||
class="dropdown-item"
|
|
||||||
@click.stop="() => collapseBucket(bucket)"
|
@click.stop="() => collapseBucket(bucket)"
|
||||||
>
|
>
|
||||||
{{ $t('list.kanban.collapse') }}
|
{{ $t('list.kanban.collapse') }}
|
||||||
</a>
|
</dropdown-item>
|
||||||
<a
|
<dropdown-item
|
||||||
:class="{'is-disabled': buckets.length <= 1}"
|
:class="{'is-disabled': buckets.length <= 1}"
|
||||||
@click.stop="() => deleteBucketModal(bucket.id)"
|
@click.stop="() => deleteBucketModal(bucket.id)"
|
||||||
class="dropdown-item has-text-danger"
|
class="has-text-danger"
|
||||||
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
||||||
>
|
>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<icon icon="trash-alt"/>
|
<icon icon="trash-alt"/>
|
||||||
</span>
|
</span>
|
||||||
{{ $t('misc.delete') }}
|
{{ $t('misc.delete') }}
|
||||||
</a>
|
</dropdown-item>
|
||||||
</dropdown>
|
</dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -241,6 +238,7 @@ import Dropdown from '@/components/misc/dropdown.vue'
|
||||||
import {getCollapsedBucketState, saveCollapsedBucketState} from '@/helpers/saveCollapsedBucketState'
|
import {getCollapsedBucketState, saveCollapsedBucketState} from '@/helpers/saveCollapsedBucketState'
|
||||||
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
||||||
import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
|
import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
|
||||||
|
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||||
|
|
||||||
const DRAG_OPTIONS = {
|
const DRAG_OPTIONS = {
|
||||||
// sortable options
|
// sortable options
|
||||||
|
@ -256,6 +254,7 @@ const MIN_SCROLL_HEIGHT_PERCENT = 0.25
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Kanban',
|
name: 'Kanban',
|
||||||
components: {
|
components: {
|
||||||
|
DropdownItem,
|
||||||
ListWrapper,
|
ListWrapper,
|
||||||
KanbanCard,
|
KanbanCard,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
@ -728,10 +727,6 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.dropdown-item {
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-collapsed {
|
&.is-collapsed {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
transform: rotate(90deg) translateY(-100%);
|
transform: rotate(90deg) translateY(-100%);
|
||||||
|
|
Loading…
Reference in a new issue