From 5eb8c11ebed812e9474bf232f87c311a22ea7826 Mon Sep 17 00:00:00 2001 From: Donisius Wigie Date: Thu, 18 Nov 2021 17:19:36 -0500 Subject: [PATCH 01/84] feat(table model): account for rowSpans in moveColumn --- .../src/table/table-model.class.spec.ts | 216 +++++++++++++++- .../angular/src/table/table-model.class.ts | 238 ++++++++++++++++-- packages/angular/src/table/table.stories.ts | 111 +++++++- 3 files changed, 532 insertions(+), 33 deletions(-) diff --git a/packages/angular/src/table/table-model.class.spec.ts b/packages/angular/src/table/table-model.class.spec.ts index f6d5786f4c..70c99a2cec 100644 --- a/packages/angular/src/table/table-model.class.spec.ts +++ b/packages/angular/src/table/table-model.class.spec.ts @@ -727,7 +727,7 @@ describe('Table', () => { [new TableItem({ data: 'D' }), new TableItem({ data: 'E' }), new TableItem({ data: 'F' })], ]); - tableModel.moveColumn(1, 3); + tableModel.moveColumn(1, 2); expect(tableModel.column(0)).toEqual([ new TableItem({ data: 'A' }), @@ -796,7 +796,7 @@ describe('Table', () => { [new TableItem({ data: 'D' }), new TableItem({ data: 'E' }), new TableItem({ data: 'F' })], ]); - tableModel.moveColumn(0, 2); + tableModel.moveColumn(0, 1); expect(tableModel.column(0)).toEqual([ new TableItem({ data: 'B' }), @@ -899,6 +899,214 @@ describe('Table', () => { ]); }); + it('should move (multi-line header with row spans) column to beginning', () => { + let tableModel = new AITableModel(); + tableModel.setHeader([ + [ + new TableHeaderItem({ data: 'h1', colSpan: 4 }), + new TableHeaderItem({ data: 'h2', rowSpan: 4 }), + new TableHeaderItem({ data: 'h3', colSpan: 2, rowSpan: 2 }), + new TableHeaderItem({ data: 'h4', colSpan: 2 }), + ], + [ + new TableHeaderItem({ data: 'h11' }), + new TableHeaderItem({ data: 'h12', rowSpan: 2, colSpan: 2 }), + new TableHeaderItem({ data: 'h13', rowSpan: 3 }), + new TableHeaderItem({ data: 'h41', rowSpan: 3 }), + new TableHeaderItem({ data: 'h42' }), + ], + [ + new TableHeaderItem({ data: 'h111' }), + new TableHeaderItem({ data: 'h31', colSpan: 2 }), + new TableHeaderItem({ data: 'h421' }), + ], + [ + new TableHeaderItem({ data: 'h1111' }), + new TableHeaderItem({ data: 'h121' }), + new TableHeaderItem({ data: 'h122' }), + new TableHeaderItem({ data: 'h311' }), + new TableHeaderItem({ data: 'h312' }), + new TableHeaderItem({ data: 'h422' }), + ], + ]); + + tableModel.setData([ + [ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd422' }), + ], + [ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd422' }), + ], + ]); + + tableModel.moveColumn(2, 0, 0); + + expect(tableModel.column(0)).toEqual([ + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd311' }) + ]); + + expect(tableModel.column(1)).toEqual([ + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd312' }) + ]); + + expect(tableModel.column(2)).toEqual([ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd1111' }) + ]); + + expect(tableModel.column(3)).toEqual([ + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd121' }) + ]); + + expect(tableModel.column(4)).toEqual([ + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd122' }) + ]); + + expect(tableModel.column(5)).toEqual([ + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd13' }) + ]); + + expect(tableModel.column(6)).toEqual([ + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd2' }) + ]); + + expect(tableModel.column(7)).toEqual([ + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd41' }) + ]); + + expect(tableModel.column(8)).toEqual([ + new TableItem({ data: 'd422' }), + new TableItem({ data: 'd422' }) + ]); + }); + + it('should move (multi-line header with row spans) column to the end', () => { + let tableModel = new AITableModel(); + tableModel.setHeader([ + [ + new TableHeaderItem({ data: 'h1', colSpan: 4 }), + new TableHeaderItem({ data: 'h2', rowSpan: 4 }), + new TableHeaderItem({ data: 'h3', colSpan: 2, rowSpan: 2 }), + new TableHeaderItem({ data: 'h4', colSpan: 2 }), + ], + [ + new TableHeaderItem({ data: 'h11' }), + new TableHeaderItem({ data: 'h12', rowSpan: 2, colSpan: 2 }), + new TableHeaderItem({ data: 'h13', rowSpan: 3 }), + new TableHeaderItem({ data: 'h41', rowSpan: 3 }), + new TableHeaderItem({ data: 'h42' }), + ], + [ + new TableHeaderItem({ data: 'h111' }), + new TableHeaderItem({ data: 'h31', colSpan: 2 }), + new TableHeaderItem({ data: 'h421' }), + ], + [ + new TableHeaderItem({ data: 'h1111' }), + new TableHeaderItem({ data: 'h121' }), + new TableHeaderItem({ data: 'h122' }), + new TableHeaderItem({ data: 'h311' }), + new TableHeaderItem({ data: 'h312' }), + new TableHeaderItem({ data: 'h422' }), + ], + ]); + + tableModel.setData([ + [ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd422' }), + ], + [ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd422' }), + ], + ]); + + tableModel.moveColumn(1, 3, 0); + + expect(tableModel.column(0)).toEqual([ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd1111' }) + ]); + + expect(tableModel.column(1)).toEqual([ + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd121' }) + ]); + + expect(tableModel.column(2)).toEqual([ + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd122' }) + ]); + + expect(tableModel.column(3)).toEqual([ + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd13' }) + ]); + + expect(tableModel.column(4)).toEqual([ + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd311' }) + ]); + + expect(tableModel.column(5)).toEqual([ + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd312' }) + ]); + + expect(tableModel.column(6)).toEqual([ + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd41' }) + ]); + + expect(tableModel.column(7)).toEqual([ + new TableItem({ data: 'd422' }), + new TableItem({ data: 'd422' }) + ]); + + expect(tableModel.column(8)).toEqual([ + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd2' }) + ]); + }); + it('should calculate correct actual index', () => { const header = [ new TableHeaderItem({ data: 'h1' }), @@ -1008,7 +1216,7 @@ describe('Table', () => { ]; const tableModel = new AITableModel(); - tableModel['moveMultipleToIndex']([1, 2, 3], 5, header); + tableModel['moveMultipleToIndex']([1, 2, 3], 4, header); expect(header.map((item) => item.data)).toEqual(['h1', 'h5', 'h2', 'h3', 'h4', 'h6']); }); @@ -1024,7 +1232,7 @@ describe('Table', () => { ]; const tableModel = new AITableModel(); - tableModel['moveMultipleToIndex']([1], 3, header); + tableModel['moveMultipleToIndex']([1], 2, header); expect(header.map((item) => item.data)).toEqual(['h1', 'h3', 'h2', 'h4', 'h5', 'h6']); }); diff --git a/packages/angular/src/table/table-model.class.ts b/packages/angular/src/table/table-model.class.ts index b36d9d9720..b39bba5c8a 100644 --- a/packages/angular/src/table/table-model.class.ts +++ b/packages/angular/src/table/table-model.class.ts @@ -16,6 +16,8 @@ export class AITableModel implements PaginationModel { */ protected static COUNT = 0; + protected static HEADER_COUNT = 0; + dataChange = new Subject(); rowsSelectedChange = new Subject(); rowsExpandedChange = new Subject(); @@ -178,6 +180,8 @@ export class AITableModel implements PaginationModel { }); } + this.setHeaderIds(this.header); + this.dataChange.next(); } @@ -197,6 +201,8 @@ export class AITableModel implements PaginationModel { this.header = newHeader as TableHeaderItem[][]; + this.setHeaderIds(this.header); + this.dataChange.next(); } @@ -707,23 +713,11 @@ export class AITableModel implements PaginationModel { * | f | g | h | j | i | */ moveColumn(indexFrom: number, indexTo: number, rowIndex = 0) { - // ignore everything above rowIndex - // find the "projected indices" of the header column we're moving - const projectedIndices = this.actualIndexToProjectedIndices(indexFrom, this.header[rowIndex]); - // based on those indices, find the "actual indices" of child rows - for (let nextRowIndex = rowIndex; nextRowIndex < this.header.length; nextRowIndex++) { - const actualIndices = this.projectedIndicesToActualIndices( - projectedIndices, - this.header[nextRowIndex] - ); - // move them to the right place (based on the "projected indexTo") - this.moveMultipleToIndex(actualIndices, indexTo, this.header[nextRowIndex]); - } - - // move the data columns as well - for (let dataRowIndex = 0; dataRowIndex < this._data.length; dataRowIndex++) { - this.moveMultipleToIndex(projectedIndices, indexTo, this._data[dataRowIndex]); - } + const nested = this.tabularToNested(this.header, this._data); + this.moveNested(nested, indexFrom, indexTo, rowIndex); + const { header, data } = this.nestedToTabular(nested); + this.header = header; + this._data = data; } /** @@ -996,8 +990,216 @@ export class AITableModel implements PaginationModel { } else { // if moving to right const block = list.slice(blockStart, blockEnd + 1); - list.splice.apply(list, [index, 0].concat(block)); + list.splice.apply(list, [index + 1, 0].concat(block)); list.splice(blockStart, blockEnd - blockStart + 1); } } + + protected setHeaderIds( + header: TableHeaderItem[][], + headerRow: TableHeaderItem[] = [], + availableHeaderItems: TableHeaderItem[][] = [], + rowIndex = 0, + parentId = null + ) { + if (!headerRow.length && rowIndex === 0) { + headerRow = header[0]; + } + + if (!availableHeaderItems.length) { + availableHeaderItems = header.map((headerRow) => headerRow.filter((headerItem) => headerItem !== null)); + } + + return headerRow + .filter((headerItem) => headerItem !== null) + .map((headerItem) => { + const headerId = `header-${AITableModel.HEADER_COUNT++}`; + + const colSpan = headerItem?.colSpan || 1; + const rowSpan = headerItem?.rowSpan || 1; + + if (rowIndex + rowSpan >= this.header.length) { + headerItem.metadata = { + ...headerItem.metadata, + id: headerId, + parentId + } + return headerId; + } + + let spaceLeft = colSpan; + const availableChildren = availableHeaderItems[rowIndex + rowSpan]; + const children = []; + + while (spaceLeft > 0 && availableChildren.length) { + const nextChild = availableChildren.shift(); + spaceLeft -= nextChild?.colSpan || 1; + children.push(nextChild); + } + + headerItem.metadata = { + ...headerItem.metadata, + id: headerId, + parentId, + childIds: this.setHeaderIds(header, children, availableHeaderItems, rowIndex + rowSpan, headerId), + } + + return headerId; + }); + } + + protected getViewIndex(colIndex: number, rowIndex: number, header: TableHeaderItem[][]) { + let id = header[rowIndex][colIndex].metadata.id; + let parentId = header[rowIndex][colIndex].metadata.parentId; + let indexOffset = 0; + + for (let i = rowIndex - 1; i >= 0; i--) { + let parentHeader; + // Find parent header, this loop is to account for row spans. + for (let j = i; j >= 0; j--) { + parentHeader = header[j].find(headerItem => headerItem?.metadata?.id === parentId); + if (parentHeader) { + i = j; + break; + } + } + const parentChildrenIds = parentHeader?.metadata?.childIds; + const children = header[i + (parentHeader?.rowSpan || 1)].filter((headerItem) => parentChildrenIds.includes(headerItem.metadata.id)) + const precedingChildren = children?.slice(0, children.indexOf(children.find(child => child.metadata.id === id))); + + if (precedingChildren?.length) { + indexOffset += precedingChildren.reduce((total, headerItem) => total += headerItem?.colSpan || 1, 0) + } + + id = parentHeader?.metadata.id; + parentId = parentHeader?.metadata?.parentId; + } + + const projectedIndex = this.actualIndexToProjectedIndices( + header[0].findIndex((headerItem) => headerItem?.metadata?.id === id), + header[0] + ); + + return projectedIndex[0] + indexOffset; + } + + protected tabularToNested( + header: TableHeaderItem[][], + data: TableItem[][], + headerRow: TableHeaderItem[] = [], + rowIndex = 0, + relativeIndex = 0 + ) { + if (!headerRow.length && rowIndex === 0) { + headerRow = header[0]; + } + return headerRow + .filter(Boolean) + .map((headerItem, i) => { + const colSpan = headerItem?.colSpan || 1; + const rowSpan = headerItem?.rowSpan || 1; + const childIds = headerItem.metadata.childIds; + + if (rowIndex + rowSpan >= this.header.length) { + const columnIndex = relativeIndex + i; + const viewIndex = this.getViewIndex(columnIndex, rowIndex, header); + + // Table body items directly undernearth this header. + const dataChildren = []; + data.forEach(row => { + dataChildren.push(row.slice(viewIndex, viewIndex + colSpan)); + }); + + return { + headerItem, + index: columnIndex, + children: [], + dataChildren + } + } + + const children = header[rowIndex + rowSpan].filter(headerItem => childIds.includes(headerItem.metadata.id)); + const index = header[rowIndex + rowSpan].indexOf(children[0]); + + return { + headerItem, + children: this.tabularToNested(header, data, children, rowIndex + rowSpan, index), + index: relativeIndex + i, + dataChildren: [] + } + }); + } + + protected nestedToTabular( + nested: any, + header: TableHeaderItem[][] = new Array(this.header.length).fill([]), + data: TableItem[][] = new Array(this._data.length).fill([]), + rowIndex = 0 + ) { + nested.forEach((headerObj) => { + const rowSpan = headerObj.headerItem?.rowSpan || 1; + header[rowIndex] = [...header[rowIndex], headerObj.headerItem]; + + if (headerObj.dataChildren.length) { + for (let i = 0; i < data.length; i++) { + data[i] = [...data[i], ...headerObj.dataChildren[i]] + } + } + + if (rowIndex + rowSpan >= this.header.length) { + return; + } + + const children = headerObj.children; + + this.nestedToTabular(children, header, data, rowIndex + rowSpan); + }); + + return { + header, + data + }; + } + + /** + * Move `nested` element at depth `rowIndex` with index `indexFrom` to `indexTo`. + */ + protected moveNested( + nested: any, + indexFrom: number, + indexTo: number, + rowIndex = 0, + currentLevel = 0 + ) { + if (rowIndex === 0) { + const indexFromElement = nested.find((obj: any) => obj.index === indexFrom); + const indexToElement = nested.find((obj: any) => obj.index === indexTo); + + if (indexFromElement && indexToElement) { + const relativeIndexFrom = nested.indexOf(indexFromElement); + const relativeIndexTo = nested.indexOf(indexToElement); + this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, nested); + } + return; + } + + for (let i = 0; i < nested.length; i++) { + const headerObj = nested[i]; + const rowSpan = headerObj.headerItem?.rowSpan || 1; + + if (currentLevel === rowIndex - rowSpan) { + const indexFromElement = headerObj.children.find((obj: any) => obj.index === indexFrom); + const indexToElement = headerObj.children.find((obj: any) => obj.index === indexTo); + + if (indexFromElement && indexToElement) { + const relativeIndexFrom = headerObj.children.indexOf(indexFromElement); + const relativeIndexTo = headerObj.children.indexOf(indexToElement); + this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, headerObj.children); + return; + } + } else { + this.moveNested(headerObj.children, indexFrom, indexTo, rowIndex, currentLevel + rowSpan); + } + } + } } diff --git a/packages/angular/src/table/table.stories.ts b/packages/angular/src/table/table.stories.ts index 41df03d741..b3780e330c 100644 --- a/packages/angular/src/table/table.stories.ts +++ b/packages/angular/src/table/table.stories.ts @@ -25,7 +25,7 @@ simpleModel.setData([ new TableItem({ data: 'qwer' }), new TableItem({ data: 'qwer1' }), ], - [new TableItem({ data: 'Name 3' }), new TableItem({ data: 'zwer', colSpan: 2 }), null], + [new TableItem({ data: 'Name 3' }), new TableItem({ data: 'zwer', colSpan: 2 })], [ new TableItem({ data: 'Name 2' }), new TableItem({ data: 'swer' }), @@ -38,6 +38,62 @@ simpleModel.setData([ ], ]); +const complexModel = new AITableModel(); + +complexModel.setHeader([ + [ + new TableHeaderItem({ data: 'h1', colSpan: 4 }), + new TableHeaderItem({ data: 'h2', rowSpan: 4 }), + new TableHeaderItem({ data: 'h3', colSpan: 2, rowSpan: 2 }), + new TableHeaderItem({ data: 'h4', colSpan: 2 }), + ], + [ + new TableHeaderItem({ data: 'h11' }), + new TableHeaderItem({ data: 'h12', rowSpan: 2, colSpan: 2 }), + new TableHeaderItem({ data: 'h13', rowSpan: 3 }), + new TableHeaderItem({ data: 'h41', rowSpan: 3 }), + new TableHeaderItem({ data: 'h42' }), + ], + [ + new TableHeaderItem({ data: 'h111' }), + new TableHeaderItem({ data: 'h31', colSpan: 2 }), + new TableHeaderItem({ data: 'h421' }), + ], + [ + new TableHeaderItem({ data: 'h1111' }), + new TableHeaderItem({ data: 'h121' }), + new TableHeaderItem({ data: 'h122' }), + new TableHeaderItem({ data: 'h311' }), + new TableHeaderItem({ data: 'h312' }), + new TableHeaderItem({ data: 'h422' }), + ], +]); + +complexModel.setData([ + [ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd422' }), + ], + [ + new TableItem({ data: 'd1111' }), + new TableItem({ data: 'd121' }), + new TableItem({ data: 'd122' }), + new TableItem({ data: 'd13' }), + new TableItem({ data: 'd2' }), + new TableItem({ data: 'd311' }), + new TableItem({ data: 'd312' }), + new TableItem({ data: 'd41' }), + new TableItem({ data: 'd422' }), + ], +]); + storiesOf('Components/Table', module) .addDecorator( moduleMetadata({ @@ -48,16 +104,16 @@ storiesOf('Components/Table', module) .add('Basic', () => { return { template: ` - - + + `, props: { model: simpleModel, @@ -78,4 +134,37 @@ storiesOf('Components/Table', module) }, }, }; + }) + .add('Complex multiheader table with move columns', () => { + return { + template: ` + +

Moving header index {{indexFrom}} to index {{indexTo}}

+ + + `, + props: { + model: complexModel, + size: select('size', { Small: 'sm', Short: 'sh', Normal: 'md', Large: 'lg' }, 'md'), + showSelectionColumn: boolean('showSelectionColumn', false), + striped: boolean('striped', true), + isDataGrid: boolean('Data grid keyboard interactions', false), + skeleton: boolean('Skeleton mode', false), + rowClick: action('row clicked'), + indexFrom: null, + indexTo: null, + moveRandomColumns: function() { + this.indexFrom = Math.floor(Math.random() * (complexModel['header'][0].length)); + this.indexTo = Math.floor(Math.random() * (complexModel['header'][0].length)); + this.model.moveColumn(this.indexFrom, this.indexTo, 0); + }, + }, + }; }); From c6d665e29554debd122c675d4bfc190e09539d53 Mon Sep 17 00:00:00 2001 From: Donisius Wigie Date: Tue, 23 Nov 2021 00:11:04 -0500 Subject: [PATCH 02/84] refactor(table model): simplify logic in moveColumns --- .../angular/src/table/table-model.class.ts | 188 +++++------------- 1 file changed, 52 insertions(+), 136 deletions(-) diff --git a/packages/angular/src/table/table-model.class.ts b/packages/angular/src/table/table-model.class.ts index b39bba5c8a..4e26227e90 100644 --- a/packages/angular/src/table/table-model.class.ts +++ b/packages/angular/src/table/table-model.class.ts @@ -16,8 +16,6 @@ export class AITableModel implements PaginationModel { */ protected static COUNT = 0; - protected static HEADER_COUNT = 0; - dataChange = new Subject(); rowsSelectedChange = new Subject(); rowsExpandedChange = new Subject(); @@ -180,8 +178,6 @@ export class AITableModel implements PaginationModel { }); } - this.setHeaderIds(this.header); - this.dataChange.next(); } @@ -201,8 +197,6 @@ export class AITableModel implements PaginationModel { this.header = newHeader as TableHeaderItem[][]; - this.setHeaderIds(this.header); - this.dataChange.next(); } @@ -995,12 +989,14 @@ export class AITableModel implements PaginationModel { } } - protected setHeaderIds( + protected tabularToNested( header: TableHeaderItem[][], + data: TableItem[][], headerRow: TableHeaderItem[] = [], availableHeaderItems: TableHeaderItem[][] = [], - rowIndex = 0, - parentId = null + leafIndex = { index: 0 }, // So we can pass by reference + relativeIndex = 0, + rowIndex = 0 ) { if (!headerRow.length && rowIndex === 0) { headerRow = header[0]; @@ -1012,19 +1008,25 @@ export class AITableModel implements PaginationModel { return headerRow .filter((headerItem) => headerItem !== null) - .map((headerItem) => { - const headerId = `header-${AITableModel.HEADER_COUNT++}`; - + .map((headerItem, i) => { const colSpan = headerItem?.colSpan || 1; const rowSpan = headerItem?.rowSpan || 1; if (rowIndex + rowSpan >= this.header.length) { - headerItem.metadata = { - ...headerItem.metadata, - id: headerId, - parentId - } - return headerId; + const dataChildren = []; + data.forEach(row => { + dataChildren.push(row.slice(leafIndex.index, leafIndex.index + colSpan)); + }); + + leafIndex.index += colSpan; + + return { + headerItem, + index: relativeIndex + i, + rowIndex, + dataChildren, + children: [] + }; } let spaceLeft = colSpan; @@ -1037,96 +1039,21 @@ export class AITableModel implements PaginationModel { children.push(nextChild); } - headerItem.metadata = { - ...headerItem.metadata, - id: headerId, - parentId, - childIds: this.setHeaderIds(header, children, availableHeaderItems, rowIndex + rowSpan, headerId), - } - - return headerId; - }); - } - - protected getViewIndex(colIndex: number, rowIndex: number, header: TableHeaderItem[][]) { - let id = header[rowIndex][colIndex].metadata.id; - let parentId = header[rowIndex][colIndex].metadata.parentId; - let indexOffset = 0; - - for (let i = rowIndex - 1; i >= 0; i--) { - let parentHeader; - // Find parent header, this loop is to account for row spans. - for (let j = i; j >= 0; j--) { - parentHeader = header[j].find(headerItem => headerItem?.metadata?.id === parentId); - if (parentHeader) { - i = j; - break; - } - } - const parentChildrenIds = parentHeader?.metadata?.childIds; - const children = header[i + (parentHeader?.rowSpan || 1)].filter((headerItem) => parentChildrenIds.includes(headerItem.metadata.id)) - const precedingChildren = children?.slice(0, children.indexOf(children.find(child => child.metadata.id === id))); - - if (precedingChildren?.length) { - indexOffset += precedingChildren.reduce((total, headerItem) => total += headerItem?.colSpan || 1, 0) - } - - id = parentHeader?.metadata.id; - parentId = parentHeader?.metadata?.parentId; - } - - const projectedIndex = this.actualIndexToProjectedIndices( - header[0].findIndex((headerItem) => headerItem?.metadata?.id === id), - header[0] - ); - - return projectedIndex[0] + indexOffset; - } - - protected tabularToNested( - header: TableHeaderItem[][], - data: TableItem[][], - headerRow: TableHeaderItem[] = [], - rowIndex = 0, - relativeIndex = 0 - ) { - if (!headerRow.length && rowIndex === 0) { - headerRow = header[0]; - } - return headerRow - .filter(Boolean) - .map((headerItem, i) => { - const colSpan = headerItem?.colSpan || 1; - const rowSpan = headerItem?.rowSpan || 1; - const childIds = headerItem.metadata.childIds; - - if (rowIndex + rowSpan >= this.header.length) { - const columnIndex = relativeIndex + i; - const viewIndex = this.getViewIndex(columnIndex, rowIndex, header); - - // Table body items directly undernearth this header. - const dataChildren = []; - data.forEach(row => { - dataChildren.push(row.slice(viewIndex, viewIndex + colSpan)); - }); - - return { - headerItem, - index: columnIndex, - children: [], - dataChildren - } - } - - const children = header[rowIndex + rowSpan].filter(headerItem => childIds.includes(headerItem.metadata.id)); - const index = header[rowIndex + rowSpan].indexOf(children[0]); - return { headerItem, - children: this.tabularToNested(header, data, children, rowIndex + rowSpan, index), index: relativeIndex + i, - dataChildren: [] - } + rowIndex, + dataChildren: [], + children: this.tabularToNested( + header, + data, + children, + availableHeaderItems, + leafIndex, + header[rowIndex + rowSpan].indexOf(children[0]), + rowIndex + rowSpan + ) + }; }); } @@ -1136,7 +1063,7 @@ export class AITableModel implements PaginationModel { data: TableItem[][] = new Array(this._data.length).fill([]), rowIndex = 0 ) { - nested.forEach((headerObj) => { + nested.forEach((headerObj: any) => { const rowSpan = headerObj.headerItem?.rowSpan || 1; header[rowIndex] = [...header[rowIndex], headerObj.headerItem]; @@ -1162,44 +1089,33 @@ export class AITableModel implements PaginationModel { } /** - * Move `nested` element at depth `rowIndex` with index `indexFrom` to `indexTo`. + * Move `nested` element at `rowIndex` with index `indexFrom` to `indexTo`. */ protected moveNested( - nested: any, - indexFrom: number, - indexTo: number, - rowIndex = 0, - currentLevel = 0 - ) { - if (rowIndex === 0) { - const indexFromElement = nested.find((obj: any) => obj.index === indexFrom); - const indexToElement = nested.find((obj: any) => obj.index === indexTo); - - if (indexFromElement && indexToElement) { - const relativeIndexFrom = nested.indexOf(indexFromElement); - const relativeIndexTo = nested.indexOf(indexToElement); - this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, nested); + nested: any, + indexFrom: number, + indexTo: number, + rowIndex = 0 + ) { + if (!nested.length) { + return; } - return; - } - - for (let i = 0; i < nested.length; i++) { - const headerObj = nested[i]; - const rowSpan = headerObj.headerItem?.rowSpan || 1; - if (currentLevel === rowIndex - rowSpan) { - const indexFromElement = headerObj.children.find((obj: any) => obj.index === indexFrom); - const indexToElement = headerObj.children.find((obj: any) => obj.index === indexTo); + const currentRowIndex = nested[0].rowIndex; + if (currentRowIndex === rowIndex) { + const indexFromElement = nested.find((obj: any) => obj.index === indexFrom); + const indexToElement = nested.find((obj: any) => obj.index === indexTo); if (indexFromElement && indexToElement) { - const relativeIndexFrom = headerObj.children.indexOf(indexFromElement); - const relativeIndexTo = headerObj.children.indexOf(indexToElement); - this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, headerObj.children); + const relativeIndexFrom = nested.indexOf(indexFromElement); + const relativeIndexTo = nested.indexOf(indexToElement); + this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, nested); return; } - } else { - this.moveNested(headerObj.children, indexFrom, indexTo, rowIndex, currentLevel + rowSpan); } + + nested.forEach((headerObj: any) => { + this.moveNested(headerObj.children, indexFrom, indexTo, rowIndex); + }); } - } } From 3e6509cb9fc4c65cce5481d04763f17388171b4a Mon Sep 17 00:00:00 2001 From: Donisius Wigie Date: Tue, 23 Nov 2021 00:14:01 -0500 Subject: [PATCH 03/84] chore: format --- .../src/table/table-model.class.spec.ts | 36 ++++++------ .../angular/src/table/table-model.class.ts | 55 +++++++++---------- packages/angular/src/table/table.stories.ts | 6 +- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/packages/angular/src/table/table-model.class.spec.ts b/packages/angular/src/table/table-model.class.spec.ts index 70c99a2cec..3b257fe0e1 100644 --- a/packages/angular/src/table/table-model.class.spec.ts +++ b/packages/angular/src/table/table-model.class.spec.ts @@ -959,47 +959,47 @@ describe('Table', () => { expect(tableModel.column(0)).toEqual([ new TableItem({ data: 'd311' }), - new TableItem({ data: 'd311' }) + new TableItem({ data: 'd311' }), ]); expect(tableModel.column(1)).toEqual([ new TableItem({ data: 'd312' }), - new TableItem({ data: 'd312' }) + new TableItem({ data: 'd312' }), ]); expect(tableModel.column(2)).toEqual([ new TableItem({ data: 'd1111' }), - new TableItem({ data: 'd1111' }) + new TableItem({ data: 'd1111' }), ]); expect(tableModel.column(3)).toEqual([ new TableItem({ data: 'd121' }), - new TableItem({ data: 'd121' }) + new TableItem({ data: 'd121' }), ]); expect(tableModel.column(4)).toEqual([ new TableItem({ data: 'd122' }), - new TableItem({ data: 'd122' }) + new TableItem({ data: 'd122' }), ]); expect(tableModel.column(5)).toEqual([ new TableItem({ data: 'd13' }), - new TableItem({ data: 'd13' }) + new TableItem({ data: 'd13' }), ]); expect(tableModel.column(6)).toEqual([ new TableItem({ data: 'd2' }), - new TableItem({ data: 'd2' }) + new TableItem({ data: 'd2' }), ]); expect(tableModel.column(7)).toEqual([ new TableItem({ data: 'd41' }), - new TableItem({ data: 'd41' }) + new TableItem({ data: 'd41' }), ]); expect(tableModel.column(8)).toEqual([ new TableItem({ data: 'd422' }), - new TableItem({ data: 'd422' }) + new TableItem({ data: 'd422' }), ]); }); @@ -1063,47 +1063,47 @@ describe('Table', () => { expect(tableModel.column(0)).toEqual([ new TableItem({ data: 'd1111' }), - new TableItem({ data: 'd1111' }) + new TableItem({ data: 'd1111' }), ]); expect(tableModel.column(1)).toEqual([ new TableItem({ data: 'd121' }), - new TableItem({ data: 'd121' }) + new TableItem({ data: 'd121' }), ]); expect(tableModel.column(2)).toEqual([ new TableItem({ data: 'd122' }), - new TableItem({ data: 'd122' }) + new TableItem({ data: 'd122' }), ]); expect(tableModel.column(3)).toEqual([ new TableItem({ data: 'd13' }), - new TableItem({ data: 'd13' }) + new TableItem({ data: 'd13' }), ]); expect(tableModel.column(4)).toEqual([ new TableItem({ data: 'd311' }), - new TableItem({ data: 'd311' }) + new TableItem({ data: 'd311' }), ]); expect(tableModel.column(5)).toEqual([ new TableItem({ data: 'd312' }), - new TableItem({ data: 'd312' }) + new TableItem({ data: 'd312' }), ]); expect(tableModel.column(6)).toEqual([ new TableItem({ data: 'd41' }), - new TableItem({ data: 'd41' }) + new TableItem({ data: 'd41' }), ]); expect(tableModel.column(7)).toEqual([ new TableItem({ data: 'd422' }), - new TableItem({ data: 'd422' }) + new TableItem({ data: 'd422' }), ]); expect(tableModel.column(8)).toEqual([ new TableItem({ data: 'd2' }), - new TableItem({ data: 'd2' }) + new TableItem({ data: 'd2' }), ]); }); diff --git a/packages/angular/src/table/table-model.class.ts b/packages/angular/src/table/table-model.class.ts index 4e26227e90..945b6fe826 100644 --- a/packages/angular/src/table/table-model.class.ts +++ b/packages/angular/src/table/table-model.class.ts @@ -1003,7 +1003,9 @@ export class AITableModel implements PaginationModel { } if (!availableHeaderItems.length) { - availableHeaderItems = header.map((headerRow) => headerRow.filter((headerItem) => headerItem !== null)); + availableHeaderItems = header.map((headerRow) => + headerRow.filter((headerItem) => headerItem !== null) + ); } return headerRow @@ -1014,7 +1016,7 @@ export class AITableModel implements PaginationModel { if (rowIndex + rowSpan >= this.header.length) { const dataChildren = []; - data.forEach(row => { + data.forEach((row) => { dataChildren.push(row.slice(leafIndex.index, leafIndex.index + colSpan)); }); @@ -1025,7 +1027,7 @@ export class AITableModel implements PaginationModel { index: relativeIndex + i, rowIndex, dataChildren, - children: [] + children: [], }; } @@ -1052,7 +1054,7 @@ export class AITableModel implements PaginationModel { leafIndex, header[rowIndex + rowSpan].indexOf(children[0]), rowIndex + rowSpan - ) + ), }; }); } @@ -1069,7 +1071,7 @@ export class AITableModel implements PaginationModel { if (headerObj.dataChildren.length) { for (let i = 0; i < data.length; i++) { - data[i] = [...data[i], ...headerObj.dataChildren[i]] + data[i] = [...data[i], ...headerObj.dataChildren[i]]; } } @@ -1084,38 +1086,33 @@ export class AITableModel implements PaginationModel { return { header, - data + data, }; } /** * Move `nested` element at `rowIndex` with index `indexFrom` to `indexTo`. */ - protected moveNested( - nested: any, - indexFrom: number, - indexTo: number, - rowIndex = 0 - ) { - if (!nested.length) { - return; - } + protected moveNested(nested: any, indexFrom: number, indexTo: number, rowIndex = 0) { + if (!nested.length) { + return; + } - const currentRowIndex = nested[0].rowIndex; - if (currentRowIndex === rowIndex) { - const indexFromElement = nested.find((obj: any) => obj.index === indexFrom); - const indexToElement = nested.find((obj: any) => obj.index === indexTo); + const currentRowIndex = nested[0].rowIndex; + if (currentRowIndex === rowIndex) { + const indexFromElement = nested.find((obj: any) => obj.index === indexFrom); + const indexToElement = nested.find((obj: any) => obj.index === indexTo); - if (indexFromElement && indexToElement) { - const relativeIndexFrom = nested.indexOf(indexFromElement); - const relativeIndexTo = nested.indexOf(indexToElement); - this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, nested); - return; - } + if (indexFromElement && indexToElement) { + const relativeIndexFrom = nested.indexOf(indexFromElement); + const relativeIndexTo = nested.indexOf(indexToElement); + this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, nested); + return; } - - nested.forEach((headerObj: any) => { - this.moveNested(headerObj.children, indexFrom, indexTo, rowIndex); - }); } + + nested.forEach((headerObj: any) => { + this.moveNested(headerObj.children, indexFrom, indexTo, rowIndex); + }); + } } diff --git a/packages/angular/src/table/table.stories.ts b/packages/angular/src/table/table.stories.ts index b3780e330c..42bdfa5882 100644 --- a/packages/angular/src/table/table.stories.ts +++ b/packages/angular/src/table/table.stories.ts @@ -160,9 +160,9 @@ storiesOf('Components/Table', module) rowClick: action('row clicked'), indexFrom: null, indexTo: null, - moveRandomColumns: function() { - this.indexFrom = Math.floor(Math.random() * (complexModel['header'][0].length)); - this.indexTo = Math.floor(Math.random() * (complexModel['header'][0].length)); + moveRandomColumns: function () { + this.indexFrom = Math.floor(Math.random() * complexModel['header'][0].length); + this.indexTo = Math.floor(Math.random() * complexModel['header'][0].length); this.model.moveColumn(this.indexFrom, this.indexTo, 0); }, }, From 753562f3b4375887bfc8cf1c8355ad37da7b7c5d Mon Sep 17 00:00:00 2001 From: Donisius Wigie Date: Mon, 29 Nov 2021 18:54:18 -0500 Subject: [PATCH 04/84] refactor(table): apply suggested changes --- .../angular/src/table/table-model.class.ts | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/packages/angular/src/table/table-model.class.ts b/packages/angular/src/table/table-model.class.ts index 945b6fe826..e1384536e2 100644 --- a/packages/angular/src/table/table-model.class.ts +++ b/packages/angular/src/table/table-model.class.ts @@ -707,7 +707,7 @@ export class AITableModel implements PaginationModel { * | f | g | h | j | i | */ moveColumn(indexFrom: number, indexTo: number, rowIndex = 0) { - const nested = this.tabularToNested(this.header, this._data); + const nested = this.tabularToNested(); this.moveNested(nested, indexFrom, indexTo, rowIndex); const { header, data } = this.nestedToTabular(nested); this.header = header; @@ -990,20 +990,20 @@ export class AITableModel implements PaginationModel { } protected tabularToNested( - header: TableHeaderItem[][], - data: TableItem[][], headerRow: TableHeaderItem[] = [], availableHeaderItems: TableHeaderItem[][] = [], - leafIndex = { index: 0 }, // So we can pass by reference + // This allows us to walk the leaves as if they were in a list from left to right. + // We need to pass by reference so that we can update this value from within the recursion. + leafIndexRef = { current: 0 }, relativeIndex = 0, rowIndex = 0 ) { if (!headerRow.length && rowIndex === 0) { - headerRow = header[0]; + headerRow = this.header[0]; } if (!availableHeaderItems.length) { - availableHeaderItems = header.map((headerRow) => + availableHeaderItems = this.header.map((headerRow) => headerRow.filter((headerItem) => headerItem !== null) ); } @@ -1014,19 +1014,16 @@ export class AITableModel implements PaginationModel { const colSpan = headerItem?.colSpan || 1; const rowSpan = headerItem?.rowSpan || 1; + // Leaf if (rowIndex + rowSpan >= this.header.length) { - const dataChildren = []; - data.forEach((row) => { - dataChildren.push(row.slice(leafIndex.index, leafIndex.index + colSpan)); - }); - - leafIndex.index += colSpan; + const leafIndex = leafIndexRef.current; + leafIndexRef.current += colSpan; return { headerItem, - index: relativeIndex + i, + tabularIndex: relativeIndex + i, + leafIndex, rowIndex, - dataChildren, children: [], }; } @@ -1043,16 +1040,14 @@ export class AITableModel implements PaginationModel { return { headerItem, - index: relativeIndex + i, + tabularIndex: relativeIndex + i, + leafIndex: -1, rowIndex, - dataChildren: [], children: this.tabularToNested( - header, - data, children, availableHeaderItems, - leafIndex, - header[rowIndex + rowSpan].indexOf(children[0]), + leafIndexRef, + this.header[rowIndex + rowSpan].indexOf(children[0]), rowIndex + rowSpan ), }; @@ -1067,11 +1062,16 @@ export class AITableModel implements PaginationModel { ) { nested.forEach((headerObj: any) => { const rowSpan = headerObj.headerItem?.rowSpan || 1; + const colSpan = headerObj.headerItem?.colSpan || 1; + header[rowIndex] = [...header[rowIndex], headerObj.headerItem]; - if (headerObj.dataChildren.length) { + if (headerObj.leafIndex >= 0) { for (let i = 0; i < data.length; i++) { - data[i] = [...data[i], ...headerObj.dataChildren[i]]; + data[i] = [ + ...data[i], + ...this._data[i].slice(headerObj.leafIndex, headerObj.leafIndex + colSpan) + ]; } } @@ -1080,7 +1080,6 @@ export class AITableModel implements PaginationModel { } const children = headerObj.children; - this.nestedToTabular(children, header, data, rowIndex + rowSpan); }); @@ -1100,8 +1099,8 @@ export class AITableModel implements PaginationModel { const currentRowIndex = nested[0].rowIndex; if (currentRowIndex === rowIndex) { - const indexFromElement = nested.find((obj: any) => obj.index === indexFrom); - const indexToElement = nested.find((obj: any) => obj.index === indexTo); + const indexFromElement = nested.find((obj: any) => obj.tabularIndex === indexFrom); + const indexToElement = nested.find((obj: any) => obj.tabularIndex === indexTo); if (indexFromElement && indexToElement) { const relativeIndexFrom = nested.indexOf(indexFromElement); From ae4fd4e9b226fdb2d5d9772d661433b2ea297fe9 Mon Sep 17 00:00:00 2001 From: Donisius Wigie Date: Tue, 30 Nov 2021 21:26:50 -0500 Subject: [PATCH 05/84] refactor(table): simplify moveNested --- .../angular/src/table/table-model.class.ts | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/angular/src/table/table-model.class.ts b/packages/angular/src/table/table-model.class.ts index e1384536e2..84cc71c0dc 100644 --- a/packages/angular/src/table/table-model.class.ts +++ b/packages/angular/src/table/table-model.class.ts @@ -995,7 +995,6 @@ export class AITableModel implements PaginationModel { // This allows us to walk the leaves as if they were in a list from left to right. // We need to pass by reference so that we can update this value from within the recursion. leafIndexRef = { current: 0 }, - relativeIndex = 0, rowIndex = 0 ) { if (!headerRow.length && rowIndex === 0) { @@ -1021,7 +1020,6 @@ export class AITableModel implements PaginationModel { return { headerItem, - tabularIndex: relativeIndex + i, leafIndex, rowIndex, children: [], @@ -1040,14 +1038,12 @@ export class AITableModel implements PaginationModel { return { headerItem, - tabularIndex: relativeIndex + i, leafIndex: -1, rowIndex, children: this.tabularToNested( children, availableHeaderItems, leafIndexRef, - this.header[rowIndex + rowSpan].indexOf(children[0]), rowIndex + rowSpan ), }; @@ -1070,7 +1066,7 @@ export class AITableModel implements PaginationModel { for (let i = 0; i < data.length; i++) { data[i] = [ ...data[i], - ...this._data[i].slice(headerObj.leafIndex, headerObj.leafIndex + colSpan) + ...this._data[i].slice(headerObj.leafIndex, headerObj.leafIndex + colSpan), ]; } } @@ -1092,26 +1088,43 @@ export class AITableModel implements PaginationModel { /** * Move `nested` element at `rowIndex` with index `indexFrom` to `indexTo`. */ - protected moveNested(nested: any, indexFrom: number, indexTo: number, rowIndex = 0) { + protected moveNested( + nested: any, + indexFrom: number, + indexTo: number, + rowIndex = 0, + startingChildIndex = 0 + ) { if (!nested.length) { return; } const currentRowIndex = nested[0].rowIndex; - if (currentRowIndex === rowIndex) { - const indexFromElement = nested.find((obj: any) => obj.tabularIndex === indexFrom); - const indexToElement = nested.find((obj: any) => obj.tabularIndex === indexTo); - - if (indexFromElement && indexToElement) { - const relativeIndexFrom = nested.indexOf(indexFromElement); - const relativeIndexTo = nested.indexOf(indexToElement); - this.moveMultipleToIndex([relativeIndexFrom], relativeIndexTo, nested); - return; - } + if ( + currentRowIndex === rowIndex && + startingChildIndex <= indexFrom && + startingChildIndex + nested.length >= indexFrom && + startingChildIndex <= indexTo && + startingChildIndex + nested.length >= indexTo + ) { + this.moveMultipleToIndex( + [indexFrom - startingChildIndex], + indexTo - startingChildIndex, + nested + ); + return; } - nested.forEach((headerObj: any) => { - this.moveNested(headerObj.children, indexFrom, indexTo, rowIndex); + nested.forEach((headerObj: any, i: number) => { + const rowSpan = headerObj.headerItem?.rowSpan || 1; + const children = headerObj.children; + this.moveNested( + children, + indexFrom, + indexTo, + rowIndex, + this.header[currentRowIndex + rowSpan]?.indexOf(children[0]?.headerItem) + ); }); } } From ad1b767c00920d043a90c864dce10d7f88a3cb4e Mon Sep 17 00:00:00 2001 From: Kevin Perrine Date: Tue, 18 Jan 2022 13:07:31 -0500 Subject: [PATCH 06/84] refactor: sync DateTimePicker v1 and v2 and re-use common code --- packages/react/package.json | 12 +- .../DateTimePicker/DateTimePicker.jsx | 373 +--- .../DateTimePicker.test.e2e.jsx | 98 +- .../DateTimePicker/DateTimePickerV2.jsx | 447 +++-- .../DateTimePickerV2.test.e2e.jsx | 240 ++- .../DateTimePicker/DateTimePickerV2.test.jsx | 195 +- .../DateTimePicker/_date-time-pickerv2.scss | 11 + .../DateTimePicker/dateTimePickerUtils.js | 390 ++++ yarn.lock | 1662 +++++++++++------ 9 files changed, 2248 insertions(+), 1180 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index ca590368cd..f5951f496d 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -236,10 +236,10 @@ "@babel/preset-react": "^7.13.13", "@commitlint/cli": "^7.2.1", "@commitlint/config-conventional": "^7.1.2", - "@cypress/code-coverage": "3.9.11", - "@cypress/react": "5.10.3", + "@cypress/code-coverage": "3.9.12", + "@cypress/react": "5.12.1", "@cypress/skip-test": "^2.6.1", - "@cypress/webpack-dev-server": "1.7.0", + "@cypress/webpack-dev-server": "1.8.0", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-json": "^4.1.0", @@ -277,10 +277,10 @@ "coveralls": "^3.0.2", "cross-env": "^6.0.3", "css-loader": "^3.4.2", - "cypress": "9.1.0", + "cypress": "9.2.1", "cypress-file-upload": "^5.0.8", - "cypress-image-diff-js": "1.15.7", - "cypress-real-events": "1.5.1", + "cypress-image-diff-js": "1.16.1", + "cypress-real-events": "1.6.0", "d3": "7.1.1", "deepdash": "^5.1.2", "enzyme": "^3.8.0", diff --git a/packages/react/src/components/DateTimePicker/DateTimePicker.jsx b/packages/react/src/components/DateTimePicker/DateTimePicker.jsx index 3dd32fd0f3..48bcefd3d1 100644 --- a/packages/react/src/components/DateTimePicker/DateTimePicker.jsx +++ b/packages/react/src/components/DateTimePicker/DateTimePicker.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState, useCallback } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { Button, @@ -24,7 +24,20 @@ import dayjs from '../../utils/dayjs'; import { handleSpecificKeyDown } from '../../utils/componentUtilityFunctions'; import { Tooltip } from '../Tooltip'; -import { parseValue } from './dateTimePickerUtils'; +import { + getIntervalValue, + invalidEndDate, + invalidStartDate, + onDatePickerClose, + parseValue, + useAbsoluteDateTimeValue, + useDateTimePickerFocus, + useDateTimePickerKeyboardInteraction, + useDateTimePickerRangeKind, + useDateTimePickerRef, + useDateTimePickerTooltip, + useRelativeDateTimeValue, +} from './dateTimePickerUtils'; const { iotPrefix } = settings; @@ -101,9 +114,9 @@ const propTypes = { timeRangeValue: PropTypes.exact({ startDate: PropTypes.string.isRequired, startTime: PropTypes.string.isRequired, - /** Can be a full parseable DateTime string or a Date object */ + /** Can be a full parsable DateTime string or a Date object */ start: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), - /** Can be a full parseable DateTime string or a Date object */ + /** Can be a full parsable DateTime string or a Date object */ end: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), endDate: PropTypes.string.isRequired, endTime: PropTypes.string.isRequired, @@ -303,29 +316,52 @@ const DateTimePicker = ({ }, [locale]); // State - const [isExpanded, setIsExpanded] = useState(expanded); - const [customRangeKind, setCustomRangeKind] = useState( - showRelativeOption ? PICKER_KINDS.RELATIVE : PICKER_KINDS.ABSOLUTE + const [customRangeKind, setCustomRangeKind, onCustomRangeChange] = useDateTimePickerRangeKind( + showRelativeOption ); + const { + isExpanded, + setIsExpanded, + presetListRef, + onFieldInteraction, + onNavigateRadioButton, + onNavigatePresets, + } = useDateTimePickerKeyboardInteraction({ expanded, setCustomRangeKind }); const [isCustomRange, setIsCustomRange] = useState(false); const [selectedPreset, setSelectedPreset] = useState(null); const [currentValue, setCurrentValue] = useState(null); const [lastAppliedValue, setLastAppliedValue] = useState(null); const [humanValue, setHumanValue] = useState(null); - const [relativeValue, setRelativeValue] = useState(null); - const [absoluteValue, setAbsoluteValue] = useState(null); - const [focusOnFirstField, setFocusOnFirstField] = useState(true); - const [isTooltipOpen, setIsTooltipOpen] = useState(false); - const [relativeLastNumberInvalid, setRelativeLastNumberInvalid] = useState(false); - const [relativeToTimeInvalid, setRelativeToTimeInvalid] = useState(false); - const [absoluteStartTimeInvalid, setAbsoluteStartTimeInvalid] = useState(false); - const [absoluteEndTimeInvalid, setAbsoluteEndTimeInvalid] = useState(false); - // Refs - const [datePickerElem, setDatePickerElem] = useState(null); const relativeSelect = useRef(null); - const presetListRef = useRef(null); - const previousActiveElement = useRef(null); + const [datePickerElem, pickerRefCallback] = useDateTimePickerRef({ id }); + const [focusOnFirstField, setFocusOnFirstField] = useDateTimePickerFocus(datePickerElem); + const { + absoluteValue, + setAbsoluteValue, + absoluteStartTimeInvalid, + setAbsoluteStartTimeInvalid, + absoluteEndTimeInvalid, + setAbsoluteEndTimeInvalid, + onAbsoluteStartTimeChange, + onAbsoluteEndTimeChange, + resetAbsoluteValue, + } = useAbsoluteDateTimeValue(); + const { + relativeValue, + setRelativeValue, + relativeToTimeInvalid, + resetRelativeValue, + relativeLastNumberInvalid, + onRelativeLastNumberChange, + onRelativeLastIntervalChange, + onRelativeToWhenChange, + onRelativeToTimeChange, + } = useRelativeDateTimeValue({ + defaultInterval: intervals[0].value, + defaultRelativeTo: relatives[0].value, + }); + const [isTooltipOpen, toggleTooltip] = useDateTimePickerTooltip({ isExpanded }); const dateTimePickerBaseValue = { kind: '', @@ -348,47 +384,6 @@ const DateTimePicker = ({ }, }; - /** - * A callback ref to capture the DateTime node. When a user changes from Relative to Absolute - * the calendar would capture focus and move the users position adding confusion to where they - * are on the page. This also checks if they're currently focused on the Absolute radio button - * and captures it so focus can be restored after the calendar has been reparented below. - */ - const handleDatePickerRef = useCallback((node) => { - if (document.activeElement?.getAttribute('value') === PICKER_KINDS.ABSOLUTE) { - previousActiveElement.current = document.activeElement; - } - - setDatePickerElem(node); - }, []); - - useEffect(() => { - const timeout = setTimeout(() => { - if (datePickerElem) { - datePickerElem.cal.open(); - // while waiting for https://github.com/carbon-design-system/carbon/issues/5713 - // the only way to display the calendar inline is to reparent its DOM to our component - const wrapper = document.getElementById(`${id}-${iotPrefix}--date-time-picker__wrapper`); - if (typeof wrapper !== 'undefined' && wrapper !== null) { - const dp = document - .getElementById(`${id}-${iotPrefix}--date-time-picker__wrapper`) - .getElementsByClassName(`${iotPrefix}--date-time-picker__datepicker`)[0]; - dp.appendChild(datePickerElem.cal.calendarContainer); - } - - // if we were focused on the Absolute radio button previously, restore focus to it. - /* istanbul ignore if */ - if (previousActiveElement.current) { - previousActiveElement.current.focus(); - previousActiveElement.current = null; - } - } - }, 0); - return () => { - clearTimeout(timeout); - }; - }, [datePickerElem, id]); - /** * Transforms a default or selected value into a full blown returnable object * @param {Object} [preset] clicked preset @@ -440,148 +435,6 @@ const DateTimePicker = ({ [absoluteValue, relativeValue] ); - const getFocusableSiblings = () => { - /* istanbul ignore else */ - if (presetListRef?.current) { - const siblings = presetListRef.current.querySelectorAll('[tabindex]'); - return Array.from(siblings).filter( - (sibling) => parseInt(sibling.getAttribute('tabindex'), 10) !== -1 - ); - } - - return []; - }; - - const onFieldInteraction = ({ key }) => { - switch (key) { - case 'Escape': - setIsExpanded(false); - break; - // if the input box is focused and a down arrow is pressed this - // moves focus to the first item in the preset list that has a tabindex - case 'ArrowDown': - /* istanbul ignore else */ - if (presetListRef?.current) { - const listItems = getFocusableSiblings(); - /* istanbul ignore else */ - if (listItems?.[0]?.focus) { - listItems[0].focus(); - } - } - break; - default: - setIsExpanded(!isExpanded); - break; - } - }; - - /** - * Moves up the preset list to the previous focusable element or wraps around to the bottom - * if already at the top. - */ - const moveToPreviousElement = () => { - const siblings = getFocusableSiblings(); - const index = siblings.findIndex((elem) => elem === document.activeElement); - const previous = siblings[index - 1]; - if (previous) { - previous.focus(); - } else { - siblings[siblings.length - 1].focus(); - } - }; - - /** - * Moves down the preset list to the next focusable element or wraps around to the top - * if already at the bottom - */ - const moveToNextElement = () => { - const siblings = getFocusableSiblings(); - const index = siblings.findIndex((elem) => elem === document.activeElement); - const next = siblings[index + 1]; - if (next) { - next.focus(); - } else { - siblings[0].focus(); - } - }; - - const onNavigatePresets = ({ key }) => { - switch (key) { - case 'ArrowUp': - moveToPreviousElement(); - break; - case 'ArrowDown': - moveToNextElement(); - break; - default: - break; - } - }; - - /** - * Allows navigation back and forth between the radio buttons for Relative/Absolute - * - * @param {KeyboardEvent} e - */ - const onNavigateRadioButton = (e) => { - if (e.target.getAttribute('id').includes('absolute')) { - setCustomRangeKind(PICKER_KINDS.RELATIVE); - document.activeElement.parentNode.previousSibling - .querySelector('input[type="radio"]') - .focus(); - } else { - setCustomRangeKind(PICKER_KINDS.ABSOLUTE); - document.activeElement.parentNode.nextSibling.querySelector('input[type="radio"]').focus(); - } - }; - - useEffect(() => { - if (datePickerElem && datePickerElem.inputField && datePickerElem.toInputField) { - if (focusOnFirstField) { - datePickerElem.inputField.click(); - } else { - datePickerElem.toInputField.click(); - } - } - }, [datePickerElem, focusOnFirstField]); - - const isValidDate = (date, time) => { - const isValid24HoursRegex = /^([01][0-9]|2[0-3]):([0-5][0-9])$/; - return date instanceof Date && !Number.isNaN(date) && isValid24HoursRegex.test(time); - }; - - // Validates absolute start date - const invalidStartDate = (startTime, endTime, absoluteValues) => { - // If start and end date have been selected - if ( - absoluteValues.hasOwnProperty('start') && - absoluteValues.hasOwnProperty('end') && - isValidDate(new Date(absoluteValues.start), startTime) - ) { - const startDate = new Date(`${absoluteValues.startDate} ${startTime}`); - const endDate = new Date(`${absoluteValues.endDate} ${endTime}`); - return startDate >= endDate; - } - // Return invalid date if start time and end date not selected or if inputted time is not valid - return true; - }; - - // Validates absolute end date - const invalidEndDate = (startTime, endTime, absoluteValues) => { - // If start and end date have been selected - if ( - absoluteValues.hasOwnProperty('start') && - absoluteValues.hasOwnProperty('end') && - isValidDate(new Date(absoluteValues.end), endTime) - ) { - const startDate = new Date(`${absoluteValues.startDate} ${startTime}`); - const endDate = new Date(`${absoluteValues.endDate} ${endTime}`); - return startDate >= endDate; - } - // Return invalid date if start time and end date not selected or if inputted time is not valid - return true; - }; - const onDatePickerChange = ([start, end], _, flatpickr) => { const calendarInFocus = document.activeElement.closest( `.${iotPrefix}--date-time-picker__datepicker` @@ -639,41 +492,11 @@ const DateTimePicker = ({ ); }; - const onDatePickerClose = (range, single, flatpickr) => { - // force it to stay open - /* istanbul ignore else */ - if (flatpickr) { - flatpickr.open(); - } - }; - - const onCustomRangeChange = (kind) => { - setCustomRangeKind(kind); - }; - const onPresetClick = (preset) => { setSelectedPreset(preset.id ?? preset.offset); renderValue(preset); }; - const resetRelativeValue = () => { - setRelativeValue({ - lastNumber: 0, - lastInterval: intervals[0].value, - relativeToWhen: relatives[0].value, - relativeToTime: '', - }); - }; - - const resetAbsoluteValue = () => { - setAbsoluteValue({ - startDate: '', - startTime: '00:00', - endDate: '', - endTime: '00:00', - }); - }; - const parseDefaultValue = (parsableValue) => { const currentCustomRangeKind = showRelativeOption ? PICKER_KINDS.RELATIVE @@ -784,77 +607,9 @@ const DateTimePicker = ({ } }; - /** - * Get an alternative human readable value for a preset to show in tooltips and dropdown - * ie. 'Last 30 minutes' displays '2020-04-01 11:30 to Now' on the tooltip - * @returns {string} an interval string, starting point in time to now - */ - const getIntervalValue = () => { - if (currentValue) { - if (currentValue.kind === PICKER_KINDS.PRESET) { - return `${dayjs().subtract(currentValue.preset.offset, 'minutes').format(dateTimeMask)} ${ - strings.toNowLabel - }`; - } - return humanValue; - } - return ''; - }; - - // Util func to update the relative value - const changeRelativePropertyValue = (property, value) => { - const newRelative = { ...relativeValue }; - newRelative[property] = value; - setRelativeValue(newRelative); - }; - - // on change functions that trigger a relative value update - const onRelativeLastNumberChange = (event) => { - const valid = !event.imaginaryTarget.getAttribute('data-invalid'); - setRelativeLastNumberInvalid(!valid); - if (valid) { - changeRelativePropertyValue('lastNumber', Number(event.imaginaryTarget.value)); - } - }; - const onRelativeLastIntervalChange = (event) => { - changeRelativePropertyValue('lastInterval', event.currentTarget.value); - }; - const onRelativeToWhenChange = (event) => { - changeRelativePropertyValue('relativeToWhen', event.currentTarget.value); - }; - const onRelativeToTimeChange = (pickerValue, evt, meta) => { - setRelativeToTimeInvalid(meta.invalid); - changeRelativePropertyValue('relativeToTime', pickerValue); - }; - - // Util func to update the absolute value - const changeAbsolutePropertyValue = (property, value) => { - const newAbsolute = { ...absoluteValue }; - newAbsolute[property] = value; - setAbsoluteValue(newAbsolute); - }; - - // on change functions that trigger a absolute value update - const onAbsoluteStartTimeChange = (pickerValue, evt, meta) => { - setAbsoluteStartTimeInvalid( - meta.invalid || invalidStartDate(pickerValue, absoluteValue.endTime, absoluteValue) - ); - setAbsoluteEndTimeInvalid(invalidEndDate(pickerValue, absoluteValue.endTime, absoluteValue)); - changeAbsolutePropertyValue('startTime', pickerValue); - }; - const onAbsoluteEndTimeChange = (pickerValue, evt, meta) => { - setAbsoluteEndTimeInvalid( - meta.invalid || invalidEndDate(absoluteValue.startTime, pickerValue, absoluteValue) - ); - setAbsoluteStartTimeInvalid( - invalidStartDate(absoluteValue.startTime, pickerValue, absoluteValue) - ); - changeAbsolutePropertyValue('endTime', pickerValue); - }; - const tooltipValue = renderPresetTooltipText ? renderPresetTooltipText(currentValue) - : getIntervalValue(); + : getIntervalValue({ currentValue, strings, dateTimeMask, humanValue }); const disableRelativeApply = isCustomRange && @@ -868,18 +623,6 @@ const DateTimePicker = ({ const disableApply = disableRelativeApply || disableAbsoluteApply; - /** - * Shows and hides the tooltip with the humanValue (Relative) or full-range (Absolute) when - * the user focuses or hovers on the input - */ - const toggleTooltip = () => { - if (isExpanded) { - setIsTooltipOpen(false); - } else { - setIsTooltipOpen((prev) => !prev); - } - }; - return ( // Escape handler added to allow pressing escape to close the picker from any via event bubbling // eslint-disable-next-line jsx-a11y/no-static-element-interactions @@ -911,7 +654,7 @@ const DateTimePicker = ({ {humanValue} ) : humanValue ? ( { + beforeEach(() => { + cy.viewport(1680, 900); + }); it('should pick a new absolute ranges', () => { const onApply = cy.stub(); const onCancel = cy.stub(); @@ -95,6 +98,97 @@ describe('DateTimePicker', () => { cy.findByText(i18n.applyBtnLabel).should('not.be.disabled'); }); + it('should disable apply button when relative TimePickerSpinner input is invalid ', () => { + const { i18n } = DateTimePicker.defaultProps; + const onApply = cy.stub(); + const onCancel = cy.stub(); + mount(); + + cy.findAllByLabelText('Calendar').eq(0).click(); + cy.findByText('Custom Range').click(); + + cy.findByPlaceholderText('hh:mm').type('91:35'); + + cy.findByText(i18n.applyBtnLabel).should('be.disabled'); + + cy.findByPlaceholderText('hh:mm').type( + '{backspace}{backspace}{backspace}{backspace}{backspace}11:35' + ); + cy.findByText(i18n.applyBtnLabel).should('not.be.disabled'); + }); + + it('should not parse when relativeToWhen is empty', () => { + const onApply = cy.stub(); + const onCancel = cy.stub(); + mount( + + ); + + cy.findAllByLabelText('Calendar').eq(0).click(); + cy.findByPlaceholderText('hh:mm').should('be.visible').type('12:04'); + cy.findByRole('button', { name: 'Apply' }) + .click() + .should(() => { + expect(onApply).to.have.been.calledWith({ + timeRangeKind: PICKER_KINDS.RELATIVE, + timeRangeValue: { + relativeToWhen: '', + relativeToTime: '12:04', + }, + }); + }); + cy.findByTestId('date-time-picker__field').should('contain.text', ''); + }); + + it('should parse time after 5 characters have been typed', () => { + const onApply = cy.stub(); + const onCancel = cy.stub(); + mount( + + ); + + cy.findAllByLabelText('Calendar').eq(0).click(); + cy.findByLabelText('Increment number').click(); + cy.findByPlaceholderText('hh:mm').should('be.visible').type('12:04'); + cy.findByRole('button', { name: 'Apply' }) + .click() + .should(() => { + expect(onApply).to.have.been.calledWith({ + timeRangeKind: PICKER_KINDS.RELATIVE, + timeRangeValue: { + lastNumber: 1, + relativeToWhen: INTERVAL_VALUES.MINUTES, + relativeToTime: '12:04', + end: Cypress.sinon.match.any, + start: Cypress.sinon.match.any, + }, + }); + }); + }); + it('should be able to navigate by keyboard', () => { const onApply = cy.stub(); const onCancel = cy.stub(); @@ -156,8 +250,8 @@ describe('DateTimePicker', () => { cy.focused().realPress('Tab'); cy.focused().should('contain.text', 'Back'); cy.focused().realPress('Tab'); - cy.focused().should('contain.text', 'Apply'); cy.focused() + .should('contain.text', 'Apply') .type('{enter}') .should(() => { expect(onApply).to.be.calledWith({ diff --git a/packages/react/src/components/DateTimePicker/DateTimePickerV2.jsx b/packages/react/src/components/DateTimePicker/DateTimePickerV2.jsx index 86c8f40a08..af86017c36 100644 --- a/packages/react/src/components/DateTimePicker/DateTimePickerV2.jsx +++ b/packages/react/src/components/DateTimePicker/DateTimePickerV2.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useMemo, useRef, useCallback } from 'react'; +import React, { useEffect, useState, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { DatePicker, @@ -29,8 +29,23 @@ import { } from '../../constants/DateConstants'; import Button from '../Button/Button'; import FlyoutMenu, { FlyoutMenuDirection } from '../FlyoutMenu/FlyoutMenu'; +import { handleSpecificKeyDown } from '../../utils/componentUtilityFunctions'; +import { Tooltip } from '../Tooltip'; -import { parseValue } from './dateTimePickerUtils'; +import { + getIntervalValue, + invalidEndDate, + invalidStartDate, + onDatePickerClose, + parseValue, + useAbsoluteDateTimeValue, + useDateTimePickerFocus, + useDateTimePickerKeyboardInteraction, + useDateTimePickerRangeKind, + useDateTimePickerRef, + useDateTimePickerTooltip, + useRelativeDateTimeValue, +} from './dateTimePickerUtils'; const { iotPrefix } = settings; @@ -58,9 +73,9 @@ export const DateTimePickerDefaultValuePropTypes = PropTypes.oneOfType([ timeRangeValue: PropTypes.exact({ startDate: PropTypes.string.isRequired, startTime: PropTypes.string.isRequired, - /** Can be a full parseable DateTime string or a Date object */ + /** Can be a full parsable DateTime string or a Date object */ start: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), - /** Can be a full parseable DateTime string or a Date object */ + /** Can be a full parsable DateTime string or a Date object */ end: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), endDate: PropTypes.string.isRequired, endTime: PropTypes.string.isRequired, @@ -253,8 +268,6 @@ const DateTimePicker = ({ hasIconOnly, ...others }) => { - // keeps track of the flyout menu state - const [isFlyoutOpen, setIsFlyoutOpen] = useState(); React.useEffect(() => { if (__DEV__) { warning( @@ -278,24 +291,53 @@ const DateTimePicker = ({ }, [locale]); // State - const [customRangeKind, setCustomRangeKind] = useState( - showRelativeOption ? PICKER_KINDS.RELATIVE : PICKER_KINDS.ABSOLUTE + const [customRangeKind, setCustomRangeKind, onCustomRangeChange] = useDateTimePickerRangeKind( + showRelativeOption ); const [isCustomRange, setIsCustomRange] = useState(false); const [selectedPreset, setSelectedPreset] = useState(null); const [currentValue, setCurrentValue] = useState(null); const [lastAppliedValue, setLastAppliedValue] = useState(null); const [humanValue, setHumanValue] = useState(null); - const [relativeValue, setRelativeValue] = useState(null); - const [absoluteValue, setAbsoluteValue] = useState(null); - const [focusOnFirstField, setFocusOnFirstField] = useState(true); - const [relativeToTimeInvalid, setRelativeToTimeInvalid] = useState(false); - const [absoluteStartTimeInvalid, setAbsoluteStartTimeInvalid] = useState(false); - const [absoluteEndTimeInvalid, setAbsoluteEndTimeInvalid] = useState(false); - - // Refs - const [datePickerElem, setDatePickerElem] = useState(null); + const [datePickerElem, handleDatePickerRef] = useDateTimePickerRef({ id, v2: true }); + const [focusOnFirstField, setFocusOnFirstField] = useDateTimePickerFocus(datePickerElem); const relativeSelect = useRef(null); + const { + absoluteValue, + setAbsoluteValue, + absoluteStartTimeInvalid, + setAbsoluteStartTimeInvalid, + absoluteEndTimeInvalid, + setAbsoluteEndTimeInvalid, + onAbsoluteStartTimeChange, + onAbsoluteEndTimeChange, + resetAbsoluteValue, + } = useAbsoluteDateTimeValue(); + + const { + relativeValue, + setRelativeValue, + relativeToTimeInvalid, + resetRelativeValue, + relativeLastNumberInvalid, + onRelativeLastNumberChange, + onRelativeLastIntervalChange, + onRelativeToWhenChange, + onRelativeToTimeChange, + } = useRelativeDateTimeValue({ + defaultInterval: intervals[0].value, + defaultRelativeTo: relatives[0].value, + }); + + const { + isExpanded, + setIsExpanded, + presetListRef, + onFieldInteraction, + onNavigateRadioButton, + onNavigatePresets, + } = useDateTimePickerKeyboardInteraction({ expanded, setCustomRangeKind }); + const [isTooltipOpen, toggleTooltip] = useDateTimePickerTooltip({ isExpanded }); const dateTimePickerBaseValue = { kind: '', @@ -318,25 +360,6 @@ const DateTimePicker = ({ }, }; - const handleDatePickerRef = useCallback((node) => { - setDatePickerElem(node); - }, []); - - useEffect(() => { - const timeout = setTimeout(() => { - if (datePickerElem) { - datePickerElem.cal.open(); - // while waiting for https://github.com/carbon-design-system/carbon/issues/5713 - // the only way to display the calendar inline is to reparent its DOM to our component - const dp = document.getElementById(`${id}-${iotPrefix}--date-time-picker__datepicker`); - dp.appendChild(datePickerElem.cal.calendarContainer); - } - }, 0); - return () => { - clearTimeout(timeout); - }; - }, [datePickerElem, id]); - /** * Transforms a default or selected value into a full blown returnable object * @param {Object} [preset] clicked preset @@ -388,16 +411,6 @@ const DateTimePicker = ({ [absoluteValue, relativeValue] ); - useEffect(() => { - if (datePickerElem && datePickerElem.inputField && datePickerElem.toInputField) { - if (focusOnFirstField) { - datePickerElem.inputField.click(); - } else { - datePickerElem.toInputField.click(); - } - } - }, [datePickerElem, focusOnFirstField]); - const onDatePickerChange = ([start, end], _, flatpickr) => { const calendarInFocus = document?.activeElement?.closest( `.${iotPrefix}--date-time-picker__datepicker` @@ -445,18 +458,14 @@ const DateTimePicker = ({ } setAbsoluteValue(newAbsolute); - }; - - const onDatePickerClose = (range, single, flatpickr) => { - // force it to stay open - /* istanbul ignore else */ - if (flatpickr) { - flatpickr.open(); - } - }; - const onCustomRangeChange = (kind) => { - setCustomRangeKind(kind); + // Update end and start time invalid state when date changed + setAbsoluteEndTimeInvalid( + invalidEndDate(newAbsolute.startTime, newAbsolute.endTime, newAbsolute) + ); + setAbsoluteStartTimeInvalid( + invalidStartDate(newAbsolute.startTime, newAbsolute.endTime, newAbsolute) + ); }; const onPresetClick = (preset) => { @@ -464,24 +473,6 @@ const DateTimePicker = ({ renderValue(preset); }; - const resetRelativeValue = () => { - setRelativeValue({ - lastNumber: 0, - lastInterval: intervals[0].value, - relativeToWhen: relatives[0].value, - relativeToTime: '', - }); - }; - - const resetAbsoluteValue = () => { - setAbsoluteValue({ - startDate: '', - startTime: '00:00', - endDate: '', - endTime: '00:00', - }); - }; - const parseDefaultValue = (parsableValue) => { const currentCustomRangeKind = showRelativeOption ? PICKER_KINDS.RELATIVE @@ -509,13 +500,15 @@ const DateTimePicker = ({ setIsCustomRange(true); setCustomRangeKind(PICKER_KINDS.ABSOLUTE); if (!absolute.hasOwnProperty('start')) { - absolute.start = dayjs(absolute.startDate).valueOf(); + absolute.start = dayjs(`${absolute.startDate} ${absolute.startTime}`).valueOf(); } if (!absolute.hasOwnProperty('end')) { - absolute.end = dayjs(absolute.endDate).valueOf(); + absolute.end = dayjs(`${absolute.endDate} ${absolute.endTime}`).valueOf(); } absolute.startDate = dayjs(absolute.start).format('MM/DD/YYYY'); + absolute.startTime = dayjs(absolute.start).format('HH:mm'); absolute.endDate = dayjs(absolute.end).format('MM/DD/YYYY'); + absolute.endTime = dayjs(absolute.end).format('HH:mm'); setAbsoluteValue(absolute); } } else { @@ -557,67 +550,14 @@ const DateTimePicker = ({ [defaultValue] ); - /** - * Get an alternative human readable value for a preset to show in tooltips and dropdown - * ie. 'Last 30 minutes' displays '2020-04-01 11:30 to Now' on the tooltip - * @returns {string} an interval string, starting point in time to now - */ - const getIntervalValue = () => { - if (currentValue) { - if (currentValue.kind === PICKER_KINDS.PRESET) { - return `${dayjs().subtract(currentValue.preset.offset, 'minutes').format(dateTimeMask)} ${ - strings.toNowLabel - }`; - } - } - return ''; - }; - - // Util func to update the relative value - const changeRelativePropertyValue = (property, value) => { - const newRelative = { ...relativeValue }; - newRelative[property] = value; - setRelativeValue(newRelative); - }; - - // on change functions that trigger a relative value update - const onRelativeLastNumberChange = (event) => { - changeRelativePropertyValue('lastNumber', Number(event.imaginaryTarget.value)); - }; - const onRelativeLastIntervalChange = (event) => { - changeRelativePropertyValue('lastInterval', event.currentTarget.value); - }; - const onRelativeToWhenChange = (event) => { - changeRelativePropertyValue('relativeToWhen', event.currentTarget.value); - }; - const onRelativeToTimeChange = (pickerValue, evt, meta) => { - setRelativeToTimeInvalid(meta.invalid); - changeRelativePropertyValue('relativeToTime', pickerValue); - }; - - // Util func to update the absolute value - const changeAbsolutePropertyValue = (property, value) => { - const newAbsolute = { ...absoluteValue }; - newAbsolute[property] = value; - setAbsoluteValue(newAbsolute); - }; - - // on change functions that trigger a absolute value update - const onAbsoluteStartTimeChange = (pickerValue, evt, meta) => { - setAbsoluteStartTimeInvalid(meta.invalid); - changeAbsolutePropertyValue('startTime', pickerValue); - }; - const onAbsoluteEndTimeChange = (pickerValue, evt, meta) => { - setAbsoluteEndTimeInvalid(meta.invalid); - changeAbsolutePropertyValue('endTime', pickerValue); - }; - const tooltipValue = renderPresetTooltipText ? renderPresetTooltipText(currentValue) - : getIntervalValue(); + : getIntervalValue({ currentValue, strings, dateTimeMask, humanValue }); const disableRelativeApply = - isCustomRange && customRangeKind === PICKER_KINDS.RELATIVE && relativeToTimeInvalid; + isCustomRange && + customRangeKind === PICKER_KINDS.RELATIVE && + (relativeLastNumberInvalid || relativeToTimeInvalid); const disableAbsoluteApply = isCustomRange && @@ -626,54 +566,54 @@ const DateTimePicker = ({ const disableApply = disableRelativeApply || disableAbsoluteApply; - // eslint-disable-next-line react/prop-types - const CustomFooter = ({ setIsOpen }) => { - const onApplyClick = () => { - setIsOpen(false); - const value = renderValue(); - setLastAppliedValue(value); - const returnValue = { - timeRangeKind: value.kind, - timeRangeValue: null, - }; - switch (value.kind) { - case PICKER_KINDS.ABSOLUTE: - returnValue.timeRangeValue = { - ...value.absolute, - humanValue, - tooltipValue, - }; - break; - case PICKER_KINDS.RELATIVE: - returnValue.timeRangeValue = { - ...value.relative, - humanValue, - tooltipValue, - }; - break; - default: - returnValue.timeRangeValue = { - ...value.preset, - tooltipValue, - }; - break; - } - - if (onApply) { - onApply(returnValue); - } + const onApplyClick = () => { + setIsExpanded(false); + const value = renderValue(); + setLastAppliedValue(value); + const returnValue = { + timeRangeKind: value.kind, + timeRangeValue: null, }; + switch (value.kind) { + case PICKER_KINDS.ABSOLUTE: + returnValue.timeRangeValue = { + ...value.absolute, + humanValue, + tooltipValue, + }; + break; + case PICKER_KINDS.RELATIVE: + returnValue.timeRangeValue = { + ...value.relative, + humanValue, + tooltipValue, + }; + break; + default: + returnValue.timeRangeValue = { + ...value.preset, + tooltipValue, + }; + break; + } - const onCancelClick = () => { - parseDefaultValue(lastAppliedValue); - setIsOpen(false); + if (onApply) { + onApply(returnValue); + } + }; - /* istanbul ignore else */ - if (onCancel) { - onCancel(); - } - }; + const onCancelClick = () => { + parseDefaultValue(lastAppliedValue); + setIsExpanded(false); + + /* istanbul ignore else */ + if (onCancel) { + onCancel(); + } + }; + // eslint-disable-next-line react/prop-types + const CustomFooter = () => { return (
{isCustomRange ? ( @@ -683,6 +623,7 @@ const DateTimePicker = ({ size="field" {...others} onClick={toggleIsCustomRange} + onKeyUp={handleSpecificKeyDown(['Enter', ' '], toggleIsCustomRange)} > {strings.backBtnLabel} @@ -693,6 +634,7 @@ const DateTimePicker = ({ onClick={onCancelClick} size="field" {...others} + onKeyUp={handleSpecificKeyDown(['Enter', ' '], onCancelClick)} > {strings.cancelBtnLabel} @@ -700,9 +642,10 @@ const DateTimePicker = ({ + +
+ + + + +`; + exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/Table With row expansion 1`] = `
@@ -32881,7 +32881,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -36688,8 +36688,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -36708,7 +36708,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -39834,8 +39834,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -39854,7 +39854,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -45609,8 +45609,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -45629,7 +45629,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -84194,8 +84194,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -84214,7 +84214,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -87675,8 +87675,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -87695,7 +87695,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > diff --git a/packages/react/src/components/Table/__snapshots__/TableColumnCustomization.story.storyshot b/packages/react/src/components/Table/__snapshots__/TableColumnCustomization.story.storyshot index 2c1a938ace..96fb08f0b6 100644 --- a/packages/react/src/components/Table/__snapshots__/TableColumnCustomization.story.storyshot +++ b/packages/react/src/components/Table/__snapshots__/TableColumnCustomization.story.storyshot @@ -560,7 +560,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="string" data-offset={0} - id="cell-table-49-row-0-string" + id="cell-table-50-row-0-string" offset={0} > +); +``` + +### Dealing with large data sets + +If the application is using the StatefulTable with large data sets and needs to lazy load the data when +the user moves between the pages this can be achieved by setting `options.hasOnlyPageData` to true which +will stop the StatefulTable from automatically updating the rows displayed and only update the actual +pagination component. In order for the pagination to show the correct number of pages in this case we +must also set the `totalItems` prop explicitly. + +Normally the number of pages are automatically calculated by the total items divided by the page size but +for very large data sets the number of options available in the current page dropdown might grow too large. +If the `totalItems` is dynamically set but risk being very large the `view.pagination.maxPages` prop, +which must be a positive integer, can be used to explicitly limit the number of pages and prevent the +current page dropdown from becoming sluggish. + +```jsx +... +const totalItemsCount = mockGetTotalItemsCount(); +const loadPageIntoDataProp = ({ page, pageSize }) => {...} + +return ( + +); +``` + +### Handling the onChangePage event + +The normal stateles Table must always implement the `onChangePage` event and respond to user initiated +changes of current page and page size as shown in the code example below. The internal state of the +pagination component is automatically reset when the `pageSize` prop changes. + +```jsx +... + +const [pageSize, setPageSize] = useState(10); +const [currentPageData, setCurrentPageData] = useState(data.slice(0, pageSize)); + +const handleOnChangePage = ({ page, pageSize: newPageSize }) => { + const endIndex = page * newPageSize; + const startIndex = endIndex - newPageSize; + if (newPageSize !== pageSize) { + setPageSize(newPageSize); + } + setCurrentPageData(data.slice(startIndex, endIndex)); +}; + +return ( + +); + +``` diff --git a/packages/react/src/components/Table/mdx/Table.mdx b/packages/react/src/components/Table/mdx/Table.mdx index fd19c36ba7..2e1e8987c8 100644 --- a/packages/react/src/components/Table/mdx/Table.mdx +++ b/packages/react/src/components/Table/mdx/Table.mdx @@ -29,6 +29,9 @@ import TableUserViewManagementTOC from './TableUserViewManagementTOC.mdx'; - [Programmatic filtering](#programmatic-filtering) - [☢️ Advanced Filtering Experimental](#%EF%B8%8F-advanced-filtering-experimental) - [Pagination](#pagination) + - [Pagination code example](#pagination-code-example) + - [Dealing with large data sets](#dealing-with-large-data-sets) + - [Handling the onChangePage event](#handling-the-onchangepage-event) - [Toolbar actions](#toolbar-actions) - [Aggregation](#aggregation) - [Row data editing](#row-data-editing) @@ -355,56 +358,9 @@ import Filtering from './Filtering.mdx'; -## Pagination +import Pagination from './Pagination.mdx'; -Pagination is controlled through the `options.hasPagination` prop combined with the `view.pagination` prop. Pagination has one callback event for onChangePage that is fired when the page or the number of items per page changes. -By default, tables with pagination will expect the entire table data to be passed in on the `data` prop; the visible data for a page is calculated dynamically by the table based on the page size and page number. In the case where the table is rendering a large data set, the `options.hasOnlyPageData` prop can be used change this behavior. With `options.hasOnlyPageData = true`, the `data` prop will be expected to contain only the rows for the visible page. -The maxPages prop must be a positive integer and is used to limit the number of pages available the dropdown when working with very large datasets. - -```jsx -
{}, - }, - }} - options={{ - hasPagination: true, - }} - view={{ - pagination: { - isItemPerPageHidden: false, - maxPages: 100, - page: 1, - pageSize: 10, - pageSizes: [10, 20, 30], - totalItems: undefined, - }, - }} -/> -``` - -```jsx -
{}, - }, - }} - options={{ - hasPagination: true, - hasOnlyPageData: true, - }} - view={{ - pagination: { - pageSize: 10, - pageSizes: [10, 20, 30], - page: 8267, - totalItems: 97532, - }, - }} -/> -``` + ## Toolbar actions @@ -759,6 +715,7 @@ import TableViewDropdownContent from '../TableViewDropdown/TableViewDropdownCont | pagination.totalItems | number | | The total number of items available to the table | | pagination.maxPages | number | | Number of pages rendered in pagination. Must be a positive integer. | | pagination.isItemPerPageHidden | bool | | Hide displaying the text for the number of rows per page | +| pagination.size | 'sm', 'md', or 'lg' | 'lg' | The size of the pagination component | | filters | object[] | | An array of objects setting the current filter state | | filters[].columnId | string | | The columnId of the column being filtered by this value | | filters[].value | string, number, bool, string[] | | The value used to filter the given columnId | diff --git a/packages/react/src/components/TableCard/__snapshots__/TableCard.story.storyshot b/packages/react/src/components/TableCard/__snapshots__/TableCard.story.storyshot index d2416e3488..04297f6986 100644 --- a/packages/react/src/components/TableCard/__snapshots__/TableCard.story.storyshot +++ b/packages/react/src/components/TableCard/__snapshots__/TableCard.story.storyshot @@ -940,8 +940,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -960,7 +960,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -2321,8 +2321,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -2341,7 +2341,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -3767,8 +3767,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -3787,7 +3787,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -4675,8 +4675,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -4695,7 +4695,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -5745,8 +5745,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -5765,7 +5765,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -7763,8 +7763,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -7783,7 +7783,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -9627,8 +9627,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -9647,7 +9647,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -11140,8 +11140,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -11160,7 +11160,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -12867,8 +12867,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -12887,7 +12887,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -15024,8 +15024,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > @@ -15044,7 +15044,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T > diff --git a/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot b/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot index 2afeb48f37..0c45d7aa4f 100644 --- a/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot +++ b/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot @@ -201,7 +201,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T onAnimationEnd={[Function]} >
+ ); + + userEvent.click(screen.getByRole('button', { name: 'open and close list of options' })); + userEvent.click(screen.getByText('overflow batch action text')); + expect(onApplyBatchAction).toHaveBeenCalledWith('overflow-batch-action-text'); + + userEvent.click(screen.getByRole('button', { name: 'open and close list of options' })); + userEvent.click(screen.getByText('overflow batch action with icon')); + expect(onApplyBatchAction).toHaveBeenCalledWith('test-overflow-batch-action-icon'); + + userEvent.click(screen.getByRole('button', { name: 'open and close list of options' })); + userEvent.click(screen.getByText('overflow batch action delete')); + expect(onApplyBatchAction).toHaveBeenCalledWith('test-overflow-batch-action-delete'); + + userEvent.click(screen.getByRole('button', { name: 'open and close list of options' })); + userEvent.click(screen.getByText("overflow batch action that's disabled")); + expect(onApplyBatchAction).not.toHaveBeenCalledWith('test-overflow-batch-action-disabled'); + + userEvent.click(screen.getByRole('button', { name: 'open and close list of options' })); + expect(screen.queryByText("overflow batch action that's hidden")).toBeNull(); + }); + it('should hide and disable batch actions', () => { + const rows = tableData.slice(0, 5); + const selectedIds = rows.map((row) => row.id); + const onApplyBatchAction = jest.fn(); + render( +
+ ); + + userEvent.click(screen.getByRole('button', { name: 'disabled batch action' })); + expect(onApplyBatchAction).not.toHaveBeenCalledWith('disabled-batch-action'); + + expect(screen.queryByText('hidden batch action')).toBeNull(); + }); + }); }); diff --git a/packages/react/src/components/Table/TableToolbar/TableToolbar.jsx b/packages/react/src/components/Table/TableToolbar/TableToolbar.jsx index 7297de248a..ec8c205f67 100644 --- a/packages/react/src/components/Table/TableToolbar/TableToolbar.jsx +++ b/packages/react/src/components/Table/TableToolbar/TableToolbar.jsx @@ -40,6 +40,7 @@ import { import { settings } from '../../../constants/Settings'; import { RuleGroupPropType } from '../../RuleBuilder/RuleBuilderPropTypes'; import useDynamicOverflowMenuItems from '../../../hooks/useDynamicOverflowMenuItems'; +import { renderTableOverflowItemText } from '../tableUtilities'; import TableToolbarAdvancedFilterFlyout from './TableToolbarAdvancedFilterFlyout'; import TableToolbarSVGButton from './TableToolbarSVGButton'; @@ -138,12 +139,27 @@ const propTypes = { customToolbarContent: PropTypes.node, /** available batch actions */ batchActions: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - labelText: PropTypes.string.isRequired, - icon: PropTypes.node, - iconDescription: PropTypes.string, - }) + PropTypes.oneOfType([ + PropTypes.shape({ + id: PropTypes.string.isRequired, + labelText: PropTypes.string.isRequired, + renderIcon: PropTypes.node, + iconDescription: PropTypes.string, + hidden: PropTypes.bool, + disabled: PropTypes.bool, + }), + PropTypes.shape({ + id: PropTypes.string.isRequired, + labelText: PropTypes.string.isRequired, + renderIcon: PropTypes.node, + iconDescription: PropTypes.string, + hidden: PropTypes.bool, + disabled: PropTypes.bool, + isOverflow: PropTypes.bool, + hasDivider: PropTypes.bool, + isDelete: PropTypes.bool, + }), + ]) ), search: TableSearchPropTypes, /** buttons to be shown with when activeBar is 'rowEdit' */ @@ -268,6 +284,16 @@ const TableToolbar = ({ (action) => !action.isOverflow && action.hidden !== true ); + const visibleBatchActions = batchActions.filter( + (action) => !action.isOverflow && action.hidden !== true + ); + + const visibleOverflowBatchActions = batchActions.filter( + (action) => action.isOverflow && action.hidden !== true + ); + + const hasVisibleOverflowBatchActions = visibleOverflowBatchActions.length > 0; + return ( tableTranslateWithId(i18n, ...args)} > - {batchActions.map(({ id, labelText, ...others }) => ( + {visibleBatchActions.map(({ id, labelText, disabled, ...others }) => ( onApplyBatchAction(id)} tabIndex={shouldShowBatchActions ? 0 : -1} - disabled={!shouldShowBatchActions} + disabled={!shouldShowBatchActions || disabled} {...others} > {labelText} ))} + {hasVisibleOverflowBatchActions ? ( + e.stopPropagation()} + renderIcon={OverflowMenuVertical20} + tabIndex={shouldShowBatchActions ? 0 : -1} + size="lg" + menuOptionsClass={`${iotPrefix}--table-overflow-batch-actions__menu`} + > + {visibleOverflowBatchActions.map( + ({ id, labelText, disabled, hasDivider, isDelete, renderIcon }) => ( + onApplyBatchAction(id)} + key={`table-batch-actions-overflow-menu-${id}`} + requireTitle={!renderIcon} + hasDivider={hasDivider} + isDelete={isDelete} + aria-label={labelText} + /> + ) + )} + + ) : null} {secondaryTitle ? ( // eslint-disable-next-line jsx-a11y/label-has-associated-control, jsx-a11y/label-has-for diff --git a/packages/react/src/components/Table/TableToolbar/_table-toolbar.scss b/packages/react/src/components/Table/TableToolbar/_table-toolbar.scss index 8427c59092..0e5b756e1b 100644 --- a/packages/react/src/components/Table/TableToolbar/_table-toolbar.scss +++ b/packages/react/src/components/Table/TableToolbar/_table-toolbar.scss @@ -113,3 +113,26 @@ html[dir='rtl'] { } } } + +.#{$iot-prefix}--table-overflow-batch-actions { + &.#{$prefix}--overflow-menu--open, + &.#{$prefix}--overflow-menu--open:hover, + &:hover { + background-color: $hover-primary; + } + + &:focus { + outline: 2px solid $ui-01; + outline-offset: -($spacing-01); + } + + > svg { + fill: $ui-01; + } +} + +.#{$iot-prefix}--table-overflow-batch-actions__menu { + &:after { + background-color: $hover-primary; + } +} diff --git a/packages/react/src/components/Table/__snapshots__/Table.main.story.storyshot b/packages/react/src/components/Table/__snapshots__/Table.main.story.storyshot index d0022f09bd..e18180bb21 100644 --- a/packages/react/src/components/Table/__snapshots__/Table.main.story.storyshot +++ b/packages/react/src/components/Table/__snapshots__/Table.main.story.storyshot @@ -136,6 +136,51 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T /> + +
+ ); + + expect(screen.queryByRole('button', { name: 'open and close list of options' })).toBeNull(); + }); }); }); From b0874978321998e0adbe5fa9546fe90fcf0e1f97 Mon Sep 17 00:00:00 2001 From: Kevin Perrine Date: Wed, 23 Feb 2022 16:55:34 -0500 Subject: [PATCH 38/84] docs(table): update docs for batch action overflow menu --- .../Table/mdx/SelectionAndBatchActions.mdx | 120 ++++++++++++++++-- .../react/src/components/Table/mdx/Table.mdx | 9 +- 2 files changed, 118 insertions(+), 11 deletions(-) diff --git a/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx b/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx index d12d9894a5..264ffe39b1 100644 --- a/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx +++ b/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx @@ -10,7 +10,7 @@ mode, the batch action bar appears at the top of the table, presenting the user The `options.hasRowSelection` can be turned on using either the value `single` for single row selection or `multi` for multiple row selection. By default all rows are selectable but they can individually be made -unselectable by setting the row data object's `isSelectable` property to false. +un-selectable by setting the row data object's `isSelectable` property to false. A table with hasRowSelection `multi` will automatically add checkboxes in front of each row using a extra column. For the StatefulTable the management of the the checkbox states are handled automatically, but the prop `view.table.selectedIds` can still be used to set the initial selection. @@ -45,7 +45,7 @@ presented in a bar that temporarily hides the toolbar. By default the bar contai clicking it in the StatefulTable will clear all selections and close the batch action bar. Deselecting all the selected rows will also close the batch actions bar. Additional batch actions can be added using the prop `view.toolbar.batchActions` which accepts an array of objects used to define the action as shown in the -example below. All explicitly defined batch actions need to be handled programatically except for the 'delete' action +example below. All explicitly defined batch actions need to be handled programmatically except for the 'delete' action which is handled automatically by the StatefulTable. ```jsx @@ -87,6 +87,108 @@ which is handled automatically by the StatefulTable. id: 'process', labelText: 'Process', }, + // items in the batch action toolbar can be hidden + { + id: 'hidden-not-overflow', + labelText: 'Hidden', + hidden: true, + }, + // item can also be added to an overflow menu by settings + { + id: 'reject', + labelText: 'Reject', + renderIcon: Error16, + iconDescription: 'Reject these items', + isOverflow: true, + }, + { + id: 'reassign', + labelText: 'Reassign', + iconDescription: 'Reassign these items to another person', + isOverflow: true, + hasDivider: true, + disabled: true, + }, + { + id: 'hidden', + labelText: 'Hide these items', + iconDescription: 'This action is hidden in the overflow menu', + isOverflow: true, + hasDivider: true, + hidden: true, + }, + { + id: 'expunge', + labelText: 'Expunge these records', + iconDescription: 'Expunge these records from the database', + renderIcon: TrashCan16, + isOverflow: true, + hasDivider: true, + isDelete: true, + }, + ], + }, + }, + }} +/> +``` + +By default, batch actions will appear in the toolbar as buttons, but can also be added to an overflow +menu that will appear last in the batch action bar (just before the Cancel button). These overflow +items can be disabled, hidden, marked as dangerous, or given icons as seen in the example below: + +```jsx + { + // Implement your actions here + }, + }, + }} + options={{ hasRowSelection: 'multi' }} + view={{ + table: { + toolbar: { + batchActions: [ + { + // item can also be added to an overflow menu by setting `isOverflow:true` + { + id: 'reject', + labelText: 'Reject', + renderIcon: Error16, + iconDescription: 'Reject these items', + isOverflow: true, + }, + // this item will appear in the menu, but be disabled + { + id: 'reassign', + labelText: 'Reassign', + isOverflow: true, + disabled: true, + }, + // this item will be hidden from the menu + { + id: 'hidden', + labelText: 'Hide these items', + isOverflow: true, + hidden: true, + }, + // `isDelete` marks the item as dangerous and will be red when hovered or focused, because + // `hasDivider` is also set, this item will have a divider above it separating it from + // other actions in the menu + { + id: 'expunge', + labelText: 'Expunge these records', + iconDescription: 'Expunge these records from the database', + renderIcon: TrashCan16, + isOverflow: true, + hasDivider: true, + isDelete: true, + }, ], }, }, @@ -97,10 +199,10 @@ which is handled automatically by the StatefulTable. ### Programmatic selection and batch actions For the normal `Table` the callbacks and states of the selections and batch actions must be managed -programmatically, inlcuding the Cancel and Delete callbacks. You can set which rows are currently selected +programmatically, including the Cancel and Delete callbacks. You can set which rows are currently selected through the `view.table.selectedIds` prop. This prop takes an array of selected row ids. There are props that let you set the state of the "select all" checkbox explicitly but that is normally -not needed since it will be set automaticaly based on the selectedIds prop and the +not needed since it will be set automatically based on the selectedIds prop and the actual rows of the table. If the table has nested rows it will also automatically show the correct selected and indeterminate state of a parent row based on its children. @@ -109,7 +211,7 @@ The following code example shows how to implement the "delete" and "cancel" batc ```jsx ... -const [selectedIds, setSelecteIds] = useState([]); +const [selectedIds, setSelectedIds] = useState([]); const [data, setData] = useState(initialData); return ( @@ -121,7 +223,7 @@ return ( toolbar: { // Cancel the action, and unselect all currently selected rows onCancelBatchAction: () => { - setSelecteIds([]); + setSelectedIds([]); }, // Apply correct action using the actionId string // ActionId is the string id of the action being completed, @@ -129,7 +231,7 @@ return ( onApplyBatchAction: (actionId) => { if (actionId === 'delete') { setData((currentData) => currentData.filter(({ id }) => !selectedIds.includes(id))); - setSelecteIds([]); + setSelectedIds([]); } }, }, @@ -138,12 +240,12 @@ return ( // If all child rows of a parent row are selected the last event's selectedIds param will also // contain the id of the now automatically selected parent. onRowSelected: (rowId, selected, selectedIds) => { - setSelecteIds(selectedIds); + setSelectedIds(selectedIds); }, // AllSelected is a boolean which is true when all are selected, false otherwise onSelectAll: (allSelected) => { const newSelection = allSelected ? data.map(({ id }) => id) : []; - setSelecteIds(newSelection); + setSelectedIds(newSelection); }, }, }} diff --git a/packages/react/src/components/Table/mdx/Table.mdx b/packages/react/src/components/Table/mdx/Table.mdx index fd19c36ba7..4ca236e3fd 100644 --- a/packages/react/src/components/Table/mdx/Table.mdx +++ b/packages/react/src/components/Table/mdx/Table.mdx @@ -772,9 +772,14 @@ import TableViewDropdownContent from '../TableViewDropdown/TableViewDropdownCont | toolbar.customToolbarContent | node | | optional content to render inside the toolbar | | toolbar.batchActions | object[] | | Specify which batch actions to render in the batch action bar. If empty, no batch action toolbar will display | | toolbar.batchActions[].id | string | | unique id for this batch action | -| toolbar.batchActions[].labeltext | string | | text shown on the action button | -| toolbar.batchActions[].icon | element | | The icon rendered on the button for this action | +| toolbar.batchActions[].labelText | string | | text shown on the action button | +| toolbar.batchActions[].renderIcon | element | | The icon rendered on the button for this action | | toolbar.batchActions[].iconDescription | string | | The description used for aria on this icon | +| toolbar.batchActions[].isOverflow | bool | | Set to true to force this batch action to appear in the overflow menu | +| toolbar.batchActions[].isDelete | bool | | Set to true to force this item to appear as dangerous (red) in the menu | +| toolbar.batchActions[].hasDivider | bool | | Set to true to separate this item from the others by adding a divider above it | +| toolbar.batchActions[].disabled | bool | | Set to true to disable this item from action, but still have it appear in a disabled state | +| toolbar.batchActions[].hidden | bool | | When true, this item will not appear in the DOM | | toolbar.search | object | | | | toolbar.search.value | string | | Deprecated in favor of defaultValue | | toolbar.search.defaultValue | string | | The current search value passed to the table | From 12ec8fa4b8bf9aa96a6dd3370898d02929b56be3 Mon Sep 17 00:00:00 2001 From: Kevin Perrine Date: Wed, 23 Feb 2022 17:00:54 -0500 Subject: [PATCH 39/84] docs(table): remove duplicate example --- .../Table/mdx/SelectionAndBatchActions.mdx | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx b/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx index 264ffe39b1..37a9b41eb0 100644 --- a/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx +++ b/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx @@ -93,39 +93,6 @@ which is handled automatically by the StatefulTable. labelText: 'Hidden', hidden: true, }, - // item can also be added to an overflow menu by settings - { - id: 'reject', - labelText: 'Reject', - renderIcon: Error16, - iconDescription: 'Reject these items', - isOverflow: true, - }, - { - id: 'reassign', - labelText: 'Reassign', - iconDescription: 'Reassign these items to another person', - isOverflow: true, - hasDivider: true, - disabled: true, - }, - { - id: 'hidden', - labelText: 'Hide these items', - iconDescription: 'This action is hidden in the overflow menu', - isOverflow: true, - hasDivider: true, - hidden: true, - }, - { - id: 'expunge', - labelText: 'Expunge these records', - iconDescription: 'Expunge these records from the database', - renderIcon: TrashCan16, - isOverflow: true, - hasDivider: true, - isDelete: true, - }, ], }, }, From 23a30900ef96595c616f3ebf10e21a4d1d0d4773 Mon Sep 17 00:00:00 2001 From: Kevin Perrine Date: Wed, 23 Feb 2022 17:03:32 -0500 Subject: [PATCH 40/84] chore(table): docs typo --- .../react/src/components/Table/mdx/SelectionAndBatchActions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx b/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx index 37a9b41eb0..19bd10bab1 100644 --- a/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx +++ b/packages/react/src/components/Table/mdx/SelectionAndBatchActions.mdx @@ -122,7 +122,7 @@ items can be disabled, hidden, marked as dangerous, or given icons as seen in th toolbar: { batchActions: [ { - // item can also be added to an overflow menu by setting `isOverflow:true` + // items can also be added to an overflow menu by setting `isOverflow:true` { id: 'reject', labelText: 'Reject', From f48b9e5a1181b3b2bb9dd010940e1ceb6e49b889 Mon Sep 17 00:00:00 2001 From: Bjorn Alm Date: Thu, 24 Feb 2022 09:27:49 +0100 Subject: [PATCH 41/84] docs(table): fix spelling in mdx --- packages/react/src/components/Table/mdx/Pagination.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/Table/mdx/Pagination.mdx b/packages/react/src/components/Table/mdx/Pagination.mdx index e7cf7b476e..b8d6d035eb 100644 --- a/packages/react/src/components/Table/mdx/Pagination.mdx +++ b/packages/react/src/components/Table/mdx/Pagination.mdx @@ -50,7 +50,7 @@ must also set the `totalItems` prop explicitly. Normally the number of pages are automatically calculated by the total items divided by the page size but for very large data sets the number of options available in the current page dropdown might grow too large. -If the `totalItems` is dynamically set but risk being very large the `view.pagination.maxPages` prop, +If the `totalItems` is dynamically set but risks being very large the `view.pagination.maxPages` prop, which must be a positive integer, can be used to explicitly limit the number of pages and prevent the current page dropdown from becoming sluggish. @@ -82,7 +82,7 @@ return ( ### Handling the onChangePage event -The normal stateles Table must always implement the `onChangePage` event and respond to user initiated +The normal stateless Table must always implement the `onChangePage` event and respond to user initiated changes of current page and page size as shown in the code example below. The internal state of the pagination component is automatically reset when the `pageSize` prop changes. From 582d3e3d321038091f838b25dfe0565262e47b42 Mon Sep 17 00:00:00 2001 From: Kevin Perrine Date: Thu, 24 Feb 2022 10:15:17 -0500 Subject: [PATCH 42/84] chore(repo): turn off sourcemaps for build to compare to esbuild branch --- packages/react/.storybook/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/.storybook/main.js b/packages/react/.storybook/main.js index bd7e9a7620..e93350a0a9 100644 --- a/packages/react/.storybook/main.js +++ b/packages/react/.storybook/main.js @@ -29,7 +29,7 @@ module.exports = { // High quality 'original source' sourcemaps are slow to generate on initial builds and rebuilds. // Using cheap-module-eval-source-map speeds up builds and rebuilds in development while not sacrificing too much source map quality. - config.devtool = configType === 'DEVELOPMENT' ? 'cheap-module-eval-source-map' : 'source-map'; + config.devtool = configType === 'DEVELOPMENT' ? 'cheap-module-eval-source-map' : ''; // Moment.js is quite large, the locales that they bundle in the core as of v2.18 are ignored to keep our bundle size down. // https://webpack.js.org/plugins/ignore-plugin/#example-of-ignoring-moment-locales From b123da0c20711430d97802366a6b8349e0a6bdd8 Mon Sep 17 00:00:00 2001 From: Bjorn Alm Date: Thu, 24 Feb 2022 18:18:28 +0100 Subject: [PATCH 43/84] docs(table): add search story and mdx --- .../src/components/Table/Table.main.story.jsx | 62 +- .../components/Table/Table.story.helpers.jsx | 7 + .../TableSaveViewModal.story.storyshot | 8 +- .../__snapshots__/Table.main.story.storyshot | 3389 ++++++++++++++--- .../TableColumnCustomization.story.storyshot | 50 +- .../src/components/Table/mdx/Filtering.mdx | 3 + .../src/components/Table/mdx/Searching.mdx | 111 + .../react/src/components/Table/mdx/Table.mdx | 7 + .../TileCatalogNew.story.storyshot | 56 +- .../__snapshots__/TileGallery.story.storyshot | 28 +- 10 files changed, 3113 insertions(+), 608 deletions(-) create mode 100644 packages/react/src/components/Table/mdx/Searching.mdx diff --git a/packages/react/src/components/Table/Table.main.story.jsx b/packages/react/src/components/Table/Table.main.story.jsx index 73ef3082aa..7cf9cb7eca 100644 --- a/packages/react/src/components/Table/Table.main.story.jsx +++ b/packages/react/src/components/Table/Table.main.story.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import { object, select, boolean } from '@storybook/addon-knobs'; +import { object, select, boolean, text } from '@storybook/addon-knobs'; import { merge, uniqueId } from 'lodash-es'; import StoryNotice from '../../internal/StoryNotice'; @@ -17,6 +17,7 @@ import SelectionAndBatchActionsREADME from './mdx/SelectionAndBatchActions.mdx'; import InlineActionsREADME from './mdx/InlineActions.mdx'; import RowNestingREADME from './mdx/RowNesting.mdx'; import FilteringREADME from './mdx/Filtering.mdx'; +import SearchingREADME from './mdx/Searching.mdx'; import Table from './Table'; import StatefulTable from './StatefulTable'; import { @@ -110,6 +111,7 @@ export const Playground = () => { selectionCheckboxEnabled, hasSearch, hasFastSearch, + searchFieldDefaultExpanded, wrapCellText, cellTextAlignment, preserveCellWhiteSpace, @@ -345,6 +347,9 @@ export const Playground = () => { toolbarActions, rowEditBarButtons, batchActions, + search: { + defaultExpanded: searchFieldDefaultExpanded, + }, }, table: { emptyState, @@ -427,6 +432,61 @@ WithSorting.parameters = { }, }; +export const WithSearch = () => { + const { selectedTableType, hasSearch, hasFastSearch, searchFieldDefaultExpanded } = getTableKnobs( + { + knobsToCreate: [ + 'selectedTableType', + 'hasSearch', + 'hasFastSearch', + 'searchFieldDefaultExpanded', + ], + enableKnob: (name) => name !== 'searchFieldDefaultExpanded', + } + ); + + const MyTable = selectedTableType === 'StatefulTable' ? StatefulTable : Table; + const data = getTableData().slice(0, 50); + const columns = getTableColumns(); + + const defaultValue = text( + 'Default search value controlled by the app (view.toolbar.search.defaultValue)', + 'helping' + ); + + const knobRegeneratedKey = `${searchFieldDefaultExpanded}`; + + return ( + + ); +}; + +WithSearch.storyName = 'With search'; +WithSearch.parameters = { + component: Table, + docs: { + page: SearchingREADME, + }, +}; + export const WithRowExpansion = () => { const { selectedTableType, hasRowExpansion, shouldExpandOnRowClick } = getTableKnobs({ knobsToCreate: ['selectedTableType', 'hasRowExpansion', 'shouldExpandOnRowClick'], diff --git a/packages/react/src/components/Table/Table.story.helpers.jsx b/packages/react/src/components/Table/Table.story.helpers.jsx index fb74586405..d9e21be3b8 100644 --- a/packages/react/src/components/Table/Table.story.helpers.jsx +++ b/packages/react/src/components/Table/Table.story.helpers.jsx @@ -890,6 +890,13 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) SEARCH_GROUP ) : null, + searchFieldDefaultExpanded: shouldCreate('searchFieldDefaultExpanded') + ? boolean( + 'Expand search field by default on initialization (view.toolbar.search.defaultExpanded)', + enableKnob('searchFieldDefaultExpanded'), + SEARCH_GROUP + ) + : null, // AGGREGATION_GROUP hasAggregations: shouldCreate('hasAggregations') diff --git a/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot b/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot index 7d4aac76c6..42332e823d 100644 --- a/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot +++ b/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot @@ -327,7 +327,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T onAnimationEnd={[Function]} > + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + kYaqK + + +
+ + + + + pinocchio eat can 8 + + + + + + 1975-03-14T03:33:20.000Z + + + + + + option-C + + + + + + y2MwmsEqiq + + + + + + + + + + + + + + 64 + + + + + + true + + + + + + + + + + + + y2Mwm + + +
+ + + + + scott pinocchio chocolate 9 + + + + + + 1975-09-26T21:46:40.000Z + + + + + + option-A + + + + + + CW82EiUYuY + + + + + + + + + + + + + + + + false + + + + + + + + + + + + CW82E + + +
+
+ + + +`; + +exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/Table With search 1`] = ` +
+
+
+
+
+

+ + 0 items selected + +

+
+
+ +
+
+
+
+
+ + + +
+ + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -27040,7 +29383,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="date" data-offset={0} - id="cell-table-33-row-8-date" + id="cell-table-32-row-41-date" offset={0} > - 1975-03-14T03:33:20.000Z + 2026-06-09T10:13:20.000Z @@ -27059,7 +29402,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="select" data-offset={0} - id="cell-table-33-row-8-select" + id="cell-table-32-row-41-select" offset={0} > - y2MwmsEqiq + Q0u8gYkG6G @@ -27097,7 +29440,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="status" data-offset={0} - id="cell-table-33-row-8-status" + id="cell-table-32-row-41-status" offset={0} > - 64 + 1681 @@ -27146,7 +29489,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="boolean" data-offset={0} - id="cell-table-33-row-8-boolean" + id="cell-table-32-row-41-boolean" offset={0} > - true + false @@ -27165,7 +29508,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="node" data-offset={0} - id="cell-table-33-row-8-node" + id="cell-table-32-row-41-node" offset={0} > - y2Mwm + Q0u8g - @@ -27268,7 +29578,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="date" data-offset={0} - id="cell-table-33-row-9-date" + id="cell-table-32-row-47-date" offset={0} > - 1975-09-26T21:46:40.000Z + 2043-03-03T12:53:20.000Z @@ -27287,7 +29597,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="select" data-offset={0} - id="cell-table-33-row-9-select" + id="cell-table-32-row-47-select" offset={0} > - option-A + option-C @@ -27306,7 +29616,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="secretField" data-offset={0} - id="cell-table-33-row-9-secretField" + id="cell-table-32-row-47-secretField" offset={0} > - CW82EiUYuY + muYiOaIWGW @@ -27325,7 +29635,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T className="data-table-start" data-column="status" data-offset={0} - id="cell-table-33-row-9-status" + id="cell-table-32-row-47-status" offset={0} > + > + + 2209 + + @@ -27628,13 +29945,13 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T checked={false} className="bx--checkbox" disabled={false} - id="table-35-head" + id="table-36-head" onChange={[Function]} type="checkbox" />
+ + + String + + + + + + Date + + + + + + Select + + + + + + Secret Information + + + + + + Status + + + + + + Number + + + + + + Boolean + + + + + + React Node + + + + + + Object Id + + +
+ + + helping whiteboard as 1 + + + + + + 1973-03-14T23:33:20.000Z + + + + + + option-B + + + + + + OewGc0QsMs + + + + + + + + + + + + + + 1 + + + + + + false + + + + + + + + + + + + OewGc + + +
+ + + chocolate can helping 7 + + + + + + 1974-09-21T12:53:20.000Z + + + + + + option-B + + + + + + kYaqK2y8W8 + + + + + + + + + + + + + + 49 + + + + + + false + + + + + + + + + + + + kYaqK + + +
+ + + helping whiteboard as 11 + + + + + + 1977-01-01T20:53:20.000Z + + + + + + option-C + + + + + + eUgE8O0yIy + + + + + + + + + + + + + + 121 + + + + + + false + + + + + + + + + + + + eUgE8 + + +
+ + + chocolate can helping 17 + + + + + + 1982-04-30T07:33:20.000Z + + + + + + option-C + + + + + + 0OKoqQYESE + + + + + + + + + + + + + + 289 + + + + + + false + + + + + + + + + + + + 0OKoq + + +
+ + + helping whiteboard as 21 + + + + + + 1987-02-22T13:46:40.000Z + + + + + + option-A + + + + + + uKQCema4E4 + + + + + + + + + + + + + + + + false + + + + + + + + + + + + uKQCe + + +
+ + + chocolate can helping 27 + + + + + + 1996-04-08T21:46:40.000Z + + + + + + option-A + + + + + + GE4mMo8KOK + + + + + + + + + + + + + + + + false + + + + + + + + + + + + GE4mM + + +
+ + + helping whiteboard as 31 + + + + + + 2003-08-16T02:13:20.000Z + + + + + + option-B + + + + + + AAAAAAAAAA + + + + + + + + + + + + + + 961 + + + + + + false + + + + + + + + + + + + AAAAA + + +
+ + + chocolate can helping 37 + + + + + + 2016-07-20T07:33:20.000Z + + + + + + option-B + + + + + + W4oksCiQKQ + + + + + + + + + + + + + + 1369 + + + + + + false + + + - kYaqK + W4oks
- - - pinocchio eat can 8 + helping whiteboard as 41
- - - scott pinocchio chocolate 9 + chocolate can helping 47 - CW82E + muYiO
+) + +``` + +Note! From a UX perspective it is not recommended to have both column filters and the built in search enabled at the +same time. diff --git a/packages/react/src/components/Table/mdx/Table.mdx b/packages/react/src/components/Table/mdx/Table.mdx index fd19c36ba7..73e72adb6f 100644 --- a/packages/react/src/components/Table/mdx/Table.mdx +++ b/packages/react/src/components/Table/mdx/Table.mdx @@ -10,6 +10,9 @@ import TableUserViewManagementTOC from './TableUserViewManagementTOC.mdx'; - [Handling the sort event](#handling-the-sort-event) - [Custom sorting](#custom-sorting) - [Multi-sorting](#multi-sorting) +- [Searching](#searching) + - [Simple search code example](#simple-search-code-example) + - [Programmatic search](#programmatic-search) - [Row expansion](#row-expansion) - [Programmatic expansion](#programmatic-expansion) - [Additional configuration](#additional-configuration) @@ -335,6 +338,10 @@ import Sorting from './Sorting.mdx'; +import Searching from './Searching.mdx'; + + + import RowExpansion from './RowExpansion.mdx'; diff --git a/packages/react/src/components/TileCatalogNew/__snapshots__/TileCatalogNew.story.storyshot b/packages/react/src/components/TileCatalogNew/__snapshots__/TileCatalogNew.story.storyshot index c9d40f556b..c00ff3e2a9 100644 --- a/packages/react/src/components/TileCatalogNew/__snapshots__/TileCatalogNew.story.storyshot +++ b/packages/react/src/components/TileCatalogNew/__snapshots__/TileCatalogNew.story.storyshot @@ -31,7 +31,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T Product name
@@ -56,8 +56,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T
@@ -65,7 +65,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T autoComplete="off" className="bx--search-input" data-testid="tile-catalog-new-search-input" - id="34" + id="35" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} @@ -377,7 +377,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T Product name
@@ -402,8 +402,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T
@@ -411,7 +411,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T autoComplete="off" className="bx--search-input" data-testid="tile-catalog-new-search-input" - id="35" + id="36" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} @@ -626,7 +626,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T Product name
@@ -651,8 +651,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T
@@ -660,7 +660,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T autoComplete="off" className="bx--search-input" data-testid="tile-catalog-new-search-input" - id="37" + id="38" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} @@ -824,7 +824,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T Product name
@@ -849,8 +849,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T
@@ -858,7 +858,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T autoComplete="off" className="bx--search-input" data-testid="tile-catalog-new-search-input" - id="36" + id="37" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} @@ -1025,7 +1025,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T Product name
@@ -1050,8 +1050,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T
@@ -1059,7 +1059,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T autoComplete="off" className="bx--search-input" data-testid="tile-catalog-new-search-input" - id="40" + id="41" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} @@ -1485,7 +1485,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T Product name
@@ -1510,8 +1510,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T
@@ -1519,7 +1519,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T autoComplete="off" className="bx--search-input" data-testid="tile-catalog-new-search-input" - id="38" + id="39" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} @@ -2070,7 +2070,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T Product name
@@ -2095,8 +2095,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T
@@ -2104,7 +2104,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T autoComplete="off" className="bx--search-input" data-testid="tile-catalog-new-search-input" - id="39" + id="40" onBlur={[Function]} onChange={[Function]} onFocus={[Function]} diff --git a/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot b/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot index 2afeb48f37..ffa624c212 100644 --- a/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot +++ b/packages/react/src/components/TileGallery/__snapshots__/TileGallery.story.storyshot @@ -201,7 +201,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T onAnimationEnd={[Function]} > ); - const customToolbarContentElement = ( -
- Custom content -
- ); + const customToolbarContentElement = getCustomToolbarContentElement(); const customEmptyState = (
@@ -285,7 +286,16 @@ export const Playground = () => { const myTableActions = merge(getTableActions(), { table: { onRowLoadMore }, toolbar: { - onShowRowEdit: () => setShowRowEditBar(true), + onShowRowEdit: () => { + action('onShowRowEdit')(); + setShowRowEditBar(true); + }, + onDownloadCSV: demoDownloadCSV + ? (dataToDownload) => { + csvDownloadHandler(dataToDownload, 'Table playground data'); + action('onDownloadCSV')(dataToDownload); + } + : undefined, }, }); const advancedFilters = hasAdvancedFilter ? getAdvancedFilters() : undefined; @@ -391,7 +401,7 @@ Playground.storyName = 'Playground'; export const WithSorting = () => { const { selectedTableType, demoSingleSort, hasMultiSort } = getTableKnobs({ knobsToCreate: ['selectedTableType', 'demoSingleSort', 'hasMultiSort'], - enableKnob: (name) => name !== 'hasMultiSort', + getDefaultValue: (name) => name !== 'hasMultiSort', }); const MyTable = selectedTableType === 'StatefulTable' ? StatefulTable : Table; @@ -454,7 +464,7 @@ export const WithSearching = () => { 'searchFieldDefaultExpanded', 'searchIsExpanded', ], - enableKnob: (name) => name !== 'searchFieldDefaultExpanded' && name !== 'searchIsExpanded', + getDefaultValue: (name) => name !== 'searchFieldDefaultExpanded' && name !== 'searchIsExpanded', }); const MyTable = selectedTableType === 'StatefulTable' ? StatefulTable : Table; @@ -503,7 +513,7 @@ WithSearching.parameters = { export const WithRowExpansion = () => { const { selectedTableType, hasRowExpansion, shouldExpandOnRowClick } = getTableKnobs({ knobsToCreate: ['selectedTableType', 'hasRowExpansion', 'shouldExpandOnRowClick'], - enableKnob: () => true, + getDefaultValue: () => true, }); const initiallyExpandedIds = object('expandedIds', ['row-1']); @@ -562,7 +572,7 @@ export const WithRowNesting = () => { 'shouldExpandOnRowClick', 'demoHasLoadMore', ], - enableKnob: () => true, + getDefaultValue: () => true, }); const initiallyExpandedIds = object('Expanded ids (view.table.expandedIds)', ['row-1']); @@ -641,7 +651,7 @@ WithRowNesting.parameters = { export const WithFiltering = () => { const { selectedTableType, hasFilter, hasAdvancedFilter } = getTableKnobs({ knobsToCreate: ['selectedTableType', 'hasFilter', 'hasAdvancedFilter'], - enableKnob: (knobName) => knobName !== 'hasAdvancedFilter', + getDefaultValue: (knobName) => knobName !== 'hasAdvancedFilter', }); const MyTable = selectedTableType === 'StatefulTable' ? StatefulTable : Table; @@ -759,7 +769,7 @@ WithFiltering.parameters = { export const WithSelectionAndBatchActions = () => { const { selectedTableType, hasRowSelection, selectionCheckboxEnabled } = getTableKnobs({ knobsToCreate: ['selectedTableType', 'hasRowSelection', 'selectionCheckboxEnabled'], - enableKnob: () => true, + getDefaultValue: () => true, }); const isStateful = selectedTableType === 'StatefulTable'; @@ -813,7 +823,7 @@ WithSelectionAndBatchActions.parameters = { export const WithInlineActions = () => { const { selectedTableType, hasRowActions } = getTableKnobs({ knobsToCreate: ['selectedTableType', 'hasRowActions'], - enableKnob: () => true, + getDefaultValue: () => true, }); const rowActions = [ @@ -889,7 +899,7 @@ export const WithPagination = () => { 'paginationSize', 'hasOnlyPageData', ], - enableKnob: (name) => name !== 'hasOnlyPageData' && name !== 'isItemPerPageHidden', + getDefaultValue: (name) => name !== 'hasOnlyPageData' && name !== 'isItemPerPageHidden', }); const MyTable = selectedTableType === 'StatefulTable' ? StatefulTable : Table; @@ -930,3 +940,94 @@ WithPagination.parameters = { page: PaginationREADME, }, }; + +export const WithToolbar = () => { + const { + selectedTableType, + secondaryTitle, + tableTooltipText, + demoCustomToolbarContent, + toolbarIsDisabled, + demoDownloadCSV, + } = getTableKnobs({ + knobsToCreate: [ + 'selectedTableType', + 'secondaryTitle', + 'tableTooltipText', + 'demoCustomToolbarContent', + 'toolbarIsDisabled', + 'demoDownloadCSV', + ], + getDefaultValue: (name) => + name === 'secondaryTitle' ? 'Table with toolbar and actions' : name !== 'toolbarIsDisabled', + }); + + const MyTable = selectedTableType === 'StatefulTable' ? StatefulTable : Table; + const data = getTableData(); + const columns = getTableColumns(); + + const flyoutMenu = ( + + Example of custom toolbar content inserting a FlyoutMenu + + ); + const tableTooltip = tableTooltipText ?
{tableTooltipText}
: null; + const customToolbarContent = demoCustomToolbarContent ? ( + <> + {getCustomToolbarContentElement()} + {flyoutMenu} + + ) : undefined; + const toolbarActions = objectWithSubstitution( + 'Toolbar actions (view.toolbar.toolbarActions)', + getTableToolbarActions(), + undefined, + 'substituted with text - no edit' + ); + + // For demo and test purposes we generate an new key for the table when + // some knobs change that normally wouldn't trigger a rerender in the StatefulTable. + const knobRegeneratedKey = `table${toolbarIsDisabled}${JSON.stringify(toolbarActions)}`; + + const onDownloadCSV = demoDownloadCSV + ? (filteredData) => csvDownloadHandler(filteredData, 'my table data') + : undefined; + + return ( + { + return toolbarActions; + }, + }, + }} + /> + ); +}; +WithToolbar.storyName = 'With toolbar'; +WithToolbar.parameters = { + component: Table, + docs: { + page: ToolbarREADME, + }, +}; diff --git a/packages/react/src/components/Table/Table.story.helpers.jsx b/packages/react/src/components/Table/Table.story.helpers.jsx index 246cdcb2b1..7f7552c4db 100644 --- a/packages/react/src/components/Table/Table.story.helpers.jsx +++ b/packages/react/src/components/Table/Table.story.helpers.jsx @@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash-es'; import { action } from '@storybook/addon-actions'; // eslint-disable-next-line import/no-extraneous-dependencies import { boolean, text, select, object } from '@storybook/addon-knobs'; -import { Add20, TrashCan16, BeeBat16, Activity16 } from '@carbon/icons-react'; +import { Add20, TrashCan16, BeeBat16, Activity16, ViewOff16 } from '@carbon/icons-react'; import Arrow from '@carbon/icons-react/es/arrow--right/16'; import Add from '@carbon/icons-react/es/add/16'; import Edit from '@carbon/icons-react/es/edit/16'; @@ -280,7 +280,7 @@ export const getTableToolbarActions = () => [ isDelete: true, hasDivider: true, isOverflow: true, - renderIcon: () => , + renderIcon: TrashCan16, }, { id: 'hidden', @@ -288,6 +288,12 @@ export const getTableToolbarActions = () => [ hidden: true, isOverflow: true, }, + { + id: 'toggle', + labelText: 'Toggle something', + renderIcon: () => , + isActive: true, + }, ]; export const getNewRow = (idx, suffix = '', withActions = false) => ({ @@ -496,6 +502,16 @@ export const getBatchActions = () => { ]; }; +export const getCustomToolbarContentElement = () => ( +
+ Custom content +
+); + const revertSubstituteReactElements = (data, substitutions) => { return data.map((obj, index) => { const objCopy = { ...obj }; @@ -517,6 +533,10 @@ const substituteReactElements = (data, msg) => { if (value.render) { objCopy[key] = `${value.render.name} (${msg})`; substitutions[index] = [key, value]; + } else if (typeof value === 'function') { + const returnValRenderName = value()?.type?.render?.name || ''; + objCopy[key] = `${returnValRenderName} (${msg})`; + substitutions[index] = [key, value]; } }); return objCopy; @@ -764,16 +784,16 @@ const getParsedIntOrUndefined = (value) => { }; /** - * Helper function that Table knobs. + * Helper function that generate the Table knobs. * * If param knobsToCreate is unspecified then all knobs will be created, otherwise only the * knobs whose names are in the array. This conditional creation is needed since StoryBook * will show a knob as soon as it is created by a story, regardless of whether it is * placed in a local variable or not. */ -export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) => { +export const getTableKnobs = ({ knobsToCreate, getDefaultValue, useGroups = false }) => { const TABLE_GROUP = useGroups ? 'Table general' : undefined; - const TITLE_TOOLBAR_GROUP = useGroups ? 'Title & toolbar' : undefined; + const TITLE_TOOLBAR_GROUP = useGroups ? 'Toolbar' : undefined; const ROW_RENDER_GROUP = useGroups ? 'Data rendering' : undefined; const ROW_EDIT_GROUP = useGroups ? 'Data editing' : undefined; const SORT_FILTER_GROUP = useGroups ? 'Sort & filter' : undefined; @@ -804,7 +824,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasUserViewManagement: shouldCreate('hasUserViewManagement') ? boolean( 'Enables table to handle creating/saving/loading of user views (options.hasUserViewManagement)', - enableKnob('hasUserViewManagement'), + getDefaultValue('hasUserViewManagement'), TABLE_GROUP ) : null, @@ -812,39 +832,50 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) // TITLE_TOOLBAR_GROUP secondaryTitle: shouldCreate('secondaryTitle') ? text( - 'Title shown in bar above header row (secondaryTitle)', - 'Table playground', + 'Title shown in toolbar (secondaryTitle)', + getDefaultValue('secondaryTitle'), TITLE_TOOLBAR_GROUP ) : null, tableTooltipText: shouldCreate('tableTooltipText') ? text( 'Table title toltip (tooltip)', - enableKnob('tableTooltipText') ? 'I must be wrapped in a react node' : '', + getDefaultValue('tableTooltipText') ? 'I must be wrapped in a react node' : '', TITLE_TOOLBAR_GROUP ) : null, stickyHeader: shouldCreate('stickyHeader') - ? boolean('Sticky header (stickyHeader) ☢️', enableKnob('stickyHeader'), TITLE_TOOLBAR_GROUP) + ? boolean( + 'Sticky header (stickyHeader) ☢️', + getDefaultValue('stickyHeader'), + TITLE_TOOLBAR_GROUP + ) : null, - demoToolbarActions: shouldCreate('tableTooltipText') + demoToolbarActions: shouldCreate('demoToolbarActions') ? boolean( 'Demo toolbar actions (view.toolbar.toolbarActions)', - enableKnob('tableTooltipText'), + getDefaultValue('demoToolbarActions'), TITLE_TOOLBAR_GROUP ) : null, demoCustomToolbarContent: shouldCreate('demoCustomToolbarContent') ? boolean( 'Demo custom toolbar content (view.toolbar.customToolbarContent)', - enableKnob('demoCustomToolbarContent'), + getDefaultValue('demoCustomToolbarContent'), TITLE_TOOLBAR_GROUP ) : null, toolbarIsDisabled: shouldCreate('toolbarIsDisabled') ? boolean( 'Disable the table toolbar (view.toolbar.isDisabled)', - enableKnob('toolbarIsDisabled'), + getDefaultValue('toolbarIsDisabled'), + TITLE_TOOLBAR_GROUP + ) + : null, + demoDownloadCSV: shouldCreate('demoDownloadCSV') + ? boolean( + 'Demo download data as CSV', + getDefaultValue('demoDownloadCSV'), TITLE_TOOLBAR_GROUP ) : null, @@ -853,14 +884,14 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) demoSingleSort: shouldCreate('demoSingleSort') ? boolean( 'Enable sort on single dimension (columns[i].isSortable)', - enableKnob('demoSingleSort'), + getDefaultValue('demoSingleSort'), SORT_FILTER_GROUP ) : null, hasMultiSort: shouldCreate('hasMultiSort') ? boolean( 'Enable sort on multiple dimensions (options.hasMultiSort)', - enableKnob('hasMultiSort'), + getDefaultValue('hasMultiSort'), SORT_FILTER_GROUP ) : null, @@ -868,14 +899,14 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) ? select( 'Enable simple filtering by column value (options.hasFilter)', ['onKeyPress', 'onEnterAndBlur', true, false], - enableKnob('hasFilter'), + getDefaultValue('hasFilter'), SORT_FILTER_GROUP ) : null, hasAdvancedFilter: shouldCreate('hasAdvancedFilter') ? boolean( 'Enable advanced filters (options.hasAdvancedFilter) ☢️', - enableKnob('hasAdvancedFilter'), + getDefaultValue('hasAdvancedFilter'), SORT_FILTER_GROUP ) : null, @@ -884,21 +915,21 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasSearch: shouldCreate('hasSearch') ? boolean( 'Enable searching on the table values (options.hasSearch)', - enableKnob('hasSearch'), + getDefaultValue('hasSearch'), SEARCH_GROUP ) : null, hasFastSearch: shouldCreate('hasFastSearch') ? boolean( 'Trigger search while typing (options.hasFastSearch)', - enableKnob('hasFastSearch'), + getDefaultValue('hasFastSearch'), SEARCH_GROUP ) : null, searchFieldDefaultExpanded: shouldCreate('searchFieldDefaultExpanded') ? boolean( 'Expand search field by default on initialization (view.toolbar.search.defaultExpanded)', - enableKnob('searchFieldDefaultExpanded'), + getDefaultValue('searchFieldDefaultExpanded'), SEARCH_GROUP ) : null, @@ -914,7 +945,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasAggregations: shouldCreate('hasAggregations') ? boolean( 'Aggregate column values in footer (options.hasAggregations)', - enableKnob('hasAggregations'), + getDefaultValue('hasAggregations'), AGGREGATION_GROUP ) : null, @@ -939,7 +970,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasPagination: shouldCreate('hasPagination') ? boolean( 'Enable pagination (options.hasPagination)', - enableKnob('hasPagination'), + getDefaultValue('hasPagination'), PAGINATION_GROUP ) : null, @@ -964,7 +995,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) isItemPerPageHidden: shouldCreate('isItemPerPageHidden') ? boolean( 'Hide items per page selection (options.pagination.isItemPerPageHidden)', - enableKnob('isItemPerPageHidden'), + getDefaultValue('isItemPerPageHidden'), PAGINATION_GROUP ) : null, @@ -979,43 +1010,47 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasOnlyPageData: shouldCreate('hasOnlyPageData') ? boolean( 'Data prop only represents the currently visible page (options.hasOnlyPageData)', - enableKnob('hasOnlyPageData'), + getDefaultValue('hasOnlyPageData'), PAGINATION_GROUP ) : null, // COLUMN_GROUP demoInitialColumnSizes: shouldCreate('demoInitialColumnSizes') - ? boolean('Demo initial columns sizes', enableKnob('demoInitialColumnSizes'), COLUMN_GROUP) + ? boolean( + 'Demo initial columns sizes', + getDefaultValue('demoInitialColumnSizes'), + COLUMN_GROUP + ) : null, hasResize: shouldCreate('hasResize') ? boolean( 'Enable resizing of column widths (options.hasResize)', - enableKnob('hasResize'), + getDefaultValue('hasResize'), COLUMN_GROUP ) : null, preserveColumnWidths: shouldCreate('preserveColumnWidths') ? boolean( 'Preserve sibling widths on column resize/show/hide (options.preserveColumnWidths)', - enableKnob('preserveColumnWidths'), + getDefaultValue('preserveColumnWidths'), COLUMN_GROUP ) : null, useAutoTableLayoutForResize: shouldCreate('useAutoTableLayoutForResize') ? boolean( 'Use CSS table-layout:auto (options.useAutoTableLayoutForResize)', - enableKnob('useAutoTableLayoutForResize'), + getDefaultValue('useAutoTableLayoutForResize'), COLUMN_GROUP ) : null, demoColumnTooltips: shouldCreate('demoColumnTooltips') - ? boolean('Demo column tooltips', enableKnob('demoColumnTooltips'), COLUMN_GROUP) + ? boolean('Demo column tooltips', getDefaultValue('demoColumnTooltips'), COLUMN_GROUP) : null, demoColumnGroupAssignments: shouldCreate('demoColumnGroupAssignments') ? boolean( 'Demo assigning columns to groups', - enableKnob('demoColumnGroupAssignments'), + getDefaultValue('demoColumnGroupAssignments'), COLUMN_GROUP ) : null, @@ -1036,14 +1071,14 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasColumnSelection: shouldCreate('hasColumnSelection') ? boolean( 'Enable legacy column management (options.hasColumnSelection)', - enableKnob('hasColumnSelection'), + getDefaultValue('hasColumnSelection'), COLUMN_GROUP ) : null, hasColumnSelectionConfig: shouldCreate('hasColumnSelectionConfig') ? boolean( 'Show config button in legacy column management (options.hasColumnSelectionConfig)', - enableKnob('hasColumnSelectionConfig'), + getDefaultValue('hasColumnSelectionConfig'), COLUMN_GROUP ) : null, @@ -1053,7 +1088,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) ? select( 'Enable row selection type (options.hasRowSelection)', ['multi', 'single', false], - enableKnob('hasRowSelection') ? 'multi' : false.valueOf, + getDefaultValue('hasRowSelection') ? 'multi' : false.valueOf, SELECTIONS_ACTIONS_GROUP ) : null, @@ -1067,7 +1102,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) selectionCheckboxEnabled: shouldCreate('selectionCheckboxEnabled') ? boolean( 'Demo row as selectable (data[i].isSelectable)', - enableKnob('selectionCheckboxEnabled'), + getDefaultValue('selectionCheckboxEnabled'), SELECTIONS_ACTIONS_GROUP ) : null, @@ -1081,7 +1116,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasRowActions: shouldCreate('hasRowActions') ? boolean( 'Demo inline actions (options.hasRowActions)', - enableKnob('hasRowActions'), + getDefaultValue('hasRowActions'), SELECTIONS_ACTIONS_GROUP ) : null, @@ -1095,7 +1130,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) false: false, '{ expandRowsExclusively: true }': { expandRowsExclusively: true }, }, - enableKnob('hasRowExpansion'), + getDefaultValue('hasRowExpansion'), NESTING_EXPANSION_GROUP ) : null, @@ -1107,7 +1142,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) false: false, '{ hasSingleNestedHierarchy: true }': { hasSingleNestedHierarchy: true }, }, - enableKnob('hasRowNesting'), + getDefaultValue('hasRowNesting'), NESTING_EXPANSION_GROUP ) : null, @@ -1117,14 +1152,14 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) shouldExpandOnRowClick: shouldCreate('shouldExpandOnRowClick') ? boolean( 'Expand row on click (options.shouldExpandOnRowClick)', - enableKnob('shouldExpandOnRowClick'), + getDefaultValue('shouldExpandOnRowClick'), NESTING_EXPANSION_GROUP ) : null, demoHasLoadMore: shouldCreate('demoHasLoadMore') ? boolean( 'Demo load more child rows (data[i].hasLoadMore)', - enableKnob('demoHasLoadMore'), + getDefaultValue('demoHasLoadMore'), NESTING_EXPANSION_GROUP ) : null, @@ -1133,14 +1168,14 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) shouldLazyRender: shouldCreate('shouldLazyRender') ? boolean( 'Enable only loading table rows as they become visible (options.shouldLazyRender)', - enableKnob('shouldLazyRender'), + getDefaultValue('shouldLazyRender'), ROW_RENDER_GROUP ) : null, useZebraStyles: shouldCreate('useZebraStyles') ? boolean( 'Alternate colors in table rows (useZebraStyles)', - enableKnob('useZebraStyles'), + getDefaultValue('useZebraStyles'), ROW_RENDER_GROUP ) : null, @@ -1166,7 +1201,7 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) preserveCellWhiteSpace: shouldCreate('preserveCellWhiteSpace') ? boolean( 'Keep extra whitespace within a table cell (options.preserveCellWhiteSpace)', - enableKnob('preserveCellWhiteSpace'), + getDefaultValue('preserveCellWhiteSpace'), ROW_RENDER_GROUP ) : null, @@ -1182,14 +1217,14 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) hasRowEdit: shouldCreate('hasRowEdit') ? boolean( 'Enables row editing for the entire table (options.hasRowEdit)', - enableKnob('hasRowEdit'), + getDefaultValue('hasRowEdit'), ROW_EDIT_GROUP ) : null, hasSingleRowEdit: shouldCreate('hasSingleRowEdit') ? boolean( 'Enables row editing for a single row (options.hasSingleRowEdit)', - enableKnob('hasSingleRowEdit'), + getDefaultValue('hasSingleRowEdit'), ROW_EDIT_GROUP ) : null, @@ -1198,14 +1233,14 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) tableIsLoading: shouldCreate('tableIsLoading') ? boolean( 'Show table loading state (view.table.loadingState.isLoading)', - enableKnob('tableIsLoading'), + getDefaultValue('tableIsLoading'), STATES_GROUP ) : null, demoEmptyColumns: shouldCreate('demoEmptyColumns') ? boolean( 'Demo empty columns in loading state (columns)', - enableKnob('demoEmptyColumns'), + getDefaultValue('demoEmptyColumns'), STATES_GROUP ) : null, @@ -1232,21 +1267,21 @@ export const getTableKnobs = ({ knobsToCreate, enableKnob, useGroups = false }) demoEmptyState: shouldCreate('demoEmptyState') ? boolean( 'Demo empty state (view.table.emptyState)', - enableKnob('demoEmptyState'), + getDefaultValue('demoEmptyState'), STATES_GROUP ) : null, demoCustomEmptyState: shouldCreate('demoCustomEmptyState') ? boolean( 'Demo custom empty state (view.table.emptyState)', - enableKnob('demoCustomEmptyState'), + getDefaultValue('demoCustomEmptyState'), STATES_GROUP ) : null, demoCustomErrorState: shouldCreate('demoCustomErrorState') ? boolean( 'Demo custom error state (view.table.errorState)', - enableKnob('demoCustomErrorState'), + getDefaultValue('demoCustomErrorState'), STATES_GROUP ) : null, diff --git a/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot b/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot index cc96319faa..01f5835567 100644 --- a/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot +++ b/packages/react/src/components/Table/TableSaveViewModal/__snapshots__/TableSaveViewModal.story.storyshot @@ -327,7 +327,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T onAnimationEnd={[Function]} >
`; + +exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/Table With toolbar 1`] = ` +
+
+
+
+
+

+ + 0 items selected + +

+
+
+ +
+
+ +
+
+ +
+ + + + +
+
+
+
+ + + +
+ Custom content +
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + String + + + + + + Date + + + + + + Select + + + + + + Secret Information + + + + + + Status + + + + + + Number + + + + + + Boolean + + + + + + React Node + + + + + + Object Id + + +
+ + + toyota toyota toyota 0 + + + + + + 1973-03-03T09:46:40.000Z + + + + + + option-A + + + + + + AAAAAAAAAA + + + + + + + + + + + + + + + + true + + + + + + + + + + + + AAAAA + + +
+ + + helping whiteboard as 1 + + + + + + 1973-03-14T23:33:20.000Z + + + + + + option-B + + + + + + OewGc0QsMs + + + + + + + + + + + + + + 1 + + + + + + false + + + + + + + + + + + + OewGc + + +
+ + + whiteboard can eat 2 + + + + + + 1973-04-18T16:53:20.000Z + + + + + + option-C + + + + + + c8iM4qgaYa + + + + + + + + + + + + + + 4 + + + + + + true + + + + + + + + + + + + c8iM4 + + +
+ + + as eat scott 3 + + + + + + 1973-06-15T13:46:40.000Z + + + + + + option-A + + + + + + qcUSWgwIkI + + + + + + + + + + + + + + + + false + + + + + + + + + + + + qcUSW + + +
+ + + can pinocchio whiteboard 4 + + + + + + 1973-09-04T14:13:20.000Z + + + + + + option-B + + + + + + 46GYyWC0w0 + + + + + + + + + + + + + + 16 + + + + + + true + + + + + + + + + + + + 46GYy + + +
+ + + bottle toyota bottle 5 + + + + + + 1973-12-17T18:13:20.000Z + + + + + + option-C + + + + + + Ia2eQMSi8i + + + + + + + + + + + + + + 25 + + + + + + false + + + + + + + + + + + + Ia2eQ + + +
+ + + eat whiteboard pinocchio 6 + + + + + + 1974-04-24T01:46:40.000Z + + + + + + option-A + + + + + + W4oksCiQKQ + + + + + + + + + + + + + + + + true + + + + + + + + + + + + W4oks + + +
+ + + chocolate can helping 7 + + + + + + 1974-09-21T12:53:20.000Z + + + + + + option-B + + + + + + kYaqK2y8W8 + + + + + + + + + + + + + + 49 + + + + + + false + + + + + + + + + + + + kYaqK + + +
+ + + pinocchio eat can 8 + + + + + + 1975-03-14T03:33:20.000Z + + + + + + option-C + + + + + + y2MwmsEqiq + + + + + + + + + + + + + + 64 + + + + + + true + + + + + + + + + + + + y2Mwm + + +
+ + + scott pinocchio chocolate 9 + + + + + + 1975-09-26T21:46:40.000Z + + + + + + option-A + + + + + + CW82EiUYuY + + + + + + + + + + + + + + + + false + + + + + + + + + + + + CW82E + + +
+ + + toyota toyota toyota 10 + + + + + + 1976-05-03T19:33:20.000Z + + + + + + option-B + + + + + + Q0u8gYkG6G + + + + + + + + + + + + + + 100 + + + + + + true + + + + + + + + + + + + Q0u8g + + +
+ + + helping whiteboard as 11 + + + + + + 1977-01-01T20:53:20.000Z + + + + + + option-C + + + + + + eUgE8O0yIy + + + + + + + + + + + + + + 121 + + + + + + false + + + + + + + + + + + + eUgE8 + + +
+ + + whiteboard can eat 12 + + + + + + 1977-09-25T01:46:40.000Z + + + + + + option-A + + + + + + sySKaEGgUg + + + + + + + + + + + + + + + + true + + + + + + + + + + + + sySKa + + +
+ + + as eat scott 13 + + + + + + 1978-07-11T10:13:20.000Z + + + + + + option-B + + + + + + 6SEQ24WOgO + + + + + + + + + + + + + + 169 + + + + + + false + + + + + + + + + + + + 6SEQ2 + + +
+ + + can pinocchio whiteboard 14 + + + + + + 1979-05-19T22:13:20.000Z + + + + + + option-C + + + + + + Kw0WUum6s6 + + + + + + + + + + + + + + 196 + + + + + + true + + + + + + + + + + + + Kw0WU + + +
+ + + bottle toyota bottle 15 + + + + + + 1980-04-19T13:46:40.000Z + + + + + + option-A + + + + + + YQmcwk2o4o + + + + + + + + + + + + + + + + false + + + + + + + + + + + + YQmcw + + +
+ + + eat whiteboard pinocchio 16 + + + + + + 1981-04-13T08:53:20.000Z + + + + + + option-B + + + + + + muYiOaIWGW + + + + + + + + + + + + + + 256 + + + + + + true + + + + + + + + + + + + muYiO + + +
+ + + chocolate can helping 17 + + + + + + 1982-04-30T07:33:20.000Z + + + + + + option-C + + + + + + 0OKoqQYESE + + + + + + + + + + + + + + 289 + + + + + + false + + + + + + + + + + + + 0OKoq + + +
+ + + pinocchio eat can 18 + + + + + + 1983-06-09T09:46:40.000Z + + + + + + option-A + + + + + + Es6uIGowew + + + + + + + + + + + + + + + + true + + + + + + + + + + + + Es6uI + + +
+ + + scott pinocchio chocolate 19 + + + + + + 1984-08-10T15:33:20.000Z + + + + + + option-B + + + + + + SMs0k64eqe + + + + + + + + + + + + + + 361 + + + + + + false + + + + + + + + + + + + SMs0k + + +
+ + + toyota toyota toyota 20 + + + + + + 1985-11-05T00:53:20.000Z + + + + + + option-C + + + + + + gqe6CwKM2M + + + + + + + + + + + + + + 400 + + + + + + true + + + + + + + + + + + + gqe6C + + +
+ + + helping whiteboard as 21 + + + + + + 1987-02-22T13:46:40.000Z + + + + + + option-A + + + + + + uKQCema4E4 + + + + + + + + + + + + + + + + false + + + + + + + + + + + + uKQCe + + +
+ + + whiteboard can eat 22 + + + + + + 1988-07-04T06:13:20.000Z + + + + + + option-B + + + + + + 8oCI6cqmQm + + + + + + + + + + + + + + 484 + + + + + + true + + + + + + + + + + + + 8oCI6 + + +
+ + + as eat scott 23 + + + + + + 1989-12-07T02:13:20.000Z + + + + + + option-C + + + + + + MIyOYS6UcU + + + + + + + + + + + + + + 529 + + + + + + false + + + + + + + + + + + + MIyOY + + +
+ + + can pinocchio whiteboard 24 + + + + + + 1991-06-04T01:46:40.000Z + + + + + + option-A + + + + + + amkU0IMCoC + + + + + + + + + + + + + + + + true + + + + + + + + + + + + amkU0 + + +
+ + + bottle toyota bottle 25 + + + + + + 1992-12-22T04:53:20.000Z + + + + + + option-B + + + + + + oGWaS8cu0u + + + + + + + + + + + + + + 625 + + + + + + false + + + + + + + + + + + + oGWaS + + +
+ + + eat whiteboard pinocchio 26 + + + + + + 1994-08-04T11:33:20.000Z + + + + + + option-C + + + + + + 2kIguyscCc + + + + + + + + + + + + + + 676 + + + + + + true + + + + + + + + + + + + 2kIgu + + +
+ + + chocolate can helping 27 + + + + + + 1996-04-08T21:46:40.000Z + + + + + + option-A + + + + + + GE4mMo8KOK + + + + + + + + + + + + + + + + false + + + + + + + + + + + + GE4mM + + +
+ + + pinocchio eat can 28 + + + + + + 1998-01-05T11:33:20.000Z + + + + + + option-B + + + + + + UiqsoeO2a2 + + + + + + + + + + + + + + 784 + + + + + + true + + + + + + + + + + + + Uiqso + + +
+ + + scott pinocchio chocolate 29 + + + + + + 1999-10-27T04:53:20.000Z + + + + + + option-C + + + + + + iCcyGUekmk + + + + + + + + + + + + + + 841 + + + + + + false + + + + + + + + + + + + iCcyG + + +
+ + + toyota toyota toyota 30 + + + + + + 2001-09-09T01:46:40.000Z + + + + + + option-A + + + + + + wgO4iKuSyS + + + + + + + + + + + + + + + + true + + + + + + + + + + + + wgO4i + + +
+ + + helping whiteboard as 31 + + + + + + 2003-08-16T02:13:20.000Z + + + + + + option-B + + + + + + AAAAAAAAAA + + + + + + + + + + + + + + 961 + + + + + + false + + + + + + + + + + + + AAAAA + + +
+ + + whiteboard can eat 32 + + + + + + 2005-08-14T06:13:20.000Z + + + + + + option-C + + + + + + OewGc0QsMs + + + + + + + + + + + + + + 1024 + + + + + + true + + + + + + + + + + + + OewGc + + +
+ + + as eat scott 33 + + + + + + 2007-09-05T13:46:40.000Z + + + + + + option-A + + + + + + c8iM4qgaYa + + + + + + + + + + + + + + + + false + + + + + + + + + + + + c8iM4 + + +
+ + + can pinocchio whiteboard 34 + + + + + + 2009-10-20T00:53:20.000Z + + + + + + option-B + + + + + + qcUSWgwIkI + + + + + + + + + + + + + + 1156 + + + + + + true + + + + + + + + + + + + qcUSW + + +
+ + + bottle toyota bottle 35 + + + + + + 2011-12-27T15:33:20.000Z + + + + + + option-C + + + + + + 46GYyWC0w0 + + + + + + + + + + + + + + 1225 + + + + + + false + + + + + + + + + + + + 46GYy + + +
+ + + eat whiteboard pinocchio 36 + + + + + + 2014-03-28T09:46:40.000Z + + + + + + option-A + + + + + + Ia2eQMSi8i + + + + + + + + + + + + + + + + true + + + + + + + + + + + + Ia2eQ + + +
+ + + chocolate can helping 37 + + + + + + 2016-07-20T07:33:20.000Z + + + + + + option-B + + + + + + W4oksCiQKQ + + + + + + + + + + + + + + 1369 + + + + + + false + + + + + + + + + + + + W4oks + + +
+ + + pinocchio eat can 38 + + + + + + 2018-12-05T08:53:20.000Z + + + + + + option-C + + + + + + kYaqK2y8W8 + + + + + + + + + + + + + + 1444 + + + + + + true + + + + + + + + + + + + kYaqK + + +
+ + + scott pinocchio chocolate 39 + + + + + + 2021-05-14T13:46:40.000Z + + + + + + option-A + + + + + + y2MwmsEqiq + + + + + + + + + + + + + + + + false + + + + + + + + + + + + y2Mwm + + +
+ + + toyota toyota toyota 40 + + + + + + 2023-11-14T22:13:20.000Z + + + + + + option-B + + + + + + CW82EiUYuY + + + + + + + + + + + + + + 1600 + + + + + + true + + + + + + + + + + + + CW82E + + +
+ + + helping whiteboard as 41 + + + + + + 2026-06-09T10:13:20.000Z + + + + + + option-C + + + + + + Q0u8gYkG6G + + + + + + + + + + + + + + 1681 + + + + + + false + + + + + + + + + + + + Q0u8g + + +
+ + + whiteboard can eat 42 + + + + + + 2029-01-25T01:46:40.000Z + + + + + + option-A + + + + + + eUgE8O0yIy + + + + + + + + + + + + + + + + true + + + + + + + + + + + + eUgE8 + + +
+ + + as eat scott 43 + + + + + + 2031-10-05T20:53:20.000Z + + + + + + option-B + + + + + + sySKaEGgUg + + + + + + + + + + + + + + 1849 + + + + + + false + + + + + + + + + + + + sySKa + + +
+ + + can pinocchio whiteboard 44 + + + + + + 2034-07-08T19:33:20.000Z + + + + + + option-C + + + + + + 6SEQ24WOgO + + + + + + + + + + + + + + 1936 + + + + + + true + + + + + + + + + + + + 6SEQ2 + + +
+ + + bottle toyota bottle 45 + + + + + + 2037-05-03T21:46:40.000Z + + + + + + option-A + + + + + + Kw0WUum6s6 + + + + + + + + + + + + + + + + false + + + + + + + + + + + + Kw0WU + + +
+ + + eat whiteboard pinocchio 46 + + + + + + 2040-03-22T03:33:20.000Z + + + + + + option-B + + + + + + YQmcwk2o4o + + + + + + + + + + + + + + 2116 + + + + + + true + + + + + + + + + + + + YQmcw + + +
+ + + chocolate can helping 47 + + + + + + 2043-03-03T12:53:20.000Z + + + + + + option-C + + + + + + muYiOaIWGW + + + + + + + + + + + + + + 2209 + + + + + + false + + + + + + + + + + + + muYiO + + +
+ + + pinocchio eat can 48 + + + + + + 2046-03-07T01:46:40.000Z + + + + + + option-A + + + + + + 0OKoqQYESE + + + + + + + + + + + + + + + + true + + + + + + + + + + + + 0OKoq + + +
+ + + scott pinocchio chocolate 49 + + + + + + 2049-04-02T18:13:20.000Z + + + + + + option-B + + + + + + Es6uIGowew + + + + + + + + + + + + + + 2401 + + + + + + false + + + + + + + + + + + + Es6uI + + +
+ + + toyota toyota toyota 50 + + + + + + 2052-05-22T14:13:20.000Z + + + + + + option-C + + + + + + SMs0k64eqe + + + + + + + + + + + + + + 2500 + + + + + + true + + + + + + + + + + + + SMs0k + + +
+ + + helping whiteboard as 51 + + + + + + 2055-08-04T13:46:40.000Z + + + + + + option-A + + + + + + gqe6CwKM2M + + + + + + + + + + + + + + + + false + + + + + + + + + + + + gqe6C + + +
+ + + whiteboard can eat 52 + + + + + + 2058-11-08T16:53:20.000Z + + + + + + option-B + + + + + + uKQCema4E4 + + + + + + + + + + + + + + 2704 + + + + + + true + + + + + + + + + + + + uKQCe + + +
+ + + as eat scott 53 + + + + + + 2062-03-07T23:33:20.000Z + + + + + + option-C + + + + + + 8oCI6cqmQm + + + + + + + + + + + + + + 2809 + + + + + + false + + + + + + + + + + + + 8oCI6 + + +
+ + + can pinocchio whiteboard 54 + + + + + + 2065-07-28T09:46:40.000Z + + + + + + option-A + + + + + + MIyOYS6UcU + + + + + + + + + + + + + + + + true + + + + + + + + + + + + MIyOY + + +
+ + + bottle toyota bottle 55 + + + + + + 2069-01-09T23:33:20.000Z + + + + + + option-B + + + + + + amkU0IMCoC + + + + + + + + + + + + + + 3025 + + + + + + false + + + + + + + + + + + + amkU0 + + +
+ + + eat whiteboard pinocchio 56 + + + + + + 2072-07-17T16:53:20.000Z + + + + + + option-C + + + + + + oGWaS8cu0u + + + + + + + + + + + + + + 3136 + + + + + + true + + + + + + + + + + + + oGWaS + + +
+ + + chocolate can helping 57 + + + + + + 2076-02-15T13:46:40.000Z + + + + + + option-A + + + + + + 2kIguyscCc + + + + + + + + + + + + + + + + false + + + + + + + + + + + + 2kIgu + + +
+ + + pinocchio eat can 58 + + + + + + 2079-10-08T14:13:20.000Z + + + + + + option-B + + + + + + GE4mMo8KOK + + + + + + + + + + + + + + 3364 + + + + + + true + + + + + + + + + + + + GE4mM + + +
+ + + scott pinocchio chocolate 59 + + + + + + 2083-06-23T18:13:20.000Z + + + + + + option-C + + + + + + UiqsoeO2a2 + + + + + + + + + + + + + + 3481 + + + + + + false + + + + + + + + + + + + Uiqso + + +
+ + + toyota toyota toyota 60 + + + + + + 2087-04-01T01:46:40.000Z + + + + + + option-A + + + + + + iCcyGUekmk + + + + + + + + + + + + + + + + true + + + + + + + + + + + + iCcyG + + +
+ + + helping whiteboard as 61 + + + + + + 2091-01-30T12:53:20.000Z + + + + + + option-B + + + + + + wgO4iKuSyS + + + + + + + + + + + + + + 3721 + + + + + + false + + + + + + + + + + + + wgO4i + + +
+ + + whiteboard can eat 62 + + + + + + 2094-12-24T03:33:20.000Z + + + + + + option-C + + + + + + AAAAAAAAAA + + + + + + + + + + + + + + 3844 + + + + + + true + + + + + + + + + + + + AAAAA + + +
+ + + as eat scott 63 + + + + + + 2098-12-09T21:46:40.000Z + + + + + + option-A + + + + + + OewGc0QsMs + + + + + + + + + + + + + + + + false + + + + + + + + + + + + OewGc + + +
+ + + can pinocchio whiteboard 64 + + + + + + 2102-12-19T19:33:20.000Z + + + + + + option-B + + + + + + c8iM4qgaYa + + + + + + + + + + + + + + 4096 + + + + + + true + + + + + + + + + + + + c8iM4 + + +
+ + + bottle toyota bottle 65 + + + + + + 2107-01-20T20:53:20.000Z + + + + + + option-C + + + + + + qcUSWgwIkI + + + + + + + + + + + + + + 4225 + + + + + + false + + + + + + + + + + + + qcUSW + + +
+ + + eat whiteboard pinocchio 66 + + + + + + 2111-03-17T01:46:40.000Z + + + + + + option-A + + + + + + 46GYyWC0w0 + + + + + + + + + + + + + + + + true + + + + + + + + + + + + 46GYy + + +
+ + + chocolate can helping 67 + + + + + + 2115-06-03T10:13:20.000Z + + + + + + option-B + + + + + + Ia2eQMSi8i + + + + + + + + + + + + + + 4489 + + + + + + false + + + + + + + + + + + + Ia2eQ + + +
+ + + pinocchio eat can 68 + + + + + + 2119-09-12T22:13:20.000Z + + + + + + option-C + + + + + + W4oksCiQKQ + + + + + + + + + + + + + + 4624 + + + + + + true + + + + + + + + + + + + W4oks + + +
+ + + scott pinocchio chocolate 69 + + + + + + 2124-01-15T13:46:40.000Z + + + + + + option-A + + + + + + kYaqK2y8W8 + + + + + + + + + + + + + + + + false + + + + + + + + + + + + kYaqK + + +
+ + + toyota toyota toyota 70 + + + + + + 2128-06-11T08:53:20.000Z + + + + + + option-B + + + + + + y2MwmsEqiq + + + + + + + + + + + + + + 4900 + + + + + + true + + + + + + + + + + + + y2Mwm + + +
+ + + helping whiteboard as 71 + + + + + + 2132-11-29T07:33:20.000Z + + + + + + option-C + + + + + + CW82EiUYuY + + + + + + + + + + + + + + 5041 + + + + + + false + + + + + + + + + + + + CW82E + + +
+ + + whiteboard can eat 72 + + + + + + 2137-06-11T09:46:40.000Z + + + + + + option-A + + + + + + Q0u8gYkG6G + + + + + + + + + + + + + + + + true + + + + + + + + + + + + Q0u8g + + +
+ + + as eat scott 73 + + + + + + 2142-01-14T15:33:20.000Z + + + + + + option-B + + + + + + eUgE8O0yIy + + + + + + + + + + + + + + 5329 + + + + + + false + + + + + + + + + + + + eUgE8 + + +
+ + + can pinocchio whiteboard 74 + + + + + + 2146-09-12T00:53:20.000Z + + + + + + option-C + + + + + + sySKaEGgUg + + + + + + + + + + + + + + 5476 + + + + + + true + + + + + + + + + + + + sySKa + + +
+ + + bottle toyota bottle 75 + + + + + + 2151-06-02T13:46:40.000Z + + + + + + option-A + + + + + + 6SEQ24WOgO + + + + + + + + + + + + + + + + false + + + + + + + + + + + + 6SEQ2 + + +
+ + + eat whiteboard pinocchio 76 + + + + + + 2156-03-15T06:13:20.000Z + + + + + + option-B + + + + + + Kw0WUum6s6 + + + + + + + + + + + + + + 5776 + + + + + + true + + + + + + + + + + + + Kw0WU + + +
+ + + chocolate can helping 77 + + + + + + 2161-01-19T02:13:20.000Z + + + + + + option-C + + + + + + YQmcwk2o4o + + + + + + + + + + + + + + 5929 + + + + + + false + + + + + + + + + + + + YQmcw + + +
+ + + pinocchio eat can 78 + + + + + + 2165-12-18T01:46:40.000Z + + + + + + option-A + + + + + + muYiOaIWGW + + + + + + + + + + + + + + + + true + + + + + + + + + + + + muYiO + + +
+ + + scott pinocchio chocolate 79 + + + + + + 2170-12-09T04:53:20.000Z + + + + + + option-B + + + + + + 0OKoqQYESE + + + + + + + + + + + + + + 6241 + + + + + + false + + + + + + + + + + + + 0OKoq + + +
+ + + toyota toyota toyota 80 + + + + + + 2175-12-23T11:33:20.000Z + + + + + + option-C + + + + + + Es6uIGowew + + + + + + + + + + + + + + 6400 + + + + + + true + + + + + + + + + + + + Es6uI + + +
+ + + helping whiteboard as 81 + + + + + + 2181-01-28T21:46:40.000Z + + + + + + option-A + + + + + + SMs0k64eqe + + + + + + + + + + + + + + + + false + + + + + + + + + + + + SMs0k + + +
+ + + whiteboard can eat 82 + + + + + + 2186-03-30T11:33:20.000Z + + + + + + option-B + + + + + + gqe6CwKM2M + + + + + + + + + + + + + + 6724 + + + + + + true + + + + + + + + + + + + gqe6C + + +
+ + + as eat scott 83 + + + + + + 2191-06-22T04:53:20.000Z + + + + + + option-C + + + + + + uKQCema4E4 + + + + + + + + + + + + + + 6889 + + + + + + false + + + + + + + + + + + + uKQCe + + +
+ + + can pinocchio whiteboard 84 + + + + + + 2196-10-06T01:46:40.000Z + + + + + + option-A + + + + + + 8oCI6cqmQm + + + + + + + + + + + + + + + + true + + + + + + + + + + + + 8oCI6 + + +
+ + + bottle toyota bottle 85 + + + + + + 2202-02-14T02:13:20.000Z + + + + + + option-B + + + + + + MIyOYS6UcU + + + + + + + + + + + + + + 7225 + + + + + + false + + + + + + + + + + + + MIyOY + + +
+ + + eat whiteboard pinocchio 86 + + + + + + 2207-07-17T06:13:20.000Z + + + + + + option-C + + + + + + amkU0IMCoC + + + + + + + + + + + + + + 7396 + + + + + + true + + + + + + + + + + + + amkU0 + + +
+ + + chocolate can helping 87 + + + + + + 2213-01-08T13:46:40.000Z + + + + + + option-A + + + + + + oGWaS8cu0u + + + + + + + + + + + + + + + + false + + + + + + + + + + + + oGWaS + + +
+ + + pinocchio eat can 88 + + + + + + 2218-07-27T00:53:20.000Z + + + + + + option-B + + + + + + 2kIguyscCc + + + + + + + + + + + + + + 7744 + + + + + + true + + + + + + + + + + + + 2kIgu + + +
+ + + scott pinocchio chocolate 89 + + + + + + 2224-03-05T15:33:20.000Z + + + + + + option-C + + + + + + GE4mMo8KOK + + + + + + + + + + + + + + 7921 + + + + + + false + + + + + + + + + + + + GE4mM + + +
+ + + toyota toyota toyota 90 + + + + + + 2229-11-06T09:46:40.000Z + + + + + + option-A + + + + + + UiqsoeO2a2 + + + + + + + + + + + + + + + + true + + + + + + + + + + + + Uiqso + + +
+ + + helping whiteboard as 91 + + + + + + 2235-08-02T07:33:20.000Z + + + + + + option-B + + + + + + iCcyGUekmk + + + + + + + + + + + + + + 8281 + + + + + + false + + + + + + + + + + + + iCcyG + + +
+ + + whiteboard can eat 92 + + + + + + 2241-05-20T08:53:20.000Z + + + + + + option-C + + + + + + wgO4iKuSyS + + + + + + + + + + + + + + 8464 + + + + + + true + + + + + + + + + + + + wgO4i + + +
+ + + as eat scott 93 + + + + + + 2247-03-31T13:46:40.000Z + + + + + + option-A + + + + + + AAAAAAAAAA + + + + + + + + + + + + + + + + false + + + + + + + + + + + + AAAAA + + +
+ + + can pinocchio whiteboard 94 + + + + + + 2253-03-03T22:13:20.000Z + + + + + + option-B + + + + + + OewGc0QsMs + + + + + + + + + + + + + + 8836 + + + + + + true + + + + + + + + + + + + OewGc + + +
+ + + bottle toyota bottle 95 + + + + + + 2259-02-28T10:13:20.000Z + + + + + + option-C + + + + + + c8iM4qgaYa + + + + + + + + + + + + + + 9025 + + + + + + false + + + + + + + + + + + + c8iM4 + + +
+ + + eat whiteboard pinocchio 96 + + + + + + 2265-03-19T01:46:40.000Z + + + + + + option-A + + + + + + qcUSWgwIkI + + + + + + + + + + + + + + + + true + + + + + + + + + + + + qcUSW + + +
+ + + chocolate can helping 97 + + + + + + 2271-04-30T20:53:20.000Z + + + + + + option-B + + + + + + 46GYyWC0w0 + + + + + + + + + + + + + + 9409 + + + + + + false + + + + + + + + + + + + 46GYy + + +
+ + + pinocchio eat can 98 + + + + + + 2277-07-04T19:33:20.000Z + + + + + + option-C + + + + + + Ia2eQMSi8i + + + + + + + + + + + + + + 9604 + + + + + + true + + + + + + + + + + + + Ia2eQ + + +
+ + + scott pinocchio chocolate 99 + + + + + + 2283-10-01T21:46:40.000Z + + + + + + option-A + + + + + + W4oksCiQKQ + + + + + + + + + + + + + + + + false + + + + + + + + + + + + W4oks + + +
+
+
+
+
+`; diff --git a/packages/react/src/components/Table/__snapshots__/Table.story.storyshot b/packages/react/src/components/Table/__snapshots__/Table.story.storyshot index 755a4f37c8..dd24bef3ee 100644 --- a/packages/react/src/components/Table/__snapshots__/Table.story.storyshot +++ b/packages/react/src/components/Table/__snapshots__/Table.story.storyshot @@ -135,6 +135,39 @@ exports[`Storybook Snapshot tests and console checks Storyshots 1 - Watson IoT/T /> + +