feat(subTasks): created SubTask and SubTaskList components.

Created the sub-tasks feature, now each Task component also has its sub-tasks and sub-task list, stored on LocalStorage parent object;
This commit is contained in:
Guz
2022-01-30 17:53:48 -03:00
parent 51c4618d9c
commit 7bd0e46f24
5 changed files with 370 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
<template>
<li :id="`subTask${id}`" class="subTask">
<div class="subTaskInfo">
<input
:id="`subTaskCheckboxInput${id}`"
type="checkbox"
name=""
class="checkbox"
:checked="checked"
@click="changeState(parent)"
/>
<p class="description">{{ description }}</p>
</div>
<nav class="subTaskControls">
<button class="deleteSubtaskButton" @click="deleteSubTask(parent)">
<IconTrash class="trashIcon" />
</button>
</nav>
</li>
</template>
<script lang="js">
import Vue from 'vue';
import sm from '~/libs/storageManagement';
export default Vue.extend({
name: 'SubTaskComp',
props: {
id: {
type: Number,
required: true,
},
parent: {
type: Number,
required: true,
},
description: {
type: String,
default: 'Task short description',
},
checked: {
type: Boolean,
default: false,
},
},
methods: {
changeState(parentId) {
const state = document.querySelector(`input#subTaskCheckboxInput${this.id}`)?.checked;
const localTasks = sm.get('tasks');
localTasks[parentId].subTasks[this.id].checked = state;
sm.set('tasks', localTasks);
},
deleteSubTask(parentId) {
const localTasks = sm.get('tasks');
localTasks[parentId].subTasks.splice(this.id, 1);
sm.set('tasks', localTasks);
}
}
});
</script>
<style lang="scss">
@import '~assets/styles/_variables.scss';
@import '~assets/styles/_mixins.scss';
.subTask {
display: flex;
align-items: center;
justify-content: space-between;
.subTaskInfo {
display: flex;
align-items: center;
.checkbox {
appearance: none;
background-color: transparent;
margin: 0;
margin-right: 10px;
.light-mode & {
color: $blue3;
background-color: transparent;
}
.dark-mode & {
color: $dark-background;
background-color: transparent;
}
font: inherit;
width: 20px;
height: 20px;
border: 5px solid currentColor;
border-radius: 50%;
&:checked {
background-color: $checked-color;
}
}
.description {
margin: 1px 0;
}
}
.subTaskControls {
opacity: 0;
@media (pointer: none), (pointer: coarse) {
opacity: 0.5;
}
transition: opacity 0.2s;
button {
background-color: transparent;
padding: 1px;
border: 0;
cursor: pointer;
}
.deleteSubtaskButton {
.trashIcon * {
transition: stroke 0.2s;
}
&:hover .trashIcon * {
stroke: $begonia-red0;
}
}
}
&:hover .subTaskControls {
transition: 1s 0.2s;
opacity: 1;
right: -7%;
}
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<span :class="`${preventAnim ? 'preventAnimLoad ' : ''}`">
<div :class="`${opened ? 'opened' : 'closed'} subTasks preventAnim`">
<ul :id="`subTaskList${parent}`" class="list">
<SubTaskComp
v-for="subTask in subTaskList"
:id="subTaskList.indexOf(subTask)"
:key="`subtask-comp-${subTaskList.indexOf(subTask)}`"
:parent="parent"
:description="subTask.description"
:checked="subTask.checked"
/>
</ul>
<button @click="addSubTask(parent)">Add subtask</button>
</div>
</span>
</template>
<script lang="ts">
import Vue from 'vue';
import sm from '~/libs/storageManagement';
const subTaskItem = {
description: 'Task',
checked: false,
};
export default Vue.extend({
name: 'SubTasks',
props: {
parent: {
type: Number,
required: true,
},
opened: {
type: Boolean,
default: false,
},
},
data() {
return {
subTaskList: sm.get('tasks')[this.parent].subTasks
? sm.get('tasks')[this.parent].subTasks
: [],
preventAnim: true,
};
},
mounted() {
window.addEventListener('localStorage-changed', () => {
this.subTaskList = sm.get('tasks')[this.parent].subTasks
? sm.get('tasks')[this.parent].subTasks
: [];
});
setTimeout(() => {
this.preventAnim = false;
}, 1000);
},
methods: {
addSubTask: (parentId: number) => {
if (sm.get('tasks') === undefined) sm.add('tasks', []);
const localTasks: any[] = sm.get('tasks');
const parentTask = localTasks[parentId];
if (!parentTask.subTasks) parentTask.subTasks = [];
subTaskItem.description = `Description sub task ${parentTask.subTasks.length}`;
parentTask.subTasks.push(subTaskItem);
localTasks[parentId] = parentTask;
sm.set('tasks', localTasks);
},
},
});
</script>
<style lang="scss">
@import '~assets/styles/_variables.scss';
@import '~assets/styles/_mixins.scss';
.opened {
transform: translateY(-80px) scaleY(0.1);
animation: open 0.5s forwards;
@keyframes open {
from {
transform: translateY(-80px) scaleY(0.1);
opacity: 0;
position: absolute;
}
to {
transform: translateY(-20px) scaleY(1);
opacity: 1;
position: relative;
}
}
}
.closed {
transform: translateY(-20px) scaleY(1);
animation: close 0.5s forwards;
@keyframes close {
from {
transform: translateY(-20px) scaleY(1);
opacity: 1;
position: relative;
}
to {
transform: translateY(-80px) scaleY(0.1);
display: none;
opacity: 0;
position: absolute;
}
}
}
.subTasks {
.light-mode & {
background-color: $blue2;
color: $independence-gray0;
}
.dark-mode & {
background-color: $eerie-black2;
color: $dark-primary;
}
font-family: $secondary-font;
width: 90%;
z-index: 2;
padding: 20px 5% 10px 5%;
border-radius: 0 0 20px 20px;
text-align: left;
.list {
padding: 1% 0;
list-style: none;
}
}
</style>

View File

@@ -28,6 +28,14 @@
<p>{{ description }}</p>
</div>
</div>
<button
:class="`${
openSubTasks ? 'openedSubtasksButton' : 'closedSubtasksButton'
} subtasksButton`"
@click="openSubTasks = !openSubTasks"
>
<IconDropdown class="dropdownIcon" />
</button>
</div>
<nav class="controls">
<button class="deleteButton" @click="deleteTask()">
@@ -35,6 +43,7 @@
</button>
</nav>
</div>
<SubTaskList :parent="id" :opened="openSubTasks" />
</li>
</template>
@@ -340,6 +349,49 @@ export default Vue.extend({
}
}
.subtasksButton {
display: flex;
align-items: center;
opacity: 0;
@media (pointer: none), (pointer: coarse) {
opacity: 0.5;
}
transition: opacity 0.2s;
background-color: transparent;
padding: 1px;
border: 0;
cursor: pointer;
.dropdownIcon {
* {
transition: stroke 0.2s;
}
&:hover * {
.light-mode & {
stroke: $independence-gray0;
}
.dark-mode & {
stroke: $dark-primary;
}
}
}
&.openedSubtasksButton {
.dropdownIcon {
transform: rotate(180deg);
}
}
&.closedSubtasksButton {
.dropdownIcon {
transform: rotate(0deg);
}
}
}
&:hover .subtasksButton {
opacity: 1;
}
}
}
}

View File

@@ -0,0 +1,30 @@
<template>
<svg
width="24"
height="24"
stroke-width="1.5"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 5H18C20.2091 5 22 6.79086 22 9V15C22 17.2091 20.2091 19 18 19H6C3.79086 19 2 17.2091 2 15V9C2 6.79086 3.79086 5 6 5Z"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14.5 10.75L12 13.25L9.5 10.75"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'IconDropdown',
});
</script>

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 5H18C20.2091 5 22 6.79086 22 9V15C22 17.2091 20.2091 19 18 19H6C3.79086 19 2 17.2091 2 15V9C2 6.79086 3.79086 5 6 5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.5 10.75L12 13.25L9.5 10.75" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 435 B