diff --git a/src/features/TaskCard/CreateOrEditTaskCard.js b/src/features/TaskCard/CreateOrEditTaskCard.js new file mode 100644 index 0000000..9d2b789 --- /dev/null +++ b/src/features/TaskCard/CreateOrEditTaskCard.js @@ -0,0 +1,98 @@ +import React, { useState } from "react"; +import { useDispatch } from "react-redux"; +import { createTask, editTask } from "../../storeSlices/boardSlice"; +import useAutoHeightTextArea from "../../utils/useAutoHeightTextArea"; + +import styles from "./TaskCard.module.css"; + +export function CreateOrEditTaskCard({ + mode = "create" /* "create" | "edit" */, + listID, + cardID, + defaultCardText = "", + onSubmit: parentOnSubmit = () => {}, + onCancel: parentOnCancel = () => {} +}) { + /* + --------- + Init here + ---------- + */ + const textAreaRef = useAutoHeightTextArea(); + const dispatch = useDispatch(); + + const isCreateMode = mode === "create"; + + const [newCardText, setNewCardText] = useState(defaultCardText); + const [isFormOpen, setIsFormOpen] = useState(isCreateMode ? false : true); + + const isTextNonEmpty = newCardText.trim().length > 0; + + /* + --------- + Create evt handlers here + ---------- + */ + const onComplete = () => { + setNewCardText(defaultCardText); + setIsFormOpen(false); + }; + + const onSubmit = e => { + e.preventDefault(); + + if (isTextNonEmpty > 0) { + if (isCreateMode) { + dispatch(createTask({ text: newCardText, listID })); + } else { + const payload = { text: newCardText, cardID: cardID, listID }; + + dispatch(editTask(payload)); + } + } + + onComplete(); + + parentOnSubmit(); + }; + + const onCancel = e => { + onComplete(); + + parentOnCancel(); + }; + + /* + --------- + Render here + ---------- + */ + return ( +
+ {!isFormOpen ? ( + + ) : ( +
+ +
+ + +
+
+ )} +
+ ); +} diff --git a/src/features/TaskCard/NewTaskCardPrompt.js b/src/features/TaskCard/NewTaskCardPrompt.js deleted file mode 100644 index c5264f9..0000000 --- a/src/features/TaskCard/NewTaskCardPrompt.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useState } from "react"; -import { useDispatch } from "react-redux"; -import { createTask } from "../../storeSlices/boardSlice"; - -import styles from "./TaskCard.module.css"; - -export function NewTaskCardPrompt({ listID }) { - const dispatch = useDispatch(); - - const [newCardText, setNewCardText] = useState(""); - - return ( -
- setNewCardText(e.target.value)} - onKeyPress={e => { - if (newCardText.trim().length > 0 && e.key === "Enter") { - dispatch(createTask({ text: newCardText, listID })); - - setNewCardText(""); - } - }} - > -
- ); -} diff --git a/src/features/TaskCard/TaskCard.js b/src/features/TaskCard/TaskCard.js index 65061c7..a236c17 100644 --- a/src/features/TaskCard/TaskCard.js +++ b/src/features/TaskCard/TaskCard.js @@ -1,15 +1,30 @@ import React, { useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { createList } from "../../storeSlices/boardSlice"; + +import { CreateOrEditTaskCard } from "./CreateOrEditTaskCard"; import styles from "./TaskCard.module.css"; -export function TaskCard({ cardData }) { - const dispatch = useDispatch(); +export function TaskCard({ cardData, listID }) { + const [isEditing, setIsEditing] = useState(false); - return ( -
+ const stopEditing = () => setIsEditing(false); + + return !isEditing ? ( +
setIsEditing(true)} + > {cardData.text}
+ ) : ( + ); } diff --git a/src/features/TaskCard/TaskCard.module.css b/src/features/TaskCard/TaskCard.module.css index 52558de..0d916d6 100644 --- a/src/features/TaskCard/TaskCard.module.css +++ b/src/features/TaskCard/TaskCard.module.css @@ -5,3 +5,41 @@ padding: 8px; margin: 8px 0; } + +.createCardContainer { + width: 100%; + resize: vertical; + font-size: 1em; +} + +.createCardForm { + margin: 8px 0; +} + +.createInitBtn { + width: 100%; + padding: 8px 0; + background: none; + border: none; + text-align: left; + font-size: 0.85em; +} + +.createActionBtnContainer { + display: flex; + justify-content: space-evenly; +} + +.createActionBtnContainer > input { + flex: 1 0 50px; + margin: 0 8px; + padding: 4px 0; +} + +.createActionBtnContainer > input:first-child { + margin-left: 0; +} + +.createActionBtnContainer > input:last-child { + margin-right: 0; +} diff --git a/src/features/TaskList/TaskList.js b/src/features/TaskList/TaskList.js index c781258..0156300 100644 --- a/src/features/TaskList/TaskList.js +++ b/src/features/TaskList/TaskList.js @@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "react-redux"; import { createList } from "../../storeSlices/boardSlice"; import { TaskCard } from "../TaskCard/TaskCard"; -import { NewTaskCardPrompt } from "../TaskCard/NewTaskCardPrompt"; +import { CreateOrEditTaskCard } from "../TaskCard/CreateOrEditTaskCard"; import styles from "./TaskList.module.css"; @@ -16,9 +16,9 @@ export function TaskList({ listData }) {

{listData.name}

{listData.cards.map(cardData => ( - + ))} - +
); diff --git a/src/storeSlices/boardSlice.js b/src/storeSlices/boardSlice.js index d428c4a..91e445e 100644 --- a/src/storeSlices/boardSlice.js +++ b/src/storeSlices/boardSlice.js @@ -72,7 +72,7 @@ export const slice = createSlice({ const { text: cardText, cardID, listID: parentListID } = action.payload; const parentList = state.lists.find(({ id }) => id === parentListID); - const existingCard = parentList.find(({ id }) => id === cardID); + const existingCard = parentList.cards.find(({ id }) => id === cardID); existingCard.text = cardText; } diff --git a/src/utils/useAutoHeightTextArea.js b/src/utils/useAutoHeightTextArea.js new file mode 100644 index 0000000..b2720e4 --- /dev/null +++ b/src/utils/useAutoHeightTextArea.js @@ -0,0 +1,42 @@ +import { createRef, useEffect, useCallback } from "react"; + +// Souce - http://bdadam.com/blog/automatically-adapting-the-height-textarea.html + +function adjustHeight(el, minHeight) { + // compute the height difference which is caused by border and outline + var outerHeight = parseInt(window.getComputedStyle(el).height, 10); + var diff = outerHeight - el.clientHeight; + + // set the height to 0 in case of it has to be shrinked + el.style.height = 0; + + // set the correct height + // el.scrollHeight is the full height of the content, not just the visible part + el.style.height = Math.max(minHeight, el.scrollHeight + diff) + "px"; +} + +export default function useAutoHeightTextArea(minHeight = 54) { + const eleRef = createRef(null); + const onChange = useCallback(() => { + if (eleRef.current !== null) { + adjustHeight(eleRef.current, minHeight); + } + }, [minHeight, eleRef]); + + useEffect(() => { + if (eleRef.current !== null) { + const el = eleRef.current; + + el.addEventListener("input", onChange); + + // we adjust height to the initial content + adjustHeight(el, minHeight); + + return () => { + el.removeEventListener("input", onChange); + }; + } + }); + + return eleRef; +}