Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
noobDev31 opened this issue Apr 29, 2024 · 8 comments
Labels
component: pagination This is the name of the generic UI component, not the React module! package: material-ui Specific to @mui/material support: Stack Overflow Please ask the community on Stack Overflow

Comments

@noobDev31
Copy link

noobDev31 commented Apr 29, 2024

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

@noobDev31 noobDev31 added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Apr 29, 2024
@zannager zannager added the component: pagination This is the name of the generic UI component, not the React module! label Apr 29, 2024
@mj12albert
Copy link
Member

@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 mj12albert added status: waiting for author Issue with insufficient information and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Apr 29, 2024
@noobDev31
Copy link
Author

@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

@github-actions github-actions bot added status: waiting for maintainer These issues haven't been looked at yet by a maintainer and removed status: waiting for author Issue with insufficient information labels Apr 29, 2024
@noobDev31
Copy link
Author

noobDev31 commented May 3, 2024

@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
Copy link
Author

Guys any update on this @mj12albert @siriwatknp

@ZeeshanTamboli
Copy link
Member

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

@ZeeshanTamboli ZeeshanTamboli changed the title [material ui] [PaginationItem] Pagination Item component crashes due to a null check missing for contrastText [material ui][PaginationItem] Pagination Item component crashes due to a null check missing for contrastText May 19, 2024
@ZeeshanTamboli ZeeshanTamboli added support: Stack Overflow Please ask the community on Stack Overflow package: material-ui Specific to @mui/material and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels May 19, 2024
Copy link

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

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale May 19, 2024
@noobDev31
Copy link
Author

@ZeeshanTamboli so there's no other way to get around this without the ThemeProvider right?

@ZeeshanTamboli
Copy link
Member

@ZeeshanTamboli so there's no other way to get around this without the ThemeProvider right?

Nope

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: pagination This is the name of the generic UI component, not the React module! package: material-ui Specific to @mui/material support: Stack Overflow Please ask the community on Stack Overflow
Projects
None yet
Development

No branches or pull requests

5 participants