From 3c1b6516a5d1eb1f6d629ad33c8e6a4cd6c28526 Mon Sep 17 00:00:00 2001 From: okplanb <26038255+ok-plan-b@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:07:48 +0400 Subject: [PATCH 1/2] feat: removed ripple effect --- src/main.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main.tsx b/src/main.tsx index 744deb9..ed796cc 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,12 +1,25 @@ import React from "react"; import ReactDOM from "react-dom/client"; +import { createTheme, ThemeProvider } from '@mui/material'; import App from "./App.tsx"; import "@fontsource/roboto/latin-300.css"; +const theme = createTheme({ + components: { + MuiButtonBase: { + defaultProps: { + disableRipple: true, + }, + }, + }, +}); + ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + + + ); From dcfc62c5a02841f8060def0f6771ef8caca03c09 Mon Sep 17 00:00:00 2001 From: okplanb <26038255+ok-plan-b@users.noreply.github.com> Date: Fri, 15 Dec 2023 03:03:15 +0400 Subject: [PATCH 2/2] feat: [S1] edit modal implemented --- index.html | 2 +- package.json | 6 +- src/App.scss | 8 ++ src/App.tsx | 223 +++++++++++++++++++++++++++++++++++++++-------- src/constants.ts | 6 +- src/helpers.ts | 13 +++ yarn.lock | 10 +++ 7 files changed, 226 insertions(+), 42 deletions(-) create mode 100644 src/helpers.ts diff --git a/index.html b/index.html index 8b53bb5..d4e76fd 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ - Vite + React + TS + Sia | Minimalistic ToDo list
diff --git a/package.json b/package.json index fe31dbd..e0364cb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sia", "private": true, - "version": "0.1.0", + "version": "0.2.0", "type": "module", "scripts": { "dev": "vite", @@ -24,12 +24,14 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-infinite-scroll-component": "^6.1.0", - "react-router-dom": "^6.12.1" + "react-router-dom": "^6.12.1", + "uuid": "^9.0.1" }, "devDependencies": { "@types/node": "^20.2.5", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", "@vitejs/plugin-react": "^4.0.0", diff --git a/src/App.scss b/src/App.scss index 6f3187b..c5aaf52 100644 --- a/src/App.scss +++ b/src/App.scss @@ -36,6 +36,14 @@ a { .item { padding: 0.2rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.item span:last-child { + overflow: hidden; + text-overflow: ellipsis; } #root .progress { diff --git a/src/App.tsx b/src/App.tsx index 18e3896..ff92573 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,33 @@ -import { useCallback, useState } from "react"; +import { useCallback, useState, useRef } from "react"; +import { v4 as uuidv4 } from 'uuid'; import { Paper, Tooltip, FormControlLabel, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Typography, LinearProgress, - Button, useMediaQuery, Box } from "@mui/material"; + Button, IconButton, useMediaQuery, Box, Input } from "@mui/material"; +import AddIcon from '@mui/icons-material/Add'; +import DeleteIcon from '@mui/icons-material/Delete'; import { useTheme } from '@mui/material/styles'; -import { basicList, storage_key, tooptip_offset, total_percent } from ":src/constants"; +import { debounce } from ":src/helpers"; +import { basicList, debounce_delay, storage_key, + task_input_limit, tooptip_offset, total_percent } from ":src/constants"; import "./App.scss"; type Task = { description: string; checked: boolean; + key: string; }; -const initialTasks: Task[] = basicList.map(description => { - return { description, checked: false } -}); -initialTasks[0].checked = true; +function getInitialTasks() { + const initialTasks: Task[] = basicList.map(description => { + return { description, checked: false, key: uuidv4() } + }); + initialTasks[0].checked = true; + return initialTasks; +} const saved_tasks = localStorage.getItem(storage_key); @@ -33,11 +42,14 @@ const calcProgress = (tasks: Task[]) => { } export default function App(): JSX.Element { - const [tasks, setTasks] = useState(saved_tasks ? JSON.parse(saved_tasks) : initialTasks); + const [tasks, setTasks] = useState(saved_tasks ? JSON.parse(saved_tasks) : getInitialTasks()); + const [editedTasks, setEditedTasks] = useState([]); const [progress, setProgress] = useState(calcProgress(tasks)); - const [open, setOpen] = useState(false); + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); + const [isConfirmOpen, setIsConfirmOpen] = useState(false); const theme = useTheme(); const fullScreen = useMediaQuery(theme.breakpoints.down('md')); + const addButtonRef = useRef(null); const updateTasks = (newTasks: Task[]) => { localStorage.setItem(storage_key, JSON.stringify(newTasks)); @@ -52,22 +64,72 @@ export default function App(): JSX.Element { }, [tasks]); const handleRestart = useCallback(() => { - const newTasks = initialTasks; + const newTasks = tasks.slice(); + newTasks.forEach(task => task.checked = false); + updateTasks(newTasks); + setIsConfirmOpen(false); + }, [tasks]); + + const handleConfirmOpen = () => { + setIsConfirmOpen(true); + }; + + const handleConfirmClose = () => { + setIsConfirmOpen(false); + }; + + const handleEdit = () => { + setEditedTasks(tasks.slice()); + setIsEditDialogOpen(true); + }; + + const handleCancelEdit = () => { + setIsEditDialogOpen(false); + }; + + const handleAddNewTask = () => { + const newTasks = editedTasks.slice(); + newTasks.push({ + key: uuidv4(), + description: '', + checked: false + }); + setEditedTasks(newTasks); + addButtonRef.current?.scrollIntoView({behavior: 'smooth', inline: 'end' }); + }; + + const handleSaveChanges = () => { + setIsEditDialogOpen(false); + const newTasks = editedTasks.filter(task => task.description); updateTasks(newTasks); - setOpen(false); - }, []); + }; + + const debouncedUpdate = debounce((newDescription: string, key: string) => { + const tasks = editedTasks.map(task => { + if (task.key === key) { + task.description = newDescription; + } + return task; + }); + setEditedTasks(tasks); + }, debounce_delay); - const handleDialogOpen = () => { - setOpen(true); + const handleTaskEdit = (value: string, key: string) => { + debouncedUpdate(value, key); }; - - const handleDialogClose = () => { - setOpen(false); + + const handleDeleteTask = (key: string) => { + const tasks = editedTasks.filter(task => task.key !== key); + setEditedTasks(tasks); }; return ( <> - + - - {tasks.map((task, index) => ( - toggleTask(index)} - /> - } - /> - ))} - + + { tasks.length === 0 ? ( + + It is so empty here... What's the plan? :) + + ) : ( + + {tasks.map((task, index) => ( + toggleTask(index)} + /> + } + /> + ))} + + )} + - + + + + + + + {/* Editing */} + + + + Edit tasks + + + + {editedTasks.map(item => + + ) => { + handleTaskEdit(event.target.value, item.key) + }} + defaultValue={item.description} + fullWidth + sx={{ marginBottom: "0.5rem" }} + inputProps={{ + 'aria-label': 'Task description', + maxLength: task_input_limit + }} + /> + { + handleDeleteTask(item.key) + }} + > + + + + )} + + + + + + + + + + {/* Restart Confirmation */} + @@ -122,7 +271,7 @@ export default function App(): JSX.Element { -