-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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] Column management design updates #16630
Changes from all commits
f1bdfe5
9827013
895b58c
c6a2a07
28ac9c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import * as React from 'react'; | ||
import { styled, keyframes } from '@mui/system'; | ||
|
||
export interface GridShadowScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> { | ||
children: React.ReactNode; | ||
} | ||
|
||
const reveal = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } }); | ||
const detectScroll = keyframes({ 'from, to': { '--scrollable': '" "' } }); | ||
|
||
const ShadowScrollArea = styled('div', { | ||
name: 'MuiDataGrid', | ||
slot: 'ShadowScrollArea', | ||
})({ | ||
flex: 1, | ||
display: 'flex', | ||
flexDirection: 'column', | ||
animation: detectScroll, | ||
animationTimeline: '--scroll-timeline', | ||
animationFillMode: 'none', | ||
boxSizing: 'border-box', | ||
overflow: 'auto', | ||
scrollTimeline: '--scroll-timeline block', | ||
'&::before, &::after': { | ||
content: '""', | ||
flexShrink: 0, | ||
display: 'block', | ||
position: 'sticky', | ||
left: 0, | ||
width: '100%', | ||
height: '4px', | ||
animation: `${reveal} linear both`, | ||
animationTimeline: '--scroll-timeline', | ||
|
||
// Custom property toggle trick: | ||
// - Detects if the element is scrollable | ||
// - https://css-tricks.com/the-css-custom-property-toggle-trick/ | ||
'--visibility-scrollable': 'var(--scrollable) visible', | ||
'--visibility-not-scrollable': 'hidden', | ||
visibility: 'var(--visibility-scrollable, var(--visibility-not-scrollable))', | ||
}, | ||
'&::before': { | ||
top: 0, | ||
background: 'linear-gradient(to bottom, rgba(0,0,0,0.05) 0, transparent 100%)', | ||
animationRange: '0 4px', | ||
}, | ||
'&::after': { | ||
bottom: 0, | ||
background: 'linear-gradient(to top, rgba(0,0,0,0.05) 0, transparent 100%)', | ||
MBilalShafi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
animationDirection: 'reverse', | ||
animationRange: 'calc(100% - 4px) 100%', | ||
}, | ||
}); | ||
|
||
/** | ||
* Adds scroll shadows above and below content in a scrollable container. | ||
*/ | ||
export function GridShadowScrollArea(props: GridShadowScrollAreaProps) { | ||
const { children, ...rest } = props; | ||
return <ShadowScrollArea {...rest}>{children}</ShadowScrollArea>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import type { GridSlotProps } from '../../models/gridSlotsComponentsProps'; | |
import { getDataGridUtilityClass } from '../../constants/gridClasses'; | ||
import { checkColumnVisibilityModelsSame, defaultSearchPredicate } from './utils'; | ||
import { NotRendered } from '../../utils/assert'; | ||
import { GridShadowScrollArea } from '../GridShadowScrollArea'; | ||
|
||
export interface GridColumnsManagementProps { | ||
/* | ||
|
@@ -237,28 +238,31 @@ function GridColumnsManagement(props: GridColumnsManagementProps) { | |
input: { | ||
startAdornment: ( | ||
<rootProps.slots.baseInputAdornment position="start"> | ||
<rootProps.slots.quickFilterIcon /> | ||
<rootProps.slots.quickFilterIcon fontSize="small" /> | ||
</rootProps.slots.baseInputAdornment> | ||
), | ||
endAdornment: ( | ||
<rootProps.slots.baseIconButton | ||
aria-label={apiRef.current.getLocaleText('columnsManagementDeleteIconLabel')} | ||
size="small" | ||
style={ | ||
searchValue | ||
? { | ||
visibility: 'visible', | ||
} | ||
: { | ||
visibility: 'hidden', | ||
} | ||
} | ||
tabIndex={-1} | ||
onClick={handleSearchReset} | ||
{...rootProps.slotProps?.baseIconButton} | ||
> | ||
<rootProps.slots.quickFilterClearIcon fontSize="small" /> | ||
</rootProps.slots.baseIconButton> | ||
<rootProps.slots.baseInputAdornment position="end"> | ||
<rootProps.slots.baseIconButton | ||
size="small" | ||
aria-label={apiRef.current.getLocaleText('columnsManagementDeleteIconLabel')} | ||
style={ | ||
searchValue | ||
? { | ||
visibility: 'visible', | ||
} | ||
: { | ||
visibility: 'hidden', | ||
} | ||
} | ||
tabIndex={-1} | ||
onClick={handleSearchReset} | ||
edge="end" | ||
{...rootProps.slotProps?.baseIconButton} | ||
> | ||
<rootProps.slots.quickFilterClearIcon fontSize="small" /> | ||
</rootProps.slots.baseIconButton> | ||
</rootProps.slots.baseInputAdornment> | ||
), | ||
}, | ||
htmlInput: { | ||
|
@@ -271,30 +275,31 @@ function GridColumnsManagement(props: GridColumnsManagementProps) { | |
{...searchInputProps} | ||
/> | ||
</GridColumnsManagementHeader> | ||
<GridColumnsManagementBody className={classes.root} ownerState={rootProps}> | ||
{currentColumns.map((column) => ( | ||
<rootProps.slots.baseCheckbox | ||
key={column.field} | ||
className={classes.row} | ||
disabled={column.hideable === false} | ||
checked={columnVisibilityModel[column.field] !== false} | ||
onClick={toggleColumn} | ||
name={column.field} | ||
inputRef={isFirstHideableColumn(column) ? firstSwitchRef : undefined} | ||
label={column.headerName || column.field} | ||
size="medium" | ||
density="compact" | ||
fullWidth | ||
{...rootProps.slotProps?.baseCheckbox} | ||
/> | ||
))} | ||
{currentColumns.length === 0 && ( | ||
<GridColumnsManagementEmptyText ownerState={rootProps}> | ||
{apiRef.current.getLocaleText('columnsManagementNoColumns')} | ||
</GridColumnsManagementEmptyText> | ||
)} | ||
</GridColumnsManagementBody> | ||
{(!disableShowHideToggle || !disableResetButton) && currentColumns.length > 0 ? ( | ||
<GridColumnsManagementScrollArea ownerState={rootProps}> | ||
<GridColumnsManagementBody className={classes.root} ownerState={rootProps}> | ||
{currentColumns.map((column) => ( | ||
<rootProps.slots.baseCheckbox | ||
key={column.field} | ||
className={classes.row} | ||
disabled={column.hideable === false} | ||
checked={columnVisibilityModel[column.field] !== false} | ||
onClick={toggleColumn} | ||
name={column.field} | ||
inputRef={isFirstHideableColumn(column) ? firstSwitchRef : undefined} | ||
label={column.headerName || column.field} | ||
density="compact" | ||
fullWidth | ||
{...rootProps.slotProps?.baseCheckbox} | ||
/> | ||
))} | ||
{currentColumns.length === 0 && ( | ||
<GridColumnsManagementEmptyText ownerState={rootProps}> | ||
{apiRef.current.getLocaleText('columnsManagementNoColumns')} | ||
</GridColumnsManagementEmptyText> | ||
)} | ||
</GridColumnsManagementBody> | ||
</GridColumnsManagementScrollArea> | ||
{!disableShowHideToggle || !disableResetButton ? ( | ||
<GridColumnsManagementFooter ownerState={rootProps} className={classes.footer}> | ||
{!disableShowHideToggle ? ( | ||
<rootProps.slots.baseCheckbox | ||
|
@@ -304,6 +309,7 @@ function GridColumnsManagement(props: GridColumnsManagementProps) { | |
onClick={() => toggleAllColumns(!allHideableColumnsVisible)} | ||
name={apiRef.current.getLocaleText('columnsManagementShowHideAllText')} | ||
label={apiRef.current.getLocaleText('columnsManagementShowHideAllText')} | ||
density="compact" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to set it (and other styling like spacing etc) according to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potentially, yes. I am thinking about how the other UI elements should respond to the density setting but it's going to require a bit of experimentation. Will create a follow up issue to review this across the board. |
||
{...rootProps.slotProps?.baseCheckbox} | ||
/> | ||
) : ( | ||
|
@@ -422,29 +428,30 @@ const GridColumnsManagementBody = styled('div', { | |
name: 'MuiDataGrid', | ||
slot: 'ColumnsManagement', | ||
})<{ ownerState: OwnerState }>({ | ||
padding: vars.spacing(0, 2, 1.5), | ||
display: 'flex', | ||
flexDirection: 'column', | ||
overflow: 'auto', | ||
flex: '1 1', | ||
padding: vars.spacing(0.5, 1.5), | ||
}); | ||
|
||
const GridColumnsManagementScrollArea = styled(GridShadowScrollArea, { | ||
name: 'MuiDataGrid', | ||
slot: 'ColumnsManagementScrollArea', | ||
})<{ ownerState: OwnerState }>({ | ||
maxHeight: 400, | ||
alignItems: 'flex-start', | ||
}); | ||
|
||
const GridColumnsManagementHeader = styled('div', { | ||
name: 'MuiDataGrid', | ||
slot: 'ColumnsManagementHeader', | ||
})<{ ownerState: OwnerState }>({ | ||
padding: vars.spacing(1.5, 3), | ||
padding: vars.spacing(1.5, 2), | ||
borderBottom: `1px solid ${vars.colors.border.base}`, | ||
}); | ||
|
||
const SearchInput = styled(NotRendered<GridSlotProps['baseTextField']>, { | ||
name: 'MuiDataGrid', | ||
slot: 'ColumnsManagementSearchInput', | ||
})<{ ownerState: OwnerState }>({ | ||
[`& .${inputBaseClasses.root}`]: { | ||
padding: vars.spacing(0, 1.5, 0, 1.5), | ||
}, | ||
[`& .${inputBaseClasses.input}::-webkit-search-decoration, | ||
& .${inputBaseClasses.input}::-webkit-search-cancel-button, | ||
& .${inputBaseClasses.input}::-webkit-search-results-button, | ||
|
@@ -458,15 +465,19 @@ const GridColumnsManagementFooter = styled('div', { | |
name: 'MuiDataGrid', | ||
slot: 'ColumnsManagementFooter', | ||
})<{ ownerState: OwnerState }>({ | ||
padding: vars.spacing(0.5, 1, 0.5, 3), | ||
padding: vars.spacing(1, 1, 1, 1.5), | ||
display: 'flex', | ||
justifyContent: 'space-between', | ||
borderTop: `1px solid ${vars.colors.border.base}`, | ||
}); | ||
|
||
const GridColumnsManagementEmptyText = styled('div')<{ ownerState: OwnerState }>({ | ||
padding: vars.spacing(0.5, 0), | ||
color: vars.colors.foreground.muted, | ||
const GridColumnsManagementEmptyText = styled('div', { | ||
name: 'MuiDataGrid', | ||
slot: 'ColumnsManagementEmptyText', | ||
})<{ ownerState: OwnerState }>({ | ||
padding: vars.spacing(1, 0), | ||
alignSelf: 'center', | ||
fontSize: vars.typography.body.fontSize, | ||
}); | ||
|
||
export { GridColumnsManagement }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uses
animation-timeline
to show scroll shadows.The support for animation-timeline isn't the best, but these scroll shadows can be a progressive enhancement as they are only a minor UX improvement. Not worth implementing with JS.