Skip to content

Instantly share code, notes, and snippets.

@Rootdiv
Created March 15, 2024 19:23
Show Gist options
  • Save Rootdiv/b6e5895714583b38fca3d9f570cba6c9 to your computer and use it in GitHub Desktop.
Save Rootdiv/b6e5895714583b38fca3d9f570cba6c9 to your computer and use it in GitHub Desktop.
Vue Todo List
<div id="app">
<div class="container">
<h1 class="title">{{ title }}</h1>
<div class="card">
<div class="card__inner">
<form>
<input type="text" placeholder="Create a new todo" v-model="newItem" />
<button @click="addItem" :disabled="newItem.length === 0">Add Todo</button>
</form>
<p v-if="items.length === 0">No todos!</p>
<ul class="todo-list">
<li class="todo-list__list-item" :class="{'todo-list__list-item--completed': item.completed}" :id="item.id" draggable="true" @dragStart="dragStart($event)" @dragend="dragEnd($event)" @drop="onDrop($event, item)" @dragover.prevent @dragenter.prevent v-for="item in items">
<div class="todo-list__move"></div>
<div class="todo-list__toggle" @click="toggleComplete(item)"></div>
<span class="todo-list__name">{{ item.name }}</span>
<div class="todo-list__remove" @click="removeItem(item.id)">+</div>
</li>
</ul>
</div>
</div>
</div>
</div>
const { createApp } = Vue
createApp({
data() {
return {
title: 'Vue Todo List',
newItem: '',
items: [
{ id: 1, name: 'Write', completed: true },
{ id: 2, name: 'Draw', completed: false },
{ id: 3, name: 'Read', completed: false },
],
}
},
methods: {
addItem() {
this.items.push({
id: Date.now(),
name: this.newItem,
completed: false,
});
this.newItem = '';
},
dragStart(event) {
event.target.classList.add('todo-list__list-item--dragging');
},
dragEnd(event) {
event.target.classList.remove('todo-list__list-item--dragging');
},
onDrop(event, item) {
// check if the item is being dropped on a different one
let selectedElement = document.querySelector('.todo-list__list-item--dragging');
let selectedElementId = parseInt(selectedElement.getAttribute('id'));
if (selectedElementId !== item.id) {
// determine the order of the drop target
let selectedIndex = this.items.findIndex(obj => { return obj.id === selectedElementId; });
let targetIndex = this.items.findIndex(obj => { return obj.id === item.id });
// insert selected before target
let selectedObject = this.items[selectedIndex];
this.items.splice(selectedIndex, 1);
this.items.splice(targetIndex, 0, selectedObject);
}
},
toggleComplete(item) {
item.completed = !item.completed
},
removeItem(itemID) {
this.items = this.items.filter(item => item.id !== itemID)
}
}
}).mount('#app')
<script src="https://unpkg.com/vue@3"></script>
$color-primary: #1aa7f5;
$color-primary-dark: #1a4d7e;
$color-secondary: #e65e75;
$color-neutral-light: #a49ebd;
$color-neutral: #2f2a43;
$color-neutral-dark: #222031;
$color-white: #ffffff;
$color-black: #0f0e13;
* {
box-sizing: border-box;
}
body {
background-color: $color-neutral-dark;
background: linear-gradient($color-neutral, $color-neutral-dark);
color: $color-white;
font-family: 'Poppins', sans-serif;
height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6, p {
margin-top: 0;
}
form {
margin-bottom: 24px;
position: relative;
}
input {
background: $color-neutral-dark;
border-radius: 40px;
border: 1px solid transparent;
box-shadow: inset 0 2px 4px rgba($color-black, 0.25);
color: $color-white;
display: block;
font-size: 16px;
height: 100%;
outline: none;
padding: 14px 120px 14px 16px;
width: 100%;
&::placeholder {
color: $color-neutral-light;
font-style: italic;
}
}
button {
background: $color-primary;
border: none;
border-radius: 40px;
box-shadow: 0 4px 8px -6px rgba($color-black, 0.5);
border-top: 1px solid rgba($color-white, 0.05);
color: $color-white;
cursor: pointer;
font-size: 16px;
font-weight: bold;
height: 32px;
padding: 0 12px;
position: absolute;
right: 8px;
transition: background-color 0.2s ease;
top: 8px;
&:hover {
background-color: $color-primary-dark;
}
&:disabled {
background-color: $color-neutral;
color: $color-neutral-light;
cursor: not-allowed;
}
}
.title {
text-align: center;
}
.container {
margin: 0 auto;
max-width: 700px;
padding: 40px;
}
.card {
align-items: center;
backdrop-filter: blur(80px) brightness(1.25);
border-radius: 8px;
box-shadow: 0 8px 16px -12px rgba($color-black, 0.5);
border-top: 1px solid rgba($color-white, 0.05);
display: flex;
justify-content: space-between;
margin-bottom: 24px;
&__inner {
padding: 32px 24px;
width: 100%;
}
p:last-of-type {
margin-bottom: 0;
}
}
.todo-list {
clip-path: polygon(0 1px, 100% 1px, 100% calc(100% - 1px), 0 calc(100% - 1px));
display: flex;
flex-direction: column;
list-style: none;
margin: 0;
padding: 0;
&__list-item {
align-items: center;
border-bottom: 1px solid $color-neutral-dark;
border-top: 1px solid $color-neutral-dark;
display: flex;
justify-content: flex-start;
margin-bottom: -1px;
padding: 8px 0;
&--completed {
.todo-list__name {
color: $color-neutral-light;
text-decoration: line-through;
}
.todo-list__toggle {
background-color: $color-neutral-light;
border-color: $color-neutral-light;
}
}
&--dragging {
opacity: 0.25;
}
}
&__move {
background: radial-gradient(#222031 25%, transparent 25%) 0 0 / 6px 6px;
cursor: ns-resize;
height: 16px;
margin-right: 8px;
width: 12px;
}
&__toggle {
border: 1px solid $color-neutral-light;
border-radius: 999px;
cursor: pointer;
height: 20px;
margin-right: 8px;
width: 20px;
}
&__name {
line-height: 1;
}
&__remove {
color: $color-neutral-light;
cursor: pointer;
font-size: 24px;
margin-left: auto;
margin-top: -2px;
text-decoration: none !important;
transform: rotate(45deg);
&:hover {
color: $color-secondary;
}
}
}
<link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400&amp;display=swap" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment