diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md index 589290a0e1ebb..0b92b9fd1ad46 100644 --- a/docs/pages/x/api/data-grid/grid-api.md +++ b/docs/pages/x/api/data-grid/grid-api.md @@ -113,9 +113,9 @@ import { GridApi } from '@mui/x-data-grid'; | setRowSelectionModel | (rowIds: GridRowId[]) => void | Updates the selected rows to be those passed to the `rowIds` argument.
Any row already selected will be unselected. | | setSortModel | (model: GridSortModel) => void | Updates the sort model and triggers the sorting of rows. | | showColumnMenu | (field: string) => void | Display the column menu under the `field` column. | -| showFilterPanel | (targetColumnField?: string) => void | Shows the filter panel. If `targetColumnField` is given, a filter for this field is also added. | +| showFilterPanel | (targetColumnField?: string, panelId?: string, labelId?: string) => void | Shows the filter panel. If `targetColumnField` is given, a filter for this field is also added. | | showHeaderFilterMenu | (field: GridColDef['field']) => void | Opens the header filter menu for the given field. | -| showPreferences | (newValue: GridPreferencePanelsValue) => void | Displays the preferences panel. The `newValue` argument controls the content of the panel. | +| showPreferences | (newValue: GridPreferencePanelsValue, panelId?: string, labelId?: string) => void | Displays the preferences panel. The `newValue` argument controls the content of the panel. | | sortColumn | (column: GridColDef, direction?: GridSortDirection, allowMultipleSorting?: boolean) => void | Sorts a column. | | startCellEditMode | (params: GridStartCellEditModeParams) => void | Puts the cell corresponding to the given row id and field into edit mode. | | startHeaderFilterEditMode | (field: GridColDef['field']) => void | Puts the cell corresponding to the given row id and field into edit mode. | diff --git a/docs/pages/x/api/data-grid/grid-filter-api.json b/docs/pages/x/api/data-grid/grid-filter-api.json index b120ad763bf5b..08c8d1f9593d7 100644 --- a/docs/pages/x/api/data-grid/grid-filter-api.json +++ b/docs/pages/x/api/data-grid/grid-filter-api.json @@ -26,7 +26,7 @@ { "name": "showFilterPanel", "description": "Shows the filter panel. If targetColumnField is given, a filter for this field is also added.", - "type": "(targetColumnField?: string) => void" + "type": "(targetColumnField?: string, panelId?: string, labelId?: string) => void" }, { "name": "upsertFilterItem", diff --git a/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx index b228fdfe3e723..bbf538399d30d 100644 --- a/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx @@ -71,6 +71,8 @@ const FULL_INITIAL_STATE: GridInitialState = { preferencePanel: { open: true, openedPanelValue: GridPreferencePanelsValue.filters, + panelId: undefined, + labelId: undefined, }, sorting: { sortModel: [{ field: 'id', sort: 'desc' }], diff --git a/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx index 5eb94358b0d78..103f594dc95c1 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridActionsCell.tsx @@ -199,9 +199,9 @@ function GridActionsCell(props: GridActionsCellProps) { ref={buttonRef} id={buttonId} aria-label={apiRef.current.getLocaleText('actionsCellMore')} - aria-controls={menuId} - aria-expanded={open ? 'true' : undefined} - aria-haspopup="true" + aria-haspopup="menu" + aria-expanded={open} + aria-controls={open ? menuId : undefined} role="menuitem" size="small" onClick={showMenu} diff --git a/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx b/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx index 1eb387b8e118b..16566f378d74e 100644 --- a/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx +++ b/packages/grid/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx @@ -59,9 +59,9 @@ export const ColumnHeaderMenuIcon = React.memo((props: ColumnHeaderMenuIconProps aria-label={apiRef.current.getLocaleText('columnMenuLabel')} size="small" onClick={handleMenuIconClick} - aria-expanded={open ? 'true' : undefined} - aria-haspopup="true" - aria-controls={columnMenuId} + aria-haspopup="menu" + aria-expanded={open} + aria-controls={open ? columnMenuId : undefined} id={columnMenuButtonId} {...rootProps.slotProps?.baseIconButton} > diff --git a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx index e9c348f056725..d9e8e77041dbe 100644 --- a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx +++ b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { unstable_composeClasses as composeClasses } from '@mui/utils'; +import { unstable_composeClasses as composeClasses, unstable_useId as useId } from '@mui/utils'; import Badge from '@mui/material/Badge'; +import { useGridSelector } from '../../hooks'; import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector'; import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; @@ -37,6 +38,9 @@ function GridColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonPro const rootProps = useGridRootProps(); const ownerState = { ...props, classes: rootProps.classes }; const classes = useUtilityClasses(ownerState); + const preferencePanel = useGridSelector(apiRef, gridPreferencePanelStateSelector); + const labelId = useId(); + const panelId = useId(); const toggleFilter = React.useCallback( (event: React.MouseEvent) => { @@ -48,27 +52,33 @@ function GridColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonPro if (open && openedPanelValue === GridPreferencePanelsValue.filters) { apiRef.current.hideFilterPanel(); } else { - apiRef.current.showFilterPanel(); + apiRef.current.showFilterPanel(undefined, panelId, labelId); } if (onClick) { onClick(apiRef.current.getColumnHeaderParams(field), event); } }, - [apiRef, field, onClick], + [apiRef, field, onClick, panelId, labelId], ); if (!counter) { return null; } + const open = preferencePanel.open && preferencePanel.labelId === labelId; + const iconButton = ( diff --git a/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx b/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx index 90532465eb55e..fe3083aa87ac6 100644 --- a/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx +++ b/packages/grid/x-data-grid/src/components/panel/GridPreferencesPanel.tsx @@ -26,6 +26,8 @@ export const GridPreferencesPanel = React.forwardRef< ref={ref} as={rootProps.slots.basePopper} open={columns.length > 0 && preferencePanelState.open} + id={preferencePanelState.panelId} + aria-labelledby={preferencePanelState.labelId} {...rootProps.slotProps?.panel} {...props} {...rootProps.slotProps?.basePopper} diff --git a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx index b582717e904b3..e1fb78e7435c6 100644 --- a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx +++ b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { ButtonProps } from '@mui/material/Button'; +import { unstable_useId as useId } from '@mui/material/utils'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector'; import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; @@ -9,15 +10,25 @@ import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; export const GridToolbarColumnsButton = React.forwardRef( function GridToolbarColumnsButton(props, ref) { const { onClick, ...other } = props; + const columnButtonId = useId(); + const columnPanelId = useId(); + const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - const { open, openedPanelValue } = useGridSelector(apiRef, gridPreferencePanelStateSelector); + const preferencePanel = useGridSelector(apiRef, gridPreferencePanelStateSelector); const showColumns = (event: React.MouseEvent) => { - if (open && openedPanelValue === GridPreferencePanelsValue.columns) { + if ( + preferencePanel.open && + preferencePanel.openedPanelValue === GridPreferencePanelsValue.columns + ) { apiRef.current.hidePreferences(); } else { - apiRef.current.showPreferences(GridPreferencePanelsValue.columns); + apiRef.current.showPreferences( + GridPreferencePanelsValue.columns, + columnPanelId, + columnButtonId, + ); } onClick?.(event); @@ -28,11 +39,17 @@ export const GridToolbarColumnsButton = React.forwardRef} {...other} onClick={showColumns} diff --git a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx index 2e54d7d626c10..61567ce612785 100644 --- a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx +++ b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx @@ -107,9 +107,9 @@ export const GridToolbarDensitySelector = React.forwardRef(null); @@ -58,11 +58,11 @@ export const GridToolbarExportContainer = React.forwardRef} - aria-expanded={open ? 'true' : undefined} + aria-expanded={open} aria-label={apiRef.current.getLocaleText('toolbarExportLabel')} aria-haspopup="menu" - aria-labelledby={menuId} - id={buttonId} + aria-controls={open ? exportMenuId : undefined} + id={exportButtonId} {...other} onClick={handleMenuOpen} {...rootProps.slotProps?.baseButton} @@ -76,9 +76,9 @@ export const GridToolbarExportContainer = React.forwardRef diff --git a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx index 36f92bcd8dfc1..690e2d2e56e06 100644 --- a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx +++ b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx @@ -4,6 +4,7 @@ import { styled } from '@mui/material/styles'; import { unstable_composeClasses as composeClasses, unstable_capitalize as capitalize, + unstable_useId as useId, } from '@mui/utils'; import Badge from '@mui/material/Badge'; import { ButtonProps } from '@mui/material/Button'; @@ -35,7 +36,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { const GridToolbarFilterListRoot = styled('ul', { name: 'MuiDataGrid', slot: 'ToolbarFilterList', - overridesResolver: (props, styles) => styles.toolbarFilterList, + overridesResolver: (_props, styles) => styles.toolbarFilterList, })<{ ownerState: OwnerState }>(({ theme }) => ({ margin: theme.spacing(1, 1, 0.5), padding: theme.spacing(0, 1), @@ -60,6 +61,8 @@ const GridToolbarFilterButton = React.forwardRef { if (preferencePanel.open) { @@ -108,9 +111,13 @@ const GridToolbarFilterButton = React.forwardRef) => { const { open, openedPanelValue } = preferencePanel; if (open && openedPanelValue === GridPreferencePanelsValue.filters) { - apiRef.current.hideFilterPanel(); + apiRef.current.hidePreferences(); } else { - apiRef.current.showFilterPanel(); + apiRef.current.showPreferences( + GridPreferencePanelsValue.filters, + filterPanelId, + filterButtonId, + ); } buttonProps.onClick?.(event); }; @@ -120,6 +127,7 @@ const GridToolbarFilterButton = React.forwardRef diff --git a/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index 2cd45748ab185..0680de637de5d 100644 --- a/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -163,7 +163,7 @@ export const useGridFilter = ( ); const showFilterPanel = React.useCallback( - (targetColumnField) => { + (targetColumnField, panelId, labelId) => { logger.debug('Displaying filter panel'); if (targetColumnField) { const filterModel = gridFilterModelSelector(apiRef); @@ -226,7 +226,7 @@ export const useGridFilter = ( items: newFilterItems, }); } - apiRef.current.showPreferences(GridPreferencePanelsValue.filters); + apiRef.current.showPreferences(GridPreferencePanelsValue.filters, panelId, labelId); }, [apiRef, logger, props.disableMultipleColumnsFiltering], ); diff --git a/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/gridPreferencePanelState.ts b/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/gridPreferencePanelState.ts index 5087a75e75db0..1a6db9ff05456 100644 --- a/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/gridPreferencePanelState.ts +++ b/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/gridPreferencePanelState.ts @@ -2,6 +2,8 @@ import { GridPreferencePanelsValue } from './gridPreferencePanelsValue'; export interface GridPreferencePanelState { open: boolean; + panelId?: string; + labelId?: string; /** * Tab currently opened. * @default GridPreferencePanelsValue.filter diff --git a/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts b/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts index 0b0ea8aedb857..2b0729ae6882c 100644 --- a/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts +++ b/packages/grid/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts @@ -57,12 +57,18 @@ export const useGridPreferencesPanel = ( }, [hidePreferences]); const showPreferences = React.useCallback( - (newValue) => { + (newValue, panelId, labelId) => { logger.debug('Opening Preferences Panel'); doNotHidePanel(); apiRef.current.setState((state) => ({ ...state, - preferencePanel: { ...state.preferencePanel, open: true, openedPanelValue: newValue }, + preferencePanel: { + ...state.preferencePanel, + open: true, + openedPanelValue: newValue, + panelId, + labelId, + }, })); apiRef.current.publishEvent('preferencePanelOpen', { openedPanelValue: newValue, diff --git a/packages/grid/x-data-grid/src/models/api/gridFilterApi.ts b/packages/grid/x-data-grid/src/models/api/gridFilterApi.ts index 9b44eeba9ed00..4b9967b63eb5e 100644 --- a/packages/grid/x-data-grid/src/models/api/gridFilterApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridFilterApi.ts @@ -9,8 +9,10 @@ export interface GridFilterApi { /** * Shows the filter panel. If `targetColumnField` is given, a filter for this field is also added. * @param {string} targetColumnField The column field to add a filter. + * @param {string} panelId The unique panel id + * @param {string} labelId The unique button id */ - showFilterPanel: (targetColumnField?: string) => void; + showFilterPanel: (targetColumnField?: string, panelId?: string, labelId?: string) => void; /** * Hides the filter panel. */ diff --git a/packages/grid/x-data-grid/src/models/api/gridPreferencesPanelApi.ts b/packages/grid/x-data-grid/src/models/api/gridPreferencesPanelApi.ts index bb08f0a3d0fde..90801594ece85 100644 --- a/packages/grid/x-data-grid/src/models/api/gridPreferencesPanelApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridPreferencesPanelApi.ts @@ -7,8 +7,14 @@ export interface GridPreferencesPanelApi { /** * Displays the preferences panel. The `newValue` argument controls the content of the panel. * @param {GridPreferencePanelsValue} newValue The panel to open. Use `"filters"` or `"columns"`. + * @param {string} panelId The unique panel id + * @param {string} labelId The unique button id */ - showPreferences: (newValue: GridPreferencePanelsValue) => void; + showPreferences: ( + newValue: GridPreferencePanelsValue, + panelId?: string, + labelId?: string, + ) => void; /** * Hides the preferences panel. */