Implement comprehensive style system with Google Calendar theme
This commit adds a complete style system alongside the existing theme system, allowing users to switch between different UI styles while maintaining theme color variations. **Core Features:** - Style enum (Default, Google Calendar) separate from Theme enum - Hot-swappable stylesheets with dynamic loading - Style preference persistence (localStorage + database) - Style picker UI in sidebar below theme picker **Frontend Implementation:** - Add Style enum to sidebar.rs with value/display methods - Implement dynamic stylesheet loading in app.rs - Add style picker dropdown with proper styling - Handle style state management and persistence - Add web-sys features for HtmlLinkElement support **Backend Integration:** - Add calendar_style column to user_preferences table - Update all database operations (insert/update/select) - Extend API models for style preference - Add migration for existing users **Google Calendar Style:** - Clean Material Design-inspired interface - White sidebar with proper contrast - Enhanced calendar grid with subtle shadows - Improved event styling with hover effects - Google Sans typography throughout - Professional color scheme and spacing **Technical Details:** - Trunk asset management for stylesheet copying - High CSS specificity to override theme styles - Modular CSS architecture for easy extensibility - Comprehensive text contrast fixes - Enhanced calendar cells and navigation Users can now choose between the original gradient design (Default) and a clean Google Calendar-inspired interface (Google Calendar), with full preference persistence across sessions. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,12 @@ pub enum Theme {
|
||||
Mint,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Style {
|
||||
Default,
|
||||
Google,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn value(&self) -> &'static str {
|
||||
match self {
|
||||
@@ -60,6 +66,36 @@ impl Theme {
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn value(&self) -> &'static str {
|
||||
match self {
|
||||
Style::Default => "default",
|
||||
Style::Google => "google",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_value(value: &str) -> Self {
|
||||
match value {
|
||||
"google" => Style::Google,
|
||||
_ => Style::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
Style::Default => "Default",
|
||||
Style::Google => "Google Calendar",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stylesheet_path(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
Style::Default => None, // No additional stylesheet needed - uses base styles.css
|
||||
Style::Google => Some("google.css"), // Trunk copies to root level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ViewMode {
|
||||
fn default() -> Self {
|
||||
ViewMode::Month
|
||||
@@ -80,6 +116,8 @@ pub struct SidebarProps {
|
||||
pub on_view_change: Callback<ViewMode>,
|
||||
pub current_theme: Theme,
|
||||
pub on_theme_change: Callback<Theme>,
|
||||
pub current_style: Style,
|
||||
pub on_style_change: Callback<Style>,
|
||||
}
|
||||
|
||||
#[function_component(Sidebar)]
|
||||
@@ -111,6 +149,18 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
})
|
||||
};
|
||||
|
||||
let on_style_change = {
|
||||
let on_style_change = props.on_style_change.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let target = e.target_dyn_into::<HtmlSelectElement>();
|
||||
if let Some(select) = target {
|
||||
let value = select.value();
|
||||
let new_style = Style::from_value(&value);
|
||||
on_style_change.emit(new_style);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<aside class="app-sidebar">
|
||||
<div class="sidebar-header">
|
||||
@@ -175,6 +225,7 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</div>
|
||||
|
||||
<div class="theme-selector">
|
||||
<label>{"Theme:"}</label>
|
||||
<select class="theme-selector-dropdown" onchange={on_theme_change}>
|
||||
<option value="default" selected={matches!(props.current_theme, Theme::Default)}>{"Default"}</option>
|
||||
<option value="ocean" selected={matches!(props.current_theme, Theme::Ocean)}>{"Ocean"}</option>
|
||||
@@ -187,6 +238,14 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="style-selector">
|
||||
<label>{"Style:"}</label>
|
||||
<select class="style-selector-dropdown" onchange={on_style_change}>
|
||||
<option value="default" selected={matches!(props.current_style, Style::Default)}>{"Default"}</option>
|
||||
<option value="google" selected={matches!(props.current_style, Style::Google)}>{"Google Calendar"}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button onclick={props.on_logout.reform(|_| ())} class="logout-button">{"Logout"}</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user