fix(striker-ui): allow number in indicator in IconWithIndicator

main
Tsu-ba-me 2 years ago
parent 0593338b01
commit d6920acb4f
  1. 222
      striker-ui/components/IconWithIndicator.tsx

@ -4,15 +4,29 @@ import {
BoxProps as MUIBoxProps, BoxProps as MUIBoxProps,
SvgIconProps, SvgIconProps,
} from '@mui/material'; } from '@mui/material';
import { createElement, FC } from 'react'; import {
createElement,
forwardRef,
ReactNode,
useCallback,
useImperativeHandle,
useMemo,
} from 'react';
import { BLACK, BLUE } from '../lib/consts/DEFAULT_THEME'; import { BLACK, BLUE } from '../lib/consts/DEFAULT_THEME';
import FlexBox, { FlexBoxProps } from './FlexBox'; 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 = { type IconWithIndicatorOptionalPropsWithDefault = {
iconProps?: SvgIconProps; iconProps?: SvgIconProps;
indicatorProps?: FlexBoxProps; indicatorProps?: FlexBoxProps;
indicatorTextProps?: BodyTextProps;
initialIndicatorValue?: IndicatorValue;
}; };
type IconWithIndicatorOptionalProps = IconWithIndicatorOptionalPropsWithDefault; type IconWithIndicatorOptionalProps = IconWithIndicatorOptionalPropsWithDefault;
@ -22,71 +36,159 @@ type IconWithIndicatorProps = MUIBoxProps &
icon: SvgIconComponent; icon: SvgIconComponent;
}; };
type IconWithIndicatorForwardedRefContent = {
indicate?: (value: IndicatorValue) => void;
};
const CONTAINER_LENGTH = '1.7em';
const ICON_WITH_INDICATOR_DEFAULT_PROPS: Required<IconWithIndicatorOptionalPropsWithDefault> = const ICON_WITH_INDICATOR_DEFAULT_PROPS: Required<IconWithIndicatorOptionalPropsWithDefault> =
{ {
iconProps: {}, iconProps: {},
indicatorProps: {}, indicatorProps: {},
indicatorTextProps: {},
initialIndicatorValue: false,
}; };
const INDICATOR_LENGTH = { small: '24%', medium: '50%' };
const IconWithIndicator: FC<IconWithIndicatorProps> = ({ const INDICATOR_MAX = 99;
icon, const INDICATOR_OFFSET = { small: '.1rem', medium: '0rem' };
iconProps: {
sx: iconSx, const IconWithIndicator = forwardRef<
IconWithIndicatorForwardedRefContent,
...restIconProps IconWithIndicatorProps
} = ICON_WITH_INDICATOR_DEFAULT_PROPS.iconProps, >(
indicatorProps: { (
sx: indicatorSx, {
icon,
...restIndicatorProps iconProps: {
} = ICON_WITH_INDICATOR_DEFAULT_PROPS.indicatorProps, sx: iconSx,
sx,
}) => { ...restIconProps
const containerLength = '1.7em'; } = ICON_WITH_INDICATOR_DEFAULT_PROPS.iconProps,
const indicatorLength = '24%'; indicatorProps: {
const indicatorOffset = '.1rem'; sx: indicatorSx,
return ( ...restIndicatorProps
<MUIBox } = ICON_WITH_INDICATOR_DEFAULT_PROPS.indicatorProps,
sx={{ indicatorTextProps: {
height: containerLength, sx: indicatorTextSx,
width: containerLength,
position: 'relative', ...restIndicatorTextProps
...sx, } = ICON_WITH_INDICATOR_DEFAULT_PROPS.indicatorTextProps,
}} initialIndicatorValue = ICON_WITH_INDICATOR_DEFAULT_PROPS.initialIndicatorValue,
> sx,
{createElement(icon, { },
...restIconProps, ref,
) => {
sx: { height: '100%', width: '100%', ...iconSx }, const { protect } = useProtect();
})} const [indicatorValue, setIndicatorValue] = useProtectedState<
{createElement(FlexBox, { boolean | number
row: true, >(initialIndicatorValue, protect);
...restIndicatorProps, const buildIndicator = useCallback(
(
sx: { indicatorContent: ReactNode,
backgroundColor: BLUE, indicatorLength: number | string,
borderColor: BLACK, indicatorOffset: number | string,
borderRadius: '50%', ) => (
borderStyle: 'solid', <FlexBox
borderWidth: '.2rem', row
bottom: indicatorOffset, {...restIndicatorProps}
boxSizing: 'content-box', sx={{
height: 0, backgroundColor: BLUE,
justifyContent: 'center', borderColor: BLACK,
paddingBottom: indicatorLength, borderRadius: '50%',
position: 'absolute', borderStyle: 'solid',
right: indicatorOffset, borderWidth: '.1em',
width: indicatorLength, bottom: indicatorOffset,
boxSizing: 'content-box',
...indicatorSx, height: 0,
}, justifyContent: 'center',
})} paddingBottom: indicatorLength,
</MUIBox> position: 'absolute',
); right: indicatorOffset,
}; width: indicatorLength,
...indicatorSx,
}}
>
{indicatorContent}
</FlexBox>
),
[indicatorSx, restIndicatorProps],
);
const buildIndicatorText = useCallback(
(value: IndicatorValue) => (
<BodyText
{...restIndicatorTextProps}
sx={{
fontSize: '.7rem',
fontWeight: '700',
paddingTop: '100%',
...indicatorTextSx,
}}
>
{value > INDICATOR_MAX ? `${INDICATOR_MAX}+` : value}
</BodyText>
),
[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 (
<MUIBox
sx={{
height: CONTAINER_LENGTH,
width: CONTAINER_LENGTH,
position: 'relative',
...sx,
}}
>
{createElement(icon, {
...restIconProps,
sx: { height: '100%', width: '100%', ...iconSx },
})}
{indicator}
</MUIBox>
);
},
);
IconWithIndicator.defaultProps = ICON_WITH_INDICATOR_DEFAULT_PROPS; IconWithIndicator.defaultProps = ICON_WITH_INDICATOR_DEFAULT_PROPS;
IconWithIndicator.displayName = 'IconWithIndicator';
export type { IconWithIndicatorForwardedRefContent, IconWithIndicatorProps };
export default IconWithIndicator; export default IconWithIndicator;

Loading…
Cancel
Save