wix-style-react
Version:
239 lines (225 loc) • 8.25 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import IconButton from '../IconButton';
import { avatarShapes, dataHooks } from './constants';
import { Avatar as CoreAvatar } from 'wix-ui-core/dist/src/components/avatar';
import Loader from '../Loader';
import { placeholderSVGs } from './assets';
import { st, classes } from './Avatar.st.css';
import { capitalize } from '../utils/cssClassUtils';
import stringToColor from './string-to-color';
import { FontUpgradeContext } from '../FontUpgrade/context';
const getSizeNumber = size => Number(size.substring(4));
const defaultSize = 48;
const minSmallIconButton = 60;
/**
* Avatar is a type of element that visually represents a user, either as an image, name initials or placeholder icon.
*/
class Avatar extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
fadeIndication: false,
showIndication: false,
};
}
_onMouseEnter = () => {
if (this.props.showIndicationOnHover) {
this.setState({ showIndication: true });
}
};
_onMouseLeave = () => {
if (this.props.showIndicationOnHover) {
this.setState({ fadeIndication: true });
setTimeout(
() => this.setState({ fadeIndication: false, showIndication: false }),
150,
);
}
};
render() {
const {
size,
presence,
indication,
color,
customIndication,
onIndicationClick,
dataHook,
className,
shape,
text,
placeholder,
name,
onClick,
showIndicationOnHover,
loading,
...rest
} = this.props;
const { fadeIndication, showIndication } = this.state;
const calculatedColor = color || stringToColor(text || name); // if color is provided as a prop use it, otherwise, generate a color based on the text
const sizeNumber = getSizeNumber(size);
const renderOnHover = !showIndicationOnHover || showIndication;
const indicationConstraints = renderOnHover && sizeNumber >= defaultSize;
const renderIndication =
indicationConstraints && !customIndication && indication;
const renderCustomIndication = indicationConstraints && customIndication;
const renderLoader = loading && sizeNumber >= defaultSize;
return (
<div data-hook={dataHook} className={st(className, classes.root)}>
<FontUpgradeContext.Consumer>
{({ active }) => (
<div
data-hook={dataHooks.avatarWSR}
onMouseEnter={this._onMouseEnter}
onMouseLeave={this._onMouseLeave}
className={st(classes.avatarContainer, {
shape,
size,
indication: Boolean(customIndication || indication),
presence: Boolean(presence),
presenceType: presence,
clickable: !!onClick,
fade: fadeIndication,
hasText: !!text,
})}
data-madefor={active}
>
<div className={classes.coreAvatar}>
<CoreAvatar
{...{
...rest,
placeholder: placeholder ? (
placeholder
) : (
<AvatarDefaultPlaceholder shape={shape} size={size} />
),
text,
name,
onClick,
initialsLimit: sizeNumber < 30 ? 1 : undefined,
'data-hook': dataHooks.avatarCore,
}}
className={st(
classes.avatar,
classes[`color${capitalize(calculatedColor)}`],
)}
/>
</div>
{renderLoader && [
<div
key="overlay"
className={st(classes.loaderContainer, classes.overlay)}
/>,
<div
key="loader"
className={st(classes.loaderContainer, classes.loader)}
>
<Loader dataHook={dataHooks.loader} size="tiny" />
</div>,
]}
{presence && <div className={classes.presence} />}
{renderIndication && (
<div className={classes.indication}>
<IconButton
className={classes.iconButtonShadow}
dataHook={dataHooks.indication}
onClick={onIndicationClick}
skin="inverted"
shape={shape}
size={sizeNumber > minSmallIconButton ? 'small' : 'tiny'}
>
{indication}
</IconButton>
</div>
)}
{renderCustomIndication && (
<div
className={classes.indication}
data-hook={dataHooks.customIndication}
onClick={onIndicationClick}
>
{customIndication}
</div>
)}
</div>
)}
</FontUpgradeContext.Consumer>
</div>
);
}
}
const AvatarDefaultPlaceholder = ({ shape, size }) =>
shape !== avatarShapes.square
? placeholderSVGs[size][avatarShapes.circle]
: placeholderSVGs[size][avatarShapes.square];
const CoreAvatarPropTypes = {
/**
* Defines a name of the avatar user. Text initials will be generated from the name.
* The name value will be used as default value for HTML title and `aria-label` attributes.
* And also as default value for the image's alt attribute if `imgProps` is provided.
*/
name: PropTypes.string,
/** Defines a text to render as a content instead of a given `name`. */
text: PropTypes.string,
/**
* Accept any content to render as a content placeholder. This placeholder will be displayed if no text, name or imgProps are provided.<br>
* By default use a generic avatar user icon.
*/
placeholder: PropTypes.node,
/** Accept all common `<img>` tag properties. */
imgProps: PropTypes.object,
/** Defines a string that labels the current element in case where text label is not visible on the screen. */
ariaLabel: PropTypes.string,
/** Defines a standard HTML title attribute value. Applies it to the root element. */
title: PropTypes.string,
/** Defines a click event handler. When defined, component will be clickable and will have a pointer cursor on hover. */
onClick: PropTypes.func,
};
Avatar.displayName = 'Avatar';
Avatar.propTypes = {
...CoreAvatarPropTypes,
/** Controls the size of an avatar. */
size: PropTypes.oneOf([
'size90',
'size72',
'size60',
'size48',
'size36',
'size30',
'size24',
'size18',
]),
/**
* Controls the background color of the avatar. If not provided,
* color is determined by the provided text or name so that each name will receive a different color.
*/
color: PropTypes.oneOf(['A1', 'A2', 'A3', 'A4', 'A5', 'A6']),
/** Controls the shape of the image. */
shape: PropTypes.oneOf(['circle', 'square']),
/** Specifies a CSS class name to be appended to the component’s root element. */
className: PropTypes.string,
/** Applies a data-hook HTML attribute that can be used in the tests. */
dataHook: PropTypes.string,
/** Sets the presence status of an avatar. */
presence: PropTypes.oneOf(['online', 'offline', 'busy']),
/** Accept any content to render as an indication item. */
indication: PropTypes.node,
/**
* Accept any content to render as a custom indication item. This indication element will not be wrapped by an IconButton.
* It could be rendered in other shapes (such as square).
* */
customIndication: PropTypes.node,
/** Defines a callback function which is called every time indication element is clicked. */
onIndicationClick: PropTypes.func,
/** Shows indication element on hover. */
showIndicationOnHover: PropTypes.bool,
/** Shows a loader on top of an avatar. */
loading: PropTypes.bool,
};
Avatar.defaultProps = {
size: 'size48',
shape: 'circle',
showIndicationOnHover: false,
};
export default Avatar;