UNPKG

@atlaskit/profilecard

Version:

A React component to display a card with user information.

369 lines (368 loc) 14.1 kB
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;