diff --git a/.tkb b/.tkb
new file mode 100644
index 0000000..9130988
--- /dev/null
+++ b/.tkb
@@ -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": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf3e06b..dac9a51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/extension/interface.ts b/extension/interface.ts
index a8efdfa..548a2e5 100644
--- a/extension/interface.ts
+++ b/extension/interface.ts
@@ -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[];
diff --git a/package.json b/package.json
index ff801d9..855a904 100644
--- a/package.json
+++ b/package.json
@@ -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",
@@ -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",
diff --git a/src/App.test.tsx b/src/App.test.tsx
index dfdb4af..0e9661e 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -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" } }));
@@ -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")));
+ });
});
diff --git a/src/components/color-selector.tsx b/src/components/color-selector.tsx
new file mode 100644
index 0000000..4f0e080
--- /dev/null
+++ b/src/components/color-selector.tsx
@@ -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 (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/column-list/column-header.tsx b/src/components/column-list/column-header.tsx
index 082fa8f..9b365e4 100644
--- a/src/components/column-list/column-header.tsx
+++ b/src/components/column-list/column-header.tsx
@@ -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 & { 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 = () => {
@@ -25,25 +27,37 @@ export default function ColumnHeader(props: HTMLProps & { 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 (
-
+ <>
+
+
+
+ {showColorSelector && (
+ setShowColorSelector(false)} />
+ )}
+ >
);
}
diff --git a/src/components/column-list/column-list.module.scss b/src/components/column-list/column-list.module.scss
index 933dfa7..e86a375 100644
--- a/src/components/column-list/column-list.module.scss
+++ b/src/components/column-list/column-list.module.scss
@@ -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;
@@ -56,10 +69,3 @@
padding: 0 10px;
margin: 0 -10px;
}
-.close {
- display: none;
- cursor: pointer;
- &:hover {
- color: red;
- }
-}
diff --git a/src/components/column-list/column.tsx b/src/components/column-list/column.tsx
index d1292d6..c9857d3 100644
--- a/src/components/column-list/column.tsx
+++ b/src/components/column-list/column.tsx
@@ -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();
@@ -47,7 +47,7 @@ export default function Column({ column, index }: { column: ColumnType; index: n
{(provided1) => (
-
+
diff --git a/src/components/task/index.tsx b/src/components/task/index.tsx
index d0d03dd..415e396 100644
--- a/src/components/task/index.tsx
+++ b/src/components/task/index.tsx
@@ -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) {
const target = textareaRef.current;
@@ -18,6 +19,7 @@ function resizeTextArea(textareaRef: RefObject) {
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(null);
const id = useId();
@@ -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 (
-
- {(provided, snapshot) => {
- if (snapshot.isDragging && provided.draggableProps.style?.transform)
- provided.draggableProps.style.transform += " rotate(5deg)";
- return (
-
- );
- }}
-
+ <>
+
+ {(provided, snapshot) => {
+ if (snapshot.isDragging && provided.draggableProps.style?.transform)
+ provided.draggableProps.style.transform += " rotate(5deg)";
+
+ return (
+
+
+
+ );
+ }}
+
+ {showColorSelector && (
+ setShowColorSelector(false)} />
+ )}
+ >
);
}
diff --git a/src/components/task/task.module.scss b/src/components/task/task.module.scss
index 70c590f..53a2345 100644
--- a/src/components/task/task.module.scss
+++ b/src/components/task/task.module.scss
@@ -18,6 +18,7 @@
position: relative;
button {
all: unset;
+ display: none;
position: absolute;
right: 24px;
top: 0;
@@ -28,6 +29,10 @@
}
}
+ button:first-of-type {
+ right: 48px;
+ }
+
.close {
right: 2px;
&:hover {
@@ -35,6 +40,10 @@
}
}
}
+
+ &:hover button {
+ display: inline-block;
+ }
textarea {
width: calc(100% - 10px);
resize: none;
diff --git a/src/index.scss b/src/index.scss
index 4cd3090..fd6bf1f 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -1,4 +1,5 @@
@import "nextjs-themes/styles";
+
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
@@ -63,3 +64,39 @@ a {
--bg: var(--vscode-editor-background);
--fg: var(--vscode-editor-foreground);
}
+
+.grow {
+ flex-grow: 1;
+}
+
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+ backdrop-filter: blur(3px);
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ justify-content: center;
+ z-index: 100;
+ overflow: auto;
+ .content {
+ box-shadow: 0 0 10px currentColor;
+ border-radius: 10px;
+ text-align: right;
+ background: #121212;
+ }
+ .rcp-root {
+ position: relative;
+ width: 400px;
+ label {
+ display: none;
+ }
+ }
+ button {
+ cursor: pointer;
+ margin: 0 10px 10px 0;
+ }
+}
diff --git a/vite.config.ts b/vite.config.ts
index 55f5012..b58271a 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -22,7 +22,8 @@ export default defineConfig({
provider: "v8",
reporter: ["text", "json", "html", "clover"],
include: ["src/**/*.{ts,tsx}"],
- exclude: ["src/**/main.tsx", "src/**/vscode.ts", "src/**/*.d.ts"],
+ // canvas tests not supported, external library
+ exclude: ["src/**/main.tsx", "src/**/color-selector.tsx", "src/**/vscode.ts", "src/**/*.d.ts"],
},
},
});
diff --git a/vitest.setup.ts b/vitest.setup.ts
index a540809..dc5f9b8 100644
--- a/vitest.setup.ts
+++ b/vitest.setup.ts
@@ -1,4 +1,5 @@
import "@testing-library/jest-dom";
+import { useState } from "react";
import { vi } from "vitest";
export const scrollIntoViewMock = vi.fn();
@@ -19,3 +20,5 @@ Object.defineProperty(window, "matchMedia", {
});
vi.mock("react-webgl-trails", () => ({ MouseTrail: () => null }));
+
+vi.mock("react-color-palette", () => ({ ColorPicker: () => null, useColor: useState }));