mui / material-ui

Material UI: Ready-to-use foundational React components, free forever. It includes Material UI, which implements Google's Material Design.
https://mui.com/material-ui/
MIT License
91.86k stars 31.57k forks source link

[material ui] [PaginationItem] Pagination Item component crashes due to a null check missing for contrastText #42055

Open noobDev31 opened 2 weeks ago

noobDev31 commented 2 weeks ago

Steps to reproduce

Link to live example: (required)

Steps:

  1. 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 ; };

const LastIcon = (color: string): JSX.Element => { return ; };

const PrevIcon = (color: string): JSX.Element => { return ; };

const NextIcon = (color: string): JSX.Element => { return ; };

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): 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 -

image image

  1. 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

mj12albert commented 2 weeks ago

@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

noobDev31 commented 2 weeks ago

@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

noobDev31 commented 2 weeks ago

@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));
};
noobDev31 commented 1 week ago

Guys any update on this @mj12albert @siriwatknp