From 53229325bdb364c9064cf2758e8642b2635d7259 Mon Sep 17 00:00:00 2001 From: Yanzhen Chen Date: Wed, 29 Jul 2026 20:22:09 -0400 Subject: [PATCH 1/5] added row number --- app/assets/stylesheets/common/_table.scss | 6 ++++ .../__tests__/instructor_table.test.jsx | 5 ++- .../__tests__/student_table.test.jsx | 5 ++- .../Components/__tests__/table.test.jsx | 35 ++++++++++++++++++- app/javascript/Components/table/table.jsx | 31 +++++++++++++--- .../Components/table/table_cell.jsx | 4 +-- app/javascript/Components/table/table_row.jsx | 6 ++-- config/locales/common/en.yml | 1 + 8 files changed, 82 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/common/_table.scss b/app/assets/stylesheets/common/_table.scss index d5624d48c8..07d36c37f2 100644 --- a/app/assets/stylesheets/common/_table.scss +++ b/app/assets/stylesheets/common/_table.scss @@ -242,6 +242,12 @@ text-align: center; } + .rt-row-number { + font-size: 0.85em; + font-variant-numeric: tabular-nums; + text-align: right; + } + .rt-expander { display: inline-block; position: relative; diff --git a/app/javascript/Components/__tests__/instructor_table.test.jsx b/app/javascript/Components/__tests__/instructor_table.test.jsx index e72f7cb177..a2a2f754bd 100644 --- a/app/javascript/Components/__tests__/instructor_table.test.jsx +++ b/app/javascript/Components/__tests__/instructor_table.test.jsx @@ -20,7 +20,10 @@ describe("For the InstructorTable's display of instructors", () => { const instructors_in_one_row = instructor => { const rows = screen.getAllByRole("row"); for (let row of rows) { - const cells = Array.from(row.childNodes).map(c => c.textContent); + const cells = Array.from( + row.querySelectorAll(".rt-td:not(.rt-row-number)"), + c => c.textContent + ); if (cells[0] === instructor.user_name) { expect(cells[1]).toEqual(instructor.first_name); expect(cells[2]).toEqual(instructor.last_name); diff --git a/app/javascript/Components/__tests__/student_table.test.jsx b/app/javascript/Components/__tests__/student_table.test.jsx index cf6948c65b..47f8d1e69a 100644 --- a/app/javascript/Components/__tests__/student_table.test.jsx +++ b/app/javascript/Components/__tests__/student_table.test.jsx @@ -119,7 +119,10 @@ describe("For the StudentTable's display of students", () => { const student_in_one_row = student => { const rows = screen.getAllByRole("row"); for (let row of rows) { - const cells = Array.from(row.childNodes).map(c => c.textContent); + const cells = Array.from( + row.querySelectorAll(".rt-td:not(.rt-row-number)"), + c => c.textContent + ); if (cells[1] === student.user_name) { expect(cells[2]).toEqual(student.first_name); expect(cells[3]).toEqual(student.last_name); diff --git a/app/javascript/Components/__tests__/table.test.jsx b/app/javascript/Components/__tests__/table.test.jsx index 1bdb74f656..0e1ba7cc2c 100644 --- a/app/javascript/Components/__tests__/table.test.jsx +++ b/app/javascript/Components/__tests__/table.test.jsx @@ -130,6 +130,10 @@ function searchInputs() { return screen.getAllByPlaceholderText(defaultSearchPlaceholderText()); } +function rowNumbers(table) { + return Array.from(table.querySelectorAll(".rt-tbody .rt-row-number"), cell => cell.textContent); +} + async function clickHeader(headerText) { await user.click(screen.getByText(headerText)); } @@ -177,6 +181,21 @@ describe("tests for the table component", () => { expectRowsInTableInOrder(table, columns, data); }); + + it("shows row numbers as the first column", () => { + const {table, data} = renderTableWithMockData(); + const header = table.querySelector(".rt-thead.-header"); + const tableRows = table.querySelectorAll(".rt-tbody .rt-tr"); + + expect(header.querySelector(".rt-th:first-child")).toHaveClass("rt-row-number"); + expect( + within(header).getByRole("columnheader", {name: I18n.t("table.row_number")}) + ).toBeInTheDocument(); + expect(rowNumbers(table)).toEqual(data.map((_, index) => String(index + 1))); + tableRows.forEach(tableRow => + expect(tableRow.querySelector(".rt-td:first-child")).toHaveClass("rt-row-number") + ); + }); }); describe("rendering of the no-data component when data is empty", () => { @@ -226,6 +245,19 @@ describe("tests for the table component", () => { expectRowsInTableInOrder(table, columns, data); } }); + + it("updates row numbers to match the sorted order", async () => { + const {table, columns, data} = renderTableWithMockData(); + + await clickHeader(columns[0].header); + await clickHeader(columns[0].header); + + const tableRows = table.querySelectorAll(".rt-tbody .rt-tr"); + tableRows.forEach((tableRow, index) => { + expect(tableRow.querySelector(".rt-row-number")).toHaveTextContent(String(index + 1)); + }); + expect(within(tableRows[0]).getByText(data[data.length - 1].col1)).toBeInTheDocument(); + }); }); describe("filtering", () => { @@ -246,7 +278,7 @@ describe("tests for the table component", () => { }); it("filters data", async () => { - const {columns} = renderTableWithMockData(); + const {table, columns} = renderTableWithMockData(); columns.pop(); const inputs = searchInputs(); @@ -266,6 +298,7 @@ describe("tests for the table component", () => { ], columns ); + expect(rowNumbers(table)).toEqual(["1", "2"]); }); it("resets the filter when the search query is erased", async () => { diff --git a/app/javascript/Components/table/table.jsx b/app/javascript/Components/table/table.jsx index b98000254e..6115ffa93f 100644 --- a/app/javascript/Components/table/table.jsx +++ b/app/javascript/Components/table/table.jsx @@ -21,8 +21,29 @@ export const defaultNoDataText = () => I18n.t("table.no_data"); const columnHelper = createColumnHelper(); export const SELECTION_COLUMN_ID = "select"; +export const ROW_NUMBER_COLUMN_ID = "row_number"; const FILTER_VARIANT_SELECT = "select"; +export const rowNumberColumn = columnHelper.display({ + id: ROW_NUMBER_COLUMN_ID, + header: () => ( + + # + + ), + size: 42, + minSize: 42, + maxSize: 42, + enableResizing: false, + enableSorting: false, + enableHiding: false, + meta: { + className: "rt-row-number", + headerClassName: "rt-row-number", + }, + cell: () => null, +}); + export const expanderColumn = columnHelper.display({ id: "expander", header: () => null, @@ -135,7 +156,7 @@ export default function Table({ if (renderSubComponent || renderSubRows) { cols = [expanderColumn, ...cols]; } - return cols; + return [rowNumberColumn, ...cols]; }, [columns, enableRowSelection, renderSubComponent, renderSubRows]); const table = useReactTable({ @@ -173,6 +194,7 @@ export default function Table({ }); const centerTotalSize = table.getCenterTotalSize(); + const rows = table.getRowModel().rows; const tableHeaders = (
@@ -227,9 +249,10 @@ export default function Table({ {tableHeaders} {tableFilters}
- {table.getRowModel().rows.map(row => ( + {rows.map((row, index) => ( ))} - {loading && table.getRowModel().rows.length > 0 && ( + {loading && rows.length > 0 && (
)} - {!table.getRowModel().rows.length && + {!rows.length && (loading ? (
- {flexRender(cell.column.columnDef.cell, cell.getContext())} + {rowNumber ?? flexRender(cell.column.columnDef.cell, cell.getContext())}
); } diff --git a/app/javascript/Components/table/table_row.jsx b/app/javascript/Components/table/table_row.jsx index 2e9731bd96..616c7ae020 100644 --- a/app/javascript/Components/table/table_row.jsx +++ b/app/javascript/Components/table/table_row.jsx @@ -1,8 +1,8 @@ import React from "react"; import TableCell from "./table_cell"; -import {SELECTION_COLUMN_ID} from "./table"; +import {ROW_NUMBER_COLUMN_ID, SELECTION_COLUMN_ID} from "./table"; -function TableRow({row, isSelected, isGrouped, isExpanded, renderSubComponent}) { +function TableRow({row, rowNumber, isSelected, isGrouped, isExpanded, renderSubComponent}) { return (
@@ -17,6 +17,7 @@ function TableRow({row, isSelected, isGrouped, isExpanded, renderSubComponent}) isSelected={cellSelection} isExpanded={isExpanded} key={cell.id} + rowNumber={cell.column.columnDef.id === ROW_NUMBER_COLUMN_ID ? rowNumber : null} width={cell.column.getSize()} /> ); @@ -33,6 +34,7 @@ export default React.memo( // react-table creates new row objects when filter values change. // We compare row.original rather than row to determine whether to re-render. prev.row.original === next.row.original && + prev.rowNumber === next.rowNumber && prev.isSelected === next.isSelected && prev.isExpanded === next.isExpanded && prev.columnSizing === next.columnSizing && diff --git a/config/locales/common/en.yml b/config/locales/common/en.yml index b6944f41cb..b1d956740a 100644 --- a/config/locales/common/en.yml +++ b/config/locales/common/en.yml @@ -92,6 +92,7 @@ en: case_sensitive_search: Match case hide_details: Hide details no_data: No rows found + row_number: Row number search: Search show_details: Show details to: to From 600607ccf86c6a8809e2e808424c9a8d74bed482 Mon Sep 17 00:00:00 2001 From: Yanzhen Chen Date: Wed, 29 Jul 2026 20:27:33 -0400 Subject: [PATCH 2/5] changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index f842ff7d43..66401f94a6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ ### 🚨 Breaking changes ### ✨ New features and improvements +- Added row numbers to tables using React Table v8, allowing users to identify row positions after sorting and filtering (#8089) - Allowed instructors assigned as graders to switch between all submissions and only their assigned submissions in the submissions, summary, and grading views (#8083) - Improved Session Timeout Logic: `check_timeout` polling paused when user is not focused on the MarkUs tab and polling stops after user session has timed out (#8074) - Migrated Groups Manager students and groups tables to use `react-table` v8 (#8068) From b7788ef0a2ead5e1134c8b0ad9d75af6b1ae0568 Mon Sep 17 00:00:00 2001 From: Yanzhen Chen Date: Wed, 29 Jul 2026 20:51:14 -0400 Subject: [PATCH 3/5] adjusted padding --- app/assets/stylesheets/common/_table.scss | 2 +- app/javascript/Components/table/table.jsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/common/_table.scss b/app/assets/stylesheets/common/_table.scss index 07d36c37f2..25d364cfdb 100644 --- a/app/assets/stylesheets/common/_table.scss +++ b/app/assets/stylesheets/common/_table.scss @@ -245,7 +245,7 @@ .rt-row-number { font-size: 0.85em; font-variant-numeric: tabular-nums; - text-align: right; + text-align: center; } .rt-expander { diff --git a/app/javascript/Components/table/table.jsx b/app/javascript/Components/table/table.jsx index 6115ffa93f..1fda88fec6 100644 --- a/app/javascript/Components/table/table.jsx +++ b/app/javascript/Components/table/table.jsx @@ -31,9 +31,9 @@ export const rowNumberColumn = columnHelper.display({ # ), - size: 42, - minSize: 42, - maxSize: 42, + size: 40, + minSize: 40, + maxSize: 40, enableResizing: false, enableSorting: false, enableHiding: false, From 4a27b887b3e83e977d7ee6fa855c5c55908477f0 Mon Sep 17 00:00:00 2001 From: Yanzhen Chen Date: Wed, 29 Jul 2026 21:58:56 -0400 Subject: [PATCH 4/5] oops --- app/assets/stylesheets/common/_table.scss | 27 +++++++++--- .../__tests__/instructor_table.test.jsx | 5 +-- .../__tests__/student_table.test.jsx | 5 +-- .../Components/__tests__/table.test.jsx | 33 +++++++-------- app/javascript/Components/table/table.jsx | 42 +++++++------------ .../Components/table/table_cell.jsx | 4 +- app/javascript/Components/table/table_row.jsx | 6 +-- config/locales/common/en.yml | 1 - 8 files changed, 55 insertions(+), 68 deletions(-) diff --git a/app/assets/stylesheets/common/_table.scss b/app/assets/stylesheets/common/_table.scss index 25d364cfdb..956f527d4b 100644 --- a/app/assets/stylesheets/common/_table.scss +++ b/app/assets/stylesheets/common/_table.scss @@ -166,6 +166,7 @@ flex: 99999 1 auto; display: flex; flex-direction: column; + counter-reset: table-row-number; overflow: auto; border-bottom: 1px solid $gridline; border-left: 1px solid $gridline; @@ -190,6 +191,24 @@ } } + .rt-tr { + counter-increment: table-row-number; + + &::before { + align-items: center; + bottom: 0; + content: counter(table-row-number); + display: flex; + font-size: 0.85em; + font-variant-numeric: tabular-nums; + justify-content: center; + left: 0; + position: absolute; + top: 0; + width: var(--row-number-gutter-width); + } + } + .rt-expandable { cursor: pointer; text-overflow: clip; @@ -210,6 +229,8 @@ .rt-tr { flex: 1 0 auto; display: inline-flex; + padding-left: var(--row-number-gutter-width); + position: relative; } .rt-th, @@ -242,12 +263,6 @@ text-align: center; } - .rt-row-number { - font-size: 0.85em; - font-variant-numeric: tabular-nums; - text-align: center; - } - .rt-expander { display: inline-block; position: relative; diff --git a/app/javascript/Components/__tests__/instructor_table.test.jsx b/app/javascript/Components/__tests__/instructor_table.test.jsx index a2a2f754bd..602d924eee 100644 --- a/app/javascript/Components/__tests__/instructor_table.test.jsx +++ b/app/javascript/Components/__tests__/instructor_table.test.jsx @@ -20,10 +20,7 @@ describe("For the InstructorTable's display of instructors", () => { const instructors_in_one_row = instructor => { const rows = screen.getAllByRole("row"); for (let row of rows) { - const cells = Array.from( - row.querySelectorAll(".rt-td:not(.rt-row-number)"), - c => c.textContent - ); + const cells = Array.from(row.querySelectorAll(".rt-td"), c => c.textContent); if (cells[0] === instructor.user_name) { expect(cells[1]).toEqual(instructor.first_name); expect(cells[2]).toEqual(instructor.last_name); diff --git a/app/javascript/Components/__tests__/student_table.test.jsx b/app/javascript/Components/__tests__/student_table.test.jsx index 47f8d1e69a..24e35e1dd0 100644 --- a/app/javascript/Components/__tests__/student_table.test.jsx +++ b/app/javascript/Components/__tests__/student_table.test.jsx @@ -119,10 +119,7 @@ describe("For the StudentTable's display of students", () => { const student_in_one_row = student => { const rows = screen.getAllByRole("row"); for (let row of rows) { - const cells = Array.from( - row.querySelectorAll(".rt-td:not(.rt-row-number)"), - c => c.textContent - ); + const cells = Array.from(row.querySelectorAll(".rt-td"), c => c.textContent); if (cells[1] === student.user_name) { expect(cells[2]).toEqual(student.first_name); expect(cells[3]).toEqual(student.last_name); diff --git a/app/javascript/Components/__tests__/table.test.jsx b/app/javascript/Components/__tests__/table.test.jsx index 0e1ba7cc2c..2805a52f93 100644 --- a/app/javascript/Components/__tests__/table.test.jsx +++ b/app/javascript/Components/__tests__/table.test.jsx @@ -130,10 +130,6 @@ function searchInputs() { return screen.getAllByPlaceholderText(defaultSearchPlaceholderText()); } -function rowNumbers(table) { - return Array.from(table.querySelectorAll(".rt-tbody .rt-row-number"), cell => cell.textContent); -} - async function clickHeader(headerText) { await user.click(screen.getByText(headerText)); } @@ -182,19 +178,18 @@ describe("tests for the table component", () => { expectRowsInTableInOrder(table, columns, data); }); - it("shows row numbers as the first column", () => { - const {table, data} = renderTableWithMockData(); + it("shows positional row numbers without adding a table column", () => { + const {table} = renderTableWithMockData(); + const tableElement = table.querySelector(".Table"); const header = table.querySelector(".rt-thead.-header"); const tableRows = table.querySelectorAll(".rt-tbody .rt-tr"); - expect(header.querySelector(".rt-th:first-child")).toHaveClass("rt-row-number"); - expect( - within(header).getByRole("columnheader", {name: I18n.t("table.row_number")}) - ).toBeInTheDocument(); - expect(rowNumbers(table)).toEqual(data.map((_, index) => String(index + 1))); - tableRows.forEach(tableRow => - expect(tableRow.querySelector(".rt-td:first-child")).toHaveClass("rt-row-number") - ); + expect(tableElement.style.getPropertyValue("--row-number-gutter-width")).toBe("40px"); + expect(header.querySelectorAll(".rt-th")).toHaveLength(mockColumns().length); + tableRows.forEach(tableRow => { + expect(tableRow.querySelectorAll(".rt-td")).toHaveLength(mockColumns().length); + expect(tableRow.querySelector(".rt-row-number")).not.toBeInTheDocument(); + }); }); }); @@ -246,17 +241,17 @@ describe("tests for the table component", () => { } }); - it("updates row numbers to match the sorted order", async () => { + it("sorts row contents without adding a row-number column", async () => { const {table, columns, data} = renderTableWithMockData(); await clickHeader(columns[0].header); await clickHeader(columns[0].header); const tableRows = table.querySelectorAll(".rt-tbody .rt-tr"); - tableRows.forEach((tableRow, index) => { - expect(tableRow.querySelector(".rt-row-number")).toHaveTextContent(String(index + 1)); - }); expect(within(tableRows[0]).getByText(data[data.length - 1].col1)).toBeInTheDocument(); + tableRows.forEach(tableRow => + expect(tableRow.querySelectorAll(".rt-td")).toHaveLength(columns.length) + ); }); }); @@ -298,7 +293,7 @@ describe("tests for the table component", () => { ], columns ); - expect(rowNumbers(table)).toEqual(["1", "2"]); + expect(table.querySelectorAll(".rt-tbody .rt-tr")).toHaveLength(2); }); it("resets the filter when the search query is erased", async () => { diff --git a/app/javascript/Components/table/table.jsx b/app/javascript/Components/table/table.jsx index 1fda88fec6..37d0d0e986 100644 --- a/app/javascript/Components/table/table.jsx +++ b/app/javascript/Components/table/table.jsx @@ -21,28 +21,8 @@ export const defaultNoDataText = () => I18n.t("table.no_data"); const columnHelper = createColumnHelper(); export const SELECTION_COLUMN_ID = "select"; -export const ROW_NUMBER_COLUMN_ID = "row_number"; const FILTER_VARIANT_SELECT = "select"; - -export const rowNumberColumn = columnHelper.display({ - id: ROW_NUMBER_COLUMN_ID, - header: () => ( - - # - - ), - size: 40, - minSize: 40, - maxSize: 40, - enableResizing: false, - enableSorting: false, - enableHiding: false, - meta: { - className: "rt-row-number", - headerClassName: "rt-row-number", - }, - cell: () => null, -}); +const ROW_NUMBER_GUTTER_WIDTH = 40; export const expanderColumn = columnHelper.display({ id: "expander", @@ -156,7 +136,7 @@ export default function Table({ if (renderSubComponent || renderSubRows) { cols = [expanderColumn, ...cols]; } - return [rowNumberColumn, ...cols]; + return cols; }, [columns, enableRowSelection, renderSubComponent, renderSubRows]); const table = useReactTable({ @@ -194,10 +174,11 @@ export default function Table({ }); const centerTotalSize = table.getCenterTotalSize(); + const tableTotalSize = centerTotalSize + ROW_NUMBER_GUTTER_WIDTH; const rows = table.getRowModel().rows; const tableHeaders = ( -
+
{table.getHeaderGroups().map(headerGroup => (
{headerGroup.headers.map(header => ( @@ -222,7 +203,7 @@ export default function Table({ [table, finalColumns] ); const tableFilters = showFilters && ( -
+
{table.getHeaderGroups().map(headerGroup => (
{headerGroup.headers.map(header => ( @@ -244,15 +225,20 @@ export default function Table({ ); return ( -
+
{tableHeaders} {tableFilters} -
- {rows.map((row, index) => ( +
+ {rows.map(row => ( - {rowNumber ?? flexRender(cell.column.columnDef.cell, cell.getContext())} + {flexRender(cell.column.columnDef.cell, cell.getContext())}
); } diff --git a/app/javascript/Components/table/table_row.jsx b/app/javascript/Components/table/table_row.jsx index 616c7ae020..2e9731bd96 100644 --- a/app/javascript/Components/table/table_row.jsx +++ b/app/javascript/Components/table/table_row.jsx @@ -1,8 +1,8 @@ import React from "react"; import TableCell from "./table_cell"; -import {ROW_NUMBER_COLUMN_ID, SELECTION_COLUMN_ID} from "./table"; +import {SELECTION_COLUMN_ID} from "./table"; -function TableRow({row, rowNumber, isSelected, isGrouped, isExpanded, renderSubComponent}) { +function TableRow({row, isSelected, isGrouped, isExpanded, renderSubComponent}) { return (
@@ -17,7 +17,6 @@ function TableRow({row, rowNumber, isSelected, isGrouped, isExpanded, renderSubC isSelected={cellSelection} isExpanded={isExpanded} key={cell.id} - rowNumber={cell.column.columnDef.id === ROW_NUMBER_COLUMN_ID ? rowNumber : null} width={cell.column.getSize()} /> ); @@ -34,7 +33,6 @@ export default React.memo( // react-table creates new row objects when filter values change. // We compare row.original rather than row to determine whether to re-render. prev.row.original === next.row.original && - prev.rowNumber === next.rowNumber && prev.isSelected === next.isSelected && prev.isExpanded === next.isExpanded && prev.columnSizing === next.columnSizing && diff --git a/config/locales/common/en.yml b/config/locales/common/en.yml index b1d956740a..b6944f41cb 100644 --- a/config/locales/common/en.yml +++ b/config/locales/common/en.yml @@ -92,7 +92,6 @@ en: case_sensitive_search: Match case hide_details: Hide details no_data: No rows found - row_number: Row number search: Search show_details: Show details to: to From 14f2695fbe5a4a022eca34482b9ab044dd7c13d1 Mon Sep 17 00:00:00 2001 From: Yanzhen Chen Date: Sun, 2 Aug 2026 15:32:48 -0400 Subject: [PATCH 5/5] david changes --- app/assets/stylesheets/common/_constants.scss | 1 + app/assets/stylesheets/common/_table.scss | 50 +++++++++++-------- .../__tests__/assignment_summary.test.jsx | 4 ++ .../Components/__tests__/table.test.jsx | 16 ++++-- .../Components/assignment_summary_table.jsx | 1 + app/javascript/Components/table/table.jsx | 19 ++++--- 6 files changed, 58 insertions(+), 33 deletions(-) diff --git a/app/assets/stylesheets/common/_constants.scss b/app/assets/stylesheets/common/_constants.scss index 2692d16d68..a07d491576 100644 --- a/app/assets/stylesheets/common/_constants.scss +++ b/app/assets/stylesheets/common/_constants.scss @@ -18,6 +18,7 @@ --primary_two: #cee3ea; --primary_three: #89b1dd; --radius: 5px; + --row-number-gutter-width: 30px; --severe_alert: #ffd452; --severe_error: #a20000; --severe_success: #246700; diff --git a/app/assets/stylesheets/common/_table.scss b/app/assets/stylesheets/common/_table.scss index 956f527d4b..6f7fee4a2b 100644 --- a/app/assets/stylesheets/common/_table.scss +++ b/app/assets/stylesheets/common/_table.scss @@ -72,6 +72,7 @@ } .rt-tr { + background-color: $background-support; text-align: center; } @@ -166,7 +167,6 @@ flex: 99999 1 auto; display: flex; flex-direction: column; - counter-reset: table-row-number; overflow: auto; border-bottom: 1px solid $gridline; border-left: 1px solid $gridline; @@ -191,24 +191,6 @@ } } - .rt-tr { - counter-increment: table-row-number; - - &::before { - align-items: center; - bottom: 0; - content: counter(table-row-number); - display: flex; - font-size: 0.85em; - font-variant-numeric: tabular-nums; - justify-content: center; - left: 0; - position: absolute; - top: 0; - width: var(--row-number-gutter-width); - } - } - .rt-expandable { cursor: pointer; text-overflow: clip; @@ -229,10 +211,38 @@ .rt-tr { flex: 1 0 auto; display: inline-flex; - padding-left: var(--row-number-gutter-width); position: relative; } + &.-show-row-numbers { + .rt-tbody { + counter-reset: table-row-number; + + .rt-tr { + counter-increment: table-row-number; + + &::before { + align-items: center; + background-color: $background-support; + bottom: 0; + content: counter(table-row-number); + display: flex; + font-size: 0.85em; + font-variant-numeric: tabular-nums; + justify-content: center; + left: 0; + position: absolute; + top: 0; + width: var(--row-number-gutter-width); + } + } + } + + .rt-tr { + padding-left: var(--row-number-gutter-width); + } + } + .rt-th, .rt-td { flex: 100 0 auto; diff --git a/app/javascript/Components/__tests__/assignment_summary.test.jsx b/app/javascript/Components/__tests__/assignment_summary.test.jsx index b8bc157850..b2c1be2c52 100644 --- a/app/javascript/Components/__tests__/assignment_summary.test.jsx +++ b/app/javascript/Components/__tests__/assignment_summary.test.jsx @@ -82,6 +82,10 @@ describe("For the AssignmentSummaryTable's display of inactive groups", () => { ); }); + it("shows row numbers", () => { + expect(document.querySelector(".Table")).toHaveClass("-show-row-numbers"); + }); + it("initially does not contain the inactive group", () => { expect(screen.queryByText(/group_0001/)).not.toBeInTheDocument(); }); diff --git a/app/javascript/Components/__tests__/table.test.jsx b/app/javascript/Components/__tests__/table.test.jsx index 2805a52f93..c39368bf4b 100644 --- a/app/javascript/Components/__tests__/table.test.jsx +++ b/app/javascript/Components/__tests__/table.test.jsx @@ -178,13 +178,23 @@ describe("tests for the table component", () => { expectRowsInTableInOrder(table, columns, data); }); - it("shows positional row numbers without adding a table column", () => { + it("does not show row numbers by default", () => { const {table} = renderTableWithMockData(); const tableElement = table.querySelector(".Table"); + + expect(tableElement).not.toHaveClass("-show-row-numbers"); + }); + + it("shows positional row numbers without adding a table column when enabled", () => { + const {table} = renderTableWithMockData({showRowNumbers: true}); + const tableElement = table.querySelector(".Table"); const header = table.querySelector(".rt-thead.-header"); const tableRows = table.querySelectorAll(".rt-tbody .rt-tr"); - expect(tableElement.style.getPropertyValue("--row-number-gutter-width")).toBe("40px"); + expect(tableElement).toHaveClass("-show-row-numbers"); + expect(table.querySelector(".rt-tbody").style.minWidth).toContain( + "var(--row-number-gutter-width)" + ); expect(header.querySelectorAll(".rt-th")).toHaveLength(mockColumns().length); tableRows.forEach(tableRow => { expect(tableRow.querySelectorAll(".rt-td")).toHaveLength(mockColumns().length); @@ -242,7 +252,7 @@ describe("tests for the table component", () => { }); it("sorts row contents without adding a row-number column", async () => { - const {table, columns, data} = renderTableWithMockData(); + const {table, columns, data} = renderTableWithMockData({showRowNumbers: true}); await clickHeader(columns[0].header); await clickHeader(columns[0].header); diff --git a/app/javascript/Components/assignment_summary_table.jsx b/app/javascript/Components/assignment_summary_table.jsx index 8e814f44f0..e1be02c341 100644 --- a/app/javascript/Components/assignment_summary_table.jsx +++ b/app/javascript/Components/assignment_summary_table.jsx @@ -541,6 +541,7 @@ export class AssignmentSummaryTable extends React.Component { }); }} loading={this.state.loading} + showRowNumbers /> I18n.t("table.no_data"); const columnHelper = createColumnHelper(); export const SELECTION_COLUMN_ID = "select"; const FILTER_VARIANT_SELECT = "select"; -const ROW_NUMBER_GUTTER_WIDTH = 40; export const expanderColumn = columnHelper.display({ id: "expander", @@ -94,6 +93,7 @@ export default function Table({ columnFilters: externalColumnFilters, onColumnFiltersChange: externalOnColumnFiltersChange, onRowSelectionChange, + showRowNumbers = false, }) { const [internalColumnFilters, setInternalColumnFilters] = React.useState([]); const [columnSizing, setColumnSizing] = React.useState({}); @@ -174,11 +174,13 @@ export default function Table({ }); const centerTotalSize = table.getCenterTotalSize(); - const tableTotalSize = centerTotalSize + ROW_NUMBER_GUTTER_WIDTH; + const tableMinWidth = showRowNumbers + ? `calc(${centerTotalSize}px + var(--row-number-gutter-width))` + : centerTotalSize; const rows = table.getRowModel().rows; const tableHeaders = ( -
+
{table.getHeaderGroups().map(headerGroup => (
{headerGroup.headers.map(header => ( @@ -203,7 +205,7 @@ export default function Table({ [table, finalColumns] ); const tableFilters = showFilters && ( -
+
{table.getHeaderGroups().map(headerGroup => (
{headerGroup.headers.map(header => ( @@ -226,16 +228,13 @@ export default function Table({ return (
{tableHeaders} {tableFilters} -
+
{rows.map(row => (