mirror of
https://github.com/bendtherules/demo-trello-app.git
synced 2026-08-18 13:42:40 +00:00
CreateOrEditTaskCard - use better combined crate/edit task card component
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className={styles.cardContainer} tabIndex={1} contentEditable="true">
|
||||
const stopEditing = () => setIsEditing(false);
|
||||
|
||||
return !isEditing ? (
|
||||
<div
|
||||
className={styles.cardContainer}
|
||||
tabIndex={1}
|
||||
onDoubleClick={e => setIsEditing(true)}
|
||||
>
|
||||
{cardData.text}
|
||||
</div>
|
||||
) : (
|
||||
<CreateOrEditTaskCard
|
||||
cardID={cardData.id}
|
||||
listID={listID}
|
||||
defaultCardText={cardData.text}
|
||||
mode="edit"
|
||||
onSubmit={stopEditing}
|
||||
onCancel={stopEditing}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 }) {
|
||||
<h3 className={styles.listName}>{listData.name}</h3>
|
||||
<div className={styles.cardsContainer}>
|
||||
{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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user