@databiosphere/findable-ui 45.0.0 → 45.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +9 -0
- package/lib/components/Detail/components/Table/common/utils.js +1 -1
- package/lib/components/Detail/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.js +1 -1
- package/lib/components/Detail/components/Table/components/TableRows/tableRows.js +1 -1
- package/lib/components/Detail/components/Table/table.js +7 -1
- package/lib/components/Index/table/hook.js +7 -1
- package/lib/components/Table/components/TableCell/components/RowSelectionCell/constants.d.ts +2 -0
- package/lib/components/Table/components/TableCell/components/RowSelectionCell/constants.js +13 -0
- package/lib/components/Table/components/TableCell/components/RowSelectionCell/rowSelectionCell.js +12 -8
- package/lib/components/Table/components/TableRow/tableRow.styles.d.ts +2 -0
- package/lib/components/Table/components/TableRow/tableRow.styles.js +15 -1
- package/lib/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.js +1 -1
- package/lib/components/Table/components/TableRows/tableRows.js +1 -1
- package/lib/components/Table/features/RowSelectionValidation/constants.d.ts +2 -0
- package/lib/components/Table/features/RowSelectionValidation/constants.js +14 -0
- package/lib/components/Table/features/RowSelectionValidation/types.d.ts +8 -0
- package/lib/components/Table/features/RowSelectionValidation/types.js +1 -0
- package/lib/components/Table/features/RowSelectionValidation/utils.d.ts +8 -0
- package/lib/components/Table/features/RowSelectionValidation/utils.js +11 -0
- package/lib/components/Table/features/entities.d.ts +3 -2
- package/lib/components/TableCreator/options/hook.js +0 -3
- package/lib/components/common/CustomIcon/components/UncheckedDisabledIcon/uncheckedDisabledIcon.d.ts +2 -0
- package/lib/components/common/CustomIcon/components/UncheckedDisabledIcon/uncheckedDisabledIcon.js +8 -0
- package/package.json +1 -1
- package/src/components/Detail/components/Table/common/utils.ts +1 -1
- package/src/components/Detail/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.tsx +1 -0
- package/src/components/Detail/components/Table/components/TableRows/tableRows.tsx +2 -0
- package/src/components/Detail/components/Table/table.tsx +7 -1
- package/src/components/Index/table/hook.ts +7 -1
- package/src/components/Table/components/TableCell/components/RowSelectionCell/constants.ts +15 -0
- package/src/components/Table/components/TableCell/components/RowSelectionCell/rowSelectionCell.tsx +25 -13
- package/src/components/Table/components/TableRow/tableRow.styles.ts +19 -1
- package/src/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.tsx +1 -0
- package/src/components/Table/components/TableRows/tableRows.tsx +2 -0
- package/src/components/Table/features/RowSelectionValidation/constants.ts +24 -0
- package/src/components/Table/features/RowSelectionValidation/types.ts +10 -0
- package/src/components/Table/features/RowSelectionValidation/utils.ts +15 -0
- package/src/components/Table/features/entities.ts +10 -2
- package/src/components/TableCreator/options/hook.ts +0 -3
- package/src/components/common/CustomIcon/components/UncheckedDisabledIcon/uncheckedDisabledIcon.tsx +27 -0
- package/tests/rowSelectionValidation.test.ts +282 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [45.1.0](https://github.com/DataBiosphere/findable-ui/compare/v45.0.0...v45.1.0) (2025-09-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* extend row selection feature to handle validation messages for each row ([#666](https://github.com/DataBiosphere/findable-ui/issues/666)) ([#669](https://github.com/DataBiosphere/findable-ui/issues/669)) ([04fc041](https://github.com/DataBiosphere/findable-ui/commit/04fc0411cae9f1ae9c47d8f384e599413cffe639))
|
|
9
|
+
* update row selection component to display disabled state ([#663](https://github.com/DataBiosphere/findable-ui/issues/663)) ([#667](https://github.com/DataBiosphere/findable-ui/issues/667)) ([c2c9fb2](https://github.com/DataBiosphere/findable-ui/commit/c2c9fb24e458858dd6d26be2be8e74f4e3e976da))
|
|
10
|
+
* update table row to display disabled state ([#665](https://github.com/DataBiosphere/findable-ui/issues/665)) ([#668](https://github.com/DataBiosphere/findable-ui/issues/668)) ([2c6000f](https://github.com/DataBiosphere/findable-ui/commit/2c6000f86d0066b91409032f9970284f6b4b6443))
|
|
11
|
+
|
|
3
12
|
## [45.0.0](https://github.com/DataBiosphere/findable-ui/compare/v44.0.0...v45.0.0) (2025-09-10)
|
|
4
13
|
|
|
5
14
|
|
|
@@ -10,7 +10,7 @@ export const CollapsableRows = ({ rows: leafOrSubRows, tableInstance, }) => {
|
|
|
10
10
|
return (React.createElement(Fragment, null, (leafOrSubRows || rows).map((row) => {
|
|
11
11
|
if (row.depth > 0 && !leafOrSubRows)
|
|
12
12
|
return null; // Hide sub rows that are not already leaf or sub rows.
|
|
13
|
-
return (React.createElement(StyledTableRow, { key: row.id, id: row.id, isPreview: row.getIsPreview() },
|
|
13
|
+
return (React.createElement(StyledTableRow, { key: row.id, id: row.id, isPreview: row.getIsPreview(), isSelected: row.getIsSelected() },
|
|
14
14
|
React.createElement(CollapsableCell, { isDisabled: isCollapsableRowDisabled(tableInstance), row: row })));
|
|
15
15
|
})));
|
|
16
16
|
};
|
|
@@ -10,7 +10,7 @@ export const TableRows = ({ rows: leafOrSubRows, tableInstance, tableView, }) =>
|
|
|
10
10
|
const { tableCell } = tableView || {};
|
|
11
11
|
const { size: tableCellSize = "medium" } = tableCell || {};
|
|
12
12
|
return (React.createElement(Fragment, null, (leafOrSubRows || rows).map((row) => {
|
|
13
|
-
return (React.createElement(StyledTableRow, { key: row.id, id: row.id, canExpand: row.getCanExpand(), isExpanded: row.getIsExpanded(), isGrouped: row.getIsGrouped(), isPreview: row.getIsPreview(), onClick: () => handleToggleExpanded(row) }, row.getVisibleCells().map((cell) => {
|
|
13
|
+
return (React.createElement(StyledTableRow, { key: row.id, id: row.id, canExpand: row.getCanExpand(), canSelect: row.getCanSelect(), isExpanded: row.getIsExpanded(), isGrouped: row.getIsGrouped(), isPreview: row.getIsPreview(), isSelected: row.getIsSelected(), onClick: () => handleToggleExpanded(row) }, row.getVisibleCells().map((cell) => {
|
|
14
14
|
if (cell.getIsAggregated())
|
|
15
15
|
return null; // Display of aggregated cells is currently not supported.
|
|
16
16
|
if (cell.getIsPlaceholder())
|
|
@@ -9,6 +9,7 @@ import { TableHead } from "../../../Table/components/TableHead/tableHead";
|
|
|
9
9
|
import { TABLE_DOWNLOAD_OPTIONS } from "../../../Table/featureOptions/tableDownload/constants";
|
|
10
10
|
import { ROW_POSITION } from "../../../Table/features/RowPosition/constants";
|
|
11
11
|
import { ROW_PREVIEW } from "../../../Table/features/RowPreview/constants";
|
|
12
|
+
import { ROW_SELECTION_VALIDATION } from "../../../Table/features/RowSelectionValidation/constants";
|
|
12
13
|
import { TABLE_DOWNLOAD } from "../../../Table/features/TableDownload/constants";
|
|
13
14
|
import { GridTable } from "../../../Table/table.styles";
|
|
14
15
|
import { generateColumnDefinitions } from "./common/utils";
|
|
@@ -20,7 +21,12 @@ export const Table = ({ className, collapsable = true, columns, gridTemplateColu
|
|
|
20
21
|
const { stickyHeader = false } = table || {};
|
|
21
22
|
const { sx: tableContainerSx } = tableContainer || {};
|
|
22
23
|
const tableInstance = useReactTable({
|
|
23
|
-
_features: [
|
|
24
|
+
_features: [
|
|
25
|
+
ROW_POSITION,
|
|
26
|
+
ROW_PREVIEW,
|
|
27
|
+
ROW_SELECTION_VALIDATION,
|
|
28
|
+
TABLE_DOWNLOAD,
|
|
29
|
+
],
|
|
24
30
|
columns: generateColumnDefinitions([
|
|
25
31
|
COLUMN_DEF.ROW_POSITION,
|
|
26
32
|
...columns,
|
|
@@ -14,6 +14,7 @@ import { getFacetedMinMaxValues } from "../../Table/featureOptions/facetedColumn
|
|
|
14
14
|
import { TABLE_DOWNLOAD_OPTIONS } from "../../Table/featureOptions/tableDownload/constants";
|
|
15
15
|
import { ROW_POSITION } from "../../Table/features/RowPosition/constants";
|
|
16
16
|
import { ROW_PREVIEW } from "../../Table/features/RowPreview/constants";
|
|
17
|
+
import { ROW_SELECTION_VALIDATION } from "../../Table/features/RowSelectionValidation/constants";
|
|
17
18
|
import { TABLE_DOWNLOAD } from "../../Table/features/TableDownload/constants";
|
|
18
19
|
import { buildBaseColumnDef } from "../../TableCreator/common/utils";
|
|
19
20
|
import { useTableOptions } from "../../TableCreator/options/hook";
|
|
@@ -99,7 +100,12 @@ export const useTable = ({ entityListType, }) => {
|
|
|
99
100
|
* - This will simplify the configuration structure and centralize table state definitions, reducing redundancy and improving clarity.
|
|
100
101
|
*/
|
|
101
102
|
const table = useReactTable({
|
|
102
|
-
_features: [
|
|
103
|
+
_features: [
|
|
104
|
+
ROW_POSITION,
|
|
105
|
+
ROW_PREVIEW,
|
|
106
|
+
ROW_SELECTION_VALIDATION,
|
|
107
|
+
TABLE_DOWNLOAD,
|
|
108
|
+
],
|
|
103
109
|
columns: columnDefs,
|
|
104
110
|
data: listItems || [],
|
|
105
111
|
enableColumnFilters: true, // client-side filtering.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const TOOLTIP_PROPS = {
|
|
2
|
+
arrow: true,
|
|
3
|
+
disableInteractive: true,
|
|
4
|
+
slotProps: {
|
|
5
|
+
popper: {
|
|
6
|
+
modifiers: [
|
|
7
|
+
{ name: "offset", options: { offset: [0, -4] } },
|
|
8
|
+
{ name: "preventOverflow", options: { padding: 8 } },
|
|
9
|
+
],
|
|
10
|
+
},
|
|
11
|
+
tooltip: { sx: { maxWidth: "220px" } },
|
|
12
|
+
},
|
|
13
|
+
};
|
package/lib/components/Table/components/TableCell/components/RowSelectionCell/rowSelectionCell.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { Checkbox as MCheckbox } from "@mui/material";
|
|
1
|
+
import { Checkbox as MCheckbox, Tooltip } from "@mui/material";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { CheckedIcon } from "../../../../../common/CustomIcon/components/CheckedIcon/checkedIcon";
|
|
4
|
+
import { UncheckedDisabledIcon } from "../../../../../common/CustomIcon/components/UncheckedDisabledIcon/uncheckedDisabledIcon";
|
|
4
5
|
import { UncheckedIcon } from "../../../../../common/CustomIcon/components/UncheckedIcon/uncheckedIcon";
|
|
6
|
+
import { TOOLTIP_PROPS } from "./constants";
|
|
5
7
|
export const RowSelectionCell = ({ row, }) => {
|
|
6
|
-
const { getIsSelected, getToggleSelectedHandler } = row;
|
|
7
|
-
return (React.createElement(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const { getCanSelect, getIsSelected, getSelectionValidation, getToggleSelectedHandler, } = row;
|
|
9
|
+
return (React.createElement(Tooltip, { ...TOOLTIP_PROPS, title: getSelectionValidation?.() },
|
|
10
|
+
React.createElement("span", null,
|
|
11
|
+
React.createElement(MCheckbox, { checked: getIsSelected(), checkedIcon: React.createElement(CheckedIcon, null), disabled: !getCanSelect(), icon: getCanSelect() ? React.createElement(UncheckedIcon, null) : React.createElement(UncheckedDisabledIcon, null),
|
|
12
|
+
/*
|
|
13
|
+
* Prevents click events from bubbling up to parent components
|
|
14
|
+
* (such as CardActionArea or Accordion) when the checkbox is activated.
|
|
15
|
+
*/
|
|
16
|
+
onClick: (e) => e.stopPropagation(), onChange: getToggleSelectedHandler() }))));
|
|
13
17
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export interface StyledTableRowProps {
|
|
2
2
|
canExpand?: boolean;
|
|
3
|
+
canSelect?: boolean;
|
|
3
4
|
isExpanded?: boolean;
|
|
4
5
|
isGrouped?: boolean;
|
|
5
6
|
isPreview?: boolean;
|
|
7
|
+
isSelected?: boolean;
|
|
6
8
|
}
|
|
7
9
|
export declare const StyledTableRow: import("@emotion/styled").StyledComponent<import("@mui/material").TableRowOwnProps & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLTableRowElement>, HTMLTableRowElement>, "ref"> & {
|
|
8
10
|
ref?: ((instance: HTMLTableRowElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLTableRowElement> | null | undefined;
|
|
@@ -5,9 +5,11 @@ import { FONT } from "../../../../styles/common/constants/font";
|
|
|
5
5
|
import { PALETTE } from "../../../../styles/common/constants/palette";
|
|
6
6
|
export const StyledTableRow = styled(MTableRow, {
|
|
7
7
|
shouldForwardProp: (prop) => prop !== "canExpand" &&
|
|
8
|
+
prop !== "canSelect" &&
|
|
8
9
|
prop !== "isExpanded" &&
|
|
10
|
+
prop !== "isGrouped" &&
|
|
9
11
|
prop !== "isPreview" &&
|
|
10
|
-
prop !== "
|
|
12
|
+
prop !== "isSelected",
|
|
11
13
|
}) `
|
|
12
14
|
&& {
|
|
13
15
|
transition: background-color 300ms ease-in;
|
|
@@ -42,5 +44,17 @@ export const StyledTableRow = styled(MTableRow, {
|
|
|
42
44
|
css `
|
|
43
45
|
background-color: ${PALETTE.PRIMARY_LIGHTEST};
|
|
44
46
|
`}
|
|
47
|
+
|
|
48
|
+
${({ isSelected }) => isSelected &&
|
|
49
|
+
css `
|
|
50
|
+
background-color: ${PALETTE.PRIMARY_LIGHTEST};
|
|
51
|
+
`}
|
|
52
|
+
|
|
53
|
+
${({ canSelect }) => !canSelect &&
|
|
54
|
+
css `
|
|
55
|
+
.MuiTableCell-root {
|
|
56
|
+
color: ${PALETTE.INK_LIGHT};
|
|
57
|
+
}
|
|
58
|
+
`}
|
|
45
59
|
}
|
|
46
60
|
`;
|
package/lib/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.js
CHANGED
|
@@ -13,7 +13,7 @@ export const CollapsableRows = ({ rows, tableInstance, virtualizer, }) => {
|
|
|
13
13
|
const row = rows[rowIndex];
|
|
14
14
|
if (grouping.length > 0 && row.depth > 0)
|
|
15
15
|
return null; // TODO(cc) hide sub rows -- sub-rows are within collapsed content -- UI TBD.
|
|
16
|
-
return (React.createElement(StyledTableRow, { key: row.id, "data-index": rowIndex, ref: virtualizer.measureElement, isPreview: row.getIsPreview() },
|
|
16
|
+
return (React.createElement(StyledTableRow, { key: row.id, "data-index": rowIndex, ref: virtualizer.measureElement, isPreview: row.getIsPreview(), isSelected: row.getIsSelected() },
|
|
17
17
|
React.createElement(CollapsableCell, { isDisabled: isCollapsableRowDisabled(tableInstance), row: row })));
|
|
18
18
|
})));
|
|
19
19
|
};
|
|
@@ -11,7 +11,7 @@ export const TableRows = ({ rows, virtualizer, }) => {
|
|
|
11
11
|
const rowIndex = virtualRow.index;
|
|
12
12
|
const row = rows[rowIndex];
|
|
13
13
|
const { getCanExpand, getIsExpanded, getIsGrouped, getIsPreview } = row;
|
|
14
|
-
return (React.createElement(StyledTableRow, { key: row.id, canExpand: getCanExpand(), "data-index": rowIndex, isExpanded: getIsExpanded(), isGrouped: getIsGrouped(), isPreview: getIsPreview(), onClick: () => handleToggleExpanded(row), ref: virtualizer.measureElement }, row.getVisibleCells().map((cell, i) => {
|
|
14
|
+
return (React.createElement(StyledTableRow, { key: row.id, canExpand: getCanExpand(), canSelect: row.getCanSelect(), "data-index": rowIndex, isExpanded: getIsExpanded(), isGrouped: getIsGrouped(), isPreview: getIsPreview(), isSelected: row.getIsSelected(), onClick: () => handleToggleExpanded(row), ref: virtualizer.measureElement }, row.getVisibleCells().map((cell, i) => {
|
|
15
15
|
if (cell.getIsAggregated())
|
|
16
16
|
return null; // Display of aggregated cells is currently not supported.
|
|
17
17
|
if (cell.getIsPlaceholder())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getSelectionValidation } from "./utils";
|
|
2
|
+
export const ROW_SELECTION_VALIDATION = {
|
|
3
|
+
createRow: (row, table) => {
|
|
4
|
+
row.getSelectionValidation = () => {
|
|
5
|
+
return getSelectionValidation(row, table);
|
|
6
|
+
};
|
|
7
|
+
},
|
|
8
|
+
getDefaultOptions: () => {
|
|
9
|
+
return {
|
|
10
|
+
enableRowSelectionValidation: false,
|
|
11
|
+
getRowSelectionValidation: undefined,
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Row, RowData } from "@tanstack/react-table";
|
|
2
|
+
export interface RowSelectionValidationOptions<T extends RowData> {
|
|
3
|
+
enableRowSelectionValidation?: boolean;
|
|
4
|
+
getRowSelectionValidation?: (row: Row<T>) => string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface RowSelectionValidationRow {
|
|
7
|
+
getSelectionValidation?: () => string | undefined;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Row, RowData, Table } from "@tanstack/react-table";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the validation message for a row.
|
|
4
|
+
* @param row - Row.
|
|
5
|
+
* @param table - Table.
|
|
6
|
+
* @returns The validation message for the row.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getSelectionValidation<T extends RowData>(row: Row<T>, table: Table<T>): string | undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the validation message for a row.
|
|
3
|
+
* @param row - Row.
|
|
4
|
+
* @param table - Table.
|
|
5
|
+
* @returns The validation message for the row.
|
|
6
|
+
*/
|
|
7
|
+
export function getSelectionValidation(row, table) {
|
|
8
|
+
if (!table.options.enableRowSelectionValidation)
|
|
9
|
+
return;
|
|
10
|
+
return table.options.getRowSelectionValidation?.(row);
|
|
11
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { RowData } from "@tanstack/react-table";
|
|
2
2
|
import { RowPositionOptions, RowPositionRow } from "./RowPosition/types";
|
|
3
3
|
import { RowPreviewInstance, RowPreviewOptions, RowPreviewRow, RowPreviewTableState } from "./RowPreview/entities";
|
|
4
|
+
import { RowSelectionValidationOptions, RowSelectionValidationRow } from "./RowSelectionValidation/types";
|
|
4
5
|
import { TableDownloadColumn, TableDownloadInstance, TableDownloadOptions } from "./TableDownload/types";
|
|
5
6
|
export type CustomFeatureColumn = TableDownloadColumn;
|
|
6
7
|
export interface CustomFeatureInstance<T extends RowData> extends TableDownloadInstance, RowPreviewInstance<T> {
|
|
7
8
|
}
|
|
8
9
|
export type CustomFeatureInitialTableState = Partial<RowPreviewTableState>;
|
|
9
|
-
export interface CustomFeatureOptions<T extends RowData> extends TableDownloadOptions<T>, RowPositionOptions, RowPreviewOptions {
|
|
10
|
+
export interface CustomFeatureOptions<T extends RowData> extends TableDownloadOptions<T>, RowPositionOptions, RowPreviewOptions, RowSelectionValidationOptions<T> {
|
|
10
11
|
}
|
|
11
|
-
export interface CustomFeatureRow extends RowPositionRow, RowPreviewRow {
|
|
12
|
+
export interface CustomFeatureRow extends RowPositionRow, RowPreviewRow, RowSelectionValidationRow {
|
|
12
13
|
}
|
|
13
14
|
export type CustomFeatureTableState = RowPreviewTableState;
|
|
@@ -2,7 +2,6 @@ import { useConfig } from "../../../hooks/useConfig";
|
|
|
2
2
|
import { useExpandedOptions } from "./expanded/hook";
|
|
3
3
|
import { useGroupingOptions } from "./grouping/hook";
|
|
4
4
|
import { useInitialState } from "./initialState/hook";
|
|
5
|
-
import { useRowSelectionOptions } from "./rowSelection/hook";
|
|
6
5
|
import { useSortingOptions } from "./sorting/hook";
|
|
7
6
|
import { useVisibilityOptions } from "./visibility/hook";
|
|
8
7
|
export function useTableOptions() {
|
|
@@ -10,14 +9,12 @@ export function useTableOptions() {
|
|
|
10
9
|
const tableOptions = entityConfig.list.tableOptions;
|
|
11
10
|
const expandedOptions = useExpandedOptions();
|
|
12
11
|
const groupingOptions = useGroupingOptions();
|
|
13
|
-
const rowSelectionOptions = useRowSelectionOptions();
|
|
14
12
|
const sortingOptions = useSortingOptions();
|
|
15
13
|
const visibilityOptions = useVisibilityOptions();
|
|
16
14
|
const initialState = useInitialState(tableOptions);
|
|
17
15
|
return {
|
|
18
16
|
...expandedOptions,
|
|
19
17
|
...groupingOptions,
|
|
20
|
-
...rowSelectionOptions,
|
|
21
18
|
...sortingOptions, // TODO(cc) merge of all sorting options.
|
|
22
19
|
...visibilityOptions,
|
|
23
20
|
...tableOptions,
|
package/lib/components/common/CustomIcon/components/UncheckedDisabledIcon/uncheckedDisabledIcon.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SvgIcon } from "@mui/material";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { PALETTE } from "../../../../../styles/common/constants/palette";
|
|
4
|
+
export const UncheckedDisabledIcon = ({ fontSize = "xsmall", viewBox = "0 0 18 18", ...props }) => {
|
|
5
|
+
return (React.createElement(SvgIcon, { fontSize: fontSize, viewBox: viewBox, ...props },
|
|
6
|
+
React.createElement("path", { d: "M4 0.5H14C15.933 0.5 17.5 2.067 17.5 4V14C17.5 15.933 15.933 17.5 14 17.5H4C2.067 17.5 0.5 15.933 0.5 14V4C0.5 2.067 2.067 0.5 4 0.5Z", stroke: PALETTE.SMOKE_MAIN }),
|
|
7
|
+
React.createElement("rect", { x: "0.5", y: "0.5", width: "17", height: "17", rx: "3.5", fill: PALETTE.SMOKE_LIGHT, stroke: PALETTE.SMOKE_MAIN })));
|
|
8
|
+
};
|
package/package.json
CHANGED
|
@@ -37,9 +37,11 @@ export const TableRows = <T extends RowData>({
|
|
|
37
37
|
key={row.id}
|
|
38
38
|
id={row.id}
|
|
39
39
|
canExpand={row.getCanExpand()}
|
|
40
|
+
canSelect={row.getCanSelect()}
|
|
40
41
|
isExpanded={row.getIsExpanded()}
|
|
41
42
|
isGrouped={row.getIsGrouped()}
|
|
42
43
|
isPreview={row.getIsPreview()}
|
|
44
|
+
isSelected={row.getIsSelected()}
|
|
43
45
|
onClick={() => handleToggleExpanded(row)}
|
|
44
46
|
>
|
|
45
47
|
{row.getVisibleCells().map((cell) => {
|
|
@@ -23,6 +23,7 @@ import { TableHead } from "../../../Table/components/TableHead/tableHead";
|
|
|
23
23
|
import { TABLE_DOWNLOAD_OPTIONS } from "../../../Table/featureOptions/tableDownload/constants";
|
|
24
24
|
import { ROW_POSITION } from "../../../Table/features/RowPosition/constants";
|
|
25
25
|
import { ROW_PREVIEW } from "../../../Table/features/RowPreview/constants";
|
|
26
|
+
import { ROW_SELECTION_VALIDATION } from "../../../Table/features/RowSelectionValidation/constants";
|
|
26
27
|
import { TABLE_DOWNLOAD } from "../../../Table/features/TableDownload/constants";
|
|
27
28
|
import { GridTable } from "../../../Table/table.styles";
|
|
28
29
|
import { generateColumnDefinitions } from "./common/utils";
|
|
@@ -60,7 +61,12 @@ export const Table = <T extends RowData>({
|
|
|
60
61
|
const { stickyHeader = false } = table || {};
|
|
61
62
|
const { sx: tableContainerSx } = tableContainer || {};
|
|
62
63
|
const tableInstance = useReactTable({
|
|
63
|
-
_features: [
|
|
64
|
+
_features: [
|
|
65
|
+
ROW_POSITION,
|
|
66
|
+
ROW_PREVIEW,
|
|
67
|
+
ROW_SELECTION_VALIDATION,
|
|
68
|
+
TABLE_DOWNLOAD,
|
|
69
|
+
],
|
|
64
70
|
columns: generateColumnDefinitions([
|
|
65
71
|
COLUMN_DEF.ROW_POSITION as ColumnDef<T>,
|
|
66
72
|
...columns,
|
|
@@ -38,6 +38,7 @@ import { TABLE_DOWNLOAD_OPTIONS } from "../../Table/featureOptions/tableDownload
|
|
|
38
38
|
import { ROW_POSITION } from "../../Table/features/RowPosition/constants";
|
|
39
39
|
import { ROW_PREVIEW } from "../../Table/features/RowPreview/constants";
|
|
40
40
|
import { RowPreviewState } from "../../Table/features/RowPreview/entities";
|
|
41
|
+
import { ROW_SELECTION_VALIDATION } from "../../Table/features/RowSelectionValidation/constants";
|
|
41
42
|
import { TABLE_DOWNLOAD } from "../../Table/features/TableDownload/constants";
|
|
42
43
|
import { buildBaseColumnDef } from "../../TableCreator/common/utils";
|
|
43
44
|
import { useTableOptions } from "../../TableCreator/options/hook";
|
|
@@ -164,7 +165,12 @@ UseTableProps): UseTable<T> => {
|
|
|
164
165
|
* - This will simplify the configuration structure and centralize table state definitions, reducing redundancy and improving clarity.
|
|
165
166
|
*/
|
|
166
167
|
const table = useReactTable<T>({
|
|
167
|
-
_features: [
|
|
168
|
+
_features: [
|
|
169
|
+
ROW_POSITION,
|
|
170
|
+
ROW_PREVIEW,
|
|
171
|
+
ROW_SELECTION_VALIDATION,
|
|
172
|
+
TABLE_DOWNLOAD,
|
|
173
|
+
],
|
|
168
174
|
columns: columnDefs,
|
|
169
175
|
data: listItems || [],
|
|
170
176
|
enableColumnFilters: true, // client-side filtering.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TooltipProps } from "@mui/material";
|
|
2
|
+
|
|
3
|
+
export const TOOLTIP_PROPS: Partial<TooltipProps> = {
|
|
4
|
+
arrow: true,
|
|
5
|
+
disableInteractive: true,
|
|
6
|
+
slotProps: {
|
|
7
|
+
popper: {
|
|
8
|
+
modifiers: [
|
|
9
|
+
{ name: "offset", options: { offset: [0, -4] } },
|
|
10
|
+
{ name: "preventOverflow", options: { padding: 8 } },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
tooltip: { sx: { maxWidth: "220px" } },
|
|
14
|
+
},
|
|
15
|
+
};
|
package/src/components/Table/components/TableCell/components/RowSelectionCell/rowSelectionCell.tsx
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
|
-
import { Checkbox as MCheckbox } from "@mui/material";
|
|
1
|
+
import { Checkbox as MCheckbox, Tooltip } from "@mui/material";
|
|
2
2
|
import { CellContext, RowData } from "@tanstack/react-table";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { CheckedIcon } from "../../../../../common/CustomIcon/components/CheckedIcon/checkedIcon";
|
|
5
|
+
import { UncheckedDisabledIcon } from "../../../../../common/CustomIcon/components/UncheckedDisabledIcon/uncheckedDisabledIcon";
|
|
5
6
|
import { UncheckedIcon } from "../../../../../common/CustomIcon/components/UncheckedIcon/uncheckedIcon";
|
|
7
|
+
import { TOOLTIP_PROPS } from "./constants";
|
|
6
8
|
|
|
7
9
|
export const RowSelectionCell = <T extends RowData, TValue = unknown>({
|
|
8
10
|
row,
|
|
9
11
|
}: CellContext<T, TValue>): JSX.Element => {
|
|
10
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
getCanSelect,
|
|
14
|
+
getIsSelected,
|
|
15
|
+
getSelectionValidation,
|
|
16
|
+
getToggleSelectedHandler,
|
|
17
|
+
} = row;
|
|
11
18
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
<Tooltip {...TOOLTIP_PROPS} title={getSelectionValidation?.()}>
|
|
20
|
+
<span>
|
|
21
|
+
<MCheckbox
|
|
22
|
+
checked={getIsSelected()}
|
|
23
|
+
checkedIcon={<CheckedIcon />}
|
|
24
|
+
disabled={!getCanSelect()}
|
|
25
|
+
icon={getCanSelect() ? <UncheckedIcon /> : <UncheckedDisabledIcon />}
|
|
26
|
+
/*
|
|
27
|
+
* Prevents click events from bubbling up to parent components
|
|
28
|
+
* (such as CardActionArea or Accordion) when the checkbox is activated.
|
|
29
|
+
*/
|
|
30
|
+
onClick={(e) => e.stopPropagation()}
|
|
31
|
+
onChange={getToggleSelectedHandler()}
|
|
32
|
+
/>
|
|
33
|
+
</span>
|
|
34
|
+
</Tooltip>
|
|
23
35
|
);
|
|
24
36
|
};
|
|
@@ -6,17 +6,21 @@ import { PALETTE } from "../../../../styles/common/constants/palette";
|
|
|
6
6
|
|
|
7
7
|
export interface StyledTableRowProps {
|
|
8
8
|
canExpand?: boolean;
|
|
9
|
+
canSelect?: boolean;
|
|
9
10
|
isExpanded?: boolean;
|
|
10
11
|
isGrouped?: boolean;
|
|
11
12
|
isPreview?: boolean;
|
|
13
|
+
isSelected?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const StyledTableRow = styled(MTableRow, {
|
|
15
17
|
shouldForwardProp: (prop) =>
|
|
16
18
|
prop !== "canExpand" &&
|
|
19
|
+
prop !== "canSelect" &&
|
|
17
20
|
prop !== "isExpanded" &&
|
|
21
|
+
prop !== "isGrouped" &&
|
|
18
22
|
prop !== "isPreview" &&
|
|
19
|
-
prop !== "
|
|
23
|
+
prop !== "isSelected",
|
|
20
24
|
})<StyledTableRowProps>`
|
|
21
25
|
&& {
|
|
22
26
|
transition: background-color 300ms ease-in;
|
|
@@ -54,5 +58,19 @@ export const StyledTableRow = styled(MTableRow, {
|
|
|
54
58
|
css`
|
|
55
59
|
background-color: ${PALETTE.PRIMARY_LIGHTEST};
|
|
56
60
|
`}
|
|
61
|
+
|
|
62
|
+
${({ isSelected }) =>
|
|
63
|
+
isSelected &&
|
|
64
|
+
css`
|
|
65
|
+
background-color: ${PALETTE.PRIMARY_LIGHTEST};
|
|
66
|
+
`}
|
|
67
|
+
|
|
68
|
+
${({ canSelect }) =>
|
|
69
|
+
!canSelect &&
|
|
70
|
+
css`
|
|
71
|
+
.MuiTableCell-root {
|
|
72
|
+
color: ${PALETTE.INK_LIGHT};
|
|
73
|
+
}
|
|
74
|
+
`}
|
|
57
75
|
}
|
|
58
76
|
`;
|
package/src/components/Table/components/TableRows/components/CollapsableRows/collapsableRows.tsx
CHANGED
|
@@ -33,6 +33,7 @@ export const CollapsableRows = <T extends RowData>({
|
|
|
33
33
|
data-index={rowIndex}
|
|
34
34
|
ref={virtualizer.measureElement}
|
|
35
35
|
isPreview={row.getIsPreview()}
|
|
36
|
+
isSelected={row.getIsSelected()}
|
|
36
37
|
>
|
|
37
38
|
<CollapsableCell
|
|
38
39
|
isDisabled={isCollapsableRowDisabled(tableInstance)}
|
|
@@ -30,10 +30,12 @@ export const TableRows = <T extends RowData>({
|
|
|
30
30
|
<StyledTableRow
|
|
31
31
|
key={row.id}
|
|
32
32
|
canExpand={getCanExpand()}
|
|
33
|
+
canSelect={row.getCanSelect()}
|
|
33
34
|
data-index={rowIndex}
|
|
34
35
|
isExpanded={getIsExpanded()}
|
|
35
36
|
isGrouped={getIsGrouped()}
|
|
36
37
|
isPreview={getIsPreview()}
|
|
38
|
+
isSelected={row.getIsSelected()}
|
|
37
39
|
onClick={() => handleToggleExpanded(row)}
|
|
38
40
|
ref={virtualizer.measureElement}
|
|
39
41
|
>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Row,
|
|
3
|
+
RowData,
|
|
4
|
+
Table,
|
|
5
|
+
TableFeature,
|
|
6
|
+
TableOptionsResolved,
|
|
7
|
+
} from "@tanstack/react-table";
|
|
8
|
+
import { getSelectionValidation } from "./utils";
|
|
9
|
+
|
|
10
|
+
export const ROW_SELECTION_VALIDATION: TableFeature = {
|
|
11
|
+
createRow: <T extends RowData>(row: Row<T>, table: Table<T>): void => {
|
|
12
|
+
row.getSelectionValidation = (): string | undefined => {
|
|
13
|
+
return getSelectionValidation(row, table);
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
getDefaultOptions: <T extends RowData>(): Partial<
|
|
17
|
+
TableOptionsResolved<T>
|
|
18
|
+
> => {
|
|
19
|
+
return {
|
|
20
|
+
enableRowSelectionValidation: false,
|
|
21
|
+
getRowSelectionValidation: undefined,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Row, RowData } from "@tanstack/react-table";
|
|
2
|
+
|
|
3
|
+
export interface RowSelectionValidationOptions<T extends RowData> {
|
|
4
|
+
enableRowSelectionValidation?: boolean;
|
|
5
|
+
getRowSelectionValidation?: (row: Row<T>) => string | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RowSelectionValidationRow {
|
|
9
|
+
getSelectionValidation?: () => string | undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Row, RowData, Table } from "@tanstack/react-table";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the validation message for a row.
|
|
5
|
+
* @param row - Row.
|
|
6
|
+
* @param table - Table.
|
|
7
|
+
* @returns The validation message for the row.
|
|
8
|
+
*/
|
|
9
|
+
export function getSelectionValidation<T extends RowData>(
|
|
10
|
+
row: Row<T>,
|
|
11
|
+
table: Table<T>
|
|
12
|
+
): string | undefined {
|
|
13
|
+
if (!table.options.enableRowSelectionValidation) return;
|
|
14
|
+
return table.options.getRowSelectionValidation?.(row);
|
|
15
|
+
}
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
RowPreviewRow,
|
|
7
7
|
RowPreviewTableState,
|
|
8
8
|
} from "./RowPreview/entities";
|
|
9
|
+
import {
|
|
10
|
+
RowSelectionValidationOptions,
|
|
11
|
+
RowSelectionValidationRow,
|
|
12
|
+
} from "./RowSelectionValidation/types";
|
|
9
13
|
import {
|
|
10
14
|
TableDownloadColumn,
|
|
11
15
|
TableDownloadInstance,
|
|
@@ -23,8 +27,12 @@ export type CustomFeatureInitialTableState = Partial<RowPreviewTableState>;
|
|
|
23
27
|
export interface CustomFeatureOptions<T extends RowData>
|
|
24
28
|
extends TableDownloadOptions<T>,
|
|
25
29
|
RowPositionOptions,
|
|
26
|
-
RowPreviewOptions
|
|
30
|
+
RowPreviewOptions,
|
|
31
|
+
RowSelectionValidationOptions<T> {}
|
|
27
32
|
|
|
28
|
-
export interface CustomFeatureRow
|
|
33
|
+
export interface CustomFeatureRow
|
|
34
|
+
extends RowPositionRow,
|
|
35
|
+
RowPreviewRow,
|
|
36
|
+
RowSelectionValidationRow {}
|
|
29
37
|
|
|
30
38
|
export type CustomFeatureTableState = RowPreviewTableState;
|
|
@@ -3,7 +3,6 @@ import { useConfig } from "../../../hooks/useConfig";
|
|
|
3
3
|
import { useExpandedOptions } from "./expanded/hook";
|
|
4
4
|
import { useGroupingOptions } from "./grouping/hook";
|
|
5
5
|
import { useInitialState } from "./initialState/hook";
|
|
6
|
-
import { useRowSelectionOptions } from "./rowSelection/hook";
|
|
7
6
|
import { useSortingOptions } from "./sorting/hook";
|
|
8
7
|
import { useVisibilityOptions } from "./visibility/hook";
|
|
9
8
|
|
|
@@ -12,14 +11,12 @@ export function useTableOptions<T extends RowData>(): Partial<TableOptions<T>> {
|
|
|
12
11
|
const tableOptions = entityConfig.list.tableOptions;
|
|
13
12
|
const expandedOptions = useExpandedOptions<T>();
|
|
14
13
|
const groupingOptions = useGroupingOptions();
|
|
15
|
-
const rowSelectionOptions = useRowSelectionOptions<T>();
|
|
16
14
|
const sortingOptions = useSortingOptions<T>();
|
|
17
15
|
const visibilityOptions = useVisibilityOptions();
|
|
18
16
|
const initialState = useInitialState<T>(tableOptions);
|
|
19
17
|
return {
|
|
20
18
|
...expandedOptions,
|
|
21
19
|
...groupingOptions,
|
|
22
|
-
...rowSelectionOptions,
|
|
23
20
|
...sortingOptions, // TODO(cc) merge of all sorting options.
|
|
24
21
|
...visibilityOptions,
|
|
25
22
|
...tableOptions,
|
package/src/components/common/CustomIcon/components/UncheckedDisabledIcon/uncheckedDisabledIcon.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SvgIcon, SvgIconProps } from "@mui/material";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { PALETTE } from "../../../../../styles/common/constants/palette";
|
|
4
|
+
|
|
5
|
+
export const UncheckedDisabledIcon = ({
|
|
6
|
+
fontSize = "xsmall",
|
|
7
|
+
viewBox = "0 0 18 18",
|
|
8
|
+
...props
|
|
9
|
+
}: SvgIconProps): JSX.Element => {
|
|
10
|
+
return (
|
|
11
|
+
<SvgIcon fontSize={fontSize} viewBox={viewBox} {...props}>
|
|
12
|
+
<path
|
|
13
|
+
d="M4 0.5H14C15.933 0.5 17.5 2.067 17.5 4V14C17.5 15.933 15.933 17.5 14 17.5H4C2.067 17.5 0.5 15.933 0.5 14V4C0.5 2.067 2.067 0.5 4 0.5Z"
|
|
14
|
+
stroke={PALETTE.SMOKE_MAIN}
|
|
15
|
+
/>
|
|
16
|
+
<rect
|
|
17
|
+
x="0.5"
|
|
18
|
+
y="0.5"
|
|
19
|
+
width="17"
|
|
20
|
+
height="17"
|
|
21
|
+
rx="3.5"
|
|
22
|
+
fill={PALETTE.SMOKE_LIGHT}
|
|
23
|
+
stroke={PALETTE.SMOKE_MAIN}
|
|
24
|
+
/>
|
|
25
|
+
</SvgIcon>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
createColumnHelper,
|
|
4
|
+
getCoreRowModel,
|
|
5
|
+
Row,
|
|
6
|
+
Table,
|
|
7
|
+
TableOptions,
|
|
8
|
+
useReactTable,
|
|
9
|
+
} from "@tanstack/react-table";
|
|
10
|
+
import { renderHook, RenderHookResult } from "@testing-library/react";
|
|
11
|
+
import { ROW_SELECTION_VALIDATION } from "../src/components/Table/features/RowSelectionValidation/constants";
|
|
12
|
+
import { RowSelectionValidationOptions } from "../src/components/Table/features/RowSelectionValidation/types";
|
|
13
|
+
import { getSelectionValidation } from "../src/components/Table/features/RowSelectionValidation/utils";
|
|
14
|
+
|
|
15
|
+
type ValidationFn = NonNullable<
|
|
16
|
+
RowSelectionValidationOptions<RowData>["getRowSelectionValidation"]
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
interface RowData {
|
|
20
|
+
id: number;
|
|
21
|
+
isValid: boolean;
|
|
22
|
+
name: string;
|
|
23
|
+
status: "active" | "inactive";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ACCESSOR_KEY: Record<string, keyof RowData> = {
|
|
27
|
+
ID: "id",
|
|
28
|
+
IS_VALID: "isValid",
|
|
29
|
+
NAME: "name",
|
|
30
|
+
STATUS: "status",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Mock data.
|
|
34
|
+
const DATA: RowData[] = [
|
|
35
|
+
{ id: 1, isValid: true, name: "Item 1", status: "active" },
|
|
36
|
+
{ id: 2, isValid: false, name: "Item 2", status: "inactive" },
|
|
37
|
+
{ id: 3, isValid: true, name: "Item 3", status: "active" },
|
|
38
|
+
{ id: 4, isValid: false, name: "Item 4", status: "inactive" },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const columnHelper = createColumnHelper<RowData>();
|
|
42
|
+
|
|
43
|
+
// Mock column definitions.
|
|
44
|
+
const COLUMNS = [
|
|
45
|
+
columnHelper.accessor(ACCESSOR_KEY.ID, { header: "ID" }),
|
|
46
|
+
columnHelper.accessor(ACCESSOR_KEY.NAME, { header: "Name" }),
|
|
47
|
+
columnHelper.accessor(ACCESSOR_KEY.IS_VALID, { header: "Valid" }),
|
|
48
|
+
columnHelper.accessor(ACCESSOR_KEY.STATUS, { header: "Status" }),
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Test constants.
|
|
52
|
+
const VALIDATION_MESSAGE = {
|
|
53
|
+
INVALID: "This item is invalid",
|
|
54
|
+
VALID: "This item is valid",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const createTable = (
|
|
58
|
+
tableOptions?: Omit<
|
|
59
|
+
TableOptions<RowData>,
|
|
60
|
+
"columns" | "data" | "getCoreRowModel"
|
|
61
|
+
>
|
|
62
|
+
): RenderHookResult<Table<RowData>, unknown> => {
|
|
63
|
+
return renderHook(() =>
|
|
64
|
+
useReactTable({
|
|
65
|
+
_features: [ROW_SELECTION_VALIDATION],
|
|
66
|
+
columns: COLUMNS,
|
|
67
|
+
data: DATA,
|
|
68
|
+
enableRowSelectionValidation: true,
|
|
69
|
+
getCoreRowModel: getCoreRowModel(),
|
|
70
|
+
...tableOptions,
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
describe("RowSelectionValidation Feature", () => {
|
|
76
|
+
let tableWithoutValidation: any;
|
|
77
|
+
let firstRowWithoutValidation: any;
|
|
78
|
+
let tableWithValidation: any;
|
|
79
|
+
let firstRowWithValidation: any;
|
|
80
|
+
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
const resultWithoutValidation = createTable();
|
|
83
|
+
const resultWithValidation = createTable({
|
|
84
|
+
getRowSelectionValidation: () => VALIDATION_MESSAGE.INVALID,
|
|
85
|
+
});
|
|
86
|
+
tableWithoutValidation = resultWithoutValidation.result.current;
|
|
87
|
+
firstRowWithoutValidation = tableWithoutValidation.getRowModel().rows[0];
|
|
88
|
+
tableWithValidation = resultWithValidation.result.current;
|
|
89
|
+
firstRowWithValidation = tableWithValidation.getRowModel().rows[0];
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("ROW_SELECTION_VALIDATION TableFeature", () => {
|
|
93
|
+
test("should have correct structure", () => {
|
|
94
|
+
expect(ROW_SELECTION_VALIDATION).toHaveProperty("createRow");
|
|
95
|
+
expect(ROW_SELECTION_VALIDATION).toHaveProperty("getDefaultOptions");
|
|
96
|
+
expect(typeof ROW_SELECTION_VALIDATION.createRow).toBe("function");
|
|
97
|
+
expect(typeof ROW_SELECTION_VALIDATION.getDefaultOptions).toBe(
|
|
98
|
+
"function"
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("getDefaultOptions should return correct default options", () => {
|
|
103
|
+
const { getDefaultOptions } = ROW_SELECTION_VALIDATION;
|
|
104
|
+
expect(getDefaultOptions!(tableWithoutValidation)).toEqual({
|
|
105
|
+
enableRowSelectionValidation: false,
|
|
106
|
+
getRowSelectionValidation: undefined,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("getSelectionValidation utility", () => {
|
|
112
|
+
test("should return undefined when no validation function is provided", () => {
|
|
113
|
+
expect(
|
|
114
|
+
getSelectionValidation(
|
|
115
|
+
firstRowWithoutValidation,
|
|
116
|
+
tableWithoutValidation
|
|
117
|
+
)
|
|
118
|
+
).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("should return validation message when validation function is provided", () => {
|
|
122
|
+
expect(
|
|
123
|
+
getSelectionValidation(firstRowWithValidation, tableWithValidation)
|
|
124
|
+
).toBe(VALIDATION_MESSAGE.INVALID);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("should return undefined when validation function returns undefined", () => {
|
|
128
|
+
const getRowSelectionValidation = jest
|
|
129
|
+
.fn<ValidationFn>()
|
|
130
|
+
.mockReturnValue(undefined);
|
|
131
|
+
|
|
132
|
+
const { result } = createTable({ getRowSelectionValidation });
|
|
133
|
+
|
|
134
|
+
const table = result.current;
|
|
135
|
+
const firstRow = table.getRowModel().rows[0];
|
|
136
|
+
|
|
137
|
+
const validationResult = getSelectionValidation(firstRow, table);
|
|
138
|
+
|
|
139
|
+
expect(validationResult).toBeUndefined();
|
|
140
|
+
expect(getRowSelectionValidation).toHaveBeenCalledWith(firstRow);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("should work with different rows from the same table", () => {
|
|
144
|
+
const getRowSelectionValidation = jest
|
|
145
|
+
.fn<ValidationFn>()
|
|
146
|
+
.mockImplementation((row) => {
|
|
147
|
+
const status = row.getValue(ACCESSOR_KEY.STATUS);
|
|
148
|
+
return status === "inactive" ? VALIDATION_MESSAGE.INVALID : undefined;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const { result } = createTable({ getRowSelectionValidation });
|
|
152
|
+
|
|
153
|
+
const table = result.current;
|
|
154
|
+
const rows = table.getRowModel().rows;
|
|
155
|
+
|
|
156
|
+
expect(getSelectionValidation(rows[0], table)).toBeUndefined();
|
|
157
|
+
expect(getSelectionValidation(rows[2], table)).toBeUndefined();
|
|
158
|
+
expect(getSelectionValidation(rows[1], table)).toBe(
|
|
159
|
+
VALIDATION_MESSAGE.INVALID
|
|
160
|
+
);
|
|
161
|
+
expect(getRowSelectionValidation).toHaveBeenCalledTimes(3);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("row.getSelectionValidation method", () => {
|
|
166
|
+
test("should be attached to rows when feature is included", () => {
|
|
167
|
+
const rows = tableWithoutValidation.getRowModel().rows;
|
|
168
|
+
|
|
169
|
+
rows.forEach((row: any) => {
|
|
170
|
+
expect(row).toHaveProperty("getSelectionValidation");
|
|
171
|
+
expect(typeof row.getSelectionValidation).toBe("function");
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("should return undefined when no validation function is configured", () => {
|
|
176
|
+
expect(
|
|
177
|
+
firstRowWithoutValidation.getSelectionValidation()
|
|
178
|
+
).toBeUndefined();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("should return validation message when validation function is configured", () => {
|
|
182
|
+
expect(firstRowWithValidation.getSelectionValidation()).toBe(
|
|
183
|
+
VALIDATION_MESSAGE.INVALID
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("Integration with table options", () => {
|
|
189
|
+
test("should work with getRowSelectionValidation function", () => {
|
|
190
|
+
const enableRowSelection = jest
|
|
191
|
+
.fn<(row: Row<RowData>) => boolean>()
|
|
192
|
+
.mockImplementation((row) => row.getValue(ACCESSOR_KEY.IS_VALID));
|
|
193
|
+
|
|
194
|
+
const getRowSelectionValidation = jest
|
|
195
|
+
.fn<ValidationFn>()
|
|
196
|
+
.mockImplementation((row) => {
|
|
197
|
+
const isValid = row.getValue(ACCESSOR_KEY.IS_VALID);
|
|
198
|
+
return isValid ? undefined : VALIDATION_MESSAGE.INVALID;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const { result } = createTable({
|
|
202
|
+
enableRowSelection,
|
|
203
|
+
getRowSelectionValidation,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const table = result.current;
|
|
207
|
+
const rows = table.getRowModel().rows;
|
|
208
|
+
|
|
209
|
+
rows.forEach((row) => {
|
|
210
|
+
const isValid = row.getValue(ACCESSOR_KEY.IS_VALID);
|
|
211
|
+
const canSelect = row.getCanSelect();
|
|
212
|
+
const validationMessage = row.getSelectionValidation?.();
|
|
213
|
+
|
|
214
|
+
if (isValid) {
|
|
215
|
+
expect(canSelect).toBe(true);
|
|
216
|
+
expect(validationMessage).toBeUndefined();
|
|
217
|
+
} else {
|
|
218
|
+
expect(canSelect).toBe(false);
|
|
219
|
+
expect(validationMessage).toBe(VALIDATION_MESSAGE.INVALID);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("should handle complex validation scenarios", () => {
|
|
225
|
+
const getRowSelectionValidation = jest
|
|
226
|
+
.fn<ValidationFn>()
|
|
227
|
+
.mockImplementation((row) => {
|
|
228
|
+
const id = row.getValue(ACCESSOR_KEY.ID);
|
|
229
|
+
const isValid = row.getValue(ACCESSOR_KEY.IS_VALID);
|
|
230
|
+
|
|
231
|
+
if (!isValid) return VALIDATION_MESSAGE.INVALID;
|
|
232
|
+
if (id === 1) return VALIDATION_MESSAGE.VALID;
|
|
233
|
+
return undefined;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const { result } = createTable({ getRowSelectionValidation });
|
|
237
|
+
|
|
238
|
+
const table = result.current;
|
|
239
|
+
const rows = table.getRowModel().rows;
|
|
240
|
+
|
|
241
|
+
expect(rows[0].getSelectionValidation?.()).toBe(VALIDATION_MESSAGE.VALID);
|
|
242
|
+
expect(rows[1].getSelectionValidation?.()).toBe(
|
|
243
|
+
VALIDATION_MESSAGE.INVALID
|
|
244
|
+
);
|
|
245
|
+
expect(rows[2].getSelectionValidation?.()).toBeUndefined();
|
|
246
|
+
expect(rows[3].getSelectionValidation?.()).toBe(
|
|
247
|
+
VALIDATION_MESSAGE.INVALID
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("Edge cases", () => {
|
|
253
|
+
test("should work when feature is not included", () => {
|
|
254
|
+
const { result } = createTable({
|
|
255
|
+
_features: [],
|
|
256
|
+
enableRowSelectionValidation: false,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const table = result.current;
|
|
260
|
+
const firstRow = table.getRowModel().rows[0];
|
|
261
|
+
|
|
262
|
+
expect(firstRow).not.toHaveProperty("getSelectionValidation");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("should return undefined when feature is disabled", () => {
|
|
266
|
+
const getRowSelectionValidation = jest
|
|
267
|
+
.fn<ValidationFn>()
|
|
268
|
+
.mockReturnValue(VALIDATION_MESSAGE.INVALID);
|
|
269
|
+
|
|
270
|
+
const { result } = createTable({
|
|
271
|
+
enableRowSelectionValidation: false,
|
|
272
|
+
getRowSelectionValidation,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const table = result.current;
|
|
276
|
+
const firstRow = table.getRowModel().rows[0];
|
|
277
|
+
|
|
278
|
+
expect(firstRow.getSelectionValidation?.()).toBeUndefined();
|
|
279
|
+
expect(getRowSelectionValidation).not.toHaveBeenCalled();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|