diff --git a/packages/grid/_modules_/grid/GridComponent.tsx b/packages/grid/_modules_/grid/GridComponent.tsx index bee37be4c3dfe..884ae64bc6a55 100644 --- a/packages/grid/_modules_/grid/GridComponent.tsx +++ b/packages/grid/_modules_/grid/GridComponent.tsx @@ -14,8 +14,8 @@ import { GridDataContainer } from './components/styled-wrappers/GridDataContaine import { GridRoot } from './components/styled-wrappers/GridRoot'; import { GridWindow } from './components/styled-wrappers/GridWindow'; import { GridToolbar } from './components/styled-wrappers/GridToolbar'; -import { ColumnsToolbarButton } from './components/toolbar/columnsToolbarButton'; -import { FilterToolbarButton } from './components/toolbar/filterToolbarButton'; +import { ColumnsToolbarButton } from './components/toolbar/ColumnsToolbarButton'; +import { FilterToolbarButton } from './components/toolbar/FilterToolbarButton'; import { Viewport } from './components/viewport'; import { Watermark } from './components/watermark'; import { DATA_CONTAINER_CSS_CLASS } from './constants/cssClassesConstants'; diff --git a/packages/grid/_modules_/grid/components/icons/index.tsx b/packages/grid/_modules_/grid/components/icons/index.tsx index 29c432de23362..8740dc35a8dea 100644 --- a/packages/grid/_modules_/grid/components/icons/index.tsx +++ b/packages/grid/_modules_/grid/components/icons/index.tsx @@ -54,3 +54,8 @@ export const LoadIcon = createSvgIcon( , 'Load', ); + +export const DragIcon = createSvgIcon( + , + 'Drag', +); diff --git a/packages/grid/_modules_/grid/components/menu/GridMenu.tsx b/packages/grid/_modules_/grid/components/menu/GridMenu.tsx index bdf6090b6e0b4..c2e8149df64fd 100644 --- a/packages/grid/_modules_/grid/components/menu/GridMenu.tsx +++ b/packages/grid/_modules_/grid/components/menu/GridMenu.tsx @@ -1,5 +1,9 @@ -import { ClickAwayListener, Grow, MenuList, Paper, Popper } from '@material-ui/core'; import * as React from 'react'; +import ClickAwayListener from '@material-ui/core/ClickAwayListener'; +import Grow from '@material-ui/core/Grow'; +import MenuList from '@material-ui/core/MenuList'; +import Paper from '@material-ui/core/Paper'; +import Popper from '@material-ui/core/Popper'; export interface MenuProps { open: boolean; diff --git a/packages/grid/_modules_/grid/components/menu/columnMenu/FilterMenuItem.tsx b/packages/grid/_modules_/grid/components/menu/columnMenu/FilterMenuItem.tsx index 6203f6bf0db71..aa663f80fd712 100644 --- a/packages/grid/_modules_/grid/components/menu/columnMenu/FilterMenuItem.tsx +++ b/packages/grid/_modules_/grid/components/menu/columnMenu/FilterMenuItem.tsx @@ -1,5 +1,5 @@ -import { MenuItem } from '@material-ui/core'; import * as React from 'react'; +import MenuItem from '@material-ui/core/MenuItem'; import { useGridSelector } from '../../../hooks/features/core/useGridSelector'; import { optionsSelector } from '../../../hooks/utils/useOptionsProp'; import { ApiContext } from '../../api-context'; diff --git a/packages/grid/_modules_/grid/components/menu/columnMenu/HideColMenuItem.tsx b/packages/grid/_modules_/grid/components/menu/columnMenu/HideColMenuItem.tsx index 33ec619b2dc0b..bdd08465de37a 100644 --- a/packages/grid/_modules_/grid/components/menu/columnMenu/HideColMenuItem.tsx +++ b/packages/grid/_modules_/grid/components/menu/columnMenu/HideColMenuItem.tsx @@ -1,5 +1,5 @@ -import { MenuItem } from '@material-ui/core'; import * as React from 'react'; +import MenuItem from '@material-ui/core/MenuItem'; import { ApiContext } from '../../api-context'; import { FilterItemProps } from './FilterItemProps'; @@ -14,5 +14,9 @@ export const HideColMenuItem: React.FC = ({ column, onClick }) [apiRef, column?.field, onClick], ); + if (!column) { + return null; + } + return Hide; }; diff --git a/packages/grid/_modules_/grid/components/menu/columnMenu/SortMenuItems.tsx b/packages/grid/_modules_/grid/components/menu/columnMenu/SortMenuItems.tsx index 0964dd9ee996e..e9b4eca2454ab 100644 --- a/packages/grid/_modules_/grid/components/menu/columnMenu/SortMenuItems.tsx +++ b/packages/grid/_modules_/grid/components/menu/columnMenu/SortMenuItems.tsx @@ -1,5 +1,5 @@ -import { MenuItem } from '@material-ui/core'; import * as React from 'react'; +import MenuItem from '@material-ui/core/MenuItem'; import { useGridSelector } from '../../../hooks/features/core/useGridSelector'; import { sortModelSelector } from '../../../hooks/features/sorting/sortingSelector'; import { SortDirection } from '../../../models/sortModel'; diff --git a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts index 74d885d42963f..a9b9e587153ab 100644 --- a/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/styled-wrappers/GridRootStyles.ts @@ -47,9 +47,7 @@ export const useStyles = makeStyles( '& .MuiDataGrid-toolbar': { display: 'flex', alignItems: 'center', - minHeight: 35, // Match MUI Small Button height - backgroundColor: theme.palette.background.default, - border: `1px solid ${borderColor}`, + padding: 4, }, '& .MuiDataGrid-columnsContainer': { position: 'absolute', diff --git a/packages/grid/_modules_/grid/components/toolbar/columnsToolbarButton.tsx b/packages/grid/_modules_/grid/components/toolbar/ColumnsToolbarButton.tsx similarity index 73% rename from packages/grid/_modules_/grid/components/toolbar/columnsToolbarButton.tsx rename to packages/grid/_modules_/grid/components/toolbar/ColumnsToolbarButton.tsx index 79579f022b6dc..ad246075986fd 100644 --- a/packages/grid/_modules_/grid/components/toolbar/columnsToolbarButton.tsx +++ b/packages/grid/_modules_/grid/components/toolbar/ColumnsToolbarButton.tsx @@ -1,5 +1,5 @@ -import { IconButton } from '@material-ui/core'; import * as React from 'react'; +import Button from '@material-ui/core/Button'; import { PreferencePanelsValue } from '../../hooks/features/preferencesPanel/preferencesPanelValue'; import { useIcons } from '../../hooks/utils/useIcons'; import { ApiContext } from '../api-context'; @@ -14,8 +14,13 @@ export const ColumnsToolbarButton: React.FC<{}> = () => { }, [apiRef]); return ( - - {iconElement} - + ); }; diff --git a/packages/grid/_modules_/grid/components/toolbar/FilterToolbarButton.tsx b/packages/grid/_modules_/grid/components/toolbar/FilterToolbarButton.tsx new file mode 100644 index 0000000000000..409185e673d8b --- /dev/null +++ b/packages/grid/_modules_/grid/components/toolbar/FilterToolbarButton.tsx @@ -0,0 +1,66 @@ +import Badge from '@material-ui/core/Badge'; +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; +import * as React from 'react'; +import { columnLookupSelector } from '../../hooks/features/columns/columnsSelector'; +import { useGridSelector } from '../../hooks/features/core/useGridSelector'; +import { + activeFilterItemsSelector, + filterItemsCounterSelector, +} from '../../hooks/features/filter/filterSelector'; +import { useIcons } from '../../hooks/utils/useIcons'; +import { optionsSelector } from '../../hooks/utils/useOptionsProp'; +import { ApiContext } from '../api-context'; + +export const FilterToolbarButton: React.FC<{}> = () => { + const apiRef = React.useContext(ApiContext); + const options = useGridSelector(apiRef, optionsSelector); + const counter = useGridSelector(apiRef, filterItemsCounterSelector); + const activeFilters = useGridSelector(apiRef, activeFilterItemsSelector); + const lookup = useGridSelector(apiRef, columnLookupSelector); + const tooltipContentNode = React.useMemo(() => { + if (counter === 0) { + return 'Show Filters'; + } + return ( +
+ {counter} active filter(s) +
    + {activeFilters.map((item) => ( +
  • + {lookup[item.columnField!].headerName || item.columnField} {item.operatorValue}{' '} + {item.value} +
  • + ))} +
+
+ ); + }, [counter, activeFilters, lookup]); + + const icons = useIcons(); + const filterIconElement = React.createElement(icons.ColumnFiltering!, {}); + const showFilter = React.useCallback(() => { + apiRef!.current.showFilterPanel(); + }, [apiRef]); + + if (options.disableColumnFilter) { + return null; + } + + return ( + + + + ); +}; diff --git a/packages/grid/_modules_/grid/components/toolbar/filterToolbarButton.tsx b/packages/grid/_modules_/grid/components/toolbar/filterToolbarButton.tsx deleted file mode 100644 index 5f3cbba923cf0..0000000000000 --- a/packages/grid/_modules_/grid/components/toolbar/filterToolbarButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { IconButton } from '@material-ui/core'; -import * as React from 'react'; -import { useGridSelector } from '../../hooks/features/core/useGridSelector'; -import { useIcons } from '../../hooks/utils/useIcons'; -import { optionsSelector } from '../../hooks/utils/useOptionsProp'; -import { ApiContext } from '../api-context'; - -export const FilterToolbarButton: React.FC<{}> = () => { - const apiRef = React.useContext(ApiContext); - const options = useGridSelector(apiRef, optionsSelector); - - const icons = useIcons(); - const filterIconElement = React.createElement(icons.ColumnFiltering!, {}); - // const columnsIconElement = React.createElement(icons.ColumnSelector!, {}); - const showFilter = React.useCallback(() => { - apiRef!.current.showFilterPanel(); - }, [apiRef]); - - if (options.disableColumnFilter) { - return null; - } - - return ( - - {filterIconElement} - - ); -}; diff --git a/packages/grid/_modules_/grid/components/tools/ColumnsPanel.tsx b/packages/grid/_modules_/grid/components/tools/ColumnsPanel.tsx index dd99bafe707a9..883cef7120250 100644 --- a/packages/grid/_modules_/grid/components/tools/ColumnsPanel.tsx +++ b/packages/grid/_modules_/grid/components/tools/ColumnsPanel.tsx @@ -1,18 +1,32 @@ +import * as React from 'react'; +import FormControl from '@material-ui/core/FormControl'; +import IconButton from '@material-ui/core/IconButton'; +import Switch from '@material-ui/core/Switch'; import Button from '@material-ui/core/Button'; -import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; -import GridList from '@material-ui/core/GridList'; -import ListItem from '@material-ui/core/ListItem'; -import * as React from 'react'; +import TextField from '@material-ui/core/TextField'; import { makeStyles } from '@material-ui/core/styles'; import { allColumnsSelector } from '../../hooks/features/columns/columnsSelector'; import { useGridSelector } from '../../hooks/features/core/useGridSelector'; -import { PREVENT_HIDE_PREFERENCES } from '../../constants/index'; +import { optionsSelector } from '../../hooks/utils/useOptionsProp'; import { ApiContext } from '../api-context'; +import { DragIcon } from '../icons/index'; const useStyles = makeStyles(() => ({ - gridListRoot: { - maxWidth: '100%', + columnsListContainer: { + paddingTop: 8, + paddingLeft: 12, + }, + column: { + display: 'flex', + justifyContent: 'space-between', + padding: '2px 4px', + }, + switch: { + marginRight: 4, + }, + dragIconRoot: { + justifyContent: 'flex-end', }, })); @@ -20,22 +34,17 @@ export const ColumnsPanel: React.FC<{}> = () => { const classes = useStyles(); const apiRef = React.useContext(ApiContext); + const searchInputRef = React.useRef(null); const columns = useGridSelector(apiRef, allColumnsSelector); - - const dontHidePreferences = React.useCallback( - (event: React.ChangeEvent<{}>) => { - apiRef!.current.publishEvent(PREVENT_HIDE_PREFERENCES, {}); - event.preventDefault(); - }, - [apiRef], - ); + const { disableColumnReorder } = useGridSelector(apiRef, optionsSelector); + const [searchValue, setSearchValue] = React.useState(''); const toggleColumn = React.useCallback( - (event: React.ChangeEvent) => { - dontHidePreferences(event); - apiRef!.current.toggleColumn(event.target.name, !event.target.checked); + (event: React.MouseEvent) => { + const { name } = event.target as HTMLInputElement; + apiRef!.current.toggleColumn(name); }, - [apiRef, dontHidePreferences], + [apiRef], ); const toggleAllColumns = React.useCallback( @@ -52,28 +61,75 @@ export const ColumnsPanel: React.FC<{}> = () => { const showAllColumns = React.useCallback(() => toggleAllColumns(false), [toggleAllColumns]); const hideAllColumns = React.useCallback(() => toggleAllColumns(true), [toggleAllColumns]); + const onSearchColumnValueChange = React.useCallback((event) => { + setSearchValue(event.target.value.toLowerCase()); + }, []); + + const currentColumns = React.useMemo( + () => + !searchValue + ? columns + : columns.filter( + (column) => + column.field.toLowerCase().indexOf(searchValue) > -1 || + (column.headerName && column.headerName.toLowerCase().indexOf(searchValue) > -1), + ), + [columns, searchValue], + ); + + React.useEffect(() => { + if (searchInputRef && searchInputRef.current) { + searchInputRef.current.focus(); + } + }); + return ( -
- - {columns.map((column) => ( - +
+ +
+
+
+ {currentColumns.map((column) => ( +
} label={column.headerName || column.field} /> - + {!disableColumnReorder && ( + + + + + + )} +
))} - +
-
+
diff --git a/packages/grid/_modules_/grid/components/tools/FilterForm.tsx b/packages/grid/_modules_/grid/components/tools/FilterForm.tsx index 5f12174557b3b..add7a6115a4bb 100644 --- a/packages/grid/_modules_/grid/components/tools/FilterForm.tsx +++ b/packages/grid/_modules_/grid/components/tools/FilterForm.tsx @@ -1,5 +1,8 @@ import * as React from 'react'; -import { FormControl, IconButton, InputLabel, Select } from '@material-ui/core'; +import FormControl from '@material-ui/core/FormControl'; +import IconButton from '@material-ui/core/IconButton'; +import InputLabel from '@material-ui/core/InputLabel'; +import Select from '@material-ui/core/Select'; import { makeStyles } from '@material-ui/core/styles'; import { filterableColumnsSelector } from '../../hooks/features/columns/columnsSelector'; import { useGridSelector } from '../../hooks/features/core/useGridSelector'; @@ -18,14 +21,13 @@ export interface FilterFormProps { applyFilterChanges: (item: FilterItem) => void; applyMultiFilterOperatorChanges: (operator: LinkOperator) => void; deleteFilter: (item: FilterItem) => void; - onSelectOpen: (event: React.ChangeEvent<{}>) => void; } const useStyles = makeStyles(() => ({ root: { display: 'flex', justifyContent: 'space-around', - padding: '10px', + padding: 8, }, linkOperatorSelect: { width: 60, @@ -36,15 +38,17 @@ const useStyles = makeStyles(() => ({ operatorSelect: { width: 120, }, - FilterValueInput: { + filterValueInput: { width: 190, }, + closeIconRoot: { + justifyContent: 'flex-end', + }, })); export const FilterForm: React.FC = ({ item, hasMultipleFilters, - onSelectOpen, deleteFilter, applyFilterChanges, multiFilterOperator, @@ -125,7 +129,6 @@ export const FilterForm: React.FC = ({ = ({ labelId="columns-filter-operator-select-label" id="columns-filter-operator-select" value={multiFilterOperator} - onOpen={onSelectOpen} onChange={changeLinkOperator} disabled={!!disableMultiFilterOperator} native @@ -155,7 +157,6 @@ export const FilterForm: React.FC = ({ id="columns-filter-select" value={item.columnField || ''} onChange={changeColumn} - onOpen={onSelectOpen} native > {filterableColumns.map((col) => ( @@ -171,7 +172,6 @@ export const FilterForm: React.FC = ({ labelId="columns-operators-select-label" id="columns-operators-select" value={item.operatorValue} - onOpen={onSelectOpen} onChange={changeOperator} native > @@ -182,7 +182,7 @@ export const FilterForm: React.FC = ({ ))} - + {currentColumn && currentOperator && React.createElement(currentOperator.InputComponent, { @@ -191,7 +191,7 @@ export const FilterForm: React.FC = ({ ...currentOperator.InputComponentProps, })} - + diff --git a/packages/grid/_modules_/grid/components/tools/FilterPanel.tsx b/packages/grid/_modules_/grid/components/tools/FilterPanel.tsx index 0477db5cce454..1dd855912da4f 100644 --- a/packages/grid/_modules_/grid/components/tools/FilterPanel.tsx +++ b/packages/grid/_modules_/grid/components/tools/FilterPanel.tsx @@ -1,27 +1,22 @@ import * as React from 'react'; -import { Button } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import { useGridSelector } from '../../hooks/features/core/useGridSelector'; import { useGridState } from '../../hooks/features/core/useGridState'; -import { PREVENT_HIDE_PREFERENCES } from '../../constants/index'; +import { optionsSelector } from '../../hooks/utils/useOptionsProp'; import { FilterItem, LinkOperator } from '../../models/filterItem'; import { ApiContext } from '../api-context'; -import { AddIcon, CloseIcon } from '../icons/index'; +import { AddIcon } from '../icons/index'; import { FilterForm } from './FilterForm'; export const FilterPanel: React.FC<{}> = () => { const apiRef = React.useContext(ApiContext); const [gridState] = useGridState(apiRef!); + const { disableMultipleColumnsFiltering } = useGridSelector(apiRef, optionsSelector); + const hasMultipleFilters = React.useMemo(() => gridState.filter.items.length > 1, [ gridState.filter.items.length, ]); - const dontHidePreferences = React.useCallback( - (event: React.ChangeEvent<{}>) => { - apiRef!.current.publishEvent(PREVENT_HIDE_PREFERENCES, {}); - event.preventDefault(); - }, - [apiRef], - ); - const applyFilter = React.useCallback( (item: FilterItem) => { apiRef!.current.upsertFilter(item); @@ -40,10 +35,6 @@ export const FilterPanel: React.FC<{}> = () => { apiRef!.current.upsertFilter({}); }, [apiRef]); - const clearFilter = React.useCallback(() => { - apiRef!.current.clearFilters(); - }, [apiRef]); - const deleteFilter = React.useCallback( (item: FilterItem) => { apiRef!.current.deleteFilter(item); @@ -59,12 +50,11 @@ export const FilterPanel: React.FC<{}> = () => { return ( -
+
{gridState.filter.items.map((item, index) => ( = () => { /> ))}
-
- - -
+ {!disableMultipleColumnsFiltering && ( +
+ +
+ )} ); }; diff --git a/packages/grid/_modules_/grid/components/tools/Panel.tsx b/packages/grid/_modules_/grid/components/tools/Panel.tsx new file mode 100644 index 0000000000000..b93e392a1b1b6 --- /dev/null +++ b/packages/grid/_modules_/grid/components/tools/Panel.tsx @@ -0,0 +1,83 @@ +import ClickAwayListener from '@material-ui/core/ClickAwayListener'; +import Paper from '@material-ui/core/Paper'; +import Popper from '@material-ui/core/Popper'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import * as React from 'react'; +import { useGridSelector } from '../../hooks/features/core/useGridSelector'; +import { viewportSizeStateSelector } from '../../hooks/features/preferencesPanel/preferencePanelSelector'; +import { ApiContext } from '../api-context'; + +const useStyles = makeStyles( + (theme: Theme) => ({ + paper: { + backgroundColor: theme.palette.background.paper, + minWidth: 300, + display: 'flex', + flexDirection: 'column', + }, + panel: { + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + flex: 1, + '& .MuiDataGridPanel-container': { + display: 'flex', + flexDirection: 'column', + overflow: 'auto', + flex: '1 1', + }, + '& .MuiDataGridPanel-footer, .MuiDataGridPanel-header': { + padding: 8, + display: 'inline-flex', + justifyContent: 'space-between', + }, + }, + }), + { name: 'MuiDataGridPanel' }, +); + +export interface PanelProps { + open: boolean; +} + +export const Panel: React.FC = ({ children, open }) => { + const classes = useStyles(); + const apiRef = React.useContext(ApiContext); + const viewportSizes = useGridSelector(apiRef, viewportSizeStateSelector); + + const hidePreferences = React.useCallback(() => { + apiRef?.current.hidePreferences(); + }, [apiRef]); + + let anchorEl; + if (apiRef?.current && apiRef?.current.columnHeadersElementRef!.current) { + anchorEl = apiRef?.current.columnHeadersElementRef!.current; + } + + if (!anchorEl) { + return null; + } + + return ( + + + +
{children}
+
+
+
+ ); +}; diff --git a/packages/grid/_modules_/grid/components/tools/Preferences.tsx b/packages/grid/_modules_/grid/components/tools/Preferences.tsx index 7a7e0c027f50e..a8331adf15da6 100644 --- a/packages/grid/_modules_/grid/components/tools/Preferences.tsx +++ b/packages/grid/_modules_/grid/components/tools/Preferences.tsx @@ -1,165 +1,27 @@ -import ClickAwayListener from '@material-ui/core/ClickAwayListener'; -import Paper from '@material-ui/core/Paper'; -import Popper from '@material-ui/core/Popper'; -import { makeStyles, Theme } from '@material-ui/core/styles'; -import Tab from '@material-ui/core/Tab'; -import Tabs from '@material-ui/core/Tabs'; import * as React from 'react'; import { allColumnsSelector } from '../../hooks/features/columns/columnsSelector'; import { useGridSelector } from '../../hooks/features/core/useGridSelector'; -import { - preferencePanelStateSelector, - viewportSizeStateSelector, -} from '../../hooks/features/preferencesPanel/preferencePanelSelector'; +import { preferencePanelStateSelector } from '../../hooks/features/preferencesPanel/preferencePanelSelector'; import { PreferencePanelsValue } from '../../hooks/features/preferencesPanel/preferencesPanelValue'; -import { useIcons } from '../../hooks/utils/useIcons'; import { optionsSelector } from '../../hooks/utils/useOptionsProp'; import { ApiContext } from '../api-context'; import { ColumnsPanel } from './ColumnsPanel'; import { FilterPanel } from './FilterPanel'; +import { Panel } from './Panel'; -export const TabPanel: React.FC> = (props) => { - const { children, ...rest } = props; - - return ( -
- {children} -
- ); -}; -const useStyles = makeStyles((theme: Theme) => ({ - paper: { - backgroundColor: theme.palette.background.paper, - width: 600, - minHeight: 200, - display: 'flex', - flexDirection: 'column', - boxShadow: '0px 5px 5px 0px #00000070', - }, - tabsRoot: { - flexShrink: 0, - }, - tab: { - minWidth: 50, - }, - tabPanel: { - display: 'flex', - flexDirection: 'column', - overflow: 'hidden', - flex: 1, - '& .panelMainContainer': { - display: 'flex', - flexDirection: 'column', - overflow: 'auto', - flex: '1 1', - paddingTop: 12, - paddingLeft: 12, - }, - '& .panelFooter': { - padding: 12, - display: 'inline-flex', - flexFlow: 'wrap', - alignItems: 'baseline', - justifyContent: 'space-between', - flex: '0 1 50px', - }, - }, -})); -// TODO refactor tab to navigation with a showNav prop on the component -// TODO Extract Panel component? - -export const PreferencesPanel = () => { - const classes = useStyles(); +export function PreferencesPanel() { const apiRef = React.useContext(ApiContext); const columns = useGridSelector(apiRef, allColumnsSelector); const options = useGridSelector(apiRef, optionsSelector); const preferencePanelState = useGridSelector(apiRef, preferencePanelStateSelector); - const viewportSizes = useGridSelector(apiRef, viewportSizeStateSelector); - - const icons = useIcons(); - const filterIconElement = React.createElement(icons.ColumnFiltering!, {}); - const columnsIconElement = React.createElement(icons.ColumnSelector!, {}); - - const changeTab = React.useCallback( - (event: React.ChangeEvent<{}>, newValue: PreferencePanelsValue) => { - apiRef?.current.showPreferences(newValue); - }, - [apiRef], - ); - - const hidePreferences = React.useCallback(() => { - apiRef?.current.hidePreferences(); - }, [apiRef]); const isColumnsTabOpen = preferencePanelState.openedPanelValue === PreferencePanelsValue.columns; const isFiltersTabOpen = !preferencePanelState.openedPanelValue || !isColumnsTabOpen; - let anchorEl; - if (apiRef?.current && apiRef?.current.columnHeadersElementRef!.current) { - anchorEl = apiRef?.current.columnHeadersElementRef!.current; - } - - if (!anchorEl) { - return null; - } - return ( - 0 && preferencePanelState.open} - anchorEl={anchorEl} - style={{ position: 'relative' }} - > - - - - {!options.disableColumnFilter && ( - - )} - {!options.disableColumnSelector && ( - - )} - - {!options.disableColumnSelector && isColumnsTabOpen && ( - - - - )} - {!options.disableColumnFilter && isFiltersTabOpen && ( - - - - )} - - - + 0 && preferencePanelState.open}> + {!options.disableColumnSelector && isColumnsTabOpen && } + {!options.disableColumnFilter && isFiltersTabOpen && } + ); -}; +} diff --git a/packages/grid/_modules_/grid/components/tools/index.ts b/packages/grid/_modules_/grid/components/tools/index.ts index b02ca1464d680..96a79959fa059 100644 --- a/packages/grid/_modules_/grid/components/tools/index.ts +++ b/packages/grid/_modules_/grid/components/tools/index.ts @@ -1,5 +1,7 @@ +export * from './ColumnsPanel'; export * from './FilterForm'; +export * from './FilterInputValue'; +export * from './FilterInputValueProps'; export * from './FilterPanel'; +export * from './Panel'; export * from './Preferences'; -export * from './FilterInputValueProps'; -export * from './FilterInputValue'; diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index dd2f50d2ee9bb..a0485ae92576d 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -46,4 +46,3 @@ export const SORT_MODEL_CHANGE = 'sortModelChange'; export const STATE_CHANGE = 'stateChange'; export const MULTIPLE_KEY_PRESS_CHANGED = 'multipleKeyPressChange'; -export const PREVENT_HIDE_PREFERENCES = 'preventHidePreferences'; diff --git a/packages/grid/_modules_/grid/hooks/features/columnMenu/useColumnMenu.ts b/packages/grid/_modules_/grid/hooks/features/columnMenu/useColumnMenu.ts index f148cd2296ef4..7fc0eaaf681d7 100644 --- a/packages/grid/_modules_/grid/hooks/features/columnMenu/useColumnMenu.ts +++ b/packages/grid/_modules_/grid/hooks/features/columnMenu/useColumnMenu.ts @@ -25,7 +25,7 @@ export const useColumnMenu = (apiRef: ApiRef): void => { logger.debug('Hiding Column Menu'); setGridState((state) => ({ ...state, - columnMenu: { open: false }, + columnMenu: { ...state.columnMenu, open: false }, })); forceUpdate(); }, [forceUpdate, logger, setGridState]); diff --git a/packages/grid/_modules_/grid/hooks/features/columns/columnsSelector.ts b/packages/grid/_modules_/grid/hooks/features/columns/columnsSelector.ts index 5cd35852aabfa..8586401b14ef8 100644 --- a/packages/grid/_modules_/grid/hooks/features/columns/columnsSelector.ts +++ b/packages/grid/_modules_/grid/hooks/features/columns/columnsSelector.ts @@ -3,6 +3,7 @@ import { Columns, InternalColumns } from '../../../models/colDef/colDef'; import { GridState } from '../core/gridState'; export const columnsSelector = (state: GridState) => state.columns; +export const columnLookupSelector = (state: GridState) => state.columns.lookup; export const allColumnsSelector = createSelector( columnsSelector, (columns: InternalColumns) => columns.all, diff --git a/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts b/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts index 7ea55b92684ca..c210c6a170863 100644 --- a/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts +++ b/packages/grid/_modules_/grid/hooks/features/columns/useColumns.ts @@ -231,9 +231,9 @@ export function useColumns(columns: Columns, apiRef: ApiRef): InternalColumns { const updateColumn = React.useCallback((col: ColDef) => updateColumns([col]), [updateColumns]); const toggleColumn = React.useCallback( - (field: string, hide: boolean) => { + (field: string, hide?: boolean) => { const col = getColumnFromField(field); - const updatedCol = { ...col, hide }; + const updatedCol = { ...col, hide: hide == null ? !col.hide : hide }; updateColumns([updatedCol]); forceUpdate(); }, diff --git a/packages/grid/_modules_/grid/hooks/features/filter/filterSelector.ts b/packages/grid/_modules_/grid/hooks/features/filter/filterSelector.ts index 0ff101bc221dc..78dd30e01c7bc 100644 --- a/packages/grid/_modules_/grid/hooks/features/filter/filterSelector.ts +++ b/packages/grid/_modules_/grid/hooks/features/filter/filterSelector.ts @@ -1,7 +1,9 @@ import { createSelector } from 'reselect'; +import { FilterItem } from '../../../models/filterItem'; import { RowModel } from '../../../models/rows'; import { GridState } from '../core/gridState'; import { sortedRowsSelector } from '../sorting/sortingSelector'; +import { FilterModelState } from './FilterModelState'; import { VisibleRowsState } from './visibleRowsState'; export const visibleRowsStateSelector = (state: GridState) => state.visibleRows; @@ -19,3 +21,16 @@ export const visibleRowCountSelector = createSelector rows.length, ); + +export const filterStateSelector: (state: GridState) => FilterModelState = (state) => state.filter; + +export const activeFilterItemsSelector = createSelector( + filterStateSelector, + (filterModel) => + filterModel.items?.filter((item) => item.value != null && item.value?.toString() !== ''), // allows to count false or 0 +); + +export const filterItemsCounterSelector = createSelector( + activeFilterItemsSelector, + (activeFilters) => activeFilters.length, +); diff --git a/packages/grid/_modules_/grid/hooks/features/filter/useFilter.ts b/packages/grid/_modules_/grid/hooks/features/filter/useFilter.ts index 957a6d880de6b..ead805d3f90ec 100644 --- a/packages/grid/_modules_/grid/hooks/features/filter/useFilter.ts +++ b/packages/grid/_modules_/grid/hooks/features/filter/useFilter.ts @@ -5,12 +5,12 @@ import { buildCellParams } from '../../../utils/paramsUtils'; import { isEqual } from '../../../utils/utils'; import { useApiMethod } from '../../root/useApiMethod'; import { useLogger } from '../../utils/useLogger'; +import { optionsSelector } from '../../utils/useOptionsProp'; import { PreferencePanelsValue } from '../preferencesPanel/preferencesPanelValue'; import { filterableColumnsSelector } from '../columns/columnsSelector'; import { useGridSelector } from '../core/useGridSelector'; import { useGridState } from '../core/useGridState'; import { sortedRowsSelector } from '../sorting/sortingSelector'; -import { getInitialFilterState } from './FilterModelState'; import { getInitialVisibleRowsState } from './visibleRowsState'; export const useFilter = (apiRef: ApiRef): void => { @@ -19,6 +19,7 @@ export const useFilter = (apiRef: ApiRef): void => { const rows = useGridSelector(apiRef, sortedRowsSelector); const filterableColumns = useGridSelector(apiRef, filterableColumnsSelector); + const { disableMultipleColumnsFiltering } = useGridSelector(apiRef, optionsSelector); const clearFilteredRows = React.useCallback(() => { setGridState((state) => ({ @@ -27,16 +28,6 @@ export const useFilter = (apiRef: ApiRef): void => { })); }, [setGridState]); - const clearFilters = React.useCallback(() => { - setGridState((state) => ({ - ...state, - filter: getInitialFilterState(), - })); - clearFilteredRows(); - apiRef.current.upsertFilter({}); - forceUpdate(); - }, [apiRef, clearFilteredRows, forceUpdate, setGridState]); - const applyFilter = React.useCallback( (filterItem: FilterItem, linkOperator: LinkOperator) => { if (!filterItem.columnField || !filterItem.operatorValue || !filterItem.value) { @@ -125,7 +116,9 @@ export const useFilter = (apiRef: ApiRef): void => { item.columnField, )!.filterOperators![0].value!; } - + if (disableMultipleColumnsFiltering && items.length > 1) { + items.length = 1; + } const newState = { ...state, filter: { ...state.filter, items }, @@ -134,7 +127,7 @@ export const useFilter = (apiRef: ApiRef): void => { }); applyFilters(); }, - [apiRef, applyFilters, filterableColumns, setGridState], + [apiRef, applyFilters, disableMultipleColumnsFiltering, filterableColumns, setGridState], ); const deleteFilter = React.useCallback( @@ -169,9 +162,8 @@ export const useFilter = (apiRef: ApiRef): void => { } } apiRef.current.showPreferences(PreferencePanelsValue.filters); - forceUpdate(); }, - [apiRef, forceUpdate, gridState.filter.items], + [apiRef, gridState.filter.items], ); const applyFilterLinkOperator = React.useCallback( @@ -191,7 +183,6 @@ export const useFilter = (apiRef: ApiRef): void => { applyFilterLinkOperator, applyFilters, upsertFilter, - clearFilters, deleteFilter, showFilterPanel, }, diff --git a/packages/grid/_modules_/grid/hooks/features/preferencesPanel/usePreferencesPanel.ts b/packages/grid/_modules_/grid/hooks/features/preferencesPanel/usePreferencesPanel.ts index 95c25b34d51d2..df39e947588ba 100644 --- a/packages/grid/_modules_/grid/hooks/features/preferencesPanel/usePreferencesPanel.ts +++ b/packages/grid/_modules_/grid/hooks/features/preferencesPanel/usePreferencesPanel.ts @@ -1,7 +1,5 @@ import * as React from 'react'; -import { PREVENT_HIDE_PREFERENCES } from '../../../constants/eventsConstants'; import { ApiRef } from '../../../models/api/apiRef'; -import { useApiEventHandler } from '../../root/useApiEventHandler'; import { useApiMethod } from '../../root/useApiMethod'; import { useLogger } from '../../utils/useLogger'; import { useGridState } from '../core/useGridState'; @@ -12,32 +10,36 @@ export const usePreferencesPanel = (apiRef: ApiRef): void => { const [, setGridState, forceUpdate] = useGridState(apiRef); const hideTimeout = React.useRef(); - const showPreferences = React.useCallback( - (newValue: PreferencePanelsValue) => { - logger.debug('Opening Preferences Panel'); - setGridState((state) => ({ - ...state, - preferencePanel: { ...state.preferencePanel, open: true, openedPanelValue: newValue }, - })); - forceUpdate(); - }, - [forceUpdate, logger, setGridState], - ); - const hidePreferences = React.useCallback(() => { logger.debug('Hiding Preferences Panel'); setGridState((state) => ({ ...state, preferencePanel: { open: false } })); forceUpdate(); }, [forceUpdate, logger, setGridState]); + // This is to prevent the preferences from closing when you open a select box or another panel, + // The issue is in MUI core V4 => Fixed in V5 + const dontHidePanel = React.useCallback(() => { + setImmediate(() => clearTimeout(hideTimeout.current)); + }, []); + + // This is a hack for the issue with Core V4, by delaying hiding the panel on the clickAwayListener, + // we can cancel the action if the trigger element still need the panel... const hidePreferencesDelayed = React.useCallback(() => { hideTimeout.current = setTimeout(hidePreferences, 100); }, [hidePreferences]); - // This is to prevent the preferences from closing when you open a select box, issue with MUI core V4 => Fixed in V5 - const dontHidePanel = React.useCallback(() => { - setImmediate(() => clearTimeout(hideTimeout.current)); - }, []); + const showPreferences = React.useCallback( + (newValue: PreferencePanelsValue) => { + logger.debug('Opening Preferences Panel'); + dontHidePanel(); + setGridState((state) => ({ + ...state, + preferencePanel: { ...state.preferencePanel, open: true, openedPanelValue: newValue }, + })); + forceUpdate(); + }, + [dontHidePanel, forceUpdate, logger, setGridState], + ); useApiMethod( apiRef, @@ -48,5 +50,9 @@ export const usePreferencesPanel = (apiRef: ApiRef): void => { 'ColumnMenuApi', ); - useApiEventHandler(apiRef!, PREVENT_HIDE_PREFERENCES, dontHidePanel); + React.useEffect(() => { + return () => { + clearTimeout(hideTimeout.current); + }; + }, []); }; diff --git a/packages/grid/_modules_/grid/hooks/utils/useOptionsProp.ts b/packages/grid/_modules_/grid/hooks/utils/useOptionsProp.ts index 706b9e5ac23a7..7ea8f21ab391e 100644 --- a/packages/grid/_modules_/grid/hooks/utils/useOptionsProp.ts +++ b/packages/grid/_modules_/grid/hooks/utils/useOptionsProp.ts @@ -36,6 +36,7 @@ export function useOptionsProp(apiRef: ApiRef, props: GridComponentProps): GridO disableSelectionOnClick: props.disableSelectionOnClick, disableMultipleColumnsSorting: props.disableMultipleColumnsSorting, disableMultipleSelection: props.disableMultipleSelection, + disableMultipleColumnsFiltering: props.disableMultipleColumnsFiltering, disableColumnResize: props.disableColumnResize, disableColumnReorder: props.disableColumnReorder, disableColumnFilter: props.disableColumnFilter, @@ -86,6 +87,7 @@ export function useOptionsProp(apiRef: ApiRef, props: GridComponentProps): GridO props.disableSelectionOnClick, props.disableMultipleColumnsSorting, props.disableMultipleSelection, + props.disableMultipleColumnsFiltering, props.disableColumnResize, props.disableColumnReorder, props.disableColumnFilter, diff --git a/packages/grid/_modules_/grid/models/api/columnApi.ts b/packages/grid/_modules_/grid/models/api/columnApi.ts index 8114cc6be2672..8dcdf7931d299 100644 --- a/packages/grid/_modules_/grid/models/api/columnApi.ts +++ b/packages/grid/_modules_/grid/models/api/columnApi.ts @@ -49,6 +49,7 @@ export interface ColumnApi { /** * Allows to toggle a column. * @param field + * @param forceHide Optional value, if not provided the column will toggle. */ - toggleColumn: (field: string, hide: boolean) => void; + toggleColumn: (field: string, forceHide?: boolean) => void; } diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 224915c4bbd05..6e5597218f445 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -85,6 +85,11 @@ export interface GridOptions { * @default false */ disableMultipleSelection?: boolean; + /** + * If `true`, filtering with multiple columns is disabled. + * @default false + */ + disableMultipleColumnsFiltering?: boolean; /** * If `true`, sorting with multiple columns is disabled. * @default false diff --git a/packages/grid/data-grid/src/DataGrid.tsx b/packages/grid/data-grid/src/DataGrid.tsx index a57268bce0f37..bcfe634e497e8 100644 --- a/packages/grid/data-grid/src/DataGrid.tsx +++ b/packages/grid/data-grid/src/DataGrid.tsx @@ -6,6 +6,7 @@ import { GridComponent, GridComponentProps, classnames } from '../../_modules_/g const FORCED_PROPS: Partial = { disableColumnResize: true, disableColumnReorder: true, + disableMultipleColumnsFiltering: true, disableMultipleColumnsSorting: true, disableMultipleSelection: true, pagination: true, @@ -16,6 +17,7 @@ export type DataGridProps = Omit< GridComponentProps, | 'disableColumnResize' | 'disableColumnReorder' + | 'disableMultipleColumnsFiltering' | 'disableMultipleColumnsSorting' | 'disableMultipleSelection' | 'licenseStatus' @@ -25,6 +27,7 @@ export type DataGridProps = Omit< > & { disableColumnResize?: true; disableColumnReorder?: true; + disableMultipleColumnsFiltering?: true; disableMultipleColumnsSorting?: true; disableMultipleSelection?: true; pagination?: true; @@ -106,6 +109,19 @@ const DataGrid2 = React.forwardRef(function DataG } return null; }), + disableMultipleColumnsFiltering: chainPropTypes(PropTypes.bool, (props: any) => { + if (props.disableMultipleColumnsFiltering === false) { + throw new Error( + [ + `Material-UI: \`\` is not a valid prop.`, + 'Only single column sorting is available in the MIT version.', + '', + 'You need to upgrade to the XGrid component to unlock this feature.', + ].join('\n'), + ); + } + return null; + }), disableMultipleColumnsSorting: chainPropTypes(PropTypes.bool, (props: any) => { if (props.disableMultipleColumnsSorting === false) { throw new Error( diff --git a/packages/storybook/src/stories/grid-columns.stories.tsx b/packages/storybook/src/stories/grid-columns.stories.tsx index 462d60b3100d3..0829cea675686 100644 --- a/packages/storybook/src/stories/grid-columns.stories.tsx +++ b/packages/storybook/src/stories/grid-columns.stories.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { ColDef, XGrid, ColTypeDef } from '@material-ui/x-grid'; import { withKnobs } from '@storybook/addon-knobs'; import CreateIcon from '@material-ui/icons/Create'; -import { Button } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; import { useData } from '../hooks/useData'; export default { diff --git a/packages/storybook/src/stories/grid-sorting.stories.tsx b/packages/storybook/src/stories/grid-sorting.stories.tsx index 24ab0dbe26283..68c63cca0349f 100644 --- a/packages/storybook/src/stories/grid-sorting.stories.tsx +++ b/packages/storybook/src/stories/grid-sorting.stories.tsx @@ -1,6 +1,6 @@ -import { Button } from '@material-ui/core'; -import { randomInt } from '@material-ui/x-grid-data-generator'; import * as React from 'react'; +import Button from '@material-ui/core/Button'; +import { randomInt } from '@material-ui/x-grid-data-generator'; import { ColDef, XGrid, diff --git a/packages/storybook/src/stories/grid-state.stories.tsx b/packages/storybook/src/stories/grid-state.stories.tsx index f13fbea2f92f4..f8f4f41701ec1 100644 --- a/packages/storybook/src/stories/grid-state.stories.tsx +++ b/packages/storybook/src/stories/grid-state.stories.tsx @@ -1,6 +1,5 @@ -import { Button } from '@material-ui/core'; -import { useState } from 'react'; import * as React from 'react'; +import Button from '@material-ui/core/Button'; import { GridState, SortingState, useApiRef, XGrid } from '@material-ui/x-grid'; import { withKnobs } from '@storybook/addon-knobs'; import { StateChangeParams } from '../../../grid/_modules_/grid/models/params/stateChangeParams'; @@ -107,7 +106,7 @@ const defaultProps = { }; export function InitialState() { - const [gridState, setGridState] = useState>({ + const [gridState, setGridState] = React.useState>({ sorting: { sortModel: [{ field: 'brand', sort: 'desc' }], sortedRows: [2, 0, 1] }, });