Skip to content

Commit d59f44e

Browse files
authored
Merge pull request #31 from ChicoState/widget-todo
Widget To-Do List and Notes
2 parents 2ced68c + 597f0a9 commit d59f44e

File tree

5 files changed

+301
-1
lines changed

5 files changed

+301
-1
lines changed

src/App.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import Menu from "./Menu";
44
import { Grid } from "./Grid";
55
import { Weather } from "./widgets/Weather";
66
import { Clock } from "./widgets/Clock";
7+
import { ToDoList } from "./widgets/ToDoList";
8+
import { Notepad } from "./widgets/Notepad";
79
import { Search } from "./widgets/Search";
810
import { Shortcut } from "./widgets/Shortcut";
911
import styles from "./App.css";
@@ -69,6 +71,20 @@ const App = () => {
6971
<Clock></Clock>
7072
</Widget>
7173

74+
<Widget
75+
size={{ width: 5, height: 5 }}
76+
position={{ gridX: 18, gridY: 1 }}
77+
>
78+
<ToDoList></ToDoList>
79+
</Widget>
80+
81+
<Widget
82+
size={{ width: 5, height: 5 }}
83+
position={{ gridX: 1, gridY: 1 }}
84+
>
85+
<Notepad></Notepad>
86+
</Widget>
87+
7288
<Widget
7389
size={{ width: 8, height: 1 }}
7490
position={{ gridX: 8, gridY: 3 }}
@@ -121,4 +137,4 @@ const App = () => {
121137
);
122138
};
123139

124-
export default App;
140+
export default App;

src/widgets/Notepad.css

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.body {
2+
width: 100%;
3+
height: 100%;
4+
display: flex;
5+
flex-direction: column;
6+
padding: 12px;
7+
}
8+
9+
.title {
10+
font-size: 1.2rem;
11+
font-weight: 700;
12+
margin-bottom: 10px;
13+
color: #fffc;
14+
text-align: center;
15+
}
16+
17+
/* The note area */
18+
.textarea {
19+
flex: 1;
20+
width: 100%;
21+
resize: none;
22+
padding: 10px;
23+
font-size: 0.95rem;
24+
font-family: "Inter", sans-serif;
25+
color: #fffc;
26+
background-color: #fff0;
27+
outline: none;
28+
transition:
29+
border-color 0.2s,
30+
background-color 0.2s;
31+
}
32+
33+
.textarea::placeholder {
34+
color: #fff6;
35+
}
36+
37+
.textarea:focus {
38+
border-color: #fff4;
39+
background-color: #0002;
40+
}

src/widgets/Notepad.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { useState, useEffect } from "react";
2+
import globalStyles from "../App.css";
3+
import styles from "./Notepad.css";
4+
5+
export function Notepad() {
6+
const [note, setNote] = useState("");
7+
8+
// Load saved note on startup
9+
useEffect(() => {
10+
const savedNote = localStorage.getItem("notepad-note");
11+
if (savedNote) setNote(savedNote);
12+
}, []);
13+
14+
// Save note to localStorage on every change
15+
useEffect(() => {
16+
localStorage.setItem("notepad-note", note);
17+
}, [note]);
18+
19+
return (
20+
<div className={[globalStyles.container, styles.body].join(" ")}>
21+
<h3 className={styles.title}>My Notes</h3>
22+
<textarea
23+
className={[globalStyles.container, styles.textarea].join(" ")}
24+
value={note}
25+
onChange={(e) => setNote(e.target.value)}
26+
placeholder="Type your notes here..."
27+
/>
28+
</div>
29+
);
30+
}

src/widgets/ToDoList.css

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/* Widget container */
2+
.body {
3+
width: 100%;
4+
height: 100%;
5+
display: flex;
6+
flex-direction: column;
7+
align-items: stretch; /* stretch title and list to full width */
8+
justify-content: flex-start;
9+
padding: 12px;
10+
box-sizing: border-box;
11+
overflow: hidden;
12+
}
13+
14+
/* Title at the top */
15+
.title {
16+
font-size: 1.2rem;
17+
font-weight: 700;
18+
margin-bottom: 10px;
19+
color: #fffc;
20+
text-align: center;
21+
}
22+
23+
/* Task list */
24+
.todo-list {
25+
list-style: none; /* remove default bullets */
26+
padding: 0;
27+
margin: 0 0 10px 0;
28+
flex: 1; /* fill available space */
29+
overflow-y: auto; /* scroll if too many tasks */
30+
}
31+
32+
/* Each task item */
33+
.todo-list li {
34+
display: flex;
35+
justify-content: space-between;
36+
align-items: center;
37+
padding: 6px 8px;
38+
border-bottom: 1px solid #fff4;
39+
border-radius: 4px;
40+
cursor: pointer;
41+
transition: background 0.2s;
42+
}
43+
44+
.todo-list li:hover {
45+
background-color: #fff2; /* subtle hover effect */
46+
}
47+
48+
/* Bullet point before task */
49+
.todo-list li span::before {
50+
content: "•";
51+
color: #fff8;
52+
display: inline-block;
53+
width: 1em;
54+
margin-right: 6px;
55+
font-size: 1.3rem; /* bigger bullet */
56+
line-height: 1;
57+
vertical-align: middle;
58+
}
59+
60+
/* Task text */
61+
.todo-list li span {
62+
color: #fffc; /* black text */
63+
flex: 1; /* fill available width */
64+
}
65+
66+
/* Completed task style */
67+
.completed span {
68+
text-decoration: line-through;
69+
opacity: 0.6;
70+
}
71+
72+
/* Checkmark delete button */
73+
.delete-task {
74+
background: transparent; /* empty square */
75+
border: 2px solid #fff4; /* green border */
76+
color: #fff8; /* green checkmark */
77+
border-radius: 4px;
78+
width: 22px;
79+
height: 22px;
80+
font-size: 14px;
81+
font-weight: bold;
82+
cursor: pointer;
83+
display: flex;
84+
align-items: center;
85+
justify-content: center;
86+
padding: 0;
87+
line-height: 1;
88+
transition:
89+
background 0.2s,
90+
color 0.2s;
91+
}
92+
93+
.delete-task:hover {
94+
background: #fff4; /* light green background on hover */
95+
}
96+
97+
/* Input container */
98+
.todo-input-container {
99+
display: flex;
100+
gap: 6px;
101+
width: 100%;
102+
}
103+
104+
.todo-input-container input {
105+
flex: 1;
106+
padding: 6px 8px;
107+
border-radius: 6px;
108+
font-size: 0.95rem;
109+
color: #fffc;
110+
outline: none;
111+
transition: border-color 0.2s;
112+
}
113+
114+
.todo-input-container input:focus {
115+
border-color: #fff4; /* green accent on focus */
116+
}
117+
118+
.todo-input-container button {
119+
padding: 6px 12px;
120+
border: 1px solid #fff2;
121+
border-radius: 6px;
122+
background-color: #fff4;
123+
color: #fff;
124+
font-weight: 600;
125+
cursor: pointer;
126+
transition: background 0.2s;
127+
}
128+
129+
.todo-input-container button:hover {
130+
background-color: #fff6;
131+
}

src/widgets/ToDoList.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React, { useState, useEffect } from "react";
2+
3+
import globalStyles from "../App.css";
4+
import styles from "./ToDoList.css";
5+
import { XIcon } from "@phosphor-icons/react";
6+
7+
interface Task {
8+
text: string;
9+
completed: boolean;
10+
}
11+
12+
export function ToDoList() {
13+
const [tasks, setTasks] = useState<Task[]>([]);
14+
const [input, setInput] = useState("");
15+
16+
// Load tasks from localStorage
17+
useEffect(() => {
18+
const saved = localStorage.getItem("todo-tasks");
19+
if (saved) setTasks(JSON.parse(saved));
20+
}, []);
21+
22+
// Save tasks to localStorage whenever tasks change
23+
useEffect(() => {
24+
localStorage.setItem("todo-tasks", JSON.stringify(tasks));
25+
}, [tasks]);
26+
27+
const addTask = () => {
28+
if (!input.trim()) return;
29+
setTasks([...tasks, { text: input.trim(), completed: false }]);
30+
setInput(""); // Clear input
31+
};
32+
33+
const toggleTask = (index: number) => {
34+
const newTasks = [...tasks];
35+
newTasks[index].completed = !newTasks[index].completed;
36+
setTasks(newTasks);
37+
};
38+
39+
const deleteTask = (index: number) => {
40+
const newTasks = [...tasks];
41+
newTasks.splice(index, 1);
42+
setTasks(newTasks);
43+
};
44+
45+
return (
46+
<div className={[globalStyles.container, styles.body].join(" ")}>
47+
{/* Title at the top */}
48+
<h3 className={styles.title}>My To-Do List</h3>
49+
50+
<ul className={styles["todo-list"]}>
51+
{tasks.map((task, i) => (
52+
<li
53+
key={i}
54+
className={task.completed ? styles.completed : ""}
55+
onClick={() => toggleTask(i)}
56+
>
57+
<span>{task.text}</span>
58+
<button
59+
className={styles["delete-task"]}
60+
onClick={(e) => {
61+
e.stopPropagation();
62+
deleteTask(i);
63+
}}
64+
>
65+
<XIcon weight="bold"></XIcon>
66+
</button>
67+
</li>
68+
))}
69+
</ul>
70+
71+
<div className={styles["todo-input-container"]}>
72+
<input
73+
className={globalStyles.container}
74+
value={input}
75+
onChange={(e) => setInput(e.target.value)}
76+
placeholder="Add a new task..."
77+
onKeyDown={(e) => e.key === "Enter" && addTask()}
78+
/>
79+
<button onClick={addTask}>Add</button>
80+
</div>
81+
</div>
82+
);
83+
}

0 commit comments

Comments
 (0)