diff --git a/src/components/TableToolsTable/TableToolsTable.stories.js b/src/components/TableToolsTable/TableToolsTable.stories.js index 97729c7..51676b2 100644 --- a/src/components/TableToolsTable/TableToolsTable.stories.js +++ b/src/components/TableToolsTable/TableToolsTable.stories.js @@ -54,6 +54,7 @@ const argProps = { customEmptyState: propTypes.bool, enableExport: propTypes.bool, enableDetails: propTypes.bool, + enableExpandAll: propTypes.bool, enableBulkSelect: propTypes.bool, enablePreselection: propTypes.bool, enableSimpleBulkSelect: propTypes.bool, @@ -84,6 +85,7 @@ const meta = { customEmptyState: true, enableExport: true, enableDetails: true, + enableExpandAll: true, enableBulkSelect: true, enablePreselection: false, enableSimpleBulkSelect: false, @@ -122,6 +124,7 @@ const CommonExample = ({ customEmptyState, enableExport, enableDetails, + enableExpandAll, enableBulkSelect, enablePreselection, enableSimpleBulkSelect, @@ -193,6 +196,7 @@ const CommonExample = ({ ...(customEmptyState ? { EmptyState: CustomEmptyState } : {}), ...(enableExport ? { exporter } : {}), ...(enableDetails ? { detailsComponent: DetailsRow } : {}), + canCollapseAll: enableExpandAll, ...(enableBulkSelect ? { ...(enablePreselection ? { selected: selectedItemIds } : {}), diff --git a/src/hooks/useExpandable/useExpandable.js b/src/hooks/useExpandable/useExpandable.js index d520b47..8516e22 100644 --- a/src/hooks/useExpandable/useExpandable.js +++ b/src/hooks/useExpandable/useExpandable.js @@ -19,6 +19,8 @@ import { itemDetailsRow, addExpandProp } from './helpers'; * @param {object} [options] AsyncTableTools options * @param {object} [options.detailsComponent] A component that should be rendered as a details row * @param {object} [options.detailsProps] Props spread onto each details row + * @param {Array} [options.items] Items currently rendered in the table (used for expand/collapse all) + * @param {boolean} [options.canCollapseAll] Whether to enable the expand/collapse all toggle in the table header (defaults to true) * * @returns {useExpandableReturn} An object of props meant to be used in the {@link TableToolsTable} * @@ -27,12 +29,24 @@ import { itemDetailsRow, addExpandProp } from './helpers'; */ const useExpandable = (options) => { const enableExpandingRow = !!options?.detailsComponent || !!options.treeTable; - const { selection: openItems, toggle } = useSelectionManager([]); + const { selection: openItems, toggle, set, clear } = useSelectionManager([]); // TODO If the selection manager is based on `useTableState`, observes can be used to reset open items const [, setOpenItemsState] = useTableState('open-items'); - const onCollapse = (_event, _index, _isOpen, { item: { itemId } }) => - toggle(itemId); + const onCollapse = useCallback( + (_event, rowIndex, isOpen, rowData) => { + if (rowIndex === undefined) { + if (isOpen) { + set(options.items?.map((item) => item.itemId)); + } else { + clear(); + } + } else { + toggle(rowData?.item?.itemId); + } + }, + [options.items, toggle, set, clear], + ); const isItemOpen = useCallback( (itemId) => (openItems || []).includes(itemId), @@ -71,6 +85,9 @@ const useExpandable = (options) => { ...(enableExpandingRow ? { tableProps: { + ...(!options.enableTreeView + ? { canCollapseAll: options.canCollapseAll !== false } + : {}), onCollapse, }, } diff --git a/src/hooks/useExpandable/useExpandable.test.js b/src/hooks/useExpandable/useExpandable.test.js index aaf89ae..ce18703 100644 --- a/src/hooks/useExpandable/useExpandable.test.js +++ b/src/hooks/useExpandable/useExpandable.test.js @@ -26,6 +26,7 @@ describe('useExpandable', () => { expect(result.current).toEqual({ tableProps: { + canCollapseAll: true, onCollapse: expect.any(Function), }, tableView: { @@ -45,7 +46,7 @@ describe('useExpandable', () => { ); act(() => { - result.current.tableProps.onCollapse(undefined, undefined, undefined, { + result.current.tableProps.onCollapse(null, 0, true, { item: { itemId }, }); }); @@ -104,6 +105,64 @@ describe('useExpandable', () => { expect(result.current.tableView.isItemOpen('test-id')).toBe(false); }); + it('should expand all rows', () => { + const items = [ + { itemId: 'item-1' }, + { itemId: 'item-2' }, + { itemId: 'item-3' }, + ]; + const { result } = renderHook( + () => useExpandable({ ...defaultOptions, items }), + DEFAULT_RENDER_OPTIONS, + ); + + act(() => { + result.current.tableProps.onCollapse(null, undefined, true); + }); + + expect(result.current.tableView.isItemOpen('item-1')).toBe(true); + expect(result.current.tableView.isItemOpen('item-2')).toBe(true); + expect(result.current.tableView.isItemOpen('item-3')).toBe(true); + }); + + it('should collapse all rows', () => { + const items = [{ itemId: 'item-1' }, { itemId: 'item-2' }]; + const { result } = renderHook( + () => useExpandable({ ...defaultOptions, items }), + DEFAULT_RENDER_OPTIONS, + ); + + // expand all first + act(() => { + result.current.tableProps.onCollapse(null, undefined, true); + }); + + expect(result.current.tableView.isItemOpen('item-1')).toBe(true); + expect(result.current.tableView.isItemOpen('item-2')).toBe(true); + + // collapse all + act(() => { + result.current.tableProps.onCollapse(null, undefined, false); + }); + + expect(result.current.tableView.isItemOpen('item-1')).toBe(false); + expect(result.current.tableView.isItemOpen('item-2')).toBe(false); + }); + + it('should not pass canCollapseAll when enableTreeView is set', () => { + const { result } = renderHook( + () => + useExpandable({ + ...defaultOptions, + enableTreeView: true, + }), + DEFAULT_RENDER_OPTIONS, + ); + + expect(result.current.tableProps).not.toHaveProperty('canCollapseAll'); + expect(result.current.tableProps).toHaveProperty('onCollapse'); + }); + it('includes control columns in details row colSpan', () => { const { result } = renderHook( () => diff --git a/src/hooks/useTableTools/useTableTools.js b/src/hooks/useTableTools/useTableTools.js index 6727dbb..b3b249f 100644 --- a/src/hooks/useTableTools/useTableTools.js +++ b/src/hooks/useTableTools/useTableTools.js @@ -77,7 +77,7 @@ const useTableTools = ( const { tableProps: expandableTableProps, tableView: expandableTableViewOptions, - } = useExpandable(options); + } = useExpandable({ ...options, items }); const { tableProps: radioSelectTableProps } = useRadioSelect({ ...options,