pleroma/src/components/profile-hover-card.tsx

151 lines
5.1 KiB
TypeScript
Raw Normal View History

2023-02-06 10:01:03 -08:00
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { useIntl, FormattedMessage } from 'react-intl';
2022-01-10 14:01:24 -08:00
import { usePopper } from 'react-popper';
2022-03-22 05:42:26 -07:00
import { useHistory } from 'react-router-dom';
import { fetchRelationships } from 'soapbox/actions/accounts';
import {
closeProfileHoverCard,
updateProfileHoverCard,
2022-11-16 05:32:32 -08:00
} from 'soapbox/actions/profile-hover-card';
import { useAccount } from 'soapbox/api/hooks';
import Badge from 'soapbox/components/badge';
2022-05-03 06:27:15 -07:00
import ActionButton from 'soapbox/features/ui/components/action-button';
import { UserPanel } from 'soapbox/features/ui/util/async-components';
2022-05-01 11:11:20 -07:00
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
2022-11-15 12:46:23 -08:00
import { showProfileHoverCard } from './hover-ref-wrapper';
import { dateFormatOptions } from './relative-timestamp';
import { Card, CardBody, HStack, Icon, Stack, Text } from './ui';
2022-03-21 11:09:01 -07:00
import type { Account } from 'soapbox/schemas';
2022-05-01 11:11:20 -07:00
import type { AppDispatch } from 'soapbox/store';
const getBadges = (
account?: Pick<Account, 'admin' | 'moderator'>,
): JSX.Element[] => {
const badges = [];
if (account?.admin) {
badges.push(<Badge key='admin' slug='admin' title={<FormattedMessage id='account_moderation_modal.roles.admin' defaultMessage='Admin' />} />);
} else if (account?.moderator) {
badges.push(<Badge key='moderator' slug='moderator' title={<FormattedMessage id='account_moderation_modal.roles.moderator' defaultMessage='Moderator' />} />);
}
return badges;
};
const handleMouseEnter = (dispatch: AppDispatch): React.MouseEventHandler => () => {
dispatch(updateProfileHoverCard());
};
const handleMouseLeave = (dispatch: AppDispatch): React.MouseEventHandler => () => {
dispatch(closeProfileHoverCard(true));
};
2022-05-01 11:11:20 -07:00
interface IProfileHoverCard {
visible?: boolean;
2022-05-01 11:11:20 -07:00
}
/** Popup profile preview that appears when hovering avatars and display names. */
const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }) => {
2022-05-01 11:11:20 -07:00
const dispatch = useAppDispatch();
2022-03-22 05:42:26 -07:00
const history = useHistory();
const intl = useIntl();
2022-05-01 11:11:20 -07:00
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
2022-05-01 11:11:20 -07:00
const me = useAppSelector(state => state.me);
const accountId: string | undefined = useAppSelector(state => state.profile_hover_card.accountId || undefined);
const { account } = useAccount(accountId, { withRelationship: true });
const targetRef = useAppSelector(state => state.profile_hover_card.ref?.current);
const badges = getBadges(account);
useEffect(() => {
if (accountId) dispatch(fetchRelationships([accountId]));
}, [dispatch, accountId]);
2022-03-21 11:09:01 -07:00
useEffect(() => {
const unlisten = history.listen(() => {
showProfileHoverCard.cancel();
dispatch(closeProfileHoverCard());
});
return () => {
unlisten();
};
}, []);
const { styles, attributes } = usePopper(targetRef, popperElement);
if (!account) return null;
2022-05-01 11:11:20 -07:00
const accountBio = { __html: account.note_emojified };
const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' });
const followedBy = me !== account.id && account.relationship?.followed_by === true;
return (
2022-03-21 11:09:01 -07:00
<div
2023-02-06 10:01:03 -08:00
className={clsx({
'absolute transition-opacity w-[320px] z-[101] top-0 left-0': true,
2022-03-21 11:09:01 -07:00
'opacity-100': visible,
'opacity-0 pointer-events-none': !visible,
})}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
onMouseEnter={handleMouseEnter(dispatch)}
onMouseLeave={handleMouseLeave(dispatch)}
>
<Card variant='rounded' className='relative isolate overflow-hidden black:rounded-xl black:border black:border-gray-800'>
2022-03-21 11:09:01 -07:00
<CardBody>
<Stack space={2}>
<UserPanel
accountId={account.id}
action={<ActionButton account={account} small />}
badges={badges}
/>
2022-03-21 11:09:01 -07:00
{account.local ? (
<HStack alignItems='center' space={0.5}>
<Icon
src={require('@tabler/icons/outline/calendar.svg')}
2023-02-01 14:13:42 -08:00
className='h-4 w-4 text-gray-800 dark:text-gray-200'
/>
<Text size='sm' title={intl.formatDate(account.created_at, dateFormatOptions)}>
<FormattedMessage
id='account.member_since' defaultMessage='Joined {date}' values={{
date: memberSinceDate,
}}
/>
</Text>
</HStack>
) : null}
{account.note.length > 0 && (
<Text
truncate
size='sm'
dangerouslySetInnerHTML={accountBio}
className='mr-2 rtl:ml-2 rtl:mr-0 [&_br]:hidden [&_p:first-child]:inline [&_p:first-child]:truncate [&_p]:hidden'
/>
2022-03-21 11:09:01 -07:00
)}
</Stack>
{followedBy && (
<div className='absolute left-2 top-2'>
2022-03-21 11:09:01 -07:00
<Badge
slug='opaque'
title={<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />}
2022-03-21 11:09:01 -07:00
/>
</div>
)}
</CardBody>
</Card>
</div>
);
};
export { ProfileHoverCard as default };