diff --git a/javascript/todo-list/index.html b/javascript/todo-list/index.html new file mode 100644 index 0000000..ada2be2 --- /dev/null +++ b/javascript/todo-list/index.html @@ -0,0 +1,29 @@ + + + + + + Todo List + + + +
+

Todo List

+ +
+ + +
+ + + + +
+ + + + + + diff --git a/javascript/todo-list/script.js b/javascript/todo-list/script.js new file mode 100644 index 0000000..1d418b0 --- /dev/null +++ b/javascript/todo-list/script.js @@ -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(); +})(); + + diff --git a/javascript/todo-list/style.css b/javascript/todo-list/style.css new file mode 100644 index 0000000..044f669 --- /dev/null +++ b/javascript/todo-list/style.css @@ -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; } + +