diff --git a/frontend/src/components/calendar_context_menu.rs b/frontend/src/components/calendar_context_menu.rs index 7e9c080..8f8dd44 100644 --- a/frontend/src/components/calendar_context_menu.rs +++ b/frontend/src/components/calendar_context_menu.rs @@ -18,9 +18,45 @@ pub fn calendar_context_menu(props: &CalendarContextMenuProps) -> Html { return html! {}; } + // Smart positioning to keep menu within viewport + let (x, y) = { + let mut x = props.x; + let mut y = props.y; + + // Try to get actual viewport dimensions + if let Some(window) = web_sys::window() { + if let (Ok(width), Ok(height)) = (window.inner_width(), window.inner_height()) { + if let (Some(w), Some(h)) = (width.as_f64(), height.as_f64()) { + let viewport_width = w as i32; + let viewport_height = h as i32; + + // Calendar context menu: "Create Event" with icon + let menu_width = 180; // "Create Event" text + icon + padding + let menu_height = 60; // Single item + padding + margins + + // Adjust horizontally if too close to right edge + if x + menu_width > viewport_width - 10 { + x = x.saturating_sub(menu_width); + } + + // Adjust vertically if too close to bottom edge + if y + menu_height > viewport_height - 10 { + y = y.saturating_sub(menu_height); + } + + // Ensure minimum margins from edges + x = x.max(5); + y = y.max(5); + } + } + } + + (x, y) + }; + let style = format!( "position: fixed; left: {}px; top: {}px; z-index: 1001;", - props.x, props.y + x, y ); let on_create_event_click = { diff --git a/frontend/src/components/context_menu.rs b/frontend/src/components/context_menu.rs index b0cd7dc..714c9c8 100644 --- a/frontend/src/components/context_menu.rs +++ b/frontend/src/components/context_menu.rs @@ -20,9 +20,45 @@ pub fn context_menu(props: &ContextMenuProps) -> Html { return html! {}; } + // Smart positioning to keep menu within viewport + let (x, y) = { + let mut x = props.x; + let mut y = props.y; + + // Try to get actual viewport dimensions + if let Some(window) = web_sys::window() { + if let (Ok(width), Ok(height)) = (window.inner_width(), window.inner_height()) { + if let (Some(w), Some(h)) = (width.as_f64(), height.as_f64()) { + let viewport_width = w as i32; + let viewport_height = h as i32; + + // Generic context menu: "Delete Calendar" + let menu_width = 180; // "Delete Calendar" text + padding + let menu_height = 60; // Single item + padding + margins + + // Adjust horizontally if too close to right edge + if x + menu_width > viewport_width - 10 { + x = x.saturating_sub(menu_width); + } + + // Adjust vertically if too close to bottom edge + if y + menu_height > viewport_height - 10 { + y = y.saturating_sub(menu_height); + } + + // Ensure minimum margins from edges + x = x.max(5); + y = y.max(5); + } + } + } + + (x, y) + }; + let style = format!( "position: fixed; left: {}px; top: {}px; z-index: 1001;", - props.x, props.y + x, y ); let on_delete_click = { diff --git a/frontend/src/components/event_context_menu.rs b/frontend/src/components/event_context_menu.rs index c5c4b3e..e54680a 100644 --- a/frontend/src/components/event_context_menu.rs +++ b/frontend/src/components/event_context_menu.rs @@ -35,9 +35,53 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html { return html! {}; } + // Smart positioning to keep menu within viewport + let (x, y) = { + let mut x = props.x; + let mut y = props.y; + + // Try to get actual viewport dimensions + if let Some(window) = web_sys::window() { + if let (Ok(width), Ok(height)) = (window.inner_width(), window.inner_height()) { + if let (Some(w), Some(h)) = (width.as_f64(), height.as_f64()) { + let viewport_width = w as i32; + let viewport_height = h as i32; + + // More accurate menu dimensions based on actual CSS and content + let menu_width = if props.event.as_ref().map_or(false, |e| e.rrule.is_some()) { + 280 // Recurring: "Edit This and Future Events" is long text + padding + } else { + 180 // Non-recurring: "Edit Event" + "Delete Event" + padding + }; + let menu_height = if props.event.as_ref().map_or(false, |e| e.rrule.is_some()) { + 200 // 6 items × ~32px per item (12px padding top/bottom + text height + borders) + } else { + 100 // 2 items × ~32px per item + some extra margin + }; + + // Adjust horizontally if too close to right edge + if x + menu_width > viewport_width - 10 { + x = x.saturating_sub(menu_width); + } + + // Adjust vertically if too close to bottom edge + if y + menu_height > viewport_height - 10 { + y = y.saturating_sub(menu_height); + } + + // Ensure minimum margins from edges + x = x.max(5); + y = y.max(5); + } + } + } + + (x, y) + }; + let style = format!( "position: fixed; left: {}px; top: {}px; z-index: 1001;", - props.x, props.y + x, y ); // Check if the event is recurring