CreateOrEditTaskCard - use better combined crate/edit task card component

This commit is contained in:
2020-03-30 20:43:14 +05:30
parent 54ad03e848
commit a69342b71e
7 changed files with 203 additions and 39 deletions
@@ -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 (
<div>
{!isFormOpen ? (
<button
className={styles.createInitBtn}
type="button"
onClick={e => setIsFormOpen(v => !v)}
>
{isCreateMode ? "+ Create Task" : "Edit Task"}
</button>
) : (
<form className={styles.createCardForm} onSubmit={onSubmit}>
<textarea
ref={textAreaRef}
className={`${styles.cardContainer} ${styles.createCardContainer}`}
placeholder="Create task"
autoFocus={true}
value={newCardText}
onChange={e => setNewCardText(e.target.value)}
></textarea>
<div className={styles.createActionBtnContainer}>
<input type="submit" value="Save" disabled={!isTextNonEmpty} />
<input type="button" value="Cancel" onClick={onCancel} />
</div>
</form>
)}
</div>
);
}
@@ -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 (
<div className={styles.listContainer}>
<input
type="text"
placeholder="Create task"
value={newCardText}
onChange={e => setNewCardText(e.target.value)}
onKeyPress={e => {
if (newCardText.trim().length > 0 && e.key === "Enter") {
dispatch(createTask({ text: newCardText, listID }));
setNewCardText("");
}
}}
></input>
</div>
);
}
+21 -6
View File
@@ -1,15 +1,30 @@
import React, { useState } from "react"; 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"; import styles from "./TaskCard.module.css";
export function TaskCard({ cardData }) { export function TaskCard({ cardData, listID }) {
const dispatch = useDispatch(); const [isEditing, setIsEditing] = useState(false);
return ( const stopEditing = () => setIsEditing(false);
<div className={styles.cardContainer} tabIndex={1} contentEditable="true">
return !isEditing ? (
<div
className={styles.cardContainer}
tabIndex={1}
onDoubleClick={e => setIsEditing(true)}
>
{cardData.text} {cardData.text}
</div> </div>
) : (
<CreateOrEditTaskCard
cardID={cardData.id}
listID={listID}
defaultCardText={cardData.text}
mode="edit"
onSubmit={stopEditing}
onCancel={stopEditing}
/>
); );
} }
+38
View File
@@ -5,3 +5,41 @@
padding: 8px; padding: 8px;
margin: 8px 0; 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;
}
+3 -3
View File
@@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "react-redux";
import { createList } from "../../storeSlices/boardSlice"; import { createList } from "../../storeSlices/boardSlice";
import { TaskCard } from "../TaskCard/TaskCard"; import { TaskCard } from "../TaskCard/TaskCard";
import { NewTaskCardPrompt } from "../TaskCard/NewTaskCardPrompt"; import { CreateOrEditTaskCard } from "../TaskCard/CreateOrEditTaskCard";
import styles from "./TaskList.module.css"; import styles from "./TaskList.module.css";
@@ -16,9 +16,9 @@ export function TaskList({ listData }) {
<h3 className={styles.listName}>{listData.name}</h3> <h3 className={styles.listName}>{listData.name}</h3>
<div className={styles.cardsContainer}> <div className={styles.cardsContainer}>
{listData.cards.map(cardData => ( {listData.cards.map(cardData => (
<TaskCard key={cardData.id} cardData={cardData} /> <TaskCard key={cardData.id} listID={listData.id} cardData={cardData} />
))} ))}
<NewTaskCardPrompt listID={listData.id} /> <CreateOrEditTaskCard listID={listData.id} />
</div> </div>
</div> </div>
); );
+1 -1
View File
@@ -72,7 +72,7 @@ export const slice = createSlice({
const { text: cardText, cardID, listID: parentListID } = action.payload; const { text: cardText, cardID, listID: parentListID } = action.payload;
const parentList = state.lists.find(({ id }) => id === parentListID); 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; existingCard.text = cardText;
} }
+42
View File
@@ -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;
}