WIP: rework single task in list
continuous-integration/drone/pr Build is failing
Details
continuous-integration/drone/pr Build is failing
Details
This commit is contained in:
parent
5f8294a64f
commit
41bf7bdaff
|
@ -381,6 +381,4 @@ export default {
|
|||
transform: translate3d(0, -4px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@include modal-transition();
|
||||
</style>
|
|
@ -28,9 +28,9 @@
|
|||
<span class="icon">
|
||||
<icon :icon="['far', 'calendar-alt']"/>
|
||||
</span>
|
||||
<time :datetime="formatISO(task.dueDate)">
|
||||
<span>
|
||||
{{ formatDateSince(task.dueDate) }}
|
||||
</time>
|
||||
</span>
|
||||
</span>
|
||||
<h3>{{ task.title }}</h3>
|
||||
<ProgressBar
|
||||
|
@ -73,8 +73,6 @@ import Done from '@/components/misc/Done.vue'
|
|||
import Labels from '@/components/tasks/partials/labels'
|
||||
import ChecklistSummary from './checklist-summary'
|
||||
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
|
||||
export default {
|
||||
name: 'kanban-card',
|
||||
components: {
|
||||
|
@ -101,7 +99,6 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
colorIsDark,
|
||||
async toggleTaskDone(task) {
|
||||
this.loadingInternal = true
|
||||
try {
|
||||
|
|
|
@ -1,365 +1,378 @@
|
|||
<template>
|
||||
<div :class="{'is-loading': taskService.loading}" class="task loader-container">
|
||||
<fancycheckbox :disabled="isArchived || disabled" @change="markAsDone" v-model="task.done"/>
|
||||
<div
|
||||
class="task loader-container"
|
||||
:class="{'is-loading': taskService.loading}"
|
||||
@click="$router.push({ name: taskDetailRoute, params: { id: task.id } })"
|
||||
>
|
||||
<fancycheckbox
|
||||
class="fancycheckbox"
|
||||
:disabled="isArchived || disabled" @change="markAsDone"
|
||||
v-model="task.done"
|
||||
/>
|
||||
|
||||
<span
|
||||
v-if="showListColor && listColor !== ''"
|
||||
:style="{backgroundColor: listColor }"
|
||||
class="color-bubble"
|
||||
>
|
||||
</span>
|
||||
<router-link
|
||||
:to="taskDetailRoute"
|
||||
:class="{ 'done': task.done}"
|
||||
class="tasktext">
|
||||
<span>
|
||||
<router-link
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list"
|
||||
v-if="showList && $store.getters['lists/getListById'](task.listId) !== null"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: $store.getters['lists/getListById'](task.listId).title})">
|
||||
{{ $store.getters['lists/getListById'](task.listId).title }}
|
||||
</router-link>
|
||||
:style="{backgroundColor: listColor }"
|
||||
/>
|
||||
|
||||
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
||||
<span class="parent-tasks" v-if="typeof task.relatedTasks.parenttask !== 'undefined'">
|
||||
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
||||
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">, </template>
|
||||
</template>
|
||||
>
|
||||
</span>
|
||||
{{ task.title }}
|
||||
</span>
|
||||
|
||||
<labels class="labels ml-2 mr-1" :labels="task.labels" v-if="task.labels.length > 0"/>
|
||||
<user
|
||||
:avatar-size="27"
|
||||
:is-inline="true"
|
||||
:key="task.id + 'assignee' + a.id + i"
|
||||
:show-username="false"
|
||||
:user="a"
|
||||
v-for="(a, i) in task.assignees"
|
||||
/>
|
||||
<time
|
||||
:datetime="formatISO(task.dueDate)"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="is-italic"
|
||||
@click.prevent.stop="showDefer = !showDefer"
|
||||
v-if="+new Date(task.dueDate) > 0"
|
||||
v-tooltip="formatDate(task.dueDate)"
|
||||
:aria-expanded="showDefer ? 'true' : 'false'"
|
||||
>
|
||||
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||
</time>
|
||||
<transition name="fade">
|
||||
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
|
||||
</transition>
|
||||
<priority-label :priority="task.priority" :done="task.done"/>
|
||||
<span>
|
||||
<span class="list-task-icon" v-if="task.attachments.length > 0">
|
||||
<icon icon="paperclip"/>
|
||||
</span>
|
||||
<span class="list-task-icon" v-if="task.description">
|
||||
<icon icon="align-left"/>
|
||||
</span>
|
||||
<span class="list-task-icon" v-if="task.repeatAfter.amount > 0">
|
||||
<icon icon="history"/>
|
||||
</span>
|
||||
</span>
|
||||
<checklist-summary :task="task"/>
|
||||
</router-link>
|
||||
<progress
|
||||
class="progress is-small"
|
||||
v-if="task.percentDone > 0"
|
||||
:value="task.percentDone * 100" max="100">
|
||||
{{ task.percentDone * 100 }}%
|
||||
</progress>
|
||||
<router-link
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
v-if="showList && list !== null"
|
||||
class="task-list"
|
||||
v-if="!showList && currentList.id !== task.listId && $store.getters['lists/getListById'](task.listId) !== null"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: $store.getters['lists/getListById'](task.listId).title})">
|
||||
{{ $store.getters['lists/getListById'](task.listId).title }}
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: list.title})"
|
||||
>
|
||||
{{ list.title }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
class="tasktext"
|
||||
:to="{ name: taskDetailRoute, params: { id: task.id } }"
|
||||
:class="{ 'done': task.done }"
|
||||
>
|
||||
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
||||
<span
|
||||
v-if="task.relatedTasks.parenttask"
|
||||
class="parent-tasks"
|
||||
>
|
||||
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
||||
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">, </template>
|
||||
</template>
|
||||
>
|
||||
</span>
|
||||
<span class="task-title">{{ task.title }}</span>
|
||||
</router-link>
|
||||
|
||||
<labels class="labels" :labels="task.labels" v-if="task.labels.length > 0"/>
|
||||
|
||||
<user
|
||||
v-for="(a, i) in task.assignees"
|
||||
:key="task.id + 'assignee' + a.id + i"
|
||||
:avatar-size="27"
|
||||
:show-username="false"
|
||||
:user="a"
|
||||
/>
|
||||
|
||||
<popup v-if="+new Date(task.dueDate) > 0">
|
||||
<template #trigger="{toggle}">
|
||||
<em
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
@click.prevent.stop="toggle()"
|
||||
v-tooltip="formatDate(task.dueDate)"
|
||||
>
|
||||
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||
</em>
|
||||
</template>
|
||||
|
||||
<template #content="{isOpen}">
|
||||
<transition name="fade">
|
||||
<defer-task v-if="isOpen" v-model="task" />
|
||||
</transition>
|
||||
</template>
|
||||
</popup>
|
||||
|
||||
<priority-label :priority="task.priority" :done="task.done"/>
|
||||
|
||||
<icon
|
||||
v-if="task.attachments.length > 0"
|
||||
class="list-task-icon"
|
||||
icon="paperclip"
|
||||
/>
|
||||
|
||||
<icon
|
||||
v-if="task.description"
|
||||
class="list-task-icon"
|
||||
icon="align-left"
|
||||
/>
|
||||
|
||||
<icon
|
||||
v-if="task.repeatAfter.amount > 0"
|
||||
class="list-task-icon"
|
||||
icon="history"
|
||||
/>
|
||||
|
||||
<checklist-summary :task="task"/>
|
||||
|
||||
<ProgressBar
|
||||
v-if="task.percentDone > 0"
|
||||
:value="task.percentDone * 100"
|
||||
is-small
|
||||
/>
|
||||
|
||||
<router-link
|
||||
v-if="!showList && currentList.id !== task.listId && list !== null"
|
||||
class="task-list"
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: list.title})"
|
||||
>
|
||||
{{ list.title }}
|
||||
</router-link>
|
||||
|
||||
<a
|
||||
class="favorite"
|
||||
:class="{'is-favorite': task.isFavorite}"
|
||||
@click="toggleFavorite"
|
||||
class="favorite">
|
||||
<icon icon="star" v-if="task.isFavorite"/>
|
||||
<icon :icon="['far', 'star']" v-else/>
|
||||
>
|
||||
<icon :icon="task.isFavorite ? 'star' : ['far', 'star']" />
|
||||
</a>
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TaskModel from '../../../models/task'
|
||||
<script lang="ts" setup>
|
||||
import {reactive, computed, watch, defineProps, defineEmits, PropType } from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
import TaskModel from '@/models/task'
|
||||
import TaskService from '@/services/task'
|
||||
|
||||
import PriorityLabel from './priorityLabel'
|
||||
import TaskService from '../../../services/task'
|
||||
import Labels from './labels'
|
||||
import User from '../../misc/user'
|
||||
import Fancycheckbox from '../../input/fancycheckbox'
|
||||
import DeferTask from './defer-task'
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
import {playPop} from '@/helpers/playPop'
|
||||
import Labels from './labels'
|
||||
import ChecklistSummary from './checklist-summary'
|
||||
|
||||
export default {
|
||||
name: 'singleTaskInList',
|
||||
data() {
|
||||
return {
|
||||
taskService: new TaskService(),
|
||||
task: new TaskModel(),
|
||||
showDefer: false,
|
||||
import User from '@/components/misc/user'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox'
|
||||
import Popup from '@/components/misc/popup.vue'
|
||||
import ProgressBar from '@/components/misc/ProgressBar'
|
||||
|
||||
import {playPop} from '@/helpers/playPop'
|
||||
import {success} from '@/message'
|
||||
|
||||
const props = defineProps({
|
||||
theTask: {
|
||||
type: Object as PropType<TaskModel>,
|
||||
required: true,
|
||||
},
|
||||
isArchived: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
taskDetailRoute: {
|
||||
type: String,
|
||||
default: 'task.list.detail',
|
||||
},
|
||||
showList: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showListColor: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['task-updated'])
|
||||
|
||||
const taskService = reactive(new TaskService())
|
||||
const task = reactive(new TaskModel())
|
||||
|
||||
watch(props.theTask, (newVal) => {
|
||||
Object.assign(task, newVal)
|
||||
}, { immediate: true },
|
||||
)
|
||||
|
||||
const store = useStore()
|
||||
const currentList = computed(() => {
|
||||
return typeof store.state.currentList === 'undefined' ? {
|
||||
id: 0,
|
||||
title: '',
|
||||
} : store.state.currentList
|
||||
})
|
||||
|
||||
const list = computed(() => store.getters['lists/getListById'](task.listId))
|
||||
const listColor = computed(() => list.value?.hexColor || '')
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
async function markAsDone(checked: boolean) {
|
||||
async function updateFunc() {
|
||||
const newTask = await taskService.update(task)
|
||||
if (task.done) {
|
||||
playPop()
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ChecklistSummary,
|
||||
DeferTask,
|
||||
Fancycheckbox,
|
||||
User,
|
||||
Labels,
|
||||
PriorityLabel,
|
||||
},
|
||||
props: {
|
||||
theTask: {
|
||||
type: TaskModel,
|
||||
required: true,
|
||||
},
|
||||
isArchived: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showList: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showListColor: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['task-updated'],
|
||||
watch: {
|
||||
theTask(newVal) {
|
||||
this.task = newVal
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.theTask
|
||||
document.addEventListener('click', this.hideDeferDueDatePopup)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.hideDeferDueDatePopup)
|
||||
},
|
||||
computed: {
|
||||
listColor() {
|
||||
const list = this.$store.getters['lists/getListById'](this.task.listId)
|
||||
return list !== null ? list.hexColor : ''
|
||||
},
|
||||
currentList() {
|
||||
return typeof this.$store.state.currentList === 'undefined' ? {
|
||||
id: 0,
|
||||
title: '',
|
||||
} : this.$store.state.currentList
|
||||
},
|
||||
taskDetailRoute() {
|
||||
return {
|
||||
name: 'task.detail',
|
||||
params: { id: this.task.id },
|
||||
// TODO: re-enable opening task detail in modal
|
||||
// state: { backdropView: this.$router.currentRoute.value.fullPath },
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async markAsDone(checked) {
|
||||
const updateFunc = async () => {
|
||||
const task = await this.taskService.update(this.task)
|
||||
if (this.task.done) {
|
||||
playPop()
|
||||
}
|
||||
this.task = task
|
||||
this.$emit('task-updated', task)
|
||||
this.$message.success({
|
||||
message: this.task.done ?
|
||||
this.$t('task.doneSuccess') :
|
||||
this.$t('task.undoneSuccess'),
|
||||
}, [{
|
||||
title: 'Undo',
|
||||
callback() {
|
||||
this.task.done = !this.task.done
|
||||
this.markAsDone(!checked)
|
||||
},
|
||||
}])
|
||||
}
|
||||
Object.assign(task, newTask)
|
||||
emit('task-updated', task)
|
||||
success({
|
||||
message: task.done ?
|
||||
t('task.doneSuccess') :
|
||||
t('task.undoneSuccess'),
|
||||
}, [{
|
||||
title: 'Undo',
|
||||
callback() {
|
||||
task.done = !task.done
|
||||
markAsDone(!checked)
|
||||
},
|
||||
}])
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
setTimeout(updateFunc, 300) // Delay it to show the animation when marking a task as done
|
||||
} else {
|
||||
await updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
|
||||
}
|
||||
},
|
||||
if (checked) {
|
||||
// Delay it to show the animation when marking a task as done
|
||||
setTimeout(updateFunc, 300)
|
||||
} else {
|
||||
// Don't delay it when un-marking it as it doesn't have an animation the other way around
|
||||
await updateFunc()
|
||||
}
|
||||
}
|
||||
|
||||
async toggleFavorite() {
|
||||
this.task.isFavorite = !this.task.isFavorite
|
||||
this.task = await this.taskService.update(this.task)
|
||||
this.$emit('task-updated', this.task)
|
||||
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
|
||||
},
|
||||
hideDeferDueDatePopup(e) {
|
||||
if (!this.showDefer) {
|
||||
return
|
||||
}
|
||||
closeWhenClickedOutside(e, this.$refs.deferDueDate.$el, () => {
|
||||
this.showDefer = false
|
||||
})
|
||||
},
|
||||
},
|
||||
async function toggleFavorite() {
|
||||
task.isFavorite = !task.isFavorite
|
||||
const newTask = await taskService.update(task)
|
||||
Object.assign(task, newTask)
|
||||
emit('task-updated', task)
|
||||
store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$line-height: 2.5;
|
||||
.task {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: .4rem;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"checkbox color-bubble list1 text list2";
|
||||
grid-template-columns: min-content max-content fit-content(10ch) minmax(0, 3fr) fit-content(10ch);
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: min-content;
|
||||
grid-template-rows: 1fr;
|
||||
place-items: center start;
|
||||
transition: background-color $transition;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-radius: $radius;
|
||||
border: 2px solid transparent;
|
||||
line-height: $line-height;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--grey-100);
|
||||
}
|
||||
|
||||
.tasktext,
|
||||
&.tasktext {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
flex: 1 0 50%;
|
||||
|
||||
.overdue {
|
||||
color: var(--danger);
|
||||
}
|
||||
> * {
|
||||
min-width: min-content;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
width: auto;
|
||||
color: var(--grey-400);
|
||||
font-size: .9rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.color-bubble {
|
||||
height: 10px;
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
vertical-align: bottom;
|
||||
margin-left: 5px;
|
||||
height: 27px;
|
||||
width: 27px;
|
||||
}
|
||||
|
||||
.list-task-icon {
|
||||
margin-left: 6px;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text);
|
||||
transition: color ease $transition-duration;
|
||||
|
||||
&:hover {
|
||||
color: var(--grey-900);
|
||||
}
|
||||
}
|
||||
|
||||
.favorite {
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
width: 27px;
|
||||
transition: opacity $transition, color $transition;
|
||||
|
||||
&:hover {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
&.is-favorite {
|
||||
opacity: 1;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .favorite {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.handle {
|
||||
opacity: 0;
|
||||
transition: opacity $transition;
|
||||
margin-right: .25rem;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
&:hover .handle {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:deep(.fancycheckbox) {
|
||||
height: 18px;
|
||||
padding-top: 0;
|
||||
padding-right: .5rem;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tasktext.done {
|
||||
text-decoration: line-through;
|
||||
color: var(--grey-500);
|
||||
}
|
||||
|
||||
span.parent-tasks {
|
||||
color: var(--grey-500);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.settings {
|
||||
float: right;
|
||||
width: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.loader-container.is-loading:after {
|
||||
top: calc(50% - 1rem);
|
||||
left: calc(50% - 1rem);
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-left-color: var(--grey-300);
|
||||
border-bottom-color: var(--grey-300);
|
||||
> * + * {
|
||||
// gap does not work since we have collapsed tracks
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.task.loader-container.is-loading::after {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
transform: translate(-50%, -50%);
|
||||
border-left-color: var(--grey-300);
|
||||
border-bottom-color: var(--grey-300);
|
||||
}
|
||||
|
||||
.fancycheckbox {
|
||||
grid-area: checkbox;
|
||||
align-self: stretch;
|
||||
padding-left: .25rem;
|
||||
padding-right: .5rem;
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
|
||||
.color-bubble {
|
||||
grid-area: color-bubble;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
@mixin overflow-text() {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
grid-area: list1;
|
||||
max-width: 100%;
|
||||
min-width: 0; // this is needed for the overflow text
|
||||
@include overflow-text();
|
||||
font-size: .9rem;
|
||||
color: var(--grey-400);
|
||||
}
|
||||
|
||||
.tasktext {
|
||||
grid-area: text;
|
||||
max-width: 100%;
|
||||
min-width: 0; // this is needed for the overflow text
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tasktext.done {
|
||||
text-decoration: line-through;
|
||||
color: var(--grey-500);
|
||||
}
|
||||
|
||||
.parent-tasks {
|
||||
color: var(--grey-500);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
flex: 1 1 0;
|
||||
@include overflow-text();
|
||||
min-width: 6vw;
|
||||
}
|
||||
|
||||
.overdue {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
vertical-align: bottom;
|
||||
height: 27px;
|
||||
width: 27px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text);
|
||||
transition: color ease $transition-duration;
|
||||
|
||||
&:hover {
|
||||
color: var(--grey-900);
|
||||
}
|
||||
}
|
||||
|
||||
.favorite {
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
width: 27px;
|
||||
transition: opacity $transition, color $transition;
|
||||
|
||||
&:hover {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
&.is-favorite {
|
||||
opacity: 1;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
.task:hover .favorite {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:deep(.handle) {
|
||||
opacity: 0;
|
||||
transition: opacity $transition;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.task:hover :deep(.handle) {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
Reference in New Issue