@atlaskit/profilecard
Version:
A React component to display a card with user information.
363 lines • 13.1 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useIntl } from 'react-intl-next';
import { useAnalyticsEvents } from '@atlaskit/analytics-next';
import { GiveKudosLauncherLazy, KudosType } from '@atlaskit/give-kudos';
import { fg } from '@atlaskit/platform-feature-flags';
import Popup from '@atlaskit/popup';
import { layers } from '@atlaskit/theme/constants';
import filterActionsInner from '../../internal/filterActions';
import getLabelMessage from '../../internal/getLabelMessage';
import { CardWrapper } from '../../styled/Card';
import { cardTriggered, fireEvent } from '../../util/analytics';
import { DELAY_MS_HIDE, DELAY_MS_SHOW } from '../../util/config';
import { AgentProfileCardResourced } from '../Agent/AgentProfileCardResourced';
import { ProfileCardLazy } from './lazyProfileCard';
import UserLoadingState from './UserLoadingState';
function ProfileCardContent({
profilecardProps,
userId,
cloudId,
resourceClient,
viewingUserId,
trigger,
product,
isAgent,
profileCardAction,
hasError,
errorType,
agentActions,
addFlag
}) {
if (isAgent) {
return /*#__PURE__*/React.createElement(AgentProfileCardResourced, {
accountId: userId,
cloudId: cloudId,
resourceClient: resourceClient,
viewingUserId: viewingUserId,
trigger: trigger,
product: product,
onChatClick: agentActions === null || agentActions === void 0 ? void 0 : agentActions.onChatClick,
onConversationStartersClick: agentActions === null || agentActions === void 0 ? void 0 : agentActions.onConversationStartersClick,
addFlag: addFlag
});
} else {
return /*#__PURE__*/React.createElement(Suspense, {
fallback: null
}, /*#__PURE__*/React.createElement(ProfileCardLazy, _extends({}, profilecardProps, {
actions: profileCardAction,
hasError: hasError,
errorType: errorType,
withoutElevation: true
})));
}
}
export default function ProfilecardTriggerNext({
autoFocus,
trigger = 'hover',
userId,
cloudId,
resourceClient,
actions = [],
position = 'bottom-start',
children,
testId,
addFlag,
onReportingLinesClick,
ariaLabel,
ariaLabelledBy,
prepopulatedData,
disabledAriaAttributes,
onVisibilityChange,
offset,
viewingUserId,
product,
agentActions,
ariaHideProfileTrigger = false,
isVisible: propsIsVisible
}) {
const {
createAnalyticsEvent
} = useAnalyticsEvents();
const {
formatMessage
} = useIntl();
const isMounted = useRef(false);
const showDelay = trigger === 'click' || propsIsVisible && fg('fix_profilecard_trigger_isvisible') ? 0 : DELAY_MS_SHOW;
const hideDelay = trigger === 'click' || propsIsVisible && fg('fix_profilecard_trigger_isvisible') ? 0 : DELAY_MS_HIDE;
const showTimer = useRef(0);
const hideTimer = useRef(0);
const [visible, setVisible] = useState(false);
const [isLoading, setIsLoading] = useState(undefined);
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
const [reportingLinesData, setReportingLinesData] = useState(undefined);
const [shouldShowGiveKudos, setShouldShowGiveKudos] = useState(false);
const [teamCentralBaseUrl, setTeamCentralBaseUrl] = useState(undefined);
const [kudosDrawerOpen, setKudosDrawerOpen] = useState(false);
const [isTriggeredUsingKeyboard, setTriggeredUsingKeyboard] = useState(false);
const triggerRef = useRef(null);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
clearTimeout(showTimer.current);
clearTimeout(hideTimer.current);
};
}, []);
useEffect(() => {
// Reset state when the userId changes
setIsLoading(undefined);
setHasError(false);
setError(null);
setData(null);
setReportingLinesData(undefined);
setShouldShowGiveKudos(false);
setTeamCentralBaseUrl(undefined);
}, [userId]);
const fireAnalytics = useCallback(payload => {
// Don't fire any analytics if the component is unmounted
if (!isMounted.current) {
return;
}
fireEvent(createAnalyticsEvent, payload);
}, [createAnalyticsEvent]);
const hideProfilecard = useCallback(() => {
clearTimeout(showTimer.current);
clearTimeout(hideTimer.current);
if (!isTriggeredUsingKeyboard) {
hideTimer.current = window.setTimeout(() => {
setVisible(false);
onVisibilityChange && onVisibilityChange(false);
}, hideDelay);
}
}, [hideDelay, isTriggeredUsingKeyboard, onVisibilityChange]);
const handleKeyboardClose = useCallback(event => {
if (event.key && event.key !== 'Escape') {
return;
}
if (triggerRef.current) {
triggerRef.current.focus();
}
setTriggeredUsingKeyboard(false);
setVisible(false);
onVisibilityChange && onVisibilityChange(false);
}, [setTriggeredUsingKeyboard, setVisible, onVisibilityChange]);
const handleClientSuccess = useCallback((profileData, reportingLinesData, shouldShowGiveKudos, teamCentralBaseUrl) => {
if (!isMounted.current) {
return;
}
setIsLoading(false);
setHasError(false);
setData(profileData);
setReportingLinesData(reportingLinesData);
setTeamCentralBaseUrl(teamCentralBaseUrl);
setShouldShowGiveKudos(shouldShowGiveKudos);
}, [setHasError, setIsLoading, setData, setReportingLinesData, setShouldShowGiveKudos]);
const handleClientError = useCallback(err => {
if (!isMounted.current) {
return;
}
setIsLoading(false);
setHasError(true);
setError(err);
}, [setHasError, setIsLoading, setError]);
const clientFetchProfile = useCallback(async () => {
if (isLoading === true) {
// don't fetch data when fetching is in process
return;
}
setIsLoading(true);
setHasError(false);
setError(null);
setData(null);
try {
const requests = Promise.all([resourceClient.getProfile(cloudId || '', userId, fireAnalytics), resourceClient.getReportingLines(userId), resourceClient.shouldShowGiveKudos(), resourceClient.getTeamCentralBaseUrl({
withOrgContext: true,
withSiteContext: true
})]);
const responses = await requests;
handleClientSuccess(...responses);
} catch (err) {
handleClientError(err);
}
}, [cloudId, fireAnalytics, isLoading, resourceClient, userId, handleClientSuccess, handleClientError]);
const showProfilecard = useCallback(() => {
clearTimeout(hideTimer.current);
clearTimeout(showTimer.current);
showTimer.current = window.setTimeout(() => {
if (!visible) {
void clientFetchProfile();
setVisible(true);
onVisibilityChange && onVisibilityChange(true);
}
}, showDelay);
}, [showDelay, visible, clientFetchProfile, onVisibilityChange]);
const onClick = useCallback(event => {
// If the user clicks on the trigger then we don't want that click event to
// propagate out to parent containers. For example when clicking a mention
// lozenge in an inline-edit.
event.stopPropagation();
showProfilecard();
if (!visible) {
fireAnalytics(cardTriggered('user', 'click'));
}
}, [fireAnalytics, showProfilecard, visible]);
const onMouseEnter = useCallback(() => {
showProfilecard();
if (!visible) {
fireAnalytics(cardTriggered('user', 'hover'));
}
}, [fireAnalytics, showProfilecard, visible]);
const onKeyPress = useCallback(event => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
setTriggeredUsingKeyboard(true);
showProfilecard();
if (!visible) {
fireAnalytics(cardTriggered('user', 'click'));
}
}
}, [fireAnalytics, showProfilecard, visible]);
const onFocus = useCallback(() => {
showProfilecard();
}, [showProfilecard]);
useEffect(() => {
if (!fg('fix_profilecard_trigger_isvisible')) {
return;
}
// If the prop isVisible is not defined, we don't want to do anything
if (propsIsVisible === undefined) {
return;
}
// If the prop isVisible is defined, we want to show or hide the profile card based on the value
if (propsIsVisible) {
showProfilecard();
} else {
hideProfilecard();
}
}, [hideProfilecard, propsIsVisible, showProfilecard]);
const containerListeners = useMemo(() => trigger === 'hover' ? {
onMouseEnter: onMouseEnter,
onMouseLeave: hideProfilecard,
onBlur: hideProfilecard,
onKeyPress: onKeyPress
} : {
onClick: onClick,
onKeyPress: onKeyPress
}, [hideProfilecard, onClick, onKeyPress, onMouseEnter, trigger]);
const filterActions = useCallback(() => {
return filterActionsInner(actions, data);
}, [actions, data]);
const openKudosDrawer = () => {
hideProfilecard();
setKudosDrawerOpen(true);
};
const closeKudosDrawer = () => {
setKudosDrawerOpen(false);
};
const showLoading = isLoading === true || isLoading === undefined;
const wrapperProps = useMemo(() => trigger === 'hover' ? {
onMouseEnter: onMouseEnter,
onMouseLeave: hideProfilecard,
onFocus: onFocus
} : {}, [hideProfilecard, onFocus, onMouseEnter, trigger]);
const profilecardProps = {
userId: userId,
fullName: prepopulatedData === null || prepopulatedData === void 0 ? void 0 : prepopulatedData.fullName,
isCurrentUser: userId === viewingUserId,
clientFetchProfile: clientFetchProfile,
...data,
reportingLines: reportingLinesData,
onReportingLinesClick: onReportingLinesClick,
isKudosEnabled: shouldShowGiveKudos,
teamCentralBaseUrl: teamCentralBaseUrl,
cloudId: cloudId,
openKudosDrawer: openKudosDrawer,
isTriggeredUsingKeyboard: isTriggeredUsingKeyboard,
disabledAriaAttributes: disabledAriaAttributes
};
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Popup, {
isOpen: !!visible,
onClose: event => {
hideProfilecard();
handleKeyboardClose(event);
},
placement: position,
offset: offset !== null && offset !== void 0 ? offset : [0, 8],
content: () => /*#__PURE__*/React.createElement("div", wrapperProps, showLoading ? /*#__PURE__*/React.createElement(LoadingView, {
fireAnalytics: fireAnalytics
}) : visible && /*#__PURE__*/React.createElement(ProfileCardContent, {
profilecardProps: profilecardProps,
isAgent: !!(data !== null && data !== void 0 && data.isAgent),
userId: userId,
cloudId: cloudId,
resourceClient: resourceClient,
viewingUserId: viewingUserId,
trigger: trigger,
product: product,
profileCardAction: filterActions(),
errorType: error,
hasError: hasError,
agentActions: agentActions,
addFlag: addFlag
})),
trigger: triggerProps => {
const {
ref: callbackRef,
...innerProps
} = triggerProps;
const ref = element => {
triggerRef.current = element;
if (typeof callbackRef === 'function') {
callbackRef(element);
}
};
const {
'aria-expanded': _,
'aria-haspopup': __,
...restInnerProps
} = innerProps;
return /*#__PURE__*/React.createElement("span", _extends({}, disabledAriaAttributes ? restInnerProps : triggerProps, containerListeners, {
ref: ref,
"data-testid": testId
}, !ariaHideProfileTrigger && {
'aria-labelledby': ariaLabelledBy
}, disabledAriaAttributes ? {} : {
role: 'button',
// aria-hidden cannot contain focusable elements: https://dequeuniversity.com/rules/axe/3.5/aria-hidden-focus
tabIndex: ariaHideProfileTrigger ? -1 : 0,
'aria-label': ariaHideProfileTrigger ? undefined : getLabelMessage(ariaLabel, profilecardProps.fullName, formatMessage)
}, ariaHideProfileTrigger && {
'aria-hidden': 'true'
}), children);
},
zIndex: layers.modal(),
shouldUseCaptureOnOutsideClick: true,
autoFocus: autoFocus !== null && autoFocus !== void 0 ? autoFocus : trigger === 'click'
// This feature gate is currently enabled only for Jira_Web to avoid UI issues in Confluence_Web.
,
shouldRenderToParent: fg('enable_appropriate_reading_order_in_profile_card')
}), shouldShowGiveKudos && teamCentralBaseUrl && /*#__PURE__*/React.createElement(Suspense, {
fallback: null
}, /*#__PURE__*/React.createElement(GiveKudosLauncherLazy, {
isOpen: kudosDrawerOpen,
recipient: {
type: KudosType.INDIVIDUAL,
recipientId: userId
},
analyticsSource: "profile-card",
teamCentralBaseUrl: teamCentralBaseUrl,
cloudId: cloudId,
addFlag: addFlag,
onClose: closeKudosDrawer
})));
}
const LoadingView = ({
fireAnalytics
}) => /*#__PURE__*/React.createElement(CardWrapper, {
testId: "profilecard.profilecardtrigger.loading"
}, /*#__PURE__*/React.createElement(UserLoadingState, {
fireAnalytics: fireAnalytics
}));