diff --git a/src/data-table/__tests__/data-table-stateful-callback.e2e.ts b/src/data-table/__tests__/data-table-stateful-callback.e2e.ts new file mode 100644 index 0000000000..7321e46caf --- /dev/null +++ b/src/data-table/__tests__/data-table-stateful-callback.e2e.ts @@ -0,0 +1,51 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ + +import { expect, test } from '@playwright/test'; +import { mount } from '../../test/integration'; + +test.describe('data table initial filters', () => { + test('mounts with initial sort applied', async ({ page }) => { + await mount(page, 'data-table--stateful-callback'); + + const textQueryInput = page.locator('input').first(); + const filterMenuButton = page.locator('text=Add Filter'); + const filterMenuColumn = page.locator('li').locator('text=Order'); + const categoricalFilterOption = page.locator('label').locator('text=Crocodylia'); + const filterMenuApply = page.locator('text=Apply'); + const changeListItems = page.locator('ul#change-list').locator('li'); + const filterClearButton = page.locator('[title="Delete"]'); + const nameColumnHeader = page.locator('text=Name'); + + await expect(changeListItems).toHaveCount(1); + await textQueryInput.type('Alligator'); + await expect(changeListItems).toHaveCount(2); + await filterMenuButton.click(); + await filterMenuColumn.click(); + await categoricalFilterOption.click(); + await filterMenuApply.click(); + await expect(changeListItems).toHaveCount(3); + await filterClearButton.click(); + await expect(changeListItems).toHaveCount(4); + await nameColumnHeader.click(); + await expect(changeListItems).toHaveCount(5); + await nameColumnHeader.click(); + await expect(changeListItems).toHaveCount(6); + await nameColumnHeader.click(); + await expect(changeListItems).toHaveCount(7); + + expect(await changeListItems.allTextContents()).toEqual([ + '{"textQuery":""}', + '{"textQuery":"Alligator"}', + '{"title":"Order","data":{"description":"Crocodylia","exclude":false,"selection":{}}}', + '{"title":"Order"}', + '{"sortIndex":0,"sortDirection":"ASC"}', + '{"sortIndex":0,"sortDirection":"DESC"}', + '{"sortIndex":-1,"sortDirection":"ASC"}', + ]); + }); +}); diff --git a/src/data-table/__tests__/data-table-stateful-callback.scenario.tsx b/src/data-table/__tests__/data-table-stateful-callback.scenario.tsx new file mode 100644 index 0000000000..de9de779bc --- /dev/null +++ b/src/data-table/__tests__/data-table-stateful-callback.scenario.tsx @@ -0,0 +1,120 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import * as React from 'react'; + +import { Tag, KIND as TAG_KIND } from '../../tag'; + +import CategoricalColumn from '../column-categorical'; +import CustomColumn from '../column-custom'; +import StringColumn from '../column-string'; +import { StatefulDataTable } from '../stateful-data-table'; + +import AnimalData from './animal-data'; + +type RowData = { + Name: string; + Kingdom: string; + Phylum: string; + Class: string; + Order: string; + Family: string; +}; + +const columns = [ + StringColumn({ + title: 'Name', + minWidth: 300, + mapDataToValue: (data: RowData) => data.Name, + }), + + CategoricalColumn({ + title: 'Kingdom', + mapDataToValue: (data: RowData) => data.Kingdom, + }), + + CustomColumn({ + title: 'Phylum', + minWidth: 90, + mapDataToValue: (data: RowData) => data.Phylum, + textQueryFilter: function (textQuery, data) { + return data.toLowerCase().includes(textQuery.toLowerCase()); + }, + renderCell: function PhylumnCell(props) { + return ( + + {props.value} + + ); + }, + }), + + CategoricalColumn({ + title: 'Class', + minWidth: 120, + mapDataToValue: (data: RowData) => data.Class, + }), + + CategoricalColumn({ + title: 'Order', + mapDataToValue: (data: RowData) => data.Order, + }), + + CategoricalColumn({ + title: 'Family', + mapDataToValue: (data: RowData) => data.Family, + }), +]; + +const rows = AnimalData.map((row) => { + return { + id: row.Name, + data: row, + }; +}); + +export function Scenario() { + const [changes, setChanges] = React.useState([]); + + function addChange(change) { + setChanges((p) => [...p, change]); + } + + return ( +
+
+ addChange({ title, data })} + onFilterRemove={(title) => addChange({ title })} + onSort={(sortIndex, sortDirection) => addChange({ sortIndex, sortDirection })} + onTextQueryChange={(textQuery) => addChange({ textQuery })} + columns={columns} + rows={rows} + /> +
+ +

change list

+ +
+ ); +} diff --git a/src/data-table/__tests__/data-table.stories.tsx b/src/data-table/__tests__/data-table.stories.tsx index 7057776333..99caa1afcf 100644 --- a/src/data-table/__tests__/data-table.stories.tsx +++ b/src/data-table/__tests__/data-table.stories.tsx @@ -33,6 +33,7 @@ import { Scenario as DataTableRowActions } from './data-table-row-actions.scenar import { Scenario as DataTableRowActionsButton } from './data-table-row-actions-button.scenario'; import { Scenario as DataTableRowActionsDynamic } from './data-table-row-actions-dynamic.scenario'; import { Scenario as DataTableRowHeight } from './data-table-row-height.scenario'; +import { Scenario as DataTableStatefulCallback } from './data-table-stateful-callback.scenario'; import { Scenario as DataTableTextSearch } from './data-table-text-search.scenario'; import { Scenario as DataTableDefault } from './data-table.scenario'; import { Scenario as DataTableRtl } from './data-table-rtl.scenario'; @@ -66,6 +67,7 @@ export const RowActions = () => ; export const RowActionsButton = () => ; export const RowActionsDynamic = () => ; export const RowHeight = () => ; +export const StatefulCallback = () => ; export const TextSearch = () => ; export const DataTable = () => ; export const TestRtl = () => ; diff --git a/src/data-table/stateful-container.ts b/src/data-table/stateful-container.ts index 14d4023e5a..13b28822a3 100644 --- a/src/data-table/stateful-container.ts +++ b/src/data-table/stateful-container.ts @@ -22,35 +22,49 @@ function useDuplicateColumnTitleWarning(columns: ColumnOptions[]) { }, [columns]); } -function useSortParameters(initialSortIndex = -1, initialSortDirection = null) { - const [sortIndex, setSortIndex] = React.useState(initialSortIndex); - const [sortDirection, setSortDirection] = React.useState(initialSortDirection); +export const StatefulContainer: React.FC = (props) => { + useDuplicateColumnTitleWarning(props.columns); + const [sortIndex, setSortIndex] = React.useState( + typeof props.initialSortIndex === 'number' ? props.initialSortIndex : -1 + ); + const [sortDirection, setSortDirection] = React.useState(props.initialSortDirection); + const [filters, setFilters] = React.useState(props.initialFilters || new Map()); + const [textQuery, setTextQuery] = React.useState(''); + const [selectedRowIds, setSelectedRowIds] = React.useState>( + props.initialSelectedRowIds || new Set() + ); function handleSort(columnIndex) { + let nextSortIndex; + let nextSortDirection; + if (columnIndex === sortIndex) { if (sortDirection === SORT_DIRECTIONS.DESC) { - setSortIndex(-1); - setSortDirection(SORT_DIRECTIONS.ASC); + nextSortIndex = -1; + nextSortDirection = SORT_DIRECTIONS.ASC; } else { - setSortDirection(SORT_DIRECTIONS.DESC); + nextSortIndex = columnIndex; + nextSortDirection = SORT_DIRECTIONS.DESC; } } else { - setSortIndex(columnIndex); - setSortDirection(SORT_DIRECTIONS.ASC); + nextSortIndex = columnIndex; + nextSortDirection = SORT_DIRECTIONS.ASC; } - } - return [sortIndex, sortDirection, handleSort]; -} + setSortIndex(nextSortIndex); + setSortDirection(nextSortDirection); -export const StatefulContainer: React.FC = (props) => { - useDuplicateColumnTitleWarning(props.columns); - const [sortIndex, sortDirection, handleSort] = useSortParameters( - props.initialSortIndex, - props.initialSortDirection - ); - const [filters, setFilters] = React.useState(props.initialFilters || new Map()); - const [textQuery, setTextQuery] = React.useState(''); + if (props.onSort) { + props.onSort(nextSortIndex, nextSortDirection); + } + } + + function handleTextQueryChange(nextTextQuery) { + setTextQuery(nextTextQuery); + if (props.onTextQueryChange) { + props.onTextQueryChange(nextTextQuery); + } + } function handleFilterAdd(title, filterParams) { filters.set(title, filterParams); @@ -67,9 +81,6 @@ export const StatefulContainer: React.FC = (props) => { setFilters(new Map(filters)); } - const [selectedRowIds, setSelectedRowIds] = React.useState>( - props.initialSelectedRowIds || new Set() - ); function handleSelectChange(next) { setSelectedRowIds(next); @@ -122,7 +133,7 @@ export const StatefulContainer: React.FC = (props) => { onSelectNone: handleSelectNone, onSelectOne: handleSelectOne, onSort: handleSort, - onTextQueryChange: setTextQuery, + onTextQueryChange: handleTextQueryChange, resizableColumnWidths: Boolean(props.resizableColumnWidths), rowHighlightIndex: props.rowHighlightIndex, selectedRowIds, diff --git a/src/data-table/stateful-data-table.tsx b/src/data-table/stateful-data-table.tsx index 32924ae709..6b29255e08 100644 --- a/src/data-table/stateful-data-table.tsx +++ b/src/data-table/stateful-data-table.tsx @@ -168,6 +168,8 @@ export function StatefulDataTable(props: StatefulDataTableProps) { onIncludedRowsChange={props.onIncludedRowsChange} onRowHighlightChange={props.onRowHighlightChange} onSelectionChange={props.onSelectionChange} + onSort={props.onSort} + onTextQueryChange={props.onTextQueryChange} resizableColumnWidths={props.resizableColumnWidths} rows={props.rows} rowActions={props.rowActions} diff --git a/src/data-table/types.js.flow b/src/data-table/types.js.flow index 80a0656976..985e0bfb97 100644 --- a/src/data-table/types.js.flow +++ b/src/data-table/types.js.flow @@ -111,6 +111,7 @@ export type StatefulDataTablePropsT = {| onIncludedRowsChange?: (rows: RowT[]) => void, onRowHighlightChange?: (rowIndex: number, row: RowT) => void, onSelectionChange?: (RowT[]) => mixed, + onSort?: (columnIndex: number, sortDirection: SortDirectionsT) => void, resizableColumnWidths?: boolean, rows: RowT[], rowActions?: RowActionT[] | ((RowT) => RowActionT[]), diff --git a/src/data-table/types.ts b/src/data-table/types.ts index ec617b1946..96516544a9 100644 --- a/src/data-table/types.ts +++ b/src/data-table/types.ts @@ -122,6 +122,8 @@ export type StatefulDataTableProps = { onIncludedRowsChange?: (rows: Row[]) => void; onRowHighlightChange?: (rowIndex: number, row: Row) => void; onSelectionChange?: (a: Row[]) => unknown; + onSort?: (columnIndex: number, sortDirection: SortDirections) => void; + onTextQueryChange?: (textQuery: string) => void; resizableColumnWidths?: boolean; rows: Row[]; rowActions?: RowAction[] | ((a: Row) => RowAction[]); diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-chromium-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-chromium-linux.png new file mode 100644 index 0000000000..bf9bfef890 Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-chromium-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-firefox-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-firefox-linux.png new file mode 100644 index 0000000000..caf54508fd Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-firefox-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-webkit-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-webkit-linux.png new file mode 100644 index 0000000000..ff16fad3d0 Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-dark-webkit-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-chromium-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-chromium-linux.png new file mode 100644 index 0000000000..0b09fda972 Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-chromium-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-firefox-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-firefox-linux.png new file mode 100644 index 0000000000..deb5326329 Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-firefox-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-webkit-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-webkit-linux.png new file mode 100644 index 0000000000..3de24ee01f Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-desktop-webkit-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-chromium-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-chromium-linux.png new file mode 100644 index 0000000000..47b796d228 Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-chromium-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-firefox-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-firefox-linux.png new file mode 100644 index 0000000000..c87dd99a9e Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-firefox-linux.png differ diff --git a/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-webkit-linux.png b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-webkit-linux.png new file mode 100644 index 0000000000..3df4f417ca Binary files /dev/null and b/vrt/tests.vrt.js-snapshots/data-table--stateful-callback-mobile-webkit-linux.png differ