Skip to content

Commit

Permalink
Merge pull request #44 from mayank1513/feat/39-custom-color
Browse files Browse the repository at this point in the history
Feat/39 custom color
  • Loading branch information
mayank1513 authored Jul 7, 2024
2 parents 332f1f4 + 5b576c3 commit 05a27a5
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 85 deletions.
29 changes: 29 additions & 0 deletions .tkb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"scope": "Workspace",
"tasks": {
"task-MMVBTdvRS-hXujgWL4gLW": {
"id": "task-MMVBTdvRS-hXujgWL4gLW",
"description": "Create new tasks\n\nTask can also contain lists\n\n- list item 1\n\n- list item 2",
"columnId": "column-todo"
}
},
"columns": [
{
"id": "column-todo",
"title": "To do",
"tasksIds": [
"task-MMVBTdvRS-hXujgWL4gLW"
]
},
{
"id": "column-doing",
"title": "Doing",
"tasksIds": []
},
{
"id": "column-done",
"title": "Done",
"tasksIds": []
}
]
}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Trello Kanban Board

## 0.7.0

### Minor Changes

- 9ea8bcd: You can now select custom colors for your taks and columns.

## 0.6.1

### Patch Changes
Expand Down
2 changes: 2 additions & 0 deletions extension/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ export interface TaskType {
id: string;
description: string;
columnId: string;
color?: string;
}

export interface ColumnType {
id: string;
title: string;
color?: string;
description?: string;
archived?: boolean;
tasksIds: string[];
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "trello-kanban-task-board",
"private": true,
"version": "0.6.1",
"version": "0.7.0",
"type": "module",
"scripts": {
"dev": "vite --port 3000",
Expand All @@ -19,6 +19,7 @@
"nextjs-themes": "^2.1.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-color-palette": "^7.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
"react-toastify": "^9.1.3",
Expand Down
19 changes: 18 additions & 1 deletion src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe("Test Board", () => {
test("Edit task", async ({ expect }) => {
const columnEl = screen.getByTestId("column-0");
const taskEl = columnEl.getElementsByClassName(taskStyles.task)[0];
const editBtn = taskEl.getElementsByTagName("button")[0];
const editBtn = taskEl.getElementsByTagName("button")[1];
act(() => fireEvent.click(editBtn));
const inputEl = taskEl.getElementsByTagName("textarea")[0];
act(() => fireEvent.input(inputEl, { target: { value: "Task 1" } }));
Expand Down Expand Up @@ -170,4 +170,21 @@ describe("Test Board", () => {
expect(el).toBeDefined();
act(() => fireEvent.click(el));
});

test("Set Column color renders", () => {
const columnEl = screen.getByTestId("column-0");
const colorBtn = columnEl
.getElementsByClassName(columnListStyles.headerContent)[0]
.getElementsByTagName("button")[0];
act(() => fireEvent.click(colorBtn));
act(() => fireEvent.click(screen.getByText("Ok")));
});

test("Set Task color renders", () => {
const columnEl = screen.getByTestId("column-0");
const taskHEaderEl = columnEl.getElementsByClassName(taskStyles.task)[0].getElementsByTagName("header")[0];
const colorBtn = taskHEaderEl.getElementsByTagName("button")[0];
act(() => fireEvent.click(colorBtn));
act(() => fireEvent.click(screen.getByText("Ok")));
});
});
15 changes: 15 additions & 0 deletions src/components/color-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ColorPicker, useColor } from "react-color-palette";
import "react-color-palette/css";

export function ColorSelector(props: { color?: string; setColor: (color: string) => void; onClose: () => void }) {
const [color, setColor] = useColor(props.color || "gray");
return (
<div className="modal">
<div className="content">
<ColorPicker hideInput={["rgb", "hsv"]} color={color} onChange={setColor} />
<button onClick={() => props.setColor(color.hex)}>Ok</button>
<button onClick={props.onClose}>Cancel</button>
</div>
</div>
);
}
52 changes: 33 additions & 19 deletions src/components/column-list/column-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { HTMLProps, useId, useState } from "react";
import styles from "./column-list.module.scss";
import { useGlobalState } from "utils/context";
import { vscode } from "utils/vscode";
import { ColorSelector } from "components/color-selector";

export default function ColumnHeader(props: HTMLProps<HTMLElement> & { column: ColumnType }) {
const { state, setState } = useGlobalState();
const { column, ...rest } = props;
const [text, setText] = useState(column.title);
const [showColorSelector, setShowColorSelector] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const id = useId();
const onBlur = () => {
Expand All @@ -25,25 +27,37 @@ export default function ColumnHeader(props: HTMLProps<HTMLElement> & { column: C
setState({ ...state, columns: state.columns.filter((c) => c !== column), tasks });
vscode.toast(`"${column.title}" column and ${column.tasksIds.length} tasks deleted!`, "warn");
};
const setColor = (color: string) => {
column.color = color;
setState({ ...state, columns: [...state.columns] });
setShowColorSelector(false);
};
return (
<header {...rest} className={styles.header}>
<label htmlFor={id} onClick={() => setIsEditing(true)}>
<input
type="text"
id={id}
value={text}
onChange={(e) => setText(e.target.value)}
onBlur={onBlur}
placeholder="Add column title..."
hidden={!isEditing}
/>
<div hidden={isEditing} className={styles.headerContent}>
{column.title || <span className={styles.placeholder}>Add column title</span>}{" "}
<span className={styles.close} onClick={removeColumn}>
</span>
</div>
</label>
</header>
<>
<header {...rest} className={styles.header}>
<label htmlFor={id} onClick={() => setIsEditing(true)}>
<input
type="text"
id={id}
value={text}
onChange={(e) => setText(e.target.value)}
onBlur={onBlur}
placeholder="Add column title..."
hidden={!isEditing}
/>
<div hidden={isEditing} className={styles.headerContent}>
{column.title || <span className={styles.placeholder}>Add column title</span>}
<div className="grow"></div>
<button onClick={() => setShowColorSelector(true)}>🖌</button>
<button className={styles.close} onClick={removeColumn}>
</button>
</div>
</label>
</header>
{showColorSelector && (
<ColorSelector color={column.color} setColor={setColor} onClose={() => setShowColorSelector(false)} />
)}
</>
);
}
24 changes: 15 additions & 9 deletions src/components/column-list/column-list.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,28 @@
background: var(--bg);
}
.header {
&:hover .close {
button {
all: unset;
display: none;
padding: 0 5px;
cursor: pointer;
}
&:hover button {
display: inline-block;
}
button:hover {
color: orange;
}

.close:hover {
color: red;
}
}
.headerContent {
cursor: grab;
display: flex;
padding: 0 5px;
justify-content: space-between;
align-items: center;
}
.placeholder {
opacity: 0.5;
Expand All @@ -56,10 +69,3 @@
padding: 0 10px;
margin: 0 -10px;
}
.close {
display: none;
cursor: pointer;
&:hover {
color: red;
}
}
4 changes: 2 additions & 2 deletions src/components/column-list/column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function Column({ column, index }: { column: ColumnType; index: n
// listRef.current?.scrollTo({ top: listRef.current.scrollHeight, behavior: "smooth" });
const newTaskElement = listRef.current?.children[listRef.current.children.length - 1] as HTMLLabelElement;
newTaskElement?.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
newTaskElement?.getElementsByTagName("button")[0].click();
newTaskElement?.getElementsByTagName("button")[1].click();
setTimeout(() => {
newTaskElement?.getElementsByTagName("textarea")[0].focus();
newTaskElement?.getElementsByTagName("textarea")[0].click();
Expand All @@ -47,7 +47,7 @@ export default function Column({ column, index }: { column: ColumnType; index: n
<Droppable droppableId={column.id} direction="vertical">
{(provided1) => (
<div ref={provided1.innerRef} {...provided1.droppableProps} className={styles.columnDropzone}>
<div className={styles.column}>
<div className={styles.column} style={{ background: column.color }}>
<ColumnHeader column={column} {...provided.dragHandleProps} />
<hr />
<ul className={styles.taskList} ref={listRef}>
Expand Down
123 changes: 71 additions & 52 deletions src/components/task/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import remarkGfm from "remark-gfm";
import { useGlobalState } from "utils/context";
import styles from "./task.module.scss";
import { vscode } from "utils/vscode";
import { ColorSelector } from "components/color-selector";

function resizeTextArea(textareaRef: RefObject<HTMLTextAreaElement>) {
const target = textareaRef.current;
Expand All @@ -18,6 +19,7 @@ function resizeTextArea(textareaRef: RefObject<HTMLTextAreaElement>) {
export default function Task({ task, index }: { task: TaskType; index: number }) {
const { state, setState } = useGlobalState();
const [isEditing, setIsEditing] = useState(false);
const [showColorSelector, setShowColorSelector] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const id = useId();

Expand All @@ -32,58 +34,75 @@ export default function Task({ task, index }: { task: TaskType; index: number })
setState({ ...state, tasks, columns });
vscode.toast("Task deleted!", "success");
};

const setColor = (color: string) => {
task.color = color;
setState({ ...state, tasks: { ...state.tasks } });
setShowColorSelector(false);
};
return (
<Draggable draggableId={task.id} index={index}>
{(provided, snapshot) => {
if (snapshot.isDragging && provided.draggableProps.style?.transform)
provided.draggableProps.style.transform += " rotate(5deg)";
return (
<label
htmlFor={id}
ref={provided.innerRef}
{...provided.draggableProps}
className={[styles.task, isEditing ? styles.active : ""].join(" ")}>
<header {...provided.dragHandleProps}>
<span>∘∘∘</span>
<button
onClick={() => {
setIsEditing(true);
if (textareaRef.current?.value) {
textareaRef.current.value = "hk";
textareaRef.current.value = task.description;
}
setTimeout(() => resizeTextArea(textareaRef), 100);
}}>
🖉
</button>
<button className={styles.close} onClick={removeTask}>
</button>
</header>
<textarea
id={id}
value={task.description}
ref={textareaRef}
onChange={(e) => {
task.description = e.target.value.replace(/ +/, " ").replace(/\n\n+/g, "\n\n");
setState({ ...state, tasks: { ...state.tasks } });
resizeTextArea(textareaRef);
}}
onBlur={() => setIsEditing(false)}
placeholder="Enter task description in Markdown format"
hidden={!isEditing}
/>
{!isEditing &&
(task.description.trim() ? (
<ReactMarkdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{task.description.replace(/\n+/g, "\n\n")}
</ReactMarkdown>
) : (
<p className={styles.placeholder}>Enter task description in Markdown format.</p>
))}
</label>
);
}}
</Draggable>
<>
<Draggable draggableId={task.id} index={index}>
{(provided, snapshot) => {
if (snapshot.isDragging && provided.draggableProps.style?.transform)
provided.draggableProps.style.transform += " rotate(5deg)";

return (
<div ref={provided.innerRef} {...provided.draggableProps}>
<label
htmlFor={id}
className={[styles.task, isEditing ? styles.active : ""].join(" ")}
style={{ background: task.color }}>
<header {...provided.dragHandleProps}>
<span>∘∘∘</span>
<button onClick={() => setShowColorSelector(true)}>🖌</button>
<button
onClick={() => {
setIsEditing(true);
if (textareaRef.current?.value) {
textareaRef.current.value = "hk";
textareaRef.current.value = task.description;
}
setTimeout(() => {
textareaRef.current && textareaRef.current.focus();
resizeTextArea(textareaRef);
}, 100);
}}>
🖉
</button>
<button className={styles.close} onClick={removeTask}>
</button>
</header>
<textarea
id={id}
value={task.description}
ref={textareaRef}
onChange={(e) => {
task.description = e.target.value.replace(/ +/, " ").replace(/\n\n+/g, "\n\n");
setState({ ...state, tasks: { ...state.tasks } });
resizeTextArea(textareaRef);
}}
onBlur={() => setIsEditing(false)}
placeholder="Enter task description in Markdown format"
hidden={!isEditing}
/>
{!isEditing &&
(task.description.trim() ? (
<ReactMarkdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
{task.description.replace(/\n+/g, "\n\n")}
</ReactMarkdown>
) : (
<p className={styles.placeholder}>Enter task description in Markdown format.</p>
))}
</label>
</div>
);
}}
</Draggable>
{showColorSelector && (
<ColorSelector color={task.color} setColor={setColor} onClose={() => setShowColorSelector(false)} />
)}
</>
);
}
Loading

0 comments on commit 05a27a5

Please sign in to comment.