Added multiline tasks support. Added task saving with enter, new line is inserted by Shift+Enter
This commit is contained in:
parent
5a7a2f00e4
commit
e30ce694d2
@ -38,7 +38,6 @@ npm run dev
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Add task saving on Enter key press, remapping new line to Shift+Enter
|
|
||||||
- Convert to monorepo and add backend for tasks syncing
|
- Convert to monorepo and add backend for tasks syncing
|
||||||
- Add ServiceWorker
|
- Add ServiceWorker
|
||||||
- Switch to IndexedDB
|
- Switch to IndexedDB
|
||||||
|
@ -12,18 +12,19 @@ import {
|
|||||||
close as closeAction,
|
close as closeAction,
|
||||||
} from "../store/slices/uiState";
|
} from "../store/slices/uiState";
|
||||||
import { add } from "../store/slices/todo";
|
import { add } from "../store/slices/todo";
|
||||||
|
import { enterHandler } from "../utils";
|
||||||
|
|
||||||
export const AddTask: React.FC = () => {
|
export const AddTask: React.FC = () => {
|
||||||
const open = useAppSelector((state) => state.uiState.addBarOpen);
|
const open = useAppSelector((state) => state.uiState.addBarOpen);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { value, onChange, submit } = useInputValue("", (submitValue) =>
|
const { value, change, submit } = useInputValue("", (submitValue) =>
|
||||||
dispatch(add(submitValue))
|
dispatch(add(submitValue))
|
||||||
);
|
);
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
submit();
|
submit();
|
||||||
onChange("");
|
change("");
|
||||||
dispatch(closeAction());
|
dispatch(closeAction());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ export const AddTask: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<InputBase
|
<InputBase
|
||||||
|
sx={{ "& .MuiInputBase-inputMultiline": { whiteSpace: "pre-wrap" } }}
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder="New task"
|
placeholder="New task"
|
||||||
autoFocus={open}
|
autoFocus={open}
|
||||||
@ -65,7 +67,8 @@ export const AddTask: React.FC = () => {
|
|||||||
onFocus={(e) => {
|
onFocus={(e) => {
|
||||||
e.currentTarget.setSelectionRange(value.length, value.length);
|
e.currentTarget.setSelectionRange(value.length, value.length);
|
||||||
}}
|
}}
|
||||||
onChange={(e) => onChange(e.currentTarget.value)}
|
onChange={(e) => change(e.currentTarget.value)}
|
||||||
|
onKeyDown={enterHandler(save)}
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
<Box sx={{ paddingTop: (theme) => theme.spacing(1) }}>
|
<Box sx={{ paddingTop: (theme) => theme.spacing(1) }}>
|
||||||
|
@ -11,6 +11,7 @@ import DeleteOutlined from "@mui/icons-material/DeleteOutlined";
|
|||||||
import { TaskItemT } from "../types";
|
import { TaskItemT } from "../types";
|
||||||
import { useAppDispatch, useInputValue } from "../hooks";
|
import { useAppDispatch, useInputValue } from "../hooks";
|
||||||
import { updateText, markDone, remove } from "../store/slices/todo";
|
import { updateText, markDone, remove } from "../store/slices/todo";
|
||||||
|
import { enterHandler } from "../utils";
|
||||||
|
|
||||||
export type TodoItemProps = { task: TaskItemT; index: number };
|
export type TodoItemProps = { task: TaskItemT; index: number };
|
||||||
|
|
||||||
@ -20,10 +21,15 @@ export const TodoItem: React.FC<TodoItemProps> = ({ task, index }) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const { value, onChange, submit } = useInputValue(text, (submitValue) =>
|
const { value, change, submit } = useInputValue(text, (submitValue) =>
|
||||||
dispatch(updateText({ index, text: submitValue }))
|
dispatch(updateText({ index, text: submitValue }))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
setEditing(false);
|
||||||
|
submit();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -39,17 +45,17 @@ export const TodoItem: React.FC<TodoItemProps> = ({ task, index }) => {
|
|||||||
onFocus={(e) => {
|
onFocus={(e) => {
|
||||||
e.currentTarget.setSelectionRange(value.length, value.length);
|
e.currentTarget.setSelectionRange(value.length, value.length);
|
||||||
}}
|
}}
|
||||||
onChange={(e) => onChange(e.currentTarget.value)}
|
onChange={(e) => change(e.currentTarget.value)}
|
||||||
onBlur={() => {
|
onBlur={save}
|
||||||
setEditing(false);
|
inputProps={{ onKeyDown: (e) => enterHandler(save) }}
|
||||||
submit();
|
|
||||||
}}
|
|
||||||
multiline
|
multiline
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ListItemText
|
<ListItemText
|
||||||
sx={{
|
sx={{
|
||||||
textDecoration: done ? "line-through" : undefined,
|
textDecoration: done ? "line-through" : undefined,
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
onClick={() => setEditing(true)}
|
onClick={() => setEditing(true)}
|
||||||
primary={text}
|
primary={text}
|
||||||
|
@ -4,7 +4,7 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
|||||||
import { AppDispatch, RootState } from "./store";
|
import { AppDispatch, RootState } from "./store";
|
||||||
|
|
||||||
export type UseInputValueReturnT = {
|
export type UseInputValueReturnT = {
|
||||||
onChange: (value: string) => void;
|
change: (value: string) => void;
|
||||||
submit: () => void;
|
submit: () => void;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
@ -16,7 +16,7 @@ export const useInputValue = (
|
|||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onChange: (value) => setValue(value),
|
change: (value) => setValue(value),
|
||||||
submit: () => onSubmit(value),
|
submit: () => onSubmit(value),
|
||||||
value,
|
value,
|
||||||
};
|
};
|
||||||
|
19
src/utils.ts
Normal file
19
src/utils.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { KeyboardEventHandler } from "react";
|
||||||
|
|
||||||
|
export const enterHandler =
|
||||||
|
(
|
||||||
|
save: () => void
|
||||||
|
): KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement> =>
|
||||||
|
(e) => {
|
||||||
|
if (e.code === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.shiftKey) {
|
||||||
|
const value = e.currentTarget.value,
|
||||||
|
selEnd = e.currentTarget.selectionEnd ?? 0,
|
||||||
|
selStart = e.currentTarget.selectionStart ?? 0;
|
||||||
|
|
||||||
|
e.currentTarget.value =
|
||||||
|
value.slice(0, selStart) + "\n" + value.slice(selEnd);
|
||||||
|
} else save();
|
||||||
|
}
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user