diff --git a/src/components/menu/items/index.ts b/src/components/menu/items/index.ts index a74fae8..079c410 100644 --- a/src/components/menu/items/index.ts +++ b/src/components/menu/items/index.ts @@ -8,3 +8,4 @@ export { SleepTimer as SleepTimerItem } from './sleep-timer'; export { BreathingExercise as BreathingExerciseItem } from './breathing-exercise'; export { Pomodoro as PomodoroItem } from './pomodoro'; export { Notepad as NotepadItem } from './notepad'; +export { Todo as TodoItem } from './todo'; diff --git a/src/components/menu/items/todo.tsx b/src/components/menu/items/todo.tsx new file mode 100644 index 0000000..f2dbc1e --- /dev/null +++ b/src/components/menu/items/todo.tsx @@ -0,0 +1,18 @@ +import { MdTaskAlt } from 'react-icons/md/index'; + +import { Item } from '../item'; + +interface TodoProps { + open: () => void; +} + +export function Todo({ open }: TodoProps) { + return ( + } + label="Todo Checklist" + shortcut="Shift + T" + onClick={open} + /> + ); +} diff --git a/src/components/menu/menu.tsx b/src/components/menu/menu.tsx index 69e44f9..da4302a 100644 --- a/src/components/menu/menu.tsx +++ b/src/components/menu/menu.tsx @@ -15,6 +15,7 @@ import { BreathingExerciseItem, PomodoroItem, NotepadItem, + TodoItem, } from './items'; import { Divider } from './divider'; import { ShareLinkModal } from '@/components/modals/share-link'; @@ -22,7 +23,7 @@ import { PresetsModal } from '@/components/modals/presets'; import { ShortcutsModal } from '@/components/modals/shortcuts'; import { SleepTimerModal } from '@/components/modals/sleep-timer'; import { BreathingExerciseModal } from '../modals/breathing'; -import { Pomodoro, Notepad } from '../toolbox'; +import { Pomodoro, Notepad, Todo } from '../toolbox'; import { fade, mix, slideY } from '@/lib/motion'; import { useSoundStore } from '@/stores/sound'; @@ -44,6 +45,7 @@ export function Menu() { shareLink: false, shortcuts: false, sleepTimer: false, + todo: false, }), [], ); @@ -113,6 +115,7 @@ export function Menu() { open('breathing')} /> open('pomodoro')} /> open('notepad')} /> + open('todo')} /> open('shortcuts')} /> @@ -146,6 +149,7 @@ export function Menu() { onClose={() => close('pomodoro')} /> close('notepad')} /> + close('todo')} /> close('presets')} /> {children}; diff --git a/src/components/toolbox/index.ts b/src/components/toolbox/index.ts index b4b226a..5e568f6 100644 --- a/src/components/toolbox/index.ts +++ b/src/components/toolbox/index.ts @@ -1,2 +1,3 @@ export { Notepad } from './notepad'; export { Pomodoro } from './pomodoro'; +export { Todo } from './todo'; diff --git a/src/components/toolbox/todo/form/form.module.css b/src/components/toolbox/todo/form/form.module.css new file mode 100644 index 0000000..e48d56f --- /dev/null +++ b/src/components/toolbox/todo/form/form.module.css @@ -0,0 +1,35 @@ +.wrapper { + display: flex; + align-items: center; + height: 45px; + padding: 4px; + margin-top: 12px; + background-color: var(--color-neutral-50); + border: 1px solid var(--color-neutral-200); + border-radius: 8px; + + & input { + flex-grow: 1; + min-width: 0; + height: 100%; + padding: 0 8px; + color: var(--color-foreground); + background-color: transparent; + border: none; + outline: none; + } + + & button { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 0 16px; + font-size: var(--font-sm); + color: var(--color-foreground); + cursor: pointer; + background-color: var(--color-neutral-100); + border: 1px solid var(--color-neutral-200); + border-radius: 4px; + } +} diff --git a/src/components/toolbox/todo/form/form.tsx b/src/components/toolbox/todo/form/form.tsx new file mode 100644 index 0000000..853f04c --- /dev/null +++ b/src/components/toolbox/todo/form/form.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react'; + +import { useTodoStore } from '@/stores/todo'; + +import styles from './form.module.css'; + +export function Form() { + const [value, setValue] = useState(''); + + const addTodo = useTodoStore(state => state.addTodo); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!value.trim().length) return; + + addTodo(value); + setValue(''); + }; + + return ( +
+
+ setValue(e.target.value)} + /> + +
+
+ ); +} diff --git a/src/components/toolbox/todo/form/index.ts b/src/components/toolbox/todo/form/index.ts new file mode 100644 index 0000000..9398c9e --- /dev/null +++ b/src/components/toolbox/todo/form/index.ts @@ -0,0 +1 @@ +export { Form } from './form'; diff --git a/src/components/toolbox/todo/index.ts b/src/components/toolbox/todo/index.ts new file mode 100644 index 0000000..b54a84e --- /dev/null +++ b/src/components/toolbox/todo/index.ts @@ -0,0 +1 @@ +export { Todo } from './todo'; diff --git a/src/components/toolbox/todo/todo.module.css b/src/components/toolbox/todo/todo.module.css new file mode 100644 index 0000000..fdbd99d --- /dev/null +++ b/src/components/toolbox/todo/todo.module.css @@ -0,0 +1 @@ +/* WIP */ diff --git a/src/components/toolbox/todo/todo.tsx b/src/components/toolbox/todo/todo.tsx new file mode 100644 index 0000000..05663cd --- /dev/null +++ b/src/components/toolbox/todo/todo.tsx @@ -0,0 +1,20 @@ +import { Modal } from '@/components/modal'; +import { Form } from './form'; +import { Todos } from './todos'; + +import styles from './todo.module.css'; + +interface TodoProps { + onClose: () => void; + show: boolean; +} + +export function Todo({ onClose, show }: TodoProps) { + return ( + +

Todos

+
+ + + ); +} diff --git a/src/components/toolbox/todo/todos/index.ts b/src/components/toolbox/todo/todos/index.ts new file mode 100644 index 0000000..55b9f3d --- /dev/null +++ b/src/components/toolbox/todo/todos/index.ts @@ -0,0 +1 @@ +export { Todos } from './todos'; diff --git a/src/components/toolbox/todo/todos/todo/index.ts b/src/components/toolbox/todo/todos/todo/index.ts new file mode 100644 index 0000000..b54a84e --- /dev/null +++ b/src/components/toolbox/todo/todos/todo/index.ts @@ -0,0 +1 @@ +export { Todo } from './todo'; diff --git a/src/components/toolbox/todo/todos/todo/todo.module.css b/src/components/toolbox/todo/todos/todo/todo.module.css new file mode 100644 index 0000000..b239bcd --- /dev/null +++ b/src/components/toolbox/todo/todos/todo/todo.module.css @@ -0,0 +1,45 @@ +.wrapper { + display: flex; + column-gap: 4px; + align-items: center; + height: 45px; + padding: 4px; + margin-top: 12px; + background-color: var(--color-neutral-50); + border: 1px solid var(--color-neutral-200); + border-radius: 8px; + + & .checkbox { + display: block; + margin: 0 8px 0 4px; + } + + & .textbox { + flex-grow: 1; + min-width: 0; + height: 100%; + font-size: var(--font-sm); + color: var(--color-foreground); + background-color: transparent; + border: none; + outline: none; + + &.done { + color: var(--color-foreground-subtle); + text-decoration: line-through; + } + } + + & button { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + aspect-ratio: 1 / 1; + color: #f43f5e; + cursor: pointer; + background-color: rgb(244 63 94 / 15%); + border: none; + border-radius: 4px; + } +} diff --git a/src/components/toolbox/todo/todos/todo/todo.tsx b/src/components/toolbox/todo/todos/todo/todo.tsx new file mode 100644 index 0000000..3861d7f --- /dev/null +++ b/src/components/toolbox/todo/todos/todo/todo.tsx @@ -0,0 +1,41 @@ +import { FaRegTrashAlt } from 'react-icons/fa/index'; + +import { useTodoStore } from '@/stores/todo'; +import { cn } from '@/helpers/styles'; + +import styles from './todo.module.css'; + +interface TodoProps { + done: boolean; + id: string; + todo: string; +} + +export function Todo({ done, id, todo }: TodoProps) { + const deleteTodo = useTodoStore(state => state.deleteTodo); + const toggleTodo = useTodoStore(state => state.toggleTodo); + const editTodo = useTodoStore(state => state.editTodo); + + const handleCheck = () => toggleTodo(id); + const handleDelete = () => deleteTodo(id); + + return ( +
+ + editTodo(id, e.target.value)} + /> + +
+ ); +} diff --git a/src/components/toolbox/todo/todos/todos.module.css b/src/components/toolbox/todo/todos/todos.module.css new file mode 100644 index 0000000..fdbd99d --- /dev/null +++ b/src/components/toolbox/todo/todos/todos.module.css @@ -0,0 +1 @@ +/* WIP */ diff --git a/src/components/toolbox/todo/todos/todos.tsx b/src/components/toolbox/todo/todos/todos.tsx new file mode 100644 index 0000000..0b81812 --- /dev/null +++ b/src/components/toolbox/todo/todos/todos.tsx @@ -0,0 +1,17 @@ +import { Todo } from './todo'; + +import { useTodoStore } from '@/stores/todo'; + +import styles from './todos.module.css'; + +export function Todos() { + const todos = useTodoStore(state => state.todos); + + return ( +
+ {todos.map(todo => ( + + ))} +
+ ); +} diff --git a/src/stores/todo.ts b/src/stores/todo.ts new file mode 100644 index 0000000..eb2101e --- /dev/null +++ b/src/stores/todo.ts @@ -0,0 +1,81 @@ +import { create } from 'zustand'; +import { createJSONStorage, persist } from 'zustand/middleware'; +import merge from 'deepmerge'; +import { v4 as uuid } from 'uuid'; + +interface TodoStore { + addTodo: (todo: string) => void; + deleteTodo: (id: string) => void; + editTodo: (id: string, newTodo: string) => void; + todos: Array<{ + createdAt: number; + done: boolean; + id: string; + todo: string; + }>; + toggleTodo: (id: string) => void; +} + +export const useTodoStore = create()( + persist( + (set, get) => ({ + addTodo(todo) { + set({ + todos: [ + { + createdAt: Date.now(), + done: false, + id: uuid(), + todo, + }, + ...get().todos, + ], + }); + }, + + deleteTodo(id) { + set({ + todos: get().todos.filter(todo => todo.id !== id), + }); + }, + + editTodo(id, newTodo) { + set({ + todos: get().todos.map(todo => { + if (todo.id !== id) return todo; + + return { + ...todo, + todo: newTodo, + }; + }), + }); + }, + + todos: [], + + toggleTodo(id) { + set({ + todos: get().todos.map(todo => { + if (todo.id !== id) return todo; + + return { + ...todo, + done: !todo.done, + }; + }), + }); + }, + }), + { + merge: (persisted, current) => + merge(current, persisted as Partial), + + name: 'moodist-todos', + partialize: state => ({ todos: state.todos }), + skipHydration: true, + storage: createJSONStorage(() => localStorage), + version: 0, + }, + ), +);