material-ui
material-ui copied to clipboard
[material ui] [PaginationItem] Pagination Item component crashes due to a null check missing for contrastText
Steps to reproduce
Link to live example: (required)
Steps:
- I am having the below code for the Pagination component in my low code platform I am working now -
import {forwardRef, Ref, JSX} from 'react'; import {Pagination, PaginationProps, PaginationItem, useTheme, PaginationItemProps} from '@mui/material'; import {ChevronMiniLeftIcon, ChevronMiniRightIcon, PageFirstPageIcon, PageLastPageIcon} from '../../icons';
export type TPaginationProps = Omit<PaginationProps, 'color'> & { color?: 'primary' | 'secondary' | 'brand'; };
const FirstIcon = (color: string): JSX.Element => { return <PageFirstPageIcon color={color} />; };
const LastIcon = (color: string): JSX.Element => { return <PageLastPageIcon color={color} />; };
const PrevIcon = (color: string): JSX.Element => { return <ChevronMiniLeftIcon color={color} />; };
const NextIcon = (color: string): JSX.Element => { return <ChevronMiniRightIcon color={color} />; };
const PaginationIcons = (props: TPaginationProps): PaginationItemProps['slots'] => { const theme = useTheme(); const color = props.disabled ? theme.palette.text.disabled : theme.palette.text.primary; const colorNavLeft = props.page === 1 ? theme.palette.text.disabled : color; const colorNavRight = props.page === props.count ? theme.palette.text.disabled : color; return { first: () => FirstIcon(colorNavLeft), previous: () => PrevIcon(colorNavLeft), next: () => NextIcon(colorNavRight), last: () => LastIcon(colorNavRight) }; };
const Pagination = forwardRef((props: TPaginationProps, ref: Ref<HTMLDivElement>): JSX.Element => { const {color = 'brand', ...restProps} = props; return ( <Pagination renderItem={(item) => (<PaginationItem slots={PaginationIcons(props)} {...item} />)} {...restProps} color={color} ref={ref} /> ); });
export {Pagination as PaginationLowCode};
2.While dragging and dropping the Pagination component without wrapping the themeProvider i get the below console error and the code goes into PaginationItem.js file -
- Shouldn't be there a null check for the contrastText ?
Current behavior
Component breaks if not having the ThemeProvider as a parent.
Expected behavior
Component shouldn't break even if the ThemeProvider is not present as parent.
Context
No response
Your environment
npx @mui/envinfo
Don't forget to mention which browser you used.
Output from `npx @mui/envinfo` goes here.
Search keywords: Pagination
@noobDev31 Thanks for reporting this, would you mind providing a minimal reproduction? You can fork this template: https://stackblitz.com/edit/stackblitz-starters-maxhor?file=src%2FApp.tsx
PS here are some tips for providing a minimal repro: https://stackoverflow.com/help/mcve
@mj12albert For some reason i get some errors on the above stackblitz link - but whenever i try to use Pagination like below the component crashes
https://stackblitz.com/edit/stackblitz-starters-swc3mv?file=src%2FPagination.tsx,src%2FApp.tsx,src%2Findex.tsx
@siriwatknp Wouldn't it be right to do a add a null check on the code on your side?
actually i am passing default color as 'brand'(our overridden colour) and without the ThemeProvider wrapping it, it breaks
We're doing styling through the ThemeProvider like with the below code -
import {ComponentsProps, ComponentsOverrides, ComponentsVariants, Theme} from '@mui/material';
declare module '@mui/material/PaginationItem' {
interface PaginationItemPropsColorOverrides {
brand: true;
}
}
export const CorePaginationItem: {
// the below 3 lines are: TypeScript definition of this object according to themeProvider
defaultProps?: ComponentsProps['MuiPaginationItem'];
styleOverrides?: ComponentsOverrides<Theme>['MuiPaginationItem'];
variants?: ComponentsVariants['MuiPaginationItem'];
} = {
styleOverrides: {
root: ({theme}: {theme: Theme}) => ({
fontSize: 30,
width: 32,
height: 32,
lineHeight: '17px',
'& .lcep-icon-svg': {
width: 24,
height: 24,
display: 'flex',
alignItems: 'center'
},
'&.MuiPaginationItem-page': {
fontSize: theme.typography.body1.fontSize
},
'&.MuiPaginationItem-textBrand': {
'&.Mui-selected': {
backgroundColor: theme.palette.brand.main
}
},
'&.MuiPaginationItem-textPrimary': {
'&.Mui-selected': {
backgroundColor: theme.palette.primary.main
}
},
'&.MuiPaginationItem-textSecondary': {
'&.Mui-selected': {
backgroundColor: theme.palette.secondary.main
}
},
'&.Mui-disabled': {
color: theme.palette.text.disabled
},
'&.Mui-disabled.Mui-selected': {
backgroundColor: theme.palette.action.disabledBackground
}
})
}
};
import {ComponentsProps, ComponentsOverrides, ComponentsVariants, Theme} from '@mui/material';
declare module '@mui/material/Pagination' {
interface PaginationPropsColorOverrides {
brand: true;
}
}
export const CorePagination: {
// the below 3 lines are: TypeScript definition of this object according to themeProvider
defaultProps?: ComponentsProps['MuiPagination'];
styleOverrides?: ComponentsOverrides<Theme>['MuiPagination'];
variants?: ComponentsVariants['MuiPagination'];
} = {
styleOverrides: {
ul: {
'& li': {
width: 44,
height: 60,
display: 'flex',
alignItems: 'center'
}
}
}
};
import {createTheme, darken, getLuminance, lighten, PaletteColor, SimplePaletteColorOptions, Theme, PaletteMode} from '@mui/material';
import type {} from '@mui/x-data-grid-premium/themeAugmentation';
import {getConfig} from './config';
import {CorePagination} from './corePagination';
import {Property} from 'csstype';
import {getCoreLocale, getDatePickerLocale, getGridLocale} from '../utils/localisationUtils';
export type ResponsivenessMode = 'Screen' | 'Container';
// typescript support for theme custom fields
declare module '@mui/material/styles' {
interface Palette {
brand: PaletteColor;
ink: PaletteColor;
surface: PaletteColor;
paper: PaletteColor;
special1: PaletteColor;
special2: PaletteColor;
special3: PaletteColor;
special4: PaletteColor;
}
interface PaletteOptions {
brand: SimplePaletteColorOptions;
ink: SimplePaletteColorOptions;
surface: SimplePaletteColorOptions;
paper: SimplePaletteColorOptions;
special1: SimplePaletteColorOptions;
special2: SimplePaletteColorOptions;
special3: SimplePaletteColorOptions;
special4: SimplePaletteColorOptions;
}
interface Theme {
responsiveness: {
responsivenessMode: ResponsivenessMode;
containerName?: string;
};
}
interface ThemeOptions {
responsiveness: {
responsivenessMode: ResponsivenessMode;
containerName?: string;
};
}
}
const modeNumber = (mode: PaletteMode, numberLight: string, numberDark: string): number => {
return (mode === 'light') ? Number(numberLight) : Number(numberDark);
};
const modeColor = (mode: PaletteMode, colorLight: string, colorDark: string): string => {
return (mode === 'light') ? colorLight : colorDark;
};
const paletteModeColor = (mode: PaletteMode, colorLight: string, colorDark: string, config: Record<string, string>): SimplePaletteColorOptions => {
const tonalOffsetLight = modeNumber(mode, config.tonalOffsetLightLight, config.tonalOffsetLightDark);
const tonalOffsetDark = modeNumber(mode, config.tonalOffsetDarkLight, config.tonalOffsetDarkDark);
const main = modeColor(mode, colorLight, colorDark);
const light = lighten(main, tonalOffsetLight);
const dark = darken(main, tonalOffsetDark);
const contrastText = (getLuminance(main) > 0.5) ? config.black : config.white;
return {main, light, dark, contrastText};
};
const baseTheme = (themeConfig: IThemeConfig = {}, mode: PaletteMode = 'light', responsivenessMode: ResponsivenessMode = 'Screen',
containerName: string = '', locale = 'en-US'): Theme => {
const config = getConfig(themeConfig);
return createTheme({
breakpoints: {
values: {
xs: 0,
sm: 768,
md: 960,
lg: 1200,
xl: 1600
}
},
responsiveness: {
responsivenessMode,
containerName
},
palette: {
mode,
common: {
black: config.black,
white: config.white
},
background: {
default: modeColor(mode, config.surfaceLight, config.surfaceDark),
paper: modeColor(mode, config.paperLight, config.paperDark)
},
primary: paletteModeColor(mode, config.primaryLight, config.primaryDark, config),
secondary: paletteModeColor(mode, config.secondaryLight, config.secondaryDark, config),
ink: paletteModeColor(mode, config.inkLight, config.inkDark, config),
surface: paletteModeColor(mode, config.surfaceLight, config.surfaceDark, config),
paper: paletteModeColor(mode, config.paperLight, config.paperDark, config),
brand: paletteModeColor(mode, config.brandLight, config.brandDark, config),
special1: paletteModeColor(mode, config.special1Light, config.special1Dark, config),
special2: paletteModeColor(mode, config.special2Light, config.special2Dark, config),
special3: paletteModeColor(mode, config.special3Light, config.special3Dark, config),
special4: paletteModeColor(mode, config.special4Light, config.special4Dark, config),
error: paletteModeColor(mode, config.errorLight, config.errorDark, config),
success: paletteModeColor(mode, config.successLight, config.successDark, config),
warning: paletteModeColor(mode, config.warningLight, config.warningDark, config),
info: paletteModeColor(mode, config.infoLight, config.infoDark, config),
grey: {
50: modeColor(mode, config.grey50Light, config.grey50Dark),
100: modeColor(mode, config.grey100Light, config.grey100Dark),
200: modeColor(mode, config.grey200Light, config.grey200Dark),
300: modeColor(mode, config.grey300Light, config.grey300Dark),
400: modeColor(mode, config.grey400Light, config.grey400Dark),
500: modeColor(mode, config.grey500Light, config.grey500Dark),
600: modeColor(mode, config.grey600Light, config.grey600Dark),
700: modeColor(mode, config.grey700Light, config.grey700Dark),
800: modeColor(mode, config.grey800Light, config.grey800Dark),
900: modeColor(mode, config.grey900Light, config.grey900Dark)
},
divider: modeColor(mode, config.dividerLight, config.dividerDark),
text: {
primary: modeColor(mode, config.textPrimaryLight, config.textPrimaryDark),
secondary: modeColor(mode, config.textSecondaryLight, config.textSecondaryDark),
disabled: modeColor(mode, config.textDisabledLight, config.textDisabledDark)
},
action: {
disabled: modeColor(mode, config.disabledLight, config.disabledDark),
selected: modeColor(mode, config.selectedLight, config.selectedDark),
hover: modeColor(mode, config.hoverLight, config.hoverDark),
active: modeColor(mode, config.activeLight, config.activeDark),
focus: modeColor(mode, config.focusLight, config.focusDark),
disabledBackground: modeColor(mode, config.disabledBackgroundLight, config.disabledBackgroundDark),
hoverOpacity: modeNumber(mode, config.hoverOpacityLight, config.hoverOpacityDark),
selectedOpacity: modeNumber(mode, config.selectedOpacityLight, config.selectedOpacityDark),
disabledOpacity: modeNumber(mode, config.disabledOpacityLight, config.disabledOpacityDark),
focusOpacity: modeNumber(mode, config.focusOpacityLight, config.focusOpacityDark),
activatedOpacity: modeNumber(mode, config.activatedOpacityLight, config.activatedOpacityDark)
},
tonalOffset: {
light: modeNumber(mode, config.tonalOffsetLightLight, config.tonalOffsetLightDark),
dark: modeNumber(mode, config.tonalOffsetDarkLight, config.tonalOffsetDarkDark)
},
contrastThreshold: 4.5
},
typography: {
fontFamily: config.font,
htmlFontSize: 14,
fontSize: 14,
h1: {
fontFamily: config.fontH1,
fontSize: config.fontH1Size,
fontWeight: config.fontH1Weight,
lineHeight: config.fontH1LineHeight,
textTransform: config.fontH1TextTransform as Property.TextTransform
},
h2: {
fontFamily: config.fontH2,
fontSize: config.fontH2Size,
fontWeight: config.fontH2Weight,
lineHeight: config.fontH2LineHeight,
textTransform: config.fontH2TextTransform as Property.TextTransform
},
h3: {
fontFamily: config.fontH3,
fontSize: config.fontH3Size,
fontWeight: config.fontH3Weight,
lineHeight: config.fontH3LineHeight,
textTransform: config.fontH3TextTransform as Property.TextTransform
},
h4: {
fontFamily: config.fontH4,
fontSize: config.fontH4Size,
fontWeight: config.fontH4Weight,
lineHeight: config.fontH4LineHeight,
textTransform: config.fontH4TextTransform as Property.TextTransform
},
h5: {
fontFamily: config.fontH5,
fontSize: config.fontH5Size,
fontWeight: config.fontH5Weight,
lineHeight: config.fontH5LineHeight,
textTransform: config.fontH5TextTransform as Property.TextTransform
},
h6: {
fontFamily: config.fontH6,
fontSize: config.fontH6Size,
fontWeight: config.fontH6Weight,
lineHeight: config.fontH6LineHeight,
textTransform: config.fontH6TextTransform as Property.TextTransform
},
body1: {
fontFamily: config.fontBody1,
fontSize: config.fontBody1Size,
fontWeight: config.fontBody1Weight,
lineHeight: config.fontBody1LineHeight,
textTransform: config.fontBody1TextTransform as Property.TextTransform
},
body2: {
fontFamily: config.fontBody2,
fontSize: config.fontBody2Size,
fontWeight: config.fontBody2Weight,
lineHeight: config.fontBody2LineHeight,
textTransform: config.fontBody2TextTransform as Property.TextTransform
},
subtitle1: {
fontFamily: config.fontSubtitle1,
fontSize: config.fontSubtitle1Size,
fontWeight: config.fontSubtitle1Weight,
lineHeight: config.fontSubtitle1LineHeight,
textTransform: config.fontSubtitle1TextTransform as Property.TextTransform
},
subtitle2: {
fontFamily: config.fontSubtitle2,
fontSize: config.fontSubtitle2Size,
fontWeight: config.fontSubtitle2Weight,
lineHeight: config.fontSubtitle2LineHeight,
textTransform: config.fontSubtitle2TextTransform as Property.TextTransform
},
caption: {
fontFamily: config.fontCaption,
fontSize: config.fontCaptionSize,
fontWeight: config.fontCaptionWeight,
lineHeight: config.fontCaptionLineHeight,
textTransform: config.fontCaptionTextTransform as Property.TextTransform
},
overline: {
fontFamily: config.fontOverline,
fontSize: config.fontOverlineSize,
fontWeight: config.fontOverlineWeight,
lineHeight: config.fontOverlineLineHeight,
textTransform: config.fontOverlineTextTransform as Property.TextTransform
},
button: {
fontFamily: config.fontButton,
fontSize: config.fontButtonSize,
fontWeight: config.fontButtonWeight,
lineHeight: config.fontButtonLineHeight,
textTransform: config.fontButtonTextTransform as Property.TextTransform
}
},
shadows: [
'none',
'0 2px 8px 0 rgba(19, 19, 24, 0.16)',
'0 2px 16px 0 rgba(19, 19, 24, 0.16)',
'0 2px 24px 0 rgba(19, 19, 24, 0.20), 0 2px 8px 0 rgba(19, 19, 24, 0.12)',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
''
],
spacing: (factor: number) => `${8 * factor}px`,
shape: {
borderRadius: Number(config.radiusBase)
},
transitions: {
easing: {
easeInOut: config.easeInOut,
easeOut: config.easeOut,
easeIn: config.easeIn,
sharp: config.sharp
},
duration: {
shortest: Number(config.shortest),
shorter: Number(config.shorter),
short: Number(config.short),
standard: Number(config.standard),
complex: Number(config.complex),
enteringScreen: Number(config.enteringScreen),
leavingScreen: Number(config.leavingScreen)
}
},
components: {
MuiPagination: CorePagination
}
},
getCoreLocale(locale),
getGridLocale(locale),
getDatePickerLocale(locale));
};
Guys any update on this @mj12albert @siriwatknp
@noobDev31 Since you are defining a new custom color named brand
so yes, you will need to wrap it with a ThemeProvider
for the component to recognize and access it from context. Check the documentation: https://mui.com/material-ui/customization/palette/#custom-colors.
👋 Thanks for using our open-source projects!
We use GitHub issues exclusively as a bug and feature requests tracker, however, this issue appears to be a support request.
For support with Material UI please check out https://mui.com/material-ui/getting-started/support/. Thanks!
If you have a question on Stack Overflow, you are welcome to link to it here, it might help others. If your issue is subsequently confirmed as a bug, and the report follows the issue template, it can be reopened.
@ZeeshanTamboli so there's no other way to get around this without the ThemeProvider right?
@ZeeshanTamboli so there's no other way to get around this without the ThemeProvider right?
Nope