Added multiline tasks support. Added task saving with enter, new line is inserted by Shift+Enter

This commit is contained in:
Dmitriy Shishkov 2021-10-19 11:48:58 +03:00
parent 5a7a2f00e4
commit e30ce694d2
No known key found for this signature in database
GPG Key ID: 14358F96FCDD8060
5 changed files with 39 additions and 12 deletions

View File

@ -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

View File

@ -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) }}>

View File

@ -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}

View File

@ -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
View 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();
}
};