Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[DataGrid] Customize FilterPanel with props #3497

Merged
merged 40 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9afd3f4
add props to filter panel
alexfauquette Dec 22, 2021
129e939
add doc
alexfauquette Dec 22, 2021
0af7213
set default values
alexfauquette Dec 22, 2021
93e7aa3
small PR feedbacks
alexfauquette Jan 7, 2022
bd3fcdd
use class override for style modification
alexfauquette Jan 11, 2022
7651d6b
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Jan 11, 2022
3c9de83
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Jan 11, 2022
d421ad9
scripts
alexfauquette Jan 11, 2022
01dbe3e
markdownlint
alexfauquette Jan 11, 2022
5b50bf0
Apply suggestions from code review
alexfauquette Jan 17, 2022
3560f2b
typing feedbacks and avoid breaking changes
alexfauquette Jan 18, 2022
2855ab6
update classes names
alexfauquette Jan 18, 2022
d8c41ac
pass props to filterForm FormeControl
alexfauquette Jan 18, 2022
7d39cd8
proptypes
alexfauquette Jan 18, 2022
15630d7
remove required
alexfauquette Jan 18, 2022
87e656a
update doc explainations
alexfauquette Jan 18, 2022
1b16222
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Jan 26, 2022
3d7b052
update styling
alexfauquette Jan 26, 2022
3936174
update doc table
alexfauquette Jan 26, 2022
9d3e4fa
fix tests
alexfauquette Jan 26, 2022
8170224
add classes
alexfauquette Jan 26, 2022
7ff5e6c
match clesse properties with slot names
alexfauquette Jan 26, 2022
08225c9
update CSS doc
alexfauquette Jan 26, 2022
808261e
update doc example
alexfauquette Feb 1, 2022
00f7cd1
use styled without memo
alexfauquette Feb 1, 2022
9dcadb0
add label for outlined input
alexfauquette Feb 1, 2022
b2e6453
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Feb 2, 2022
7e659ab
docs:typescript:formatted
alexfauquette Feb 2, 2022
6c6d6a9
Jose feedback
alexfauquette Feb 7, 2022
84f4e16
mat feedbacks on typing
alexfauquette Feb 7, 2022
29bd314
language
alexfauquette Feb 7, 2022
6d5c487
yarn proptypes
alexfauquette Feb 7, 2022
72d2886
fix lint
alexfauquette Feb 7, 2022
64e7767
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Feb 8, 2022
04a5331
feedbacks
alexfauquette Feb 8, 2022
f10cb3d
add files removed during migration
alexfauquette Feb 8, 2022
7127c06
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Feb 10, 2022
3c8dbf7
Fix demo migration
alexfauquette Feb 10, 2022
ec687de
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Feb 10, 2022
cc8ef3e
Merge remote-tracking branch 'upstream/master' into customize-filters
alexfauquette Feb 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/src/pages/components/data-grid/filtering/CustomFilterPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { DataGridPro, GridLinkOperator } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';

export default function DisableSortingGrid() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 10,
maxColumns: 6,
});

return (
<div style={{ height: 400, width: '100%' }}>
<DataGridPro
{...data}
// components={{
Copy link
Member

Choose a reason for hiding this comment

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

Remove commented-out code (or maybe it's still a WIP).

Copy link
Member Author

Choose a reason for hiding this comment

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

In fact, I was looking for a way to show all the customization options in the same code, and comment on those which are not used in the demo. But yes, that's not explicit enough.

// FilterPanel: MyCustomFilterPanel,
// }}
componentsProps={{
filterPanel: {
linkOperators: [GridLinkOperator.And],
columnsSort: 'asc',
deleteIconContainerSx: { display: 'none' },
valueContainerSx: { width: 200 },
// linkOperatorContainerSx: {},
// columnContainerSx: {},
// operatorContainerSx: {}
},
}}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { DataGridPro, GridLinkOperator } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';

export default function DisableSortingGrid() {
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 10,
maxColumns: 6,
});

return (
<div style={{ height: 400, width: '100%' }}>
<DataGridPro
{...data}
// components={{
// FilterPanel: MyCustomFilterPanel,
// }}
componentsProps={{
filterPanel: {
linkOperators: [GridLinkOperator.And],
columnsSort: 'asc',
deleteIconContainerSx: { display: 'none' },
valueContainerSx: { width: 200 },
// linkOperatorContainerSx: {},
// columnContainerSx: {},
// operatorContainerSx: {}
},
}}
/>
</div>
);
}
16 changes: 16 additions & 0 deletions docs/src/pages/components/data-grid/filtering/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ In this demo, you can see how to create a completely new operator for the Rating

{{"demo": "pages/components/data-grid/filtering/CustomRatingOperator.js", "bg": "inline", "defaultCodeOpen": false}}

### Customize the filterPanel

Like most of the components, the filter panel can be [overridden](/components/data-grid/components/#overriding-components) by a custom one.
For common modification, a simpler approach is available: modifying props.
The default component `<GridFilterPanel/>` provides props allowing you to customize:

- The available `linkOperators`
- The order of the column selector
- The style of the container for each input

In this example, the filter panel is customized such that the column selector is sorted by ascending order.
The link operator is fixed to `"And"`.
The delete icon has been removed and the value input is larger.

{{"demo": "pages/components/data-grid/filtering/CustomFilterPanel.js", "bg": "inline"}}

## Server-side filter

Filtering can be run server-side by setting the `filterMode` prop to `server`, and implementing the `onFilterModelChange` handler.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import IconButton from '@mui/material/IconButton';
import InputLabel from '@mui/material/InputLabel';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import { capitalize, unstable_useId as useId } from '@mui/material/utils';
import { styled } from '@mui/material/styles';
import { styled, SxProps, Theme } from '@mui/material/styles';
import { filterableGridColumnsSelector } from '../../../hooks/features/columns/gridColumnsSelector';
import { useGridSelector } from '../../../hooks/utils/useGridSelector';
import { GridFilterItem, GridLinkOperator } from '../../../models/gridFilterItem';
Expand All @@ -27,6 +27,14 @@ export interface GridFilterFormProps {
applyFilterChanges: (item: GridFilterItem) => void;
applyMultiFilterOperatorChanges: (operator: GridLinkOperator) => void;
deleteFilter: (item: GridFilterItem) => void;
hasLinkOperatorColumn?: boolean;
linkOperators?: (GridLinkOperator.And | GridLinkOperator.Or)[];
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
columnsSort?: 'asc' | 'desc';
deleteIconContainerSx?: SxProps<Theme>;
Copy link
Member

Choose a reason for hiding this comment

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

@mnajdova in the core how to you handle when you want the user to easily customize the style of nested elements with sx ?

Do you pass down the sx function like this or do you expose the classes and let the user nest inside the root sx ?

Copy link
Member

Choose a reason for hiding this comment

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

I would recommend using components and componentsProps for this. Then developers can do:

<GridFilterForm
  componentsProps={{ deleteIconContainer: { sx: /* ... */ } }}
  ...
/>

At least this is what we do in the @mui/base.

Copy link
Member Author

@alexfauquette alexfauquette Jan 11, 2022

Choose a reason for hiding this comment

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

@mnajdova Do you mean using components props to let dev use a custom filter as follow?

const CustomFilterForm = (props) => <GridFilterForm
	componentsProps={{ deleteIconContainer: { sx: /* ... */ } }}
	{...props}
/>

<DataGrid
	...
	components={{
		filterForm: CustomFilterForm
	}}

For now, I tried to use nesting classes as follows. Considering that <FilterPanel /> is the only component user can have access to. The other components are considered as nested ones, and can only be customized by CSS selectors.

<DataGrid
	...
	componentsProps={{
		filterPanel: {
			sx: {
				"& .MuiDataGrid-filterForm": { ... },
				"& .MuiDataGrid-closeIconController": { ... },
				...
			}
		}
	}}

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, for some reason I have missed this comment. If the other nested components can only be customized by CSS selectors, why are you adding this new prop deleteIconContainerSx. It contradicts with the previous statement.

My point was that, we don't add first class props on the component for prop that should be passed on some nested elements, we prefer using componentsProps for this use-case.

linkOperatorContainerSx?: SxProps<Theme>;
columnContainerSx?: SxProps<Theme>;
operatorContainerSx?: SxProps<Theme>;
valueContainerSx?: SxProps<Theme>;
}

type OwnerState = { classes: GridComponentProps['classes'] };
Expand All @@ -47,21 +55,66 @@ const GridFilterFormRoot = styled('div', {
overridesResolver: (props, styles) => styles.filterForm,
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
})(({ theme }) => ({
display: 'flex',
justifyContent: 'space-around',
padding: theme.spacing(1),
}));

const getLinkOperatorLocaleKey = (linkOperator) => {
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
switch (linkOperator) {
case GridLinkOperator.And:
return 'filterPanelOperatorAnd';
case GridLinkOperator.Or:
return 'filterPanelOperatorOr';
default:
throw new Error('MUI: Invalid `linkOperator` property in the `GridFilterPanel`.');
}
};

const getColumnLabel = (col) => col.headerName || col.field;

const useColumnSorting = (filterableColumns, columnsSort) => {
const [sortedColumns, setSortedColumns] = React.useState(filterableColumns);

React.useEffect(() => {
switch (columnsSort) {
case 'asc':
setSortedColumns(
filterableColumns.sort((a, b) => (getColumnLabel(a) < getColumnLabel(b) ? -1 : 1)),
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
);

break;
case 'desc':
setSortedColumns(
filterableColumns.sort((a, b) => (getColumnLabel(a) < getColumnLabel(b) ? 1 : -1)),
);

break;
default:
setSortedColumns(filterableColumns);
break;
}
}, [filterableColumns, columnsSort]);

return sortedColumns;
};

function GridFilterForm(props: GridFilterFormProps) {
const {
item,
hasMultipleFilters,
deleteFilter,
applyFilterChanges,
multiFilterOperator,
showMultiFilterOperators,
disableMultiFilterOperator,
applyMultiFilterOperatorChanges,
focusElementRef,
hasLinkOperatorColumn,
linkOperators = [GridLinkOperator.And, GridLinkOperator.Or],
columnsSort,
deleteIconContainerSx = {},
linkOperatorContainerSx = {},
columnContainerSx = {},
operatorContainerSx = {},
valueContainerSx = {},
} = props;
const apiRef = useGridApiContext();
const filterableColumns = useGridSelector(apiRef, filterableGridColumnsSelector);
Expand All @@ -77,6 +130,8 @@ function GridFilterForm(props: GridFilterFormProps) {
const valueRef = React.useRef<any>(null);
const filterSelectorRef = React.useRef<HTMLInputElement>(null);

const sortedFilterableColumns = useColumnSorting(filterableColumns, columnsSort);
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved

const currentColumn = item.columnField ? apiRef.current.getColumn(item.columnField) : null;

const currentOperator = React.useMemo(() => {
Expand Down Expand Up @@ -175,7 +230,12 @@ function GridFilterForm(props: GridFilterFormProps) {
<GridFilterFormRoot className={classes.root}>
<FormControl
variant="standard"
sx={{ flexShrink: 0, justifyContent: 'flex-end', marginRight: 0.5, marginBottom: 0.2 }}
sx={[
{ flexShrink: 0, justifyContent: 'flex-end', marginRight: 0.5, marginBottom: 0.2 },
...(Array.isArray(deleteIconContainerSx)
? deleteIconContainerSx
: [deleteIconContainerSx]),
]}
Copy link
Member

@m4theushw m4theushw Dec 27, 2021

Choose a reason for hiding this comment

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

As we discussed, the prop could be renamed to deleteIconContainerProps and allow to override other props of the FormControl.

Suggested change
sx={[
{ flexShrink: 0, justifyContent: 'flex-end', marginRight: 0.5, marginBottom: 0.2 },
...(Array.isArray(deleteIconContainerSx)
? deleteIconContainerSx
: [deleteIconContainerSx]),
]}
{...deleteIconContainerProps}
sx={{
flexShrink: 0,
justifyContent: 'flex-end',
marginRight: 0.5,
marginBottom: 0.2,
...deleteIconContainerProps.sx
}}

Another alternative is to make the FormControl a styled component, then you don't need to care about spreading deleteIconContainerProps.sx, since it's done automatically.

const StyledFormControl = styled(FormControl)({ flexShrink: 0 });

<StyledFormControl {...deleteIconContainerProps} />

>
<IconButton
aria-label={apiRef.current.getLocaleText('filterPanelDeleteIconLabel')}
Expand All @@ -188,11 +248,16 @@ function GridFilterForm(props: GridFilterFormProps) {
</FormControl>
<FormControl
variant="standard"
sx={{
minWidth: 60,
display: hasMultipleFilters ? 'block' : 'none',
visibility: showMultiFilterOperators ? 'visible' : 'hidden',
}}
sx={[
{
minWidth: 60,
display: hasLinkOperatorColumn ? 'block' : 'none',
visibility: showMultiFilterOperators ? 'visible' : 'hidden',
},
...(Array.isArray(linkOperatorContainerSx)
? linkOperatorContainerSx
: [linkOperatorContainerSx]),
]}
>
<InputLabel htmlFor={linkOperatorSelectId} id={linkOperatorSelectLabelId}>
{apiRef.current.getLocaleText('filterPanelOperators')}
Expand All @@ -202,18 +267,23 @@ function GridFilterForm(props: GridFilterFormProps) {
id={linkOperatorSelectId}
value={multiFilterOperator}
onChange={changeLinkOperator}
disabled={!!disableMultiFilterOperator}
disabled={!!disableMultiFilterOperator || linkOperators.length === 1}
native
>
<option key={GridLinkOperator.And.toString()} value={GridLinkOperator.And.toString()}>
{apiRef.current.getLocaleText('filterPanelOperatorAnd')}
</option>
<option key={GridLinkOperator.Or.toString()} value={GridLinkOperator.Or.toString()}>
{apiRef.current.getLocaleText('filterPanelOperatorOr')}
</option>
{linkOperators.map((linkOperator) => (
<option key={linkOperator.toString()} value={linkOperator.toString()}>
{apiRef.current.getLocaleText(getLinkOperatorLocaleKey(linkOperator))}
</option>
))}
</Select>
</FormControl>
<FormControl variant="standard" sx={{ width: 150 }}>
<FormControl
variant="standard"
sx={[
{ width: 150 },
...(Array.isArray(columnContainerSx) ? columnContainerSx : [columnContainerSx]),
]}
>
<InputLabel htmlFor={columnSelectId} id={columnSelectLabelId}>
{apiRef.current.getLocaleText('filterPanelColumns')}
</InputLabel>
Expand All @@ -224,14 +294,20 @@ function GridFilterForm(props: GridFilterFormProps) {
onChange={changeColumn}
native
>
{filterableColumns.map((col) => (
{sortedFilterableColumns.map((col) => (
<option key={col.field} value={col.field}>
{col.headerName || col.field}
{getColumnLabel(col)}
</option>
))}
</Select>
</FormControl>
<FormControl variant="standard" sx={{ width: 120 }}>
<FormControl
variant="standard"
sx={[
{ width: 120 },
...(Array.isArray(operatorContainerSx) ? operatorContainerSx : [operatorContainerSx]),
]}
>
<InputLabel htmlFor={operatorSelectId} id={operatorSelectLabelId}>
{apiRef.current.getLocaleText('filterPanelOperators')}
</InputLabel>
Expand All @@ -253,7 +329,13 @@ function GridFilterForm(props: GridFilterFormProps) {
))}
</Select>
</FormControl>
<FormControl variant="standard" sx={{ width: 190 }}>
<FormControl
variant="standard"
sx={[
{ width: 190 },
...(Array.isArray(valueContainerSx) ? valueContainerSx : [valueContainerSx]),
]}
>
{currentOperator?.InputComponent ? (
<currentOperator.InputComponent
apiRef={apiRef}
Expand All @@ -275,21 +357,49 @@ GridFilterForm.propTypes = {
// ----------------------------------------------------------------------
applyFilterChanges: PropTypes.func.isRequired,
applyMultiFilterOperatorChanges: PropTypes.func.isRequired,
columnContainerSx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
columnsSort: PropTypes.oneOf(['asc', 'desc']),
deleteFilter: PropTypes.func.isRequired,
deleteIconContainerSx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
disableMultiFilterOperator: PropTypes.bool,
focusElementRef: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
PropTypes.func,
PropTypes.object,
]),
hasLinkOperatorColumn: PropTypes.bool,
hasMultipleFilters: PropTypes.bool.isRequired,
item: PropTypes.shape({
columnField: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
operatorValue: PropTypes.string,
value: PropTypes.any,
}).isRequired,
linkOperatorContainerSx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
linkOperators: PropTypes.arrayOf(PropTypes.oneOf(['and', 'or']).isRequired),
multiFilterOperator: PropTypes.oneOf(['and', 'or']),
operatorContainerSx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
showMultiFilterOperators: PropTypes.bool,
valueContainerSx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
} as any;

export { GridFilterForm };
Loading