From bb3a96c1be279d96f06feabc3f348d289a703e06 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 18 May 2023 18:20:57 +0500 Subject: [PATCH] [DataGridPro] Filtering on Column Header (#7760) --- .../CustomHeaderFilterDataGridPro.js | 124 +++++++ .../CustomHeaderFilterDataGridPro.tsx | 121 +++++++ .../CustomHeaderFilterDataGridPro.tsx.preview | 16 + .../CustomHeaderFilterOperatorDataGridPro.js | 119 +++++++ .../CustomHeaderFilterOperatorDataGridPro.tsx | 95 ++++++ ...eaderFilterOperatorDataGridPro.tsx.preview | 1 + .../CustomHeaderFilterSingleDataGridPro.js | 100 ++++++ .../CustomHeaderFilterSingleDataGridPro.tsx | 103 ++++++ ...mHeaderFilterSingleDataGridPro.tsx.preview | 6 + .../filtering/HeaderFilteringDataGridPro.js | 28 ++ .../filtering/HeaderFilteringDataGridPro.tsx | 28 ++ .../HeaderFilteringDataGridPro.tsx.preview | 13 + .../SimpleHeaderFilteringDataGridPro.js | 32 ++ .../SimpleHeaderFilteringDataGridPro.tsx | 32 ++ docs/data/data-grid/filtering/filtering.md | 68 ++++ .../getting-started/getting-started.md | 1 + docs/data/data-grid/localization/data.json | 120 +++---- .../x/api/data-grid/data-grid-premium.json | 11 + docs/pages/x/api/data-grid/data-grid-pro.json | 11 + docs/pages/x/api/data-grid/data-grid.json | 1 + .../x/api/data-grid/grid-actions-col-def.md | 1 + docs/pages/x/api/data-grid/grid-api.md | 5 + docs/pages/x/api/data-grid/grid-col-def.md | 1 + .../x/api/data-grid/grid-filter-operator.md | 1 + .../data-grid/grid-single-select-col-def.md | 1 + docs/pages/x/api/data-grid/selectors.json | 30 ++ .../api-docs/data-grid/data-grid-premium.json | 5 + .../api-docs/data-grid/data-grid-pro.json | 5 + .../api-docs/data-grid/data-grid.json | 1 + .../src/DataGridPremium/DataGridPremium.tsx | 5 + .../useDataGridPremiumComponent.tsx | 4 + .../src/DataGridPro/DataGridPro.tsx | 5 + .../DataGridPro/useDataGridProComponent.tsx | 4 + .../src/DataGridPro/useDataGridProProps.ts | 1 + .../src/components/GridColumnHeaders.tsx | 20 +- .../src/components/GridDetailPanel.tsx | 5 +- .../GridHeaderFilterAdornment.tsx | 118 +++++++ .../headerFiltering/GridHeaderFilterCell.tsx | 321 ++++++++++++++++++ .../GridHeaderFilterClearButton.tsx | 26 ++ .../headerFiltering/GridHeaderFilterMenu.tsx | 95 ++++++ .../components/headerFiltering/constants.ts | 30 ++ .../src/components/headerFiltering/index.ts | 2 + .../x-data-grid-pro/src/components/index.ts | 1 + .../dataGridProDefaultSlotsComponents.ts | 4 + .../columnHeaders/useGridColumnHeaders.tsx | 153 +++++++++ .../columnResize/useGridColumnResize.tsx | 17 + .../x-data-grid-pro/src/internals/index.ts | 3 + .../x-data-grid-pro/src/material/icons.tsx | 5 + .../x-data-grid-pro/src/material/index.ts | 3 +- .../src/models/dataGridProProps.ts | 20 +- .../src/models/gridProIconSlotsComponent.ts | 5 + .../src/models/gridProSlotProps.ts | 11 + .../src/models/gridProSlotsComponent.ts | 18 +- .../src/tests/filtering.DataGridPro.test.tsx | 131 +++++++ .../src/typeOverloads/modules.ts | 14 +- .../src/colDef/gridNumericOperators.ts | 6 - .../src/components/cell/GridEditInputCell.tsx | 6 +- .../filterPanel/GridFilterInputBoolean.tsx | 156 ++++++--- .../panel/filterPanel/GridFilterInputDate.tsx | 47 ++- .../GridFilterInputSingleSelect.tsx | 107 ++++-- .../filterPanel/GridFilterInputValue.tsx | 59 +++- .../panel/filterPanel/GridFilterPanel.tsx | 2 +- .../src/components/panel/filterPanel/index.ts | 4 +- .../src/constants/localeTextConstants.ts | 27 ++ .../columnHeaders/useGridColumnHeaders.tsx | 11 +- .../hooks/features/focus/gridFocusState.ts | 2 + .../features/focus/gridFocusStateSelector.ts | 12 + .../src/hooks/features/focus/useGridFocus.ts | 93 ++++- .../gridHeaderFilteringSelectors.ts | 16 + .../hooks/features/headerFiltering/index.ts | 1 + .../headerFiltering/useGridHeaderFiltering.ts | 120 +++++++ .../x-data-grid/src/hooks/features/index.ts | 1 + .../useGridKeyboardNavigation.ts | 162 ++++++++- .../grid/x-data-grid/src/internals/index.ts | 16 +- packages/grid/x-data-grid/src/locales/arSD.ts | 27 ++ packages/grid/x-data-grid/src/locales/beBY.ts | 27 ++ packages/grid/x-data-grid/src/locales/bgBG.ts | 27 ++ packages/grid/x-data-grid/src/locales/csCZ.ts | 27 ++ packages/grid/x-data-grid/src/locales/daDK.ts | 27 ++ packages/grid/x-data-grid/src/locales/deDE.ts | 27 ++ packages/grid/x-data-grid/src/locales/elGR.ts | 27 ++ packages/grid/x-data-grid/src/locales/esES.ts | 27 ++ packages/grid/x-data-grid/src/locales/faIR.ts | 27 ++ packages/grid/x-data-grid/src/locales/fiFI.ts | 27 ++ packages/grid/x-data-grid/src/locales/frFR.ts | 27 ++ packages/grid/x-data-grid/src/locales/heIL.ts | 27 ++ packages/grid/x-data-grid/src/locales/huHU.ts | 27 ++ packages/grid/x-data-grid/src/locales/itIT.ts | 27 ++ packages/grid/x-data-grid/src/locales/jaJP.ts | 27 ++ packages/grid/x-data-grid/src/locales/koKR.ts | 27 ++ packages/grid/x-data-grid/src/locales/nbNO.ts | 27 ++ packages/grid/x-data-grid/src/locales/nlNL.ts | 27 ++ packages/grid/x-data-grid/src/locales/plPL.ts | 27 ++ packages/grid/x-data-grid/src/locales/ptBR.ts | 27 ++ packages/grid/x-data-grid/src/locales/roRO.ts | 27 ++ packages/grid/x-data-grid/src/locales/ruRU.ts | 27 ++ packages/grid/x-data-grid/src/locales/skSK.ts | 27 ++ packages/grid/x-data-grid/src/locales/svSE.ts | 27 ++ packages/grid/x-data-grid/src/locales/trTR.ts | 27 ++ packages/grid/x-data-grid/src/locales/ukUA.ts | 27 ++ packages/grid/x-data-grid/src/locales/urPK.ts | 27 ++ packages/grid/x-data-grid/src/locales/viVN.ts | 27 ++ packages/grid/x-data-grid/src/locales/zhCN.ts | 27 ++ packages/grid/x-data-grid/src/locales/zhTW.ts | 27 ++ .../grid/x-data-grid/src/material/index.ts | 5 +- .../src/models/api/gridApiCommon.ts | 7 +- .../x-data-grid/src/models/api/gridCoreApi.ts | 4 + .../src/models/api/gridFocusApi.ts | 6 + .../src/models/api/gridHeaderFilteringApi.ts | 32 ++ .../src/models/api/gridLocaleTextApi.ts | 27 ++ .../src/models/events/gridEventLookup.ts | 28 ++ .../src/models/gridFilterOperator.ts | 4 + .../src/models/gridHeaderFilteringModel.ts | 6 + .../src/models/gridSlotsComponent.ts | 21 +- .../src/models/gridStateCommunity.ts | 2 + packages/grid/x-data-grid/src/models/index.ts | 2 +- scripts/l10n.ts | 4 +- scripts/x-data-grid-premium.exports.json | 15 +- scripts/x-data-grid-pro.exports.json | 15 +- scripts/x-data-grid.exports.json | 11 +- test/regressions/index.test.js | 4 + 121 files changed, 3744 insertions(+), 208 deletions(-) create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx.preview create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.js create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx.preview create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.js create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx create mode 100644 docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx.preview create mode 100644 docs/data/data-grid/filtering/HeaderFilteringDataGridPro.js create mode 100644 docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx create mode 100644 docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx.preview create mode 100644 docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.js create mode 100644 docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.tsx create mode 100644 packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterAdornment.tsx create mode 100644 packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx create mode 100644 packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterClearButton.tsx create mode 100644 packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx create mode 100644 packages/grid/x-data-grid-pro/src/components/headerFiltering/constants.ts create mode 100644 packages/grid/x-data-grid-pro/src/components/headerFiltering/index.ts create mode 100644 packages/grid/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx create mode 100644 packages/grid/x-data-grid-pro/src/models/gridProSlotProps.ts create mode 100644 packages/grid/x-data-grid/src/hooks/features/headerFiltering/gridHeaderFilteringSelectors.ts create mode 100644 packages/grid/x-data-grid/src/hooks/features/headerFiltering/index.ts create mode 100644 packages/grid/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts create mode 100644 packages/grid/x-data-grid/src/models/api/gridHeaderFilteringApi.ts create mode 100644 packages/grid/x-data-grid/src/models/gridHeaderFilteringModel.ts diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js new file mode 100644 index 0000000000000..6a29d98a580e8 --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js @@ -0,0 +1,124 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; +import { + DataGridPro, + useGridSelector, + gridFilterModelSelector, + useGridApiContext, +} from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +function CustomHeaderFilter(props) { + const { colDef, width, height, hasFocus, colIndex, tabIndex } = props; + const apiRef = useGridApiContext(); + const cellRef = React.useRef(null); + + React.useLayoutEffect(() => { + if (hasFocus && cellRef.current) { + const focusableElement = cellRef.current.querySelector('[tabindex="0"]'); + const elementToFocus = focusableElement || cellRef.current; + elementToFocus?.focus(); + } + }, [apiRef, hasFocus]); + + const publish = React.useCallback( + (eventName, propHandler) => (event) => { + apiRef.current.publishEvent( + eventName, + apiRef.current.getColumnHeaderParams(colDef.field), + event, + ); + + if (propHandler) { + propHandler(event); + } + }, + [apiRef, colDef.field], + ); + + const onMouseDown = React.useCallback( + (event) => { + if (!hasFocus) { + cellRef.current?.focus(); + apiRef.current.setColumnHeaderFilterFocus(colDef.field, event); + } + }, + [apiRef, colDef.field, hasFocus], + ); + + const mouseEventsHandlers = React.useMemo( + () => ({ + onKeyDown: publish('headerFilterKeyDown'), + onClick: publish('headerFilterClick'), + onMouseDown: publish('headerFilterMouseDown', onMouseDown), + }), + [publish, onMouseDown], + ); + + const filterModel = useGridSelector(apiRef, gridFilterModelSelector); + const activeFiltersCount = + filterModel.items?.filter(({ field }) => field === colDef.field).length ?? 0; + + return ( + + + + ); +} + +CustomHeaderFilter.propTypes = { + colDef: PropTypes.object.isRequired, + colIndex: PropTypes.number.isRequired, + hasFocus: PropTypes.bool, + height: PropTypes.number.isRequired, + tabIndex: PropTypes.oneOf([-1, 0]).isRequired, + width: PropTypes.number.isRequired, +}; + +export default function CustomHeaderFilterDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx new file mode 100644 index 0000000000000..650530d8c00a1 --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx @@ -0,0 +1,121 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Stack from '@mui/material/Stack'; +import { + DataGridPro, + useGridSelector, + gridFilterModelSelector, + useGridApiContext, + GridHeaderFilterCellProps, + GridHeaderFilterEventLookup, +} from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +function CustomHeaderFilter(props: GridHeaderFilterCellProps) { + const { colDef, width, height, hasFocus, colIndex, tabIndex } = props; + const apiRef = useGridApiContext(); + const cellRef = React.useRef(null); + + React.useLayoutEffect(() => { + if (hasFocus && cellRef.current) { + const focusableElement = + cellRef.current!.querySelector('[tabindex="0"]'); + const elementToFocus = focusableElement || cellRef.current; + elementToFocus?.focus(); + } + }, [apiRef, hasFocus]); + + const publish = React.useCallback( + ( + eventName: keyof GridHeaderFilterEventLookup, + propHandler?: React.EventHandler, + ) => + (event: React.SyntheticEvent) => { + apiRef.current.publishEvent( + eventName, + apiRef.current.getColumnHeaderParams(colDef.field), + event as any, + ); + + if (propHandler) { + propHandler(event); + } + }, + [apiRef, colDef.field], + ); + + const onMouseDown = React.useCallback( + (event: React.MouseEvent) => { + if (!hasFocus) { + cellRef.current?.focus(); + apiRef.current.setColumnHeaderFilterFocus(colDef.field, event); + } + }, + [apiRef, colDef.field, hasFocus], + ); + + const mouseEventsHandlers = React.useMemo( + () => ({ + onKeyDown: publish('headerFilterKeyDown'), + onClick: publish('headerFilterClick'), + onMouseDown: publish('headerFilterMouseDown', onMouseDown), + }), + [publish, onMouseDown], + ); + + const filterModel = useGridSelector(apiRef, gridFilterModelSelector); + const activeFiltersCount = + filterModel.items?.filter(({ field }) => field === colDef.field).length ?? 0; + + return ( + + + + ); +} + +export default function CustomHeaderFilterDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx.preview b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx.preview new file mode 100644 index 0000000000000..1ecb83419cebf --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.tsx.preview @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.js b/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.js new file mode 100644 index 0000000000000..99d648a9dd980 --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.js @@ -0,0 +1,119 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Box from '@mui/material/Box'; +import Rating from '@mui/material/Rating'; +import { DataGridPro, getGridNumericOperators } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +function RatingInputValue(props) { + const { item, applyValue, focusElementRef, headerFilterMenu, clearButton } = props; + + const ratingRef = React.useRef(null); + React.useImperativeHandle(focusElementRef, () => ({ + focus: () => { + ratingRef.current + .querySelector(`input[value="${Number(item.value) || ''}"]`) + .focus(); + }, + })); + + const handleFilterChange = (event, newValue) => { + applyValue({ ...item, value: newValue }); + }; + + return ( + + {headerFilterMenu} + + + + {clearButton} + + ); +} + +RatingInputValue.propTypes = { + applyValue: PropTypes.func.isRequired, + clearButton: PropTypes.node, + focusElementRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: PropTypes.any.isRequired, + }), + ]), + headerFilterMenu: PropTypes.node, + item: PropTypes.shape({ + /** + * The column from which we want to filter the rows. + */ + field: PropTypes.string.isRequired, + /** + * Must be unique. + * Only useful when the model contains several items. + */ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + /** + * The name of the operator we want to apply. + */ + operator: PropTypes.string.isRequired, + /** + * The filtering value. + * The operator filtering function will decide for each row if the row values is correct compared to this value. + */ + value: PropTypes.any, + }).isRequired, +}; + +export default function CustomHeaderFilterOperatorDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + visibleFields: VISIBLE_FIELDS, + }); + + const columns = React.useMemo( + () => + data.columns.map((colDef) => { + if (colDef.field === 'rating') { + return { + ...colDef, + minWidth: 200, + filterOperators: getGridNumericOperators() + .filter((operator) => operator.value !== 'isAnyOf') + .map((operator) => ({ + ...operator, + InputComponent: operator.InputComponent + ? RatingInputValue + : undefined, + })), + }; + } + return colDef; + }), + [data.columns], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx b/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx new file mode 100644 index 0000000000000..0e63e03f112de --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Rating, { RatingProps } from '@mui/material/Rating'; +import { + DataGridPro, + getGridNumericOperators, + GridFilterInputValueProps, +} from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +function RatingInputValue( + props: GridFilterInputValueProps & { + headerFilterMenu: React.ReactNode; + clearButton: React.ReactNode; + }, +) { + const { item, applyValue, focusElementRef, headerFilterMenu, clearButton } = props; + + const ratingRef: React.Ref = React.useRef(null); + React.useImperativeHandle(focusElementRef, () => ({ + focus: () => { + ratingRef.current + .querySelector(`input[value="${Number(item.value) || ''}"]`) + .focus(); + }, + })); + + const handleFilterChange: RatingProps['onChange'] = (event, newValue) => { + applyValue({ ...item, value: newValue }); + }; + + return ( + + {headerFilterMenu} + + + + {clearButton} + + ); +} + +export default function CustomHeaderFilterOperatorDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + visibleFields: VISIBLE_FIELDS, + }); + + const columns = React.useMemo( + () => + data.columns.map((colDef) => { + if (colDef.field === 'rating') { + return { + ...colDef, + minWidth: 200, + filterOperators: getGridNumericOperators() + .filter((operator) => operator.value !== 'isAnyOf') + .map((operator) => ({ + ...operator, + InputComponent: operator.InputComponent + ? RatingInputValue + : undefined, + })), + }; + } + return colDef; + }), + [data.columns], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx.preview b/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx.preview new file mode 100644 index 0000000000000..a5bedcee0fbe3 --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterOperatorDataGridPro.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.js b/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.js new file mode 100644 index 0000000000000..f136c917e9675 --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.js @@ -0,0 +1,100 @@ +import * as React from 'react'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import { + DataGridPro, + gridFilterModelSelector, + useGridSelector, + useGridApiContext, +} from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const getDefaultFilter = (field) => ({ field, operator: 'is' }); + +function AdminFilter(props) { + const { colDef } = props; + const apiRef = useGridApiContext(); + const filterModel = useGridSelector(apiRef, gridFilterModelSelector); + const currentFieldFilters = React.useMemo( + () => filterModel.items?.filter(({ field }) => field === colDef.field), + [colDef.field, filterModel.items], + ); + + const handleChange = React.useCallback( + (event) => { + if (!event.target.value) { + if (currentFieldFilters[0]) { + apiRef.current.deleteFilterItem(currentFieldFilters[0]); + } + return; + } + apiRef.current.upsertFilterItem({ + ...(currentFieldFilters[0] || getDefaultFilter(colDef.field)), + value: event.target.value, + }); + }, + [apiRef, colDef.field, currentFieldFilters], + ); + + const value = currentFieldFilters[0]?.value ?? ''; + const label = !value ? 'Filter' : 'Is admin'; + + return ( + + {label} + + + ); +} + +export default function CustomHeaderFilterSingleDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + visibleFields: ['name', 'website', 'phone', 'isAdmin', 'salary'], + }); + + const columns = React.useMemo( + () => + data.columns.map((colDef) => { + if (colDef.field === 'isAdmin') { + return { + ...colDef, + width: 200, + renderHeaderFilter: (params) => , + }; + } + if (colDef.field === 'phone') { + // no header filter for `phone` field + return { ...colDef, renderHeaderFilter: () => null }; + } + return colDef; + }), + [data.columns], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx b/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx new file mode 100644 index 0000000000000..ef9b195add006 --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx @@ -0,0 +1,103 @@ +import * as React from 'react'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import { + DataGridPro, + GridColumnHeaderParams, + gridFilterModelSelector, + useGridSelector, + useGridApiContext, +} from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const getDefaultFilter = (field: string) => ({ field, operator: 'is' }); + +function AdminFilter(props: GridColumnHeaderParams) { + const { colDef } = props; + const apiRef = useGridApiContext(); + const filterModel = useGridSelector(apiRef, gridFilterModelSelector); + const currentFieldFilters = React.useMemo( + () => filterModel.items?.filter(({ field }) => field === colDef.field), + [colDef.field, filterModel.items], + ); + + const handleChange = React.useCallback( + (event: SelectChangeEvent) => { + if (!event.target.value) { + if (currentFieldFilters[0]) { + apiRef.current.deleteFilterItem(currentFieldFilters[0]); + } + return; + } + apiRef.current.upsertFilterItem({ + ...(currentFieldFilters[0] || getDefaultFilter(colDef.field)), + value: event.target.value, + }); + }, + [apiRef, colDef.field, currentFieldFilters], + ); + + const value = currentFieldFilters[0]?.value ?? ''; + const label = !value ? 'Filter' : 'Is admin'; + + return ( + + {label} + + + ); +} + +export default function CustomHeaderFilterSingleDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + visibleFields: ['name', 'website', 'phone', 'isAdmin', 'salary'], + }); + + const columns = React.useMemo( + () => + data.columns.map((colDef) => { + if (colDef.field === 'isAdmin') { + return { + ...colDef, + width: 200, + renderHeaderFilter: (params: GridColumnHeaderParams) => ( + + ), + }; + } + if (colDef.field === 'phone') { + // no header filter for `phone` field + return { ...colDef, renderHeaderFilter: () => null }; + } + return colDef; + }), + [data.columns], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx.preview b/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx.preview new file mode 100644 index 0000000000000..2135ac9144868 --- /dev/null +++ b/docs/data/data-grid/filtering/CustomHeaderFilterSingleDataGridPro.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.js b/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.js new file mode 100644 index 0000000000000..49ac53c1e49c6 --- /dev/null +++ b/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.js @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function HeaderFilteringDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx b/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx new file mode 100644 index 0000000000000..49ac53c1e49c6 --- /dev/null +++ b/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function HeaderFilteringDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx.preview b/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx.preview new file mode 100644 index 0000000000000..922deeb4056ff --- /dev/null +++ b/docs/data/data-grid/filtering/HeaderFilteringDataGridPro.tsx.preview @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.js b/docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.js new file mode 100644 index 0000000000000..5b6d07fbca901 --- /dev/null +++ b/docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.js @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function SimpleHeaderFilteringDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.tsx b/docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.tsx new file mode 100644 index 0000000000000..5b6d07fbca901 --- /dev/null +++ b/docs/data/data-grid/filtering/SimpleHeaderFilteringDataGridPro.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function SimpleHeaderFilteringDataGridPro() { + const { data } = useDemoData({ + dataSet: 'Employee', + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/filtering.md b/docs/data/data-grid/filtering/filtering.md index 84ed699558815..0b6ba4a15e8db 100644 --- a/docs/data/data-grid/filtering/filtering.md +++ b/docs/data/data-grid/filtering/filtering.md @@ -47,6 +47,74 @@ To disable `Add filter` or `Remove all` buttons, pass `disableAddFilterButton` o {{"demo": "DisableActionButtonsDataGridPro.js", "bg": "inline", "defaultCodeOpen": false}} +## Header filters [](/x/introduction/licensing/#pro-plan) + +:::warning +This feature is unstable, it needs to be explicitly activated using the `unstable_headerFilters` prop. + +```tsx + +``` + +To use header filters, you need to upgrade to the [Pro plan](/x/introduction/licensing/#pro-plan) or above. + +::: + +Header filters add a new header row that lets users quickly filter the columns. The filters added on the filter panel are synchronized with the header filters and vice versa. + +You can switch between different operators by clicking the operator button in the header filter cell or pressing Ctrl+Enter (or ⌘ Command+Enter on macOS) when focusing on a header filter cell. + +{{"demo": "HeaderFilteringDataGridPro.js", "bg": "inline", "defaultCodeOpen": false}} + +### Simple header filters + +You can disable default filter panel using `disableColumnFilter` prop and only show the default operator by passing `slots.headerFilterMenu` as `null`. + +{{"demo": "SimpleHeaderFilteringDataGridPro.js", "bg": "inline", "defaultCodeOpen": false}} + +### Customize header filters + +There are multiple ways to customize header filters. + +#### `renderHeaderFilter` method + +You can use the `renderHeaderFilter` method of the `GridColDef` to customize the header filter cell for a specific column. + +```tsx +const columns: GridColDef[] = [ + { + field: 'isAdmin', + renderHeaderFilter: (params: GridColumnHeaderParams) => ( + + ), + }, +]; +``` + +The following demo uses the `renderHeaderFilter` method to customize the header filter cell for the `isAdmin` column and hide the filter cell for the `phone` column. + +{{"demo": "CustomHeaderFilterSingleDataGridPro.js", "bg": "inline", "defaultCodeOpen": false}} + +#### Customize using `filterOperators` + +If the filter operator has a [custom `InputComponent`](https://mui.com/x/react-data-grid/filtering/#custom-input-component), the same component is being used for the header filter. + +When rendered as a header filter, the `InputComponent` also receives the `headerFilterMenu` and `clearButton` props that contain the filter operator menu and clear button. + +{{"demo": "CustomHeaderFilterOperatorDataGridPro.js", "bg": "inline", "defaultCodeOpen": false}} + +#### `headerFilterCell` slot + +You can use `slots.headerFilterCell` to customize the header filter cell completely. Since the default slot component handles keyboard navigation and focus management, you may have to handle them yourself if you are using a custom component. + +Additionally, `slots.headerFilterMenu` could also be used to customize the menu of the header filter cell. + +```tsx + +``` + +{{"demo": "CustomHeaderFilterDataGridPro.js", "bg": "inline", "defaultCodeOpen": false}} + ## Pass filters to the Data Grid ### Structure of the model diff --git a/docs/data/data-grid/getting-started/getting-started.md b/docs/data/data-grid/getting-started/getting-started.md index c97871d03d58c..497fc5b2684e4 100644 --- a/docs/data/data-grid/getting-started/getting-started.md +++ b/docs/data/data-grid/getting-started/getting-started.md @@ -171,6 +171,7 @@ The enterprise components come in two plans: Pro and Premium. | [Quick filter](/x/react-data-grid/filtering/#quick-filter) | ✅ | ✅ | ✅ | | [Column filters](/x/react-data-grid/filtering/#single-and-multi-filtering) | ✅ | ✅ | ✅ | | [Multi-column filtering](/x/react-data-grid/filtering/#multi-filtering) | ❌ | ✅ | ✅ | +| [Header filtering](/x/react-data-grid/filtering/#header-filters) | ❌ | ✅ | ✅ | | **Sorting** | | | | | [Column sorting](/x/react-data-grid/sorting/) | ✅ | ✅ | ✅ | | [Multi-column sorting](/x/react-data-grid/sorting/#multi-sorting) | ❌ | ✅ | ✅ | diff --git a/docs/data/data-grid/localization/data.json b/docs/data/data-grid/localization/data.json index 1bbf3abf1cd32..bb6e560a56e9a 100644 --- a/docs/data/data-grid/localization/data.json +++ b/docs/data/data-grid/localization/data.json @@ -3,240 +3,240 @@ "languageTag": "ar-SD", "importName": "arSD", "localeName": "Arabic (Sudan)", - "missingKeysCount": 2, - "totalKeysCount": 94, + "missingKeysCount": 27, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/arSD.ts" }, { "languageTag": "be-BY", "importName": "beBY", "localeName": "Belarusian", - "missingKeysCount": 1, - "totalKeysCount": 94, + "missingKeysCount": 26, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/beBY.ts" }, { "languageTag": "bg-BG", "importName": "bgBG", "localeName": "Bulgarian", - "missingKeysCount": 7, - "totalKeysCount": 94, + "missingKeysCount": 32, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/bgBG.ts" }, { "languageTag": "zh-CN", "importName": "zhCN", "localeName": "Chinese (Simplified)", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/zhCN.ts" }, { "languageTag": "zh-TW", "importName": "zhTW", "localeName": "Chinese (Taiwan)", - "missingKeysCount": 8, - "totalKeysCount": 94, + "missingKeysCount": 33, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/zhTW.ts" }, { "languageTag": "cs-CZ", "importName": "csCZ", "localeName": "Czech", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/csCZ.ts" }, { "languageTag": "da-DK", "importName": "daDK", "localeName": "Danish", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/daDK.ts" }, { "languageTag": "nl-NL", "importName": "nlNL", "localeName": "Dutch", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/nlNL.ts" }, { "languageTag": "fi-FI", "importName": "fiFI", "localeName": "Finnish", - "missingKeysCount": 2, - "totalKeysCount": 94, + "missingKeysCount": 27, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/fiFI.ts" }, { "languageTag": "fr-FR", "importName": "frFR", "localeName": "French", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/frFR.ts" }, { "languageTag": "de-DE", "importName": "deDE", "localeName": "German", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/deDE.ts" }, { "languageTag": "el-GR", "importName": "elGR", "localeName": "Greek", - "missingKeysCount": 38, - "totalKeysCount": 94, + "missingKeysCount": 63, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/elGR.ts" }, { "languageTag": "he-IL", "importName": "heIL", "localeName": "Hebrew", - "missingKeysCount": 8, - "totalKeysCount": 94, + "missingKeysCount": 33, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/heIL.ts" }, { "languageTag": "hu-HU", "importName": "huHU", "localeName": "Hungarian", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/huHU.ts" }, { "languageTag": "it-IT", "importName": "itIT", "localeName": "Italian", - "missingKeysCount": 8, - "totalKeysCount": 94, + "missingKeysCount": 33, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/itIT.ts" }, { "languageTag": "ja-JP", "importName": "jaJP", "localeName": "Japanese", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/jaJP.ts" }, { "languageTag": "ko-KR", "importName": "koKR", "localeName": "Korean", - "missingKeysCount": 2, - "totalKeysCount": 94, + "missingKeysCount": 27, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/koKR.ts" }, { "languageTag": "nb-NO", "importName": "nbNO", "localeName": "Norwegian (bokmål)", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/nbNO.ts" }, { "languageTag": "fa-IR", "importName": "faIR", "localeName": "Persian", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/faIR.ts" }, { "languageTag": "pl-PL", "importName": "plPL", "localeName": "Polish", - "missingKeysCount": 9, - "totalKeysCount": 94, + "missingKeysCount": 34, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/plPL.ts" }, { "languageTag": "pt-BR", "importName": "ptBR", "localeName": "Portuguese (Brazil)", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/ptBR.ts" }, { "languageTag": "ro-RO", "importName": "roRO", "localeName": "Romanian", - "missingKeysCount": 8, - "totalKeysCount": 94, + "missingKeysCount": 33, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/roRO.ts" }, { "languageTag": "ru-RU", "importName": "ruRU", "localeName": "Russian", - "missingKeysCount": 1, - "totalKeysCount": 94, + "missingKeysCount": 26, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/ruRU.ts" }, { "languageTag": "sk-SK", "importName": "skSK", "localeName": "Slovak", - "missingKeysCount": 2, - "totalKeysCount": 94, + "missingKeysCount": 27, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/skSK.ts" }, { "languageTag": "es-ES", "importName": "esES", "localeName": "Spanish", - "missingKeysCount": 1, - "totalKeysCount": 94, + "missingKeysCount": 26, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/esES.ts" }, { "languageTag": "sv-SE", "importName": "svSE", "localeName": "Swedish", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/svSE.ts" }, { "languageTag": "tr-TR", "importName": "trTR", "localeName": "Turkish", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/trTR.ts" }, { "languageTag": "uk-UA", "importName": "ukUA", "localeName": "Ukrainian", - "missingKeysCount": 0, - "totalKeysCount": 94, + "missingKeysCount": 25, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/ukUA.ts" }, { "languageTag": "ur-PK", "importName": "urPK", "localeName": "Urdu (Pakistan)", - "missingKeysCount": 1, - "totalKeysCount": 94, + "missingKeysCount": 26, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/urPK.ts" }, { "languageTag": "vi-VN", "importName": "viVN", "localeName": "Vietnamese", - "missingKeysCount": 9, - "totalKeysCount": 94, + "missingKeysCount": 34, + "totalKeysCount": 119, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/grid/x-data-grid/src/locales/viVN.ts" } ] diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 67ebc7c5a9622..63fd176f346e5 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -259,6 +259,7 @@ "treeData": { "type": { "name": "bool" } }, "unstable_cellSelection": { "type": { "name": "bool" } }, "unstable_cellSelectionModel": { "type": { "name": "object" } }, + "unstable_headerFilters": { "type": { "name": "bool" } }, "unstable_ignoreValueFormatterDuringExport": { "type": { "name": "union", @@ -275,6 +276,7 @@ "baseChip": { "default": "Chip", "type": { "name": "elementType" } }, "baseFormControl": { "default": "FormControl", "type": { "name": "elementType" } }, "baseIconButton": { "default": "IconButton", "type": { "name": "elementType" } }, + "baseInputAdornment": { "default": "InputAdornment", "type": { "name": "elementType" } }, "baseInputLabel": { "default": "InputLabel", "type": { "name": "elementType" } }, "basePopper": { "default": "Popper", "type": { "name": "elementType" } }, "baseSelect": { "default": "Select", "type": { "name": "elementType" } }, @@ -366,6 +368,15 @@ "default": "GridKeyboardArrowRight", "type": { "name": "elementType" } }, + "headerFilterCell": { "default": "GridHeaderFilterCell", "type": { "name": "elementType" } }, + "headerFilterClearIcon": { + "default": "GridHighlightOffIcon", + "type": { "name": "elementType" } + }, + "headerFilterMenu": { + "default": "GridHeaderFilterMenu", + "type": { "name": "elementType | null" } + }, "loadIcon": { "default": "GridLoadIcon", "type": { "name": "elementType" } }, "loadingOverlay": { "default": "GridLoadingOverlay", "type": { "name": "elementType" } }, "moreActionsIcon": { "default": "GridMoreVertIcon", "type": { "name": "elementType" } }, diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index db8a8d356fef6..45bdf2347c5be 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -231,6 +231,7 @@ }, "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, "treeData": { "type": { "name": "bool" } }, + "unstable_headerFilters": { "type": { "name": "bool" } }, "unstable_ignoreValueFormatterDuringExport": { "type": { "name": "union", @@ -245,6 +246,7 @@ "baseChip": { "default": "Chip", "type": { "name": "elementType" } }, "baseFormControl": { "default": "FormControl", "type": { "name": "elementType" } }, "baseIconButton": { "default": "IconButton", "type": { "name": "elementType" } }, + "baseInputAdornment": { "default": "InputAdornment", "type": { "name": "elementType" } }, "baseInputLabel": { "default": "InputLabel", "type": { "name": "elementType" } }, "basePopper": { "default": "Popper", "type": { "name": "elementType" } }, "baseSelect": { "default": "Select", "type": { "name": "elementType" } }, @@ -330,6 +332,15 @@ "default": "GridKeyboardArrowRight", "type": { "name": "elementType" } }, + "headerFilterCell": { "default": "GridHeaderFilterCell", "type": { "name": "elementType" } }, + "headerFilterClearIcon": { + "default": "GridHighlightOffIcon", + "type": { "name": "elementType" } + }, + "headerFilterMenu": { + "default": "GridHeaderFilterMenu", + "type": { "name": "elementType | null" } + }, "loadIcon": { "default": "GridLoadIcon", "type": { "name": "elementType" } }, "loadingOverlay": { "default": "GridLoadingOverlay", "type": { "name": "elementType" } }, "moreActionsIcon": { "default": "GridMoreVertIcon", "type": { "name": "elementType" } }, diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 52edd0ccbdf42..7612c78240bba 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -190,6 +190,7 @@ "baseChip": { "default": "Chip", "type": { "name": "elementType" } }, "baseFormControl": { "default": "FormControl", "type": { "name": "elementType" } }, "baseIconButton": { "default": "IconButton", "type": { "name": "elementType" } }, + "baseInputAdornment": { "default": "InputAdornment", "type": { "name": "elementType" } }, "baseInputLabel": { "default": "InputLabel", "type": { "name": "elementType" } }, "basePopper": { "default": "Popper", "type": { "name": "elementType" } }, "baseSelect": { "default": "Select", "type": { "name": "elementType" } }, diff --git a/docs/pages/x/api/data-grid/grid-actions-col-def.md b/docs/pages/x/api/data-grid/grid-actions-col-def.md index 82466c72626cf..e3eda643aaf1c 100644 --- a/docs/pages/x/api/data-grid/grid-actions-col-def.md +++ b/docs/pages/x/api/data-grid/grid-actions-col-def.md @@ -56,6 +56,7 @@ import { GridActionsColDef } from '@mui/x-data-grid'; | renderCell? | (params: GridRenderCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered as cell for this column. | | renderEditCell? | (params: GridRenderEditCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered in edit cell mode for this column. | | renderHeader? | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header cell. | +| renderHeaderFilter? [](/x/introduction/licensing/#pro-plan) | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header filter cell. | | resizable? | boolean | true | If `true`, the column is resizable. | | sortable? | boolean | true | If `true`, the column is sortable. | | sortComparator? | GridComparatorFn<V> | | A comparator function used to sort rows. | diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md index aabe4227b9ef2..589290a0e1ebb 100644 --- a/docs/pages/x/api/data-grid/grid-api.md +++ b/docs/pages/x/api/data-grid/grid-api.md @@ -70,6 +70,7 @@ import { GridApi } from '@mui/x-data-grid'; | getVisibleColumns | () => GridStateColDef[] | Returns the currently visible columns. | | hideColumnMenu | () => void | Hides the column menu that is open. | | hideFilterPanel | () => void | Hides the filter panel. | +| hideHeaderFilterMenu | () => void | Hides the header filter menu. | | hidePreferences | () => void | Hides the preferences panel. | | isCellEditable | (params: GridCellParams) => boolean | Controls if a cell is editable. | | isColumnPinned [](/x/introduction/licensing/#pro-plan) | (field: string) => GridPinnedPosition \| false | Returns which side a column is pinned to. | @@ -88,6 +89,7 @@ import { GridApi } from '@mui/x-data-grid'; | selectRows [](/x/introduction/licensing/#pro-plan) | (ids: GridRowId[], isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of multiple rows. | | setAggregationModel [](/x/introduction/licensing/#premium-plan) | (model: GridAggregationModel) => void | Sets the aggregation model to the one given by `model`. | | setCellFocus | (id: GridRowId, field: string) => void | Sets the focus to the cell at the given `id` and `field`. | +| setColumnHeaderFilterFocus | (field: string, event?: MuiBaseEvent) => void | Sets the focus to the column header filter at the given `field`. | | setColumnHeaderFocus | (field: string, event?: MuiBaseEvent) => void | Sets the focus to the column header at the given `field`. | | setColumnIndex [](/x/introduction/licensing/#pro-plan) | (field: string, targetIndexPosition: number) => void | Moves a column from its original position to the position given by `targetIndexPosition`. | | setColumnVisibility | (field: string, isVisible: boolean) => void | Changes the visibility of the column referred by `field`. | @@ -112,12 +114,15 @@ import { GridApi } from '@mui/x-data-grid'; | 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. | +| 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. | | 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. | | startRowEditMode | (params: GridStartRowEditModeParams) => void | Puts the row corresponding to the given id into edit mode. | | state | State | Property that contains the whole state of the grid. | | stopCellEditMode | (params: GridStopCellEditModeParams) => void | Puts the cell corresponding to the given row id and field into view mode and updates the original row with the new value stored.
If `params.ignoreModifications` is `false` it will discard the modifications made. | +| stopHeaderFilterEditMode | () => void | Stops the edit mode for the current field. | | stopRowEditMode | (params: GridStopRowEditModeParams) => void | Puts the row corresponding to the given id and into view mode and updates the original row with the new values stored.
If `params.ignoreModifications` is `false` it will discard the modifications made. | | subscribeEvent | <E extends GridEvents>(event: E, handler: GridEventListener<E>, options?: EventListenerOptions) => () => void | Registers a handler for an event. | | toggleColumnMenu | (field: string) => void | Toggles the column menu under the `field` column. | diff --git a/docs/pages/x/api/data-grid/grid-col-def.md b/docs/pages/x/api/data-grid/grid-col-def.md index b13e388bed40f..8697722b50f0e 100644 --- a/docs/pages/x/api/data-grid/grid-col-def.md +++ b/docs/pages/x/api/data-grid/grid-col-def.md @@ -55,6 +55,7 @@ import { GridColDef } from '@mui/x-data-grid'; | renderCell? | (params: GridRenderCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered as cell for this column. | | renderEditCell? | (params: GridRenderEditCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered in edit cell mode for this column. | | renderHeader? | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header cell. | +| renderHeaderFilter? [](/x/introduction/licensing/#pro-plan) | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header filter cell. | | resizable? | boolean | true | If `true`, the column is resizable. | | sortable? | boolean | true | If `true`, the column is sortable. | | sortComparator? | GridComparatorFn<V> | | A comparator function used to sort rows. | diff --git a/docs/pages/x/api/data-grid/grid-filter-operator.md b/docs/pages/x/api/data-grid/grid-filter-operator.md index 5f90ecfcd9e98..a39cfeb1fffec 100644 --- a/docs/pages/x/api/data-grid/grid-filter-operator.md +++ b/docs/pages/x/api/data-grid/grid-filter-operator.md @@ -27,6 +27,7 @@ import { GridFilterOperator } from '@mui/x-data-grid'; | :---------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | getApplyFilterFn | (filterItem: GridFilterItem, column: GridColDef<R, V, F>) => null \| ((params: GridCellParams<R, V, F>) => boolean) | | The callback that generates a filtering function for a given filter item and column.
This function can return `null` to skip filtering for this item and column. | | getValueAsString? | (value: GridFilterItem['value']) => string | | Converts the value of a filter item to a human-readable form. | +| headerLabel? | string | | The label of the filter shown in header filter row. | | InputComponent? | React.JSXElementConstructor<any> | | The input component to render in the filter panel for this filter operator. | | InputComponentProps? | Record<string, any> | | The props to pass to the input component in the filter panel for this filter operator. | | label? | string | | The label of the filter operator. | diff --git a/docs/pages/x/api/data-grid/grid-single-select-col-def.md b/docs/pages/x/api/data-grid/grid-single-select-col-def.md index aeebfd49f6964..3d57fe94dca82 100644 --- a/docs/pages/x/api/data-grid/grid-single-select-col-def.md +++ b/docs/pages/x/api/data-grid/grid-single-select-col-def.md @@ -57,6 +57,7 @@ import { GridSingleSelectColDef } from '@mui/x-data-grid'; | renderCell? | (params: GridRenderCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered as cell for this column. | | renderEditCell? | (params: GridRenderEditCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered in edit cell mode for this column. | | renderHeader? | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header cell. | +| renderHeaderFilter? [](/x/introduction/licensing/#pro-plan) | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header filter cell. | | resizable? | boolean | true | If `true`, the column is resizable. | | sortable? | boolean | true | If `true`, the column is sortable. | | sortComparator? | GridComparatorFn<V> | | A comparator function used to sort rows. | diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json index 9c8eaa89aebf2..418ad2366f6bc 100644 --- a/docs/pages/x/api/data-grid/selectors.json +++ b/docs/pages/x/api/data-grid/selectors.json @@ -462,10 +462,40 @@ "description": "", "supportsApiRef": true }, + { + "name": "unstable_gridFocusColumnHeaderFilterSelector", + "returnType": "GridColumnIdentifier | null", + "description": "", + "supportsApiRef": true + }, + { + "name": "unstable_gridHeaderFilteringEditFieldSelector", + "returnType": "string | null", + "description": "", + "supportsApiRef": true + }, + { + "name": "unstable_gridHeaderFilteringMenuSelector", + "returnType": "string | null", + "description": "", + "supportsApiRef": true + }, + { + "name": "unstable_gridHeaderFilteringStateSelector", + "returnType": "GridHeaderFilteringState", + "description": "", + "supportsApiRef": false + }, { "name": "unstable_gridTabIndexColumnGroupHeaderSelector", "returnType": "GridColumnGroupIdentifier | null", "description": "", "supportsApiRef": true + }, + { + "name": "unstable_gridTabIndexColumnHeaderFilterSelector", + "returnType": "GridColumnIdentifier | null", + "description": "", + "supportsApiRef": true } ] diff --git a/docs/translations/api-docs/data-grid/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium.json index b6efd2b7034b5..b9bd6b0b2aaac 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium.json @@ -148,6 +148,7 @@ "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop.", "unstable_cellSelection": "If true, the cell selection mode is enabled.", "unstable_cellSelectionModel": "Set the cell selection model of the grid.", + "unstable_headerFilters": "If true, enables the data grid filtering on header feature.", "unstable_ignoreValueFormatterDuringExport": "If true, the grid will not use valueFormatter when exporting to CSV or copying to clipboard. If an object is provided, you can choose to ignore the valueFormatter for CSV export or clipboard export.", "unstable_onCellSelectionModelChange": "Callback fired when the selection state of one or multiple cells changes.

Signature:
function(cellSelectionModel: GridCellSelectionModel, details: GridCallbackDetails) => void
cellSelectionModel: Object in the shape of GridCellSelectionModel containing the selected cells.
details: Additional details for this callback.", "unstable_splitClipboardPastedText": "The function is used to split the pasted text into rows and cells.

Signature:
function(text: string) => void
text: The text pasted from the clipboard." @@ -699,6 +700,7 @@ "baseChip": "The custom Chip component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", + "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", @@ -745,6 +747,9 @@ "footer": "Footer component rendered at the bottom of the grid viewport.", "groupingCriteriaCollapseIcon": "Icon displayed on the grouping column when the children are expanded", "groupingCriteriaExpandIcon": "Icon displayed on the grouping column when the children are collapsed", + "headerFilterCell": "Component responsible for showing menu adornment in Header filter row", + "headerFilterClearIcon": "Component responsible for clear icon in header filter", + "headerFilterMenu": "Component responsible for showing menu in Header filter row", "loadIcon": "Icon displayed on the input while processing.", "loadingOverlay": "Loading overlay component rendered when the grid is in a loading state.", "moreActionsIcon": "Icon displayed on the actions column type to open the menu.", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro.json index a82fd747ae0f1..58dd8a3aea82e 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro.json @@ -132,6 +132,7 @@ "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "throttleRowsMs": "If positive, the Grid will throttle updates coming from apiRef.current.updateRows and apiRef.current.setRows. It can be useful if you have a high update rate but do not want to do heavy work like filtering / sorting or rendering on each individual update.", "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop.", + "unstable_headerFilters": "If true, enables the data grid filtering on header feature.", "unstable_ignoreValueFormatterDuringExport": "If true, the grid will not use valueFormatter when exporting to CSV or copying to clipboard. If an object is provided, you can choose to ignore the valueFormatter for CSV export or clipboard export." }, "classDescriptions": { @@ -681,6 +682,7 @@ "baseChip": "The custom Chip component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", + "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", @@ -724,6 +726,9 @@ "footer": "Footer component rendered at the bottom of the grid viewport.", "groupingCriteriaCollapseIcon": "Icon displayed on the grouping column when the children are expanded", "groupingCriteriaExpandIcon": "Icon displayed on the grouping column when the children are collapsed", + "headerFilterCell": "Component responsible for showing menu adornment in Header filter row", + "headerFilterClearIcon": "Component responsible for clear icon in header filter", + "headerFilterMenu": "Component responsible for showing menu in Header filter row", "loadIcon": "Icon displayed on the input while processing.", "loadingOverlay": "Loading overlay component rendered when the grid is in a loading state.", "moreActionsIcon": "Icon displayed on the actions column type to open the menu.", diff --git a/docs/translations/api-docs/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid.json index b79b37cdd64ed..217e19a9d5609 100644 --- a/docs/translations/api-docs/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid.json @@ -649,6 +649,7 @@ "baseChip": "The custom Chip component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", + "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 3c7f7f9da6301..46e7e72ad69d8 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -948,6 +948,11 @@ DataGridPremiumRaw.propTypes = { * Set the cell selection model of the grid. */ unstable_cellSelectionModel: PropTypes.object, + /** + * If `true`, enables the data grid filtering on header feature. + * @default false + */ + unstable_headerFilters: PropTypes.bool, /** * If `true`, the grid will not use `valueFormatter` when exporting to CSV or copying to clipboard. * If an object is provided, you can choose to ignore the `valueFormatter` for CSV export or clipboard export. diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 3dc137259e8e8..960bc5f166f23 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -60,6 +60,8 @@ import { columnGroupsStateInitializer, useGridLazyLoader, useGridLazyLoaderPreProcessors, + headerFilteringStateInitializer, + useGridHeaderFiltering, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; @@ -109,6 +111,7 @@ export const useDataGridPremiumComponent = ( /** * Register all state initializers here. */ + useGridInitializeState(headerFilteringStateInitializer, privateApiRef, props); useGridInitializeState(rowGroupingStateInitializer, privateApiRef, props); useGridInitializeState(aggregationStateInitializer, privateApiRef, props); useGridInitializeState(rowSelectionStateInitializer, privateApiRef, props); @@ -132,6 +135,7 @@ export const useDataGridPremiumComponent = ( useGridInitializeState(columnGroupsStateInitializer, privateApiRef, props); useGridRowGrouping(privateApiRef, props); + useGridHeaderFiltering(privateApiRef, props); useGridTreeData(privateApiRef); useGridAggregation(privateApiRef, props); useGridKeyboardNavigation(privateApiRef, props); diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 6f67fa96d4342..64b2711323a82 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -864,6 +864,11 @@ DataGridProRaw.propTypes = { * @default false */ treeData: PropTypes.bool, + /** + * If `true`, enables the data grid filtering on header feature. + * @default false + */ + unstable_headerFilters: PropTypes.bool, /** * If `true`, the grid will not use `valueFormatter` when exporting to CSV or copying to clipboard. * If an object is provided, you can choose to ignore the `valueFormatter` for CSV export or clipboard export. diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index d341937858dc5..36ff4a78c824b 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -40,6 +40,8 @@ import { rowSelectionStateInitializer, useGridColumnGrouping, columnGroupsStateInitializer, + headerFilteringStateInitializer, + useGridHeaderFiltering, } from '@mui/x-data-grid/internals'; import { GridApiPro, GridPrivateApiPro } from '../models/gridApiPro'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; @@ -98,6 +100,7 @@ export const useDataGridProComponent = ( /** * Register all state initializers here. */ + useGridInitializeState(headerFilteringStateInitializer, apiRef, props); useGridInitializeState(rowSelectionStateInitializer, apiRef, props); useGridInitializeState(detailPanelStateInitializer, apiRef, props); useGridInitializeState(columnPinningStateInitializer, apiRef, props); @@ -117,6 +120,7 @@ export const useDataGridProComponent = ( useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); + useGridHeaderFiltering(apiRef, props); useGridTreeData(apiRef); useGridKeyboardNavigation(apiRef, props); useGridRowSelection(apiRef, props); diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 92b46349389dc..9c991e4f24d8d 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -29,6 +29,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu rowReordering: false, rowsLoadingMode: 'client', getDetailPanelHeight: () => 500, + unstable_headerFilters: false, }; const defaultSlots = uncapitalizeObjectKeys(DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS)!; diff --git a/packages/grid/x-data-grid-pro/src/components/GridColumnHeaders.tsx b/packages/grid/x-data-grid-pro/src/components/GridColumnHeaders.tsx index 30b2beb3e579a..b527f5d3df96a 100644 --- a/packages/grid/x-data-grid-pro/src/components/GridColumnHeaders.tsx +++ b/packages/grid/x-data-grid-pro/src/components/GridColumnHeaders.tsx @@ -14,7 +14,6 @@ import { import { GridBaseColumnHeaders, GridColumnHeadersInner, - useGridColumnHeaders, UseGridColumnHeadersProps, } from '@mui/x-data-grid/internals'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; @@ -22,6 +21,7 @@ import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; import { GridPinnedPosition, GridPinnedColumns } from '../hooks/features/columnPinning'; import { filterColumns } from './DataGridProVirtualScroller'; +import { useGridColumnHeaders } from '../hooks/features/columnHeaders/useGridColumnHeaders'; type OwnerState = DataGridProProcessedProps & { leftPinnedColumns: GridPinnedColumns['left']; @@ -176,6 +176,7 @@ const GridColumnHeaders = React.forwardRef )} @@ -267,6 +274,11 @@ const GridColumnHeaders = React.forwardRef {rightRenderContext && ( )} diff --git a/packages/grid/x-data-grid-pro/src/components/GridDetailPanel.tsx b/packages/grid/x-data-grid-pro/src/components/GridDetailPanel.tsx index 8fddcb56efe33..73b22ae900bdb 100644 --- a/packages/grid/x-data-grid-pro/src/components/GridDetailPanel.tsx +++ b/packages/grid/x-data-grid-pro/src/components/GridDetailPanel.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { styled, SxProps, Theme } from '@mui/material/styles'; -import { GridRowId, useGridRootProps } from '@mui/x-data-grid'; +import { GridRowId } from '@mui/x-data-grid'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; @@ -38,7 +39,7 @@ function GridDetailPanel(props: GridDetailPanelProps) { const { rowId, height, style: styleProp = {}, ...other } = props; const apiRef = useGridPrivateApiContext(); const ref = React.useRef(); - const rootProps = useGridRootProps() as DataGridProProcessedProps; + const rootProps = useGridRootProps(); const ownerState = rootProps; React.useLayoutEffect(() => { diff --git a/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterAdornment.tsx b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterAdornment.tsx new file mode 100644 index 0000000000000..5851d274080b7 --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterAdornment.tsx @@ -0,0 +1,118 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { + GridFilterItem, + GridFilterOperator, + useGridApiContext, + GridColDef, +} from '@mui/x-data-grid'; +import { unstable_useId as useId } from '@mui/utils'; +import { unstable_gridHeaderFilteringMenuSelector } from '@mui/x-data-grid/internals'; +import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; +import { OPERATOR_SYMBOL_MAPPING } from './constants'; + +const sx = { + width: 22, + height: 22, + padding: '0px 0px 2px 2px', +}; + +function GridHeaderFilterAdornment(props: { + operators: GridFilterOperator[]; + field: GridColDef['field']; + item: GridFilterItem; + applyFilterChanges: (item: GridFilterItem) => void; + headerFilterMenuRef: React.MutableRefObject; + buttonRef: React.Ref; +}) { + const { operators, item, field, buttonRef, headerFilterMenuRef, ...others } = props; + + const buttonId = useId(); + const menuId = useId(); + + const rootProps = useGridRootProps(); + const apiRef = useGridApiContext(); + const open = Boolean( + unstable_gridHeaderFilteringMenuSelector(apiRef) === field && headerFilterMenuRef.current, + ); + + const handleClick = (event: React.MouseEvent) => { + headerFilterMenuRef.current = event.currentTarget; + apiRef.current.showHeaderFilterMenu(field); + }; + + if (!rootProps.slots.headerFilterMenu) { + return null; + } + + return ( + + + + {OPERATOR_SYMBOL_MAPPING[item?.operator] ?? '='} + + + + + ); +} + +GridHeaderFilterAdornment.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + applyFilterChanges: PropTypes.func.isRequired, + buttonRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: PropTypes.object, + }), + ]), + field: PropTypes.string.isRequired, + headerFilterMenuRef: PropTypes.shape({ + current: PropTypes.object, + }).isRequired, + item: PropTypes.shape({ + field: PropTypes.string.isRequired, + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + operator: PropTypes.string.isRequired, + value: PropTypes.any, + }).isRequired, + operators: PropTypes.arrayOf( + PropTypes.shape({ + getApplyFilterFn: PropTypes.func.isRequired, + getValueAsString: PropTypes.func, + headerLabel: PropTypes.string, + InputComponent: PropTypes.elementType, + InputComponentProps: PropTypes.object, + label: PropTypes.string, + requiresFilterValue: PropTypes.bool, + value: PropTypes.string.isRequired, + }), + ).isRequired, +} as any; + +export { GridHeaderFilterAdornment }; diff --git a/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx new file mode 100644 index 0000000000000..cdb410304fb41 --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx @@ -0,0 +1,321 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { + unstable_useForkRef as useForkRef, + unstable_composeClasses as composeClasses, + unstable_capitalize as capitalize, +} from '@mui/utils'; +import { + GridFilterItem, + GridFilterOperator, + GridHeaderFilterEventLookup, + GridColDef, + gridVisibleColumnFieldsSelector, + getDataGridUtilityClass, +} from '@mui/x-data-grid'; +import { + GridStateColDef, + useGridPrivateApiContext, + unstable_gridHeaderFilteringEditFieldSelector, + unstable_gridHeaderFilteringMenuSelector, + isNavigationKey, +} from '@mui/x-data-grid/internals'; +import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; +import { DataGridProProcessedProps } from '../../models/dataGridProProps'; +import { GridHeaderFilterAdornment } from './GridHeaderFilterAdornment'; +import { GridHeaderFilterClearButton } from './GridHeaderFilterClearButton'; + +export interface GridHeaderFilterCellProps extends Pick { + colIndex: number; + height: number; + sortIndex?: number; + hasFocus?: boolean; + tabIndex: 0 | -1; + headerFilterComponent?: React.ReactNode; + filterOperators?: GridFilterOperator[]; + width: number; + colDef: GridColDef; + headerFilterMenuRef: React.MutableRefObject; + item: GridFilterItem; + showClearIcon?: boolean; + InputComponentProps: GridFilterOperator['InputComponentProps']; +} + +type OwnerState = DataGridProProcessedProps & GridHeaderFilterCellProps; + +const useUtilityClasses = (ownerState: OwnerState) => { + const { colDef, classes, showColumnVerticalBorder } = ownerState; + + const slots = { + root: [ + 'columnHeader', + colDef.headerAlign === 'left' && 'columnHeader--alignLeft', + colDef.headerAlign === 'center' && 'columnHeader--alignCenter', + colDef.headerAlign === 'right' && 'columnHeader--alignRight', + 'withBorderColor', + showColumnVerticalBorder && 'columnHeader--withRightBorder', + ], + }; + + return composeClasses(slots, getDataGridUtilityClass, classes); +}; + +const GridHeaderFilterCell = React.forwardRef( + (props, ref) => { + const { + colIndex, + height, + hasFocus, + headerFilterComponent, + filterOperators, + width, + headerClassName, + colDef, + item, + headerFilterMenuRef, + InputComponentProps, + showClearIcon = true, + ...other + } = props; + + const apiRef = useGridPrivateApiContext(); + const columnFields = gridVisibleColumnFieldsSelector(apiRef); + const rootProps = useGridRootProps(); + const cellRef = React.useRef(null); + const handleRef = useForkRef(ref, cellRef); + const inputRef = React.useRef(null); + const buttonRef = React.useRef(null); + + const isEditing = unstable_gridHeaderFilteringEditFieldSelector(apiRef) === colDef.field; + const isMenuOpen = unstable_gridHeaderFilteringMenuSelector(apiRef) === colDef.field; + + const currentOperator = filterOperators![0]; + + const InputComponent = colDef.filterable ? currentOperator!.InputComponent : null; + + const applyFilterChanges = React.useCallback( + (updatedItem: GridFilterItem) => { + if (item.value && !updatedItem.value) { + apiRef.current.deleteFilterItem(updatedItem); + return; + } + apiRef.current.upsertFilterItem(updatedItem); + }, + [apiRef, item], + ); + + const clearFilterItem = React.useCallback(() => { + apiRef.current.deleteFilterItem(item); + }, [apiRef, item]); + + React.useLayoutEffect(() => { + if (hasFocus && !isMenuOpen) { + let focusableElement = cellRef.current!.querySelector('[tabindex="0"]'); + if (isEditing && InputComponent) { + focusableElement = inputRef.current; + } + const elementToFocus = focusableElement || cellRef.current; + elementToFocus?.focus(); + apiRef.current.columnHeadersContainerElementRef!.current!.scrollLeft = 0; + } + }, [InputComponent, apiRef, hasFocus, isEditing, isMenuOpen]); + + const onKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (isMenuOpen || isNavigationKey(event.key)) { + return; + } + switch (event.key) { + case 'Escape': + if (isEditing) { + apiRef.current.stopHeaderFilterEditMode(); + } + break; + case 'Enter': + if (isEditing) { + apiRef.current.stopHeaderFilterEditMode(); + break; + } + if (event.metaKey || event.ctrlKey) { + headerFilterMenuRef.current = buttonRef.current; + apiRef.current.showHeaderFilterMenu(colDef.field); + break; + } + apiRef.current.startHeaderFilterEditMode(colDef.field); + break; + case 'Tab': { + if (isEditing) { + const fieldToFocus = columnFields[colIndex + (event.shiftKey ? -1 : 1)] ?? null; + + if (fieldToFocus) { + apiRef.current.startHeaderFilterEditMode(fieldToFocus); + apiRef.current.setColumnHeaderFilterFocus(fieldToFocus, event); + } + } + break; + } + default: + if (isEditing || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) { + break; + } + apiRef.current.startHeaderFilterEditMode(colDef.field); + break; + } + }, + [apiRef, colDef.field, colIndex, columnFields, headerFilterMenuRef, isEditing, isMenuOpen], + ); + + const publish = React.useCallback( + (eventName: keyof GridHeaderFilterEventLookup, propHandler?: React.EventHandler) => + (event: React.SyntheticEvent) => { + apiRef.current.publishEvent( + eventName, + apiRef.current.getColumnHeaderParams(colDef.field), + event as any, + ); + + if (propHandler) { + propHandler(event); + } + }, + [apiRef, colDef.field], + ); + + const onMouseDown = React.useCallback( + (event: React.MouseEvent) => { + if (!hasFocus) { + if (inputRef.current) { + inputRef.current.focus(); + } + apiRef.current.setColumnHeaderFilterFocus(colDef.field, event); + } + }, + [apiRef, colDef.field, hasFocus], + ); + + const mouseEventsHandlers = React.useMemo( + () => ({ + onKeyDown: publish('headerFilterKeyDown', onKeyDown), + onClick: publish('headerFilterClick'), + onMouseDown: publish('headerFilterMouseDown', onMouseDown), + }), + [onMouseDown, onKeyDown, publish], + ); + + const ownerState = { + ...rootProps, + colDef, + }; + + const classes = useUtilityClasses(ownerState as OwnerState); + + const isNoInputOperator = + filterOperators?.find(({ value }) => item.operator === value)?.requiresFilterValue === false; + + const isApplied = Boolean(item?.value) || isNoInputOperator; + const label = + currentOperator.headerLabel ?? + apiRef.current.getLocaleText( + `headerFilterOperator${capitalize(item.operator)}` as 'headerFilterOperatorContains', + ); + + const isFilterActive = isApplied || hasFocus; + + return ( +
+ {headerFilterComponent} + {InputComponent && headerFilterComponent === undefined ? ( + apiRef.current.startHeaderFilterEditMode(colDef.field)} + onBlur={() => apiRef.current.stopHeaderFilterEditMode()} + placeholder={apiRef.current.getLocaleText('columnMenuFilter')} + label={isFilterActive ? capitalize(label) : ' '} + isFilterActive={isFilterActive} + headerFilterMenu={ + + } + clearButton={ + showClearIcon && isApplied ? ( + + ) : null + } + disabled={isNoInputOperator} + tabIndex={-1} + {...currentOperator?.InputComponentProps} + {...InputComponentProps} + /> + ) : null} +
+ ); + }, +); + +GridHeaderFilterCell.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + colDef: PropTypes.object.isRequired, + colIndex: PropTypes.number.isRequired, + filterOperators: PropTypes.arrayOf( + PropTypes.shape({ + getApplyFilterFn: PropTypes.func.isRequired, + getValueAsString: PropTypes.func, + headerLabel: PropTypes.string, + InputComponent: PropTypes.elementType, + InputComponentProps: PropTypes.object, + label: PropTypes.string, + requiresFilterValue: PropTypes.bool, + value: PropTypes.string.isRequired, + }), + ), + hasFocus: PropTypes.bool, + /** + * Class name that will be added in the column header cell. + */ + headerClassName: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + headerFilterComponent: PropTypes.node, + headerFilterMenuRef: PropTypes.shape({ + current: PropTypes.object, + }).isRequired, + height: PropTypes.number.isRequired, + InputComponentProps: PropTypes.object, + item: PropTypes.shape({ + field: PropTypes.string.isRequired, + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + operator: PropTypes.string.isRequired, + value: PropTypes.any, + }).isRequired, + showClearIcon: PropTypes.bool, + sortIndex: PropTypes.number, + tabIndex: PropTypes.oneOf([-1, 0]).isRequired, + width: PropTypes.number.isRequired, +} as any; + +export { GridHeaderFilterCell }; diff --git a/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterClearButton.tsx b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterClearButton.tsx new file mode 100644 index 0000000000000..6a200b65cb59b --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterClearButton.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; + +interface GridHeaderFilterClearIconProps { + onClick: () => void; +} + +const sx = { padding: '2px' }; + +function GridHeaderFilterClearButton({ onClick }: GridHeaderFilterClearIconProps) { + const rootProps = useGridRootProps(); + return ( + + + + ); +} + +export { GridHeaderFilterClearButton }; diff --git a/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx new file mode 100644 index 0000000000000..b781f307082a8 --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import MenuList from '@mui/material/MenuList'; +import MenuItem from '@mui/material/MenuItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import { unstable_capitalize as capitalize } from '@mui/utils'; +import { + useGridApiContext, + GridMenu, + GridFilterOperator, + GridFilterItem, + GridColDef, +} from '@mui/x-data-grid'; +import { OPERATOR_SYMBOL_MAPPING } from './constants'; + +interface GridHeaderFilterMenuProps { + field: GridColDef['field']; + applyFilterChanges: (item: GridFilterItem) => void; + operators: GridFilterOperator[]; + item: GridFilterItem; + open: boolean; + id: string; + labelledBy: string; + targetRef: React.MutableRefObject; +} + +function GridHeaderFilterMenu({ + open, + field, + targetRef, + applyFilterChanges, + operators, + item, + id, + labelledBy, +}: GridHeaderFilterMenuProps) { + const apiRef = useGridApiContext(); + + const hideMenu = React.useCallback(() => { + apiRef.current.hideHeaderFilterMenu(); + }, [apiRef]); + + const handleListKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'Tab') { + event.preventDefault(); + } + if (event.key === 'Escape' || event.key === 'Tab') { + hideMenu(); + } + }, + [hideMenu], + ); + + if (!targetRef.current) { + return null; + } + + return ( + + + {operators.map((op, i) => { + const label = + op?.headerLabel ?? + apiRef.current.getLocaleText( + `headerFilterOperator${capitalize(op.value)}` as 'headerFilterOperatorContains', + ); + + return ( + { + applyFilterChanges({ ...item, operator: op.value }); + hideMenu(); + }} + autoFocus={i === 0 ? open : false} + selected={op.value === item.operator} + key={`${field}-${op.value}`} + > + {OPERATOR_SYMBOL_MAPPING[op.value]} + {label} + + ); + })} + + + ); +} + +export { GridHeaderFilterMenu }; diff --git a/packages/grid/x-data-grid-pro/src/components/headerFiltering/constants.ts b/packages/grid/x-data-grid-pro/src/components/headerFiltering/constants.ts new file mode 100644 index 0000000000000..74339cf0b4c4c --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/components/headerFiltering/constants.ts @@ -0,0 +1,30 @@ +export const OPERATOR_SYMBOL_MAPPING: { [key: string]: string } = { + contains: '∋', + equals: '=', + '=': '=', + '!=': '≠', + '>': '>', + '>=': '≥', + '<': '<', + '<=': '≤', + startsWith: '⊃', + endsWith: '⊂', + is: '=', + not: '≠', + isNot: '≠', + isEmpty: '∅', + isNotEmpty: '∉', + isIn: '∈', + isNotIn: '∉', + isLessThan: '<', + isLessThanOrEqual: '≤', + isGreaterThan: '>', + isGreaterThanOrEqual: '≥', + isBetween: '∈', + isNotBetween: '∉', + isAnyOf: '∈', + after: '>', + onOrAfter: '≥', + before: '<', + onOrBefore: '≤', +}; diff --git a/packages/grid/x-data-grid-pro/src/components/headerFiltering/index.ts b/packages/grid/x-data-grid-pro/src/components/headerFiltering/index.ts new file mode 100644 index 0000000000000..64dbc58fb139c --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/components/headerFiltering/index.ts @@ -0,0 +1,2 @@ +export * from './GridHeaderFilterAdornment'; +export * from './GridHeaderFilterCell'; diff --git a/packages/grid/x-data-grid-pro/src/components/index.ts b/packages/grid/x-data-grid-pro/src/components/index.ts index a5a833f916ba9..c49dfb937ee3a 100644 --- a/packages/grid/x-data-grid-pro/src/components/index.ts +++ b/packages/grid/x-data-grid-pro/src/components/index.ts @@ -3,3 +3,4 @@ export * from './GridTreeDataGroupingCell'; export * from './GridColumnMenuPinningItem'; export * from './GridDetailPanelToggleCell'; export * from '../material/icons'; +export * from './headerFiltering'; diff --git a/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts b/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts index affb475b3c17d..9e098b64ec828 100644 --- a/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts +++ b/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts @@ -2,6 +2,8 @@ import { DATA_GRID_DEFAULT_SLOTS_COMPONENTS } from '@mui/x-data-grid/internals'; import type { GridProSlotsComponent } from '../models'; import { GridProColumnMenu } from '../components/GridProColumnMenu'; import { GridColumnHeaders } from '../components/GridColumnHeaders'; +import { GridHeaderFilterMenu } from '../components/headerFiltering/GridHeaderFilterMenu'; +import { GridHeaderFilterCell } from '../components/headerFiltering/GridHeaderFilterCell'; import materialSlots from '../material'; export const DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS: GridProSlotsComponent = { @@ -9,4 +11,6 @@ export const DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS: GridProSlotsComponent = { ...materialSlots, ColumnMenu: GridProColumnMenu, ColumnHeaders: GridColumnHeaders, + HeaderFilterCell: GridHeaderFilterCell, + HeaderFilterMenu: GridHeaderFilterMenu, }; diff --git a/packages/grid/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx new file mode 100644 index 0000000000000..31e5ac67c8b88 --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -0,0 +1,153 @@ +import * as React from 'react'; +import { + unstable_gridFocusColumnHeaderFilterSelector, + useGridSelector, + gridFilterModelSelector, + unstable_gridTabIndexColumnHeaderFilterSelector, +} from '@mui/x-data-grid'; +import { styled } from '@mui/system'; +import { + useGridColumnHeaders as useGridColumnHeadersCommunity, + UseGridColumnHeadersProps, + GetHeadersParams, + getTotalHeaderHeight, + useGridPrivateApiContext, + getGridFilter, +} from '@mui/x-data-grid/internals'; +import { useGridRootProps } from '../../utils/useGridRootProps'; +import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; + +type OwnerState = DataGridProProcessedProps; + +const GridHeaderFilterRow = styled('div', { + name: 'MuiDataGrid', + slot: 'HeaderFilterRow', + overridesResolver: (props, styles) => styles.headerFilterRow, +})<{ ownerState: OwnerState }>(() => ({ + display: 'flex', + borderTop: '1px solid rgba(224, 224, 224, 1)', +})); + +export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { + const apiRef = useGridPrivateApiContext(); + + const { headerGroupingMaxDepth, hasOtherElementInTabSequence } = props; + const columnHeaderFilterTabIndexState = useGridSelector( + apiRef, + unstable_gridTabIndexColumnHeaderFilterSelector, + ); + const { getColumnsToRender, getRootProps, ...otherProps } = useGridColumnHeadersCommunity({ + ...props, + hasOtherElementInTabSequence: + hasOtherElementInTabSequence || columnHeaderFilterTabIndexState !== null, + }); + const headerFiltersRef = React.useRef(null); + apiRef.current.register('private', { + headerFiltersElementRef: headerFiltersRef, + }); + const headerFilterMenuRef = React.useRef(null); + const rootProps = useGridRootProps(); + const disableHeaderFiltering = !rootProps.unstable_headerFilters; + const headerHeight = Math.floor(rootProps.columnHeaderHeight * props.densityFactor); + const filterModel = useGridSelector(apiRef, gridFilterModelSelector); + const totalHeaderHeight = + getTotalHeaderHeight(apiRef, rootProps.columnHeaderHeight) + + (disableHeaderFiltering ? 0 : headerHeight); + + const columnHeaderFilterFocus = useGridSelector( + apiRef, + unstable_gridFocusColumnHeaderFilterSelector, + ); + + const getColumnFilters = (params?: GetHeadersParams, other = {}) => { + if (disableHeaderFiltering) { + return null; + } + + const columnsToRender = getColumnsToRender(params); + + if (columnsToRender == null) { + return null; + } + + const { renderedColumns, firstColumnToRender } = columnsToRender; + + const filters: JSX.Element[] = []; + for (let i = 0; i < renderedColumns.length; i += 1) { + const colDef = renderedColumns[i]; + const columnIndex = firstColumnToRender + i; + const hasFocus = columnHeaderFilterFocus?.field === colDef.field; + const isFirstColumn = columnIndex === 0; + const tabIndexField = columnHeaderFilterTabIndexState?.field; + const tabIndex = + tabIndexField === colDef.field || (isFirstColumn && !props.hasOtherElementInTabSequence) + ? 0 + : -1; + + let headerFilterComponent: React.ReactNode; + if (colDef.renderHeaderFilter) { + headerFilterComponent = colDef.renderHeaderFilter( + apiRef.current.getColumnHeaderParams(colDef.field), + ); + } + + const headerClassName = + typeof colDef.headerClassName === 'function' + ? colDef.headerClassName({ field: colDef.field, colDef }) + : colDef.headerClassName; + + // TODO: Support for `isAnyOf` operator + const filterOperators = + colDef.filterOperators?.filter((operator) => operator.value !== 'isAnyOf') ?? []; + + const item = + filterModel?.items.find((it) => it.field === colDef.field && it.operator !== 'isAnyOf') ?? + getGridFilter(colDef); + + filters.push( + , + ); + } + + return ( + + {filters} + + ); + }; + + const rootStyle = { + minHeight: totalHeaderHeight, + maxHeight: totalHeaderHeight, + lineHeight: `${headerHeight}px`, + }; + + return { + ...otherProps, + getColumnFilters, + getRootProps: disableHeaderFiltering + ? getRootProps + : (other = {}) => ({ style: rootStyle, ...other }), + }; +}; diff --git a/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx index dcb67489b617b..735fd7dff2989 100644 --- a/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx +++ b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx @@ -136,6 +136,7 @@ export const useGridColumnResize = ( const colDefRef = React.useRef(); const colElementRef = React.useRef(); + const headerFilterElementRef = React.useRef(); const colGroupingElementRef = React.useRef(); const colCellElementsRef = React.useRef(); const theme = useTheme(); @@ -163,6 +164,14 @@ export const useGridColumnResize = ( colElementRef.current!.style.minWidth = `${newWidth}px`; colElementRef.current!.style.maxWidth = `${newWidth}px`; + const headerFilterElement = headerFilterElementRef.current; + + if (headerFilterElement) { + headerFilterElement.style.width = `${newWidth}px`; + headerFilterElement.style.minWidth = `${newWidth}px`; + headerFilterElement.style.maxWidth = `${newWidth}px`; + } + [...colCellElementsRef.current!, ...colGroupingElementRef.current!].forEach((element) => { const div = element as HTMLDivElement; let finalWidth: `${number}px`; @@ -257,6 +266,14 @@ export const useGridColumnResize = ( `[data-field="${colDef.field}"]`, )!; + const headerFilterRowElement = apiRef.current.headerFiltersElementRef?.current; + + if (headerFilterRowElement) { + headerFilterElementRef.current = headerFilterRowElement.querySelector( + `[data-field="${colDef.field}"]`, + ) as HTMLDivElement; + } + colGroupingElementRef.current = findGroupHeaderElementsFromField( apiRef.current.columnHeadersContainerElementRef?.current!, colDef.field, diff --git a/packages/grid/x-data-grid-pro/src/internals/index.ts b/packages/grid/x-data-grid-pro/src/internals/index.ts index 7c62bd5352239..e27291f96019e 100644 --- a/packages/grid/x-data-grid-pro/src/internals/index.ts +++ b/packages/grid/x-data-grid-pro/src/internals/index.ts @@ -1,9 +1,12 @@ +// eslint-disable-next-line import/export export * from '@mui/x-data-grid/internals'; export { DataGridProVirtualScroller } from '../components/DataGridProVirtualScroller'; export { GridColumnHeaders } from '../components/GridColumnHeaders'; export { DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridProDefaultSlotsComponents'; +// eslint-disable-next-line import/export +export { useGridColumnHeaders } from '../hooks/features/columnHeaders/useGridColumnHeaders'; export { useGridColumnResize, columnResizeStateInitializer, diff --git a/packages/grid/x-data-grid-pro/src/material/icons.tsx b/packages/grid/x-data-grid-pro/src/material/icons.tsx index f3b00c0713069..92e0daa257961 100644 --- a/packages/grid/x-data-grid-pro/src/material/icons.tsx +++ b/packages/grid/x-data-grid-pro/src/material/icons.tsx @@ -20,3 +20,8 @@ export const GridPushPinLeftIcon = createSvgIcon( , 'PushPinLeft', ); + +export const GridHighlightOffIcon = createSvgIcon( + , + 'HighlightOff', +); diff --git a/packages/grid/x-data-grid-pro/src/material/index.ts b/packages/grid/x-data-grid-pro/src/material/index.ts index 28b3d08f2bcc7..c9b64d6b31558 100644 --- a/packages/grid/x-data-grid-pro/src/material/index.ts +++ b/packages/grid/x-data-grid-pro/src/material/index.ts @@ -1,9 +1,10 @@ import type { GridProIconSlotsComponent } from '../models'; -import { GridPushPinRightIcon, GridPushPinLeftIcon } from './icons'; +import { GridPushPinRightIcon, GridPushPinLeftIcon, GridHighlightOffIcon } from './icons'; const iconSlots: GridProIconSlotsComponent = { ColumnMenuPinRightIcon: GridPushPinRightIcon, ColumnMenuPinLeftIcon: GridPushPinLeftIcon, + HeaderFilterClearIcon: GridHighlightOffIcon, }; const materialSlots = { diff --git a/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts index acd9ac0781bf8..7f3af31b3fc45 100644 --- a/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts @@ -24,6 +24,7 @@ import { } from './gridGroupingColDefOverride'; import { GridInitialStatePro } from './gridStatePro'; import { GridProSlotsComponent, UncapitalizedGridProSlotsComponent } from './gridProSlotsComponent'; +import type { GridProSlotProps } from './gridProSlotProps'; export interface GridExperimentalProFeatures extends GridExperimentalFeatures { /** @@ -140,10 +141,18 @@ export interface DataGridProPropsWithDefaultValue extends DataGridPropsWithDefau * @default false */ keepColumnPositionIfDraggedOutside: boolean; + /** + * If `true`, enables the data grid filtering on header feature. + * @default false + */ + unstable_headerFilters: boolean; } export interface DataGridProPropsWithoutDefaultValue - extends Omit, 'initialState'> { + extends Omit< + DataGridPropsWithoutDefaultValue, + 'initialState' | 'componentsProps' | 'slotProps' + > { /** * The ref object that allows grid manipulation. Can be instantiated with `useGridApiRef()`. */ @@ -241,4 +250,13 @@ export interface DataGridProPropsWithoutDefaultValue; + /** + * Overridable components props dynamically passed to the component at rendering. + */ + slotProps?: GridProSlotProps; + /** + * Overridable components props dynamically passed to the component at rendering. + * @deprecated Use the `slotProps` prop instead. + */ + componentsProps?: GridProSlotProps; } diff --git a/packages/grid/x-data-grid-pro/src/models/gridProIconSlotsComponent.ts b/packages/grid/x-data-grid-pro/src/models/gridProIconSlotsComponent.ts index 634bb56455480..19a4ba8bbcd0c 100644 --- a/packages/grid/x-data-grid-pro/src/models/gridProIconSlotsComponent.ts +++ b/packages/grid/x-data-grid-pro/src/models/gridProIconSlotsComponent.ts @@ -11,4 +11,9 @@ export interface GridProIconSlotsComponent { * @default GridPushPinRightIcon */ ColumnMenuPinRightIcon: React.JSXElementConstructor; + /** + * Component responsible for clear icon in header filter + * @default GridHighlightOffIcon + */ + HeaderFilterClearIcon: React.JSXElementConstructor; } diff --git a/packages/grid/x-data-grid-pro/src/models/gridProSlotProps.ts b/packages/grid/x-data-grid-pro/src/models/gridProSlotProps.ts new file mode 100644 index 0000000000000..2bb78d3c49ab2 --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/models/gridProSlotProps.ts @@ -0,0 +1,11 @@ +import { GridSlotsComponentsProps } from '@mui/x-data-grid/internals'; +import type { GridHeaderFilterCellProps } from '../components/headerFiltering/GridHeaderFilterCell'; + +// Overrides for module augmentation +export interface HeaderFilterCellPropsOverrides {} + +type SlotProps = Partial; + +export interface GridProSlotProps extends GridSlotsComponentsProps { + headerFilterCell?: SlotProps; +} diff --git a/packages/grid/x-data-grid-pro/src/models/gridProSlotsComponent.ts b/packages/grid/x-data-grid-pro/src/models/gridProSlotsComponent.ts index 08b3af672ac7e..51c7cdd00bbf9 100644 --- a/packages/grid/x-data-grid-pro/src/models/gridProSlotsComponent.ts +++ b/packages/grid/x-data-grid-pro/src/models/gridProSlotsComponent.ts @@ -1,4 +1,4 @@ -import { GridSlotsComponent, UncapitalizedGridSlotsComponent } from '@mui/x-data-grid'; +import { GridSlotsComponent } from '@mui/x-data-grid'; import { UncapitalizeObjectKeys } from '@mui/x-data-grid/internals'; import { GridProIconSlotsComponent } from './gridProIconSlotsComponent'; @@ -6,9 +6,19 @@ import { GridProIconSlotsComponent } from './gridProIconSlotsComponent'; * Grid components React prop interface containing all the overridable components * for Pro package */ -export interface GridProSlotsComponent extends GridSlotsComponent, GridProIconSlotsComponent {} +export interface GridProSlotsComponent extends GridSlotsComponent, GridProIconSlotsComponent { + /** + * Component responsible for showing menu adornment in Header filter row + * @default GridHeaderFilterCell + */ + HeaderFilterCell: React.JSXElementConstructor; + /** + * Component responsible for showing menu in Header filter row + * @default GridHeaderFilterMenu + */ + HeaderFilterMenu: React.JSXElementConstructor | null; +} // TODO: remove in v7 export interface UncapitalizedGridProSlotsComponent - extends UncapitalizedGridSlotsComponent, - UncapitalizeObjectKeys {} + extends UncapitalizeObjectKeys {} diff --git a/packages/grid/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx index 6c73ef4492bc4..8e0c7ccc72b76 100644 --- a/packages/grid/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx @@ -864,4 +864,135 @@ describe(' - Filter', () => { const newId = filterForm!.dataset.id; expect(oldId).to.equal(newId); }); + + describe('Header filters', () => { + it('should reflect the `filterModel` prop in header filters correctly', () => { + render(); + + expect(getColumnValues(0)).to.deep.equal(['Adidas', 'Puma']); + const filterCellInput = getColumnHeaderCell(0, 1).querySelector('input'); + expect(filterCellInput).to.have.value('a'); + }); + + it('should apply filters on type when the focus is on cell', () => { + render(); + + expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas', 'Puma']); + const filterCell = getColumnHeaderCell(0, 1); + const filterCellInput = filterCell.querySelector('input')!; + expect(filterCellInput).not.toHaveFocus(); + fireEvent.mouseDown(filterCell); + expect(filterCellInput).toHaveFocus(); + fireEvent.change(filterCellInput, { target: { value: 'ad' } }); + clock.tick(SUBMIT_FILTER_STROKE_TIME); + expect(getColumnValues(0)).to.deep.equal(['Adidas']); + }); + + it('should call `onFilterModelChange` when filters are updated', () => { + const onFilterModelChange = spy(); + render(); + + const filterCell = getColumnHeaderCell(0, 1); + const filterCellInput = filterCell.querySelector('input')!; + fireEvent.click(filterCell); + fireEvent.change(filterCellInput, { target: { value: 'ad' } }); + clock.tick(SUBMIT_FILTER_STROKE_TIME); + expect(onFilterModelChange.callCount).to.equal(1); + }); + + it('should allow to change the operator from operator menu', () => { + const onFilterModelChange = spy(); + render( + , + ); + expect(getColumnValues(0)).to.deep.equal(['Adidas', 'Puma']); + + const filterCell = getColumnHeaderCell(0, 1); + fireEvent.click(filterCell); + + fireEvent.click(within(filterCell).getByLabelText('Operator')); + fireEvent.click(screen.getByRole('menuitem', { name: '= Equals' })); + + expect(onFilterModelChange.callCount).to.equal(1); + expect(onFilterModelChange.lastCall.firstArg.items[0].operator).to.equal('equals'); + expect(getColumnValues(0)).to.deep.equal([]); + }); + + it('should allow to clear the filter by clear button', () => { + render( + , + ); + + expect(getColumnValues(0)).to.deep.equal(['Adidas', 'Puma']); + fireEvent.click(screen.getByRole('button', { name: 'Clear filter' })); + expect(getColumnValues(0)).to.deep.equal(['Nike', 'Adidas', 'Puma']); + }); + + it('should allow to customize header filter cell using `renderHeaderFilter`', () => { + render( + 'Custom Filter Cell' }, + ]} + unstable_headerFilters + />, + ); + + expect(getColumnHeaderCell(0, 1).textContent).to.equal('Custom Filter Cell'); + }); + + it('should allow to customize header filter cell using `filterOperators`', () => { + render( + () => true, + InputComponent: () =>
Custom Input
, + }, + ], + }, + ]} + unstable_headerFilters + />, + ); + + expect(getColumnHeaderCell(0, 1).textContent).to.equal('Custom Input'); + }); + }); }); diff --git a/packages/grid/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/grid/x-data-grid-pro/src/typeOverloads/modules.ts index e804ab1b6d3de..6300554f4f45e 100644 --- a/packages/grid/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/grid/x-data-grid-pro/src/typeOverloads/modules.ts @@ -1,4 +1,4 @@ -import { GridRowId } from '@mui/x-data-grid'; +import { GridRowId, GridColumnHeaderParams, GridValidRowModel } from '@mui/x-data-grid'; import type { GridRowScrollEndParams, GridRowOrderChangeParams, @@ -11,6 +11,16 @@ import type { import type { GridCanBeReorderedPreProcessingContext } from '../hooks/features/columnReorder/columnReorderInterfaces'; import { GridRowPinningInternalCache } from '../hooks/features/rowPinning/gridRowPinningInterface'; +export interface GridColDefPro { + /** + * Allows to render a component in the column header filter cell. + * @template R, V, F + * @param {GridColumnHeaderParams} params Object containing parameters for the renderer. + * @returns {React.ReactNode} The element to be rendered. + */ + renderHeaderFilter?: (params: GridColumnHeaderParams) => React.ReactNode; +} + export interface GridControlledStateEventLookupPro { /** * Fired when the open detail panels are changed. @@ -61,4 +71,6 @@ declare module '@mui/x-data-grid' { declare module '@mui/x-data-grid/internals' { interface GridApiCaches extends GridApiCachesPro {} + + interface GridBaseColDef extends GridColDefPro {} } diff --git a/packages/grid/x-data-grid/src/colDef/gridNumericOperators.ts b/packages/grid/x-data-grid/src/colDef/gridNumericOperators.ts index 95ab52d9b52e8..182b839e6f2c6 100644 --- a/packages/grid/x-data-grid/src/colDef/gridNumericOperators.ts +++ b/packages/grid/x-data-grid/src/colDef/gridNumericOperators.ts @@ -27,7 +27,6 @@ export const getGridNumericOperators = (): GridFilterOperator< any >[] => [ { - label: '=', value: '=', getApplyFilterFn: (filterItem) => { if (filterItem.value == null || Number.isNaN(filterItem.value)) { @@ -42,7 +41,6 @@ export const getGridNumericOperators = (): GridFilterOperator< InputComponentProps: { type: 'number' }, }, { - label: '!=', value: '!=', getApplyFilterFn: (filterItem) => { if (filterItem.value == null || Number.isNaN(filterItem.value)) { @@ -57,7 +55,6 @@ export const getGridNumericOperators = (): GridFilterOperator< InputComponentProps: { type: 'number' }, }, { - label: '>', value: '>', getApplyFilterFn: (filterItem) => { if (filterItem.value == null || Number.isNaN(filterItem.value)) { @@ -76,7 +73,6 @@ export const getGridNumericOperators = (): GridFilterOperator< InputComponentProps: { type: 'number' }, }, { - label: '>=', value: '>=', getApplyFilterFn: (filterItem) => { if (filterItem.value == null || Number.isNaN(filterItem.value)) { @@ -95,7 +91,6 @@ export const getGridNumericOperators = (): GridFilterOperator< InputComponentProps: { type: 'number' }, }, { - label: '<', value: '<', getApplyFilterFn: (filterItem) => { if (filterItem.value == null || Number.isNaN(filterItem.value)) { @@ -114,7 +109,6 @@ export const getGridNumericOperators = (): GridFilterOperator< InputComponentProps: { type: 'number' }, }, { - label: '<=', value: '<=', getApplyFilterFn: (filterItem) => { if (filterItem.value == null || Number.isNaN(filterItem.value)) { diff --git a/packages/grid/x-data-grid/src/components/cell/GridEditInputCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridEditInputCell.tsx index 43de448032ee1..5cc528ceb64f7 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridEditInputCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridEditInputCell.tsx @@ -132,7 +132,11 @@ const GridEditInputCell = React.forwardRef : undefined} + endAdornment={ + isProcessingProps ? ( + + ) : undefined + } {...other} /> ); diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx index 50d0989d653f5..09860164e2fb9 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx @@ -1,11 +1,41 @@ import * as React from 'react'; +import PropTypes from 'prop-types'; import { TextFieldProps } from '@mui/material/TextField'; import { unstable_useId as useId } from '@mui/utils'; +import { styled } from '@mui/material/styles'; import { GridFilterInputValueProps } from './GridFilterInputValueProps'; import { useGridRootProps } from '../../../hooks/utils/useGridRootProps'; -export function GridFilterInputBoolean(props: GridFilterInputValueProps & TextFieldProps) { - const { item, applyValue, apiRef, focusElementRef, ...others } = props; +export type GridFilterInputBooleanProps = GridFilterInputValueProps & + TextFieldProps & { + headerFilterMenu?: React.ReactNode | null; + clearButton?: React.ReactNode | null; + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive?: boolean; + }; + +const BooleanOperatorContainer = styled('div')({ + display: 'flex', + alignItems: 'flex-end', + width: '100%', +}); + +function GridFilterInputBoolean(props: GridFilterInputBooleanProps) { + const { + item, + applyValue, + apiRef, + focusElementRef, + headerFilterMenu, + isFilterActive, + clearButton, + tabIndex, + label: labelProp, + ...others + } = props; const [filterValueState, setFilterValueState] = React.useState(item.value || ''); const rootProps = useGridRootProps(); @@ -30,53 +60,89 @@ export function GridFilterInputBoolean(props: GridFilterInputValueProps & TextFi setFilterValueState(item.value || ''); }, [item.value]); - const label = apiRef.current.getLocaleText('filterPanelInputLabel'); + const label = labelProp ?? apiRef.current.getLocaleText('filterPanelInputLabel'); return ( - - - {label} - - - + + - {apiRef.current.getLocaleText('filterValueAny')} - - + - {apiRef.current.getLocaleText('filterValueTrue')} - - - {apiRef.current.getLocaleText('filterValueFalse')} - - - + + {apiRef.current.getLocaleText('filterValueAny')} + + + {apiRef.current.getLocaleText('filterValueTrue')} + + + {apiRef.current.getLocaleText('filterValueFalse')} + + + + {clearButton} + ); } + +GridFilterInputBoolean.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + apiRef: PropTypes.shape({ + current: PropTypes.object.isRequired, + }).isRequired, + applyValue: PropTypes.func.isRequired, + clearButton: PropTypes.node, + focusElementRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: PropTypes.any.isRequired, + }), + ]), + headerFilterMenu: PropTypes.node, + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive: PropTypes.bool, + item: PropTypes.shape({ + field: PropTypes.string.isRequired, + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + operator: PropTypes.string.isRequired, + value: PropTypes.any, + }).isRequired, +} as any; + +export { GridFilterInputBoolean }; diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx index d14f519a58da8..c5ac4aee9052d 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx @@ -6,12 +6,34 @@ import { GridFilterInputValueProps } from './GridFilterInputValueProps'; import { useGridRootProps } from '../../../hooks/utils/useGridRootProps'; export type GridFilterInputDateProps = GridFilterInputValueProps & - TextFieldProps & { type?: 'date' | 'datetime-local' }; + TextFieldProps & { + type?: 'date' | 'datetime-local'; + headerFilterMenu?: React.ReactNode | null; + clearButton?: React.ReactNode | null; + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive?: boolean; + }; export const SUBMIT_FILTER_DATE_STROKE_TIME = 500; function GridFilterInputDate(props: GridFilterInputDateProps) { - const { item, applyValue, type, apiRef, focusElementRef, InputProps, ...other } = props; + const { + item, + applyValue, + type, + apiRef, + focusElementRef, + InputProps, + headerFilterMenu, + isFilterActive, + clearButton, + tabIndex, + disabled, + ...other + } = props; const filterTimeout = React.useRef(); const [filterValueState, setFilterValueState] = React.useState(item.value ?? ''); const [applying, setIsApplying] = React.useState(false); @@ -47,6 +69,7 @@ function GridFilterInputDate(props: GridFilterInputDateProps) { return ( } : {}), + ...(applying || clearButton + ? { + endAdornment: applying ? ( + + ) : ( + clearButton + ), + } + : {}), + ...(headerFilterMenu && isFilterActive ? { startAdornment: headerFilterMenu } : {}), + disabled, ...InputProps, inputProps: { max: type === 'datetime-local' ? '9999-12-31T23:59' : '9999-12-31', + tabIndex, ...InputProps?.inputProps, }, }} @@ -81,10 +115,17 @@ GridFilterInputDate.propTypes = { current: PropTypes.object.isRequired, }).isRequired, applyValue: PropTypes.func.isRequired, + clearButton: PropTypes.node, focusElementRef: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ PropTypes.func, PropTypes.object, ]), + headerFilterMenu: PropTypes.node, + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive: PropTypes.bool, item: PropTypes.shape({ field: PropTypes.string.isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx index 40401a033b80f..f8a08f0f37c7d 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { TextFieldProps } from '@mui/material/TextField'; +import { SelectChangeEvent } from '@mui/material/Select'; import { unstable_useId as useId } from '@mui/utils'; +import { styled } from '@mui/material/styles'; import { GridFilterInputValueProps } from './GridFilterInputValueProps'; import { GridSingleSelectColDef } from '../../../models/colDef/gridColDef'; import { useGridRootProps } from '../../../hooks/utils/useGridRootProps'; @@ -40,9 +42,22 @@ const renderSingleSelectOptions = ({ }); }; +const SingleSelectOperatorContainer = styled('div')({ + display: 'flex', + alignItems: 'flex-end', + width: '100%', +}); + export type GridFilterInputSingleSelectProps = GridFilterInputValueProps & TextFieldProps & Pick & { + headerFilterMenu?: React.ReactNode | null; + clearButton?: React.ReactNode | null; + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive?: boolean; type?: 'singleSelect'; }; @@ -55,6 +70,12 @@ function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { focusElementRef, getOptionLabel: getOptionLabelProp, getOptionValue: getOptionValueProp, + placeholder, + tabIndex, + label: labelProp, + headerFilterMenu, + isFilterActive, + clearButton, ...others } = props; const [filterValueState, setFilterValueState] = React.useState(item.value ?? ''); @@ -85,7 +106,7 @@ function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { }, [resolvedColumn]); const onFilterChange = React.useCallback( - (event: React.ChangeEvent) => { + (event: SelectChangeEvent) => { let value = event.target.value; // NativeSelect casts the value to a string. @@ -124,44 +145,49 @@ function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { return null; } - const label = apiRef.current.getLocaleText('filterPanelInputLabel'); + const label = labelProp ?? apiRef.current.getLocaleText('filterPanelInputLabel'); return ( - - - {label} - - - {renderSingleSelectOptions({ - column: resolvedColumn, - OptionComponent: rootProps.slots.baseSelectOption, - getOptionLabel, - getOptionValue, - isSelectNative, - baseSelectOptionProps: rootProps.slotProps?.baseSelectOption, - })} - - + + + + {label} + + + {renderSingleSelectOptions({ + column: resolvedColumn, + OptionComponent: rootProps.slots.baseSelectOption, + getOptionLabel, + getOptionValue, + isSelectNative, + baseSelectOptionProps: rootProps.slotProps?.baseSelectOption, + })} + + + {clearButton} + ); } @@ -174,6 +200,7 @@ GridFilterInputSingleSelect.propTypes = { current: PropTypes.object.isRequired, }).isRequired, applyValue: PropTypes.func.isRequired, + clearButton: PropTypes.node, focusElementRef: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ PropTypes.func, PropTypes.object, @@ -190,6 +217,12 @@ GridFilterInputSingleSelect.propTypes = { * @returns {string} The value to be used. */ getOptionValue: PropTypes.func, + headerFilterMenu: PropTypes.node, + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive: PropTypes.bool, item: PropTypes.shape({ field: PropTypes.string.isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx index fdcf42d0239dc..f590bc7744db4 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx @@ -7,12 +7,33 @@ import { useGridRootProps } from '../../../hooks/utils/useGridRootProps'; export const SUBMIT_FILTER_STROKE_TIME = 500; -export interface GridTypeFilterInputValueProps extends GridFilterInputValueProps { - type?: 'text' | 'number' | 'date' | 'datetime-local'; -} +export type GridTypeFilterInputValueProps = GridFilterInputValueProps & + TextFieldProps & { + type?: 'text' | 'number' | 'date' | 'datetime-local'; + headerFilterMenu?: React.ReactNode | null; + clearButton?: React.ReactNode | null; + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive?: boolean; + }; -function GridFilterInputValue(props: GridTypeFilterInputValueProps & TextFieldProps) { - const { item, applyValue, type, apiRef, focusElementRef, ...others } = props; +function GridFilterInputValue(props: GridTypeFilterInputValueProps) { + const { + item, + applyValue, + type, + apiRef, + focusElementRef, + tabIndex, + disabled, + headerFilterMenu, + isFilterActive, + clearButton, + InputProps, + ...others + } = props; const filterTimeout = React.useRef(); const [filterValueState, setFilterValueState] = React.useState(item.value ?? ''); const [applying, setIsApplying] = React.useState(false); @@ -45,8 +66,6 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps & TextFieldPr setFilterValueState(String(itemValue)); }, [item.value]); - const InputProps = applying ? { endAdornment: } : others.InputProps; - return ( + ) : ( + clearButton + ), + } + : {}), + ...(headerFilterMenu && isFilterActive ? { startAdornment: headerFilterMenu } : {}), + disabled, + ...InputProps, + inputProps: { + tabIndex, + ...InputProps?.inputProps, + }, + }} InputLabelProps={{ shrink: true, }} @@ -76,10 +112,17 @@ GridFilterInputValue.propTypes = { current: PropTypes.object.isRequired, }).isRequired, applyValue: PropTypes.func.isRequired, + clearButton: PropTypes.node, focusElementRef: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ PropTypes.func, PropTypes.object, ]), + headerFilterMenu: PropTypes.node, + /** + * It is `true` if the filter either has a value or an operator with no value + * required is selected (e.g. `isEmpty`) + */ + isFilterActive: PropTypes.bool, item: PropTypes.shape({ field: PropTypes.string.isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx index 6ac20c3e4aba6..757725d1a964a 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx @@ -314,4 +314,4 @@ GridFilterPanel.propTypes = { ]), } as any; -export { GridFilterPanel }; +export { GridFilterPanel, getGridFilter }; diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/index.ts b/packages/grid/x-data-grid/src/components/panel/filterPanel/index.ts index d3f73c4e7a939..6490e6abb478d 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/index.ts +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/index.ts @@ -2,7 +2,9 @@ export * from './GridFilterForm'; export * from './GridFilterInputValue'; export * from './GridFilterInputDate'; export * from './GridFilterInputSingleSelect'; +export * from './GridFilterInputBoolean'; export * from './GridFilterInputValueProps'; -export * from './GridFilterPanel'; +export { GridFilterPanel } from './GridFilterPanel'; +export type { GetColumnForNewFilterArgs } from './GridFilterPanel'; export * from './GridFilterInputMultipleValue'; export * from './GridFilterInputMultipleSingleSelect'; diff --git a/packages/grid/x-data-grid/src/constants/localeTextConstants.ts b/packages/grid/x-data-grid/src/constants/localeTextConstants.ts index a096bdb62c29b..5c0ec8a795b38 100644 --- a/packages/grid/x-data-grid/src/constants/localeTextConstants.ts +++ b/packages/grid/x-data-grid/src/constants/localeTextConstants.ts @@ -69,6 +69,33 @@ export const GRID_DEFAULT_LOCALE_TEXT: GridLocaleText = { filterOperatorIsEmpty: 'is empty', filterOperatorIsNotEmpty: 'is not empty', filterOperatorIsAnyOf: 'is any of', + 'filterOperator=': '=', + 'filterOperator!=': '!=', + 'filterOperator>': '>', + 'filterOperator>=': '>=', + 'filterOperator<': '<', + 'filterOperator<=': '<=', + + // Header filter operators text + headerFilterOperatorContains: 'Contains', + headerFilterOperatorEquals: 'Equals', + headerFilterOperatorStartsWith: 'Starts with', + headerFilterOperatorEndsWith: 'Ends with', + headerFilterOperatorIs: 'Is', + headerFilterOperatorNot: 'Is not', + headerFilterOperatorAfter: 'Is after', + headerFilterOperatorOnOrAfter: 'Is on or after', + headerFilterOperatorBefore: 'Is before', + headerFilterOperatorOnOrBefore: 'Is on or before', + headerFilterOperatorIsEmpty: 'Is empty', + headerFilterOperatorIsNotEmpty: 'Is not empty', + headerFilterOperatorIsAnyOf: 'Is any of', + 'headerFilterOperator=': 'Equals', + 'headerFilterOperator!=': 'Not equals', + 'headerFilterOperator>': 'Is greater than', + 'headerFilterOperator>=': 'Is greater than or equal to', + 'headerFilterOperator<': 'Is less than', + 'headerFilterOperator<=': 'Is less than or equal to', // Filter values text filterValueAny: 'any', diff --git a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index 58d8ba5492d5d..c8c1314294a40 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -62,7 +62,7 @@ export interface UseGridColumnHeadersProps { hasOtherElementInTabSequence: boolean; } -interface GetHeadersParams { +export interface GetHeadersParams { renderContext: GridRenderContext | null; minFirstColumn?: number; maxLastColumn?: number; @@ -416,9 +416,10 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const headerInfo: HeaderInfo = { groupId, - width: columnFields - .map((field) => apiRef.current.getColumn(field).computedWidth) - .reduce((acc, val) => acc + val, 0), + width: columnFields.reduce( + (acc, field) => acc + apiRef.current.getColumn(field).computedWidth, + 0, + ), fields: columnFields, colIndex: columnIndex, hasFocus, @@ -478,6 +479,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { return { renderContext, getColumnHeaders, + getColumnsToRender, getColumnGroupHeaders, isDragging: !!dragCol, getRootProps: (other = {}) => ({ style: rootStyle, ...other }), @@ -485,5 +487,6 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { ref: handleInnerRef, role: 'rowgroup', }), + headerHeight, }; }; diff --git a/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusState.ts b/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusState.ts index 54c16a7fec8cc..3a4f313aadbe2 100644 --- a/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusState.ts +++ b/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusState.ts @@ -6,11 +6,13 @@ export type GridColumnGroupIdentifier = { field: string; depth: number }; export interface GridFocusState { cell: GridCellCoordinates | null; columnHeader: GridColumnIdentifier | null; + columnHeaderFilter: GridColumnIdentifier | null; columnGroupHeader: GridColumnGroupIdentifier | null; } export interface GridTabIndexState { cell: GridCellCoordinates | null; columnHeader: GridColumnIdentifier | null; + columnHeaderFilter: GridColumnIdentifier | null; columnGroupHeader: GridColumnGroupIdentifier | null; } diff --git a/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusStateSelector.ts b/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusStateSelector.ts index d48d170da81de..fbaec55fd5879 100644 --- a/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusStateSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/features/focus/gridFocusStateSelector.ts @@ -14,6 +14,12 @@ export const gridFocusColumnHeaderSelector = createSelector( (focusState: GridFocusState) => focusState.columnHeader, ); +// eslint-disable-next-line @typescript-eslint/naming-convention +export const unstable_gridFocusColumnHeaderFilterSelector = createSelector( + gridFocusStateSelector, + (focusState: GridFocusState) => focusState.columnHeaderFilter, +); + // eslint-disable-next-line @typescript-eslint/naming-convention export const unstable_gridFocusColumnGroupHeaderSelector = createSelector( gridFocusStateSelector, @@ -32,6 +38,12 @@ export const gridTabIndexColumnHeaderSelector = createSelector( (state: GridTabIndexState) => state.columnHeader, ); +// eslint-disable-next-line @typescript-eslint/naming-convention +export const unstable_gridTabIndexColumnHeaderFilterSelector = createSelector( + gridTabIndexStateSelector, + (state: GridTabIndexState) => state.columnHeaderFilter, +); + // eslint-disable-next-line @typescript-eslint/naming-convention export const unstable_gridTabIndexColumnGroupHeaderSelector = createSelector( gridTabIndexStateSelector, diff --git a/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts b/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts index ff7e05434ccb3..8c3e03cbbbe89 100644 --- a/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts +++ b/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts @@ -23,8 +23,8 @@ import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; export const focusStateInitializer: GridStateInitializer = (state) => ({ ...state, - focus: { cell: null, columnHeader: null, columnGroupHeader: null }, - tabIndex: { cell: null, columnHeader: null, columnGroupHeader: null }, + focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null }, + tabIndex: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null }, }); /** @@ -67,8 +67,18 @@ export const useGridFocus = ( logger.debug(`Focusing on cell with id=${id} and field=${field}`); return { ...state, - tabIndex: { cell: { id, field }, columnHeader: null, columnGroupHeader: null }, - focus: { cell: { id, field }, columnHeader: null, columnGroupHeader: null }, + tabIndex: { + cell: { id, field }, + columnHeader: null, + columnHeaderFilter: null, + columnGroupHeader: null, + }, + focus: { + cell: { id, field }, + columnHeader: null, + columnHeaderFilter: null, + columnGroupHeader: null, + }, }; }); apiRef.current.forceUpdate(); @@ -99,8 +109,48 @@ export const useGridFocus = ( return { ...state, - tabIndex: { columnHeader: { field }, cell: null, columnGroupHeader: null }, - focus: { columnHeader: { field }, cell: null, columnGroupHeader: null }, + tabIndex: { + columnHeader: { field }, + columnHeaderFilter: null, + cell: null, + columnGroupHeader: null, + }, + focus: { + columnHeader: { field }, + columnHeaderFilter: null, + cell: null, + columnGroupHeader: null, + }, + }; + }); + + apiRef.current.forceUpdate(); + }, + [apiRef, logger, publishCellFocusOut], + ); + + const setColumnHeaderFilterFocus = React.useCallback( + (field, event = {}) => { + const cell = gridFocusCellSelector(apiRef); + publishCellFocusOut(cell, event); + + apiRef.current.setState((state) => { + logger.debug(`Focusing on column header filter with colIndex=${field}`); + + return { + ...state, + tabIndex: { + columnHeader: null, + columnHeaderFilter: { field }, + cell: null, + columnGroupHeader: null, + }, + focus: { + columnHeader: null, + columnHeaderFilter: { field }, + cell: null, + columnGroupHeader: null, + }, }; }); @@ -125,8 +175,18 @@ export const useGridFocus = ( apiRef.current.setState((state) => { return { ...state, - tabIndex: { columnGroupHeader: { field, depth }, columnHeader: null, cell: null }, - focus: { columnGroupHeader: { field, depth }, columnHeader: null, cell: null }, + tabIndex: { + columnGroupHeader: { field, depth }, + columnHeader: null, + columnHeaderFilter: null, + cell: null, + }, + focus: { + columnGroupHeader: { field, depth }, + columnHeader: null, + columnHeaderFilter: null, + cell: null, + }, }; }); @@ -270,7 +330,7 @@ export const useGridFocus = ( logger.debug(`Clearing focus`); apiRef.current.setState((state) => ({ ...state, - focus: { cell: null, columnHeader: null, columnGroupHeader: null }, + focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null }, })); }, [logger, apiRef]); @@ -315,7 +375,12 @@ export const useGridFocus = ( } else { apiRef.current.setState((state) => ({ ...state, - focus: { cell: null, columnHeader: null, columnGroupHeader: null }, + focus: { + cell: null, + columnHeader: null, + columnHeaderFilter: null, + columnGroupHeader: null, + }, })); apiRef.current.forceUpdate(); @@ -347,7 +412,12 @@ export const useGridFocus = ( if (cell && !apiRef.current.getRow(cell.id)) { apiRef.current.setState((state) => ({ ...state, - focus: { cell: null, columnHeader: null, columnGroupHeader: null }, + focus: { + cell: null, + columnHeader: null, + columnHeaderFilter: null, + columnGroupHeader: null, + }, })); } }, [apiRef]); @@ -355,6 +425,7 @@ export const useGridFocus = ( const focusApi: GridFocusApi = { setCellFocus, setColumnHeaderFocus, + setColumnHeaderFilterFocus, }; const focusPrivateApi: GridFocusPrivateApi = { diff --git a/packages/grid/x-data-grid/src/hooks/features/headerFiltering/gridHeaderFilteringSelectors.ts b/packages/grid/x-data-grid/src/hooks/features/headerFiltering/gridHeaderFilteringSelectors.ts new file mode 100644 index 0000000000000..f1c276166cd93 --- /dev/null +++ b/packages/grid/x-data-grid/src/hooks/features/headerFiltering/gridHeaderFilteringSelectors.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { createSelector } from '../../../utils/createSelector'; +import { GridStateCommunity } from '../../../models/gridStateCommunity'; + +export const unstable_gridHeaderFilteringStateSelector = (state: GridStateCommunity) => + state.headerFiltering; + +export const unstable_gridHeaderFilteringEditFieldSelector = createSelector( + unstable_gridHeaderFilteringStateSelector, + (headerFilteringState) => headerFilteringState.editing, +); + +export const unstable_gridHeaderFilteringMenuSelector = createSelector( + unstable_gridHeaderFilteringStateSelector, + (headerFilteringState) => headerFilteringState.menuOpen, +); diff --git a/packages/grid/x-data-grid/src/hooks/features/headerFiltering/index.ts b/packages/grid/x-data-grid/src/hooks/features/headerFiltering/index.ts new file mode 100644 index 0000000000000..8d88119325326 --- /dev/null +++ b/packages/grid/x-data-grid/src/hooks/features/headerFiltering/index.ts @@ -0,0 +1 @@ +export * from './gridHeaderFilteringSelectors'; diff --git a/packages/grid/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts b/packages/grid/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts new file mode 100644 index 0000000000000..414d1a167a532 --- /dev/null +++ b/packages/grid/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts @@ -0,0 +1,120 @@ +import * as React from 'react'; +import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; +import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; +import { GridHeaderFilteringState } from '../../../models/gridHeaderFilteringModel'; +import { useGridApiMethod } from '../../utils/useGridApiMethod'; +import { GridStateInitializer } from '../../utils/useGridInitializeState'; +import { useGridLogger } from '../../utils'; +import { + gridColumnLookupSelector, + gridColumnVisibilityModelSelector, + gridColumnFieldsSelector, +} from '../columns/gridColumnsSelector'; +import { + GridHeaderFilteringApi, + GridHeaderFilteringPrivateApi, +} from '../../../models/api/gridHeaderFilteringApi'; + +export const headerFilteringStateInitializer: GridStateInitializer = (state) => ({ + ...state, + headerFiltering: { editing: null, menuOpen: null }, +}); + +export const useGridHeaderFiltering = ( + apiRef: React.MutableRefObject, + props: Pick, +) => { + const logger = useGridLogger(apiRef, 'useGridHeaderFiltering'); + + const setHeaderFilterState = React.useCallback( + (headerFilterState: Partial) => { + apiRef.current.setState((state) => { + // Safety check to avoid MIT users from using it + // This hook should ultimately be moved to the Pro package + if (props.signature === 'DataGrid') { + return state; + } + return { + ...state, + headerFiltering: { + editing: headerFilterState.editing ?? null, + menuOpen: headerFilterState.menuOpen ?? null, + }, + }; + }); + apiRef.current.forceUpdate(); + }, + [apiRef, props.signature], + ); + + const startHeaderFilterEditMode = React.useCallback< + GridHeaderFilteringApi['startHeaderFilterEditMode'] + >( + (field) => { + logger.debug(`Starting edit mode on header filter for field: ${field}`); + apiRef.current.setHeaderFilterState({ editing: field }); + }, + [apiRef, logger], + ); + + const stopHeaderFilterEditMode = React.useCallback< + GridHeaderFilteringApi['stopHeaderFilterEditMode'] + >(() => { + logger.debug(`Stopping edit mode on header filter`); + apiRef.current.setHeaderFilterState({ editing: null }); + }, [apiRef, logger]); + + const showHeaderFilterMenu = React.useCallback( + (field) => { + logger.debug(`Opening header filter menu for field: ${field}`); + apiRef.current.setHeaderFilterState({ menuOpen: field }); + }, + [apiRef, logger], + ); + + const hideHeaderFilterMenu = React.useCallback< + GridHeaderFilteringApi['hideHeaderFilterMenu'] + >(() => { + logger.debug(`Hiding header filter menu for active field`); + let fieldToFocus = apiRef.current.state.headerFiltering.menuOpen; + if (fieldToFocus) { + const columnLookup = gridColumnLookupSelector(apiRef); + const columnVisibilityModel = gridColumnVisibilityModelSelector(apiRef); + const orderedFields = gridColumnFieldsSelector(apiRef); + + // If the column was removed from the grid, we need to find the closest visible field + if (!columnLookup[fieldToFocus]) { + fieldToFocus = orderedFields[0]; + } + + // If the field to focus is hidden, we need to find the closest visible field + if (columnVisibilityModel[fieldToFocus] === false) { + // contains visible column fields + the field that was just hidden + const visibleOrderedFields = orderedFields.filter((field) => { + if (field === fieldToFocus) { + return true; + } + return columnVisibilityModel[field] !== false; + }); + const fieldIndex = visibleOrderedFields.indexOf(fieldToFocus); + fieldToFocus = visibleOrderedFields[fieldIndex + 1] || visibleOrderedFields[fieldIndex - 1]; + } + apiRef.current.setHeaderFilterState({ menuOpen: null }); + apiRef.current.setColumnHeaderFilterFocus(fieldToFocus); + } + }, [apiRef, logger]); + + const headerFilterPrivateApi: GridHeaderFilteringPrivateApi = { + setHeaderFilterState, + }; + + const headerFilterApi: GridHeaderFilteringApi = { + startHeaderFilterEditMode, + stopHeaderFilterEditMode, + showHeaderFilterMenu, + hideHeaderFilterMenu, + }; + + useGridApiMethod(apiRef, headerFilterApi, 'public'); + useGridApiMethod(apiRef, headerFilterPrivateApi, 'private'); +}; diff --git a/packages/grid/x-data-grid/src/hooks/features/index.ts b/packages/grid/x-data-grid/src/hooks/features/index.ts index 47f133f9d1898..dc565a3269ebb 100644 --- a/packages/grid/x-data-grid/src/hooks/features/index.ts +++ b/packages/grid/x-data-grid/src/hooks/features/index.ts @@ -12,3 +12,4 @@ export * from './rowSelection'; export * from './sorting'; export * from './dimensions'; export * from './statePersistence'; +export * from './headerFiltering'; diff --git a/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts b/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts index e15492ab8bd2b..d52065399e7a0 100644 --- a/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts +++ b/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts @@ -19,6 +19,10 @@ import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; import { unstable_gridFocusColumnGroupHeaderSelector } from '../focus'; import { gridColumnGroupsHeaderMaxDepthSelector } from '../columnGrouping/gridColumnGroupsSelector'; import { useGridSelector } from '../../utils/useGridSelector'; +import { + unstable_gridHeaderFilteringEditFieldSelector, + unstable_gridHeaderFilteringMenuSelector, +} from '../headerFiltering/gridHeaderFilteringSelectors'; import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; function enrichPageRowsWithPinnedRows( @@ -87,7 +91,10 @@ const getRightColumnIndex = ({ */ export const useGridKeyboardNavigation = ( apiRef: React.MutableRefObject, - props: Pick, + props: Pick< + DataGridProcessedProps, + 'pagination' | 'paginationMode' | 'getRowId' | 'experimentalFeatures' | 'signature' + >, ): void => { const logger = useGridLogger(apiRef, 'useGridKeyboardNavigation'); const initialCurrentPageRows = useGridVisibleRows(apiRef, props).rows; @@ -98,6 +105,10 @@ export const useGridKeyboardNavigation = ( [apiRef, initialCurrentPageRows], ); + const headerFilteringEnabled = + // @ts-expect-error // TODO move relevant code to the `DataGridPro` + props.signature !== 'DataGrid' && props.unstable_headerFilters; + /** * @param {number} colIndex Index of the column to focus * @param {number} rowIndex index of the row to focus @@ -139,6 +150,16 @@ export const useGridKeyboardNavigation = ( [apiRef, logger], ); + const goToHeaderFilter = React.useCallback( + (colIndex: number, event: React.SyntheticEvent) => { + logger.debug(`Navigating to header filter col ${colIndex}`); + apiRef.current.scrollToIndexes({ colIndex }); + const field = apiRef.current.getVisibleColumns()[colIndex].field; + apiRef.current.setColumnHeaderFilterFocus(field, event); + }, + [apiRef, logger], + ); + const goToGroupHeader = React.useCallback( (colIndex: number, depth: number, event: React.SyntheticEvent) => { logger.debug(`Navigating to header col ${colIndex}`); @@ -151,7 +172,7 @@ export const useGridKeyboardNavigation = ( const getRowIdFromIndex = React.useCallback( (rowIndex: number) => { - return currentPageRows[rowIndex].id; + return currentPageRows?.[rowIndex].id; }, [currentPageRows], ); @@ -187,7 +208,11 @@ export const useGridKeyboardNavigation = ( switch (event.key) { case 'ArrowDown': { if (firstRowIndexInPage !== null) { - goToCell(colIndexBefore, getRowIdFromIndex(firstRowIndexInPage)); + if (headerFilteringEnabled) { + goToHeaderFilter(colIndexBefore, event); + } else { + goToCell(colIndexBefore, getRowIdFromIndex(firstRowIndexInPage)); + } } break; } @@ -273,14 +298,129 @@ export const useGridKeyboardNavigation = ( [ apiRef, currentPageRows.length, - theme.direction, + headerFilteringEnabled, + goToHeaderFilter, goToCell, getRowIdFromIndex, + theme.direction, goToHeader, goToGroupHeader, ], ); + const handleHeaderFilterKeyDown = React.useCallback>( + (params, event) => { + const dimensions = apiRef.current.getRootDimensions(); + if (!dimensions) { + return; + } + + const isEditing = unstable_gridHeaderFilteringEditFieldSelector(apiRef) === params.field; + const isHeaderMenuOpen = unstable_gridHeaderFilteringMenuSelector(apiRef) === params.field; + + if (isEditing || isHeaderMenuOpen || !isNavigationKey(event.key)) { + return; + } + + const viewportPageSize = apiRef.current.getViewportPageSize(); + const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0; + const firstRowIndexInPage = 0; + const lastRowIndexInPage = currentPageRows.length - 1; + const firstColIndex = 0; + const lastColIndex = gridVisibleColumnDefinitionsSelector(apiRef).length - 1; + let shouldPreventDefault = true; + + switch (event.key) { + case 'ArrowDown': { + const rowId = getRowIdFromIndex(firstRowIndexInPage); + if (firstRowIndexInPage !== null && rowId != null) { + goToCell(colIndexBefore, rowId); + } + break; + } + + case 'ArrowRight': { + const rightColIndex = getRightColumnIndex({ + currentColIndex: colIndexBefore, + firstColIndex, + lastColIndex, + direction: theme.direction, + }); + + if (rightColIndex !== null) { + goToHeaderFilter(rightColIndex, event); + } + + break; + } + + case 'ArrowLeft': { + const leftColIndex = getLeftColumnIndex({ + currentColIndex: colIndexBefore, + firstColIndex, + lastColIndex, + direction: theme.direction, + }); + if (leftColIndex !== null) { + goToHeaderFilter(leftColIndex, event); + } else { + apiRef.current.setColumnHeaderFilterFocus(params.field, event); + } + break; + } + + case 'ArrowUp': { + goToHeader(colIndexBefore, event); + break; + } + + case 'PageDown': { + if (firstRowIndexInPage !== null && lastRowIndexInPage !== null) { + goToCell( + colIndexBefore, + getRowIdFromIndex( + Math.min(firstRowIndexInPage + viewportPageSize, lastRowIndexInPage), + ), + ); + } + break; + } + + case 'Home': { + goToHeaderFilter(firstColIndex, event); + break; + } + + case 'End': { + goToHeaderFilter(lastColIndex, event); + break; + } + + case ' ': { + // prevent Space event from scrolling + break; + } + + default: { + shouldPreventDefault = false; + } + } + + if (shouldPreventDefault) { + event.preventDefault(); + } + }, + [ + apiRef, + currentPageRows.length, + goToHeaderFilter, + theme.direction, + goToHeader, + goToCell, + getRowIdFromIndex, + ], + ); + const focusedColumnGroup = useGridSelector(apiRef, unstable_gridFocusColumnGroupHeaderSelector); const handleColumnGroupHeaderKeyDown = React.useCallback< GridEventListener<'columnGroupHeaderKeyDown'> @@ -441,6 +581,8 @@ export const useGridKeyboardNavigation = ( case 'ArrowUp': { if (rowIndexBefore > firstRowIndexInPage) { goToCell(colIndexBefore, getRowIdFromIndex(rowIndexBefore - 1)); + } else if (headerFilteringEnabled) { + goToHeaderFilter(colIndexBefore, event); } else { goToHeader(colIndexBefore, event); } @@ -557,7 +699,16 @@ export const useGridKeyboardNavigation = ( event.preventDefault(); } }, - [apiRef, currentPageRows, theme.direction, getRowIdFromIndex, goToCell, goToHeader], + [ + apiRef, + currentPageRows, + theme.direction, + goToCell, + getRowIdFromIndex, + headerFilteringEnabled, + goToHeaderFilter, + goToHeader, + ], ); const checkIfCanStartEditing = React.useCallback>( @@ -574,6 +725,7 @@ export const useGridKeyboardNavigation = ( useGridRegisterPipeProcessor(apiRef, 'canStartEditing', checkIfCanStartEditing); useGridApiEventHandler(apiRef, 'columnHeaderKeyDown', handleColumnHeaderKeyDown); + useGridApiEventHandler(apiRef, 'headerFilterKeyDown', handleHeaderFilterKeyDown); useGridApiEventHandler(apiRef, 'columnGroupHeaderKeyDown', handleColumnGroupHeaderKeyDown); useGridApiEventHandler(apiRef, 'cellKeyDown', handleCellKeyDown); }; diff --git a/packages/grid/x-data-grid/src/internals/index.ts b/packages/grid/x-data-grid/src/internals/index.ts index c20f1de969392..061f8ea0ecd86 100644 --- a/packages/grid/x-data-grid/src/internals/index.ts +++ b/packages/grid/x-data-grid/src/internals/index.ts @@ -10,6 +10,7 @@ export { GridBaseColumnHeaders } from '../components/columnHeaders/GridBaseColum export { GridColumnHeadersInner } from '../components/columnHeaders/GridColumnHeadersInner'; export { DATA_GRID_DEFAULT_SLOTS_COMPONENTS } from '../constants/defaultGridSlotsComponents'; +export { getGridFilter } from '../components/panel/filterPanel/GridFilterPanel'; export { useGridRegisterPipeProcessor } from '../hooks/core/pipeProcessing'; export type { GridPipeProcessor } from '../hooks/core/pipeProcessing'; export { @@ -21,12 +22,21 @@ export { useGridInitialization } from '../hooks/core/useGridInitialization'; export { useGridClipboard } from '../hooks/features/clipboard/useGridClipboard'; export { useGridColumnHeaders } from '../hooks/features/columnHeaders/useGridColumnHeaders'; -export type { UseGridColumnHeadersProps } from '../hooks/features/columnHeaders/useGridColumnHeaders'; +export { + unstable_gridHeaderFilteringEditFieldSelector, + unstable_gridHeaderFilteringMenuSelector, +} from '../hooks/features/headerFiltering/gridHeaderFilteringSelectors'; +export type { GridSlotsComponentsProps } from '../models/gridSlotsComponentsProps'; +export type { + UseGridColumnHeadersProps, + GetHeadersParams, +} from '../hooks/features/columnHeaders/useGridColumnHeaders'; export { useGridColumnMenu, columnMenuStateInitializer, } from '../hooks/features/columnMenu/useGridColumnMenu'; export { useGridColumns, columnsStateInitializer } from '../hooks/features/columns/useGridColumns'; +export { getTotalHeaderHeight } from '../hooks/features/columns/gridColumnsUtils'; export { useGridColumnSpanning } from '../hooks/features/columns/useGridColumnSpanning'; export { useGridColumnGrouping, @@ -78,6 +88,10 @@ export { gridAdditionalRowGroupsSelector, gridPinnedRowsSelector, } from '../hooks/features/rows/gridRowsSelector'; +export { + headerFilteringStateInitializer, + useGridHeaderFiltering, +} from '../hooks/features/headerFiltering/useGridHeaderFiltering'; export { calculatePinnedRowsHeight } from '../hooks/features/rows/gridRowsUtils'; export { useGridRowSelection, diff --git a/packages/grid/x-data-grid/src/locales/arSD.ts b/packages/grid/x-data-grid/src/locales/arSD.ts index 9e7dc616540bd..37532fda528fb 100644 --- a/packages/grid/x-data-grid/src/locales/arSD.ts +++ b/packages/grid/x-data-grid/src/locales/arSD.ts @@ -71,6 +71,33 @@ const arSDGrid: Partial = { filterOperatorIsEmpty: 'خالي', filterOperatorIsNotEmpty: 'غير خالي', filterOperatorIsAnyOf: 'أي من', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'أي', diff --git a/packages/grid/x-data-grid/src/locales/beBY.ts b/packages/grid/x-data-grid/src/locales/beBY.ts index 65a7297835850..9d0b9b633deac 100644 --- a/packages/grid/x-data-grid/src/locales/beBY.ts +++ b/packages/grid/x-data-grid/src/locales/beBY.ts @@ -94,6 +94,33 @@ const beBYGrid: Partial = { filterOperatorIsEmpty: 'пусты', filterOperatorIsNotEmpty: 'не пусты', filterOperatorIsAnyOf: 'усякі з', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'усякі', diff --git a/packages/grid/x-data-grid/src/locales/bgBG.ts b/packages/grid/x-data-grid/src/locales/bgBG.ts index b78eb82e87912..0c5ab1633e1b1 100644 --- a/packages/grid/x-data-grid/src/locales/bgBG.ts +++ b/packages/grid/x-data-grid/src/locales/bgBG.ts @@ -70,6 +70,33 @@ const bgBGGrid: Partial = { filterOperatorIsEmpty: 'е празен', filterOperatorIsNotEmpty: 'не е празен', filterOperatorIsAnyOf: 'е някой от', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'всякакви', diff --git a/packages/grid/x-data-grid/src/locales/csCZ.ts b/packages/grid/x-data-grid/src/locales/csCZ.ts index 4160a386a3e11..427f999327760 100644 --- a/packages/grid/x-data-grid/src/locales/csCZ.ts +++ b/packages/grid/x-data-grid/src/locales/csCZ.ts @@ -78,6 +78,33 @@ const csCZGrid: Partial = { filterOperatorIsEmpty: 'je prázdný', filterOperatorIsNotEmpty: 'není prázdný', filterOperatorIsAnyOf: 'je jeden z', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'jakýkoliv', diff --git a/packages/grid/x-data-grid/src/locales/daDK.ts b/packages/grid/x-data-grid/src/locales/daDK.ts index 83714ca36baae..cd7f2999e0bd5 100644 --- a/packages/grid/x-data-grid/src/locales/daDK.ts +++ b/packages/grid/x-data-grid/src/locales/daDK.ts @@ -71,6 +71,33 @@ const daDKGrid: Partial = { filterOperatorIsEmpty: 'Indeholder ikke data', filterOperatorIsNotEmpty: 'Indeholder data', filterOperatorIsAnyOf: 'indeholder en af', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'hvilken som helst', diff --git a/packages/grid/x-data-grid/src/locales/deDE.ts b/packages/grid/x-data-grid/src/locales/deDE.ts index fe95a8e0d6041..604349301a0dc 100644 --- a/packages/grid/x-data-grid/src/locales/deDE.ts +++ b/packages/grid/x-data-grid/src/locales/deDE.ts @@ -71,6 +71,33 @@ const deDEGrid: Partial = { filterOperatorIsEmpty: 'ist leer', filterOperatorIsNotEmpty: 'ist nicht leer', filterOperatorIsAnyOf: 'ist einer der Werte', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'Beliebig', diff --git a/packages/grid/x-data-grid/src/locales/elGR.ts b/packages/grid/x-data-grid/src/locales/elGR.ts index 86f5daaeeb928..a459366d76a2d 100644 --- a/packages/grid/x-data-grid/src/locales/elGR.ts +++ b/packages/grid/x-data-grid/src/locales/elGR.ts @@ -70,6 +70,33 @@ const elGRGrid: Partial = { filterOperatorIsEmpty: 'είναι κενό', filterOperatorIsNotEmpty: 'δεν είναι κενό', // filterOperatorIsAnyOf: 'is any of', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text // filterValueAny: 'any', diff --git a/packages/grid/x-data-grid/src/locales/esES.ts b/packages/grid/x-data-grid/src/locales/esES.ts index 98d53226b19ea..716c4dc67c5a4 100644 --- a/packages/grid/x-data-grid/src/locales/esES.ts +++ b/packages/grid/x-data-grid/src/locales/esES.ts @@ -71,6 +71,33 @@ const esESGrid: Partial = { filterOperatorIsEmpty: 'está vacío', filterOperatorIsNotEmpty: 'no esta vacío', filterOperatorIsAnyOf: 'es cualquiera de', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'cualquiera', diff --git a/packages/grid/x-data-grid/src/locales/faIR.ts b/packages/grid/x-data-grid/src/locales/faIR.ts index 2f473636fbb6b..6f568f5b68486 100644 --- a/packages/grid/x-data-grid/src/locales/faIR.ts +++ b/packages/grid/x-data-grid/src/locales/faIR.ts @@ -71,6 +71,33 @@ const faIRGrid: Partial = { filterOperatorIsEmpty: 'خالی است', filterOperatorIsNotEmpty: 'خالی نیست', filterOperatorIsAnyOf: 'هر یک از', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'هرچیزی', diff --git a/packages/grid/x-data-grid/src/locales/fiFI.ts b/packages/grid/x-data-grid/src/locales/fiFI.ts index 1d33cc4191e9b..129640d4292aa 100644 --- a/packages/grid/x-data-grid/src/locales/fiFI.ts +++ b/packages/grid/x-data-grid/src/locales/fiFI.ts @@ -71,6 +71,33 @@ const fiFIGrid: Partial = { filterOperatorIsEmpty: 'on tyhjä', filterOperatorIsNotEmpty: 'ei ole tyhjä', filterOperatorIsAnyOf: 'mikä tahansa seuraavista', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'mikä tahansa', diff --git a/packages/grid/x-data-grid/src/locales/frFR.ts b/packages/grid/x-data-grid/src/locales/frFR.ts index 06da45ba2b8eb..ebf2508f8c5ef 100644 --- a/packages/grid/x-data-grid/src/locales/frFR.ts +++ b/packages/grid/x-data-grid/src/locales/frFR.ts @@ -71,6 +71,33 @@ const frFRGrid: Partial = { filterOperatorIsEmpty: 'est vide', filterOperatorIsNotEmpty: "n'est pas vide", filterOperatorIsAnyOf: 'fait partie de', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'tous', diff --git a/packages/grid/x-data-grid/src/locales/heIL.ts b/packages/grid/x-data-grid/src/locales/heIL.ts index c7bfea82c9e9b..859e5a37e7c51 100644 --- a/packages/grid/x-data-grid/src/locales/heIL.ts +++ b/packages/grid/x-data-grid/src/locales/heIL.ts @@ -71,6 +71,33 @@ const heILGrid: Partial = { filterOperatorIsEmpty: 'ריק', filterOperatorIsNotEmpty: 'אינו ריק', filterOperatorIsAnyOf: 'הוא אחד מ-', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'כל ערך', diff --git a/packages/grid/x-data-grid/src/locales/huHU.ts b/packages/grid/x-data-grid/src/locales/huHU.ts index 3dac1c75ae25b..26bd1b3a7c20a 100644 --- a/packages/grid/x-data-grid/src/locales/huHU.ts +++ b/packages/grid/x-data-grid/src/locales/huHU.ts @@ -70,6 +70,33 @@ const huHUGrid: Partial = { filterOperatorIsEmpty: 'üres', filterOperatorIsNotEmpty: 'nem üres', filterOperatorIsAnyOf: 'a következők egyike:', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'bármilyen', diff --git a/packages/grid/x-data-grid/src/locales/itIT.ts b/packages/grid/x-data-grid/src/locales/itIT.ts index 2844f52150557..82359f699bb18 100644 --- a/packages/grid/x-data-grid/src/locales/itIT.ts +++ b/packages/grid/x-data-grid/src/locales/itIT.ts @@ -71,6 +71,33 @@ const itITGrid: Partial = { filterOperatorIsEmpty: 'è vuoto', filterOperatorIsNotEmpty: 'non è vuoto', filterOperatorIsAnyOf: 'è uno tra', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'qualunque', diff --git a/packages/grid/x-data-grid/src/locales/jaJP.ts b/packages/grid/x-data-grid/src/locales/jaJP.ts index f85bdc6a23b4a..8b655043cbc35 100644 --- a/packages/grid/x-data-grid/src/locales/jaJP.ts +++ b/packages/grid/x-data-grid/src/locales/jaJP.ts @@ -70,6 +70,33 @@ const jaJPGrid: Partial = { filterOperatorIsEmpty: '...空である', filterOperatorIsNotEmpty: '...空でない', filterOperatorIsAnyOf: '...のいずれか', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'いずれか', diff --git a/packages/grid/x-data-grid/src/locales/koKR.ts b/packages/grid/x-data-grid/src/locales/koKR.ts index e55ad0557b218..091a77af59f29 100644 --- a/packages/grid/x-data-grid/src/locales/koKR.ts +++ b/packages/grid/x-data-grid/src/locales/koKR.ts @@ -70,6 +70,33 @@ const koKRGrid: Partial = { filterOperatorIsEmpty: '값이 없는', filterOperatorIsNotEmpty: '값이 있는', filterOperatorIsAnyOf: '값 중 하나인', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: '아무값', diff --git a/packages/grid/x-data-grid/src/locales/nbNO.ts b/packages/grid/x-data-grid/src/locales/nbNO.ts index bb06bc9ac01be..88e7e53d00871 100644 --- a/packages/grid/x-data-grid/src/locales/nbNO.ts +++ b/packages/grid/x-data-grid/src/locales/nbNO.ts @@ -71,6 +71,33 @@ const nbNOGrid: Partial = { filterOperatorIsEmpty: 'er tom', filterOperatorIsNotEmpty: 'er ikke tom', filterOperatorIsAnyOf: 'er en av', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'noen', diff --git a/packages/grid/x-data-grid/src/locales/nlNL.ts b/packages/grid/x-data-grid/src/locales/nlNL.ts index 4018368bc9e64..beff8c4e3b863 100644 --- a/packages/grid/x-data-grid/src/locales/nlNL.ts +++ b/packages/grid/x-data-grid/src/locales/nlNL.ts @@ -71,6 +71,33 @@ const nlNLGrid: Partial = { filterOperatorIsEmpty: 'is leeg', filterOperatorIsNotEmpty: 'is niet leeg', filterOperatorIsAnyOf: 'is een van', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'alles', diff --git a/packages/grid/x-data-grid/src/locales/plPL.ts b/packages/grid/x-data-grid/src/locales/plPL.ts index 15850dd3ffc6b..5de04acc32709 100644 --- a/packages/grid/x-data-grid/src/locales/plPL.ts +++ b/packages/grid/x-data-grid/src/locales/plPL.ts @@ -70,6 +70,33 @@ const plPLGrid: Partial = { filterOperatorIsEmpty: 'jest pusty', filterOperatorIsNotEmpty: 'nie jest pusty', filterOperatorIsAnyOf: 'jest jednym z', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'dowolny', diff --git a/packages/grid/x-data-grid/src/locales/ptBR.ts b/packages/grid/x-data-grid/src/locales/ptBR.ts index 0181aaf2e696b..0740dcfab0fe7 100644 --- a/packages/grid/x-data-grid/src/locales/ptBR.ts +++ b/packages/grid/x-data-grid/src/locales/ptBR.ts @@ -71,6 +71,33 @@ const ptBRGrid: Partial = { filterOperatorIsEmpty: 'está vazio', filterOperatorIsNotEmpty: 'não está vazio', filterOperatorIsAnyOf: 'é qualquer um dos', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'qualquer', diff --git a/packages/grid/x-data-grid/src/locales/roRO.ts b/packages/grid/x-data-grid/src/locales/roRO.ts index 1176350288766..649e8fff0b636 100644 --- a/packages/grid/x-data-grid/src/locales/roRO.ts +++ b/packages/grid/x-data-grid/src/locales/roRO.ts @@ -71,6 +71,33 @@ const roROGrid: Partial = { filterOperatorIsEmpty: 'este gol', filterOperatorIsNotEmpty: 'nu este gol', filterOperatorIsAnyOf: 'este una din valori', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'Aleatoriu', diff --git a/packages/grid/x-data-grid/src/locales/ruRU.ts b/packages/grid/x-data-grid/src/locales/ruRU.ts index b687eae080f7f..e3bb7957fabd2 100644 --- a/packages/grid/x-data-grid/src/locales/ruRU.ts +++ b/packages/grid/x-data-grid/src/locales/ruRU.ts @@ -79,6 +79,33 @@ const ruRUGrid: Partial = { filterOperatorIsEmpty: 'пустой', filterOperatorIsNotEmpty: 'не пустой', filterOperatorIsAnyOf: 'любой из', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'любой', diff --git a/packages/grid/x-data-grid/src/locales/skSK.ts b/packages/grid/x-data-grid/src/locales/skSK.ts index a4cc8dc70906c..2f254858bb9c8 100644 --- a/packages/grid/x-data-grid/src/locales/skSK.ts +++ b/packages/grid/x-data-grid/src/locales/skSK.ts @@ -78,6 +78,33 @@ const skSKGrid: Partial = { filterOperatorIsEmpty: 'je prázdny', filterOperatorIsNotEmpty: 'nie je prázdny', filterOperatorIsAnyOf: 'je jeden z', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'akýkoľvek', diff --git a/packages/grid/x-data-grid/src/locales/svSE.ts b/packages/grid/x-data-grid/src/locales/svSE.ts index 223bb41818889..2f8561e9bab66 100644 --- a/packages/grid/x-data-grid/src/locales/svSE.ts +++ b/packages/grid/x-data-grid/src/locales/svSE.ts @@ -71,6 +71,33 @@ const svSEGrid: Partial = { filterOperatorIsEmpty: 'är tom', filterOperatorIsNotEmpty: 'är inte tom', filterOperatorIsAnyOf: 'är någon av', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'något', diff --git a/packages/grid/x-data-grid/src/locales/trTR.ts b/packages/grid/x-data-grid/src/locales/trTR.ts index 76fa74c5418b7..9821989752e1c 100644 --- a/packages/grid/x-data-grid/src/locales/trTR.ts +++ b/packages/grid/x-data-grid/src/locales/trTR.ts @@ -70,6 +70,33 @@ const trTRGrid: Partial = { filterOperatorIsEmpty: 'boş', filterOperatorIsNotEmpty: 'dolu', filterOperatorIsAnyOf: 'herhangi biri', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'herhangi', diff --git a/packages/grid/x-data-grid/src/locales/ukUA.ts b/packages/grid/x-data-grid/src/locales/ukUA.ts index e408954892a5d..2db0421c25f19 100644 --- a/packages/grid/x-data-grid/src/locales/ukUA.ts +++ b/packages/grid/x-data-grid/src/locales/ukUA.ts @@ -94,6 +94,33 @@ const ukUAGrid: Partial = { filterOperatorIsEmpty: 'порожній', filterOperatorIsNotEmpty: 'не порожній', filterOperatorIsAnyOf: 'будь-що із', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'будь-який', diff --git a/packages/grid/x-data-grid/src/locales/urPK.ts b/packages/grid/x-data-grid/src/locales/urPK.ts index 440d625b4a88d..fc569e33da375 100644 --- a/packages/grid/x-data-grid/src/locales/urPK.ts +++ b/packages/grid/x-data-grid/src/locales/urPK.ts @@ -71,6 +71,33 @@ const urPKGrid: Partial = { filterOperatorIsEmpty: 'خالی ہے', filterOperatorIsNotEmpty: 'خالی نہیں ہے', filterOperatorIsAnyOf: 'ان میں سے کوئی ہے', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'کوئی بھی', diff --git a/packages/grid/x-data-grid/src/locales/viVN.ts b/packages/grid/x-data-grid/src/locales/viVN.ts index a7cbec09be58f..dec1ba5dcf1ce 100644 --- a/packages/grid/x-data-grid/src/locales/viVN.ts +++ b/packages/grid/x-data-grid/src/locales/viVN.ts @@ -71,6 +71,33 @@ const viVNGrid: Partial = { filterOperatorIsEmpty: 'Rỗng', filterOperatorIsNotEmpty: 'Khác rỗng', // filterOperatorIsAnyOf: 'is any of', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: 'bất kỳ giá trị nào', diff --git a/packages/grid/x-data-grid/src/locales/zhCN.ts b/packages/grid/x-data-grid/src/locales/zhCN.ts index 563e44b11e0b1..4ec5406a68e23 100644 --- a/packages/grid/x-data-grid/src/locales/zhCN.ts +++ b/packages/grid/x-data-grid/src/locales/zhCN.ts @@ -70,6 +70,33 @@ const zhCNGrid: Partial = { filterOperatorIsEmpty: '为空', filterOperatorIsNotEmpty: '不为空', filterOperatorIsAnyOf: '属于', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: '任何', diff --git a/packages/grid/x-data-grid/src/locales/zhTW.ts b/packages/grid/x-data-grid/src/locales/zhTW.ts index 43504ea05de97..718e3106964ae 100644 --- a/packages/grid/x-data-grid/src/locales/zhTW.ts +++ b/packages/grid/x-data-grid/src/locales/zhTW.ts @@ -70,6 +70,33 @@ const zhTWGrid: Partial = { filterOperatorIsEmpty: '為空', filterOperatorIsNotEmpty: '不為空', filterOperatorIsAnyOf: '是其中之一', + // filterOperator=: '=', + // filterOperator!=: '!=', + // filterOperator>: '>', + // filterOperator>=: '>=', + // filterOperator<: '<', + // filterOperator<=: '<=', + + // Header filter operators text + // headerFilterOperatorContains: 'Contains', + // headerFilterOperatorEquals: 'Equals', + // headerFilterOperatorStartsWith: 'Starts with', + // headerFilterOperatorEndsWith: 'Ends with', + // headerFilterOperatorIs: 'Is', + // headerFilterOperatorNot: 'Is not', + // headerFilterOperatorAfter: 'Is after', + // headerFilterOperatorOnOrAfter: 'Is on or after', + // headerFilterOperatorBefore: 'Is before', + // headerFilterOperatorOnOrBefore: 'Is on or before', + // headerFilterOperatorIsEmpty: 'Is empty', + // headerFilterOperatorIsNotEmpty: 'Is not empty', + // headerFilterOperatorIsAnyOf: 'Is any of', + // headerFilterOperator=: 'Equals', + // headerFilterOperator!=: 'Not equals', + // headerFilterOperator>: 'Is greater than', + // headerFilterOperator>=: 'Is greater than or equal to', + // headerFilterOperator<: 'Is less than', + // headerFilterOperator<=: 'Is less than or equal to', // Filter values text filterValueAny: '任何值', diff --git a/packages/grid/x-data-grid/src/material/index.ts b/packages/grid/x-data-grid/src/material/index.ts index 30e1b788b215a..e58d09f5adfa0 100644 --- a/packages/grid/x-data-grid/src/material/index.ts +++ b/packages/grid/x-data-grid/src/material/index.ts @@ -5,6 +5,7 @@ import MUISelect from '@mui/material/Select'; import MUISwitch from '@mui/material/Switch'; import MUIButton from '@mui/material/Button'; import MUIIconButton from '@mui/material/IconButton'; +import MUIInputAdornment from '@mui/material/InputAdornment'; import MUITooltip from '@mui/material/Tooltip'; import MUIPopper from '@mui/material/Popper'; import MUIInputLabel from '@mui/material/InputLabel'; @@ -38,6 +39,7 @@ import { GridDeleteForeverIcon, } from './icons'; import type { GridIconSlotsComponent } from '../models'; +import type { GridBaseSlots } from '../models/gridSlotsComponent'; import MUISelectOption from './components/MUISelectOption'; const iconSlots: GridIconSlotsComponent = { @@ -78,7 +80,7 @@ const iconSlots: GridIconSlotsComponent = { ColumnReorderIcon: GridDragIcon, }; -const materialSlots = { +const materialSlots: GridBaseSlots & GridIconSlotsComponent = { ...iconSlots, BaseCheckbox: MUICheckbox, BaseTextField: MUITextField, @@ -87,6 +89,7 @@ const materialSlots = { BaseSwitch: MUISwitch, BaseButton: MUIButton, BaseIconButton: MUIIconButton, + BaseInputAdornment: MUIInputAdornment, BaseTooltip: MUITooltip, BasePopper: MUIPopper, BaseInputLabel: MUIInputLabel, diff --git a/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts b/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts index bc783f3631136..17c98ff59b273 100644 --- a/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts +++ b/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts @@ -33,6 +33,7 @@ import type { GridPaginationApi } from '../../hooks/features/pagination'; import type { GridStatePersistenceApi } from '../../hooks/features/statePersistence'; import { GridColumnGroupingApi } from './gridColumnGroupingApi'; import type { GridInitialStateCommunity, GridStateCommunity } from '../gridStateCommunity'; +import { GridHeaderFilteringApi, GridHeaderFilteringPrivateApi } from './gridHeaderFilteringApi'; export interface GridApiCommon< GridState extends GridStateCommunity = any, @@ -61,7 +62,8 @@ export interface GridApiCommon< GridColumnSpanningApi, GridStateApi, GridStatePersistenceApi, - GridColumnGroupingApi {} + GridColumnGroupingApi, + GridHeaderFilteringApi {} export interface GridPrivateOnlyApiCommon< Api extends GridApiCommon, @@ -76,7 +78,8 @@ export interface GridPrivateOnlyApiCommon< GridVirtualScrollerApi, GridEditingPrivateApi, GridLoggerApi, - GridFocusPrivateApi {} + GridFocusPrivateApi, + GridHeaderFilteringPrivateApi {} export interface GridPrivateApiCommon extends GridApiCommon, diff --git a/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts b/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts index 9483c6cf8b520..cdf04b24c0f71 100644 --- a/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts @@ -85,6 +85,10 @@ export interface GridCorePrivateApi< * The React ref of the grid column container virtualized div element. */ columnHeadersContainerElementRef?: React.RefObject; + /** + * The React ref of the grid header filter row element. + */ + headerFiltersElementRef?: React.RefObject; /** * The React ref of the grid column headers container element. */ diff --git a/packages/grid/x-data-grid/src/models/api/gridFocusApi.ts b/packages/grid/x-data-grid/src/models/api/gridFocusApi.ts index e2b98e1e008bf..5e6f221464fd8 100644 --- a/packages/grid/x-data-grid/src/models/api/gridFocusApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridFocusApi.ts @@ -15,6 +15,12 @@ export interface GridFocusApi { * @param {string} event The event that triggers the action. */ setColumnHeaderFocus: (field: string, event?: MuiBaseEvent) => void; + /** + * Sets the focus to the column header filter at the given `field`. + * @param {string} field The column field. + * @param {string} event The event that triggers the action. + */ + setColumnHeaderFilterFocus: (field: string, event?: MuiBaseEvent) => void; } export interface GridFocusPrivateApi { diff --git a/packages/grid/x-data-grid/src/models/api/gridHeaderFilteringApi.ts b/packages/grid/x-data-grid/src/models/api/gridHeaderFilteringApi.ts new file mode 100644 index 0000000000000..b88229c0fd371 --- /dev/null +++ b/packages/grid/x-data-grid/src/models/api/gridHeaderFilteringApi.ts @@ -0,0 +1,32 @@ +import { GridColDef } from '../colDef'; +import { GridHeaderFilteringState } from '../gridHeaderFilteringModel'; + +export interface GridHeaderFilteringPrivateApi { + /** + * Internal function to set the header filter state. + * @param {Partial} headerFilterState The field to be edited. + * @ignore - do not document. + */ + setHeaderFilterState: (headerFilterState: Partial) => void; +} + +export interface GridHeaderFilteringApi { + /** + * Puts the cell corresponding to the given row id and field into edit mode. + * @param {GridColDef['field']} field The field of the header filter to put in edit mode. + */ + startHeaderFilterEditMode: (field: GridColDef['field']) => void; + /** + * Stops the edit mode for the current field. + */ + stopHeaderFilterEditMode: () => void; + /** + * Opens the header filter menu for the given field. + * @param {GridColDef['field']} field The field of the header filter to open menu for. + */ + showHeaderFilterMenu: (field: GridColDef['field']) => void; + /** + * Hides the header filter menu. + */ + hideHeaderFilterMenu: () => void; +} diff --git a/packages/grid/x-data-grid/src/models/api/gridLocaleTextApi.ts b/packages/grid/x-data-grid/src/models/api/gridLocaleTextApi.ts index bc2a837cce1b8..c20e868e6dd62 100644 --- a/packages/grid/x-data-grid/src/models/api/gridLocaleTextApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridLocaleTextApi.ts @@ -72,6 +72,33 @@ export interface GridLocaleText { filterOperatorIsEmpty: string; filterOperatorIsNotEmpty: string; filterOperatorIsAnyOf: string; + 'filterOperator=': string; + 'filterOperator!=': string; + 'filterOperator>': string; + 'filterOperator>=': string; + 'filterOperator<': string; + 'filterOperator<=': string; + + // Header filter operators text + headerFilterOperatorContains: string; + headerFilterOperatorEquals: string; + headerFilterOperatorStartsWith: string; + headerFilterOperatorEndsWith: string; + headerFilterOperatorIs: string; + headerFilterOperatorNot: string; + headerFilterOperatorAfter: string; + headerFilterOperatorOnOrAfter: string; + headerFilterOperatorBefore: string; + headerFilterOperatorOnOrBefore: string; + headerFilterOperatorIsEmpty: string; + headerFilterOperatorIsNotEmpty: string; + headerFilterOperatorIsAnyOf: string; + 'headerFilterOperator=': string; + 'headerFilterOperator!=': string; + 'headerFilterOperator>': string; + 'headerFilterOperator>=': string; + 'headerFilterOperator<': string; + 'headerFilterOperator<=': string; // Filter values text filterValueAny: string; diff --git a/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts b/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts index 231953a840fcc..e28ee58ee7d9f 100644 --- a/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts @@ -192,6 +192,33 @@ export interface GridColumnHeaderEventLookup { }; } +export interface GridHeaderFilterEventLookup { + /** + * Fired when a column header filter is clicked + * @ignore - do not document. + */ + headerFilterClick: { + params: GridColumnHeaderParams; + event: React.MouseEvent; + }; + /** + * Fired when a key is pressed in a column header filter. It's mapped to the `keydown` DOM event. + * @ignore - do not document. + */ + headerFilterKeyDown: { + params: GridColumnHeaderParams; + event: React.KeyboardEvent; + }; + /** + * Fired when a mouse is pressed in a column header filter. It's mapped to the `mousedown` DOM event. + * @ignore - do not document. + */ + headerFilterMouseDown: { + params: GridColumnHeaderParams; + event: React.KeyboardEvent; + }; +} + export interface GridColumnGroupHeaderEventLookup { /** * Fired when a key is pressed in a column group header. It's mapped do the `keydown` DOM event. @@ -322,6 +349,7 @@ export interface GridControlledStateReasonLookup { export interface GridEventLookup extends GridRowEventLookup, GridColumnHeaderEventLookup, + GridHeaderFilterEventLookup, GridColumnGroupHeaderEventLookup, GridCellEventLookup, GridControlledStateEventLookup { diff --git a/packages/grid/x-data-grid/src/models/gridFilterOperator.ts b/packages/grid/x-data-grid/src/models/gridFilterOperator.ts index cf259c23fc4b2..df045f81fd18e 100644 --- a/packages/grid/x-data-grid/src/models/gridFilterOperator.ts +++ b/packages/grid/x-data-grid/src/models/gridFilterOperator.ts @@ -14,6 +14,10 @@ export interface GridFilterOperator; + /** + * The custom Chip component used in the grid. + * @default Chip + */ + BaseChip: React.JSXElementConstructor; + /** + * The custom InputAdornment component used in the grid. + * @default InputAdornment + */ + BaseInputAdornment: React.JSXElementConstructor; /** * The custom TextField component used in the grid. * @default TextField @@ -61,6 +68,12 @@ export interface GridSlotsComponent extends GridIconSlotsComponent { * @default MenuItem */ BaseSelectOption: React.JSXElementConstructor; +} + +/** + * Grid components React prop interface containing all the overridable components. + */ +export interface GridSlotsComponent extends GridBaseSlots, GridIconSlotsComponent { /** * The custom Chip component used in the grid. * @default Chip diff --git a/packages/grid/x-data-grid/src/models/gridStateCommunity.ts b/packages/grid/x-data-grid/src/models/gridStateCommunity.ts index bce4d0bc1378e..3e44061e48bb8 100644 --- a/packages/grid/x-data-grid/src/models/gridStateCommunity.ts +++ b/packages/grid/x-data-grid/src/models/gridStateCommunity.ts @@ -18,6 +18,7 @@ import type { } from '../hooks'; import type { GridRowsMetaState } from '../hooks/features/rows/gridRowsMetaState'; import type { GridEditingState } from './gridEditRowModel'; +import { GridHeaderFilteringState } from './gridHeaderFilteringModel'; import type { GridRowSelectionModel } from './gridRowSelectionModel'; /** @@ -27,6 +28,7 @@ export interface GridStateCommunity { rows: GridRowsState; rowsMeta: GridRowsMetaState; editRows: GridEditingState; + headerFiltering: GridHeaderFilteringState; pagination: GridPaginationState; columns: GridColumnsState; columnGrouping: GridColumnsGroupingState; diff --git a/packages/grid/x-data-grid/src/models/index.ts b/packages/grid/x-data-grid/src/models/index.ts index 651804a72b576..ce7c0563f5409 100644 --- a/packages/grid/x-data-grid/src/models/index.ts +++ b/packages/grid/x-data-grid/src/models/index.ts @@ -16,7 +16,7 @@ export * from './gridCell'; export * from './gridColumnHeaderClass'; export * from './api'; export * from './gridIconSlotsComponent'; -export * from './gridSlotsComponent'; +export type { GridSlotsComponent, UncapitalizedGridSlotsComponent } from './gridSlotsComponent'; export * from './gridSlotsComponentsProps'; export * from './gridDensity'; export * from './logger'; diff --git a/scripts/l10n.ts b/scripts/l10n.ts index beeeea81a2c64..4ffaacf84d448 100644 --- a/scripts/l10n.ts +++ b/scripts/l10n.ts @@ -129,7 +129,9 @@ function extractTranslations(translationsPath: string): [TranslationsByGroup, Tr return; } - const key = (property.key as babelTypes.Identifier).name; + const key = + (property.key as babelTypes.Identifier).name || + (property.key as babelTypes.StringLiteral).value; // Ignore translations for MUI Core components, e.g. MuiTablePagination if (key.startsWith('Mui')) { diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 482a59a8001f8..ae513ba15ea75 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -311,6 +311,8 @@ { "name": "GridFilterForm", "kind": "Variable" }, { "name": "GridFilterFormProps", "kind": "Interface" }, { "name": "GridFilterInitialState", "kind": "Interface" }, + { "name": "GridFilterInputBoolean", "kind": "Function" }, + { "name": "GridFilterInputBooleanProps", "kind": "TypeAlias" }, { "name": "GridFilterInputDate", "kind": "Function" }, { "name": "GridFilterInputDateProps", "kind": "TypeAlias" }, { "name": "GridFilterInputMultipleSingleSelect", "kind": "Function" }, @@ -327,7 +329,6 @@ { "name": "gridFilterModelSelector", "kind": "Variable" }, { "name": "GridFilterOperator", "kind": "Interface" }, { "name": "GridFilterPanel", "kind": "Variable" }, - { "name": "GridFilterPanelProps", "kind": "Interface" }, { "name": "GridFilterState", "kind": "Interface" }, { "name": "GridFocusApi", "kind": "Interface" }, { "name": "gridFocusCellSelector", "kind": "Variable" }, @@ -352,7 +353,12 @@ { "name": "GridGroupWorkIcon", "kind": "Variable" }, { "name": "GridHeader", "kind": "Function" }, { "name": "GridHeaderCheckbox", "kind": "Variable" }, + { "name": "GridHeaderFilterAdornment", "kind": "Function" }, + { "name": "GridHeaderFilterCell", "kind": "Variable" }, + { "name": "GridHeaderFilterCellProps", "kind": "Interface" }, + { "name": "GridHeaderFilterEventLookup", "kind": "Interface" }, { "name": "GridHeaderSelectionCheckboxParams", "kind": "Interface" }, + { "name": "GridHighlightOffIcon", "kind": "Variable" }, { "name": "GridIconSlotsComponent", "kind": "Interface" }, { "name": "GridInitialState", "kind": "TypeAlias" }, { "name": "GridInputRowSelectionModel", "kind": "TypeAlias" }, @@ -551,7 +557,7 @@ { "name": "GridTreeNode", "kind": "TypeAlias" }, { "name": "GridTreeNodeWithRender", "kind": "TypeAlias" }, { "name": "GridTripleDotsVerticalIcon", "kind": "Variable" }, - { "name": "GridTypeFilterInputValueProps", "kind": "Interface" }, + { "name": "GridTypeFilterInputValueProps", "kind": "TypeAlias" }, { "name": "GridUpdateAction", "kind": "TypeAlias" }, { "name": "GridValidRowModel", "kind": "TypeAlias" }, { "name": "GridValueFormatterParams", "kind": "Interface" }, @@ -613,7 +619,12 @@ { "name": "UncapitalizedGridProSlotsComponent", "kind": "Interface" }, { "name": "UncapitalizedGridSlotsComponent", "kind": "Interface" }, { "name": "unstable_gridFocusColumnGroupHeaderSelector", "kind": "Variable" }, + { "name": "unstable_gridFocusColumnHeaderFilterSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringEditFieldSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringMenuSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringStateSelector", "kind": "Variable" }, { "name": "unstable_gridTabIndexColumnGroupHeaderSelector", "kind": "Variable" }, + { "name": "unstable_gridTabIndexColumnHeaderFilterSelector", "kind": "Variable" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, { "name": "urPK", "kind": "Variable" }, { "name": "useFirstRender", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 789e98d8a9a79..95b1e32a0b994 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -280,6 +280,8 @@ { "name": "GridFilterForm", "kind": "Variable" }, { "name": "GridFilterFormProps", "kind": "Interface" }, { "name": "GridFilterInitialState", "kind": "Interface" }, + { "name": "GridFilterInputBoolean", "kind": "Function" }, + { "name": "GridFilterInputBooleanProps", "kind": "TypeAlias" }, { "name": "GridFilterInputDate", "kind": "Function" }, { "name": "GridFilterInputDateProps", "kind": "TypeAlias" }, { "name": "GridFilterInputMultipleSingleSelect", "kind": "Function" }, @@ -296,7 +298,6 @@ { "name": "gridFilterModelSelector", "kind": "Variable" }, { "name": "GridFilterOperator", "kind": "Interface" }, { "name": "GridFilterPanel", "kind": "Variable" }, - { "name": "GridFilterPanelProps", "kind": "Interface" }, { "name": "GridFilterState", "kind": "Interface" }, { "name": "GridFocusApi", "kind": "Interface" }, { "name": "gridFocusCellSelector", "kind": "Variable" }, @@ -316,7 +317,12 @@ { "name": "GridGroupNode", "kind": "TypeAlias" }, { "name": "GridHeader", "kind": "Function" }, { "name": "GridHeaderCheckbox", "kind": "Variable" }, + { "name": "GridHeaderFilterAdornment", "kind": "Function" }, + { "name": "GridHeaderFilterCell", "kind": "Variable" }, + { "name": "GridHeaderFilterCellProps", "kind": "Interface" }, + { "name": "GridHeaderFilterEventLookup", "kind": "Interface" }, { "name": "GridHeaderSelectionCheckboxParams", "kind": "Interface" }, + { "name": "GridHighlightOffIcon", "kind": "Variable" }, { "name": "GridIconSlotsComponent", "kind": "Interface" }, { "name": "GridInitialState", "kind": "TypeAlias" }, { "name": "GridInputRowSelectionModel", "kind": "TypeAlias" }, @@ -506,7 +512,7 @@ { "name": "GridTreeNode", "kind": "TypeAlias" }, { "name": "GridTreeNodeWithRender", "kind": "TypeAlias" }, { "name": "GridTripleDotsVerticalIcon", "kind": "Variable" }, - { "name": "GridTypeFilterInputValueProps", "kind": "Interface" }, + { "name": "GridTypeFilterInputValueProps", "kind": "TypeAlias" }, { "name": "GridUpdateAction", "kind": "TypeAlias" }, { "name": "GridValidRowModel", "kind": "TypeAlias" }, { "name": "GridValueFormatterParams", "kind": "Interface" }, @@ -564,7 +570,12 @@ { "name": "UncapitalizedGridProSlotsComponent", "kind": "Interface" }, { "name": "UncapitalizedGridSlotsComponent", "kind": "Interface" }, { "name": "unstable_gridFocusColumnGroupHeaderSelector", "kind": "Variable" }, + { "name": "unstable_gridFocusColumnHeaderFilterSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringEditFieldSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringMenuSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringStateSelector", "kind": "Variable" }, { "name": "unstable_gridTabIndexColumnGroupHeaderSelector", "kind": "Variable" }, + { "name": "unstable_gridTabIndexColumnHeaderFilterSelector", "kind": "Variable" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, { "name": "urPK", "kind": "Variable" }, { "name": "useFirstRender", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 6808c10d99d0f..532a331907fa6 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -255,6 +255,8 @@ { "name": "GridFilterForm", "kind": "Variable" }, { "name": "GridFilterFormProps", "kind": "Interface" }, { "name": "GridFilterInitialState", "kind": "Interface" }, + { "name": "GridFilterInputBoolean", "kind": "Function" }, + { "name": "GridFilterInputBooleanProps", "kind": "TypeAlias" }, { "name": "GridFilterInputDate", "kind": "Function" }, { "name": "GridFilterInputDateProps", "kind": "TypeAlias" }, { "name": "GridFilterInputMultipleSingleSelect", "kind": "Function" }, @@ -271,7 +273,6 @@ { "name": "gridFilterModelSelector", "kind": "Variable" }, { "name": "GridFilterOperator", "kind": "Interface" }, { "name": "GridFilterPanel", "kind": "Variable" }, - { "name": "GridFilterPanelProps", "kind": "Interface" }, { "name": "GridFilterState", "kind": "Interface" }, { "name": "GridFocusApi", "kind": "Interface" }, { "name": "gridFocusCellSelector", "kind": "Variable" }, @@ -289,6 +290,7 @@ { "name": "GridGroupNode", "kind": "TypeAlias" }, { "name": "GridHeader", "kind": "Function" }, { "name": "GridHeaderCheckbox", "kind": "Variable" }, + { "name": "GridHeaderFilterEventLookup", "kind": "Interface" }, { "name": "GridHeaderSelectionCheckboxParams", "kind": "Interface" }, { "name": "GridIconSlotsComponent", "kind": "Interface" }, { "name": "GridInitialState", "kind": "TypeAlias" }, @@ -465,7 +467,7 @@ { "name": "GridTreeNode", "kind": "TypeAlias" }, { "name": "GridTreeNodeWithRender", "kind": "TypeAlias" }, { "name": "GridTripleDotsVerticalIcon", "kind": "Variable" }, - { "name": "GridTypeFilterInputValueProps", "kind": "Interface" }, + { "name": "GridTypeFilterInputValueProps", "kind": "TypeAlias" }, { "name": "GridUpdateAction", "kind": "TypeAlias" }, { "name": "GridValidRowModel", "kind": "TypeAlias" }, { "name": "GridValueFormatterParams", "kind": "Interface" }, @@ -521,7 +523,12 @@ { "name": "ukUA", "kind": "Variable" }, { "name": "UncapitalizedGridSlotsComponent", "kind": "Interface" }, { "name": "unstable_gridFocusColumnGroupHeaderSelector", "kind": "Variable" }, + { "name": "unstable_gridFocusColumnHeaderFilterSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringEditFieldSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringMenuSelector", "kind": "Variable" }, + { "name": "unstable_gridHeaderFilteringStateSelector", "kind": "Variable" }, { "name": "unstable_gridTabIndexColumnGroupHeaderSelector", "kind": "Variable" }, + { "name": "unstable_gridTabIndexColumnHeaderFilterSelector", "kind": "Variable" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, { "name": "urPK", "kind": "Variable" }, { "name": "useFirstRender", "kind": "Variable" }, diff --git a/test/regressions/index.test.js b/test/regressions/index.test.js index dcf347f6cc884..710a4467b7c8c 100644 --- a/test/regressions/index.test.js +++ b/test/regressions/index.test.js @@ -102,6 +102,10 @@ async function main() { page.mouse.move(0, 0); const pathsToNotWaitForFlagCDN = [ + '/docs-data-grid-filtering/HeaderFilteringDataGridPro', // No flag column + '/docs-data-grid-filtering/CustomHeaderFilterDataGridPro', // No flag column + '/docs-data-grid-filtering/CustomHeaderFilterSingleDataGridPro', // No flag column + '/docs-data-grid-filtering/SimpleHeaderFilteringDataGridPro', // No flag column '/docs-data-grid-filtering/ServerFilterGrid', // No content rendered '/docs-data-grid-filtering/CustomMultiValueOperator', // No content rendered '/docs-data-grid-filtering/QuickFilteringInitialize', // No content rendered