Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/components/task-list/task-list-table.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
background-color: #f5f5f5;
}

.taskListTableRow.taskListTableRowSelected {
background-color: #f8fafc;
}

.taskListCell {
display: table-cell;
vertical-align: middle;
Expand All @@ -22,6 +26,16 @@
padding: 0 6px;
}

.taskListCell:not(.taskListCellSelected):hover {
background-color: #eff6ff;
box-shadow: inset 0 0 0 1px #93c5fd;
}

.taskListCellSelected {
background-color: #dbeafe;
box-shadow: inset 0 0 0 2px #3b82f6;
}

.overlayEditor {
position: fixed;
z-index: 9999;
Expand Down
22 changes: 18 additions & 4 deletions src/components/task-list/task-list-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ export const TaskListTableDefault: React.FC<{
const columnIds = columns.map(column =>
typeof column === "string" ? column : column.id
);
const selectedColumnId = editingState?.columnId ?? null;
const hasValidSelection =
editingState?.mode !== "viewing" &&
!!selectedColumnId &&
columnIds.includes(selectedColumnId as VisibleField);

const resolveColumnId = (column: (typeof columns)[number]) =>
(typeof column === "string" ? column : column.id) as VisibleField;
Expand Down Expand Up @@ -244,6 +249,7 @@ export const TaskListTableDefault: React.FC<{
} else if (t.hideChildren === true) {
expanderSymbol = "▶";
}
const isSelectedRow = hasValidSelection && editingState?.rowId === t.id;

const renderCell = (field: VisibleField) => {
switch (field) {
Expand Down Expand Up @@ -304,16 +310,20 @@ export const TaskListTableDefault: React.FC<{
};
return (
<div
className={styles.taskListTableRow}
className={
isSelectedRow
? `${styles.taskListTableRow} ${styles.taskListTableRowSelected}`
: styles.taskListTableRow
}
style={{ height: rowHeight }}
key={`${t.id}row`}
>
{columns.map(column => {
const columnId = resolveColumnId(column);
const isSelected =
editingState?.mode !== "viewing" &&
hasValidSelection &&
editingState?.rowId === t.id &&
editingState?.columnId === columnId;
selectedColumnId === columnId;
const handleCellClick = (
event: React.MouseEvent<HTMLDivElement>
) => {
Expand Down Expand Up @@ -398,7 +408,11 @@ export const TaskListTableDefault: React.FC<{
return (
<div
key={`${t.id}-${columnId}`}
className={styles.taskListCell}
className={
isSelected
? `${styles.taskListCell} ${styles.taskListCellSelected}`
: styles.taskListCell
}
data-row-id={t.id}
data-column-id={columnId}
aria-selected={isSelected || undefined}
Expand Down
95 changes: 95 additions & 0 deletions src/test/task-list-table-highlight.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from "react";
import { render } from "@testing-library/react";
import "@testing-library/jest-dom";
import { TaskListTableDefault } from "../components/task-list/task-list-table";
import { TaskListEditingStateContext } from "../components/task-list/task-list";
import { Task, VisibleField } from "../types/public-types";
import styles from "../components/task-list/task-list-table.module.css";

/* eslint-disable testing-library/no-container */
/* eslint-disable testing-library/no-node-access */

const createMockTask = (id: string, name: string): Task => ({
id,
name,
// Using 2026 as test data - far enough in the future to avoid date-related issues
start: new Date(2026, 0, 1),
end: new Date(2026, 0, 10),
progress: 50,
type: "task",
});

const createEditingContext = (
mode: "viewing" | "selected" | "editing",
rowId: string | null,
columnId: VisibleField | null
) => ({
editingState: {
mode,
rowId,
columnId,
trigger: null,
pending: false,
errorMessage: null,
},
selectCell: jest.fn(),
startEditing: jest.fn(),
});

describe("TaskListTable cell highlight", () => {
const defaultProps = {
rowHeight: 40,
rowWidth: "155px",
fontFamily: "Arial",
fontSize: "14px",
tasks: [createMockTask("task-1", "Task 1"), createMockTask("task-2", "Task 2")],
selectedTaskId: "",
setSelectedTask: jest.fn(),
onExpanderClick: jest.fn(),
visibleFields: ["name", "start"] as VisibleField[],
effortDisplayUnit: "MH" as const,
onCellCommit: jest.fn().mockResolvedValue(undefined),
};

it("adds selected cell and row classes when a cell is selected", () => {
const context = createEditingContext("selected", "task-1", "name");

render(
<TaskListEditingStateContext.Provider value={context}>
<TaskListTableDefault {...defaultProps} />
</TaskListEditingStateContext.Provider>
);

const selectedCell = document.querySelector(
'[data-row-id="task-1"][data-column-id="name"]'
) as HTMLElement;
const selectedRow = selectedCell.closest(
`.${styles.taskListTableRow}`
) as HTMLElement;
const otherCell = document.querySelector(
'[data-row-id="task-1"][data-column-id="start"]'
) as HTMLElement;

expect(selectedCell).toHaveClass(styles.taskListCellSelected);
expect(selectedRow).toHaveClass(styles.taskListTableRowSelected);
expect(otherCell).not.toHaveClass(styles.taskListCellSelected);
});

it("does not highlight when the selected column is not visible", () => {
const context = createEditingContext("selected", "task-1", "end");

render(
<TaskListEditingStateContext.Provider value={context}>
<TaskListTableDefault {...defaultProps} />
</TaskListEditingStateContext.Provider>
);

const cell = document.querySelector(
'[data-row-id="task-1"][data-column-id="name"]'
) as HTMLElement;
const row = cell.closest(`.${styles.taskListTableRow}`) as HTMLElement;

expect(cell).not.toHaveClass(styles.taskListCellSelected);
expect(row).not.toHaveClass(styles.taskListTableRowSelected);
});
});