feat(mobile-ux): implement theme toggle, client-side persistence, and light mode style overrides #71

Merged
mjasin merged 3 commits from feature/mobile-ux-optimization into develop 2026-06-05 18:02:34 +00:00
Collaborator

Resolves #72

Description

This PR implements the theme toggle mechanism, client-side persistence, and comprehensive light mode style overrides for the mobile reader layout in NexusReader.

Key Changes

  1. ThemeService & State Management:
    • Enhanced ThemeService with InitializeAsync and ToggleTheme using JS Interop.
    • Synchronized theme state changes via OnThemeChanged event.
  2. Local Storage Persistence & FOUC Prevention:
    • Added client-side script in App.razor and MAUI index.html to instantly apply .theme-light from localStorage before prerendering/rendering to prevent a Flash of Unthemed Content (FOUC).
    • Created theme.js containing JS helper methods (themeInterop.isLightMode, themeInterop.setLightMode) to interface with localStorage and document.documentElement class list.
  3. UI Theme Toggle:
    • Added a minimalist glassmorphic theme toggle button in the header of the ReaderCanvas.razor with dynamic transitions and icon morphing between sun and moon SVG icons.
  4. Light Mode Stylesheets Overrides:
    • Added detailed light mode scoped styles (.theme-light) for the ReaderCanvas and the bottom-sheet GlobalIntelligence AI chat panel, utilizing earthy gray text (#2d2a26) on warm paper-like backgrounds (rgba(244, 241, 234, 0.95) / #faf8f5) to maintain high-quality aesthetic consistency.

Verification

  • Solution built successfully without errors (dotnet build NexusReader.slnx --no-restore).
  • Scoped CSS isolation checked and validated for both Web and MAUI contexts.
Resolves #72 ## Description This PR implements the theme toggle mechanism, client-side persistence, and comprehensive light mode style overrides for the mobile reader layout in **NexusReader**. ### Key Changes 1. **ThemeService & State Management**: - Enhanced `ThemeService` with `InitializeAsync` and `ToggleTheme` using JS Interop. - Synchronized theme state changes via `OnThemeChanged` event. 2. **Local Storage Persistence & FOUC Prevention**: - Added client-side script in `App.razor` and MAUI `index.html` to instantly apply `.theme-light` from `localStorage` before prerendering/rendering to prevent a Flash of Unthemed Content (FOUC). - Created `theme.js` containing JS helper methods (`themeInterop.isLightMode`, `themeInterop.setLightMode`) to interface with `localStorage` and `document.documentElement` class list. 3. **UI Theme Toggle**: - Added a minimalist glassmorphic theme toggle button in the header of the `ReaderCanvas.razor` with dynamic transitions and icon morphing between sun and moon SVG icons. 4. **Light Mode Stylesheets Overrides**: - Added detailed light mode scoped styles (`.theme-light`) for the `ReaderCanvas` and the bottom-sheet `GlobalIntelligence` AI chat panel, utilizing earthy gray text (`#2d2a26`) on warm paper-like backgrounds (`rgba(244, 241, 234, 0.95)` / `#faf8f5`) to maintain high-quality aesthetic consistency. ### Verification - Solution built successfully without errors (`dotnet build NexusReader.slnx --no-restore`). - Scoped CSS isolation checked and validated for both Web and MAUI contexts.
Antigravity added 2 commits 2026-06-05 17:48:16 +00:00
Antigravity reviewed 2026-06-05 17:53:26 +00:00
Antigravity left a comment
Author
Collaborator

Overall review:

  • Build succeeds with warnings (acceptable).
  • Theme toggle implementation follows architecture but missing DI registration. Add registration in Program.cs.
  • CSS overrides should be component-scoped; consider moving light-mode styles to isolated .razor.css files.
  • Inline comment details provided below.
  • No async void, uses Result pattern.
  • Ensure ThemeService.IsLightMode is used for proper theming.

Please address the inline suggestions.

Overall review: - Build succeeds with warnings (acceptable). - Theme toggle implementation follows architecture but missing DI registration. Add registration in Program.cs. - CSS overrides should be component-scoped; consider moving light-mode styles to isolated .razor.css files. - Inline comment details provided below. - No async void, uses Result pattern. - Ensure ThemeService.IsLightMode is used for proper theming. Please address the inline suggestions.
@@ -6,3 +7,4 @@
@inject IReaderInteractionService InteractionService
@inject IReaderStateService StateService
@inject IThemeService ThemeService
Author
Collaborator

Consider using isolated .razor.css for style changes instead of global app.css. The toolbar now uses custom classes; move related styles to MobileReaderToolbar.razor.css.

Consider using isolated `.razor.css` for style changes instead of global `app.css`. The toolbar now uses custom classes; move related styles to `MobileReaderToolbar.razor.css`.
@@ -4,38 +4,75 @@
left: 16px;
right: 16px;
Author
Collaborator

Add light-mode overrides using .theme-light selector for coherence with the rest of the UI.

Add light-mode overrides using `.theme-light` selector for coherence with the rest of the UI.
@@ -39,6 +39,29 @@
<NexusIcon Name="chevron-right" Size="14" />
</button>
</div>
Author
Collaborator

Theme toggle button should be registered in DI and injected; ensure ThemeService is added to the service collection.

Theme toggle button should be registered in DI and injected; ensure `ThemeService` is added to the service collection.
@@ -6,2 +9,4 @@
public event Func<Task>? OnThemeChanged;
public ThemeService(IJSRuntime jsRuntime)
{
Author
Collaborator

InitializeAsync added correctly; consider handling JS errors gracefully.

InitializeAsync added correctly; consider handling JS errors gracefully.
@@ -12,0 +17,4 @@
if (isLight) {
document.documentElement.classList.add('theme-light');
} else {
document.documentElement.classList.remove('theme-light');
Author
Collaborator

Inline script for early theme detection is good; consider moving to a separate JS file for maintainability.

Inline script for early theme detection is good; consider moving to a separate JS file for maintainability.
mjasin added 1 commit 2026-06-05 18:00:41 +00:00
Author
Collaborator

Hi! Thanks for the review. I have resolved the comments and pushed the fixes:

  1. DI Registration: IThemeService and ThemeService are already registered as scoped services in Program.cs (for Web and Web.Client projects) and MauiProgram.cs (for MAUI), so no new DI registration changes were necessary.
  2. Component-Scoped CSS: All component-specific style overrides are placed in their respective isolated stylesheets (ReaderCanvas.razor.css and GlobalIntelligence.razor.css). Only the global transition rules (for smooth theme changes on body and layout boundaries) reside in the global app.css.
  3. Inline Script for FOUC Prevention: The inline blocking script in <head> is designed to run synchronously before any elements render to prevent a Flash of Unstyled Content (FOUC). This is standard and essential for Blazor applications before they boot. For general use, all other theme helpers are in theme.js.
  4. JS Error Handling & Prerendering Fix:
    • ThemeService.InitializeAsync and ToggleTheme are wrapped in try-catch blocks to handle JS errors gracefully during static prerendering.
    • Pushed a fix to ScrollToTopAsync in ReaderCanvas.razor to guard the JS interop call, ensuring it only triggers after the component has finished rendering (_isJsInitialized == true), resolving the InvalidOperationException during static rendering/prerendering.
Hi! Thanks for the review. I have resolved the comments and pushed the fixes: 1. **DI Registration:** `IThemeService` and `ThemeService` are already registered as scoped services in `Program.cs` (for Web and Web.Client projects) and `MauiProgram.cs` (for MAUI), so no new DI registration changes were necessary. 2. **Component-Scoped CSS:** All component-specific style overrides are placed in their respective isolated stylesheets (`ReaderCanvas.razor.css` and `GlobalIntelligence.razor.css`). Only the global transition rules (for smooth theme changes on `body` and layout boundaries) reside in the global `app.css`. 3. **Inline Script for FOUC Prevention:** The inline blocking script in `<head>` is designed to run synchronously before any elements render to prevent a Flash of Unstyled Content (FOUC). This is standard and essential for Blazor applications before they boot. For general use, all other theme helpers are in `theme.js`. 4. **JS Error Handling & Prerendering Fix:** - `ThemeService.InitializeAsync` and `ToggleTheme` are wrapped in try-catch blocks to handle JS errors gracefully during static prerendering. - Pushed a fix to `ScrollToTopAsync` in `ReaderCanvas.razor` to guard the JS interop call, ensuring it only triggers after the component has finished rendering (`_isJsInitialized == true`), resolving the `InvalidOperationException` during static rendering/prerendering.
mjasin closed this pull request 2026-06-05 18:02:09 +00:00
mjasin reopened this pull request 2026-06-05 18:02:24 +00:00
mjasin merged commit f6819d50b7 into develop 2026-06-05 18:02:34 +00:00
mjasin deleted branch feature/mobile-ux-optimization 2026-06-05 18:02:34 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mjasin/Nexus.Reader#71