@spaced-out/ui-design-system
Version:
Sense UI components library
142 lines (123 loc) • 3.99 kB
Flow
// @flow strict
import * as React from 'react';
import {
borderRadiusCircle,
borderRadiusMedium,
borderRadiusNone,
borderRadiusXSmall,
} from '../../styles/variables/_border';
import {size40, sizeFluid} from '../../styles/variables/_size';
import classify from '../../utils/classify';
import {appendPx} from '../../utils/string';
import css from './Shimmer.module.css';
type ClassNames = $ReadOnly<{wrapper?: string}>;
export const SHIMMER_TYPES = Object.freeze({
text: 'text',
rounded: 'rounded',
circular: 'circular',
rectangular: 'rectangular',
});
export const SHIMMER_TYPE_TO_BORDER_RADIUS_MAP = Object.freeze({
text: borderRadiusXSmall,
rounded: borderRadiusMedium,
circular: borderRadiusCircle,
rectangular: borderRadiusNone,
});
export type ShimmerType = $Values<typeof SHIMMER_TYPES>;
export type ShimmerProps = {
classNames?: ClassNames,
show?: boolean,
type?: ShimmerType,
width?: number | string,
height?: number | string,
borderRadius?: number | string,
children?: React.Node,
};
export type ShimmerWrapperProps = {
children?: React.Node,
};
/**
* Note(Nishant): ShimmerWrapper is a wrapper component for Shimmer component. This should only be used for Text based Shimmers
* This solves a very annoying problem with out text components where the display prop is set to flex.
* Genesis assumes that every element is flexible for simplicity and for text text shimmers to work in use cases
* where text wraps across multiple lines, we need to wrap the shimmer in a span element.
* to avoid the misuse where consumers use there own / other block level components, we have this wrapper.
* This would ensure the layout remains same even when you toggle the shimmer to show your actual content
* @param {React.Node} children - The children to be rendered
*/
export const ShimmerWrapper = ({
children,
}: ShimmerWrapperProps): React$Element<'span'> => <span>{children}</span>;
export const Shimmer: React$AbstractComponent<ShimmerProps, HTMLSpanElement> =
React.forwardRef<ShimmerProps, HTMLSpanElement>(
(
{
classNames,
show = true,
type = SHIMMER_TYPES.text,
children,
width = size40,
height = sizeFluid,
borderRadius,
}: ShimmerProps,
ref,
) => {
if (!show) {
return <>{children}</>;
}
const borderRadiusValue =
borderRadius ?? SHIMMER_TYPE_TO_BORDER_RADIUS_MAP[type];
return (
<span
ref={ref}
data-testid="Shimmer"
className={classify(css.wrapper, css[type], classNames?.wrapper)}
style={{
'--width': appendPx(width),
'--height': appendPx(height),
'--border-radius': appendPx(borderRadiusValue),
}}
></span>
);
},
);
type KPIShimmerClassNames = $ReadOnly<{
wrapper?: string,
icon?: string,
text?: string,
}>;
export type KPIShimmerProps = {
textWidth?: number | string,
hasTopContent?: boolean,
hasMiddleContent?: boolean,
hasBottomContent?: boolean,
hasIcon?: boolean,
classNames?: KPIShimmerClassNames,
};
export const KPIShimmer = ({
textWidth = 150,
hasBottomContent = true,
hasIcon = true,
hasTopContent = true,
hasMiddleContent = true,
classNames,
}: KPIShimmerProps): React.Node => (
<div className={classify(css.kpiBox, classNames?.wrapper)}>
{hasIcon && (
<div className={classify(css.section, css.iconSection, classNames?.icon)}>
<Shimmer type="rounded" width={60} height={60}></Shimmer>
</div>
)}
<div className={classify(css.section, classNames?.text)}>
{hasTopContent && (
<Shimmer type="text" width={textWidth} height={15}></Shimmer>
)}
{hasMiddleContent && (
<Shimmer type="text" width={textWidth} height={25}></Shimmer>
)}
{hasBottomContent && (
<Shimmer type="text" width={textWidth} height={15}></Shimmer>
)}
</div>
</div>
);