metadata-based-explorer1
Version:
Box UI Elements
131 lines (120 loc) • 5.48 kB
JavaScript
// @flow strict
import * as React from 'react';
import { injectIntl, FormattedMessage } from 'react-intl';
import type { InjectIntlProvidedProps } from 'react-intl';
import uniqueId from 'lodash/uniqueId';
import { Flyout, Overlay } from '../../../../components/flyout';
import Tooltip from '../../../../components/tooltip';
import PlainButton from '../../../../components/plain-button';
import ReadableTime from '../../../../components/time/ReadableTime';
import messages from './messages';
import AvatarGroupAvatar from './AvatarGroupAvatar';
import { TASK_NEW_APPROVED, TASK_NEW_REJECTED, TASK_NEW_COMPLETED, TASK_NEW_NOT_STARTED } from '../../../../constants';
import type { TaskAssigneeCollection } from '../../../../common/types/tasks';
import type { ISODate } from '../../../../common/types/core';
import './AvatarGroup.scss';
const MAX_AVATARS = 3;
type Props = {|
getAvatarUrl: GetAvatarUrlCallback,
maxAvatars: number,
users: TaskAssigneeCollection,
|} & InjectIntlProvidedProps;
const statusMessages = {
[TASK_NEW_APPROVED]: messages.tasksFeedStatusApproved,
[TASK_NEW_REJECTED]: messages.tasksFeedStatusRejected,
[TASK_NEW_COMPLETED]: messages.tasksFeedStatusCompleted,
[TASK_NEW_NOT_STARTED]: null,
};
const Datestamp = ({ date }: { date: ISODate | Date }) => {
return <ReadableTime timestamp={new Date(date).getTime()} alwaysShowTime relativeThreshold={0} />;
};
const AvatarDetails = React.memo(({ user, status, completedAt, className }) => {
const statusMessage = statusMessages[status] || null;
return (
<div className={className}>
<div className="bcs-AvatarGroup-detailsName">{user.name}</div>
{statusMessage && completedAt && (
<div className="bcs-AvatarGroup-detailsStatus">
<FormattedMessage {...statusMessage} values={{ dateTime: <Datestamp date={completedAt} /> }} />
</div>
)}
</div>
);
});
class AvatarGroup extends React.Component<Props> {
static defaultProps = {
maxAvatars: MAX_AVATARS,
users: {},
};
listTitleId = uniqueId('avatar-group-list-title-');
render() {
const { maxAvatars, users, getAvatarUrl, intl } = this.props;
const { entries = [], next_marker } = users;
const entryCount = entries.length;
const hiddenAvatarCount = Math.max(0, entryCount - maxAvatars);
const areThereMoreEntries = !!next_marker; // there are more entries in another page of results
const visibleAvatars = entries.slice(0, maxAvatars).map(({ id, target, status, completed_at: completedAt }) => {
return (
<Tooltip
key={id}
text={
<AvatarDetails
className="bcs-AvatarGroup-tooltip"
user={target}
status={status}
completedAt={completedAt}
/>
}
>
<AvatarGroupAvatar status={status} user={target} getAvatarUrl={getAvatarUrl} />
</Tooltip>
);
});
const allUsers = entries.map(({ id, target, status, completed_at: completedAt }) => {
return (
<li key={id} className="bcs-AvatarGroup-listItem">
<AvatarGroupAvatar
status={status}
className="bcs-AvatarGroup-listItemAvatar"
user={target}
getAvatarUrl={getAvatarUrl}
/>
<AvatarDetails
className="bcs-AvatarGroup-listItemDetails"
user={target}
status={status}
completedAt={completedAt}
/>
</li>
);
});
return (
<div className="bcs-AvatarGroup">
{visibleAvatars}
{hiddenAvatarCount > 0 && (
<Flyout position="top-left" shouldDefaultFocus>
<PlainButton type="button" className="bcs-AvatarGroup-overflowCountContainer">
<Tooltip text={intl.formatMessage(messages.tasksFeedMoreAssigneesLabel)}>
<span
className="bcs-AvatarGroup-overflowCount bcs-AvatarGroup-avatar"
data-testid="avatar-group-overflow-count"
>
{areThereMoreEntries ? `${hiddenAvatarCount}+` : `+${hiddenAvatarCount}`}
</span>
</Tooltip>
</PlainButton>
<Overlay className="bcs-AvatarGroup-listFlyout">
<p className="bcs-AvatarGroup-listTitle" id={this.listTitleId}>
<FormattedMessage {...messages.tasksFeedAssigneeListTitle} />
</p>
<ul className="bcs-AvatarGroup-list" arial-labelledby={this.listTitleId}>
{allUsers}
</ul>
</Overlay>
</Flyout>
)}
</div>
);
}
}
export default injectIntl(AvatarGroup);