From 5242c9ae712f1f303c1e30122c1d1585766efe04 Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 0000000000000..3bf61c51dcf9c
--- /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<Record<GridColType, [number, number]>> = {
+  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<HTMLDivElement, GridOverlayProps>(
+  function GridSkeletonLoadingOverlay(props, forwardedRef) {
+    const ref = React.useRef<HTMLDivElement>(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(
+            <SkeletonCell
+              key={`skeleton-column-${i}-${column.field}`}
+              sx={{ justifyContent: column.align }}
+            >
+              <Skeleton
+                width={isCircular ? '1.3em' : `${width}%`}
+                height={isCircular ? '1.3em' : '1.2em'}
+                variant={isCircular ? 'circular' : 'text'}
+                sx={{ mx: 1 }}
+              />
+            </SkeletonCell>,
+          );
+        }
+        array.push(<SkeletonCell key={`fill-${i}`} />);
+      }
+      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<string, string>, ''],
+    );
+
+    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 (
+      <StyledGridOverlay
+        ref={handleRef}
+        {...props}
+        style={{
+          gridTemplateColumns: `${gridTemplateColumns} 1fr`,
+          gridAutoRows: rowHeight,
+          ...initialColWidthVariables,
+          ...props.style,
+        }}
+      >
+        {children}
+      </StyledGridOverlay>
+    );
+  },
+);
+
+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 fef4188a67f00..b64d6b0aacd72 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<HTMLDivElement> & 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 (
     <div className={classes.root} style={{ height, maxWidth: width, minWidth: width }} {...other}>
diff --git a/packages/x-data-grid/src/components/index.ts b/packages/x-data-grid/src/components/index.ts
index 2657dac0cd890..578d34492989f 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 cc682162e59f3..16fc3d81c7153 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<string, any>) {

From a2b1bb03d6722495cb1b89253c1b338320087c5f Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 2bcb4eed7a89d..d4b5fd83b84e1 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<GridLoadingOverlayVariant, React.ComponentType> = {
+  'circular-progress': CircularProgress,
+  'linear-progress': LinearProgress,
+  skeleton: GridSkeletonLoadingOverlay,
+};
+
+const GridLoadingOverlay = React.forwardRef<HTMLDivElement, GridLoadingOverlayProps>(
+  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<HTMLDivElement, GridOverlayProps>(
-  function GridLoadingOverlay(props, ref) {
     return (
-      <GridOverlay ref={ref} {...props}>
-        <CircularProgress />
+      <GridOverlay
+        ref={ref}
+        sx={{
+          display: loadingVariant === 'circular-progress' ? 'flex' : 'block',
+          ...sx,
+        }}
+        {...props}
+      >
+        <LoadingComponent />
       </GridOverlay>
     );
   },
@@ -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 3bf61c51dcf9c..5545e033efbf6 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<Record<GridColType, [number, number]>>
 
 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<HTMLDivElement, GridOverlayProps>(
-  function GridSkeletonLoadingOverlay(props, forwardedRef) {
-    const ref = React.useRef<HTMLDivElement>(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(
-            <SkeletonCell
-              key={`skeleton-column-${i}-${column.field}`}
-              sx={{ justifyContent: column.align }}
-            >
-              <Skeleton
-                width={isCircular ? '1.3em' : `${width}%`}
-                height={isCircular ? '1.3em' : '1.2em'}
-                variant={isCircular ? 'circular' : 'text'}
-                sx={{ mx: 1 }}
-              />
-            </SkeletonCell>,
-          );
-        }
-        array.push(<SkeletonCell key={`fill-${i}`} />);
+const GridSkeletonLoadingOverlay = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(function GridSkeletonLoadingOverlay(props, forwardedRef) {
+  const ref = React.useRef<HTMLDivElement>(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(
+          <SkeletonCell
+            key={`skeleton-column-${i}-${column.field}`}
+            sx={{ justifyContent: column.align }}
+          >
+            <Skeleton
+              width={isCircular ? '1.3em' : `${width}%`}
+              height={isCircular ? '1.3em' : '1.2em'}
+              variant={isCircular ? 'circular' : 'text'}
+              sx={{ mx: 1 }}
+            />
+          </SkeletonCell>,
+        );
       }
-      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<string, string>, ''],
-    );
-
-    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 (
-      <StyledGridOverlay
-        ref={handleRef}
-        {...props}
-        style={{
-          gridTemplateColumns: `${gridTemplateColumns} 1fr`,
-          gridAutoRows: rowHeight,
-          ...initialColWidthVariables,
-          ...props.style,
-        }}
-      >
-        {children}
-      </StyledGridOverlay>
-    );
-  },
-);
-
-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(<SkeletonCell key={`skeleton-filler-column-${i}`} />);
+    }
+    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<string, string>, ''],
+  );
+
+  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 (
+    <SkeletonOverlay
+      ref={handleRef}
+      {...props}
+      style={{
+        gridTemplateColumns: `${gridTemplateColumns} 1fr`,
+        gridAutoRows: rowHeight,
+        ...initialColWidthVariables,
+        ...props.style,
+      }}
+    >
+      {children}
+    </SkeletonOverlay>
+  );
+});
 
 export { GridSkeletonLoadingOverlay };
diff --git a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts
index 6623ae5d4483e..3684059f5c8e4 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<TablePaginationProps> & PaginationPropsOverrides;

From d32fa8b1c45257fd054d67b96745f962686502b5 Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 5545e033efbf6..bd85240b4d323 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 <kenan.m.yusuf@gmail.com>
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 bd85240b4d323..9923cf372297f 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<string, string>, ''],
   );
 
-  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 (
     <SkeletonOverlay

From e8c9e0f5e3bed97115de93bdec8d336422538d1c Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 578d34492989f..2657dac0cd890 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 <kenan.m.yusuf@gmail.com>
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 d4b5fd83b84e1..cb0f5e8a6ef28 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<GridLoadingOverlayVariant, React.ComponentType> = {
-  'circular-progress': CircularProgress,
-  'linear-progress': LinearProgress,
-  skeleton: GridSkeletonLoadingOverlay,
+const LOADING_VARIANTS: Record<
+  GridLoadingOverlayVariant,
+  {
+    component: React.ComponentType;
+    sx: SystemStyleObject<Theme>;
+  }
+> = {
+  '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<HTMLDivElement, GridLoadingOverlayProps>(
@@ -34,19 +53,11 @@ const GridLoadingOverlay = React.forwardRef<HTMLDivElement, GridLoadingOverlayPr
   ) {
     const apiRef = useGridApiContext();
     const rowsCount = apiRef.current.getRowsCount();
-    const loadingVariant = rowsCount === 0 ? noRowsVariant : variant;
-    const LoadingComponent = LOADING_COMPONENTS[loadingVariant];
+    const activeVariant = LOADING_VARIANTS[rowsCount === 0 ? noRowsVariant : variant];
 
     return (
-      <GridOverlay
-        ref={ref}
-        sx={{
-          display: loadingVariant === 'circular-progress' ? 'flex' : 'block',
-          ...sx,
-        }}
-        {...props}
-      >
-        <LoadingComponent />
+      <GridOverlay ref={ref} sx={{ ...activeVariant.sx, ...sx }} {...props}>
+        <activeVariant.component />
       </GridOverlay>
     );
   },

From 4373460f0851931c966c7d43825b489533edb132 Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 9923cf372297f..278eff9c0367a 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 b64d6b0aacd72..8220d3a0a29c5 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 16fc3d81c7153..0aa1bd32980e1 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 <kenan.m.yusuf@gmail.com>
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 <olivier.tassinari@gmail.com>
Signed-off-by: Kenan Yusuf <kenan.m.yusuf@gmail.com>
---
 .../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 278eff9c0367a..a12a2742e4f89 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 <kenan.m.yusuf@gmail.com>
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 cb0f5e8a6ef28..8f6b85539fd2c 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<HTMLDivElement, GridLoadingOverlayProps>(
-  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 (
-      <GridOverlay ref={ref} sx={{ ...activeVariant.sx, ...sx }} {...props}>
+      <GridOverlay ref={ref} sx={{ ...activeVariant.sx, ...sx }} {...other}>
         <activeVariant.component />
       </GridOverlay>
     );

From 6cf0ffa6d8a5eb177d9b01cea2539a9212fa288e Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 a12a2742e4f89..0a4bba8f49088 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 0aa1bd32980e1..097ed683d9b21 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 <kenan.m.yusuf@gmail.com>
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 <kenan.m.yusuf@gmail.com>
---
 .../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 0a4bba8f49088..cd8f2c951a69d 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 <kenan.m.yusuf@gmail.com>
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 5bcef38335557..8eca3254dcf8c 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<HTMLDivElement, GridRowProps>(function GridRow(
       return (
         <slots.skeletonCell
           key={column.field}
+          type={column.type}
           width={width}
           height={rowHeight}
           field={column.field}
-          align={column.align ?? 'left'}
+          align={column.align}
         />
       );
     }
diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx
index cd8f2c951a69d..c17fa6b8a9492 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<Record<GridColType, [number, number]>> = {
-  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<HTMLDivElement>
 >(function GridSkeletonLoadingOverlay(props, forwardedRef) {
+  const rootProps = useGridRootProps();
+  const classes = useUtilityClasses({ ...props, classes: rootProps.classes });
   const ref = React.useRef<HTMLDivElement>(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(
-          <SkeletonCell
+          <slots.skeletonCell
             key={`skeleton-column-${i}-${column.field}`}
-            sx={{ justifyContent: column.align }}
-          >
-            <Skeleton
-              width={isCircular ? '1.3em' : `${width}%`}
-              height={isCircular ? '1.3em' : '1.2em'}
-              variant={isCircular ? 'circular' : 'text'}
-              sx={{ mx: 1 }}
-            />
-          </SkeletonCell>,
+            type={column.type}
+            align={column.align}
+          />,
         );
       }
-      array.push(<SkeletonCell key={`skeleton-filler-column-${i}`} />);
+      array.push(<slots.skeletonCell key={`skeleton-filler-column-${i}`} empty />);
     }
     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 (
     <SkeletonOverlay
+      className={classes.root}
       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,
diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx
index 8220d3a0a29c5..07b191c02ffb7 100644
--- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx
+++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx
@@ -10,14 +10,29 @@ import { createRandomNumberGenerator } from '../../utils/utils';
 import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
 import { getDataGridUtilityClass } from '../../constants/gridClasses';
 import { DataGridProcessedProps } from '../../models/props/DataGridProps';
+import { GridColType } from '../../models';
 
-const randomWidth = createRandomNumberGenerator(10000);
+const DEFAULT_CONTENT_WIDTH_RANGE = [40, 80] as const;
+
+const CONTENT_WIDTH_RANGE_BY_TYPE: Partial<Record<GridColType, [number, number]>> = {
+  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<GridSkeletonCellProps, 'align'> & {
@@ -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<HTMLDivElement> & 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 (
     <div className={classes.root} style={{ height, maxWidth: width, minWidth: width }} {...other}>
-      <Skeleton width={`${contentWidth}%`} height={25} />
+      {!empty && <Skeleton {...skeletonProps} />}
     </div>
   );
 }
@@ -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 <kenan.m.yusuf@gmail.com>
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 8f6b85539fd2c..717d02db7ea93 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 c17fa6b8a9492..0db11aef49bcd 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<string, string>, ''],
   );
 
-  // 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 2e214bc219ff4..2082045b3207c 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
-}));
+})<GridOverlaysProps>(({ 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<GridOverlaysProps>) {
   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 (
-    <GridOverlayWrapperRoot className={clsx(classes.root)} overlayType={props.overlayType}>
+    <GridOverlayWrapperRoot className={clsx(classes.root)} {...props}>
       <GridOverlayWrapperInner
         className={clsx(classes.inner)}
         style={{
@@ -93,38 +100,20 @@ GridOverlayWrapper.propTypes = {
   overlayType: PropTypes.string.isRequired,
 } as any;
 
-export function GridOverlays() {
-  const apiRef = useGridApiContext();
+export function GridOverlays(props: GridOverlaysProps) {
+  const { overlayType } = props;
   const rootProps = useGridRootProps();
 
-  const totalRowCount = useGridSelector(apiRef, gridRowCountSelector);
-  const visibleRowCount = useGridSelector(apiRef, gridExpandedRowCountSelector);
-  const loading = useGridSelector(apiRef, gridRowsLoadingSelector);
-
-  const showNoRowsOverlay = !loading && totalRowCount === 0;
-  const showNoResultsOverlay = !loading && totalRowCount > 0 && visibleRowCount === 0;
-
-  let overlay: React.JSX.Element | null = null;
-  let overlayType = '';
-
-  if (showNoRowsOverlay) {
-    overlay = <rootProps.slots.noRowsOverlay {...rootProps.slotProps?.noRowsOverlay} />;
-    overlayType = 'noRowsOverlay';
-  }
-
-  if (showNoResultsOverlay) {
-    overlay = <rootProps.slots.noResultsOverlay {...rootProps.slotProps?.noResultsOverlay} />;
-    overlayType = 'noResultsOverlay';
-  }
-
-  if (loading) {
-    overlay = <rootProps.slots.loadingOverlay {...rootProps.slotProps?.loadingOverlay} />;
-    overlayType = 'loadingOverlay';
-  }
-
-  if (overlay === null) {
+  if (!overlayType) {
     return null;
   }
 
-  return <GridOverlayWrapper overlayType={overlayType}>{overlay}</GridOverlayWrapper>;
+  const Overlay = rootProps.slots?.[overlayType];
+  const overlayProps = rootProps.slotProps?.[overlayType];
+
+  return (
+    <GridOverlayWrapper {...props}>
+      <Overlay {...overlayProps} />
+    </GridOverlayWrapper>
+  );
 }
diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts
index 0d5e88de51f60..6c212b777c7da 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 aa17ed93be6d9..42c2c2bf443ac 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) {
           <rootProps.slots.pinnedRows position="top" virtualScroller={virtualScroller} />
         </TopContainer>
 
-        <GridOverlays />
+        <Overlays {...overlaysProps} />
 
         <Content {...getContentProps()}>
           <RenderZone {...getRenderZoneProps()}>
diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts
index c795ccda8336a..a5d1b21dcb29d 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<GridClassKey>('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 0000000000000..f3f9f3998423a
--- /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<GridSlotsComponent, 'noRowsOverlay' | 'noResultsOverlay' | 'loadingOverlay'>
+  | 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 <kenan.m.yusuf@gmail.com>
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 0db11aef49bcd..005cf7c30ad5a 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 <kenan.m.yusuf@gmail.com>
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 5800b14cc52fe..aae9623f9b675 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 5e96f8adbe84c..b6134018c09a4 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 8fad1a4c40630..e13f8f5f654c1 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 891f2d299dc2e..99dc8d050e6be 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 be79188d0bbc9..4e23b431b3c19 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 7ea702d9b2107..8f6dfd205c3f2 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 2082045b3207c..fc64a9c3d7594 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<GridOverlaysProps>) {
   );
 }
 
-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 <kenan.m.yusuf@gmail.com>
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 0000000000000..feb4bb20dd28c
--- /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 (
+    <Box sx={{ width: '100%', height: 340 }}>
+      <DataGrid {...data} loading />
+    </Box>
+  );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlay.tsx b/docs/data/data-grid/overlays/LoadingOverlay.tsx
new file mode 100644
index 0000000000000..feb4bb20dd28c
--- /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 (
+    <Box sx={{ width: '100%', height: 340 }}>
+      <DataGrid {...data} loading />
+    </Box>
+  );
+}
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 0000000000000..0948ca9faeecd
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlay.tsx.preview
@@ -0,0 +1 @@
+<DataGrid {...data} loading />
\ 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 0000000000000..a9b65aaca7c59
--- /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 (
+    <Box sx={{ width: '100%' }}>
+      <Stack direction="row" spacing={1} sx={{ mb: 1 }}>
+        <Button size="small" onClick={toggleRows}>
+          {withRows ? 'Remove rows' : 'Add rows'}
+        </Button>
+      </Stack>
+      <Box sx={{ height: 345 }}>
+        <DataGrid
+          {...data}
+          loading
+          slotProps={{
+            loadingOverlay: {
+              noRowsVariant: 'skeleton',
+              variant: 'linear-progress',
+            },
+          }}
+          rows={withRows ? data.rows : []}
+        />
+      </Box>
+    </Box>
+  );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx
new file mode 100644
index 0000000000000..a9b65aaca7c59
--- /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 (
+    <Box sx={{ width: '100%' }}>
+      <Stack direction="row" spacing={1} sx={{ mb: 1 }}>
+        <Button size="small" onClick={toggleRows}>
+          {withRows ? 'Remove rows' : 'Add rows'}
+        </Button>
+      </Stack>
+      <Box sx={{ height: 345 }}>
+        <DataGrid
+          {...data}
+          loading
+          slotProps={{
+            loadingOverlay: {
+              noRowsVariant: 'skeleton',
+              variant: 'linear-progress',
+            },
+          }}
+          rows={withRows ? data.rows : []}
+        />
+      </Box>
+    </Box>
+  );
+}
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 0000000000000..b05ff555924f5
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx.preview
@@ -0,0 +1,11 @@
+<DataGrid
+  {...data}
+  loading
+  slotProps={{
+    loadingOverlay: {
+      noRowsVariant: 'skeleton',
+      variant: 'linear-progress',
+    },
+  }}
+  rows={withRows ? data.rows : []}
+/>
\ 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 0000000000000..b1fb5300c626e
--- /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 (
+    <Box sx={{ width: '100%', height: 340 }}>
+      <DataGrid
+        {...data}
+        initialState={{
+          ...data.initialState,
+          filter: {
+            filterModel: {
+              items: [],
+              quickFilterValues: ['abc'],
+            },
+          },
+        }}
+        slots={{ toolbar: GridToolbar }}
+        slotProps={{
+          toolbar: {
+            showQuickFilter: true,
+          },
+        }}
+      />
+    </Box>
+  );
+}
diff --git a/docs/data/data-grid/overlays/NoResultsOverlay.tsx b/docs/data/data-grid/overlays/NoResultsOverlay.tsx
new file mode 100644
index 0000000000000..b1fb5300c626e
--- /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 (
+    <Box sx={{ width: '100%', height: 340 }}>
+      <DataGrid
+        {...data}
+        initialState={{
+          ...data.initialState,
+          filter: {
+            filterModel: {
+              items: [],
+              quickFilterValues: ['abc'],
+            },
+          },
+        }}
+        slots={{ toolbar: GridToolbar }}
+        slotProps={{
+          toolbar: {
+            showQuickFilter: true,
+          },
+        }}
+      />
+    </Box>
+  );
+}
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 0000000000000..12b4676815dc6
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoResultsOverlay.tsx.preview
@@ -0,0 +1,18 @@
+<DataGrid
+  {...data}
+  initialState={{
+    ...data.initialState,
+    filter: {
+      filterModel: {
+        items: [],
+        quickFilterValues: ['abc'],
+      },
+    },
+  }}
+  slots={{ toolbar: GridToolbar }}
+  slotProps={{
+    toolbar: {
+      showQuickFilter: true,
+    },
+  }}
+/>
\ 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 0000000000000..d748985be91a1
--- /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 (
+    <Box sx={{ width: '100%', height: 340 }}>
+      <DataGrid {...data} rows={[]} />
+    </Box>
+  );
+}
diff --git a/docs/data/data-grid/overlays/NoRowsOverlay.tsx b/docs/data/data-grid/overlays/NoRowsOverlay.tsx
new file mode 100644
index 0000000000000..d748985be91a1
--- /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 (
+    <Box sx={{ width: '100%', height: 340 }}>
+      <DataGrid {...data} rows={[]} />
+    </Box>
+  );
+}
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 0000000000000..364fac54f80cc
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview
@@ -0,0 +1 @@
+<DataGrid {...data} rows={[]} />
\ 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 0000000000000..8c98b791776d7
--- /dev/null
+++ b/docs/data/data-grid/overlays/overlays.md
@@ -0,0 +1,48 @@
+# Data Grid - Overlays
+
+<p class="description">The various data grid overlays.</p>
+
+## 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 3977bf50b95af..0fc60917a85ef 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 0000000000000..eecf19cc11c3d
--- /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 <MarkdownDocs {...pageProps} />;
+}

From 7c226b688013eba8dcc46a8617405c00518956f1 Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 aae9623f9b675..5800b14cc52fe 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 b6134018c09a4..5e96f8adbe84c 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 e13f8f5f654c1..8fad1a4c40630 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 99dc8d050e6be..891f2d299dc2e 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 4e23b431b3c19..be79188d0bbc9 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 8f6dfd205c3f2..7ea702d9b2107 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 a5d1b21dcb29d..d811c4b10760c 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 9723176f87546..fd768bb4c0451 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 314428c421b6a..3ab3043f9e5fe 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 16a74ef16850e..09a72efb3edb5 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 <kenan.m.yusuf@gmail.com>
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 717d02db7ea93..36cb3830deb68 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<Theme>;
+    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<HTMLDivElement, GridLoadingOverlayPr
     const {
       variant = 'circular-progress',
       noRowsVariant = 'circular-progress',
-      sx,
+      style,
       ...other
     } = props;
     const apiRef = useGridApiContext();
@@ -56,7 +55,7 @@ const GridLoadingOverlay = React.forwardRef<HTMLDivElement, GridLoadingOverlayPr
     const activeVariant = LOADING_VARIANTS[rowsCount === 0 ? noRowsVariant : variant];
 
     return (
-      <GridOverlay ref={ref} sx={{ ...activeVariant.sx, ...sx }} {...other}>
+      <GridOverlay ref={ref} style={{ ...activeVariant.style, ...style }} {...other}>
         <activeVariant.component />
       </GridOverlay>
     );
diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx
index 005cf7c30ad5a..d00e8b256864c 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<HTMLDivElement>
 >(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<HTMLDivElement>(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(
+            <slots.skeletonCell key={`skeleton-filler-column-${i}`} width={emptyCellWidth} empty />,
+          );
+        }
+
+        rowCells.push(
           <slots.skeletonCell
             key={`skeleton-column-${i}-${column.field}`}
             type={column.type}
             align={column.align}
+            width={`var(${colWidthVar(colIndex)})`}
+            height={dimensions.rowHeight}
+            className={clsx(
+              isPinnedLeft && gridClasses['cell--pinnedLeft'],
+              isPinnedRight && gridClasses['cell--pinnedRight'],
+              showRightBorder && gridClasses['cell--withRightBorder'],
+              showLeftBorder && gridClasses['cell--withLeftBorder'],
+            )}
+            style={style}
           />,
         );
       }
-      array.push(<slots.skeletonCell key={`skeleton-filler-column-${i}`} empty />);
+
+      array.push(
+        <div
+          key={`skeleton-row-${i}`}
+          className={clsx(gridClasses.row, i === 0 && gridClasses['row--firstVisible'])}
+        >
+          {rowCells}
+        </div>,
+      );
     }
     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<Record<string, string>>(
+    (initialSize, column, i) => {
       const varName = colWidthVar(i);
       initialSize[varName] = `${column.computedWidth}px`;
-      templateColumn += ` var(${varName})`;
-      return [initialSize, templateColumn];
+      return initialSize;
     },
-    [{} as Record<string, string>, ''],
+    {},
   );
 
   // 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 07b191c02ffb7..19b710cefeb28 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<Record<GridColType, [number, number]>
   singleSelect: [40, 80],
 } as const;
 
-export interface GridSkeletonCellProps {
+export interface GridSkeletonCellProps extends React.HTMLAttributes<HTMLDivElement> {
   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<GridSkeletonCellProps, 'align'> & {
+type OwnerState = Pick<GridSkeletonCellProps, 'align' | 'empty'> & {
   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<HTMLDivElement> & 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<HTMLDivElement> & GridSkel
       } as const);
 
   return (
-    <div className={classes.root} style={{ height, maxWidth: width, minWidth: width }} {...other}>
+    <div
+      className={clsx(classes.root, className)}
+      style={{ height, maxWidth: width, minWidth: width, ...style }}
+      {...other}
+    >
       {!empty && <Skeleton {...skeletonProps} />}
     </div>
   );
@@ -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 <kenan.m.yusuf@gmail.com>
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 5800b14cc52fe..5592b85d4c82f 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 5e96f8adbe84c..cab9513fd3487 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 8fad1a4c40630..5e35b054db085 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 891f2d299dc2e..0eea28689572a 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&#39;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 be79188d0bbc9..ad898e4699d20 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&#39;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 7ea702d9b2107..9eaa71f8ec1c1 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&#39;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 d00e8b256864c..1f66b496572e2 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(
         <div
           key={`skeleton-row-${i}`}
-          className={clsx(gridClasses.row, i === 0 && gridClasses['row--firstVisible'])}
+          className={clsx(
+            gridClasses.row,
+            gridClasses.rowSkeleton,
+            i === 0 && gridClasses['row--firstVisible'],
+          )}
         >
           {rowCells}
         </div>,
diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts
index 6c212b777c7da..2e24df04dfe3f 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 d811c4b10760c..aedd29fb67cf4 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<GridClassKey>('MuiDataGrid', [
   'rowReorderCellContainer',
   'rowReorderCell',
   'rowReorderCell--draggable',
+  'rowSkeleton',
   'scrollArea--left',
   'scrollArea--right',
   'scrollArea',

From 5d54f0124bb0a2846d1161ad42cded6da5f09ae6 Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 1f66b496572e2..15cbddb58d0ee 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 = (
+          <slots.skeletonCell key={`skeleton-filler-column-${i}`} width={emptyCellWidth} empty />
+        );
 
-        if (isFirstPinnedRight) {
-          const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth;
-          const emptyCellWidth = Math.max(0, expandedWidth);
-          rowCells.push(
-            <slots.skeletonCell key={`skeleton-filler-column-${i}`} width={emptyCellWidth} empty />,
-          );
+        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 <kenan.m.yusuf@gmail.com>
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 15cbddb58d0ee..d3bb7316119c9 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(
           <slots.skeletonCell
             key={`skeleton-column-${i}-${column.field}`}
+            field={column.field}
             type={column.type}
             align={column.align}
-            width={`var(${colWidthVar(colIndex)})`}
+            width="var(--width)"
             height={dimensions.rowHeight}
             className={clsx(
               isPinnedLeft && gridClasses['cell--pinnedLeft'],
@@ -149,7 +149,9 @@ const GridSkeletonLoadingOverlay = React.forwardRef<
               showRightBorder && gridClasses['cell--withRightBorder'],
               showLeftBorder && gridClasses['cell--withLeftBorder'],
             )}
-            style={style}
+            style={
+              { '--width': `${column.computedWidth}px`, ...pinnedStyle } as React.CSSProperties
+            }
           />,
         );
 
@@ -185,32 +187,22 @@ const GridSkeletonLoadingOverlay = React.forwardRef<
     getPinnedStyle,
   ]);
 
-  const initialColWidthVariables = columns.reduce<Record<string, string>>(
-    (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 (
-    <SkeletonOverlay
-      className={classes.root}
-      ref={handleRef}
-      {...props}
-      style={{
-        ...initialColWidthVariables,
-        ...props.style,
-      }}
-    >
+    <SkeletonOverlay className={classes.root} ref={handleRef} {...props}>
       {children}
     </SkeletonOverlay>
   );
diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx
index 19b710cefeb28..5dd008e37dae9 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 (
     <div
+      data-field={field}
       className={clsx(classes.root, className)}
       style={{ height, maxWidth: width, minWidth: width, ...style }}
       {...other}

From 7de9a5612a0e38c222bce6015bafdbca1884133a Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 d3bb7316119c9..4865478610a06 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<HTMLDivElement>
@@ -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<HTMLElement>(
       `[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<HTMLElement>(
+        `.${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<HTMLElement>(
+        `.${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 <kenan.m.yusuf@gmail.com>
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 481a8a5949d21..a936b4296f194 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 (
+    <Box sx={{ position: 'relative', display: 'inline-flex' }}>
+      <CircularProgress variant="determinate" {...props} />
+      <Box
+        sx={{
+          position: 'absolute',
+          inset: 0,
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+        }}
+      >
+        <Typography variant="caption" component="div" color="text.primary">
+          {`${Math.round(props.value)}%`}
+        </Typography>
+      </Box>
+    </Box>
+  );
+}
+
+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 (
+    <StyledGridOverlay>
+      <CircularProgressWithLabel value={progress} />
+      <Box sx={{ mt: 2 }}>Loading rows…</Box>
+    </StyledGridOverlay>
+  );
+}
 
 export default function LoadingOverlayCustom() {
   const { data } = useDemoData({
@@ -14,7 +70,7 @@ export default function LoadingOverlayCustom() {
     <div style={{ height: 400, width: '100%' }}>
       <DataGrid
         slots={{
-          loadingOverlay: LinearProgress,
+          loadingOverlay: CustomLoadingOverlay,
         }}
         loading
         {...data}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx
index 2c5c594627777..59209a50e686a 100644
--- a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx
+++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx
@@ -1,7 +1,69 @@
 import * as React from 'react';
-import { DataGrid, GridSlots } from '@mui/x-data-grid';
-import LinearProgress from '@mui/material/LinearProgress';
+import { DataGrid } from '@mui/x-data-grid';
 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, {
+  CircularProgressProps,
+} 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: CircularProgressProps & { value: number },
+) {
+  return (
+    <Box sx={{ position: 'relative', display: 'inline-flex' }}>
+      <CircularProgress variant="determinate" {...props} />
+      <Box
+        sx={{
+          position: 'absolute',
+          inset: 0,
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+        }}
+      >
+        <Typography
+          variant="caption"
+          component="div"
+          color="text.primary"
+        >{`${Math.round(props.value)}%`}</Typography>
+      </Box>
+    </Box>
+  );
+}
+
+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 (
+    <StyledGridOverlay>
+      <CircularProgressWithLabel value={progress} />
+      <Box sx={{ mt: 2 }}>Loading rows…</Box>
+    </StyledGridOverlay>
+  );
+}
 
 export default function LoadingOverlayCustom() {
   const { data } = useDemoData({
@@ -14,7 +76,7 @@ export default function LoadingOverlayCustom() {
     <div style={{ height: 400, width: '100%' }}>
       <DataGrid
         slots={{
-          loadingOverlay: LinearProgress as GridSlots['loadingOverlay'],
+          loadingOverlay: CustomLoadingOverlay,
         }}
         loading
         {...data}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx.preview
index 84c2df95e8387..55daad1518bbb 100644
--- a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx.preview
+++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx.preview
@@ -1,6 +1,6 @@
 <DataGrid
   slots={{
-    loadingOverlay: LinearProgress as GridSlots['loadingOverlay'],
+    loadingOverlay: CustomLoadingOverlay,
   }}
   loading
   {...data}
diff --git a/docs/data/data-grid/overlays/overlays.md b/docs/data/data-grid/overlays/overlays.md
index 9f34d8c9bb1bf..80c0e79daf161 100644
--- a/docs/data/data-grid/overlays/overlays.md
+++ b/docs/data/data-grid/overlays/overlays.md
@@ -40,7 +40,9 @@ Use the demo below to try out the different variants. You can toggle whether the
 
 ### Custom component
 
-If you want to customize the no rows overlay, a component can be passed to the `loadingOverlay` slot. In the following demo, a [LinearProgress](/material-ui/react-progress/#linear) component is rendered in place of the default circular loading spinner.
+If you want to customize the no rows overlay, a component can be passed to the `loadingOverlay` slot.
+
+In the following demo, a labelled determinate [CircularProgress](/material-ui/react-progress/#circular-determinate) component is rendered in place of the default loading overlay, with some additional _Loading rows…_ text.
 
 {{"demo": "LoadingOverlayCustom.js", "bg": "inline"}}
 
@@ -52,7 +54,9 @@ The no rows overlay is displayed when the data grid has no rows.
 
 ### Custom component
 
-If you want to customize the no rows overlay, a component can be passed to the `noRowsOverlay` slot and rendered in place. In the following demo, an illustration is added on top of the default "No rows" message.
+If you want to customize the no rows overlay, a component can be passed to the `noRowsOverlay` slot and rendered in place.
+
+In the following demo, an illustration is added on top of the default "No rows" message.
 
 {{"demo": "NoRowsOverlayCustom.js", "bg": "inline"}}
 
@@ -64,7 +68,9 @@ The no results overlay is displayed when the data grid has no results after filt
 
 ### Custom component
 
-If you want to customize the no results overlay, a component can be passed to the `noResults` slot and rendered in place. In the following demo, an illustration is added on top of the default "No results found" message.
+If you want to customize the no results overlay, a component can be passed to the `noResults` slot and rendered in place.
+
+In the following demo, an illustration is added on top of the default "No results found" message.
 
 {{"demo": "NoResultsOverlayCustom.js", "bg": "inline"}}
 

From 5567ef3b17f21401533302cec7136cd4333a7a6c Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 4865478610a06..ffd930efdb621 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 = (
           <slots.skeletonCell key={`skeleton-filler-column-${i}`} width={emptyCellWidth} empty />
         );
+        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(<GridScrollbarFillerCell pinnedRight={pinnedColumns.right.length > 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 2e24df04dfe3f..d30f0d0afb51f 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 <kenan.m.yusuf@gmail.com>
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 0c65d421df94b..333a904448f63 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 (
-    <Box sx={{ width: '100%', height: 340 }}>
+    <Box sx={{ width: '100%', height: 400 }}>
       <DataGrid {...data} loading />
     </Box>
   );
diff --git a/docs/data/data-grid/overlays/LoadingOverlay.tsx b/docs/data/data-grid/overlays/LoadingOverlay.tsx
index 0c65d421df94b..333a904448f63 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 (
-    <Box sx={{ width: '100%', height: 340 }}>
+    <Box sx={{ width: '100%', height: 400 }}>
       <DataGrid {...data} loading />
     </Box>
   );
diff --git a/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js
new file mode 100644
index 0000000000000..5ad0b8a4b3e5f
--- /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 (
+    <Box sx={{ width: '100%', height: 400 }}>
+      <DataGrid
+        {...data}
+        loading
+        slotProps={{
+          loadingOverlay: {
+            variant: 'linear-progress',
+            noRowsVariant: 'linear-progress',
+          },
+        }}
+      />
+    </Box>
+  );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx
new file mode 100644
index 0000000000000..5ad0b8a4b3e5f
--- /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 (
+    <Box sx={{ width: '100%', height: 400 }}>
+      <DataGrid
+        {...data}
+        loading
+        slotProps={{
+          loadingOverlay: {
+            variant: 'linear-progress',
+            noRowsVariant: 'linear-progress',
+          },
+        }}
+      />
+    </Box>
+  );
+}
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 0000000000000..f4e0a9ef74176
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview
@@ -0,0 +1,10 @@
+<DataGrid
+  {...data}
+  loading
+  slotProps={{
+    loadingOverlay: {
+      variant: 'linear-progress',
+      noRowsVariant: 'linear-progress',
+    },
+  }}
+/>
\ 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 0000000000000..3bed3c29bbb56
--- /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 (
+    <Box sx={{ width: '100%', height: 400 }}>
+      <DataGridPro
+        {...data}
+        loading
+        slotProps={{
+          loadingOverlay: {
+            variant: 'skeleton',
+            noRowsVariant: 'skeleton',
+          },
+        }}
+        pinnedColumns={{
+          left: ['desk'],
+        }}
+      />
+    </Box>
+  );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx
new file mode 100644
index 0000000000000..3bed3c29bbb56
--- /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 (
+    <Box sx={{ width: '100%', height: 400 }}>
+      <DataGridPro
+        {...data}
+        loading
+        slotProps={{
+          loadingOverlay: {
+            variant: 'skeleton',
+            noRowsVariant: 'skeleton',
+          },
+        }}
+        pinnedColumns={{
+          left: ['desk'],
+        }}
+      />
+    </Box>
+  );
+}
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 0000000000000..57e1af72a47a5
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview
@@ -0,0 +1,13 @@
+<DataGridPro
+  {...data}
+  loading
+  slotProps={{
+    loadingOverlay: {
+      variant: 'skeleton',
+      noRowsVariant: 'skeleton',
+    },
+  }}
+  pinnedColumns={{
+    left: ['desk'],
+  }}
+/>
\ 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 da2b643ef58ab..0000000000000
--- 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 (
-    <FormControl>
-      <InputLabel htmlFor={id} id={labelId}>
-        {label}
-      </InputLabel>
-      <Select
-        label={label}
-        id={id}
-        labelId={labelId}
-        value={value}
-        onChange={onChange}
-        size="small"
-        sx={{ width: 180 }}
-      >
-        <MenuItem value="circular-progress">Circular Progress</MenuItem>
-        <MenuItem value="linear-progress">Linear Progress</MenuItem>
-        <MenuItem value="skeleton">Skeleton</MenuItem>
-      </Select>
-    </FormControl>
-  );
-}
-
-function RowsControl(props) {
-  const { checked, onChange } = props;
-  return (
-    <FormControlLabel
-      control={<Switch checked={checked} onChange={onChange} />}
-      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 (
-    <Box sx={{ width: '100%' }}>
-      <Stack direction="row" spacing={1} sx={{ my: 1 }}>
-        <VariantControl
-          label="Variant"
-          id="variant"
-          value={variant}
-          onChange={(event) => setVariant(event.target.value)}
-        />
-        <VariantControl
-          label="No rows variant"
-          id="noRowsVariant"
-          value={noRowsVariant}
-          onChange={(event) => setNoRowsVariant(event.target.value)}
-        />
-        <RowsControl
-          checked={withRows}
-          onChange={(event) => setWithRows(event.target.checked)}
-        />
-      </Stack>
-      <Box sx={{ height: 345 }}>
-        <DataGrid
-          {...data}
-          loading
-          slotProps={{
-            loadingOverlay: {
-              noRowsVariant,
-              variant,
-            },
-          }}
-          rows={withRows ? data.rows : []}
-        />
-      </Box>
-    </Box>
-  );
-}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx b/docs/data/data-grid/overlays/LoadingOverlayVariants.tsx
deleted file mode 100644
index f2c38fbaaa095..0000000000000
--- 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 (
-    <FormControl>
-      <InputLabel htmlFor={id} id={labelId}>
-        {label}
-      </InputLabel>
-      <Select
-        label={label}
-        id={id}
-        labelId={labelId}
-        value={value}
-        onChange={onChange}
-        size="small"
-        sx={{ width: 180 }}
-      >
-        <MenuItem value="circular-progress">Circular Progress</MenuItem>
-        <MenuItem value="linear-progress">Linear Progress</MenuItem>
-        <MenuItem value="skeleton">Skeleton</MenuItem>
-      </Select>
-    </FormControl>
-  );
-}
-
-function RowsControl(props: SwitchProps) {
-  const { checked, onChange } = props;
-  return (
-    <FormControlLabel
-      control={<Switch checked={checked} onChange={onChange} />}
-      label="Rows"
-    />
-  );
-}
-
-export default function LoadingOverlayVariants() {
-  const [withRows, setWithRows] = React.useState(true);
-  const [variant, setVariant] =
-    React.useState<GridLoadingOverlayVariant>('linear-progress');
-  const [noRowsVariant, setNoRowsVariant] =
-    React.useState<GridLoadingOverlayVariant>('skeleton');
-
-  const { data } = useDemoData({
-    dataSet: 'Commodity',
-    rowLength: 5,
-    maxColumns: 9,
-  });
-
-  return (
-    <Box sx={{ width: '100%' }}>
-      <Stack direction="row" spacing={1} sx={{ my: 1 }}>
-        <VariantControl
-          label="Variant"
-          id="variant"
-          value={variant}
-          onChange={(event) =>
-            setVariant(event.target.value as GridLoadingOverlayVariant)
-          }
-        />
-        <VariantControl
-          label="No rows variant"
-          id="noRowsVariant"
-          value={noRowsVariant}
-          onChange={(event) =>
-            setNoRowsVariant(event.target.value as GridLoadingOverlayVariant)
-          }
-        />
-        <RowsControl
-          checked={withRows}
-          onChange={(event) => setWithRows(event.target.checked)}
-        />
-      </Stack>
-      <Box sx={{ height: 345 }}>
-        <DataGrid
-          {...data}
-          loading
-          slotProps={{
-            loadingOverlay: {
-              noRowsVariant,
-              variant,
-            },
-          }}
-          rows={withRows ? data.rows : []}
-        />
-      </Box>
-    </Box>
-  );
-}
diff --git a/docs/data/data-grid/overlays/overlays.md b/docs/data/data-grid/overlays/overlays.md
index 80c0e79daf161..426e8b615b3ef 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 <kenan.m.yusuf@gmail.com>
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 <andrew.cherniavskii@gmail.com>
Signed-off-by: Kenan Yusuf <kenan.m.yusuf@gmail.com>
---
 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 3bed3c29bbb56..0e86a253dfcde 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'],
+          },
         }}
       />
     </Box>

From 6dc2f5d7a6f34a126d78a2afea098a64eeef6ff1 Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 3bed3c29bbb56..0e86a253dfcde 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'],
+          },
         }}
       />
     </Box>
diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview
index 57e1af72a47a5..9173142c9f99a 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 <kenan.m.yusuf@gmail.com>
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 36cb3830deb68..dc68ffccc6d89 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<HTMLDivElement, GridLoadingOverlayPr
       ...other
     } = props;
     const apiRef = useGridApiContext();
-    const rowsCount = apiRef.current.getRowsCount();
+    const rowsCount = useGridSelector(apiRef, gridRowCountSelector);
     const activeVariant = LOADING_VARIANTS[rowsCount === 0 ? noRowsVariant : variant];
 
     return (
diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx
index ffd930efdb621..cd622a892b1e8 100644
--- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx
+++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx
@@ -68,7 +68,10 @@ const GridSkeletonLoadingOverlay = React.forwardRef<
     [totalWidth, positions],
   );
   const allVisibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector);
-  const columns = allVisibleColumns.slice(0, inViewportCount);
+  const columns = React.useMemo(
+    () => 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 5dd008e37dae9..28a25539b7062 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<Record<GridColType, [number, number]>> = {
@@ -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 (
     <div

From c92a3a3d442dbfa54bd134449ece7a94e8d5d8aa Mon Sep 17 00:00:00 2001
From: Kenan Yusuf <kenan.m.yusuf@gmail.com>
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 cd622a892b1e8..62643bb382f36 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;