Major CSS cleanup and mobile detection system
CSS Improvements: - Remove all mobile responsive CSS (@media queries) - 348+ lines removed - Add comprehensive CSS variables for glass effects, control dimensions, transitions - Consolidate duplicate patterns (43+ transition, 37+ border-radius, 61+ padding instances) - Remove legacy week grid CSS section - Reduce total CSS from 4,197 to 3,828 lines (8.8% reduction) Sidebar Enhancements: - Remove unused sidebar-nav div and navigation link - Standardize all dropdown controls to consistent 40px height and styling - Reduce calendar item padding from 0.75rem to 0.5rem for more compact display - Unify theme-selector and style-selector styling with view-selector Mobile Detection: - Add MobileWarningModal component with device detection - Show helpful popup directing mobile users to native CalDAV apps - Add Navigator and DomTokenList web-sys features - Desktop-focused experience with appropriate mobile guidance 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ web-sys = { version = "0.3", features = [
|
|||||||
"Document",
|
"Document",
|
||||||
"Window",
|
"Window",
|
||||||
"Location",
|
"Location",
|
||||||
|
"Navigator",
|
||||||
|
"DomTokenList",
|
||||||
"Headers",
|
"Headers",
|
||||||
"Request",
|
"Request",
|
||||||
"RequestInit",
|
"RequestInit",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::components::{
|
use crate::components::{
|
||||||
CalendarContextMenu, ContextMenu, CreateCalendarModal, CreateEventModal, DeleteAction,
|
CalendarContextMenu, ContextMenu, CreateCalendarModal, CreateEventModal, DeleteAction,
|
||||||
EditAction, EventContextMenu, EventModal, EventCreationData, ExternalCalendarModal, RouteHandler,
|
EditAction, EventContextMenu, EventModal, EventCreationData, ExternalCalendarModal,
|
||||||
Sidebar, Theme, ViewMode,
|
MobileWarningModal, RouteHandler, Sidebar, Theme, ViewMode,
|
||||||
};
|
};
|
||||||
|
use crate::components::mobile_warning_modal::is_mobile_device;
|
||||||
use crate::components::sidebar::{Style};
|
use crate::components::sidebar::{Style};
|
||||||
use crate::models::ical::VEvent;
|
use crate::models::ical::VEvent;
|
||||||
use crate::services::{calendar_service::{UserInfo, ExternalCalendar}, CalendarService};
|
use crate::services::{calendar_service::{UserInfo, ExternalCalendar}, CalendarService};
|
||||||
@@ -117,6 +118,9 @@ pub fn App() -> Html {
|
|||||||
// External calendar state
|
// External calendar state
|
||||||
let external_calendars = use_state(|| -> Vec<ExternalCalendar> { Vec::new() });
|
let external_calendars = use_state(|| -> Vec<ExternalCalendar> { Vec::new() });
|
||||||
let external_calendar_events = use_state(|| -> Vec<VEvent> { Vec::new() });
|
let external_calendar_events = use_state(|| -> Vec<VEvent> { Vec::new() });
|
||||||
|
|
||||||
|
// Mobile warning state
|
||||||
|
let mobile_warning_open = use_state(|| is_mobile_device());
|
||||||
let external_calendar_modal_open = use_state(|| false);
|
let external_calendar_modal_open = use_state(|| false);
|
||||||
let refresh_interval = use_state(|| -> Option<Interval> { None });
|
let refresh_interval = use_state(|| -> Option<Interval> { None });
|
||||||
|
|
||||||
@@ -274,6 +278,13 @@ pub fn App() -> Html {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let on_mobile_warning_close = {
|
||||||
|
let mobile_warning_open = mobile_warning_open.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
mobile_warning_open.set(false);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let on_view_change = {
|
let on_view_change = {
|
||||||
let current_view = current_view.clone();
|
let current_view = current_view.clone();
|
||||||
Callback::from(move |new_view: ViewMode| {
|
Callback::from(move |new_view: ViewMode| {
|
||||||
@@ -1676,6 +1687,12 @@ pub fn App() -> Html {
|
|||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
// Mobile warning modal
|
||||||
|
<MobileWarningModal
|
||||||
|
is_open={*mobile_warning_open}
|
||||||
|
on_close={on_mobile_warning_close}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
}
|
}
|
||||||
|
|||||||
96
frontend/src/components/mobile_warning_modal.rs
Normal file
96
frontend/src/components/mobile_warning_modal.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
use web_sys::window;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct MobileWarningModalProps {
|
||||||
|
pub is_open: bool,
|
||||||
|
pub on_close: Callback<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(MobileWarningModal)]
|
||||||
|
pub fn mobile_warning_modal(props: &MobileWarningModalProps) -> Html {
|
||||||
|
let on_backdrop_click = {
|
||||||
|
let on_close = props.on_close.clone();
|
||||||
|
Callback::from(move |e: MouseEvent| {
|
||||||
|
if let Some(target) = e.target() {
|
||||||
|
let element = target.dyn_into::<web_sys::Element>().unwrap();
|
||||||
|
if element.class_list().contains("modal-overlay") {
|
||||||
|
on_close.emit(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if !props.is_open {
|
||||||
|
return html! {};
|
||||||
|
}
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div class="modal-overlay mobile-warning-overlay" onclick={on_backdrop_click}>
|
||||||
|
<div class="modal-content mobile-warning-modal">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>{"Desktop Application"}</h2>
|
||||||
|
<button class="modal-close" onclick={
|
||||||
|
let on_close = props.on_close.clone();
|
||||||
|
Callback::from(move |_: MouseEvent| on_close.emit(()))
|
||||||
|
}>{"×"}</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mobile-warning-icon">
|
||||||
|
{"💻"}
|
||||||
|
</div>
|
||||||
|
<p class="mobile-warning-title">
|
||||||
|
{"This calendar application is designed for desktop usage"}
|
||||||
|
</p>
|
||||||
|
<p class="mobile-warning-description">
|
||||||
|
{"For the best mobile calendar experience, we recommend using dedicated CalDAV apps available on your device's app store:"}
|
||||||
|
</p>
|
||||||
|
<ul class="mobile-warning-apps">
|
||||||
|
<li>
|
||||||
|
<strong>{"iOS:"}</strong>
|
||||||
|
{" Calendar (built-in), Calendars 5, Fantastical"}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>{"Android:"}</strong>
|
||||||
|
{" Google Calendar, DAVx5, CalDAV Sync"}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p class="mobile-warning-note">
|
||||||
|
{"These apps can sync with the same CalDAV server you're using here."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="continue-anyway-button" onclick={
|
||||||
|
let on_close = props.on_close.clone();
|
||||||
|
Callback::from(move |_: MouseEvent| on_close.emit(()))
|
||||||
|
}>
|
||||||
|
{"Continue Anyway"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to detect mobile devices
|
||||||
|
pub fn is_mobile_device() -> bool {
|
||||||
|
if let Some(window) = window() {
|
||||||
|
let navigator = window.navigator();
|
||||||
|
let user_agent = navigator.user_agent().unwrap_or_default();
|
||||||
|
let user_agent = user_agent.to_lowercase();
|
||||||
|
|
||||||
|
// Check for mobile device indicators
|
||||||
|
user_agent.contains("mobile")
|
||||||
|
|| user_agent.contains("android")
|
||||||
|
|| user_agent.contains("iphone")
|
||||||
|
|| user_agent.contains("ipad")
|
||||||
|
|| user_agent.contains("ipod")
|
||||||
|
|| user_agent.contains("blackberry")
|
||||||
|
|| user_agent.contains("webos")
|
||||||
|
|| user_agent.contains("opera mini")
|
||||||
|
|| user_agent.contains("windows phone")
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ pub mod event_form;
|
|||||||
pub mod event_modal;
|
pub mod event_modal;
|
||||||
pub mod external_calendar_modal;
|
pub mod external_calendar_modal;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
pub mod mobile_warning_modal;
|
||||||
pub mod month_view;
|
pub mod month_view;
|
||||||
pub mod recurring_edit_modal;
|
pub mod recurring_edit_modal;
|
||||||
pub mod route_handler;
|
pub mod route_handler;
|
||||||
@@ -29,6 +30,7 @@ pub use event_context_menu::{DeleteAction, EditAction, EventContextMenu};
|
|||||||
pub use event_modal::EventModal;
|
pub use event_modal::EventModal;
|
||||||
pub use external_calendar_modal::ExternalCalendarModal;
|
pub use external_calendar_modal::ExternalCalendarModal;
|
||||||
pub use login::Login;
|
pub use login::Login;
|
||||||
|
pub use mobile_warning_modal::MobileWarningModal;
|
||||||
pub use month_view::MonthView;
|
pub use month_view::MonthView;
|
||||||
pub use recurring_edit_modal::{RecurringEditAction, RecurringEditModal};
|
pub use recurring_edit_modal::{RecurringEditAction, RecurringEditModal};
|
||||||
pub use route_handler::RouteHandler;
|
pub use route_handler::RouteHandler;
|
||||||
|
|||||||
@@ -204,9 +204,6 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<nav class="sidebar-nav">
|
|
||||||
<Link<Route> to={Route::Calendar} classes="nav-link">{"Calendar"}</Link<Route>>
|
|
||||||
</nav>
|
|
||||||
{
|
{
|
||||||
if let Some(ref info) = props.user_info {
|
if let Some(ref info) = props.user_info {
|
||||||
if !info.calendars.is_empty() {
|
if !info.calendars.is_empty() {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user