From 5242c9ae712f1f303c1e30122c1d1585766efe04 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Mon, 27 May 2024 16:01:14 +0100 Subject: [PATCH 01/29] [DataGrid] Add GridSkeletonLoadingOverlay --- .../components/GridSkeletonLoadingOverlay.tsx | 151 ++++++++++++++++++ .../src/components/cell/GridSkeletonCell.tsx | 6 +- packages/x-data-grid/src/components/index.ts | 1 + packages/x-data-grid/src/utils/utils.ts | 4 +- 4 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx new file mode 100644 index 000000000000..3bf61c51dcf9 --- /dev/null +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -0,0 +1,151 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import Skeleton from '@mui/material/Skeleton'; + +import { styled } from '@mui/system'; +import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; +import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { gridColumnPositionsSelector, gridColumnsTotalWidthSelector } from '../hooks'; +import { GridColType, GridEventListener } from '../models'; +import { seededRandomNumberGenerator } from '../utils/utils'; + +const DEFAULT_COLUMN_WIDTH_RANGE = [40, 80] as const; + +const COLUMN_WIDTH_RANGE_BY_TYPE: Partial> = { + number: [40, 60], + string: [40, 80], + date: [40, 60], + dateTime: [60, 80], + singleSelect: [40, 80], +} as const; + +const colWidthVar = (index: number) => `--colWidth-${index}`; + +const SkeletonCell = styled('div')({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + borderBottom: '1px solid var(--DataGrid-rowBorderColor)', +}); + +const StyledGridOverlay = styled(GridOverlay)({ + display: 'grid', + overflow: 'hidden', + placeItems: 'initial', + justifyContent: 'initial', + alignItems: 'initial', +}); + +const GridSkeletonLoadingOverlay = React.forwardRef( + function GridSkeletonLoadingOverlay(props, forwardedRef) { + const ref = React.useRef(null); + const handleRef = useForkRef(ref, forwardedRef); + + const apiRef = useGridApiContext(); + + const dimensions = apiRef.current?.getRootDimensions(); + const viewportHeight = dimensions?.viewportInnerSize.height ?? 0; + + // @ts-expect-error Function signature expects to be called with parameters, but the implementation suggests otherwise + const rowHeight = apiRef.current.unstable_getRowHeight(); + const skeletonRowsCount = Math.ceil(viewportHeight / rowHeight); + + const totalWidth = gridColumnsTotalWidthSelector(apiRef); + const positions = gridColumnPositionsSelector(apiRef); + const inViewportCount = React.useMemo( + () => positions.filter((value) => value <= totalWidth).length, + [totalWidth, positions], + ); + const columns = apiRef.current.getVisibleColumns().slice(0, inViewportCount); + + const children = React.useMemo(() => { + // reseed random number generator to create stable lines betwen renders + const array: React.ReactNode[] = []; + const randomNumberBetween = seededRandomNumberGenerator(12345); + + for (let i = 0; i < skeletonRowsCount; i += 1) { + // eslint-disable-next-line no-restricted-syntax + for (const column of columns) { + const [min, max] = column.type + ? COLUMN_WIDTH_RANGE_BY_TYPE[column.type] ?? DEFAULT_COLUMN_WIDTH_RANGE + : DEFAULT_COLUMN_WIDTH_RANGE; + const width = Math.round(randomNumberBetween(min, max)); + const isCircular = column.type === 'boolean' || column.type === 'actions'; + + array.push( + + + , + ); + } + array.push(); + } + return array; + }, [skeletonRowsCount, columns]); + + const [initialColWidthVariables, gridTemplateColumns] = columns.reduce( + ([initialSize, templateColumn], column, i) => { + const varName = colWidthVar(i); + initialSize[varName] = `${column.computedWidth}px`; + templateColumn += ` var(${varName})`; + return [initialSize, templateColumn]; + }, + [{} as Record, ''], + ); + + React.useEffect(() => { + const handleScrollChange: GridEventListener<'scrollPositionChange'> = (params) => { + if (ref.current) { + ref.current.scrollLeft = params.left; + } + }; + return apiRef.current.subscribeEvent('scrollPositionChange', handleScrollChange); + }, [apiRef]); + + React.useEffect(() => { + const handleScrollChange: GridEventListener<'columnResize'> = (params) => { + const columnIndex = columns.findIndex((column) => column.field === params.colDef.field); + ref.current?.style.setProperty(colWidthVar(columnIndex), `${params.width}px`); + }; + return apiRef.current.subscribeEvent('columnResize', handleScrollChange); + }, [apiRef, columns]); + + return ( + + {children} + + ); + }, +); + +GridSkeletonLoadingOverlay.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export { GridSkeletonLoadingOverlay }; diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx index fef4188a67f0..b64d6b0aacd7 100644 --- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx @@ -6,12 +6,12 @@ import { unstable_capitalize as capitalize, } from '@mui/utils'; import { fastMemo } from '../../utils/fastMemo'; -import { randomNumberBetween } from '../../utils/utils'; +import { seededRandomNumberGenerator } from '../../utils/utils'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; -const randomWidth = randomNumberBetween(10000, 20, 80); +const randomWidth = seededRandomNumberGenerator(10000); export interface GridSkeletonCellProps { width: number; @@ -39,7 +39,7 @@ function GridSkeletonCell(props: React.HTMLAttributes & GridSkel const rootProps = useGridRootProps(); const ownerState = { classes: rootProps.classes, align }; const classes = useUtilityClasses(ownerState); - const contentWidth = Math.round(randomWidth()); + const contentWidth = Math.round(randomWidth(20, 80)); return (
diff --git a/packages/x-data-grid/src/components/index.ts b/packages/x-data-grid/src/components/index.ts index 2657dac0cd89..578d34492989 100644 --- a/packages/x-data-grid/src/components/index.ts +++ b/packages/x-data-grid/src/components/index.ts @@ -18,3 +18,4 @@ export { GridPagination } from './GridPagination'; export * from './GridRowCount'; export * from './GridRow'; export * from './GridSelectedRowCount'; +export * from './GridSkeletonLoadingOverlay'; diff --git a/packages/x-data-grid/src/utils/utils.ts b/packages/x-data-grid/src/utils/utils.ts index cc682162e59f..16fc3d81c715 100644 --- a/packages/x-data-grid/src/utils/utils.ts +++ b/packages/x-data-grid/src/utils/utils.ts @@ -190,9 +190,9 @@ function mulberry32(a: number): () => number { }; } -export function randomNumberBetween(seed: number, min: number, max: number): () => number { +export function seededRandomNumberGenerator(seed: number): (min: number, max: number) => number { const random = mulberry32(seed); - return () => min + (max - min) * random(); + return (min: number, max: number) => min + (max - min) * random(); } export function deepClone(obj: Record) { From a2b1bb03d6722495cb1b89253c1b338320087c5f Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 29 May 2024 11:12:40 +0100 Subject: [PATCH 02/29] allow users to set a variant and noRowsVariant through loadingOverlay slotProps --- .../src/components/GridLoadingOverlay.tsx | 57 ++++- .../components/GridSkeletonLoadingOverlay.tsx | 222 ++++++++---------- .../src/models/gridSlotsComponentsProps.ts | 3 +- 3 files changed, 158 insertions(+), 124 deletions(-) diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx index 2bcb4eed7a89..d4b5fd83b84e 100644 --- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx @@ -1,13 +1,52 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import LinearProgress from '@mui/material/LinearProgress'; import CircularProgress from '@mui/material/CircularProgress'; import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; +import { GridSkeletonLoadingOverlay } from './GridSkeletonLoadingOverlay'; +import { useGridApiContext } from '../hooks/utils/useGridApiContext'; + +type GridLoadingOverlayVariant = 'circular-progress' | 'linear-progress' | 'skeleton'; + +export interface GridLoadingOverlayProps extends GridOverlayProps { + /** + * The variant of the overlay. + * @default 'circular-progress' + */ + variant?: GridLoadingOverlayVariant; + /** + * The variant of the overlay when no rows are displayed. + * @default 'circular-progress' + */ + noRowsVariant?: GridLoadingOverlayVariant; +} + +const LOADING_COMPONENTS: Record = { + 'circular-progress': CircularProgress, + 'linear-progress': LinearProgress, + skeleton: GridSkeletonLoadingOverlay, +}; + +const GridLoadingOverlay = React.forwardRef( + function GridLoadingOverlay( + { variant = 'circular-progress', noRowsVariant = 'circular-progress', sx, ...props }, + ref, + ) { + const apiRef = useGridApiContext(); + const rowsCount = apiRef.current.getRowsCount(); + const loadingVariant = rowsCount === 0 ? noRowsVariant : variant; + const LoadingComponent = LOADING_COMPONENTS[loadingVariant]; -const GridLoadingOverlay = React.forwardRef( - function GridLoadingOverlay(props, ref) { return ( - - + + ); }, @@ -18,11 +57,21 @@ GridLoadingOverlay.propTypes = { // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- + /** + * The variant of the overlay when no rows are displayed. + * @default 'circular-progress' + */ + noRowsVariant: PropTypes.oneOf(['circular-progress', 'linear-progress', 'skeleton']), sx: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object, ]), + /** + * The variant of the overlay. + * @default 'circular-progress' + */ + variant: PropTypes.oneOf(['circular-progress', 'linear-progress', 'skeleton']), } as any; export { GridLoadingOverlay }; diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 3bf61c51dcf9..5545e033efbf 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -1,10 +1,8 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import Skeleton from '@mui/material/Skeleton'; import { styled } from '@mui/system'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { gridColumnPositionsSelector, gridColumnsTotalWidthSelector } from '../hooks'; import { GridColType, GridEventListener } from '../models'; @@ -22,6 +20,11 @@ const COLUMN_WIDTH_RANGE_BY_TYPE: Partial> const colWidthVar = (index: number) => `--colWidth-${index}`; +const SkeletonOverlay = styled('div')({ + display: 'grid', + overflow: 'hidden', +}); + const SkeletonCell = styled('div')({ display: 'flex', flexDirection: 'row', @@ -29,123 +32,104 @@ const SkeletonCell = styled('div')({ borderBottom: '1px solid var(--DataGrid-rowBorderColor)', }); -const StyledGridOverlay = styled(GridOverlay)({ - display: 'grid', - overflow: 'hidden', - placeItems: 'initial', - justifyContent: 'initial', - alignItems: 'initial', -}); - -const GridSkeletonLoadingOverlay = React.forwardRef( - function GridSkeletonLoadingOverlay(props, forwardedRef) { - const ref = React.useRef(null); - const handleRef = useForkRef(ref, forwardedRef); - - const apiRef = useGridApiContext(); - - const dimensions = apiRef.current?.getRootDimensions(); - const viewportHeight = dimensions?.viewportInnerSize.height ?? 0; - - // @ts-expect-error Function signature expects to be called with parameters, but the implementation suggests otherwise - const rowHeight = apiRef.current.unstable_getRowHeight(); - const skeletonRowsCount = Math.ceil(viewportHeight / rowHeight); - - const totalWidth = gridColumnsTotalWidthSelector(apiRef); - const positions = gridColumnPositionsSelector(apiRef); - const inViewportCount = React.useMemo( - () => positions.filter((value) => value <= totalWidth).length, - [totalWidth, positions], - ); - const columns = apiRef.current.getVisibleColumns().slice(0, inViewportCount); - - const children = React.useMemo(() => { - // reseed random number generator to create stable lines betwen renders - const array: React.ReactNode[] = []; - const randomNumberBetween = seededRandomNumberGenerator(12345); - - for (let i = 0; i < skeletonRowsCount; i += 1) { - // eslint-disable-next-line no-restricted-syntax - for (const column of columns) { - const [min, max] = column.type - ? COLUMN_WIDTH_RANGE_BY_TYPE[column.type] ?? DEFAULT_COLUMN_WIDTH_RANGE - : DEFAULT_COLUMN_WIDTH_RANGE; - const width = Math.round(randomNumberBetween(min, max)); - const isCircular = column.type === 'boolean' || column.type === 'actions'; - - array.push( - - - , - ); - } - array.push(); +const GridSkeletonLoadingOverlay = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(function GridSkeletonLoadingOverlay(props, forwardedRef) { + const ref = React.useRef(null); + const handleRef = useForkRef(ref, forwardedRef); + + const apiRef = useGridApiContext(); + + const dimensions = apiRef.current?.getRootDimensions(); + const viewportHeight = dimensions?.viewportInnerSize.height ?? 0; + + // @ts-expect-error Function signature expects to be called with parameters, but the implementation suggests otherwise + const rowHeight = apiRef.current.unstable_getRowHeight(); + const skeletonRowsCount = Math.ceil(viewportHeight / rowHeight); + + const totalWidth = gridColumnsTotalWidthSelector(apiRef); + const positions = gridColumnPositionsSelector(apiRef); + const inViewportCount = React.useMemo( + () => positions.filter((value) => value <= totalWidth).length, + [totalWidth, positions], + ); + const columns = apiRef.current.getVisibleColumns().slice(0, inViewportCount); + + const children = React.useMemo(() => { + // reseed random number generator to create stable lines betwen renders + const array: React.ReactNode[] = []; + const randomNumberBetween = seededRandomNumberGenerator(12345); + + for (let i = 0; i < skeletonRowsCount; i += 1) { + // eslint-disable-next-line no-restricted-syntax + for (const column of columns) { + const [min, max] = column.type + ? COLUMN_WIDTH_RANGE_BY_TYPE[column.type] ?? DEFAULT_COLUMN_WIDTH_RANGE + : DEFAULT_COLUMN_WIDTH_RANGE; + const width = Math.round(randomNumberBetween(min, max)); + const isCircular = column.type === 'boolean' || column.type === 'actions'; + + array.push( + + + , + ); } - return array; - }, [skeletonRowsCount, columns]); - - const [initialColWidthVariables, gridTemplateColumns] = columns.reduce( - ([initialSize, templateColumn], column, i) => { - const varName = colWidthVar(i); - initialSize[varName] = `${column.computedWidth}px`; - templateColumn += ` var(${varName})`; - return [initialSize, templateColumn]; - }, - [{} as Record, ''], - ); - - React.useEffect(() => { - const handleScrollChange: GridEventListener<'scrollPositionChange'> = (params) => { - if (ref.current) { - ref.current.scrollLeft = params.left; - } - }; - return apiRef.current.subscribeEvent('scrollPositionChange', handleScrollChange); - }, [apiRef]); - - React.useEffect(() => { - const handleScrollChange: GridEventListener<'columnResize'> = (params) => { - const columnIndex = columns.findIndex((column) => column.field === params.colDef.field); - ref.current?.style.setProperty(colWidthVar(columnIndex), `${params.width}px`); - }; - return apiRef.current.subscribeEvent('columnResize', handleScrollChange); - }, [apiRef, columns]); - - return ( - - {children} - - ); - }, -); - -GridSkeletonLoadingOverlay.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- - sx: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), - PropTypes.func, - PropTypes.object, - ]), -} as any; + array.push(); + } + return array; + }, [skeletonRowsCount, columns]); + + const [initialColWidthVariables, gridTemplateColumns] = columns.reduce( + ([initialSize, templateColumn], column, i) => { + const varName = colWidthVar(i); + initialSize[varName] = `${column.computedWidth}px`; + templateColumn += ` var(${varName})`; + return [initialSize, templateColumn]; + }, + [{} as Record, ''], + ); + + React.useEffect(() => { + const handleScrollChange: GridEventListener<'scrollPositionChange'> = (params) => { + if (ref.current) { + ref.current.scrollLeft = params.left; + } + }; + return apiRef.current.subscribeEvent('scrollPositionChange', handleScrollChange); + }, [apiRef]); + + React.useEffect(() => { + const handleScrollChange: GridEventListener<'columnResize'> = (params) => { + const columnIndex = columns.findIndex((column) => column.field === params.colDef.field); + ref.current?.style.setProperty(colWidthVar(columnIndex), `${params.width}px`); + }; + return apiRef.current.subscribeEvent('columnResize', handleScrollChange); + }, [apiRef, columns]); + + return ( + + {children} + + ); +}); export { GridSkeletonLoadingOverlay }; diff --git a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts index 6623ae5d4483..3684059f5c8e 100644 --- a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts +++ b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts @@ -27,6 +27,7 @@ import type { GridColumnHeadersProps } from '../components/GridColumnHeaders'; import type { GridDetailPanelsProps } from '../components/GridDetailPanels'; import type { GridPinnedRowsProps } from '../components/GridPinnedRows'; import type { GridColumnsManagementProps } from '../components/columnsManagement/GridColumnsManagement'; +import type { GridLoadingOverlayProps } from '../components/GridLoadingOverlay'; import type { GridRowCountProps } from '../components'; // Overrides for module augmentation @@ -91,7 +92,7 @@ export interface GridSlotProps { filterPanel: GridFilterPanelProps & FilterPanelPropsOverrides; footer: GridFooterContainerProps & FooterPropsOverrides; footerRowCount: GridRowCountProps & FooterRowCountOverrides; - loadingOverlay: GridOverlayProps & LoadingOverlayPropsOverrides; + loadingOverlay: GridLoadingOverlayProps & LoadingOverlayPropsOverrides; noResultsOverlay: GridOverlayProps & NoResultsOverlayPropsOverrides; noRowsOverlay: GridOverlayProps & NoRowsOverlayPropsOverrides; pagination: Partial & PaginationPropsOverrides; From d32fa8b1c45257fd054d67b96745f962686502b5 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 29 May 2024 14:27:11 +0100 Subject: [PATCH 03/29] fix selector usage --- .../components/GridSkeletonLoadingOverlay.tsx | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 5545e033efbf..bd85240b4d32 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -4,7 +4,13 @@ import Skeleton from '@mui/material/Skeleton'; import { styled } from '@mui/system'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; -import { gridColumnPositionsSelector, gridColumnsTotalWidthSelector } from '../hooks'; +import { + gridColumnPositionsSelector, + gridColumnsTotalWidthSelector, + gridDimensionsSelector, + gridVisibleColumnDefinitionsSelector, + useGridSelector, +} from '../hooks'; import { GridColType, GridEventListener } from '../models'; import { seededRandomNumberGenerator } from '../utils/utils'; @@ -41,20 +47,19 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const apiRef = useGridApiContext(); - const dimensions = apiRef.current?.getRootDimensions(); + const dimensions = useGridSelector(apiRef, gridDimensionsSelector); const viewportHeight = dimensions?.viewportInnerSize.height ?? 0; - // @ts-expect-error Function signature expects to be called with parameters, but the implementation suggests otherwise - const rowHeight = apiRef.current.unstable_getRowHeight(); - const skeletonRowsCount = Math.ceil(viewportHeight / rowHeight); + const skeletonRowsCount = Math.ceil(viewportHeight / dimensions.rowHeight); - const totalWidth = gridColumnsTotalWidthSelector(apiRef); - const positions = gridColumnPositionsSelector(apiRef); + const totalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); + const positions = useGridSelector(apiRef, gridColumnPositionsSelector); const inViewportCount = React.useMemo( () => positions.filter((value) => value <= totalWidth).length, [totalWidth, positions], ); - const columns = apiRef.current.getVisibleColumns().slice(0, inViewportCount); + const allVisibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); + const columns = allVisibleColumns.slice(0, inViewportCount); const children = React.useMemo(() => { // reseed random number generator to create stable lines betwen renders @@ -122,7 +127,7 @@ const GridSkeletonLoadingOverlay = React.forwardRef< {...props} style={{ gridTemplateColumns: `${gridTemplateColumns} 1fr`, - gridAutoRows: rowHeight, + gridAutoRows: dimensions.rowHeight, ...initialColWidthVariables, ...props.style, }} From d45dfddcd90f0b572d548992a8ae2e6a22884882 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 29 May 2024 14:39:23 +0100 Subject: [PATCH 04/29] use useGridApiEventHandler for event subscriptions --- .../components/GridSkeletonLoadingOverlay.tsx | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index bd85240b4d32..9923cf372297 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -9,6 +9,7 @@ import { gridColumnsTotalWidthSelector, gridDimensionsSelector, gridVisibleColumnDefinitionsSelector, + useGridApiEventHandler, useGridSelector, } from '../hooks'; import { GridColType, GridEventListener } from '../models'; @@ -104,22 +105,20 @@ const GridSkeletonLoadingOverlay = React.forwardRef< [{} as Record, ''], ); - React.useEffect(() => { - const handleScrollChange: GridEventListener<'scrollPositionChange'> = (params) => { - if (ref.current) { - ref.current.scrollLeft = params.left; - } - }; - return apiRef.current.subscribeEvent('scrollPositionChange', handleScrollChange); - }, [apiRef]); - - React.useEffect(() => { - const handleScrollChange: GridEventListener<'columnResize'> = (params) => { - const columnIndex = columns.findIndex((column) => column.field === params.colDef.field); - ref.current?.style.setProperty(colWidthVar(columnIndex), `${params.width}px`); - }; - return apiRef.current.subscribeEvent('columnResize', handleScrollChange); - }, [apiRef, columns]); + // Sync the horizontal scroll of the overlay with the grid + const handleScrollChange: GridEventListener<'scrollPositionChange'> = (params) => { + if (ref.current) { + ref.current.scrollLeft = params.left; + } + }; + useGridApiEventHandler(apiRef, 'scrollPositionChange', handleScrollChange); + + // Sync the column resize of the overlay columns with the grid + const handleColumnResize: GridEventListener<'columnResize'> = (params) => { + const columnIndex = columns.findIndex((column) => column.field === params.colDef.field); + ref.current?.style.setProperty(colWidthVar(columnIndex), `${params.width}px`); + }; + useGridApiEventHandler(apiRef, 'columnResize', handleColumnResize); return ( Date: Wed, 29 May 2024 14:46:13 +0100 Subject: [PATCH 05/29] remove skeleton export --- packages/x-data-grid/src/components/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid/src/components/index.ts b/packages/x-data-grid/src/components/index.ts index 578d34492989..2657dac0cd89 100644 --- a/packages/x-data-grid/src/components/index.ts +++ b/packages/x-data-grid/src/components/index.ts @@ -18,4 +18,3 @@ export { GridPagination } from './GridPagination'; export * from './GridRowCount'; export * from './GridRow'; export * from './GridSelectedRowCount'; -export * from './GridSkeletonLoadingOverlay'; From e05cf1d52fceaefe300eee4dc95567af5f71c3ee Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Thu, 30 May 2024 10:25:13 +0100 Subject: [PATCH 06/29] apply solid background colour to skeleton overlay --- .../src/components/GridLoadingOverlay.tsx | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx index d4b5fd83b84e..cb0f5e8a6ef2 100644 --- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import LinearProgress from '@mui/material/LinearProgress'; import CircularProgress from '@mui/material/CircularProgress'; +import { Theme, SystemStyleObject } from '@mui/system'; import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; import { GridSkeletonLoadingOverlay } from './GridSkeletonLoadingOverlay'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; @@ -21,10 +22,28 @@ export interface GridLoadingOverlayProps extends GridOverlayProps { noRowsVariant?: GridLoadingOverlayVariant; } -const LOADING_COMPONENTS: Record = { - 'circular-progress': CircularProgress, - 'linear-progress': LinearProgress, - skeleton: GridSkeletonLoadingOverlay, +const LOADING_VARIANTS: Record< + GridLoadingOverlayVariant, + { + component: React.ComponentType; + sx: SystemStyleObject; + } +> = { + 'circular-progress': { + component: CircularProgress, + sx: {}, + }, + 'linear-progress': { + component: LinearProgress, + sx: { display: 'block' }, + }, + skeleton: { + component: GridSkeletonLoadingOverlay, + sx: { + display: 'block', + background: 'var(--DataGrid-containerBackground)', + }, + }, }; const GridLoadingOverlay = React.forwardRef( @@ -34,19 +53,11 @@ const GridLoadingOverlay = React.forwardRef - + + ); }, From 4373460f0851931c966c7d43825b489533edb132 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Thu, 30 May 2024 11:08:48 +0100 Subject: [PATCH 07/29] rename random number generator function and expand on comment --- .../src/components/GridSkeletonLoadingOverlay.tsx | 8 +++++--- .../x-data-grid/src/components/cell/GridSkeletonCell.tsx | 4 ++-- packages/x-data-grid/src/utils/utils.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 9923cf372297..278eff9c0367 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -13,7 +13,7 @@ import { useGridSelector, } from '../hooks'; import { GridColType, GridEventListener } from '../models'; -import { seededRandomNumberGenerator } from '../utils/utils'; +import { createRandomNumberGenerator } from '../utils/utils'; const DEFAULT_COLUMN_WIDTH_RANGE = [40, 80] as const; @@ -63,9 +63,11 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const columns = allVisibleColumns.slice(0, inViewportCount); const children = React.useMemo(() => { - // reseed random number generator to create stable lines betwen renders + // We use a seeded random number generator to determine the width of each skeleton element. + // The seed ensures that the random number generator produces the same sequence of 'random' numbers on every render. + // It prevents the skeleton overlay from appearing to flicker when the component re-renders. + const randomNumberBetween = createRandomNumberGenerator(12345); const array: React.ReactNode[] = []; - const randomNumberBetween = seededRandomNumberGenerator(12345); for (let i = 0; i < skeletonRowsCount; i += 1) { // eslint-disable-next-line no-restricted-syntax diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx index b64d6b0aacd7..8220d3a0a29c 100644 --- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx @@ -6,12 +6,12 @@ import { unstable_capitalize as capitalize, } from '@mui/utils'; import { fastMemo } from '../../utils/fastMemo'; -import { seededRandomNumberGenerator } from '../../utils/utils'; +import { createRandomNumberGenerator } from '../../utils/utils'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; -const randomWidth = seededRandomNumberGenerator(10000); +const randomWidth = createRandomNumberGenerator(10000); export interface GridSkeletonCellProps { width: number; diff --git a/packages/x-data-grid/src/utils/utils.ts b/packages/x-data-grid/src/utils/utils.ts index 16fc3d81c715..0aa1bd32980e 100644 --- a/packages/x-data-grid/src/utils/utils.ts +++ b/packages/x-data-grid/src/utils/utils.ts @@ -190,7 +190,7 @@ function mulberry32(a: number): () => number { }; } -export function seededRandomNumberGenerator(seed: number): (min: number, max: number) => number { +export function createRandomNumberGenerator(seed: number): (min: number, max: number) => number { const random = mulberry32(seed); return (min: number, max: number) => min + (max - min) * random(); } From f9d5442bd7735012e187a2b9412337f845848db4 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Fri, 31 May 2024 08:57:47 +0100 Subject: [PATCH 08/29] Update packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx Co-authored-by: Olivier Tassinari Signed-off-by: Kenan Yusuf --- .../x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 278eff9c0367..a12a2742e4f8 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import Skeleton from '@mui/material/Skeleton'; - import { styled } from '@mui/system'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; From d745a3ec8d41a7e4153400350ad80ce30b66ee26 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Fri, 31 May 2024 09:08:03 +0100 Subject: [PATCH 09/29] update GridLoadingOverlay to use props destructuring convention --- .../src/components/GridLoadingOverlay.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx index cb0f5e8a6ef2..8f6b85539fd2 100644 --- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx @@ -47,16 +47,19 @@ const LOADING_VARIANTS: Record< }; const GridLoadingOverlay = React.forwardRef( - function GridLoadingOverlay( - { variant = 'circular-progress', noRowsVariant = 'circular-progress', sx, ...props }, - ref, - ) { + function GridLoadingOverlay(props, ref) { + const { + variant = 'circular-progress', + noRowsVariant = 'circular-progress', + sx, + ...other + } = props; const apiRef = useGridApiContext(); const rowsCount = apiRef.current.getRowsCount(); const activeVariant = LOADING_VARIANTS[rowsCount === 0 ? noRowsVariant : variant]; return ( - + ); From 6cf0ffa6d8a5eb177d9b01cea2539a9212fa288e Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Fri, 31 May 2024 09:51:12 +0100 Subject: [PATCH 10/29] update RNG comments --- .../src/components/GridSkeletonLoadingOverlay.tsx | 5 ++--- packages/x-data-grid/src/utils/utils.ts | 7 +++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index a12a2742e4f8..0a4bba8f4908 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -62,9 +62,8 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const columns = allVisibleColumns.slice(0, inViewportCount); const children = React.useMemo(() => { - // We use a seeded random number generator to determine the width of each skeleton element. - // The seed ensures that the random number generator produces the same sequence of 'random' numbers on every render. - // It prevents the skeleton overlay from appearing to flicker when the component re-renders. + // The random number generator is used to determine the width of each skeleton element. + // The seed ensures the width of each skeleton element width remains the same across renders and does not flicker. const randomNumberBetween = createRandomNumberGenerator(12345); const array: React.ReactNode[] = []; diff --git a/packages/x-data-grid/src/utils/utils.ts b/packages/x-data-grid/src/utils/utils.ts index 0aa1bd32980e..097ed683d9b2 100644 --- a/packages/x-data-grid/src/utils/utils.ts +++ b/packages/x-data-grid/src/utils/utils.ts @@ -190,6 +190,13 @@ function mulberry32(a: number): () => number { }; } +/** + * Create a random number generator from a seed. The seed + * ensures that the random number generator produces the + * same sequence of 'random' numbers on every render. It + * returns a function that generates a random number between + * a specified min and max. + */ export function createRandomNumberGenerator(seed: number): (min: number, max: number) => number { const random = mulberry32(seed); return (min: number, max: number) => min + (max - min) * random(); From e91886f1108ca7dad0f2f8abee88ee24585d2380 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Fri, 31 May 2024 09:53:16 +0100 Subject: [PATCH 11/29] Update packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx Signed-off-by: Kenan Yusuf --- .../x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 0a4bba8f4908..cd8f2c951a69 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -63,7 +63,7 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const children = React.useMemo(() => { // The random number generator is used to determine the width of each skeleton element. - // The seed ensures the width of each skeleton element width remains the same across renders and does not flicker. + // The seed ensures the width of each skeleton element remains the same across renders and does not flicker. const randomNumberBetween = createRandomNumberGenerator(12345); const array: React.ReactNode[] = []; From 721e78a55ba31395fdfb1de83616bdcfa53fb9c2 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Fri, 31 May 2024 21:11:43 +0100 Subject: [PATCH 12/29] reuse GridSkeletonCell component in skeleton loading overlay --- .../x-data-grid/src/components/GridRow.tsx | 3 +- .../components/GridSkeletonLoadingOverlay.tsx | 80 +++++++++--------- .../src/components/cell/GridSkeletonCell.tsx | 84 ++++++++++++++++--- 3 files changed, 111 insertions(+), 56 deletions(-) diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 5bcef3833555..8eca3254dcf8 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -383,10 +383,11 @@ const GridRow = React.forwardRef(function GridRow( return ( ); } diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index cd8f2c951a69..c17fa6b8a949 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -1,8 +1,11 @@ import * as React from 'react'; -import Skeleton from '@mui/material/Skeleton'; import { styled } from '@mui/system'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { + unstable_useForkRef as useForkRef, + unstable_composeClasses as composeClasses, +} from '@mui/utils'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { gridColumnPositionsSelector, gridColumnsTotalWidthSelector, @@ -11,37 +14,42 @@ import { useGridApiEventHandler, useGridSelector, } from '../hooks'; -import { GridColType, GridEventListener } from '../models'; -import { createRandomNumberGenerator } from '../utils/utils'; - -const DEFAULT_COLUMN_WIDTH_RANGE = [40, 80] as const; - -const COLUMN_WIDTH_RANGE_BY_TYPE: Partial> = { - number: [40, 60], - string: [40, 80], - date: [40, 60], - dateTime: [60, 80], - singleSelect: [40, 80], -} as const; +import { GridEventListener } from '../models'; +import { DataGridProcessedProps } from '../models/props/DataGridProps'; +import { getDataGridUtilityClass } from '../constants/gridClasses'; const colWidthVar = (index: number) => `--colWidth-${index}`; -const SkeletonOverlay = styled('div')({ +const SkeletonOverlay = styled('div', { + name: 'MuiDataGrid', + slot: 'SkeletonLoadingOverlay', + overridesResolver: (props, styles) => styles.skeletonLoadingOverlay, +})({ display: 'grid', overflow: 'hidden', + '& .MuiDataGrid-cellSkeleton': { + borderBottom: '1px solid var(--DataGrid-rowBorderColor)', + }, }); -const SkeletonCell = styled('div')({ - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - borderBottom: '1px solid var(--DataGrid-rowBorderColor)', -}); +type OwnerState = { classes: DataGridProcessedProps['classes'] }; + +const useUtilityClasses = (ownerState: OwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['skeletonLoadingOverlay'], + }; + + return composeClasses(slots, getDataGridUtilityClass, classes); +}; const GridSkeletonLoadingOverlay = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(function GridSkeletonLoadingOverlay(props, forwardedRef) { + const rootProps = useGridRootProps(); + const classes = useUtilityClasses({ ...props, classes: rootProps.classes }); const ref = React.useRef(null); const handleRef = useForkRef(ref, forwardedRef); @@ -61,39 +69,25 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const allVisibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); const columns = allVisibleColumns.slice(0, inViewportCount); + const { slots } = rootProps; const children = React.useMemo(() => { - // The random number generator is used to determine the width of each skeleton element. - // The seed ensures the width of each skeleton element remains the same across renders and does not flicker. - const randomNumberBetween = createRandomNumberGenerator(12345); const array: React.ReactNode[] = []; for (let i = 0; i < skeletonRowsCount; i += 1) { // eslint-disable-next-line no-restricted-syntax for (const column of columns) { - const [min, max] = column.type - ? COLUMN_WIDTH_RANGE_BY_TYPE[column.type] ?? DEFAULT_COLUMN_WIDTH_RANGE - : DEFAULT_COLUMN_WIDTH_RANGE; - const width = Math.round(randomNumberBetween(min, max)); - const isCircular = column.type === 'boolean' || column.type === 'actions'; - array.push( - - - , + type={column.type} + align={column.align} + />, ); } - array.push(); + array.push(); } return array; - }, [skeletonRowsCount, columns]); + }, [skeletonRowsCount, columns, slots]); const [initialColWidthVariables, gridTemplateColumns] = columns.reduce( ([initialSize, templateColumn], column, i) => { @@ -122,9 +116,11 @@ const GridSkeletonLoadingOverlay = React.forwardRef< return ( > = { + number: [40, 60], + string: [40, 80], + date: [40, 60], + dateTime: [60, 80], + singleSelect: [40, 80], +} as const; export interface GridSkeletonCellProps { - width: number; - height: number | 'auto'; - field: string; - align: string; + type?: GridColType; + width?: number; + height?: number | 'auto'; + field?: string; + align?: string; + /** + * If `true`, the cell will not display the skeleton but still reserve the cell space. + * @default false + */ + empty?: boolean; } type OwnerState = Pick & { @@ -28,22 +43,50 @@ const useUtilityClasses = (ownerState: OwnerState) => { const { align, classes } = ownerState; const slots = { - root: ['cell', 'cellSkeleton', `cell--text${capitalize(align)}`, 'withBorderColor'], + root: [ + 'cell', + 'cellSkeleton', + `cell--text${align ? capitalize(align) : 'Left'}`, + 'withBorderColor', + ], }; return composeClasses(slots, getDataGridUtilityClass, classes); }; +const randomNumberGenerator = createRandomNumberGenerator(12345); + function GridSkeletonCell(props: React.HTMLAttributes & GridSkeletonCellProps) { - const { field, align, width, height, ...other } = props; + const { field, type, align, width, height, empty = false, ...other } = props; const rootProps = useGridRootProps(); const ownerState = { classes: rootProps.classes, align }; const classes = useUtilityClasses(ownerState); - const contentWidth = Math.round(randomWidth(20, 80)); + + // The width of the skeleton is a random number between the min and max values + // The min and max values are determined by the type of the column + const [min, max] = type + ? CONTENT_WIDTH_RANGE_BY_TYPE[type] ?? DEFAULT_CONTENT_WIDTH_RANGE + : DEFAULT_CONTENT_WIDTH_RANGE; + + // Memo prevents the skeleton width changing to a random width on every render + const contentWidth = React.useMemo(() => Math.round(randomNumberGenerator(min, max)), [min, max]); + + const isCircularContent = type === 'boolean' || type === 'actions'; + const skeletonProps = isCircularContent + ? ({ + variant: 'circular', + width: '1.3em', + height: '1.3em', + } as const) + : ({ + variant: 'text', + width: `${contentWidth}%`, + height: '1.2em', + } as const); return (
- + {!empty && }
); } @@ -53,10 +96,25 @@ GridSkeletonCell.propTypes = { // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- - align: PropTypes.string.isRequired, - field: PropTypes.string.isRequired, - height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired, - width: PropTypes.number.isRequired, + align: PropTypes.string, + /** + * If `true`, the cell will not display the skeleton but still reserve the cell space. + * @default false + */ + empty: PropTypes.bool, + field: PropTypes.string, + height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]), + type: PropTypes.oneOf([ + 'actions', + 'boolean', + 'custom', + 'date', + 'dateTime', + 'number', + 'singleSelect', + 'string', + ]), + width: PropTypes.number, } as any; const Memoized = fastMemo(GridSkeletonCell); From ceb9c7e279436afabf509ee34ba8872f19b94c60 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Mon, 3 Jun 2024 15:57:49 +0100 Subject: [PATCH 13/29] make skeleton loader flow with scroll container --- .../src/components/GridLoadingOverlay.tsx | 7 +- .../components/GridSkeletonLoadingOverlay.tsx | 11 +-- .../src/components/base/GridOverlays.tsx | 87 ++++++++----------- .../components/containers/GridRootStyles.ts | 9 +- .../virtualization/GridVirtualScroller.tsx | 21 +++-- .../x-data-grid/src/constants/gridClasses.ts | 5 ++ .../features/overlays/useGridOverlays.ts | 47 ++++++++++ 7 files changed, 119 insertions(+), 68 deletions(-) create mode 100644 packages/x-data-grid/src/hooks/features/overlays/useGridOverlays.ts diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx index 8f6b85539fd2..717d02db7ea9 100644 --- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx @@ -7,7 +7,7 @@ import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; import { GridSkeletonLoadingOverlay } from './GridSkeletonLoadingOverlay'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; -type GridLoadingOverlayVariant = 'circular-progress' | 'linear-progress' | 'skeleton'; +export type GridLoadingOverlayVariant = 'circular-progress' | 'linear-progress' | 'skeleton'; export interface GridLoadingOverlayProps extends GridOverlayProps { /** @@ -39,10 +39,7 @@ const LOADING_VARIANTS: Record< }, skeleton: { component: GridSkeletonLoadingOverlay, - sx: { - display: 'block', - background: 'var(--DataGrid-containerBackground)', - }, + sx: { display: 'block' }, }, }; diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index c17fa6b8a949..0db11aef49bc 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -26,6 +26,9 @@ const SkeletonOverlay = styled('div', { overridesResolver: (props, styles) => styles.skeletonLoadingOverlay, })({ display: 'grid', + width: 'max-content', // ensures overflow: hidden; does not cut off the x axis + minWidth: '100%', // ensures the filler column takes up the remaining space in a row + height: '100%', overflow: 'hidden', '& .MuiDataGrid-cellSkeleton': { borderBottom: '1px solid var(--DataGrid-rowBorderColor)', @@ -99,14 +102,6 @@ const GridSkeletonLoadingOverlay = React.forwardRef< [{} as Record, ''], ); - // Sync the horizontal scroll of the overlay with the grid - const handleScrollChange: GridEventListener<'scrollPositionChange'> = (params) => { - if (ref.current) { - ref.current.scrollLeft = params.left; - } - }; - useGridApiEventHandler(apiRef, 'scrollPositionChange', handleScrollChange); - // Sync the column resize of the overlay columns with the grid const handleColumnResize: GridEventListener<'columnResize'> = (params) => { const columnIndex = columns.findIndex((column) => column.field === params.colDef.field); diff --git a/packages/x-data-grid/src/components/base/GridOverlays.tsx b/packages/x-data-grid/src/components/base/GridOverlays.tsx index 2e214bc219ff..2082045b3207 100644 --- a/packages/x-data-grid/src/components/base/GridOverlays.tsx +++ b/packages/x-data-grid/src/components/base/GridOverlays.tsx @@ -4,40 +4,47 @@ import { styled } from '@mui/system'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; import clsx from 'clsx'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; -import { gridExpandedRowCountSelector } from '../../hooks/features/filter/gridFilterSelector'; -import { - gridRowCountSelector, - gridRowsLoadingSelector, -} from '../../hooks/features/rows/gridRowsSelector'; import { gridDimensionsSelector } from '../../hooks/features/dimensions'; +import { GridOverlayType } from '../../hooks/features/overlays/useGridOverlays'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridVisibleRows } from '../../hooks/utils/useGridVisibleRows'; import { getMinimalContentHeight } from '../../hooks/features/rows/gridRowsUtils'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; +import { GridLoadingOverlayVariant } from '../GridLoadingOverlay'; + +interface GridOverlaysProps { + overlayType: GridOverlayType; + loadingOverlayVariant: GridLoadingOverlayVariant | null; +} const GridOverlayWrapperRoot = styled('div', { name: 'MuiDataGrid', slot: 'OverlayWrapper', - shouldForwardProp: (prop) => prop !== 'overlayType', + shouldForwardProp: (prop) => prop !== 'overlayType' && prop !== 'loadingOverlayVariant', overridesResolver: (props, styles) => styles.overlayWrapper, -})<{ overlayType: 'loadingOverlay' | string }>(({ overlayType }) => ({ - position: 'sticky', // To stay in place while scrolling - top: 'var(--DataGrid-headersTotalHeight)', - left: 0, - width: 0, // To stay above the content instead of shifting it down - height: 0, // To stay above the content instead of shifting it down - zIndex: - overlayType === 'loadingOverlay' - ? 5 // Should be above pinned columns, pinned rows, and detail panel - : 4, // Should be above pinned columns and detail panel -})); +})(({ overlayType, loadingOverlayVariant }) => + // Skeleton overlay should flow with the scroll container and not be sticky + loadingOverlayVariant !== 'skeleton' + ? { + position: 'sticky', // To stay in place while scrolling + top: 'var(--DataGrid-headersTotalHeight)', + left: 0, + width: 0, // To stay above the content instead of shifting it down + height: 0, // To stay above the content instead of shifting it down + zIndex: + overlayType === 'loadingOverlay' + ? 5 // Should be above pinned columns, pinned rows, and detail panel + : 4, // Should be above pinned columns and detail panel + } + : {}, +); const GridOverlayWrapperInner = styled('div', { name: 'MuiDataGrid', slot: 'OverlayWrapperInner', - shouldForwardProp: (prop) => prop !== 'overlayType', + shouldForwardProp: (prop) => prop !== 'overlayType' && prop !== 'loadingOverlayVariant', overridesResolver: (props, styles) => styles.overlayWrapperInner, })({}); @@ -54,7 +61,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { return composeClasses(slots, getDataGridUtilityClass, classes); }; -function GridOverlayWrapper(props: React.PropsWithChildren<{ overlayType: string }>) { +function GridOverlayWrapper(props: React.PropsWithChildren) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); const currentPage = useGridVisibleRows(apiRef, rootProps); @@ -72,7 +79,7 @@ function GridOverlayWrapper(props: React.PropsWithChildren<{ overlayType: string const classes = useUtilityClasses({ ...props, classes: rootProps.classes }); return ( - + 0 && visibleRowCount === 0; - - let overlay: React.JSX.Element | null = null; - let overlayType = ''; - - if (showNoRowsOverlay) { - overlay = ; - overlayType = 'noRowsOverlay'; - } - - if (showNoResultsOverlay) { - overlay = ; - overlayType = 'noResultsOverlay'; - } - - if (loading) { - overlay = ; - overlayType = 'loadingOverlay'; - } - - if (overlay === null) { + if (!overlayType) { return null; } - return {overlay}; + const Overlay = rootProps.slots?.[overlayType]; + const overlayProps = rootProps.slotProps?.[overlayType]; + + return ( + + + + ); } diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index 0d5e88de51f6..6c212b777c7d 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -145,7 +145,7 @@ export const GridRootStyles = styled('div', { const selectedHoverBackground = t.vars ? `rgba(${t.vars.palette.primary.mainChannel} / calc( - ${t.vars.palette.action.selectedOpacity} + + ${t.vars.palette.action.selectedOpacity} + ${t.vars.palette.action.hoverOpacity} ))` : alpha( @@ -651,6 +651,13 @@ export const GridRootStyles = styled('div', { [`& .${c['filler--borderTop']}`]: { borderTop: '1px solid var(--DataGrid-rowBorderColor)', }, + + /* Hide grid rows and vertical scrollbar when skeleton overlay is visible */ + [`& .${c['main--hasSkeletonLoadingOverlay']}`]: { + [`& .${c.virtualScrollerContent}, & .${c['scrollbar--vertical']}, & .${c.pinnedRows}`]: { + display: 'none', + }, + }, }; return gridStyle; diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx index aa17ed93be6d..42c2c2bf443a 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx @@ -9,7 +9,8 @@ import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { GridDimensions, gridDimensionsSelector } from '../../hooks/features/dimensions'; import { useGridVirtualScroller } from '../../hooks/features/virtualization/useGridVirtualScroller'; -import { GridOverlays } from '../base/GridOverlays'; +import { useGridOverlays } from '../../hooks/features/overlays/useGridOverlays'; +import { GridOverlays as Overlays } from '../base/GridOverlays'; import { GridHeaders } from '../GridHeaders'; import { GridMainContainer as Container } from './GridMainContainer'; import { GridTopContainer as TopContainer } from './GridTopContainer'; @@ -18,14 +19,23 @@ import { GridVirtualScrollerContent as Content } from './GridVirtualScrollerCont import { GridVirtualScrollerFiller as SpaceFiller } from './GridVirtualScrollerFiller'; import { GridVirtualScrollerRenderZone as RenderZone } from './GridVirtualScrollerRenderZone'; import { GridVirtualScrollbar as Scrollbar } from './GridVirtualScrollbar'; +import { GridLoadingOverlayVariant } from '../GridLoadingOverlay'; type OwnerState = DataGridProcessedProps; -const useUtilityClasses = (ownerState: OwnerState, dimensions: GridDimensions) => { +const useUtilityClasses = ( + ownerState: OwnerState, + dimensions: GridDimensions, + loadingOverlayVariant: GridLoadingOverlayVariant | null, +) => { const { classes } = ownerState; const slots = { - root: ['main', dimensions.rightPinnedWidth > 0 && 'main--hasPinnedRight'], + root: [ + 'main', + dimensions.rightPinnedWidth > 0 && 'main--hasPinnedRight', + loadingOverlayVariant === 'skeleton' && 'main--hasSkeletonLoadingOverlay', + ], scroller: ['virtualScroller'], }; @@ -61,7 +71,8 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - const classes = useUtilityClasses(rootProps, dimensions); + const overlaysProps = useGridOverlays(); + const classes = useUtilityClasses(rootProps, dimensions, overlaysProps.loadingOverlayVariant); const virtualScroller = useGridVirtualScroller(); const { @@ -86,7 +97,7 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) { - + diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts index c795ccda8336..a5d1b21dcb29 100644 --- a/packages/x-data-grid/src/constants/gridClasses.ts +++ b/packages/x-data-grid/src/constants/gridClasses.ts @@ -367,6 +367,10 @@ export interface GridClasses { * Styles applied to the main container element when it has right pinned columns. */ 'main--hasPinnedRight': string; + /** + * Styles applied to the main container element when it has an active skeleton loading overlay. + */ + 'main--hasSkeletonLoadingOverlay': string; /** * Styles applied to the menu element. */ @@ -701,6 +705,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'iconSeparator', 'main', 'main--hasPinnedRight', + 'main--hasSkeletonLoadingOverlay', 'menu', 'menuIcon', 'menuIconButton', diff --git a/packages/x-data-grid/src/hooks/features/overlays/useGridOverlays.ts b/packages/x-data-grid/src/hooks/features/overlays/useGridOverlays.ts new file mode 100644 index 000000000000..f3f9f3998423 --- /dev/null +++ b/packages/x-data-grid/src/hooks/features/overlays/useGridOverlays.ts @@ -0,0 +1,47 @@ +import { useGridSelector } from '../../utils'; +import { useGridApiContext } from '../../utils/useGridApiContext'; +import { useGridRootProps } from '../../utils/useGridRootProps'; +import { gridExpandedRowCountSelector } from '../filter'; +import { gridRowCountSelector, gridRowsLoadingSelector } from '../rows'; +import { GridLoadingOverlayVariant } from '../../../components/GridLoadingOverlay'; +import { GridSlotsComponent } from '../../../models/gridSlotsComponent'; + +export type GridOverlayType = + | keyof Pick + | null; + +/** + * Uses the grid state to determine which overlay to display. + * Returns the active overlay type and the active loading overlay variant. + */ +export const useGridOverlays = () => { + const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); + + const totalRowCount = useGridSelector(apiRef, gridRowCountSelector); + const visibleRowCount = useGridSelector(apiRef, gridExpandedRowCountSelector); + const noRows = totalRowCount === 0; + const loading = useGridSelector(apiRef, gridRowsLoadingSelector); + + const showNoRowsOverlay = !loading && noRows; + const showNoResultsOverlay = !loading && totalRowCount > 0 && visibleRowCount === 0; + + let overlayType: GridOverlayType = null; + let loadingOverlayVariant: GridLoadingOverlayVariant | null = null; + + if (showNoRowsOverlay) { + overlayType = 'noRowsOverlay'; + } + + if (showNoResultsOverlay) { + overlayType = 'noResultsOverlay'; + } + + if (loading) { + overlayType = 'loadingOverlay'; + loadingOverlayVariant = + rootProps.slotProps?.loadingOverlay?.[noRows ? 'noRowsVariant' : 'variant'] || null; + } + + return { overlayType, loadingOverlayVariant }; +}; From e98680ebdff3c49683772c7cfdae2d07df52fe90 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Mon, 3 Jun 2024 16:05:43 +0100 Subject: [PATCH 14/29] import syntax --- .../src/components/GridSkeletonLoadingOverlay.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 0db11aef49bc..005cf7c30ad5 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -1,9 +1,7 @@ import * as React from 'react'; import { styled } from '@mui/system'; -import { - unstable_useForkRef as useForkRef, - unstable_composeClasses as composeClasses, -} from '@mui/utils'; +import useForkRef from '@mui/utils/useForkRef'; +import composeClasses from '@mui/utils/composeClasses'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { From 09118fb619a9e4c66305471f353ae32e77e4326c Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Tue, 4 Jun 2024 09:14:11 +0100 Subject: [PATCH 15/29] update classes docs --- docs/pages/x/api/data-grid/data-grid-premium.json | 6 ++++++ docs/pages/x/api/data-grid/data-grid-pro.json | 6 ++++++ docs/pages/x/api/data-grid/data-grid.json | 6 ++++++ .../data-grid/data-grid-premium/data-grid-premium.json | 5 +++++ .../api-docs/data-grid/data-grid-pro/data-grid-pro.json | 5 +++++ .../api-docs/data-grid/data-grid/data-grid.json | 5 +++++ packages/x-data-grid/src/components/base/GridOverlays.tsx | 5 +++-- 7 files changed, 36 insertions(+), 2 deletions(-) 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 5800b14cc52f..aae9623f9b67 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -1548,6 +1548,12 @@ "description": "Styles applied to the main container element when it has right pinned columns.", "isGlobal": false }, + { + "key": "main--hasSkeletonLoadingOverlay", + "className": "MuiDataGridPremium-main--hasSkeletonLoadingOverlay", + "description": "Styles applied to the main container element when it has an active skeleton loading overlay.", + "isGlobal": false + }, { "key": "menu", "className": "MuiDataGridPremium-menu", 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 5e96f8adbe84..b6134018c09a 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -1465,6 +1465,12 @@ "description": "Styles applied to the main container element when it has right pinned columns.", "isGlobal": false }, + { + "key": "main--hasSkeletonLoadingOverlay", + "className": "MuiDataGridPro-main--hasSkeletonLoadingOverlay", + "description": "Styles applied to the main container element when it has an active skeleton loading overlay.", + "isGlobal": false + }, { "key": "menu", "className": "MuiDataGridPro-menu", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 8fad1a4c4063..e13f8f5f654c 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -1351,6 +1351,12 @@ "description": "Styles applied to the main container element when it has right pinned columns.", "isGlobal": false }, + { + "key": "main--hasSkeletonLoadingOverlay", + "className": "MuiDataGrid-main--hasSkeletonLoadingOverlay", + "description": "Styles applied to the main container element when it has an active skeleton loading overlay.", + "isGlobal": false + }, { "key": "menu", "className": "MuiDataGrid-menu", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 891f2d299dc2..99dc8d050e6b 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -989,6 +989,11 @@ "nodeName": "the main container element", "conditions": "it has right pinned columns" }, + "main--hasSkeletonLoadingOverlay": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the main container element", + "conditions": "it has an active skeleton loading overlay" + }, "menu": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the menu element" }, "menuIcon": { "description": "Styles applied to {{nodeName}}.", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index be79188d0bbc..4e23b431b3c1 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -927,6 +927,11 @@ "nodeName": "the main container element", "conditions": "it has right pinned columns" }, + "main--hasSkeletonLoadingOverlay": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the main container element", + "conditions": "it has an active skeleton loading overlay" + }, "menu": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the menu element" }, "menuIcon": { "description": "Styles applied to {{nodeName}}.", diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 7ea702d9b210..8f6dfd205c3f 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -816,6 +816,11 @@ "nodeName": "the main container element", "conditions": "it has right pinned columns" }, + "main--hasSkeletonLoadingOverlay": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the main container element", + "conditions": "it has an active skeleton loading overlay" + }, "menu": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the menu element" }, "menuIcon": { "description": "Styles applied to {{nodeName}}.", diff --git a/packages/x-data-grid/src/components/base/GridOverlays.tsx b/packages/x-data-grid/src/components/base/GridOverlays.tsx index 2082045b3207..fc64a9c3d759 100644 --- a/packages/x-data-grid/src/components/base/GridOverlays.tsx +++ b/packages/x-data-grid/src/components/base/GridOverlays.tsx @@ -92,12 +92,13 @@ function GridOverlayWrapper(props: React.PropsWithChildren) { ); } -GridOverlayWrapper.propTypes = { +GridOverlays.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- - overlayType: PropTypes.string.isRequired, + loadingOverlayVariant: PropTypes.oneOf(['circular-progress', 'linear-progress', 'skeleton']), + overlayType: PropTypes.oneOf(['loadingOverlay', 'noResultsOverlay', 'noRowsOverlay']), } as any; export function GridOverlays(props: GridOverlaysProps) { From 650ae4e509df04cb8a7b5d21b8d33ba395149df9 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Tue, 4 Jun 2024 16:59:33 +0100 Subject: [PATCH 16/29] overlays documentation --- .../data/data-grid/overlays/LoadingOverlay.js | 18 +++++++ .../data-grid/overlays/LoadingOverlay.tsx | 18 +++++++ .../overlays/LoadingOverlay.tsx.preview | 1 + .../overlays/LoadingOverlayVariants.js | 40 ++++++++++++++++ .../overlays/LoadingOverlayVariants.tsx | 40 ++++++++++++++++ .../LoadingOverlayVariants.tsx.preview | 11 +++++ .../data-grid/overlays/NoResultsOverlay.js | 35 ++++++++++++++ .../data-grid/overlays/NoResultsOverlay.tsx | 35 ++++++++++++++ .../overlays/NoResultsOverlay.tsx.preview | 18 +++++++ docs/data/data-grid/overlays/NoRowsOverlay.js | 18 +++++++ .../data/data-grid/overlays/NoRowsOverlay.tsx | 18 +++++++ .../overlays/NoRowsOverlay.tsx.preview | 1 + docs/data/data-grid/overlays/overlays.md | 48 +++++++++++++++++++ docs/data/pages.ts | 1 + docs/pages/x/react-data-grid/overlays.js | 7 +++ 15 files changed, 309 insertions(+) create mode 100644 docs/data/data-grid/overlays/LoadingOverlay.js create mode 100644 docs/data/data-grid/overlays/LoadingOverlay.tsx create mode 100644 docs/data/data-grid/overlays/LoadingOverlay.tsx.preview create mode 100644 docs/data/data-grid/overlays/LoadingOverlayVariants.js create mode 100644 docs/data/data-grid/overlays/LoadingOverlayVariants.tsx create mode 100644 docs/data/data-grid/overlays/LoadingOverlayVariants.tsx.preview create mode 100644 docs/data/data-grid/overlays/NoResultsOverlay.js create mode 100644 docs/data/data-grid/overlays/NoResultsOverlay.tsx create mode 100644 docs/data/data-grid/overlays/NoResultsOverlay.tsx.preview create mode 100644 docs/data/data-grid/overlays/NoRowsOverlay.js create mode 100644 docs/data/data-grid/overlays/NoRowsOverlay.tsx create mode 100644 docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview create mode 100644 docs/data/data-grid/overlays/overlays.md create mode 100644 docs/pages/x/react-data-grid/overlays.js diff --git a/docs/data/data-grid/overlays/LoadingOverlay.js b/docs/data/data-grid/overlays/LoadingOverlay.js new file mode 100644 index 000000000000..feb4bb20dd28 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlay.js @@ -0,0 +1,18 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function NoRowsOverlay() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 6, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlay.tsx b/docs/data/data-grid/overlays/LoadingOverlay.tsx new file mode 100644 index 000000000000..feb4bb20dd28 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlay.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function NoRowsOverlay() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 6, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlay.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlay.tsx.preview new file mode 100644 index 000000000000..0948ca9faeec --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlay.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/overlays/LoadingOverlayVariants.js b/docs/data/data-grid/overlays/LoadingOverlayVariants.js new file mode 100644 index 000000000000..a9b65aaca7c5 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlayVariants.js @@ -0,0 +1,40 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function LoadingOverlayVariants() { + const [withRows, setWithRows] = React.useState(false); + const toggleRows = () => setWithRows((prevwithRows) => !prevwithRows); + + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 5, + maxColumns: 9, + }); + + return ( + + + + + + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx new file mode 100644 index 000000000000..a9b65aaca7c5 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function LoadingOverlayVariants() { + const [withRows, setWithRows] = React.useState(false); + const toggleRows = () => setWithRows((prevwithRows) => !prevwithRows); + + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 5, + maxColumns: 9, + }); + + return ( + + + + + + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx.preview new file mode 100644 index 000000000000..b05ff555924f --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx.preview @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/overlays/NoResultsOverlay.js b/docs/data/data-grid/overlays/NoResultsOverlay.js new file mode 100644 index 000000000000..b1fb5300c626 --- /dev/null +++ b/docs/data/data-grid/overlays/NoResultsOverlay.js @@ -0,0 +1,35 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function NoRowsOverlay() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 6, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/NoResultsOverlay.tsx b/docs/data/data-grid/overlays/NoResultsOverlay.tsx new file mode 100644 index 000000000000..b1fb5300c626 --- /dev/null +++ b/docs/data/data-grid/overlays/NoResultsOverlay.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function NoRowsOverlay() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 6, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/NoResultsOverlay.tsx.preview b/docs/data/data-grid/overlays/NoResultsOverlay.tsx.preview new file mode 100644 index 000000000000..12b4676815dc --- /dev/null +++ b/docs/data/data-grid/overlays/NoResultsOverlay.tsx.preview @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/overlays/NoRowsOverlay.js b/docs/data/data-grid/overlays/NoRowsOverlay.js new file mode 100644 index 000000000000..d748985be91a --- /dev/null +++ b/docs/data/data-grid/overlays/NoRowsOverlay.js @@ -0,0 +1,18 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function NoRowsOverlay() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 0, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/NoRowsOverlay.tsx b/docs/data/data-grid/overlays/NoRowsOverlay.tsx new file mode 100644 index 000000000000..d748985be91a --- /dev/null +++ b/docs/data/data-grid/overlays/NoRowsOverlay.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function NoRowsOverlay() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 0, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview b/docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview new file mode 100644 index 000000000000..364fac54f80c --- /dev/null +++ b/docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/overlays/overlays.md b/docs/data/data-grid/overlays/overlays.md new file mode 100644 index 000000000000..8c98b791776d --- /dev/null +++ b/docs/data/data-grid/overlays/overlays.md @@ -0,0 +1,48 @@ +# Data Grid - Overlays + +

The various data grid overlays.

+ +## Loading overlay + +To display a loading overlay and signify that the data grid is in a loading state, set the `loading` prop to `true`. + +{{"demo": "LoadingOverlay.js", "bg": "inline"}} + +### Variants + +The data grid supports 3 loading overlay variants out of the box: + +- `circular-progress` (default): a circular loading spinner. +- `linear-progress`: an indeterminate linear progress bar. +- `skeleton`: an animated placeholder of the data grid. + +The type of loading overlay to display can be set via `slotProps.loadingOverlay` for the following two props: + +- `variant`: when `loading` and there are rows in the table. +- `noRowsVariant`: when `loading` and there are not any rows in the table. + +In the following demo, we are showing a skeleton overlay when there are no rows, and a linear progress bar when more are loading: + +{{"demo": "LoadingOverlayVariants.js", "bg": "inline"}} + +## No rows overlay + +The no rows overlay is displayed when the data grid has no rows. + +{{"demo": "NoRowsOverlay.js", "bg": "inline"}} + +## No results overlay + +The no results overlay is displayed when the data grid has no results after filtering. + +{{"demo": "NoResultsOverlay.js", "bg": "inline"}} + +## Custom overlays + +You can customize the rendering of the overlays as shown in [the component section](/x/react-data-grid/components/#component-slots) of the documentation. + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 3977bf50b95a..0fc60917a85e 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -88,6 +88,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/export' }, { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste', newFeature: true }, { pathname: '/x/react-data-grid/components', title: 'Custom subcomponents' }, + { pathname: '/x/react-data-grid/overlays', title: 'Overlays' }, { pathname: '/x/react-data-grid/style-group', title: 'Style', diff --git a/docs/pages/x/react-data-grid/overlays.js b/docs/pages/x/react-data-grid/overlays.js new file mode 100644 index 000000000000..eecf19cc11c3 --- /dev/null +++ b/docs/pages/x/react-data-grid/overlays.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/overlays/overlays.md?muiMarkdown'; + +export default function Page() { + return ; +} From 7c226b688013eba8dcc46a8617405c00518956f1 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Tue, 4 Jun 2024 19:32:33 +0100 Subject: [PATCH 17/29] remove skeleton overlay class from public api --- docs/pages/x/api/data-grid/data-grid-premium.json | 6 ------ docs/pages/x/api/data-grid/data-grid-pro.json | 6 ------ docs/pages/x/api/data-grid/data-grid.json | 6 ------ .../data-grid/data-grid-premium/data-grid-premium.json | 5 ----- .../api-docs/data-grid/data-grid-pro/data-grid-pro.json | 5 ----- .../api-docs/data-grid/data-grid/data-grid.json | 5 ----- packages/x-data-grid/src/constants/gridClasses.ts | 1 + scripts/x-data-grid-premium.exports.json | 2 ++ scripts/x-data-grid-pro.exports.json | 2 ++ scripts/x-data-grid.exports.json | 2 ++ 10 files changed, 7 insertions(+), 33 deletions(-) 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 aae9623f9b67..5800b14cc52f 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -1548,12 +1548,6 @@ "description": "Styles applied to the main container element when it has right pinned columns.", "isGlobal": false }, - { - "key": "main--hasSkeletonLoadingOverlay", - "className": "MuiDataGridPremium-main--hasSkeletonLoadingOverlay", - "description": "Styles applied to the main container element when it has an active skeleton loading overlay.", - "isGlobal": false - }, { "key": "menu", "className": "MuiDataGridPremium-menu", 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 b6134018c09a..5e96f8adbe84 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -1465,12 +1465,6 @@ "description": "Styles applied to the main container element when it has right pinned columns.", "isGlobal": false }, - { - "key": "main--hasSkeletonLoadingOverlay", - "className": "MuiDataGridPro-main--hasSkeletonLoadingOverlay", - "description": "Styles applied to the main container element when it has an active skeleton loading overlay.", - "isGlobal": false - }, { "key": "menu", "className": "MuiDataGridPro-menu", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index e13f8f5f654c..8fad1a4c4063 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -1351,12 +1351,6 @@ "description": "Styles applied to the main container element when it has right pinned columns.", "isGlobal": false }, - { - "key": "main--hasSkeletonLoadingOverlay", - "className": "MuiDataGrid-main--hasSkeletonLoadingOverlay", - "description": "Styles applied to the main container element when it has an active skeleton loading overlay.", - "isGlobal": false - }, { "key": "menu", "className": "MuiDataGrid-menu", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 99dc8d050e6b..891f2d299dc2 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -989,11 +989,6 @@ "nodeName": "the main container element", "conditions": "it has right pinned columns" }, - "main--hasSkeletonLoadingOverlay": { - "description": "Styles applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the main container element", - "conditions": "it has an active skeleton loading overlay" - }, "menu": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the menu element" }, "menuIcon": { "description": "Styles applied to {{nodeName}}.", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index 4e23b431b3c1..be79188d0bbc 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -927,11 +927,6 @@ "nodeName": "the main container element", "conditions": "it has right pinned columns" }, - "main--hasSkeletonLoadingOverlay": { - "description": "Styles applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the main container element", - "conditions": "it has an active skeleton loading overlay" - }, "menu": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the menu element" }, "menuIcon": { "description": "Styles applied to {{nodeName}}.", diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 8f6dfd205c3f..7ea702d9b210 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -816,11 +816,6 @@ "nodeName": "the main container element", "conditions": "it has right pinned columns" }, - "main--hasSkeletonLoadingOverlay": { - "description": "Styles applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the main container element", - "conditions": "it has an active skeleton loading overlay" - }, "menu": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the menu element" }, "menuIcon": { "description": "Styles applied to {{nodeName}}.", diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts index a5d1b21dcb29..d811c4b10760 100644 --- a/packages/x-data-grid/src/constants/gridClasses.ts +++ b/packages/x-data-grid/src/constants/gridClasses.ts @@ -369,6 +369,7 @@ export interface GridClasses { 'main--hasPinnedRight': string; /** * Styles applied to the main container element when it has an active skeleton loading overlay. + * @ignore - do not document. */ 'main--hasSkeletonLoadingOverlay': string; /** diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 9723176f8754..fd768bb4c045 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -387,6 +387,8 @@ { "name": "GridLeafNode", "kind": "Interface" }, { "name": "GridLoadIcon", "kind": "Variable" }, { "name": "GridLoadingOverlay", "kind": "Variable" }, + { "name": "GridLoadingOverlayProps", "kind": "Interface" }, + { "name": "GridLoadingOverlayVariant", "kind": "TypeAlias" }, { "name": "GridLocaleText", "kind": "Interface" }, { "name": "GridLocaleTextApi", "kind": "Interface" }, { "name": "GridLogicOperator", "kind": "Enum" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 314428c421b6..3ab3043f9e5f 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -351,6 +351,8 @@ { "name": "GridLeafNode", "kind": "Interface" }, { "name": "GridLoadIcon", "kind": "Variable" }, { "name": "GridLoadingOverlay", "kind": "Variable" }, + { "name": "GridLoadingOverlayProps", "kind": "Interface" }, + { "name": "GridLoadingOverlayVariant", "kind": "TypeAlias" }, { "name": "GridLocaleText", "kind": "Interface" }, { "name": "GridLocaleTextApi", "kind": "Interface" }, { "name": "GridLogicOperator", "kind": "Enum" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 16a74ef16850..09a72efb3edb 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -322,6 +322,8 @@ { "name": "GridLeafNode", "kind": "Interface" }, { "name": "GridLoadIcon", "kind": "Variable" }, { "name": "GridLoadingOverlay", "kind": "Variable" }, + { "name": "GridLoadingOverlayProps", "kind": "Interface" }, + { "name": "GridLoadingOverlayVariant", "kind": "TypeAlias" }, { "name": "GridLocaleText", "kind": "Interface" }, { "name": "GridLocaleTextApi", "kind": "Interface" }, { "name": "GridLogicOperator", "kind": "Enum" }, From bdbfeb5185c2bf6f73132cff8b1a5fb9174a2e34 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 5 Jun 2024 14:31:24 +0100 Subject: [PATCH 18/29] support pinned columns --- .../src/components/GridLoadingOverlay.tsx | 13 +- .../components/GridSkeletonLoadingOverlay.tsx | 135 ++++++++++++++---- .../src/components/cell/GridSkeletonCell.tsx | 25 ++-- 3 files changed, 128 insertions(+), 45 deletions(-) diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx index 717d02db7ea9..36cb3830deb6 100644 --- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import LinearProgress from '@mui/material/LinearProgress'; import CircularProgress from '@mui/material/CircularProgress'; -import { Theme, SystemStyleObject } from '@mui/system'; import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; import { GridSkeletonLoadingOverlay } from './GridSkeletonLoadingOverlay'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; @@ -26,20 +25,20 @@ const LOADING_VARIANTS: Record< GridLoadingOverlayVariant, { component: React.ComponentType; - sx: SystemStyleObject; + style: React.CSSProperties; } > = { 'circular-progress': { component: CircularProgress, - sx: {}, + style: {}, }, 'linear-progress': { component: LinearProgress, - sx: { display: 'block' }, + style: { display: 'block' }, }, skeleton: { component: GridSkeletonLoadingOverlay, - sx: { display: 'block' }, + style: { display: 'block' }, }, }; @@ -48,7 +47,7 @@ const GridLoadingOverlay = React.forwardRef + ); diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 005cf7c30ad5..d00e8b256864 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -1,20 +1,25 @@ import * as React from 'react'; +import clsx from 'clsx'; import { styled } from '@mui/system'; import useForkRef from '@mui/utils/useForkRef'; import composeClasses from '@mui/utils/composeClasses'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { + GridPinnedColumnPosition, gridColumnPositionsSelector, gridColumnsTotalWidthSelector, gridDimensionsSelector, gridVisibleColumnDefinitionsSelector, + gridVisiblePinnedColumnDefinitionsSelector, useGridApiEventHandler, useGridSelector, } from '../hooks'; import { GridEventListener } from '../models'; import { DataGridProcessedProps } from '../models/props/DataGridProps'; -import { getDataGridUtilityClass } from '../constants/gridClasses'; +import { getDataGridUtilityClass, gridClasses } from '../constants/gridClasses'; +import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset'; +import { shouldCellShowLeftBorder, shouldCellShowRightBorder } from '../utils/cellBorderUtils'; const colWidthVar = (index: number) => `--colWidth-${index}`; @@ -23,14 +28,9 @@ const SkeletonOverlay = styled('div', { slot: 'SkeletonLoadingOverlay', overridesResolver: (props, styles) => styles.skeletonLoadingOverlay, })({ - display: 'grid', - width: 'max-content', // ensures overflow: hidden; does not cut off the x axis - minWidth: '100%', // ensures the filler column takes up the remaining space in a row + width: 'max-content', // prevents overflow: clip; cutting off the x axis height: '100%', - overflow: 'hidden', - '& .MuiDataGrid-cellSkeleton': { - borderBottom: '1px solid var(--DataGrid-rowBorderColor)', - }, + overflow: 'clip', // y axis is hidden while the x axis is allowed to overflow }); type OwnerState = { classes: DataGridProcessedProps['classes'] }; @@ -50,17 +50,14 @@ const GridSkeletonLoadingOverlay = React.forwardRef< React.HTMLAttributes >(function GridSkeletonLoadingOverlay(props, forwardedRef) { const rootProps = useGridRootProps(); - const classes = useUtilityClasses({ ...props, classes: rootProps.classes }); + const { slots } = rootProps; + const classes = useUtilityClasses({ classes: rootProps.classes }); const ref = React.useRef(null); const handleRef = useForkRef(ref, forwardedRef); - const apiRef = useGridApiContext(); - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); const viewportHeight = dimensions?.viewportInnerSize.height ?? 0; - const skeletonRowsCount = Math.ceil(viewportHeight / dimensions.rowHeight); - const totalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); const positions = useGridSelector(apiRef, gridColumnPositionsSelector); const inViewportCount = React.useMemo( @@ -69,35 +66,120 @@ const GridSkeletonLoadingOverlay = React.forwardRef< ); const allVisibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); const columns = allVisibleColumns.slice(0, inViewportCount); + const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector); + + const getPinnedStyle = React.useCallback( + (computedWidth: number, index: number, position: GridPinnedColumnPosition) => { + const pinnedOffset = getPinnedCellOffset( + position, + computedWidth, + index, + positions, + dimensions, + ); + return { [position]: pinnedOffset } as const; + }, + [dimensions, positions], + ); + + const getPinnedPosition = React.useCallback( + (field: string) => { + if (pinnedColumns.left.findIndex((col) => col.field === field) !== -1) { + return GridPinnedColumnPosition.LEFT; + } + if (pinnedColumns.right.findIndex((col) => col.field === field) !== -1) { + return GridPinnedColumnPosition.RIGHT; + } + return undefined; + }, + [pinnedColumns.left, pinnedColumns.right], + ); - const { slots } = rootProps; const children = React.useMemo(() => { const array: React.ReactNode[] = []; for (let i = 0; i < skeletonRowsCount; i += 1) { - // eslint-disable-next-line no-restricted-syntax - for (const column of columns) { - array.push( + const rowCells: React.ReactNode[] = []; + + for (let colIndex = 0; colIndex < columns.length; colIndex += 1) { + const column = columns[colIndex]; + const pinnedPosition = getPinnedPosition(column.field); + const isPinnedLeft = pinnedPosition === GridPinnedColumnPosition.LEFT; + const isPinnedRight = pinnedPosition === GridPinnedColumnPosition.RIGHT; + const sectionLength = pinnedPosition ? pinnedColumns[pinnedPosition].length : 0; + const sectionIndex = pinnedPosition + ? pinnedColumns[pinnedPosition].findIndex((col) => col.field === column.field) + : -1; + const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; + const showRightBorder = shouldCellShowRightBorder( + pinnedPosition, + sectionIndex, + sectionLength, + rootProps.showCellVerticalBorder, + gridHasFiller, + ); + const showLeftBorder = shouldCellShowLeftBorder(pinnedPosition, sectionIndex); + const style = + pinnedPosition && getPinnedStyle(column.computedWidth, colIndex, pinnedPosition); + + const isFirstPinnedRight = isPinnedRight && sectionIndex === 0; + + if (isFirstPinnedRight) { + const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth; + const emptyCellWidth = Math.max(0, expandedWidth); + rowCells.push( + , + ); + } + + rowCells.push( , ); } - array.push(); + + array.push( +
+ {rowCells} +
, + ); } return array; - }, [skeletonRowsCount, columns, slots]); - - const [initialColWidthVariables, gridTemplateColumns] = columns.reduce( - ([initialSize, templateColumn], column, i) => { + }, [ + slots, + columns, + pinnedColumns, + skeletonRowsCount, + rootProps.showCellVerticalBorder, + dimensions.columnsTotalWidth, + dimensions.viewportOuterSize.width, + dimensions.rowHeight, + getPinnedPosition, + getPinnedStyle, + ]); + + const initialColWidthVariables = columns.reduce>( + (initialSize, column, i) => { const varName = colWidthVar(i); initialSize[varName] = `${column.computedWidth}px`; - templateColumn += ` var(${varName})`; - return [initialSize, templateColumn]; + return initialSize; }, - [{} as Record, ''], + {}, ); // Sync the column resize of the overlay columns with the grid @@ -113,9 +195,6 @@ const GridSkeletonLoadingOverlay = React.forwardRef< ref={handleRef} {...props} style={{ - // the filler column is set to `1fr` to take up the remaining space in a row - gridTemplateColumns: `${gridTemplateColumns} 1fr`, - gridAutoRows: dimensions.rowHeight, ...initialColWidthVariables, ...props.style, }} diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx index 07b191c02ffb..19b710cefeb2 100644 --- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx @@ -5,6 +5,7 @@ import { unstable_composeClasses as composeClasses, unstable_capitalize as capitalize, } from '@mui/utils'; +import clsx from 'clsx'; import { fastMemo } from '../../utils/fastMemo'; import { createRandomNumberGenerator } from '../../utils/utils'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -22,9 +23,9 @@ const CONTENT_WIDTH_RANGE_BY_TYPE: Partial singleSelect: [40, 80], } as const; -export interface GridSkeletonCellProps { +export interface GridSkeletonCellProps extends React.HTMLAttributes { type?: GridColType; - width?: number; + width?: number | string; height?: number | 'auto'; field?: string; align?: string; @@ -35,19 +36,19 @@ export interface GridSkeletonCellProps { empty?: boolean; } -type OwnerState = Pick & { +type OwnerState = Pick & { classes?: DataGridProcessedProps['classes']; }; const useUtilityClasses = (ownerState: OwnerState) => { - const { align, classes } = ownerState; + const { align, classes, empty } = ownerState; const slots = { root: [ 'cell', 'cellSkeleton', `cell--text${align ? capitalize(align) : 'Left'}`, - 'withBorderColor', + empty && 'cellEmpty', ], }; @@ -56,10 +57,10 @@ const useUtilityClasses = (ownerState: OwnerState) => { const randomNumberGenerator = createRandomNumberGenerator(12345); -function GridSkeletonCell(props: React.HTMLAttributes & GridSkeletonCellProps) { - const { field, type, align, width, height, empty = false, ...other } = props; +function GridSkeletonCell(props: GridSkeletonCellProps) { + const { field, type, align, width, height, empty = false, style, className, ...other } = props; const rootProps = useGridRootProps(); - const ownerState = { classes: rootProps.classes, align }; + const ownerState = { classes: rootProps.classes, align, empty }; const classes = useUtilityClasses(ownerState); // The width of the skeleton is a random number between the min and max values @@ -85,7 +86,11 @@ function GridSkeletonCell(props: React.HTMLAttributes & GridSkel } as const); return ( -
+
{!empty && }
); @@ -114,7 +119,7 @@ GridSkeletonCell.propTypes = { 'singleSelect', 'string', ]), - width: PropTypes.number, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), } as any; const Memoized = fastMemo(GridSkeletonCell); From 6413802df70bf48452ed55a84016b7ee055c8af8 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 5 Jun 2024 15:15:32 +0100 Subject: [PATCH 19/29] remove row hover style and expose rowSkeleton class --- docs/pages/x/api/data-grid/data-grid-premium.json | 6 ++++++ docs/pages/x/api/data-grid/data-grid-pro.json | 6 ++++++ docs/pages/x/api/data-grid/data-grid.json | 6 ++++++ .../data-grid/data-grid-premium/data-grid-premium.json | 4 ++++ .../api-docs/data-grid/data-grid-pro/data-grid-pro.json | 4 ++++ .../api-docs/data-grid/data-grid/data-grid.json | 4 ++++ .../src/components/GridSkeletonLoadingOverlay.tsx | 6 +++++- .../x-data-grid/src/components/containers/GridRootStyles.ts | 3 +++ packages/x-data-grid/src/constants/gridClasses.ts | 5 +++++ 9 files changed, 43 insertions(+), 1 deletion(-) 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 5800b14cc52f..5592b85d4c82 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -1758,6 +1758,12 @@ "description": "Styles applied to the row's draggable placeholder element inside the special row reorder cell.", "isGlobal": false }, + { + "key": "rowSkeleton", + "className": "MuiDataGridPremium-rowSkeleton", + "description": "Styles applied to the skeleton row element.", + "isGlobal": false + }, { "key": "scrollArea", "className": "MuiDataGridPremium-scrollArea", 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 5e96f8adbe84..cab9513fd348 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -1675,6 +1675,12 @@ "description": "Styles applied to the row's draggable placeholder element inside the special row reorder cell.", "isGlobal": false }, + { + "key": "rowSkeleton", + "className": "MuiDataGridPro-rowSkeleton", + "description": "Styles applied to the skeleton row element.", + "isGlobal": false + }, { "key": "scrollArea", "className": "MuiDataGridPro-scrollArea", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 8fad1a4c4063..5e35b054db08 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -1561,6 +1561,12 @@ "description": "Styles applied to the row's draggable placeholder element inside the special row reorder cell.", "isGlobal": false }, + { + "key": "rowSkeleton", + "className": "MuiDataGrid-rowSkeleton", + "description": "Styles applied to the skeleton row element.", + "isGlobal": false + }, { "key": "scrollArea", "className": "MuiDataGrid-scrollArea", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 891f2d299dc2..0eea28689572 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -1128,6 +1128,10 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the row's draggable placeholder element inside the special row reorder cell" }, + "rowSkeleton": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the skeleton row element" + }, "scrollArea": { "description": "Styles applied to {{nodeName}}.", "nodeName": "both scroll area elements" diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index be79188d0bbc..ad898e4699d2 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -1066,6 +1066,10 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the row's draggable placeholder element inside the special row reorder cell" }, + "rowSkeleton": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the skeleton row element" + }, "scrollArea": { "description": "Styles applied to {{nodeName}}.", "nodeName": "both scroll area elements" diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 7ea702d9b210..9eaa71f8ec1c 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -955,6 +955,10 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the row's draggable placeholder element inside the special row reorder cell" }, + "rowSkeleton": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the skeleton row element" + }, "scrollArea": { "description": "Styles applied to {{nodeName}}.", "nodeName": "both scroll area elements" diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index d00e8b256864..1f66b496572e 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -153,7 +153,11 @@ const GridSkeletonLoadingOverlay = React.forwardRef< array.push(
{rowCells}
, diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index 6c212b777c7d..2e24df04dfe3 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -433,6 +433,9 @@ export const GridRootStyles = styled('div', { backgroundColor: 'transparent', }, }, + [`&.${c.rowSkeleton}:hover`]: { + backgroundColor: 'transparent', + }, '&.Mui-selected': selectedStyles, }, [`& .${c['container--top']}, & .${c['container--bottom']}`]: { diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts index d811c4b10760..aedd29fb67cf 100644 --- a/packages/x-data-grid/src/constants/gridClasses.ts +++ b/packages/x-data-grid/src/constants/gridClasses.ts @@ -493,6 +493,10 @@ export interface GridClasses { * Styles applied to the root element of the row reorder cell when dragging is allowed */ 'rowReorderCell--draggable': string; + /** + * Styles applied to the skeleton row element. + */ + rowSkeleton: string; /** * Styles applied to both scroll area elements. */ @@ -733,6 +737,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'rowReorderCellContainer', 'rowReorderCell', 'rowReorderCell--draggable', + 'rowSkeleton', 'scrollArea--left', 'scrollArea--right', 'scrollArea', From 5d54f0124bb0a2846d1161ad42cded6da5f09ae6 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 5 Jun 2024 15:44:58 +0100 Subject: [PATCH 20/29] add filler cell if the columns do not take up the full width of the row --- .../components/GridSkeletonLoadingOverlay.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 1f66b496572e..15cbddb58d0e 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -28,6 +28,7 @@ const SkeletonOverlay = styled('div', { slot: 'SkeletonLoadingOverlay', overridesResolver: (props, styles) => styles.skeletonLoadingOverlay, })({ + minWidth: '100%', width: 'max-content', // prevents overflow: clip; cutting off the x axis height: '100%', overflow: 'clip', // y axis is hidden while the x axis is allowed to overflow @@ -110,6 +111,8 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const sectionIndex = pinnedPosition ? pinnedColumns[pinnedPosition].findIndex((col) => col.field === column.field) : -1; + const style = + pinnedPosition && getPinnedStyle(column.computedWidth, colIndex, pinnedPosition); const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; const showRightBorder = shouldCellShowRightBorder( pinnedPosition, @@ -119,17 +122,18 @@ const GridSkeletonLoadingOverlay = React.forwardRef< gridHasFiller, ); const showLeftBorder = shouldCellShowLeftBorder(pinnedPosition, sectionIndex); - const style = - pinnedPosition && getPinnedStyle(column.computedWidth, colIndex, pinnedPosition); - + const isLastColumn = colIndex === columns.length - 1; const isFirstPinnedRight = isPinnedRight && sectionIndex === 0; + const hasFillerBefore = isFirstPinnedRight && gridHasFiller; + const hasFillerAfter = isLastColumn && !isFirstPinnedRight && gridHasFiller; + const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth; + const emptyCellWidth = Math.max(0, expandedWidth); + const emptyCell = ( + + ); - if (isFirstPinnedRight) { - const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth; - const emptyCellWidth = Math.max(0, expandedWidth); - rowCells.push( - , - ); + if (hasFillerBefore) { + rowCells.push(emptyCell); } rowCells.push( @@ -148,6 +152,10 @@ const GridSkeletonLoadingOverlay = React.forwardRef< style={style} />, ); + + if (hasFillerAfter) { + rowCells.push(emptyCell); + } } array.push( From eda2557a4b0bd79ff9c121b7d20e7ef9b408a028 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Fri, 21 Jun 2024 15:04:23 +0100 Subject: [PATCH 21/29] set width var directly on skeleton cell --- .../components/GridSkeletonLoadingOverlay.tsx | 42 ++++++++----------- .../src/components/cell/GridSkeletonCell.tsx | 1 + 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 15cbddb58d0e..d3bb7316119c 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -20,8 +20,7 @@ import { DataGridProcessedProps } from '../models/props/DataGridProps'; import { getDataGridUtilityClass, gridClasses } from '../constants/gridClasses'; import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset'; import { shouldCellShowLeftBorder, shouldCellShowRightBorder } from '../utils/cellBorderUtils'; - -const colWidthVar = (index: number) => `--colWidth-${index}`; +import { escapeOperandAttributeSelector } from '../utils/domUtils'; const SkeletonOverlay = styled('div', { name: 'MuiDataGrid', @@ -111,7 +110,7 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const sectionIndex = pinnedPosition ? pinnedColumns[pinnedPosition].findIndex((col) => col.field === column.field) : -1; - const style = + const pinnedStyle = pinnedPosition && getPinnedStyle(column.computedWidth, colIndex, pinnedPosition); const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; const showRightBorder = shouldCellShowRightBorder( @@ -139,9 +138,10 @@ const GridSkeletonLoadingOverlay = React.forwardRef< rowCells.push( , ); @@ -185,32 +187,22 @@ const GridSkeletonLoadingOverlay = React.forwardRef< getPinnedStyle, ]); - const initialColWidthVariables = columns.reduce>( - (initialSize, column, i) => { - const varName = colWidthVar(i); - initialSize[varName] = `${column.computedWidth}px`; - return initialSize; - }, - {}, - ); - // Sync the column resize of the overlay columns with the grid const handleColumnResize: GridEventListener<'columnResize'> = (params) => { - const columnIndex = columns.findIndex((column) => column.field === params.colDef.field); - ref.current?.style.setProperty(colWidthVar(columnIndex), `${params.width}px`); + const { colDef, width } = params; + const cells = ref.current?.querySelectorAll( + `[data-field="${escapeOperandAttributeSelector(colDef.field)}"]`, + ); + if (cells) { + cells.forEach((element) => { + (element as HTMLElement).style.setProperty('--width', `${width}px`); + }); + } }; useGridApiEventHandler(apiRef, 'columnResize', handleColumnResize); return ( - + {children} ); diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx index 19b710cefeb2..5dd008e37dae 100644 --- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx @@ -87,6 +87,7 @@ function GridSkeletonCell(props: GridSkeletonCellProps) { return (
Date: Fri, 21 Jun 2024 16:12:48 +0100 Subject: [PATCH 22/29] fix pinned cell offset position during resize --- .../components/GridSkeletonLoadingOverlay.tsx | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index d3bb7316119c..4865478610a0 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -45,6 +45,8 @@ const useUtilityClasses = (ownerState: OwnerState) => { return composeClasses(slots, getDataGridUtilityClass, classes); }; +const getColIndex = (el: HTMLElement) => parseInt(el.getAttribute('data-colindex')!, 10); + const GridSkeletonLoadingOverlay = React.forwardRef< HTMLDivElement, React.HTMLAttributes @@ -143,6 +145,7 @@ const GridSkeletonLoadingOverlay = React.forwardRef< align={column.align} width="var(--width)" height={dimensions.rowHeight} + data-colindex={colIndex} className={clsx( isPinnedLeft && gridClasses['cell--pinnedLeft'], isPinnedRight && gridClasses['cell--pinnedRight'], @@ -190,15 +193,52 @@ const GridSkeletonLoadingOverlay = React.forwardRef< // Sync the column resize of the overlay columns with the grid const handleColumnResize: GridEventListener<'columnResize'> = (params) => { const { colDef, width } = params; - const cells = ref.current?.querySelectorAll( + const cells = ref.current?.querySelectorAll( `[data-field="${escapeOperandAttributeSelector(colDef.field)}"]`, ); + + if (!cells) { + throw new Error('MUI X: Expected skeleton cells to be defined with `data-field` attribute.'); + } + + const resizedColIndex = columns.findIndex((col) => col.field === colDef.field); + const pinnedPosition = getPinnedPosition(colDef.field); + const isPinnedLeft = pinnedPosition === GridPinnedColumnPosition.LEFT; + const isPinnedRight = pinnedPosition === GridPinnedColumnPosition.RIGHT; + const currentWidth = getComputedStyle(cells[0]).getPropertyValue('--width'); + const delta = parseInt(currentWidth, 10) - width; + if (cells) { cells.forEach((element) => { - (element as HTMLElement).style.setProperty('--width', `${width}px`); + element.style.setProperty('--width', `${width}px`); + }); + } + + if (isPinnedLeft) { + const pinnedCells = ref.current?.querySelectorAll( + `.${gridClasses['cell--pinnedLeft']}`, + ); + pinnedCells?.forEach((element) => { + const colIndex = getColIndex(element); + if (colIndex > resizedColIndex) { + element.style.left = `${parseInt(getComputedStyle(element).left, 10) - delta}px`; + } + }); + } + + if (isPinnedRight) { + const pinnedCells = ref.current?.querySelectorAll( + `.${gridClasses['cell--pinnedRight']}`, + ); + pinnedCells?.forEach((element) => { + const colIndex = getColIndex(element); + if (colIndex < resizedColIndex) { + element.style.right = `${parseInt(getComputedStyle(element).right, 10) + delta}px`; + } }); } }; + useGridApiEventHandler(apiRef, 'columnResize', handleColumnResize); return ( From 0e0e8ccabf8f9812e7914b64c62b8ceea7be339a Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 26 Jun 2024 11:33:10 +0100 Subject: [PATCH 23/29] Update custom loading overlay example --- .../overlays/LoadingOverlayCustom.js | 60 +++++++++++++++- .../overlays/LoadingOverlayCustom.tsx | 68 ++++++++++++++++++- .../overlays/LoadingOverlayCustom.tsx.preview | 2 +- docs/data/data-grid/overlays/overlays.md | 12 +++- 4 files changed, 133 insertions(+), 9 deletions(-) diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.js b/docs/data/data-grid/overlays/LoadingOverlayCustom.js index 481a8a5949d2..a936b4296f19 100644 --- a/docs/data/data-grid/overlays/LoadingOverlayCustom.js +++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.js @@ -1,7 +1,63 @@ import * as React from 'react'; import { DataGrid } from '@mui/x-data-grid'; -import LinearProgress from '@mui/material/LinearProgress'; import { useDemoData } from '@mui/x-data-grid-generator'; +import { styled } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import CircularProgress from '@mui/material/CircularProgress'; + +const StyledGridOverlay = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + backgroundColor: + theme.palette.mode === 'light' + ? 'rgba(255, 255, 255, 0.9)' + : 'rgba(18, 18, 18, 0.9)', +})); + +function CircularProgressWithLabel(props) { + return ( + + + + + {`${Math.round(props.value)}%`} + + + + ); +} + +function CustomLoadingOverlay() { + const [progress, setProgress] = React.useState(10); + + React.useEffect(() => { + const timer = setInterval(() => { + setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10)); + }, 800); + return () => { + clearInterval(timer); + }; + }, []); + + return ( + + + Loading rows… + + ); +} export default function LoadingOverlayCustom() { const { data } = useDemoData({ @@ -14,7 +70,7 @@ export default function LoadingOverlayCustom() {
({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + backgroundColor: + theme.palette.mode === 'light' + ? 'rgba(255, 255, 255, 0.9)' + : 'rgba(18, 18, 18, 0.9)', +})); + +function CircularProgressWithLabel( + props: CircularProgressProps & { value: number }, +) { + return ( + + + + {`${Math.round(props.value)}%`} + + + ); +} + +function CustomLoadingOverlay() { + const [progress, setProgress] = React.useState(10); + + React.useEffect(() => { + const timer = setInterval(() => { + setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10)); + }, 800); + return () => { + clearInterval(timer); + }; + }, []); + + return ( + + + Loading rows… + + ); +} export default function LoadingOverlayCustom() { const { data } = useDemoData({ @@ -14,7 +76,7 @@ export default function LoadingOverlayCustom() {
Date: Wed, 26 Jun 2024 14:59:04 +0100 Subject: [PATCH 24/29] add scrollbar filler cell --- .../src/components/GridSkeletonLoadingOverlay.tsx | 9 +++++++++ .../src/components/containers/GridRootStyles.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 4865478610a0..ffd930efdb62 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -21,6 +21,7 @@ import { getDataGridUtilityClass, gridClasses } from '../constants/gridClasses'; import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset'; import { shouldCellShowLeftBorder, shouldCellShowRightBorder } from '../utils/cellBorderUtils'; import { escapeOperandAttributeSelector } from '../utils/domUtils'; +import { GridScrollbarFillerCell } from './GridScrollbarFillerCell'; const SkeletonOverlay = styled('div', { name: 'MuiDataGrid', @@ -132,6 +133,8 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const emptyCell = ( ); + const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; + const hasScrollbarFiller = isLastColumn && scrollbarWidth !== 0; if (hasFillerBefore) { rowCells.push(emptyCell); @@ -161,6 +164,10 @@ const GridSkeletonLoadingOverlay = React.forwardRef< if (hasFillerAfter) { rowCells.push(emptyCell); } + + if (hasScrollbarFiller) { + rowCells.push( 0} />); + } } array.push( @@ -186,6 +193,8 @@ const GridSkeletonLoadingOverlay = React.forwardRef< dimensions.columnsTotalWidth, dimensions.viewportOuterSize.width, dimensions.rowHeight, + dimensions.hasScrollY, + dimensions.scrollbarSize, getPinnedPosition, getPinnedStyle, ]); diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index 2e24df04dfe3..d30f0d0afb51 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -639,7 +639,7 @@ export const GridRootStyles = styled('div', { minWidth: 'calc(var(--DataGrid-hasScrollY) * var(--DataGrid-scrollbarSize))', alignSelf: 'stretch', [`&.${c['scrollbarFiller--borderTop']}`]: { - borderTop: '1px solid var(--DataGrid-rowBorderColor)', + borderTop: '1px solid var(--rowBorderColor)', }, [`&.${c['scrollbarFiller--pinnedRight']}`]: { backgroundColor: 'var(--DataGrid-pinnedBackground)', From be974f55dc7f773013caf6a9676270859404d998 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 26 Jun 2024 16:09:08 +0100 Subject: [PATCH 25/29] separate variant demo into multiple demos --- .../data/data-grid/overlays/LoadingOverlay.js | 2 +- .../data-grid/overlays/LoadingOverlay.tsx | 2 +- .../overlays/LoadingOverlayLinearProgress.js | 27 +++++ .../overlays/LoadingOverlayLinearProgress.tsx | 27 +++++ .../LoadingOverlayLinearProgress.tsx.preview | 10 ++ .../overlays/LoadingOverlaySkeleton.js | 30 ++++++ .../overlays/LoadingOverlaySkeleton.tsx | 30 ++++++ .../LoadingOverlaySkeleton.tsx.preview | 13 +++ .../overlays/LoadingOverlayVariants.js | 94 ---------------- .../overlays/LoadingOverlayVariants.tsx | 100 ------------------ docs/data/data-grid/overlays/overlays.md | 22 ++-- 11 files changed, 155 insertions(+), 202 deletions(-) create mode 100644 docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js create mode 100644 docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx create mode 100644 docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview create mode 100644 docs/data/data-grid/overlays/LoadingOverlaySkeleton.js create mode 100644 docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx create mode 100644 docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview delete mode 100644 docs/data/data-grid/overlays/LoadingOverlayVariants.js delete mode 100644 docs/data/data-grid/overlays/LoadingOverlayVariants.tsx diff --git a/docs/data/data-grid/overlays/LoadingOverlay.js b/docs/data/data-grid/overlays/LoadingOverlay.js index 0c65d421df94..333a904448f6 100644 --- a/docs/data/data-grid/overlays/LoadingOverlay.js +++ b/docs/data/data-grid/overlays/LoadingOverlay.js @@ -11,7 +11,7 @@ export default function LoadingOverlay() { }); return ( - + ); diff --git a/docs/data/data-grid/overlays/LoadingOverlay.tsx b/docs/data/data-grid/overlays/LoadingOverlay.tsx index 0c65d421df94..333a904448f6 100644 --- a/docs/data/data-grid/overlays/LoadingOverlay.tsx +++ b/docs/data/data-grid/overlays/LoadingOverlay.tsx @@ -11,7 +11,7 @@ export default function LoadingOverlay() { }); return ( - + ); diff --git a/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js new file mode 100644 index 000000000000..5ad0b8a4b3e5 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js @@ -0,0 +1,27 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import { DataGrid } from '@mui/x-data-grid'; + +export default function LoadingOverlayLinearProgress() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx new file mode 100644 index 000000000000..5ad0b8a4b3e5 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import { DataGrid } from '@mui/x-data-grid'; + +export default function LoadingOverlayLinearProgress() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 6, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview new file mode 100644 index 000000000000..f4e0a9ef7417 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js new file mode 100644 index 000000000000..3bed3c29bbb5 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js @@ -0,0 +1,30 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import { DataGridPro } from '@mui/x-data-grid-pro'; + +export default function LoadingOverlaySkeleton() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 9, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx new file mode 100644 index 000000000000..3bed3c29bbb5 --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import { DataGridPro } from '@mui/x-data-grid-pro'; + +export default function LoadingOverlaySkeleton() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 9, + }); + + return ( + + + + ); +} diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview new file mode 100644 index 000000000000..57e1af72a47a --- /dev/null +++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/overlays/LoadingOverlayVariants.js b/docs/data/data-grid/overlays/LoadingOverlayVariants.js deleted file mode 100644 index da2b643ef58a..000000000000 --- a/docs/data/data-grid/overlays/LoadingOverlayVariants.js +++ /dev/null @@ -1,94 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import FormControl from '@mui/material/FormControl'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import InputLabel from '@mui/material/InputLabel'; -import Switch from '@mui/material/Switch'; -import Select from '@mui/material/Select'; -import MenuItem from '@mui/material/MenuItem'; -import { DataGrid } from '@mui/x-data-grid'; -import { useDemoData } from '@mui/x-data-grid-generator'; - -function VariantControl(props) { - const { value, onChange, label, id } = props; - const labelId = `${id}-label`; - return ( - - - {label} - - - - ); -} - -function RowsControl(props) { - const { checked, onChange } = props; - return ( - } - label="Rows" - /> - ); -} - -export default function LoadingOverlayVariants() { - const [withRows, setWithRows] = React.useState(true); - const [variant, setVariant] = React.useState('linear-progress'); - const [noRowsVariant, setNoRowsVariant] = React.useState('skeleton'); - - const { data } = useDemoData({ - dataSet: 'Commodity', - rowLength: 5, - maxColumns: 9, - }); - - return ( - - - setVariant(event.target.value)} - /> - setNoRowsVariant(event.target.value)} - /> - setWithRows(event.target.checked)} - /> - - - - - - ); -} diff --git a/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx deleted file mode 100644 index f2c38fbaaa09..000000000000 --- a/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import FormControl from '@mui/material/FormControl'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import InputLabel from '@mui/material/InputLabel'; -import Switch, { SwitchProps } from '@mui/material/Switch'; -import Select, { SelectProps } from '@mui/material/Select'; -import MenuItem from '@mui/material/MenuItem'; -import { DataGrid, GridLoadingOverlayVariant } from '@mui/x-data-grid'; -import { useDemoData } from '@mui/x-data-grid-generator'; - -function VariantControl(props: SelectProps) { - const { value, onChange, label, id } = props; - const labelId = `${id}-label`; - return ( - - - {label} - - - - ); -} - -function RowsControl(props: SwitchProps) { - const { checked, onChange } = props; - return ( - } - label="Rows" - /> - ); -} - -export default function LoadingOverlayVariants() { - const [withRows, setWithRows] = React.useState(true); - const [variant, setVariant] = - React.useState('linear-progress'); - const [noRowsVariant, setNoRowsVariant] = - React.useState('skeleton'); - - const { data } = useDemoData({ - dataSet: 'Commodity', - rowLength: 5, - maxColumns: 9, - }); - - return ( - - - - setVariant(event.target.value as GridLoadingOverlayVariant) - } - /> - - setNoRowsVariant(event.target.value as GridLoadingOverlayVariant) - } - /> - setWithRows(event.target.checked)} - /> - - - - - - ); -} diff --git a/docs/data/data-grid/overlays/overlays.md b/docs/data/data-grid/overlays/overlays.md index 80c0e79daf16..426e8b615b3e 100644 --- a/docs/data/data-grid/overlays/overlays.md +++ b/docs/data/data-grid/overlays/overlays.md @@ -6,10 +6,6 @@ To display a loading overlay and signify that the data grid is in a loading state, set the `loading` prop to `true`. -{{"demo": "LoadingOverlay.js", "bg": "inline"}} - -### Variants - The data grid supports 3 loading overlay variants out of the box: - `circular-progress` (default): a circular loading spinner. @@ -34,9 +30,23 @@ The type of loading overlay to display can be set via `slotProps.loadingOverlay` /> ``` -Use the demo below to try out the different variants. You can toggle whether there are rows in the table or not with the _Rows_ switch; unchecking it will let you preview the selected `noRowsVariant`. +### Circular progress + +A circular loading spinner, the default loading overlay. + +{{"demo": "LoadingOverlay.js", "bg": "inline"}} + +### Linear progress + +An indeterminate linear progress bar. + +{{"demo": "LoadingOverlayLinearProgress.js", "bg": "inline"}} + +### Skeleton + +An animated placeholder of the data grid. -{{"demo": "LoadingOverlayVariants.js", "bg": "inline"}} +{{"demo": "LoadingOverlaySkeleton.js", "bg": "inline"}} ### Custom component From 7bd1b4936737cb25e5949b24196f94a73574c9fc Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Mon, 1 Jul 2024 15:35:13 +0100 Subject: [PATCH 26/29] Update docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx Co-authored-by: Andrew Cherniavskii Signed-off-by: Kenan Yusuf --- docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx index 3bed3c29bbb5..0e86a253dfcd 100644 --- a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx +++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx @@ -21,8 +21,10 @@ export default function LoadingOverlaySkeleton() { noRowsVariant: 'skeleton', }, }} - pinnedColumns={{ - left: ['desk'], + initialState={{ + pinnedColumns: { + left: ['desk'], + }, }} /> From 6dc2f5d7a6f34a126d78a2afea098a64eeef6ff1 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Tue, 2 Jul 2024 16:36:03 +0100 Subject: [PATCH 27/29] update demos --- docs/data/data-grid/overlays/LoadingOverlaySkeleton.js | 6 ++++-- .../data-grid/overlays/LoadingOverlaySkeleton.tsx.preview | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js index 3bed3c29bbb5..0e86a253dfcd 100644 --- a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js +++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js @@ -21,8 +21,10 @@ export default function LoadingOverlaySkeleton() { noRowsVariant: 'skeleton', }, }} - pinnedColumns={{ - left: ['desk'], + initialState={{ + pinnedColumns: { + left: ['desk'], + }, }} /> diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview index 57e1af72a47a..9173142c9f99 100644 --- a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview +++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview @@ -7,7 +7,9 @@ noRowsVariant: 'skeleton', }, }} - pinnedColumns={{ - left: ['desk'], + initialState={{ + pinnedColumns: { + left: ['desk'], + }, }} /> \ No newline at end of file From 65da878ea6df508e9733d5482e3781494feca07c Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Wed, 3 Jul 2024 14:53:32 +0100 Subject: [PATCH 28/29] optimizations from code review --- .../src/components/GridLoadingOverlay.tsx | 3 +- .../components/GridSkeletonLoadingOverlay.tsx | 5 ++- .../src/components/cell/GridSkeletonCell.tsx | 45 +++++++++++-------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx index 36cb3830deb6..dc68ffccc6d8 100644 --- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx @@ -5,6 +5,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; import { GridSkeletonLoadingOverlay } from './GridSkeletonLoadingOverlay'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { gridRowCountSelector, useGridSelector } from '../hooks'; export type GridLoadingOverlayVariant = 'circular-progress' | 'linear-progress' | 'skeleton'; @@ -51,7 +52,7 @@ const GridLoadingOverlay = React.forwardRef allVisibleColumns.slice(0, inViewportCount), + [allVisibleColumns, inViewportCount], + ); const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector); const getPinnedStyle = React.useCallback( diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx index 5dd008e37dae..28a25539b706 100644 --- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx @@ -13,6 +13,10 @@ import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { GridColType } from '../../models'; +const CIRCULAR_CONTENT_SIZE = '1.3em'; + +const CONTENT_HEIGHT = '1.2em'; + const DEFAULT_CONTENT_WIDTH_RANGE = [40, 80] as const; const CONTENT_WIDTH_RANGE_BY_TYPE: Partial> = { @@ -63,27 +67,30 @@ function GridSkeletonCell(props: GridSkeletonCellProps) { const ownerState = { classes: rootProps.classes, align, empty }; const classes = useUtilityClasses(ownerState); - // The width of the skeleton is a random number between the min and max values - // The min and max values are determined by the type of the column - const [min, max] = type - ? CONTENT_WIDTH_RANGE_BY_TYPE[type] ?? DEFAULT_CONTENT_WIDTH_RANGE - : DEFAULT_CONTENT_WIDTH_RANGE; - - // Memo prevents the skeleton width changing to a random width on every render - const contentWidth = React.useMemo(() => Math.round(randomNumberGenerator(min, max)), [min, max]); + // Memo prevents the non-circular skeleton widths changing to random widths on every render + const skeletonProps = React.useMemo(() => { + const isCircularContent = type === 'boolean' || type === 'actions'; - const isCircularContent = type === 'boolean' || type === 'actions'; - const skeletonProps = isCircularContent - ? ({ + if (isCircularContent) { + return { variant: 'circular', - width: '1.3em', - height: '1.3em', - } as const) - : ({ - variant: 'text', - width: `${contentWidth}%`, - height: '1.2em', - } as const); + width: CIRCULAR_CONTENT_SIZE, + height: CIRCULAR_CONTENT_SIZE, + } as const; + } + + // The width of the skeleton is a random number between the min and max values + // The min and max values are determined by the type of the column + const [min, max] = type + ? CONTENT_WIDTH_RANGE_BY_TYPE[type] ?? DEFAULT_CONTENT_WIDTH_RANGE + : DEFAULT_CONTENT_WIDTH_RANGE; + + return { + variant: 'text', + width: `${Math.round(randomNumberGenerator(min, max))}%`, + height: CONTENT_HEIGHT, + } as const; + }, [type]); return (
Date: Wed, 3 Jul 2024 15:32:43 +0100 Subject: [PATCH 29/29] fix sectionLength and sectionIndex for middle section skeleton cells --- .../src/components/GridSkeletonLoadingOverlay.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index cd622a892b1e..62643bb382f3 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -112,10 +112,12 @@ const GridSkeletonLoadingOverlay = React.forwardRef< const pinnedPosition = getPinnedPosition(column.field); const isPinnedLeft = pinnedPosition === GridPinnedColumnPosition.LEFT; const isPinnedRight = pinnedPosition === GridPinnedColumnPosition.RIGHT; - const sectionLength = pinnedPosition ? pinnedColumns[pinnedPosition].length : 0; + const sectionLength = pinnedPosition + ? pinnedColumns[pinnedPosition].length // pinned section + : columns.length - pinnedColumns.left.length - pinnedColumns.right.length; // middle section const sectionIndex = pinnedPosition - ? pinnedColumns[pinnedPosition].findIndex((col) => col.field === column.field) - : -1; + ? pinnedColumns[pinnedPosition].findIndex((col) => col.field === column.field) // pinned section + : colIndex - pinnedColumns.left.length; // middle section const pinnedStyle = pinnedPosition && getPinnedStyle(column.computedWidth, colIndex, pinnedPosition); const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width;