feat(chat): add ESC key support and improve positioning for UserProfilePopover
This commit is contained in:
parent
98a982b9fb
commit
ac964ea98b
2 changed files with 19 additions and 3 deletions
|
|
@ -83,6 +83,7 @@ export function ChatItem({
|
||||||
? `/api/dm/messages/${message.id}`
|
? `/api/dm/messages/${message.id}`
|
||||||
: `/api/messages/${message.id}`;
|
: `/api/messages/${message.id}`;
|
||||||
|
|
||||||
|
/** Closes the profile popover when clicking outside or pressing the Escape key. */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -93,14 +94,24 @@ export function ChatItem({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
setIsProfileOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (isProfileOpen) {
|
if (isProfileOpen) {
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
document.addEventListener("keydown", handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
document.removeEventListener("keydown", handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [isProfileOpen]);
|
}, [isProfileOpen]);
|
||||||
|
|
||||||
|
/** Determines the friendship status between the current user and the message author. */
|
||||||
const getFriendshipStatus = () => {
|
const getFriendshipStatus = () => {
|
||||||
if (
|
if (
|
||||||
!user?.id ||
|
!user?.id ||
|
||||||
|
|
|
||||||
|
|
@ -40,25 +40,30 @@ export function UserProfilePopover({
|
||||||
const [justSent, setJustSent] = useState(false);
|
const [justSent, setJustSent] = useState(false);
|
||||||
|
|
||||||
const isSelf = user.id === currentUserId;
|
const isSelf = user.id === currentUserId;
|
||||||
|
|
||||||
const isAlreadyFriend = friendshipStatus === "ACCEPTED";
|
const isAlreadyFriend = friendshipStatus === "ACCEPTED";
|
||||||
const isPending = friendshipStatus === "PENDING" || justSent;
|
const isPending = friendshipStatus === "PENDING" || justSent;
|
||||||
const isButtonDisabled = isLoading || isAlreadyFriend || isPending;
|
const isButtonDisabled = isLoading || isAlreadyFriend || isPending;
|
||||||
|
|
||||||
|
/** Precise positioning with Y- and X-axis corrections. */
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!triggerRef.current || !popoverRef.current) return;
|
if (!triggerRef.current || !popoverRef.current) return;
|
||||||
|
|
||||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||||
const popoverRect = popoverRef.current.getBoundingClientRect();
|
const popoverRect = popoverRef.current.getBoundingClientRect();
|
||||||
const viewportHeight = window.innerHeight;
|
const viewportHeight = window.innerHeight;
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
|
||||||
let top = triggerRect.bottom + 8;
|
let top = triggerRect.bottom + 8;
|
||||||
const left = triggerRect.left;
|
let left = triggerRect.left;
|
||||||
|
|
||||||
if (top + popoverRect.height > viewportHeight - 16) {
|
if (top + popoverRect.height > viewportHeight - 16) {
|
||||||
top = triggerRect.top - popoverRect.height - 8;
|
top = triggerRect.top - popoverRect.height - 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (left + popoverRect.width > viewportWidth - 16) {
|
||||||
|
left = viewportWidth - popoverRect.width - 16;
|
||||||
|
}
|
||||||
|
|
||||||
setCoords({ top, left });
|
setCoords({ top, left });
|
||||||
setIsVisible(true);
|
setIsVisible(true);
|
||||||
}, [triggerRef]);
|
}, [triggerRef]);
|
||||||
|
|
@ -89,7 +94,7 @@ export function UserProfilePopover({
|
||||||
}}
|
}}
|
||||||
className="z-50 w-64 bg-background border border-surface/80 rounded-2xl p-4 shadow-2xl transition-opacity duration-75 pointer-events-auto"
|
className="z-50 w-64 bg-background border border-surface/80 rounded-2xl p-4 shadow-2xl transition-opacity duration-75 pointer-events-auto"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3">
|
||||||
<UserAvatar user={user} size="sm" />
|
<UserAvatar user={user} size="sm" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-semibold text-foreground truncate leading-tight">
|
<p className="text-sm font-semibold text-foreground truncate leading-tight">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue