Added a modal window
This commit is contained in:
94
src/components/Modal.vue
Normal file
94
src/components/Modal.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="modal"
|
||||||
|
:class="{hidden: !active}"
|
||||||
|
>
|
||||||
|
<div class="bg" @click="close" />
|
||||||
|
<div class="window">
|
||||||
|
<div class="close" @click="close">×</div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
active: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.active = true;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.active = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
transition: all .4s;
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(90, 90, 90, 0.28);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
z-index: 1000;
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: $lighter;
|
||||||
|
color: $dark;
|
||||||
|
width: 340px;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $max-width) {
|
||||||
|
.modal {
|
||||||
|
.window {
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
top: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transition: transform .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
.window {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(0, 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,15 +5,16 @@
|
|||||||
v-model="newCategory"
|
v-model="newCategory"
|
||||||
placeholder="New category"
|
placeholder="New category"
|
||||||
@keypress.enter="addCategory"
|
@keypress.enter="addCategory"
|
||||||
@mouseenter="newCategory=''"
|
@mouseenter="newCategory = ''"
|
||||||
@mouseleave="blurInput"
|
@mouseleave="blurInput"
|
||||||
/>
|
/>
|
||||||
<div class="categories">
|
<div class="categories">
|
||||||
<div
|
<div
|
||||||
class="category"
|
class="category"
|
||||||
v-for="category in categories" :key="category"
|
v-for="category in categories"
|
||||||
:style="{background: stringToColor(category)}"
|
:key="category"
|
||||||
:class="{selected: selectedCategory===category}"
|
:style="{ background: stringToColor(category) }"
|
||||||
|
:class="{ selected: selectedCategory === category }"
|
||||||
@click="selectCategory(category)"
|
@click="selectCategory(category)"
|
||||||
>
|
>
|
||||||
{{ category }}
|
{{ category }}
|
||||||
@@ -75,7 +76,7 @@ export default {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid $light;
|
//border-bottom: 1px solid $light;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
background: $dark;
|
background: $dark;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -89,20 +90,21 @@ export default {
|
|||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: width .6s;
|
transition: width 0.6s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
width: 220px
|
width: 220px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.categories {
|
.categories {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: 32px;
|
margin-left: 32px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
|
||||||
.category {
|
.category {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
border: 3px solid transparent;
|
border: 3px solid $dark;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border: 3px double $dark;
|
border: 3px double $dark;
|
||||||
@@ -110,4 +112,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $max-width) {
|
||||||
|
.category-bar {
|
||||||
|
padding: 0 0 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
$dark-blue: #05263B;
|
$dark: #1f1f1f;
|
||||||
$misty-blue: #AEB8C4;
|
$darker: #121212;
|
||||||
$blue-grotto: #163B50;
|
$light: rgb(219, 219, 219);
|
||||||
$slate: #9CA6B8;
|
$lighter: white;
|
||||||
|
|
||||||
$dark: $blue-grotto;
|
|
||||||
$darker: $dark-blue;
|
|
||||||
$light: $slate;
|
|
||||||
$lighter: $misty-blue;
|
|
||||||
|
|
||||||
|
|
||||||
$max-width: 1024px;
|
$max-width: 1024px;
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
color: $light;
|
color: $light;
|
||||||
background: $darker;
|
background: $darker;
|
||||||
@@ -13,12 +14,24 @@
|
|||||||
input, button {
|
input, button {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $dark;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.category {
|
.category {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 4px 12px;
|
padding: 6px 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
color: $darker;
|
color: $darker;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,59 +1,69 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="task" :class="{ active: task.running }">
|
||||||
class="task"
|
|
||||||
:class="{active: task.running}"
|
|
||||||
>
|
|
||||||
<div class="time">
|
<div class="time">
|
||||||
{{ formattedTime }}
|
{{ formattedTime }}
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-state">
|
<div class="toggle-state">
|
||||||
<div v-if="task.running" @click="stopTask">
|
<div v-if="task.running" @click="stopTask">
|
||||||
<img src="@/assets/pause.svg">
|
<img src="@/assets/pause.svg" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else @click="startTask">
|
<div v-else @click="startTask">
|
||||||
<img src="@/assets/play.svg">
|
<img src="@/assets/play.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="name">
|
|
||||||
{{ task.name }}
|
<div class="optional-break">
|
||||||
</div>
|
<div class="name">
|
||||||
<div class="spacer"/>
|
{{ task.name }}
|
||||||
<div
|
</div>
|
||||||
v-if="task.category"
|
|
||||||
class="category"
|
<div
|
||||||
:style="{ background: stringToColor(task.category) }"
|
v-if="task.category"
|
||||||
>
|
class="category"
|
||||||
{{ task.category }}
|
:style="{ background: stringToColor(task.category) }"
|
||||||
<span @click="removeCategory">×</span>
|
>
|
||||||
</div>
|
{{ task.category }}
|
||||||
<div
|
<span @click="removeCategory">×</span>
|
||||||
v-else
|
</div>
|
||||||
class="select-category"
|
<div v-else class="select-category" @click="$refs.categorySelect.open">
|
||||||
>
|
Assign Category
|
||||||
Assign Category
|
<!-- <CategoryDropdown
|
||||||
<CategoryDropdown
|
|
||||||
class="dropdown"
|
class="dropdown"
|
||||||
@selected="assignCategory"
|
@selected="assignCategory"
|
||||||
/>
|
/> -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<Modal ref="categorySelect" class="modal">
|
||||||
class="delete"
|
<div class="title">Assign Category</div>
|
||||||
@click="removeTask"
|
<div
|
||||||
>
|
v-for="category in categories"
|
||||||
<img src="@/assets/trash.svg">
|
:key="category"
|
||||||
|
class="category"
|
||||||
|
:style="{ background: stringToColor(category) }"
|
||||||
|
@click="assignCategory(category)"
|
||||||
|
>
|
||||||
|
{{ category }}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<div class="delete" @click="removeTask">
|
||||||
|
<img src="@/assets/trash.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
import toColor from '@/stringToColor';
|
import toColor from '@/stringToColor';
|
||||||
|
|
||||||
import CategoryDropdown from './CategoryDropdown.vue';
|
// import CategoryDropdown from './CategoryDropdown.vue';
|
||||||
|
import Modal from '@/components/Modal.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CategoryDropdown,
|
// CategoryDropdown,
|
||||||
|
Modal,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
task: Object,
|
task: Object,
|
||||||
@@ -73,9 +83,13 @@ export default {
|
|||||||
},
|
},
|
||||||
assignCategory(category) {
|
assignCategory(category) {
|
||||||
this.$store.commit('assignCategory', { name: this.task.name, category });
|
this.$store.commit('assignCategory', { name: this.task.name, category });
|
||||||
|
this.$refs.categorySelect.close();
|
||||||
},
|
},
|
||||||
removeCategory() {
|
removeCategory() {
|
||||||
this.$store.commit('assignCategory', { name: this.task.name, category: undefined });
|
this.$store.commit('assignCategory', {
|
||||||
|
name: this.task.name,
|
||||||
|
category: undefined,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
stringToColor(str) {
|
stringToColor(str) {
|
||||||
return toColor(str);
|
return toColor(str);
|
||||||
@@ -106,6 +120,9 @@ export default {
|
|||||||
|
|
||||||
return `${days}d ${hours}h ${mins}m`;
|
return `${days}d ${hours}h ${mins}m`;
|
||||||
},
|
},
|
||||||
|
...mapState({
|
||||||
|
categories: (state) => state.categories,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
@@ -118,12 +135,12 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.task {
|
.task {
|
||||||
color: $lighter;
|
color: $lighter;
|
||||||
height: 42px;
|
min-height: 42px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
width: 124px;
|
min-width: 104px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-state {
|
.toggle-state {
|
||||||
@@ -146,37 +163,54 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacer {
|
.optional-break {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-category {
|
.select-category {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: $darker;
|
color: $light;
|
||||||
background: $dark;
|
background: $dark;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
.dropdown {
|
// .dropdown {
|
||||||
display: none;
|
// display: none;
|
||||||
}
|
// }
|
||||||
|
|
||||||
&:hover {
|
// &:hover {
|
||||||
.dropdown {
|
// .dropdown {
|
||||||
display: block;
|
// display: block;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete {
|
.delete {
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
.category {
|
||||||
|
margin-top: 12px;
|
||||||
|
border-radius: 24px !important;
|
||||||
|
padding: 12px 24px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
from {opacity: .3;}
|
from {
|
||||||
to {opacity: 1;}
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task.active {
|
.task.active {
|
||||||
@@ -184,4 +218,16 @@ export default {
|
|||||||
animation: pulse 1s infinite alternate;
|
animation: pulse 1s infinite alternate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $max-width) {
|
||||||
|
.task {
|
||||||
|
min-height: 64px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.optional-break {
|
||||||
|
flex-direction: column;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default {
|
|||||||
.task-list {
|
.task-list {
|
||||||
max-width: $max-width;
|
max-width: $max-width;
|
||||||
margin: 32px auto;
|
margin: 32px auto;
|
||||||
|
padding: 0 8px;
|
||||||
|
|
||||||
.add-task {
|
.add-task {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
Reference in New Issue
Block a user