mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Implement smart context menu positioning
This commit is contained in:
@@ -356,10 +356,34 @@
|
|||||||
animation: fadeInDown 0.2s ease forwards;
|
animation: fadeInDown 0.2s ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.entering-left {
|
||||||
|
animation: fadeInLeft 0.2s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.entering-up {
|
||||||
|
animation: fadeInUp 0.2s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.entering-up-left {
|
||||||
|
animation: fadeInUpLeft 0.2s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
&.closing {
|
&.closing {
|
||||||
animation: fadeOutUp 0.2s ease forwards;
|
animation: fadeOutUp 0.2s ease forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.closing-left {
|
||||||
|
animation: fadeOutRight 0.2s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closing-up {
|
||||||
|
animation: fadeOutDown 0.2s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closing-up-left {
|
||||||
|
animation: fadeOutDownRight 0.2s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
.context-menu-item {
|
.context-menu-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -38,6 +38,78 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUpLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(10px, 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOutRight {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOutDown {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOutDownRight {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(10px, 10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.chat-switch-out {
|
.chat-switch-out {
|
||||||
animation: fadeOutUp 0.2s ease forwards;
|
animation: fadeOutUp 0.2s ease forwards;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,46 @@ export function MessageContextMenu({
|
|||||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
const [replyDialogOpen, setReplyDialogOpen] = useState(false);
|
const [replyDialogOpen, setReplyDialogOpen] = useState(false);
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
|
const [calculatedPosition, setCalculatedPosition] = useState(position);
|
||||||
|
const [animationClass, setAnimationClass] = useState('entering');
|
||||||
|
|
||||||
|
// Calculate smart positioning when component opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
const menuWidth = 160; // min-width from CSS
|
||||||
|
const menuHeight = isAuthor ? 120 : 60; // Approximate height based on items
|
||||||
|
const padding = 10; // Padding from viewport edges
|
||||||
|
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
let x = position.x;
|
||||||
|
let y = position.y;
|
||||||
|
let animation = 'entering';
|
||||||
|
|
||||||
|
// Check if menu would overflow right edge
|
||||||
|
if (x + menuWidth + padding > viewportWidth) {
|
||||||
|
x = viewportWidth - menuWidth - padding;
|
||||||
|
animation = 'entering-left'; // Animation from left side
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if menu would overflow bottom edge
|
||||||
|
if (y + menuHeight + padding > viewportHeight) {
|
||||||
|
y = viewportHeight - menuHeight - padding;
|
||||||
|
animation = 'entering-up'; // Animation from bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both edges would overflow, use top-left positioning
|
||||||
|
if (x + menuWidth + padding > viewportWidth && y + menuHeight + padding > viewportHeight) {
|
||||||
|
x = Math.max(padding, position.x - menuWidth);
|
||||||
|
y = Math.max(padding, position.y - menuHeight);
|
||||||
|
animation = 'entering-up-left';
|
||||||
|
}
|
||||||
|
|
||||||
|
setCalculatedPosition({ x, y });
|
||||||
|
setAnimationClass(animation);
|
||||||
|
}
|
||||||
|
}, [isOpen, position, isAuthor]);
|
||||||
|
|
||||||
// Effect to handle clicks outside the context menu
|
// Effect to handle clicks outside the context menu
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -97,10 +137,15 @@ export function MessageContextMenu({
|
|||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setIsClosing(true);
|
setIsClosing(true);
|
||||||
|
// Set appropriate closing animation based on opening animation
|
||||||
|
const closingAnimation = animationClass.replace('entering', 'closing');
|
||||||
|
setAnimationClass(closingAnimation);
|
||||||
|
|
||||||
// Wait for animation to complete before calling onOpenChange
|
// Wait for animation to complete before calling onOpenChange
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
setIsClosing(false);
|
setIsClosing(false);
|
||||||
|
setAnimationClass('entering'); // Reset for next opening
|
||||||
}, 200); // Match the animation duration from _animations.scss
|
}, 200); // Match the animation duration from _animations.scss
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,12 +167,12 @@ export function MessageContextMenu({
|
|||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<div
|
<div
|
||||||
className={`context-menu ${isClosing ? 'closing' : 'entering'}`}
|
className={`context-menu ${animationClass}`}
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
display: "block",
|
display: "block",
|
||||||
top: position.y,
|
top: calculatedPosition.y,
|
||||||
left: position.x,
|
left: calculatedPosition.x,
|
||||||
zIndex: 1000
|
zIndex: 1000
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
|||||||
Reference in New Issue
Block a user