import { SvgIconComponent } from '@mui/icons-material'; import { Box as MUIBox, BoxProps as MUIBoxProps, SvgIconProps, } from '@mui/material'; import { createElement, forwardRef, ReactNode, useCallback, useImperativeHandle, useMemo, } from 'react'; import { BLACK, BLUE } from '../lib/consts/DEFAULT_THEME'; import FlexBox, { FlexBoxProps } from './FlexBox'; import { BodyText, BodyTextProps } from './Text'; import useProtect from '../hooks/useProtect'; import useProtectedState from '../hooks/useProtectedState'; type IndicatorValue = boolean | number; type IconWithIndicatorOptionalPropsWithDefault = { iconProps?: SvgIconProps; indicatorProps?: FlexBoxProps; indicatorTextProps?: BodyTextProps; initialIndicatorValue?: IndicatorValue; }; type IconWithIndicatorOptionalProps = IconWithIndicatorOptionalPropsWithDefault; type IconWithIndicatorProps = MUIBoxProps & IconWithIndicatorOptionalProps & { icon: SvgIconComponent; }; type IconWithIndicatorForwardedRefContent = { indicate?: (value: IndicatorValue) => void; }; const CONTAINER_LENGTH = '1.7em'; const ICON_WITH_INDICATOR_DEFAULT_PROPS: Required = { iconProps: {}, indicatorProps: {}, indicatorTextProps: {}, initialIndicatorValue: false, }; const INDICATOR_LENGTH = { small: '24%', medium: '50%' }; const INDICATOR_MAX = 9; const INDICATOR_OFFSET = { small: '.1rem', medium: '0rem' }; const IconWithIndicator = forwardRef< IconWithIndicatorForwardedRefContent, IconWithIndicatorProps >( ( { icon, iconProps: { sx: iconSx, ...restIconProps } = ICON_WITH_INDICATOR_DEFAULT_PROPS.iconProps, indicatorProps: { sx: indicatorSx, ...restIndicatorProps } = ICON_WITH_INDICATOR_DEFAULT_PROPS.indicatorProps, indicatorTextProps: { sx: indicatorTextSx, ...restIndicatorTextProps } = ICON_WITH_INDICATOR_DEFAULT_PROPS.indicatorTextProps, initialIndicatorValue = ICON_WITH_INDICATOR_DEFAULT_PROPS.initialIndicatorValue, sx, }, ref, ) => { const { protect } = useProtect(); const [indicatorValue, setIndicatorValue] = useProtectedState< boolean | number >(initialIndicatorValue, protect); const buildIndicator = useCallback( ( indicatorContent: ReactNode, indicatorLength: number | string, indicatorOffset: number | string, ) => ( {indicatorContent} ), [indicatorSx, restIndicatorProps], ); const buildIndicatorText = useCallback( (value: IndicatorValue) => ( {value > INDICATOR_MAX ? `${INDICATOR_MAX}+` : value} ), [indicatorTextSx, restIndicatorTextProps], ); const indicator = useMemo(() => { let result; if (indicatorValue) { let indicatorContent; let indicatorLength = INDICATOR_LENGTH.small; let indicatorOffset = INDICATOR_OFFSET.small; if (Number.isFinite(indicatorValue)) { indicatorContent = buildIndicatorText(indicatorValue); indicatorLength = INDICATOR_LENGTH.medium; indicatorOffset = INDICATOR_OFFSET.medium; } result = buildIndicator( indicatorContent, indicatorLength, indicatorOffset, ); } return result; }, [buildIndicator, buildIndicatorText, indicatorValue]); useImperativeHandle( ref, () => ({ indicate: (value) => setIndicatorValue(value), }), [setIndicatorValue], ); return ( {createElement(icon, { ...restIconProps, sx: { height: '100%', width: '100%', ...iconSx }, })} {indicator} ); }, ); IconWithIndicator.defaultProps = ICON_WITH_INDICATOR_DEFAULT_PROPS; IconWithIndicator.displayName = 'IconWithIndicator'; export type { IconWithIndicatorForwardedRefContent, IconWithIndicatorProps }; export default IconWithIndicator;