Skip to content

Commit f169e81

Browse files
Merge pull request #53 from gkasoff/master
Add new todo item on pressing enter in between todo list rows
2 parents 0de4e40 + a8306a4 commit f169e81

File tree

7 files changed

+167
-92
lines changed

7 files changed

+167
-92
lines changed

src/components/Todo/Todo.stories.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ export default {
77
title: "Components/Todo",
88
component: Todo,
99
argTypes: {
10-
defaultItems: [{name: "test it", isComplete: false, uuid: '1'}],
10+
defaultItems: [{ name: "test it", isComplete: false, uuid: "1" }],
1111
},
1212
} as Meta;
1313

1414
const Template: Story<TodoAppProps> = (args) => <Todo {...args} />;
1515

1616
export const Example = Template.bind({});
17-
Example.args = {defaultItems: [{name: "test it", isComplete: false, uuid: '1'}]}
18-
17+
Example.args = {
18+
defaultItems: [{ name: "test it", isComplete: false, uuid: "1" }],
19+
};

src/components/Todo/Todo.tsx

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { Item, TodoCompletedList } from "./common";
66
import { Form } from "./common/Todo/Form";
77
import { TodoItem } from "./common/types";
88

9+
import uuid from "react-uuid";
10+
911
export interface TodoAppProps {
1012
defaultItems?: TodoItem[];
1113
onChange: (items: TodoItem[]) => void;
@@ -15,29 +17,83 @@ function TodoApp(props: TodoAppProps) {
1517
const { defaultItems = [], onChange } = props;
1618
const [items, setItems] = useState<TodoItem[]>(defaultItems);
1719
const [focus, setFocus] = useState(-1);
18-
20+
1921
const setItemsCallback = (updatedItems: TodoItem[]) => {
2022
setItems(updatedItems);
2123
onChange(updatedItems);
2224
};
2325

24-
const addItem = (item: TodoItem | TodoItem[]) => {
26+
const changeFocus = useCallback((focusIndex: number) => {
27+
setFocus(focusIndex);
28+
}, []);
29+
30+
const addItem = (
31+
item: TodoItem | TodoItem[],
32+
cursorLocation?: number | null | undefined,
33+
itemIndex?: number
34+
) => {
2535
const itemsCopy = [...items];
26-
if (Array.isArray(item)) {
27-
item.forEach((it) => {
28-
itemsCopy.unshift(it);
29-
});
30-
setItemsCallback([...itemsCopy]);
36+
//if we're typing in the "Add Item" input...
37+
if (
38+
typeof cursorLocation != "number" ||
39+
Array.isArray(item) ||
40+
itemIndex === undefined
41+
) {
42+
if (Array.isArray(item)) {
43+
item.forEach((it) => {
44+
itemsCopy.unshift(it);
45+
});
46+
setItemsCallback([...itemsCopy]);
47+
} else {
48+
itemsCopy.unshift(item);
49+
setItemsCallback([...itemsCopy]);
50+
}
51+
return;
52+
}
53+
// else if we are typing in any other input
54+
let charsAfterCursor = "";
55+
for (let i = cursorLocation; i < item.name.length; i++) {
56+
charsAfterCursor += item.name[i];
57+
}
58+
let charsBeforeCursor = "";
59+
for (let i = 0; i < cursorLocation; i++) {
60+
charsBeforeCursor += item.name[i];
61+
}
62+
// do nothing if the field we are trying to Enter is blank
63+
if (!charsBeforeCursor && !charsAfterCursor) return;
64+
// split up names based on where cursor is when user clicks Enter
65+
const beforeItem = {
66+
name: charsBeforeCursor,
67+
uuid: uuid(),
68+
isComplete: false,
69+
};
70+
const afterItem = {
71+
name: charsAfterCursor,
72+
uuid: uuid(),
73+
isComplete: false,
74+
};
75+
// insert both halves of the input into the itemsCopy array
76+
itemsCopy.splice(itemIndex, 1, beforeItem, afterItem);
77+
// set items with updated array
78+
setItemsCallback([...itemsCopy]);
79+
// after enter is hit, re-position the cursor depending on where in the input it is
80+
if (!charsBeforeCursor) {
81+
changeFocus(itemsCopy.indexOf(beforeItem));
3182
} else {
32-
itemsCopy.unshift(item);
33-
setItemsCallback([...itemsCopy]);
83+
setTimeout(() => {
84+
const inputs = document.querySelectorAll("input[type='text']");
85+
const inputsArray = Array.from(inputs);
86+
const nextInputElement = inputsArray[
87+
itemsCopy.indexOf(afterItem) + 1
88+
] as HTMLInputElement;
89+
changeFocus(itemsCopy.indexOf(afterItem));
90+
requestAnimationFrame(() => {
91+
nextInputElement.setSelectionRange(0, 0);
92+
});
93+
}, 0);
3494
}
3595
};
3696

37-
const changeFocus = useCallback((focusIndex: number) => {
38-
setFocus(focusIndex);
39-
},[])
40-
4197
const completedItems = items.filter((item: TodoItem) => item.isComplete);
4298
const todoItems = items.filter((item: TodoItem) => !item.isComplete);
4399

@@ -77,7 +133,7 @@ function TodoApp(props: TodoAppProps) {
77133
);
78134
})}
79135
</Reorder.Group>
80-
<TodoCompletedList
136+
<TodoCompletedList
81137
items={items}
82138
setItemsCallback={setItemsCallback}
83139
completedItems={completedItems}

src/components/Todo/common/Todo/CompletedList/index.tsx

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import React, { FC } from "react";
2-
import {
3-
Checkbox,
4-
Container,
5-
makeStyles,
6-
Typography
7-
} from "@material-ui/core";
2+
import { Checkbox, Container, makeStyles, Typography } from "@material-ui/core";
83
import { Accordion } from "../../Accordion";
94
import { TodoItem } from "../../types";
105

@@ -17,7 +12,7 @@ const useStyles = makeStyles({
1712
textDecorationLine: "line-through",
1813
textDecorationStyle: "solid",
1914
padding: "10px 0px 7px",
20-
}
15+
},
2116
});
2217

2318
export interface TodoCompletedItemProps {
@@ -53,7 +48,6 @@ export const TodoCompletedItem: FC<TodoCompletedItemProps> = ({
5348
return null;
5449
};
5550

56-
5751
export interface TodoCompletedListProps {
5852
items: TodoItem[];
5953
completedItems: TodoItem[];
@@ -63,24 +57,24 @@ export interface TodoCompletedListProps {
6357
export const TodoCompletedList: FC<TodoCompletedListProps> = ({
6458
completedItems,
6559
items,
66-
setItemsCallback
60+
setItemsCallback,
6761
}) => {
6862
const completedItemsLength = completedItems.length;
6963

70-
if(completedItemsLength === 0) return null;
64+
if (completedItemsLength === 0) return null;
7165

7266
return (
73-
<Accordion title={`${completedItemsLength} Completed items`}>
74-
{items.map((item, index) => {
75-
return (
76-
<TodoCompletedItem
77-
items={items}
78-
key={item.uuid}
79-
itemIndex={index}
80-
setItemsCallback={setItemsCallback}
81-
/>
82-
);
83-
})}
84-
</Accordion>
85-
)
86-
}
67+
<Accordion title={`${completedItemsLength} Completed items`}>
68+
{items.map((item, index) => {
69+
return (
70+
<TodoCompletedItem
71+
items={items}
72+
key={item.uuid}
73+
itemIndex={index}
74+
setItemsCallback={setItemsCallback}
75+
/>
76+
);
77+
})}
78+
</Accordion>
79+
);
80+
};

src/components/Todo/common/Todo/Form/index.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111
import { TodoItem } from "../../types";
1212

1313
export interface AddProps {
14-
addItem: (item: TodoItem | TodoItem[]) => void;
14+
addItem: (
15+
item: TodoItem | TodoItem[],
16+
cursorLocation?: number | null | undefined
17+
) => void;
1518
changeFocus: (focusIndex: number) => void;
1619
}
1720

@@ -68,8 +71,13 @@ export const Form = (props: AddProps) => {
6871
changeFocus(items.length - 1);
6972
}}
7073
onChange={(e) => {
71-
72-
setItemName(e.target.value);
74+
addItem({
75+
name: e.target.value,
76+
uuid: uuid(),
77+
isComplete: false,
78+
});
79+
changeFocus(0);
80+
setItemName("");
7381
}}
7482
placeholder="Add item."
7583
value={itemName}

0 commit comments

Comments
 (0)