feat: add collapsible subtasks in list view
- Add chevron toggle button for each task with subtasks - Add global collapse/expand all button in filter toolbar - Use provide/inject for global collapse state - Smooth rotation animation on collapse toggle Closes #126
This commit is contained in:
parent
89ee1ef507
commit
db1e51428b
|
|
@ -17,6 +17,14 @@
|
|||
:project-id="projectId"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
<button
|
||||
class="collapse-all-btn"
|
||||
:class="{ 'is-collapsed': collapseAll }"
|
||||
@click="toggleCollapseAll"
|
||||
:title="collapseAll ? 'Expand all subtasks' : 'Collapse all subtasks'"
|
||||
>
|
||||
<Icon :icon="collapseAll ? 'chevron-right' : 'chevron-down'" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -101,7 +109,7 @@
|
|||
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, nextTick, onMounted, onBeforeUnmount, watch, toRef} from 'vue'
|
||||
import {ref, computed, nextTick, onMounted, onBeforeUnmount, watch, toRef, provide} from 'vue'
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
|
||||
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
|
||||
|
|
@ -143,6 +151,14 @@ const ctaVisible = ref(false)
|
|||
|
||||
const drag = ref(false)
|
||||
|
||||
// Collapse all subtasks
|
||||
const collapseAll = ref(false)
|
||||
provide('collapseAll', collapseAll)
|
||||
|
||||
function toggleCollapseAll() {
|
||||
collapseAll.value = !collapseAll.value
|
||||
}
|
||||
|
||||
const {
|
||||
tasks: allTasks,
|
||||
loading,
|
||||
|
|
@ -368,6 +384,31 @@ onBeforeUnmount(() => {
|
|||
}
|
||||
}
|
||||
|
||||
.collapse-all-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: 32px;
|
||||
block-size: 32px;
|
||||
border: 1px solid var(--grey-300);
|
||||
background: var(--white);
|
||||
color: var(--grey-500);
|
||||
cursor: pointer;
|
||||
border-radius: $radius;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--grey-700);
|
||||
background: var(--grey-100);
|
||||
border-color: var(--grey-400);
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.tasks {
|
||||
padding: .5rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,8 +178,17 @@
|
|||
/>
|
||||
</BaseButton>
|
||||
<slot />
|
||||
<button
|
||||
v-if="hasSubtasks"
|
||||
class="collapse-toggle"
|
||||
:class="{ 'is-collapsed': isCollapsed }"
|
||||
@click.stop="toggleCollapse"
|
||||
:aria-label="isCollapsed ? 'Expand subtasks' : 'Collapse subtasks'"
|
||||
>
|
||||
<Icon icon="chevron-down" />
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="typeof task.relatedTasks?.subtask !== 'undefined'">
|
||||
<template v-if="hasSubtasks && !isCollapsed">
|
||||
<template v-for="subtask in task.relatedTasks.subtask">
|
||||
<template v-if="getTaskById(subtask.id)">
|
||||
<single-task-in-project
|
||||
|
|
@ -197,7 +206,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, watch, shallowReactive, onMounted, computed} from 'vue'
|
||||
import {ref, watch, shallowReactive, onMounted, computed, inject, type Ref} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import TaskModel, {getHexColor} from '@/models/task'
|
||||
|
|
@ -246,6 +255,24 @@ const props = withDefaults(defineProps<{
|
|||
allTasks: () => [],
|
||||
})
|
||||
|
||||
// Collapse state
|
||||
const isCollapsed = ref(false)
|
||||
const collapseAll = inject<Ref<boolean>>('collapseAll', ref(false))
|
||||
|
||||
// Watch for global collapse/expand all
|
||||
watch(collapseAll, (newVal) => {
|
||||
isCollapsed.value = newVal
|
||||
})
|
||||
|
||||
const hasSubtasks = computed(() => {
|
||||
return typeof props.theTask.relatedTasks?.subtask !== 'undefined' &&
|
||||
props.theTask.relatedTasks.subtask.length > 0
|
||||
})
|
||||
|
||||
function toggleCollapse() {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
'taskUpdated': [task: ITask],
|
||||
}>()
|
||||
|
|
@ -597,6 +624,30 @@ defineExpose({
|
|||
margin-inline-start: 1.75rem;
|
||||
}
|
||||
|
||||
.collapse-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: 20px;
|
||||
block-size: 20px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--grey-400);
|
||||
cursor: pointer;
|
||||
border-radius: $radius;
|
||||
transition: transform 0.2s ease, color 0.2s ease;
|
||||
margin-inline-start: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--grey-600);
|
||||
background: var(--grey-100);
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.popup) {
|
||||
border-radius: $radius;
|
||||
background-color: var(--white);
|
||||
|
|
|
|||
Loading…
Reference in New Issue