Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions javascript/todo-list/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="app">
<h1 class="title">Todo List</h1>

<form id="todo-form" class="todo-form" autocomplete="off">
<input id="todo-input" class="todo-input" type="text" placeholder="Add a new task..." aria-label="Todo text" />
<button type="submit" class="add-btn" aria-label="Add todo">Add</button>
</form>

<ul id="todo-list" class="todo-list" aria-live="polite"></ul>

<div class="footer">
<button id="clear-completed" class="clear-btn" type="button">Clear Completed</button>
</div>
</main>

<script src="script.js"></script>
</body>
</html>


122 changes: 122 additions & 0 deletions javascript/todo-list/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
(() => {
const STORAGE_KEY = 'todo.list.items.v1';

/** @type {{ id: string; text: string; completed: boolean }[]} */
let items = [];

const form = document.getElementById('todo-form');
const input = document.getElementById('todo-input');
const list = document.getElementById('todo-list');
const clearCompletedBtn = document.getElementById('clear-completed');

function load() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
items = raw ? JSON.parse(raw) : [];
if (!Array.isArray(items)) items = [];
} catch { items = []; }
}

function save() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
}

function createItem(text) {
return { id: String(Date.now()) + Math.random().toString(36).slice(2), text, completed: false };
}

function render() {
list.innerHTML = '';
for (const item of items) {
const li = document.createElement('li');
li.className = 'todo-item';
li.dataset.id = item.id;

const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'todo-check';
checkbox.checked = item.completed;

const p = document.createElement('p');
p.className = 'todo-text' + (item.completed ? ' completed' : '');
p.textContent = item.text;

const del = document.createElement('button');
del.className = 'delete-btn';
del.type = 'button';
del.textContent = 'Delete';

li.appendChild(checkbox);
li.appendChild(p);
li.appendChild(del);
list.appendChild(li);
}
}

function addItem(text) {
const trimmed = text.trim();
if (!trimmed) return;
items.unshift(createItem(trimmed));
save();
render();
}

function deleteItem(id) {
items = items.filter(i => i.id !== id);
save();
render();
}

function toggleItem(id, completed) {
const item = items.find(i => i.id === id);
if (!item) return;
item.completed = completed;
save();
render();
}

function clearCompleted() {
items = items.filter(i => !i.completed);
save();
render();
}

form.addEventListener('submit', (e) => {
e.preventDefault();
addItem(input.value);
input.value = '';
input.focus();
});

list.addEventListener('click', (e) => {
const target = e.target;
if (!(target instanceof HTMLElement)) return;
const li = target.closest('.todo-item');
if (!li) return;
const id = li.dataset.id;
if (!id) return;
if (target.classList.contains('delete-btn')) {
deleteItem(id);
}
});

list.addEventListener('change', (e) => {
const target = e.target;
if (!(target instanceof HTMLInputElement)) return;
if (!target.classList.contains('todo-check')) return;
const li = target.closest('.todo-item');
if (!li) return;
const id = li.dataset.id;
if (!id) return;
toggleItem(id, target.checked);
});

clearCompletedBtn.addEventListener('click', () => {
clearCompleted();
});

load();
render();
})();


88 changes: 88 additions & 0 deletions javascript/todo-list/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
:root {
--bg: #0f172a;
--panel: #111827;
--text: #e5e7eb;
--muted: #9ca3af;
--accent: #22c55e;
--danger: #ef4444;
--border: #1f2937;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, "Apple Color Emoji", "Segoe UI Emoji";
background: linear-gradient(180deg, #0b1023, #121a36 60%, #0b1023);
color: var(--text);
display: grid;
place-items: center;
}

.app {
width: 100%;
max-width: 720px;
padding: 24px;
}

.title { margin: 0 0 16px; font-weight: 700; letter-spacing: 0.3px; }

.todo-form {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
margin-bottom: 16px;
}

.todo-input {
padding: 12px 14px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text);
outline: none;
}
.todo-input::placeholder { color: var(--muted); }

.add-btn, .clear-btn {
padding: 12px 16px;
border: 1px solid var(--border);
border-radius: 10px;
background: #0b1324;
color: var(--text);
cursor: pointer;
}
.add-btn:hover { background: #0e172e; }
.clear-btn:hover { background: #0e172e; }

.todo-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; }

.todo-item {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 12px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px 14px;
}

.todo-check { width: 18px; height: 18px; cursor: pointer; }

.todo-text { margin: 0; }
.todo-text.completed { color: var(--muted); text-decoration: line-through; }

.delete-btn {
background: transparent;
color: var(--danger);
border: 1px solid var(--border);
padding: 8px 10px;
border-radius: 8px;
cursor: pointer;
}
.delete-btn:hover { background: rgba(239, 68, 68, 0.08); }

.footer { margin-top: 14px; display: flex; justify-content: flex-end; }