Skip to content

Commit f102b3b

Browse files
Merge pull request #185 from SaiSathwikAnchuri/ui-theme-improvements
Fixes #177: Added light theme and improved overall UI
2 parents 9e60887 + 2de64a2 commit f102b3b

File tree

3 files changed

+256
-25
lines changed

3 files changed

+256
-25
lines changed

projects/todo/index.html

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,44 @@
1010

1111
<body>
1212
<main>
13-
<h1>Todo List</h1>
14-
<form id="form"><input id="input" placeholder="Add a task" required> <button>Add</button></form>
15-
<ul id="list"></ul>
16-
<p class="notes">Add persistence, sorting, filters, themes.</p>
13+
<header class="app-header">
14+
<h1>Todo</h1>
15+
<p class="subtitle">Simple, focused tasks</p>
16+
</header>
17+
18+
<form id="form" class="add-form" aria-label="Add todo">
19+
<input id="input" placeholder="Add a task" required aria-label="Task description">
20+
<input id="due" type="date" aria-label="Due date (optional)">
21+
<button type="submit">Add</button>
22+
</form>
23+
24+
<ul id="list" aria-live="polite"></ul>
25+
26+
<div class="controls">
27+
<div class="filter-controls">
28+
<button class="filter-btn active" data-filter="all">All</button>
29+
<button class="filter-btn" data-filter="active">Active</button>
30+
<button class="filter-btn" data-filter="completed">Completed</button>
31+
</div>
32+
<div class="sort-controls">
33+
<label for="sort">Sort</label>
34+
<select id="sort" aria-label="Sort todos">
35+
<option value="created">Created</option>
36+
<option value="due">Due date</option>
37+
</select>
38+
</div>
39+
</div>
40+
41+
<div class="stats">
42+
<div id="counts">0 active • 0 total</div>
43+
<div>
44+
<button id="clear-completed" aria-label="Clear completed tasks">Clear completed tasks</button>
45+
</div>
46+
</div>
47+
48+
<div id="pending-list" class="pending-list muted" aria-live="polite">No pending tasks</div>
49+
50+
1751
</main>
1852
<script type="module" src="./main.js"></script>
1953
</body>

projects/todo/main.js

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
const form = document.getElementById('form');
22
const input = document.getElementById('input');
3+
const dueInput = document.getElementById('due');
34
const list = document.getElementById('list');
5+
const counts = document.getElementById('counts');
6+
const clearCompletedBtn = document.getElementById('clear-completed');
7+
const filterButtons = document.querySelectorAll('.filter-btn');
8+
const sortSelect = document.getElementById('sort');
49

5-
let items = [];
10+
let items = JSON.parse(localStorage.getItem('todo-items') || '[]');
11+
let filter = 'all';
12+
let sortBy = 'created';
13+
14+
function uid() {
15+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();
16+
// fallback: timestamp + random
17+
return 'id-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2,9);
18+
}
619

720
function escapeHTML(text) {
821
const div = document.createElement('div');
@@ -12,39 +25,136 @@ function escapeHTML(text) {
1225

1326
form.addEventListener('submit', (e) => {
1427
e.preventDefault();
15-
items.push({ id: crypto.randomUUID(), text: input.value.trim(), done: false });
28+
const text = input.value.trim();
29+
if (!text) return;
30+
const due = dueInput.value ? new Date(dueInput.value).toISOString() : null;
31+
items.push({ id: uid(), text, done: false, created: Date.now(), due });
1632
input.value = '';
17-
render();
33+
dueInput.value = '';
34+
saveAndRender();
1835
});
1936

2037
function render() {
2138
list.innerHTML = '';
22-
for (const it of items) {
39+
40+
// Apply filter and sort
41+
let visible = items.slice();
42+
if (filter === 'active') visible = visible.filter(i => !i.done);
43+
if (filter === 'completed') visible = visible.filter(i => i.done);
44+
45+
if (sortBy === 'due') {
46+
visible.sort((a,b) => {
47+
if (!a.due && !b.due) return a.created - b.created;
48+
if (!a.due) return 1;
49+
if (!b.due) return -1;
50+
return new Date(a.due) - new Date(b.due);
51+
});
52+
} else {
53+
visible.sort((a,b) => a.created - b.created);
54+
}
55+
56+
for (const it of visible) {
2357
const li = document.createElement('li');
2458
if (it.done) li.classList.add('done');
2559

60+
// overdue detection
61+
const isOverdue = it.due && !it.done && (new Date(it.due) < startOfToday());
62+
if (isOverdue) li.classList.add('overdue');
63+
64+
const dueMeta = it.due ? `<small class="meta">Due ${new Date(it.due).toLocaleDateString()}</small>` : '';
65+
2666
li.innerHTML = `
2767
<label>
28-
<input type="checkbox" ${it.done ? 'checked' : ''}>
29-
<span>${escapeHTML(it.text)}</span>
68+
<input type="checkbox" ${it.done ? 'checked' : ''} aria-label="Mark task as done">
69+
<span>${escapeHTML(it.text)} ${dueMeta}</span>
3070
</label>
31-
<button class="del" aria-label="Delete">×</button>
71+
<button class="del" aria-label="Delete task">×</button>
3272
`;
3373

34-
const checkbox = li.querySelector('input');
74+
const checkbox = li.querySelector('input[type=checkbox]');
3575
checkbox.addEventListener('change', (e) => {
3676
it.done = e.target.checked;
3777
li.classList.toggle('done', it.done);
78+
saveAndRender();
3879
});
3980

4081
li.querySelector('.del').addEventListener('click', () => {
4182
items = items.filter((x) => x.id !== it.id);
42-
render();
83+
saveAndRender();
4384
});
4485

4586
list.appendChild(li);
4687
}
88+
89+
updateCounts();
90+
}
91+
92+
function startOfToday() {
93+
const d = new Date();
94+
d.setHours(0,0,0,0);
95+
return d;
96+
}
97+
98+
function updateCounts() {
99+
const total = items.length;
100+
const active = items.filter(i => !i.done).length;
101+
counts.textContent = `${active} active • ${total} total`;
102+
// Render pending tasks as short text list (up to 5)
103+
const pendingEl = document.getElementById('pending-list');
104+
if (pendingEl) {
105+
const pending = items.filter(i => !i.done);
106+
if (!pending.length) {
107+
pendingEl.textContent = 'No pending tasks';
108+
} else {
109+
const max = 5;
110+
const slice = pending.slice(0, max);
111+
pendingEl.innerHTML = slice.map(i => `<div class="pending-item">${escapeHTML(i.text)}</div>`).join('');
112+
if (pending.length > max) {
113+
const more = pending.length - max;
114+
pendingEl.insertAdjacentHTML('beforeend', `<div class="muted">+${more} more</div>`);
115+
}
116+
}
117+
}
47118
}
48119

120+
function saveAndRender() {
121+
localStorage.setItem('todo-items', JSON.stringify(items));
122+
render();
123+
}
124+
125+
// initialize
49126
render();
50-
// TODOs: save to localStorage; add filter (all/active/done); sort; theme switcher
127+
128+
// wire filters
129+
filterButtons.forEach(btn => btn.addEventListener('click', (e) => {
130+
filterButtons.forEach(b => b.classList.remove('active'));
131+
btn.classList.add('active');
132+
filter = btn.dataset.filter;
133+
render();
134+
}));
135+
136+
sortSelect.addEventListener('change', (e) => { sortBy = e.target.value; render(); });
137+
138+
clearCompletedBtn.addEventListener('click', () => { items = items.filter(i => !i.done); saveAndRender(); });
139+
140+
// theme sync: read global theme from localStorage (some apps use body.dark-mode), apply data-theme
141+
function syncTheme() {
142+
const theme = localStorage.getItem('theme') || (document.body.classList.contains('dark-mode') ? 'dark' : 'light');
143+
if (theme === 'light') {
144+
document.documentElement.setAttribute('data-theme', 'light');
145+
} else {
146+
document.documentElement.removeAttribute('data-theme');
147+
}
148+
}
149+
150+
syncTheme();
151+
152+
// respond to storage changes (theme toggled in another tab or via global control)
153+
window.addEventListener('storage', (e) => {
154+
if (e.key === 'theme') syncTheme();
155+
});
156+
157+
// keyboard: quick add focus
158+
document.addEventListener('keydown', (e) => {
159+
if (e.key === 'n') { input.focus(); }
160+
});

0 commit comments

Comments
 (0)