diff --git a/docs/data/data-grid/custom-columns/CustomColumnFullExample.js b/docs/data/data-grid/custom-columns/CustomColumnFullExample.js
new file mode 100644
index 000000000000..be7c002fb2b7
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/CustomColumnFullExample.js
@@ -0,0 +1,159 @@
+import * as React from 'react';
+import {
+ randomColor,
+ randomEmail,
+ randomInt,
+ randomName,
+ randomArrayItem,
+ random,
+} from '@mui/x-data-grid-generator';
+import { DataGrid, gridStringOrNumberComparator } from '@mui/x-data-grid';
+import { renderAvatar } from './cell-renderers/avatar';
+import { renderEmail } from './cell-renderers/email';
+import { renderEditRating, renderRating } from './cell-renderers/rating';
+import {
+ COUNTRY_ISO_OPTIONS,
+ renderCountry,
+ renderEditCountry,
+} from './cell-renderers/country';
+import { renderSparkline } from './cell-renderers/sparkline';
+import { renderEditProgress, renderProgress } from './cell-renderers/progress';
+import {
+ renderEditStatus,
+ renderStatus,
+ STATUS_OPTIONS,
+} from './cell-renderers/status';
+import {
+ INCOTERM_OPTIONS,
+ renderEditIncoterm,
+ renderIncoterm,
+} from './cell-renderers/incoterm';
+
+const columns = [
+ {
+ field: 'name',
+ headerName: 'Name',
+ width: 120,
+ editable: true,
+ },
+ {
+ field: 'avatar',
+ headerName: 'Avatar',
+ display: 'flex',
+ renderCell: renderAvatar,
+ valueGetter: (value, row) =>
+ row.name == null || row.avatar == null
+ ? null
+ : { name: row.name, color: row.avatar },
+ sortable: false,
+ filterable: false,
+ },
+ {
+ field: 'email',
+ headerName: 'Email',
+ renderCell: renderEmail,
+ width: 150,
+ editable: true,
+ },
+ {
+ field: 'rating',
+ headerName: 'Rating',
+ display: 'flex',
+ renderCell: renderRating,
+ renderEditCell: renderEditRating,
+ width: 180,
+ type: 'number',
+ editable: true,
+ availableAggregationFunctions: ['avg', 'min', 'max', 'size'],
+ },
+ {
+ field: 'country',
+ headerName: 'Country',
+ type: 'singleSelect',
+ valueOptions: COUNTRY_ISO_OPTIONS,
+ valueFormatter: (value) => value?.label,
+ renderCell: renderCountry,
+ renderEditCell: renderEditCountry,
+ sortComparator: (v1, v2, param1, param2) =>
+ gridStringOrNumberComparator(v1.label, v2.label, param1, param2),
+ width: 150,
+ editable: true,
+ },
+ {
+ field: 'salary',
+ headerName: 'Salary',
+ type: 'number',
+ valueFormatter: (value) => {
+ if (!value || typeof value !== 'number') {
+ return value;
+ }
+ return `$${value.toLocaleString()}`;
+ },
+ editable: true,
+ },
+ {
+ field: 'monthlyActivity',
+ headerName: 'Monthly activity',
+ type: 'custom',
+ resizable: false,
+ filterable: false,
+ sortable: false,
+ editable: false,
+ groupable: false,
+ display: 'flex',
+ renderCell: renderSparkline,
+ width: 150,
+ valueGetter: (value, row) => row.monthlyActivity,
+ },
+ {
+ field: 'budget',
+ headerName: 'Budget left',
+ renderCell: renderProgress,
+ renderEditCell: renderEditProgress,
+ availableAggregationFunctions: ['min', 'max', 'avg', 'size'],
+ type: 'number',
+ width: 120,
+ editable: true,
+ },
+ {
+ field: 'status',
+ headerName: 'Status',
+ renderCell: renderStatus,
+ renderEditCell: renderEditStatus,
+ type: 'singleSelect',
+ valueOptions: STATUS_OPTIONS,
+ width: 150,
+ editable: true,
+ },
+ {
+ field: 'incoTerm',
+ headerName: 'Incoterm',
+ renderCell: renderIncoterm,
+ renderEditCell: renderEditIncoterm,
+ type: 'singleSelect',
+ valueOptions: INCOTERM_OPTIONS,
+ editable: true,
+ },
+];
+
+const rows = Array.from({ length: 10 }, (_, index) => ({
+ id: index,
+ name: randomName({}, {}),
+ avatar: randomColor(),
+ email: randomEmail(),
+ rating: randomInt(1, 5),
+ country: randomArrayItem(COUNTRY_ISO_OPTIONS),
+ salary: randomInt(35000, 80000),
+ monthlyActivity: Array.from({ length: 30 }, () => randomInt(1, 25)),
+ budget: random(0, 1).toPrecision(),
+ status: randomArrayItem(STATUS_OPTIONS),
+ incoTerm: randomArrayItem(INCOTERM_OPTIONS),
+}));
+
+export default function CustomColumnFullExample() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/CustomColumnFullExample.tsx b/docs/data/data-grid/custom-columns/CustomColumnFullExample.tsx
new file mode 100644
index 000000000000..2f0fa78b81a4
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/CustomColumnFullExample.tsx
@@ -0,0 +1,164 @@
+import * as React from 'react';
+import {
+ randomColor,
+ randomEmail,
+ randomInt,
+ randomName,
+ randomArrayItem,
+ random,
+} from '@mui/x-data-grid-generator';
+import {
+ DataGrid,
+ GridColDef,
+ gridStringOrNumberComparator,
+} from '@mui/x-data-grid';
+import { renderAvatar } from './cell-renderers/avatar';
+import { renderEmail } from './cell-renderers/email';
+import { renderEditRating, renderRating } from './cell-renderers/rating';
+import {
+ COUNTRY_ISO_OPTIONS,
+ CountryIsoOption,
+ renderCountry,
+ renderEditCountry,
+} from './cell-renderers/country';
+import { renderSparkline } from './cell-renderers/sparkline';
+import { renderEditProgress, renderProgress } from './cell-renderers/progress';
+import {
+ renderEditStatus,
+ renderStatus,
+ STATUS_OPTIONS,
+} from './cell-renderers/status';
+import {
+ INCOTERM_OPTIONS,
+ renderEditIncoterm,
+ renderIncoterm,
+} from './cell-renderers/incoterm';
+
+const columns: GridColDef<(typeof rows)[number]>[] = [
+ {
+ field: 'name',
+ headerName: 'Name',
+ width: 120,
+ editable: true,
+ },
+ {
+ field: 'avatar',
+ headerName: 'Avatar',
+ display: 'flex',
+ renderCell: renderAvatar,
+ valueGetter: (value, row) =>
+ row.name == null || row.avatar == null
+ ? null
+ : { name: row.name, color: row.avatar },
+ sortable: false,
+ filterable: false,
+ } as GridColDef,
+ {
+ field: 'email',
+ headerName: 'Email',
+ renderCell: renderEmail,
+ width: 150,
+ editable: true,
+ },
+ {
+ field: 'rating',
+ headerName: 'Rating',
+ display: 'flex',
+ renderCell: renderRating,
+ renderEditCell: renderEditRating,
+ width: 180,
+ type: 'number',
+ editable: true,
+ availableAggregationFunctions: ['avg', 'min', 'max', 'size'],
+ },
+ {
+ field: 'country',
+ headerName: 'Country',
+ type: 'singleSelect',
+ valueOptions: COUNTRY_ISO_OPTIONS,
+ valueFormatter: (value: CountryIsoOption) => value?.label,
+ renderCell: renderCountry,
+ renderEditCell: renderEditCountry,
+ sortComparator: (v1, v2, param1, param2) =>
+ gridStringOrNumberComparator(v1.label, v2.label, param1, param2),
+ width: 150,
+ editable: true,
+ } as GridColDef,
+ {
+ field: 'salary',
+ headerName: 'Salary',
+ type: 'number',
+ valueFormatter: (value?: number) => {
+ if (!value || typeof value !== 'number') {
+ return value;
+ }
+ return `$${value.toLocaleString()}`;
+ },
+ editable: true,
+ },
+ {
+ field: 'monthlyActivity',
+ headerName: 'Monthly activity',
+ type: 'custom',
+ resizable: false,
+ filterable: false,
+ sortable: false,
+ editable: false,
+ groupable: false,
+ display: 'flex',
+ renderCell: renderSparkline,
+ width: 150,
+ valueGetter: (value, row) => row.monthlyActivity,
+ },
+ {
+ field: 'budget',
+ headerName: 'Budget left',
+ renderCell: renderProgress,
+ renderEditCell: renderEditProgress,
+ availableAggregationFunctions: ['min', 'max', 'avg', 'size'],
+ type: 'number',
+ width: 120,
+ editable: true,
+ },
+ {
+ field: 'status',
+ headerName: 'Status',
+ renderCell: renderStatus,
+ renderEditCell: renderEditStatus,
+ type: 'singleSelect',
+ valueOptions: STATUS_OPTIONS,
+ width: 150,
+ editable: true,
+ },
+ {
+ field: 'incoTerm',
+ headerName: 'Incoterm',
+ renderCell: renderIncoterm,
+ renderEditCell: renderEditIncoterm,
+ type: 'singleSelect',
+ valueOptions: INCOTERM_OPTIONS,
+ editable: true,
+ },
+];
+
+const rows = Array.from({ length: 10 }, (_, index) => ({
+ id: index,
+ name: randomName({}, {}),
+ avatar: randomColor(),
+ email: randomEmail(),
+ rating: randomInt(1, 5),
+ country: randomArrayItem(COUNTRY_ISO_OPTIONS),
+ salary: randomInt(35000, 80000),
+ monthlyActivity: Array.from({ length: 30 }, () => randomInt(1, 25)),
+ budget: random(0, 1).toPrecision(),
+ status: randomArrayItem(STATUS_OPTIONS),
+ incoTerm: randomArrayItem(INCOTERM_OPTIONS),
+}));
+
+export default function CustomColumnFullExample() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/CustomColumnFullExample.tsx.preview b/docs/data/data-grid/custom-columns/CustomColumnFullExample.tsx.preview
new file mode 100644
index 000000000000..074afd47c441
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/CustomColumnFullExample.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/avatar.js b/docs/data/data-grid/custom-columns/cell-renderers/avatar.js
new file mode 100644
index 000000000000..a928435e763c
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/avatar.js
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import Avatar from '@mui/material/Avatar';
+
+export function renderAvatar(params) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+ {params.value.name.toUpperCase().substring(0, 1)}
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/avatar.tsx b/docs/data/data-grid/custom-columns/cell-renderers/avatar.tsx
new file mode 100644
index 000000000000..e2f1f5b5b1a9
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/avatar.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react';
+import Avatar from '@mui/material/Avatar';
+import { GridRenderCellParams } from '@mui/x-data-grid';
+
+export function renderAvatar(
+ params: GridRenderCellParams<{ name: string; color: string }, any, any>,
+) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+ {params.value.name.toUpperCase().substring(0, 1)}
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/country.js b/docs/data/data-grid/custom-columns/cell-renderers/country.js
new file mode 100644
index 000000000000..d8dbd277fb67
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/country.js
@@ -0,0 +1,130 @@
+import * as React from 'react';
+import { useGridApiContext } from '@mui/x-data-grid';
+import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete';
+
+import Box from '@mui/material/Box';
+import InputBase from '@mui/material/InputBase';
+import { styled } from '@mui/material/styles';
+
+export const COUNTRY_ISO_OPTIONS = [
+ { value: 'DE', code: 'DE', label: 'Germany', phone: '49' },
+ { value: 'ES', code: 'ES', label: 'Spain', phone: '34' },
+ { value: 'FR', code: 'FR', label: 'France', phone: '33' },
+ { value: 'GB', code: 'GB', label: 'United Kingdom', phone: '44' },
+];
+
+const Country = React.memo(function Country(props) {
+ const { value } = props;
+
+ return (
+ img': {
+ mr: 0.5,
+ flexShrink: 0,
+ width: '20px',
+ },
+ }}
+ >
+
+
+ {value.label}
+
+
+ );
+});
+
+const StyledAutocomplete = styled(Autocomplete)(({ theme }) => ({
+ height: '100%',
+ [`& .${autocompleteClasses.inputRoot}`]: {
+ ...theme.typography.body2,
+ padding: '1px 0',
+ height: '100%',
+ '& input': {
+ padding: '0 16px',
+ height: '100%',
+ },
+ },
+}));
+
+function EditCountry(props) {
+ const { id, value, field } = props;
+
+ const apiRef = useGridApiContext();
+
+ const handleChange = React.useCallback(
+ async (event, newValue) => {
+ await apiRef.current.setEditCellValue({ id, field, value: newValue }, event);
+ apiRef.current.stopCellEditMode({ id, field });
+ },
+ [apiRef, field, id],
+ );
+
+ return (
+ option.label}
+ autoHighlight
+ fullWidth
+ open
+ disableClearable
+ renderOption={(optionProps, option) => (
+ img': {
+ mr: 1.5,
+ flexShrink: 0,
+ },
+ }}
+ {...optionProps}
+ key={option.code}
+ >
+
+ {option.label}
+
+ )}
+ renderInput={(params) => (
+
+ )}
+ />
+ );
+}
+
+export function renderCountry(params) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return ;
+}
+
+export function renderEditCountry(params) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/country.tsx b/docs/data/data-grid/custom-columns/cell-renderers/country.tsx
new file mode 100644
index 000000000000..54a3a293aac9
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/country.tsx
@@ -0,0 +1,152 @@
+import * as React from 'react';
+import {
+ GridRenderCellParams,
+ GridRenderEditCellParams,
+ useGridApiContext,
+} from '@mui/x-data-grid';
+import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete';
+import type { AutocompleteProps } from '@mui/material/Autocomplete';
+import Box from '@mui/material/Box';
+import InputBase from '@mui/material/InputBase';
+import { styled } from '@mui/material/styles';
+
+export interface CountryIsoOption {
+ value: string;
+ code: string;
+ label: string;
+ phone: string;
+ suggested?: boolean;
+}
+
+export const COUNTRY_ISO_OPTIONS: CountryIsoOption[] = [
+ { value: 'DE', code: 'DE', label: 'Germany', phone: '49' },
+ { value: 'ES', code: 'ES', label: 'Spain', phone: '34' },
+ { value: 'FR', code: 'FR', label: 'France', phone: '33' },
+ { value: 'GB', code: 'GB', label: 'United Kingdom', phone: '44' },
+];
+
+interface CountryProps {
+ value: CountryIsoOption;
+}
+
+const Country = React.memo(function Country(props: CountryProps) {
+ const { value } = props;
+
+ return (
+ img': {
+ mr: 0.5,
+ flexShrink: 0,
+ width: '20px',
+ },
+ }}
+ >
+
+
+ {value.label}
+
+
+ );
+});
+
+const StyledAutocomplete = styled(Autocomplete)(({ theme }) => ({
+ height: '100%',
+ [`& .${autocompleteClasses.inputRoot}`]: {
+ ...theme.typography.body2,
+ padding: '1px 0',
+ height: '100%',
+ '& input': {
+ padding: '0 16px',
+ height: '100%',
+ },
+ },
+})) as typeof Autocomplete;
+
+function EditCountry(props: GridRenderEditCellParams) {
+ const { id, value, field } = props;
+
+ const apiRef = useGridApiContext();
+
+ const handleChange = React.useCallback<
+ NonNullable['onChange']>
+ >(
+ async (event, newValue) => {
+ await apiRef.current.setEditCellValue({ id, field, value: newValue }, event);
+ apiRef.current.stopCellEditMode({ id, field });
+ },
+ [apiRef, field, id],
+ );
+
+ return (
+
+ value={value}
+ onChange={handleChange}
+ options={COUNTRY_ISO_OPTIONS}
+ getOptionLabel={(option: any) => option.label}
+ autoHighlight
+ fullWidth
+ open
+ disableClearable
+ renderOption={(optionProps, option: any) => (
+ img': {
+ mr: 1.5,
+ flexShrink: 0,
+ },
+ }}
+ {...optionProps}
+ key={option.code}
+ >
+
+ {option.label}
+
+ )}
+ renderInput={(params) => (
+
+ )}
+ />
+ );
+}
+
+export function renderCountry(
+ params: GridRenderCellParams,
+) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return ;
+}
+
+export function renderEditCountry(
+ params: GridRenderEditCellParams,
+) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/email.js b/docs/data/data-grid/custom-columns/cell-renderers/email.js
new file mode 100644
index 000000000000..a80f6c387fba
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/email.js
@@ -0,0 +1,32 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+
+const Link = styled('a')({
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ color: 'inherit',
+});
+
+const DemoLink = React.memo(function DemoLink(props) {
+ const handleClick = (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ };
+
+ return (
+
+ {props.children}
+
+ );
+});
+
+export function renderEmail(params) {
+ const email = params.value ?? '';
+
+ return (
+
+ {email}
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/email.tsx b/docs/data/data-grid/custom-columns/cell-renderers/email.tsx
new file mode 100644
index 000000000000..e58fc5d78261
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/email.tsx
@@ -0,0 +1,39 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import { GridRenderCellParams } from '@mui/x-data-grid';
+
+interface DemoLinkProps {
+ href: string;
+ children: string;
+ tabIndex: number;
+}
+
+const Link = styled('a')({
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ color: 'inherit',
+});
+
+const DemoLink = React.memo(function DemoLink(props: DemoLinkProps) {
+ const handleClick = (event: React.MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ };
+
+ return (
+
+ {props.children}
+
+ );
+});
+
+export function renderEmail(params: GridRenderCellParams) {
+ const email = params.value ?? '';
+
+ return (
+
+ {email}
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/incoterm.js b/docs/data/data-grid/custom-columns/cell-renderers/incoterm.js
new file mode 100644
index 000000000000..524293d92754
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/incoterm.js
@@ -0,0 +1,104 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText';
+import MenuItem from '@mui/material/MenuItem';
+
+import Select from '@mui/material/Select';
+import Tooltip from '@mui/material/Tooltip';
+import InfoIcon from '@mui/icons-material/Info';
+import { useGridApiContext } from '@mui/x-data-grid';
+
+export const INCOTERM_OPTIONS = [
+ 'EXW (Ex Works)',
+ 'FAS (Free Alongside Ship)',
+ 'FCA (Free Carrier)',
+ 'CPT (Carriage Paid To)',
+ 'DAP (Delivered at Place)',
+ 'DPU (Delivered at Place Unloaded)',
+ 'DDP (Delivered Duty Paid)',
+];
+
+const Incoterm = React.memo(function Incoterm(props) {
+ const { value } = props;
+
+ if (!value) {
+ return null;
+ }
+
+ const valueStr = value.toString();
+ const tooltip = valueStr.slice(valueStr.indexOf('(') + 1, valueStr.indexOf(')'));
+ const code = valueStr.slice(0, valueStr.indexOf('(')).trim();
+
+ return (
+
+ {code}
+
+
+
+
+ );
+});
+
+function EditIncoterm(props) {
+ const { id, value, field } = props;
+
+ const apiRef = useGridApiContext();
+
+ const handleChange = async (event) => {
+ await apiRef.current.setEditCellValue(
+ { id, field, value: event.target.value },
+ event,
+ );
+ apiRef.current.stopCellEditMode({ id, field });
+ };
+
+ const handleClose = (event, reason) => {
+ if (reason === 'backdropClick') {
+ apiRef.current.stopCellEditMode({ id, field });
+ }
+ };
+
+ return (
+
+ );
+}
+
+export function renderIncoterm(params) {
+ return ;
+}
+
+export function renderEditIncoterm(params) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/incoterm.tsx b/docs/data/data-grid/custom-columns/cell-renderers/incoterm.tsx
new file mode 100644
index 000000000000..de90756039bf
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/incoterm.tsx
@@ -0,0 +1,116 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText';
+import MenuItem from '@mui/material/MenuItem';
+import type { MenuProps } from '@mui/material/Menu';
+import Select, { SelectProps } from '@mui/material/Select';
+import Tooltip from '@mui/material/Tooltip';
+import InfoIcon from '@mui/icons-material/Info';
+import {
+ GridRenderCellParams,
+ GridRenderEditCellParams,
+ useGridApiContext,
+} from '@mui/x-data-grid';
+
+export const INCOTERM_OPTIONS = [
+ 'EXW (Ex Works)',
+ 'FAS (Free Alongside Ship)',
+ 'FCA (Free Carrier)',
+ 'CPT (Carriage Paid To)',
+ 'DAP (Delivered at Place)',
+ 'DPU (Delivered at Place Unloaded)',
+ 'DDP (Delivered Duty Paid)',
+];
+
+interface IncotermProps {
+ value: string | null | undefined;
+}
+
+const Incoterm = React.memo(function Incoterm(props: IncotermProps) {
+ const { value } = props;
+
+ if (!value) {
+ return null;
+ }
+
+ const valueStr = value.toString();
+ const tooltip = valueStr.slice(valueStr.indexOf('(') + 1, valueStr.indexOf(')'));
+ const code = valueStr.slice(0, valueStr.indexOf('(')).trim();
+
+ return (
+
+ {code}
+
+
+
+
+ );
+});
+
+function EditIncoterm(props: GridRenderEditCellParams) {
+ const { id, value, field } = props;
+
+ const apiRef = useGridApiContext();
+
+ const handleChange: SelectProps['onChange'] = async (event) => {
+ await apiRef.current.setEditCellValue(
+ { id, field, value: event.target.value as any },
+ event,
+ );
+ apiRef.current.stopCellEditMode({ id, field });
+ };
+
+ const handleClose: MenuProps['onClose'] = (event, reason) => {
+ if (reason === 'backdropClick') {
+ apiRef.current.stopCellEditMode({ id, field });
+ }
+ };
+
+ return (
+
+ );
+}
+
+export function renderIncoterm(
+ params: GridRenderCellParams,
+) {
+ return ;
+}
+
+export function renderEditIncoterm(
+ params: GridRenderEditCellParams,
+) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/progress.js b/docs/data/data-grid/custom-columns/cell-renderers/progress.js
new file mode 100644
index 000000000000..d604aaca719a
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/progress.js
@@ -0,0 +1,178 @@
+import * as React from 'react';
+import clsx from 'clsx';
+import { useGridApiContext } from '@mui/x-data-grid';
+import { alpha, styled } from '@mui/material/styles';
+import Slider, { sliderClasses } from '@mui/material/Slider';
+import Tooltip from '@mui/material/Tooltip';
+import { debounce } from '@mui/material/utils';
+
+const Center = styled('div')({
+ height: '100%',
+ display: 'flex',
+ alignItems: 'center',
+});
+
+const Element = styled('div')(({ theme }) => ({
+ border: `1px solid ${(theme.vars || theme).palette.divider}`,
+ position: 'relative',
+ overflow: 'hidden',
+ width: '100%',
+ height: 26,
+ borderRadius: 2,
+}));
+
+const Value = styled('div')({
+ position: 'absolute',
+ lineHeight: '24px',
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+});
+
+const Bar = styled('div')({
+ height: '100%',
+ '&.low': {
+ backgroundColor: '#f44336',
+ },
+ '&.medium': {
+ backgroundColor: '#efbb5aa3',
+ },
+ '&.high': {
+ backgroundColor: '#088208a3',
+ },
+});
+
+const ProgressBar = React.memo(function ProgressBar(props) {
+ const { value } = props;
+ const valueInPercent = value * 100;
+
+ return (
+
+ {`${valueInPercent.toLocaleString()} %`}
+ = 30 && valueInPercent <= 70,
+ high: valueInPercent > 70,
+ })}
+ style={{ maxWidth: `${valueInPercent}%` }}
+ />
+
+ );
+});
+
+const StyledSlider = styled(Slider)(({ theme }) => ({
+ display: 'flex',
+ height: '100%',
+ width: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 0,
+ borderRadius: 0,
+ [`& .${sliderClasses.rail}`]: {
+ height: '100%',
+ backgroundColor: 'transparent',
+ },
+ [`& .${sliderClasses.track}`]: {
+ height: '100%',
+ transition: theme.transitions.create('background-color', {
+ duration: theme.transitions.duration.shorter,
+ }),
+ '&.low': {
+ backgroundColor: '#f44336',
+ },
+ '&.medium': {
+ backgroundColor: '#efbb5aa3',
+ },
+ '&.high': {
+ backgroundColor: '#088208a3',
+ },
+ },
+ [`& .${sliderClasses.thumb}`]: {
+ height: '100%',
+ width: 5,
+ borderRadius: 0,
+ marginTop: 0,
+ backgroundColor: alpha('#000000', 0.2),
+ },
+}));
+
+const ValueLabelComponent = React.memo(function ValueLabelComponent(props) {
+ const { children, open, value } = props;
+
+ return (
+
+ {children}
+
+ );
+});
+
+function EditProgress(props) {
+ const { id, value, field } = props;
+ const [valueState, setValueState] = React.useState(Number(value));
+
+ const apiRef = useGridApiContext();
+
+ const updateCellEditProps = React.useCallback(
+ (newValue) => {
+ apiRef.current.setEditCellValue({ id, field, value: newValue });
+ },
+ [apiRef, field, id],
+ );
+
+ const debouncedUpdateCellEditProps = React.useMemo(
+ () => debounce(updateCellEditProps, 60),
+ [updateCellEditProps],
+ );
+
+ const handleChange = (event, newValue) => {
+ setValueState(newValue);
+ debouncedUpdateCellEditProps(newValue);
+ };
+
+ React.useEffect(() => {
+ setValueState(Number(value));
+ }, [value]);
+
+ const handleRef = (element) => {
+ if (element) {
+ element.querySelector('[type="range"]').focus();
+ }
+ };
+
+ return (
+ = 0.3 && valueState <= 0.7,
+ high: valueState > 0.7,
+ }),
+ }}
+ value={valueState}
+ max={1}
+ step={0.00001}
+ onChange={handleChange}
+ components={{ ValueLabel: ValueLabelComponent }}
+ valueLabelDisplay="auto"
+ valueLabelFormat={(newValue) => `${(newValue * 100).toLocaleString()} %`}
+ />
+ );
+}
+
+export function renderProgress(params) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+
+
+ );
+}
+
+export function renderEditProgress(params) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/progress.tsx b/docs/data/data-grid/custom-columns/cell-renderers/progress.tsx
new file mode 100644
index 000000000000..a831d003ff58
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/progress.tsx
@@ -0,0 +1,186 @@
+import * as React from 'react';
+import clsx from 'clsx';
+import {
+ GridRenderCellParams,
+ GridRenderEditCellParams,
+ useGridApiContext,
+} from '@mui/x-data-grid';
+import { alpha, styled } from '@mui/material/styles';
+import Slider, { sliderClasses, SliderProps } from '@mui/material/Slider';
+import Tooltip from '@mui/material/Tooltip';
+import { debounce } from '@mui/material/utils';
+
+interface ProgressBarProps {
+ value: number;
+}
+
+const Center = styled('div')({
+ height: '100%',
+ display: 'flex',
+ alignItems: 'center',
+});
+
+const Element = styled('div')(({ theme }) => ({
+ border: `1px solid ${(theme.vars || theme).palette.divider}`,
+ position: 'relative',
+ overflow: 'hidden',
+ width: '100%',
+ height: 26,
+ borderRadius: 2,
+}));
+
+const Value = styled('div')({
+ position: 'absolute',
+ lineHeight: '24px',
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+});
+
+const Bar = styled('div')({
+ height: '100%',
+ '&.low': {
+ backgroundColor: '#f44336',
+ },
+ '&.medium': {
+ backgroundColor: '#efbb5aa3',
+ },
+ '&.high': {
+ backgroundColor: '#088208a3',
+ },
+});
+
+const ProgressBar = React.memo(function ProgressBar(props: ProgressBarProps) {
+ const { value } = props;
+ const valueInPercent = value * 100;
+
+ return (
+
+ {`${valueInPercent.toLocaleString()} %`}
+ = 30 && valueInPercent <= 70,
+ high: valueInPercent > 70,
+ })}
+ style={{ maxWidth: `${valueInPercent}%` }}
+ />
+
+ );
+});
+
+const StyledSlider = styled(Slider)(({ theme }) => ({
+ display: 'flex',
+ height: '100%',
+ width: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 0,
+ borderRadius: 0,
+ [`& .${sliderClasses.rail}`]: {
+ height: '100%',
+ backgroundColor: 'transparent',
+ },
+ [`& .${sliderClasses.track}`]: {
+ height: '100%',
+ transition: theme.transitions.create('background-color', {
+ duration: theme.transitions.duration.shorter,
+ }),
+ '&.low': {
+ backgroundColor: '#f44336',
+ },
+ '&.medium': {
+ backgroundColor: '#efbb5aa3',
+ },
+ '&.high': {
+ backgroundColor: '#088208a3',
+ },
+ },
+ [`& .${sliderClasses.thumb}`]: {
+ height: '100%',
+ width: 5,
+ borderRadius: 0,
+ marginTop: 0,
+ backgroundColor: alpha('#000000', 0.2),
+ },
+}));
+
+const ValueLabelComponent = React.memo(function ValueLabelComponent(props: any) {
+ const { children, open, value } = props;
+
+ return (
+
+ {children}
+
+ );
+});
+
+function EditProgress(props: GridRenderEditCellParams) {
+ const { id, value, field } = props;
+ const [valueState, setValueState] = React.useState(Number(value));
+
+ const apiRef = useGridApiContext();
+
+ const updateCellEditProps = React.useCallback(
+ (newValue: number) => {
+ apiRef.current.setEditCellValue({ id, field, value: newValue });
+ },
+ [apiRef, field, id],
+ );
+
+ const debouncedUpdateCellEditProps = React.useMemo(
+ () => debounce(updateCellEditProps, 60),
+ [updateCellEditProps],
+ );
+
+ const handleChange = (event: Event, newValue: number | number[]) => {
+ setValueState(newValue as number);
+ debouncedUpdateCellEditProps(newValue as number);
+ };
+
+ React.useEffect(() => {
+ setValueState(Number(value));
+ }, [value]);
+
+ const handleRef: SliderProps['ref'] = (element) => {
+ if (element) {
+ element.querySelector('[type="range"]')!.focus();
+ }
+ };
+
+ return (
+ = 0.3 && valueState <= 0.7,
+ high: valueState > 0.7,
+ }),
+ }}
+ value={valueState}
+ max={1}
+ step={0.00001}
+ onChange={handleChange}
+ components={{ ValueLabel: ValueLabelComponent }}
+ valueLabelDisplay="auto"
+ valueLabelFormat={(newValue) => `${(newValue * 100).toLocaleString()} %`}
+ />
+ );
+}
+
+export function renderProgress(params: GridRenderCellParams) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+
+
+ );
+}
+
+export function renderEditProgress(params: GridRenderEditCellParams) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/rating.js b/docs/data/data-grid/custom-columns/cell-renderers/rating.js
new file mode 100644
index 000000000000..46d1d09ea751
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/rating.js
@@ -0,0 +1,93 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Rating from '@mui/material/Rating';
+import { useGridApiContext } from '@mui/x-data-grid';
+
+const RatingValue = React.memo(function RatingValue(props) {
+ const { value } = props;
+ return (
+
+ {' '}
+ {Math.round(Number(value) * 10) / 10}
+
+ );
+});
+
+function EditRating(props) {
+ const { id, value, field } = props;
+
+ const apiRef = useGridApiContext();
+
+ const changedThroughKeyboard = React.useRef(false);
+
+ const handleChange = async (event) => {
+ await apiRef.current.setEditCellValue(
+ { id, field, value: Number(event.target.value) },
+ event,
+ );
+ if (!changedThroughKeyboard.current) {
+ apiRef.current.stopCellEditMode({ id, field });
+ }
+ changedThroughKeyboard.current = false;
+ };
+
+ const handleRef = (element) => {
+ if (element) {
+ if (value !== 0) {
+ element.querySelector(`input[value="${value}"]`).focus();
+ } else {
+ element.querySelector('input[value=""]').focus();
+ }
+ }
+ };
+
+ const handleKeyDown = (event) => {
+ if (event.key.startsWith('Arrow')) {
+ changedThroughKeyboard.current = true;
+ } else {
+ changedThroughKeyboard.current = false;
+ }
+ };
+
+ return (
+
+
+ {Number(value)}
+
+ );
+}
+
+export function renderRating(params) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return ;
+}
+
+export function renderEditRating(params) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/rating.tsx b/docs/data/data-grid/custom-columns/cell-renderers/rating.tsx
new file mode 100644
index 000000000000..9eb9c68ce767
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/rating.tsx
@@ -0,0 +1,101 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Rating from '@mui/material/Rating';
+import {
+ GridRenderCellParams,
+ GridRenderEditCellParams,
+ useGridApiContext,
+} from '@mui/x-data-grid';
+
+interface RatingValueProps {
+ value: number;
+}
+
+const RatingValue = React.memo(function RatingValue(props: RatingValueProps) {
+ const { value } = props;
+ return (
+
+ {' '}
+ {Math.round(Number(value) * 10) / 10}
+
+ );
+});
+
+function EditRating(props: GridRenderEditCellParams) {
+ const { id, value, field } = props;
+
+ const apiRef = useGridApiContext();
+
+ const changedThroughKeyboard = React.useRef(false);
+
+ const handleChange = async (event: any) => {
+ await apiRef.current.setEditCellValue(
+ { id, field, value: Number(event.target.value) },
+ event,
+ );
+ if (!changedThroughKeyboard.current) {
+ apiRef.current.stopCellEditMode({ id, field });
+ }
+ changedThroughKeyboard.current = false;
+ };
+
+ const handleRef = (element: HTMLElement | undefined) => {
+ if (element) {
+ if (value !== 0) {
+ element.querySelector(`input[value="${value}"]`)!.focus();
+ } else {
+ element.querySelector('input[value=""]')!.focus();
+ }
+ }
+ };
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key.startsWith('Arrow')) {
+ changedThroughKeyboard.current = true;
+ } else {
+ changedThroughKeyboard.current = false;
+ }
+ };
+
+ return (
+
+
+ {Number(value)}
+
+ );
+}
+
+export function renderRating(params: GridRenderCellParams) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return ;
+}
+
+export function renderEditRating(params: GridRenderEditCellParams) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/sparkline.js b/docs/data/data-grid/custom-columns/cell-renderers/sparkline.js
new file mode 100644
index 000000000000..b6b083cf7b54
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/sparkline.js
@@ -0,0 +1,16 @@
+import * as React from 'react';
+import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
+
+export function renderSparkline(params) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/sparkline.tsx b/docs/data/data-grid/custom-columns/cell-renderers/sparkline.tsx
new file mode 100644
index 000000000000..c5e44fa6c885
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/sparkline.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react';
+import { SparkLineChart } from '@mui/x-charts/SparkLineChart';
+import { GridRenderCellParams } from '@mui/x-data-grid';
+
+export function renderSparkline(params: GridRenderCellParams) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return (
+
+ );
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/status.js b/docs/data/data-grid/custom-columns/cell-renderers/status.js
new file mode 100644
index 000000000000..f67ec6746c92
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/status.js
@@ -0,0 +1,156 @@
+import * as React from 'react';
+import Chip from '@mui/material/Chip';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText';
+import MenuItem from '@mui/material/MenuItem';
+
+import Select from '@mui/material/Select';
+import { styled } from '@mui/material/styles';
+import ReportProblemIcon from '@mui/icons-material/ReportProblem';
+import InfoIcon from '@mui/icons-material/Info';
+import AutorenewIcon from '@mui/icons-material/Autorenew';
+import DoneIcon from '@mui/icons-material/Done';
+import {
+ GridEditModes,
+ useGridApiContext,
+ useGridRootProps,
+} from '@mui/x-data-grid';
+
+export const STATUS_OPTIONS = ['Open', 'PartiallyFilled', 'Filled', 'Rejected'];
+
+const StyledChip = styled(Chip)(({ theme }) => ({
+ justifyContent: 'left',
+ '& .icon': {
+ color: 'inherit',
+ },
+ '&.Open': {
+ color: (theme.vars || theme).palette.info.dark,
+ border: `1px solid ${(theme.vars || theme).palette.info.main}`,
+ },
+ '&.Filled': {
+ color: (theme.vars || theme).palette.success.dark,
+ border: `1px solid ${(theme.vars || theme).palette.success.main}`,
+ },
+ '&.PartiallyFilled': {
+ color: (theme.vars || theme).palette.warning.dark,
+ border: `1px solid ${(theme.vars || theme).palette.warning.main}`,
+ },
+ '&.Rejected': {
+ color: (theme.vars || theme).palette.error.dark,
+ border: `1px solid ${(theme.vars || theme).palette.error.main}`,
+ },
+}));
+
+const Status = React.memo((props) => {
+ const { status } = props;
+
+ let icon = null;
+ if (status === 'Rejected') {
+ icon = ;
+ } else if (status === 'Open') {
+ icon = ;
+ } else if (status === 'PartiallyFilled') {
+ icon = ;
+ } else if (status === 'Filled') {
+ icon = ;
+ }
+
+ let label = status;
+ if (status === 'PartiallyFilled') {
+ label = 'Partially Filled';
+ }
+
+ return (
+
+ );
+});
+
+function EditStatus(props) {
+ const { id, value, field } = props;
+ const rootProps = useGridRootProps();
+ const apiRef = useGridApiContext();
+
+ const handleChange = async (event) => {
+ const isValid = await apiRef.current.setEditCellValue({
+ id,
+ field,
+ value: event.target.value,
+ });
+
+ if (isValid && rootProps.editMode === GridEditModes.Cell) {
+ apiRef.current.stopCellEditMode({ id, field, cellToFocusAfter: 'below' });
+ }
+ };
+
+ const handleClose = (event, reason) => {
+ if (reason === 'backdropClick') {
+ apiRef.current.stopCellEditMode({ id, field, ignoreModifications: true });
+ }
+ };
+
+ return (
+
+ );
+}
+
+export function renderStatus(params) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return ;
+}
+
+export function renderEditStatus(params) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/cell-renderers/status.tsx b/docs/data/data-grid/custom-columns/cell-renderers/status.tsx
new file mode 100644
index 000000000000..85bb29b22699
--- /dev/null
+++ b/docs/data/data-grid/custom-columns/cell-renderers/status.tsx
@@ -0,0 +1,162 @@
+import * as React from 'react';
+import Chip from '@mui/material/Chip';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText';
+import MenuItem from '@mui/material/MenuItem';
+import type { MenuProps } from '@mui/material/Menu';
+import Select, { SelectProps } from '@mui/material/Select';
+import { styled } from '@mui/material/styles';
+import ReportProblemIcon from '@mui/icons-material/ReportProblem';
+import InfoIcon from '@mui/icons-material/Info';
+import AutorenewIcon from '@mui/icons-material/Autorenew';
+import DoneIcon from '@mui/icons-material/Done';
+import {
+ GridEditModes,
+ GridRenderCellParams,
+ GridRenderEditCellParams,
+ useGridApiContext,
+ useGridRootProps,
+} from '@mui/x-data-grid';
+
+export const STATUS_OPTIONS = ['Open', 'PartiallyFilled', 'Filled', 'Rejected'];
+
+interface StatusProps {
+ status: string;
+}
+
+const StyledChip = styled(Chip)(({ theme }) => ({
+ justifyContent: 'left',
+ '& .icon': {
+ color: 'inherit',
+ },
+ '&.Open': {
+ color: (theme.vars || theme).palette.info.dark,
+ border: `1px solid ${(theme.vars || theme).palette.info.main}`,
+ },
+ '&.Filled': {
+ color: (theme.vars || theme).palette.success.dark,
+ border: `1px solid ${(theme.vars || theme).palette.success.main}`,
+ },
+ '&.PartiallyFilled': {
+ color: (theme.vars || theme).palette.warning.dark,
+ border: `1px solid ${(theme.vars || theme).palette.warning.main}`,
+ },
+ '&.Rejected': {
+ color: (theme.vars || theme).palette.error.dark,
+ border: `1px solid ${(theme.vars || theme).palette.error.main}`,
+ },
+}));
+
+const Status = React.memo((props: StatusProps) => {
+ const { status } = props;
+
+ let icon: any = null;
+ if (status === 'Rejected') {
+ icon = ;
+ } else if (status === 'Open') {
+ icon = ;
+ } else if (status === 'PartiallyFilled') {
+ icon = ;
+ } else if (status === 'Filled') {
+ icon = ;
+ }
+
+ let label: string = status;
+ if (status === 'PartiallyFilled') {
+ label = 'Partially Filled';
+ }
+
+ return (
+
+ );
+});
+
+function EditStatus(props: GridRenderEditCellParams) {
+ const { id, value, field } = props;
+ const rootProps = useGridRootProps();
+ const apiRef = useGridApiContext();
+
+ const handleChange: SelectProps['onChange'] = async (event) => {
+ const isValid = await apiRef.current.setEditCellValue({
+ id,
+ field,
+ value: event.target.value,
+ });
+
+ if (isValid && rootProps.editMode === GridEditModes.Cell) {
+ apiRef.current.stopCellEditMode({ id, field, cellToFocusAfter: 'below' });
+ }
+ };
+
+ const handleClose: MenuProps['onClose'] = (event, reason) => {
+ if (reason === 'backdropClick') {
+ apiRef.current.stopCellEditMode({ id, field, ignoreModifications: true });
+ }
+ };
+
+ return (
+
+ );
+}
+
+export function renderStatus(params: GridRenderCellParams) {
+ if (params.value == null) {
+ return '';
+ }
+
+ return ;
+}
+
+export function renderEditStatus(params: GridRenderEditCellParams) {
+ return ;
+}
diff --git a/docs/data/data-grid/custom-columns/custom-columns.md b/docs/data/data-grid/custom-columns/custom-columns.md
index 118a6905b663..ebabc45e057f 100644
--- a/docs/data/data-grid/custom-columns/custom-columns.md
+++ b/docs/data/data-grid/custom-columns/custom-columns.md
@@ -44,6 +44,18 @@ You can change the date format by importing different locale (`en-US` locale is
See [Localization](/x/react-date-pickers/localization/) for more information.
:::
+## Full example
+
+The demo below shows the most common custom column renderers used across our demos.
+
+:::success
+You can copy the column definitions and custom cell renderers from the demo source code.
+
+All column definitions are located in the main component file, while each cell renderer is in a separate file.
+:::
+
+{{"demo": "CustomColumnFullExample.js", "bg": "inline"}}
+
## API
- [DataGrid](/x/api/data-grid/data-grid/)
diff --git a/docs/data/data-grid/faq/faq.md b/docs/data/data-grid/faq/faq.md
index 7c3d17e7c94b..2aec0da7e174 100644
--- a/docs/data/data-grid/faq/faq.md
+++ b/docs/data/data-grid/faq/faq.md
@@ -124,7 +124,14 @@ It only impacts the rendering part and does not impact the internal calculations
## What is the purpose of useDemoData hook used in the documentation examples?
The `useDemoData` hook is a utility hook from the `@mui/x-data-grid-generator` package.
-It generates random data for the Data Grid. It is often used in documentation examples to provide realistic data without polluting the code with data generation logic.
+It contains columns definitions and generates random data for the Data Grid.
+It is often used in our demos to provide realistic data without polluting the code with data generation logic.
+
+:::success
+Looking for the column definitions and custom cell renderers from the `useDemoData` hook?
+
+Check out the [Custom columns demo](/x/react-data-grid/custom-columns/#full-example) where you can copy them from the demo source code.
+:::
Here's how it's used:
diff --git a/packages/x-data-grid-generator/src/columns/commodities.columns.tsx b/packages/x-data-grid-generator/src/columns/commodities.columns.tsx
index 60c9f269b87f..56f6f1e9528a 100644
--- a/packages/x-data-grid-generator/src/columns/commodities.columns.tsx
+++ b/packages/x-data-grid-generator/src/columns/commodities.columns.tsx
@@ -174,6 +174,7 @@ export const getCommodityColumns = (editable = false): GridColDefGenerator[] =>
},
{
field: 'incoTerm',
+ headerName: 'Incoterm',
generateData: randomIncoterm,
renderCell: renderIncoterm,
renderEditCell: renderEditIncoterm,
diff --git a/packages/x-data-grid-generator/src/renderer/renderEditCountry.tsx b/packages/x-data-grid-generator/src/renderer/renderEditCountry.tsx
index f963d73b6563..52157b6a69ea 100644
--- a/packages/x-data-grid-generator/src/renderer/renderEditCountry.tsx
+++ b/packages/x-data-grid-generator/src/renderer/renderEditCountry.tsx
@@ -54,6 +54,7 @@ function EditCountry(props: GridRenderEditCellParams) {
},
}}
{...optionProps}
+ key={option.code}
>