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
35 changes: 35 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,38 @@ 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 {string[]|integer[]} ids A list of record identifiers
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved
* @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 useExpandedMultiple = (
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved
resource: string,
ids: Identifier[]
): [boolean, () => void] => {
const [expandedIds, setExpandedIds] = useStore<Identifier[]>(
`${resource}.datagrid.expanded`,
[]
);

const expanded = expandedIds.some(id => ids.some(id2 => id2 == id));
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved

const setExpanded = useCallback(() => {
const filtered = expandedIds.filter(
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved
expanded_id => !ids.some(id => id === expanded_id)
);
setExpandedIds(expanded ? filtered : filtered.concat(ids));
}, [expandedIds, setExpandedIds, expanded, ids]);

return [expanded, setExpanded];
};
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
44 changes: 44 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,44 @@
import * as React from 'react';
import { ElementType, memo } from 'react';
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useTranslate, useExpandedMultiple } from 'ra-core';
import { DatagridClasses } from './useDatagridStyles';
import clsx from 'clsx';

interface ExpandMultipleButtonProps {
resource: string;
ids: string[] | number[];
}
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved

const ExpandAllButton = ({ resource, ids }: ExpandMultipleButtonProps) => {
const translate = useTranslate();
const [expanded, toggleExpanded] = useExpandedMultiple(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"
component="div"
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved
onClick={toggleExpanded}
size="small"
>
<ExpandMoreIcon fontSize="inherit" />
</IconButton>
);
};

export interface ExpandRowButtonProps extends IconButtonProps {
component?: ElementType;
expanded: boolean;
expandContentId?: string;
}
hiaselhans marked this conversation as resolved.
Show resolved Hide resolved

export default memo(ExpandAllButton);