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
18 changes: 17 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Menu from "./Menu";
import { Grid } from "./Grid";
import { Weather } from "./widgets/Weather";
import { Clock } from "./widgets/Clock";
import { ToDoList } from "./widgets/ToDoList";
import { Notepad } from "./widgets/Notepad";
import { Search } from "./widgets/Search";
import { Shortcut } from "./widgets/Shortcut";
import styles from "./App.css";
Expand Down Expand Up @@ -69,6 +71,20 @@ const App = () => {
<Clock></Clock>
</Widget>

<Widget
size={{ width: 5, height: 5 }}
position={{ gridX: 18, gridY: 1 }}
>
<ToDoList></ToDoList>
</Widget>

<Widget
size={{ width: 5, height: 5 }}
position={{ gridX: 1, gridY: 1 }}
>
<Notepad></Notepad>
</Widget>

<Widget
size={{ width: 8, height: 1 }}
position={{ gridX: 8, gridY: 3 }}
Expand Down Expand Up @@ -121,4 +137,4 @@ const App = () => {
);
};

export default App;
export default App;
40 changes: 40 additions & 0 deletions src/widgets/Notepad.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.body {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 12px;
}

.title {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 10px;
color: #fffc;
text-align: center;
}

/* The note area */
.textarea {
flex: 1;
width: 100%;
resize: none;
padding: 10px;
font-size: 0.95rem;
font-family: "Inter", sans-serif;
color: #fffc;
background-color: #fff0;
outline: none;
transition:
border-color 0.2s,
background-color 0.2s;
}

.textarea::placeholder {
color: #fff6;
}

.textarea:focus {
border-color: #fff4;
background-color: #0002;
}
30 changes: 30 additions & 0 deletions src/widgets/Notepad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { useState, useEffect } from "react";
import globalStyles from "../App.css";
import styles from "./Notepad.css";

export function Notepad() {
const [note, setNote] = useState("");

// Load saved note on startup
useEffect(() => {
const savedNote = localStorage.getItem("notepad-note");
if (savedNote) setNote(savedNote);
}, []);

// Save note to localStorage on every change
useEffect(() => {
localStorage.setItem("notepad-note", note);
}, [note]);

return (
<div className={[globalStyles.container, styles.body].join(" ")}>
<h3 className={styles.title}>My Notes</h3>
<textarea
className={[globalStyles.container, styles.textarea].join(" ")}
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="Type your notes here..."
/>
</div>
);
}
131 changes: 131 additions & 0 deletions src/widgets/ToDoList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* Widget container */
.body {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: stretch; /* stretch title and list to full width */
justify-content: flex-start;
padding: 12px;
box-sizing: border-box;
overflow: hidden;
}

/* Title at the top */
.title {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 10px;
color: #fffc;
text-align: center;
}

/* Task list */
.todo-list {
list-style: none; /* remove default bullets */
padding: 0;
margin: 0 0 10px 0;
flex: 1; /* fill available space */
overflow-y: auto; /* scroll if too many tasks */
}

/* Each task item */
.todo-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 8px;
border-bottom: 1px solid #fff4;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}

.todo-list li:hover {
background-color: #fff2; /* subtle hover effect */
}

/* Bullet point before task */
.todo-list li span::before {
content: "•";
color: #fff8;
display: inline-block;
width: 1em;
margin-right: 6px;
font-size: 1.3rem; /* bigger bullet */
line-height: 1;
vertical-align: middle;
}

/* Task text */
.todo-list li span {
color: #fffc; /* black text */
flex: 1; /* fill available width */
}

/* Completed task style */
.completed span {
text-decoration: line-through;
opacity: 0.6;
}

/* Checkmark delete button */
.delete-task {
background: transparent; /* empty square */
border: 2px solid #fff4; /* green border */
color: #fff8; /* green checkmark */
border-radius: 4px;
width: 22px;
height: 22px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
line-height: 1;
transition:
background 0.2s,
color 0.2s;
}

.delete-task:hover {
background: #fff4; /* light green background on hover */
}

/* Input container */
.todo-input-container {
display: flex;
gap: 6px;
width: 100%;
}

.todo-input-container input {
flex: 1;
padding: 6px 8px;
border-radius: 6px;
font-size: 0.95rem;
color: #fffc;
outline: none;
transition: border-color 0.2s;
}

.todo-input-container input:focus {
border-color: #fff4; /* green accent on focus */
}

.todo-input-container button {
padding: 6px 12px;
border: 1px solid #fff2;
border-radius: 6px;
background-color: #fff4;
color: #fff;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}

.todo-input-container button:hover {
background-color: #fff6;
}
83 changes: 83 additions & 0 deletions src/widgets/ToDoList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useState, useEffect } from "react";

import globalStyles from "../App.css";
import styles from "./ToDoList.css";
import { XIcon } from "@phosphor-icons/react";

interface Task {
text: string;
completed: boolean;
}

export function ToDoList() {
const [tasks, setTasks] = useState<Task[]>([]);
const [input, setInput] = useState("");

// Load tasks from localStorage
useEffect(() => {
const saved = localStorage.getItem("todo-tasks");
if (saved) setTasks(JSON.parse(saved));
}, []);

// Save tasks to localStorage whenever tasks change
useEffect(() => {
localStorage.setItem("todo-tasks", JSON.stringify(tasks));
}, [tasks]);

const addTask = () => {
if (!input.trim()) return;
setTasks([...tasks, { text: input.trim(), completed: false }]);
setInput(""); // Clear input
};

const toggleTask = (index: number) => {
const newTasks = [...tasks];
newTasks[index].completed = !newTasks[index].completed;
setTasks(newTasks);
};

const deleteTask = (index: number) => {
const newTasks = [...tasks];
newTasks.splice(index, 1);
setTasks(newTasks);
};

return (
<div className={[globalStyles.container, styles.body].join(" ")}>
{/* Title at the top */}
<h3 className={styles.title}>My To-Do List</h3>

<ul className={styles["todo-list"]}>
{tasks.map((task, i) => (
<li
key={i}
className={task.completed ? styles.completed : ""}
onClick={() => toggleTask(i)}
>
<span>{task.text}</span>
<button
className={styles["delete-task"]}
onClick={(e) => {
e.stopPropagation();
deleteTask(i);
}}
>
<XIcon weight="bold"></XIcon>
</button>
</li>
))}
</ul>

<div className={styles["todo-input-container"]}>
<input
className={globalStyles.container}
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add a new task..."
onKeyDown={(e) => e.key === "Enter" && addTask()}
/>
<button onClick={addTask}>Add</button>
</div>
</div>
);
}