@atlaskit/profilecard
Version:
A React component to display a card with user information.
369 lines (368 loc) • 14.1 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl-next';
import Avatar from '@atlaskit/avatar';
import AvatarGroup from '@atlaskit/avatar-group';
import LoadingButton from '@atlaskit/button/loading-button';
import Button, { IconButton, LinkButton } from '@atlaskit/button/new';
import ButtonLegacy from '@atlaskit/button/standard-button';
import FocusRing from '@atlaskit/focus-ring';
import MoreIcon from '@atlaskit/icon/core/migration/show-more-horizontal--more';
import { LinkItem, MenuGroup } from '@atlaskit/menu';
import { VerifiedTeamIcon } from '@atlaskit/people-teams-ui-public/verified-team-icon';
import { fg } from '@atlaskit/platform-feature-flags';
import Popup from '@atlaskit/popup';
import { Inline, Text } from '@atlaskit/primitives/compiled';
import { layers } from '@atlaskit/theme/constants';
import Tooltip from '@atlaskit/tooltip';
import messages from '../../messages';
import { AnimatedKudosButton, AnimationWrapper, KudosBlobAnimation } from '../../styled/Card';
import { ErrorWrapper, TeamErrorText } from '../../styled/Error';
import { ActionButtons, AvatarSection, CardContent, CardHeader, CardWrapper, Description, DescriptionWrapper, MemberCount, MoreButton, TeamName, WrappedButton } from '../../styled/TeamCard';
import { actionClicked, errorRetryClicked, moreActionsClicked, moreMembersClicked, profileCardRendered, teamAvatarClicked } from '../../util/analytics';
import { isBasicClick } from '../../util/click';
import { ErrorIllustration } from '../Error';
import TeamForbiddenErrorState from './TeamForbiddenErrorState';
import TeamLoadingState from './TeamLoadingState';
const LARGE_MEMBER_COUNT = 50;
const GIVE_KUDOS_ACTION_ID = 'give-kudos';
const avatarGroupMaxCount = 9;
function onMemberClick(callback, userId, analytics, index, hasHref) {
return event => {
analytics(duration => teamAvatarClicked({
duration,
hasHref,
hasOnClick: !!callback,
index
}));
if (callback) {
callback(userId, event);
}
};
}
const TeamMembers = ({
analytics,
generateUserLink,
members,
onUserClick,
includingYou,
isTriggeredByKeyboard
}) => {
const {
formatMessage
} = useIntl();
const count = members ? members.length : 0;
const message = includingYou ? count >= LARGE_MEMBER_COUNT ? messages.membersMoreThan50IncludingYou : messages.memberCountIncludingYou : count >= LARGE_MEMBER_COUNT ? messages.membersMoreThan50 : messages.memberCount;
// Use a ref to track whether this is currently open, so we can fire events
// iff the more section is being opened (not closed).
const isMoreMembersOpen = useRef(false);
const avatarRef = useRef(null);
const ref = element => {
if (isTriggeredByKeyboard) {
var _avatarRef$current;
avatarRef.current = element;
(_avatarRef$current = avatarRef.current) === null || _avatarRef$current === void 0 ? void 0 : _avatarRef$current.focus();
}
};
const onMoreClick = useCallback(() => {
const {
current: isOpen
} = isMoreMembersOpen;
if (!isOpen) {
analytics(duration => moreMembersClicked({
duration,
memberCount: count
}));
}
isMoreMembersOpen.current = !isOpen;
}, [analytics, count]);
const showMoreButtonProps = {
onClick: onMoreClick,
...(fg('platform_profilecard-enable_reporting_lines_label') ? {
'aria-label': formatMessage(messages.profileCardMoreMembersLabel, {
count: count - avatarGroupMaxCount + 1
})
} : {})
};
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MemberCount, null, /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, message, {
values: {
count
}
}))), members && members.length > 0 && /*#__PURE__*/React.createElement(AvatarSection, null, fg('enable_team_profilecard_toggletip_a11y_fix') ? /*#__PURE__*/React.createElement(AvatarGroup, {
appearance: "stack",
data: members.map((member, index) => {
const href = generateUserLink === null || generateUserLink === void 0 ? void 0 : generateUserLink(member.id);
const onClick = onMemberClick(onUserClick, member.id, analytics, index, !!generateUserLink);
return {
key: member.id,
name: member.fullName,
src: member.avatarUrl,
href,
onClick
};
}),
maxCount: avatarGroupMaxCount,
showMoreButtonProps: showMoreButtonProps,
testId: "profilecard-avatar-group",
overrides: {
Avatar: {
render: (Component, props, index) => index === 0 ? /*#__PURE__*/React.createElement(Avatar, _extends({
ref: ref
}, props, {
testId: "first-member"
})) : /*#__PURE__*/React.createElement(Component, props)
}
}
}) : /*#__PURE__*/React.createElement(AvatarGroup, {
appearance: "stack",
data: members.map((member, index) => {
const href = generateUserLink === null || generateUserLink === void 0 ? void 0 : generateUserLink(member.id);
const onClick = onMemberClick(onUserClick, member.id, analytics, index, !!generateUserLink);
return {
key: member.id,
name: member.fullName,
src: member.avatarUrl,
href,
onClick
};
}),
maxCount: avatarGroupMaxCount,
showMoreButtonProps: showMoreButtonProps,
testId: "profilecard-avatar-group"
})));
};
function onActionClick(action, analytics, index) {
return (event, ...args) => {
analytics(duration => actionClicked('team', {
duration,
hasHref: !!action.link,
hasOnClick: !!action.callback,
index,
actionId: action.id || ''
}));
if (action.callback && isBasicClick(event)) {
event.preventDefault();
action.callback(event, ...args);
}
};
}
const ActionButton = ({
action,
analytics,
index
}) => {
const isGiveKudosActionButton = action.id === GIVE_KUDOS_ACTION_ID;
const actionButton = /*#__PURE__*/React.createElement(FocusRing, {
isInset: true
}, fg('ptc_migrate_buttons') ? /*#__PURE__*/React.createElement(LinkButton, {
key: action.id || index,
onClick: onActionClick(action, analytics, index),
href: action.link || '',
shouldFitContainer: true
}, action.label, isGiveKudosActionButton && /*#__PURE__*/React.createElement(AnimationWrapper, null, /*#__PURE__*/React.createElement(KudosBlobAnimation, null))) : /*#__PURE__*/React.createElement(ButtonLegacy, {
key: action.id || index,
onClick: onActionClick(action, analytics, index),
href: action.link,
shouldFitContainer: true
}, action.label, isGiveKudosActionButton && /*#__PURE__*/React.createElement(AnimationWrapper, null, /*#__PURE__*/React.createElement(KudosBlobAnimation, null))));
if (isGiveKudosActionButton) {
return /*#__PURE__*/React.createElement(AnimatedKudosButton, null, actionButton);
}
return /*#__PURE__*/React.createElement(WrappedButton, null, actionButton);
};
const ExtraActions = ({
actions,
analytics
}) => {
const [isOpen, setOpen] = useState(false);
const count = actions.length;
const onMoreClick = useCallback(shouldBeOpen => {
if (shouldBeOpen) {
// Only fire this event when OPENING the dropdown
analytics(duration => moreActionsClicked('team', {
duration,
numActions: count + 2
}));
}
setOpen(shouldBeOpen);
}, [analytics, count]);
if (!count) {
return null;
}
return /*#__PURE__*/React.createElement(MoreButton, null, /*#__PURE__*/React.createElement(Popup, {
isOpen: isOpen,
onClose: () => setOpen(false),
placement: "bottom-start",
content: () => /*#__PURE__*/React.createElement(MenuGroup, null, actions.map((action, index) => /*#__PURE__*/React.createElement(LinkItem, {
onClick: onActionClick(action, analytics, index + 2),
key: action.id || index,
href: action.link
}, action.label))),
trigger: triggerProps => {
return fg('ptc_migrate_buttons') ? /*#__PURE__*/React.createElement(IconButton, _extends({
testId: "more-actions-button"
}, triggerProps, {
isSelected: isOpen,
onClick: () => onMoreClick(!isOpen),
icon: MoreIcon,
label: "actions"
})) : /*#__PURE__*/React.createElement(ButtonLegacy, _extends({
testId: "more-actions-button"
}, triggerProps, {
isSelected: isOpen,
onClick: () => onMoreClick(!isOpen),
iconAfter: /*#__PURE__*/React.createElement(MoreIcon, {
spacing: "spacious",
label: "actions",
color: "currentColor"
})
}));
},
zIndex: layers.modal(),
shouldRenderToParent: fg('enable_appropriate_reading_order_in_profile_card')
}));
};
const ButtonSection = ({
actions,
analytics
}) => {
if (!actions) {
return null;
}
const extraActions = actions.slice(2);
const initialActions = actions.slice(0, 2);
return /*#__PURE__*/React.createElement(ActionButtons, null, initialActions.map((action, index) => /*#__PURE__*/React.createElement(ActionButton, {
action: action,
analytics: analytics,
index: index,
key: index
})), extraActions && /*#__PURE__*/React.createElement(ExtraActions, {
actions: extraActions,
analytics: analytics
}));
};
const TeamProfilecardContent = ({
actions,
analytics,
team,
viewingUserId,
generateUserLink,
onUserClick,
viewProfileLink,
viewProfileOnClick,
isTriggeredByKeyboard
}) => {
const allActions = [{
label: /*#__PURE__*/React.createElement(FormattedMessage, messages.teamViewProfile),
link: viewProfileLink,
callback: viewProfileOnClick,
id: 'view-profile'
}, ...(actions || [])];
const includingYou = team.members && team.members.some(member => member.id === viewingUserId);
useEffect(() => {
analytics(duration => {
var _team$members;
return profileCardRendered('team', 'content', {
duration,
numActions: allActions.length,
memberCount: (_team$members = team.members) === null || _team$members === void 0 ? void 0 : _team$members.length,
includingYou,
descriptionLength: team.description.length,
titleLength: team.displayName.length
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [analytics]);
return /*#__PURE__*/React.createElement(CardWrapper, {
testId: "team-profilecard"
}, /*#__PURE__*/React.createElement(CardHeader, {
image: team.largeHeaderImageUrl || team.smallHeaderImageUrl,
label: team.displayName
}), /*#__PURE__*/React.createElement(CardContent, null, /*#__PURE__*/React.createElement(Tooltip, {
content: team.displayName
}, /*#__PURE__*/React.createElement(Inline, null, /*#__PURE__*/React.createElement(TeamName, null, team.displayName), team.isVerified && /*#__PURE__*/React.createElement(VerifiedTeamIcon, null))), /*#__PURE__*/React.createElement(TeamMembers, {
analytics: analytics,
members: team.members,
generateUserLink: generateUserLink,
includingYou: includingYou,
onUserClick: onUserClick,
isTriggeredByKeyboard: isTriggeredByKeyboard
}), team.description.trim() && /*#__PURE__*/React.createElement(DescriptionWrapper, null, /*#__PURE__*/React.createElement(Description, null, team.description)), /*#__PURE__*/React.createElement(ButtonSection, {
actions: allActions,
analytics: analytics
})));
};
const ErrorMessage = ({
analytics,
clientFetchProfile,
isLoading
}) => {
const hasRetry = !!clientFetchProfile;
useEffect(() => {
analytics(duration => profileCardRendered('team', 'error', {
duration,
hasRetry
}));
}, [analytics, hasRetry]);
const retry = useCallback(() => {
analytics(duration => errorRetryClicked({
duration
}));
if (clientFetchProfile) {
clientFetchProfile();
}
}, [analytics, clientFetchProfile]);
return /*#__PURE__*/React.createElement(ErrorWrapper, {
testId: "team-profilecard-error"
}, /*#__PURE__*/React.createElement(ErrorIllustration, null), /*#__PURE__*/React.createElement(Text, {
as: "p",
weight: "semibold"
}, /*#__PURE__*/React.createElement(FormattedMessage, messages.teamErrorTitle)), /*#__PURE__*/React.createElement(TeamErrorText, null, /*#__PURE__*/React.createElement(FormattedMessage, messages.teamErrorText)), clientFetchProfile && /*#__PURE__*/React.createElement(ActionButtons, null, /*#__PURE__*/React.createElement(WrappedButton, null, fg('ptc_migrate_buttons') ? /*#__PURE__*/React.createElement(Button, {
testId: "client-fetch-profile-button",
shouldFitContainer: true,
onClick: retry,
isLoading: isLoading
}, /*#__PURE__*/React.createElement(FormattedMessage, messages.teamErrorButton)) : /*#__PURE__*/React.createElement(LoadingButton, {
testId: "client-fetch-profile-button",
shouldFitContainer: true,
onClick: retry,
isLoading: isLoading
}, /*#__PURE__*/React.createElement(FormattedMessage, messages.teamErrorButton)))));
};
const TeamProfileCard = props => {
const {
analytics,
clientFetchProfile,
hasError,
isLoading,
team,
errorType
} = props;
if (hasError) {
if ((errorType === null || errorType === void 0 ? void 0 : errorType.reason) === 'TEAMS_FORBIDDEN') {
return /*#__PURE__*/React.createElement(TeamForbiddenErrorState, {
analytics: analytics
});
} else {
return /*#__PURE__*/React.createElement(CardWrapper, {
testId: "team-profilecard"
}, /*#__PURE__*/React.createElement(ErrorMessage, {
analytics: analytics,
clientFetchProfile: clientFetchProfile,
isLoading: isLoading
}));
}
}
if (isLoading) {
return /*#__PURE__*/React.createElement(TeamLoadingState, {
analytics: analytics
});
}
if (team) {
return /*#__PURE__*/React.createElement(TeamProfilecardContent, _extends({}, props, {
team
}));
}
return null;
};
export default TeamProfileCard;