Skip to content

Commit

Permalink
finished prop drilling
Browse files Browse the repository at this point in the history
  • Loading branch information
sasanqc committed Jun 20, 2023
1 parent a5d9083 commit bb0e7c1
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 165 deletions.
143 changes: 98 additions & 45 deletions components/CreateTask.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,139 @@
import { useState, useRef } from "react";
import Button from "./UI/Button";
import CrossIcon from "@/icons/icon-cross.svg";
import Select from "./UI/Select";
import TextInput from "./UI/TextInput";
const CreateTask = () => {
import Task from "@/model/Task";
interface CreateTaskProps {
task?: Task;
columns: { label: string; value: string }[];
onCreateTask: (task: Task) => void;
}
interface ImperativeInput {
error: (message: string) => void;
}
const CreateTask: React.FC<CreateTaskProps> = ({
task,
columns,
onCreateTask,
}) => {
const [subtasks, setSubtasks] = useState(
task?.subtasks ? task.subtasks.map((el) => el.title) : ["", ""]
);
const [title, setTitle] = useState(task?.title || "");
const [description, setDescription] = useState("");
const [status, setStatus] = useState(columns[0].value);
const inputRef = useRef<ImperativeInput>(null);

const onChangedTitle = (e: React.FormEvent<HTMLInputElement>) => {
setTitle((e.target as HTMLInputElement).value);
};
const handleChangedSubtask = (
e: React.FormEvent<HTMLInputElement>,
index: number
) => {
const updatedColumns = [...subtasks];
updatedColumns[index] = (e.target as HTMLInputElement).value;
setSubtasks(updatedColumns);
};

const handleDeleteSubtask = (e: MouseEvent, index: number) => {
e.stopPropagation();
const updatedColumns = [...subtasks];
updatedColumns.splice(index, 1);
setSubtasks(updatedColumns);
};

const handleSubmitTask = () => {
if (title?.trim().length === 0) {
inputRef.current?.error("Can't be empty");
return;
}

onCreateTask({
title,
description,
status,
subtasks: subtasks
.filter((el) => el.length > 0)
.map((el) => {
return { title: el, isCompleted: false };
}),
});
};

return (
<div className="modal-content">
<h2 className="text-black4 mb-6">Add New Task</h2>
<h2 className="text-black4 mb-6 dark:text-white">Add New Task</h2>
<div className="space-y-6">
<TextInput
label={"Title"}
placeholder={"e.g. Take coffee break"}
name={"title"}
onChange={onChangedTitle}
value={title}
ref={inputRef}
/>
<div className="">
<label
htmlFor="description"
className="block text-gray3 text-sm font-bold"
className="block text-gray3 text-sm font-bold"
>
Description
</label>
<div className="mt-2">
<textarea
name="description"
className="min-h-[120px] resize-none w-full border text-base border-gray2 focus:border-primary2 py-2 px-4 rounded-md outline-none placeholder:text-black placeholder:opacity-25 focus:ring-0"
className="min-h-[120px] resize-none w-full dark:bg-black2 border text-base border-gray2 focus:border-primary2 py-2 px-4 rounded-md outline-none placeholder:text-black placeholder:opacity-25 dark:placeholder:text-white focus:ring-0"
placeholder="e.g. It’s always good to take a break. This 15 minute break will recharge the batteries a little."
required
value={description}
onChange={(e: React.FormEvent<HTMLTextAreaElement>) =>
setDescription((e.target as HTMLTextAreaElement).value)
}
/>
</div>
</div>
<div className="space-y-6">
<label
htmlFor="title"
className="block text-gray3 text-sm font-bold"
>
Subtasks
</label>
<div className="mt-2 space-y-2">
<div className="flex items-center">
<input
type="text"
name="subtask"
className=""
placeholder="e.g. Make coffee"
required
/>
<div className="ml-4">
<CrossIcon />
</div>
</div>
<div className="flex items-center">
<input
type="text"
name="subtask"
className=""
placeholder="e.g. Drink coffee & smile"
required
/>
<div className="ml-4">
<CrossIcon />
</div>
</div>
<div className="mt-2 space-y-6">
<div className="space-y-2">
<label className="block text-gray3 text-sm font-bold">
Subtasks
</label>
<ul className="space-y-2 ">
{subtasks.map((el, index) => (
<li className="flex items-center " key={index}>
<TextInput
placeholder={"e.g. TODO"}
name={"name"}
onChange={(e: React.FormEvent<HTMLInputElement>) =>
handleChangedSubtask(e, index)
}
value={subtasks[index]}
/>
<div className="ml-4 cursor-pointer">
<CrossIcon
onClick={(e: MouseEvent) => handleDeleteSubtask(e, index)}
/>
</div>
</li>
))}
</ul>
<Button
type={"small secondary"}
classes="w-full"
label="+ Add New Subtask"
onClick={() => {}}
onClick={() => setSubtasks((prev) => [...prev, ""])}
/>
</div>

<Select
label={"Status"}
items={[
{ label: "Todo", value: "todo" },
{ label: "Doing", value: "doing" },
{ label: "Done", value: "done" },
]}
onChanged={() => {}}
items={columns}
onChanged={(value) => setStatus(value)}
/>

<Button
label="Create Task"
onClick={() => {}}
onClick={handleSubmitTask}
classes={"w-full"}
type={"primary small"}
/>
Expand Down
8 changes: 6 additions & 2 deletions components/TaskColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import Task from "@/model/Task";

interface TaskColumnProps {
col: { name: string; tasks: Task[] };
onClickedTask: (index: number) => void;
}
const TaskColumn: React.FC<TaskColumnProps> = ({ col }) => {
const TaskColumn: React.FC<TaskColumnProps> = ({ col, onClickedTask }) => {
const doneSubtasksNumber = (task: Task) => {
return task.subtasks.filter((el) => el.isCompleted).length;
};
Expand All @@ -18,7 +19,10 @@ const TaskColumn: React.FC<TaskColumnProps> = ({ col }) => {
<ul className="space-y-5 pb-6 ">
{col.tasks.map((task, index) => (
<li key={index}>
<section className="py-6 px-4 bg-white dark:bg-black2 rounded-lg shadow-task">
<section
className="py-6 px-4 bg-white dark:bg-black2 rounded-lg shadow-task cursor-pointer"
onClick={(e) => onClickedTask(index)}
>
<h3 className="mb-2">{task.title}</h3>
<p className="text-sm text-gray3 font-bold ">
{`${doneSubtasksNumber(task)} of ${
Expand Down
14 changes: 9 additions & 5 deletions components/UI/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ import React, { useState } from "react";
interface CheckBoxProps {
checked: boolean;
label: string;
onChange: (value: boolean) => void;
}
const Checkbox: React.FC<CheckBoxProps> = ({ label, checked }) => {
const Checkbox: React.FC<CheckBoxProps> = ({ label, checked, onChange }) => {
const defaultChecked = checked ? checked : false;
const [isChecked, setIsChecked] = useState(defaultChecked);

return (
<label className="flex p-3 bg-white2 hover:bg-secondary2 rounded select-none cursor-pointer items-center">
<label className="flex p-3 bg-white2 dark:bg-black3 hover:bg-secondary2 hover:dark:bg-secondary2 rounded select-none cursor-pointer items-center transition-all">
<input
type="checkbox"
className="cursor-pointer appearance-none w-4 h-4 bg-white border rounded-sm border-gray1 mr-4 checked:bg-primary2 checked:bg-no-repeat checked:bg-center checked:bg-[url(/assets/images/icon-check.svg)]"
className="cursor-pointer appearance-none w-4 h-4 bg-white dark:bg-black1 border rounded-sm border-gray1 dark:border-black1 mr-4 checked:bg-primary2 checked:dark:bg-primary2 checked:bg-no-repeat checked:bg-center checked:bg-[url(/assets/images/icon-check.svg)]"
checked={isChecked}
onChange={() => setIsChecked((prev) => !prev)}
onChange={() => {
setIsChecked((prev) => !prev);
onChange(!isChecked);
}}
/>
<span
className={`text-sm font-bold ${
isChecked ? "text-[#00010c80] line-through" : ""
isChecked ? "text-black line-through dark:text-white opacity-25" : ""
}`}
>
{label}
Expand Down
52 changes: 52 additions & 0 deletions components/UI/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useRef, useState, useEffect } from "react";
import VerticalElipsisIcon from "@/icons/icon-vertical-ellipsis.svg";
interface DropdownProps {
items: { label: string; className: string; onClick: () => void }[];
}
const Dropdown: React.FC<DropdownProps> = ({ items }) => {
const menuRef = useRef<HTMLDivElement>(null);
const [menuIsOpoen, setMenuIsOpen] = useState(false);
const handleClickedOnBackdrop = (e: MouseEvent) => {
if (menuRef?.current && !menuRef?.current?.contains(e.target as Node)) {
setMenuIsOpen(false);
}
};

useEffect(() => {
setTimeout(() => {
window.addEventListener("click", handleClickedOnBackdrop);
});
return () => {
window.removeEventListener("click", handleClickedOnBackdrop);
};
}, []);
return (
<div className="relative z-30" ref={menuRef}>
<VerticalElipsisIcon
className="cursor-pointer"
onClick={() => setMenuIsOpen(true)}
/>
{menuIsOpoen && (
<ul className="absolute text-base font-semibold bg-white dark:bg-black3 right-0 mt-8 w-48 p-4 rounded-md space-y-4">
{items.map((el, index) => {
return (
<li
key={index}
className={`cursor-pointer ${el.className}`}
onClick={(event) => {
event.stopPropagation();
setMenuIsOpen(false);
el.onClick();
}}
>
{el.label}
</li>
);
})}
</ul>
)}
</div>
);
};

export default Dropdown;
16 changes: 12 additions & 4 deletions components/UI/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ interface SelectProps {
onChanged: (value: string) => void;
label?: string;
items: { label: string; value: string }[];
initialItem?: number;
}

const Select: React.FC<SelectProps> = ({ items, label, onChanged }) => {
const Select: React.FC<SelectProps> = ({
items,
label,
onChanged,
initialItem,
}) => {
const [isOpen, setIsOpen] = useState(false);
const dropDownRef = useRef<HTMLDivElement>(null);
const [selected, setSelected] = useState(items?.[0].value);
const [selected, setSelected] = useState(items?.[initialItem || 0]?.value);

const handleClickDropDown = () => {
setIsOpen(true);
Expand Down Expand Up @@ -52,12 +58,14 @@ const Select: React.FC<SelectProps> = ({ items, label, onChanged }) => {
className="border rounded-md border-gray2 cursor-pointer focus:border-primary2 px-4 py-2 flex items-center justify-between"
onClick={handleClickDropDown}
>
<p className="">{items?.find((el) => el.value === selected)?.label}</p>
<p className="text-base">
{items?.find((el) => el.value === selected)?.label}
</p>
<ChevronDownIcon />
</div>
{isOpen && (
<ul
className="bg-white dark:bg-black3 p-4 text-gray3 absolute mt-2 rounded-lg w-full space-y-2 z-20"
className="bg-white text-base dark:bg-black3 p-4 text-gray3 absolute mt-2 rounded-lg w-full space-y-2 z-20"
onClick={handleSelectedItem}
>
{items.map((el) => (
Expand Down
14 changes: 9 additions & 5 deletions components/UI/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import React, {

interface TextInputProps {
label?: string;
name: string;
name?: string;
placeholder?: string;
id?: string;
value: string;
onChange: (e: React.FormEvent<HTMLInputElement>) => void;
}
Expand All @@ -18,7 +19,7 @@ interface ImperativeInput {
}

const TextInput = forwardRef<ImperativeInput, TextInputProps>(
({ label, name, placeholder, value, onChange }, ref) => {
({ label, id, name, placeholder, value, onChange }, ref) => {
const [error, setError] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
Expand All @@ -31,7 +32,7 @@ const TextInput = forwardRef<ImperativeInput, TextInputProps>(
<div className="w-full">
{label && (
<label
htmlFor={name}
htmlFor={id}
className="block mb-2 text-gray3 text-sm font-bold"
>
{label}
Expand All @@ -41,10 +42,13 @@ const TextInput = forwardRef<ImperativeInput, TextInputProps>(
<input
type="text"
name={name}
id={id}
value={value}
ref={inputRef}
className={`w-full border text-base focus:border-primary2 py-2 px-4 rounded-md outline-none dark:bg-black2 dark:placeholder:text-gray3 placeholder:opacity-25 focus:ring-0 ${
error ? " border-destructive2" : "border-black1 "
className={`w-full border text-base focus:border-primary2 py-2 px-4 rounded-md outline-none dark:bg-black2 dark:placeholder:text-white placeholder:text-black placeholder:opacity-25 focus:ring-0 ${
error
? " border-destructive2"
: "dark:border-black1 border-black border-opacity-25"
}`}
placeholder={placeholder}
onChange={onChange}
Expand Down
Loading

0 comments on commit bb0e7c1

Please sign in to comment.