mirror of
https://github.com/LucasVbr/todo-list.git
synced 2026-05-13 17:22:04 +00:00
Remove components and center the card
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/font-awesome/scss/font-awesome.scss",
|
||||
"node_modules/bulma/bulma.sass",
|
||||
"src/styles.scss"
|
||||
],
|
||||
|
||||
Generated
+14
@@ -17,6 +17,7 @@
|
||||
"@angular/platform-browser-dynamic": "^14.1.0",
|
||||
"@angular/router": "^14.1.0",
|
||||
"bulma": "^0.9.4",
|
||||
"font-awesome": "^4.7.0",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.11.4"
|
||||
@@ -5822,6 +5823,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/font-awesome": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
||||
"integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.3"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -15989,6 +15998,11 @@
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
|
||||
"dev": true
|
||||
},
|
||||
"font-awesome": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
||||
"integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg=="
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@angular/platform-browser-dynamic": "^14.1.0",
|
||||
"@angular/router": "^14.1.0",
|
||||
"bulma": "^0.9.4",
|
||||
"font-awesome": "^4.7.0",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.11.4"
|
||||
|
||||
@@ -1,10 +1,40 @@
|
||||
<div class="container">
|
||||
<div class="card m-6">
|
||||
<div class="content p-6">
|
||||
<h1 class="title has-text-centered">Todo List</h1>
|
||||
<app-todo-list></app-todo-list>
|
||||
<app-todo-input></app-todo-input>
|
||||
<div class="card p-6 mt-6">
|
||||
<h1 class="title has-text-centered">{{ title }}</h1>
|
||||
|
||||
<!-- Todolist -->
|
||||
<div class="m-5">
|
||||
<div *ngIf="list.length == 0">
|
||||
<p class="empty-list subtitle has-text-centered">Empty list</p>
|
||||
</div>
|
||||
|
||||
<div class="columns" *ngFor="let item of list">
|
||||
<input type="checkbox" [(ngModel)]="item.checked" name="{{ item.text }}" id="{{ item.text }}">
|
||||
<label class="checkbox pl-2" for="{{ item.text }}">
|
||||
{{ item.text }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Input -->
|
||||
<div class="columns is-multiline">
|
||||
<div class="column">
|
||||
<input class="input" type="text" [(ngModel)]="inputValue" (keydown.enter)="addItem()">
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<button class="button is-primary" (click)="addItem()">
|
||||
<span class="icon">
|
||||
<i class="fa fa-plus"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<button class="button is-danger" (click)="removeCheckedItems()">
|
||||
<span class="icon">
|
||||
<i class="fa fa-trash"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
input[type=checkbox] + label {
|
||||
transition: all 500ms;
|
||||
}
|
||||
|
||||
input[type=checkbox]:checked + label {
|
||||
text-decoration: line-through;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
color: gray;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,56 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import TodoItem from "../models/todo-item.model";
|
||||
import LocalService from "../services/local.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'todo-list';
|
||||
export class AppComponent implements OnInit {
|
||||
public title = 'Todo List App';
|
||||
public list: TodoItem[] = [];
|
||||
|
||||
private LOCAL_STORAGE_KEY = "todoList";
|
||||
inputValue: any;
|
||||
|
||||
ngOnInit(): void {
|
||||
const savedData: null | TodoItem[] = LocalService.getData(this.LOCAL_STORAGE_KEY)
|
||||
if (savedData) this.list = savedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item if input is not empty and the item doesn't exist already
|
||||
*/
|
||||
addItem(): void {
|
||||
const listOfTodoText = this.list.map(item => item.text);
|
||||
|
||||
if (this.inputValue && !listOfTodoText.includes(this.inputValue)) {
|
||||
this.list.push(new TodoItem(this.inputValue))
|
||||
this.inputValue = "";
|
||||
this.saveList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific item of the list
|
||||
* @param toRemove TodoItem to remove
|
||||
*/
|
||||
removeItem(toRemove: TodoItem): void {
|
||||
this.list = this.list.filter(item => item !== toRemove);
|
||||
this.saveList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all checked items of the list
|
||||
*/
|
||||
removeCheckedItems(): void {
|
||||
this.list = this.list.filter(item => !item.checked)
|
||||
this.saveList();
|
||||
}
|
||||
|
||||
private saveList(): void {
|
||||
LocalService.saveData(this.LOCAL_STORAGE_KEY, this.list);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,17 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { TodoListComponent } from './todo-list/todo-list.component';
|
||||
import { TodoItemComponent } from './todo-item/todo-item.component';
|
||||
import { TodoInputComponent } from './todo-input/todo-input.component';
|
||||
import {FormsModule} from "@angular/forms";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
TodoListComponent,
|
||||
TodoItemComponent,
|
||||
TodoInputComponent
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<div class="mt-5">
|
||||
<input class="input" type="text" [(ngModel)]="textInput" (keyup.enter)="createTodoItem()">
|
||||
<input class="button is-primary mt-2 is-fullwidth" type="button" value="Create" (click)="createTodoItem()">
|
||||
</div>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TodoInputComponent } from './todo-input.component';
|
||||
|
||||
describe('TodoInputComponent', () => {
|
||||
let component: TodoInputComponent;
|
||||
let fixture: ComponentFixture<TodoInputComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TodoInputComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TodoInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {TodoItemService} from "../../services/todo-item.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-todo-input',
|
||||
templateUrl: './todo-input.component.html',
|
||||
styleUrls: ['./todo-input.component.scss']
|
||||
})
|
||||
export class TodoInputComponent implements OnInit {
|
||||
textInput: string = "";
|
||||
|
||||
constructor(private todoItemService: TodoItemService) { }
|
||||
ngOnInit(): void {}
|
||||
|
||||
createTodoItem(): void {
|
||||
if (this.textInput) {
|
||||
this.todoItemService.newItem(this.textInput);
|
||||
this.textInput = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<li>
|
||||
<input type="checkbox" [checked]="todoItem.checked" (change)="check()" name="checkbox"
|
||||
id="checkbox-{{ todoItem.id }}">
|
||||
<label class="checkbox ml-2" for="checkbox-{{ todoItem.id }}">{{ todoItem.text }}</label>
|
||||
<!-- Todo add delete button -->
|
||||
</li>
|
||||
@@ -1,20 +0,0 @@
|
||||
li {
|
||||
list-style: none;
|
||||
|
||||
input:checked + label {
|
||||
color: gray;
|
||||
&::after { width: 100%; }
|
||||
}
|
||||
|
||||
label::after {
|
||||
content: '';
|
||||
width: 0; // Before animation
|
||||
height: 2px;
|
||||
background-color: gray;
|
||||
|
||||
position: absolute;
|
||||
top: 50%; left: 0;
|
||||
|
||||
transition: width 300ms ease-in-out;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TodoItemComponent } from './todo-item.component';
|
||||
|
||||
describe('TodoItemComponent', () => {
|
||||
let component: TodoItemComponent;
|
||||
let fixture: ComponentFixture<TodoItemComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TodoItemComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TodoItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {TodoItem} from "../../models/todo-item.model";
|
||||
import {TodoItemService} from "../../services/todo-item.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-todo-item',
|
||||
templateUrl: './todo-item.component.html',
|
||||
styleUrls: ['./todo-item.component.scss']
|
||||
})
|
||||
export class TodoItemComponent implements OnInit {
|
||||
@Input() todoItem!: TodoItem
|
||||
|
||||
constructor(private todoItemService: TodoItemService) {}
|
||||
ngOnInit(): void {}
|
||||
|
||||
check() {
|
||||
this.todoItem.checked = !this.todoItem.checked;
|
||||
this.todoItemService.checkById(this.todoItem.id, this.todoItem.checked)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<ul>
|
||||
<app-todo-item *ngFor="let todoItem of todoItems" [todoItem]="todoItem"></app-todo-item>
|
||||
</ul>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TodoListComponent } from './todo-list.component';
|
||||
|
||||
describe('TodoListComponent', () => {
|
||||
let component: TodoListComponent;
|
||||
let fixture: ComponentFixture<TodoListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TodoListComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TodoListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {TodoItem} from "../../models/todo-item.model";
|
||||
import {TodoItemService} from "../../services/todo-item.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-todo-list',
|
||||
templateUrl: './todo-list.component.html',
|
||||
styleUrls: ['./todo-list.component.scss']
|
||||
})
|
||||
export class TodoListComponent implements OnInit {
|
||||
todoItems!: TodoItem[];
|
||||
|
||||
constructor(private todoItemService: TodoItemService) {}
|
||||
ngOnInit(): void {
|
||||
this.todoItemService.loadItems();
|
||||
this.todoItems = this.todoItemService.todoItems;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export class TodoItem {
|
||||
export default class TodoItem {
|
||||
|
||||
constructor(
|
||||
public id: number,
|
||||
public text: string,
|
||||
public checked: boolean = false
|
||||
) {}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import TodoItem from "../models/todo-item.model";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export default class LocalService {
|
||||
|
||||
static saveData(key: string, data: TodoItem[]) {
|
||||
localStorage.setItem(key, JSON.stringify(data));
|
||||
}
|
||||
|
||||
static getData(key: string): TodoItem[] | null {
|
||||
const data = localStorage.getItem(key);
|
||||
if (!data) return null
|
||||
|
||||
return JSON.parse(data) as TodoItem[];
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {TodoItem} from "../models/todo-item.model";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TodoItemService {
|
||||
todoItems: TodoItem[] = [];
|
||||
|
||||
get nextId(): number {
|
||||
return this.todoItems.length + 1
|
||||
}
|
||||
|
||||
getById(id: number): TodoItem {
|
||||
const todoItem = this.todoItems.find(item => item.id === id);
|
||||
if (!todoItem) throw new Error('Todo item not found!');
|
||||
return todoItem;
|
||||
}
|
||||
|
||||
newItem(text: string): void {
|
||||
const newTodoItem: TodoItem = new TodoItem(this.nextId, text);
|
||||
this.todoItems.push(newTodoItem);
|
||||
|
||||
this.saveItems()
|
||||
}
|
||||
|
||||
checkById(id: number, checked: boolean) {
|
||||
this.todoItems[id - 1].checked = checked;
|
||||
this.saveItems()
|
||||
}
|
||||
|
||||
loadItems() {
|
||||
const saveDataRaw: any = localStorage.getItem("todoList");
|
||||
|
||||
if (saveDataRaw as string) this.todoItems = JSON.parse(saveDataRaw);
|
||||
}
|
||||
|
||||
saveItems() {
|
||||
localStorage.setItem("todoList", JSON.stringify(this.todoItems))
|
||||
}
|
||||
}
|
||||
@@ -1 +1,9 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
body {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background-color: whitesmoke;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user