Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add "expand_all" toggle to datagrid header #8152

Merged
merged 12 commits into from
Sep 12, 2022
21 changes: 19 additions & 2 deletions cypress/integration/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('List Page', () => {
it('should accept multiple expands', () => {
cy.contains('1-10 of 13'); // wait for data
cy.get('[aria-label="Expand"]')
.eq(0)
.eq(1) // we have to skip the first button (expand-all button)
.click()
.should(el => expect(el).to.have.attr('aria-expanded', 'true'))
.should(el => expect(el).to.have.attr('aria-label', 'Close'));
Expand All @@ -270,12 +270,29 @@ describe('List Page', () => {
cy.wait(500); // Ensure animations are done

cy.get('[aria-label="Expand"]')
.eq(0) // We still target the first button labeled Expand because the previous one should now have a Close label
.eq(0) // We now target the first button because the expand-all button and expand-13 should now have a Close label
.click()
.should(el => expect(el).to.have.attr('aria-expanded', 'true'))
.should(el => expect(el).to.have.attr('aria-label', 'Close'));

cy.get('#12-expand').should(el => expect(el).to.exist);

let all_labels = [13, 12, 11, 10, 9, 8, 7, 6, 4, 2];

// two opened => collapse all
cy.get('[aria-label="Close"]').eq(0).click();
all_labels.forEach(label => {
cy.get(`#${label}-expand`).should(
el => expect(el).not.to.exist
);
});

// expand all
cy.get('[aria-label="Expand"]').eq(0).click();

all_labels.forEach(label => {
cy.get(`#${label}-expand`).should(el => expect(el).to.exist);
});
});
});

Expand Down
43 changes: 43 additions & 0 deletions packages/ra-core/src/controller/list/useExpanded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,46 @@ export const useExpanded = (

return [expanded, toggleExpanded];
};

/**
* State-like hook for controlling the expanded state of many list items
* expanded state is true when at least one item from ids is expanded.
*
* @param {string} resource The resource name, e.g. 'posts'
* @param {Identifier[]} ids A list of record identifiers
* @returns {Object} Destructure as [expanded, toggleExpanded].
*
* @example
*
* const [expanded, toggleExpanded] = useExpandedMultiple('posts', [123, 124, 125]);
* const expandIcon = expanded ? ExpandLess : ExpandMore;
* const onExpandClick = () => toggleExpanded();
*/
export const useExpandAll = (
resource: string,
ids: Identifier[]
): [boolean, () => void] => {
const [expandedIds, setExpandedIds] = useStore<Identifier[]>(
`${resource}.datagrid.expanded`,
[]
);

const isEexpanded = Array.isArray(expandedIds)
? // eslint-disable-next-line eqeqeq
expandedIds.some(id => ids.some(id2 => id2 == id))
: false;

const toggleExpandedAll = useCallback(() => {
const unaffectedExpandedIds = expandedIds.filter(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this "unaffected".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible synonyms:

  • unChanged
  • unAltered
  • unTouched

// eslint-disable-next-line eqeqeq
expanded_id => !ids.some(id => id == expanded_id)
);
setExpandedIds(
isEexpanded
? unaffectedExpandedIds
: unaffectedExpandedIds.concat(ids)
);
}, [expandedIds, setExpandedIds, isEexpanded, ids]);

return [isEexpanded, toggleExpandedAll];
};
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const Basic = () => (
const ExpandPanel = () => {
const book = useRecordContext();
return (
<div>
<div data-testid="ExpandPanel">
<i>{book.title}</i>, by {book.author} ({book.year})
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import clsx from 'clsx';

import DatagridHeaderCell from './DatagridHeaderCell';
import { DatagridClasses } from './useDatagridStyles';
import ExpandAllButton from './ExpandAllButton';

/**
* The default Datagrid Header component.
Expand Down Expand Up @@ -93,7 +94,12 @@ export const DatagridHeader = (props: DatagridHeaderProps) => {
DatagridClasses.headerCell,
DatagridClasses.expandHeader
)}
/>
>
<ExpandAllButton
resource={resource}
ids={data.map(record => record.id)}
/>
</TableCell>
)}
{hasBulkActions && selectedIds && (
<TableCell
Expand Down
37 changes: 37 additions & 0 deletions packages/ra-ui-materialui/src/list/datagrid/ExpandAllButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import { memo } from 'react';
import IconButton from '@mui/material/IconButton';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useTranslate, useExpandAll } from 'ra-core';
import { DatagridClasses } from './useDatagridStyles';
import clsx from 'clsx';

interface ExpandAllButtonProps {
resource: string;
ids: string[] | number[];
}

const ExpandAllButton = ({ resource, ids }: ExpandAllButtonProps) => {
const translate = useTranslate();
const [expanded, toggleExpanded] = useExpandAll(resource, ids);

return (
<IconButton
className={clsx(DatagridClasses.expandIcon, {
[DatagridClasses.expanded]: expanded,
})}
aria-label={translate(
expanded ? 'ra.action.close' : 'ra.action.expand'
)}
aria-expanded={expanded}
tabIndex={-1}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this tabindex?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dito

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since ExpandRowButtons are not tab-selectable, the least i would do here is to stay consistent and keep tabindex = -1.

aria-hidden="true"
onClick={toggleExpanded}
size="small"
>
<ExpandMoreIcon fontSize="inherit" />
</IconButton>
);
};

export default memo(ExpandAllButton);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved

import { Expand } from './Datagrid.stories';

describe('ExpandAllButton', () => {
it('should expand all rows at once', async () => {
render(<Expand />);
const expand = () => {
const button = screen.getAllByLabelText('ra.action.expand')[0];
fireEvent.click(button);
};
const collapse = () => {
const button = screen.getAllByLabelText('ra.action.close')[0];
fireEvent.click(button);
};

const expectExpandedRows = (count: number) => {
expect(screen.queryAllByTestId('ExpandPanel')).toHaveLength(count);
};

expectExpandedRows(0);

expand();
expectExpandedRows(4);

collapse();
expectExpandedRows(0);
});
});