Files
ncd-fe/packages/pl-fe/src/components/status-hover-card.tsx
nicole mikołajczyk 9f98b5b07d nicolium: oxlint and oxfmt migration, remove eslint
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-15 13:30:55 +01:00

115 lines
2.9 KiB
TypeScript

import { autoUpdate, flip, shift, useFloating, useTransitionStyles } from '@floating-ui/react';
import { useRouter } from '@tanstack/react-router';
import clsx from 'clsx';
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import { fetchStatus } from '@/actions/statuses';
import { showStatusHoverCard } from '@/components/hover-status-wrapper';
import StatusContainer from '@/containers/status-container';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useAppSelector } from '@/hooks/use-app-selector';
import { useStatusHoverCardActions, useStatusHoverCardStore } from '@/stores/status-hover-card';
interface IStatusHoverCard {
visible?: boolean;
}
/** Popup status preview that appears when hovering reply to */
const StatusHoverCard: React.FC<IStatusHoverCard> = ({ visible = true }) => {
const dispatch = useAppDispatch();
const router = useRouter();
const intl = useIntl();
const { statusId, ref } = useStatusHoverCardStore();
const { closeStatusHoverCard, updateStatusHoverCard } = useStatusHoverCardActions();
const status = useAppSelector((state) => state.statuses[statusId!]);
useEffect(() => {
if (statusId && !status) {
dispatch(fetchStatus(statusId, intl));
}
}, [statusId, status]);
useEffect(() => {
const unlisten = router.subscribe('onLoad', ({ pathChanged }) => {
if (pathChanged) {
showStatusHoverCard.cancel();
closeStatusHoverCard(true);
}
});
return () => {
unlisten();
};
}, []);
const { x, y, strategy, refs, context, placement } = useFloating({
open: !!statusId,
elements: {
reference: ref?.current,
},
placement: 'top',
middleware: [
flip(),
shift({
padding: 8,
}),
],
whileElementsMounted: autoUpdate,
});
const { styles } = useTransitionStyles(context, {
initial: {
opacity: 0,
transform: 'scale(0.8)',
transformOrigin: placement === 'bottom' ? 'top' : 'bottom',
},
duration: {
open: 100,
close: 100,
},
});
const handleMouseEnter = () => {
updateStatusHoverCard();
};
const handleMouseLeave = () => {
closeStatusHoverCard(true);
};
if (!statusId) return null;
return (
<div
className={clsx({
'absolute left-0 top-0 z-50 w-[500px] transition-opacity': true,
'opacity-100': visible,
'pointer-events-none opacity-0': !visible,
})}
ref={refs.setFloating}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
...styles,
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<StatusContainer
className='isolate black:rounded-xl black:border black:border-gray-800'
key={statusId}
id={statusId}
hoverable={false}
hideActionBar
muted
/>
</div>
);
};
export { StatusHoverCard as default };