Skip to content

Automic dark mode#346

Merged
maurerle merged 7 commits intofreifunk:mainfrom
freifunkMUC:auto-dark-mode
May 10, 2026
Merged

Automic dark mode#346
maurerle merged 7 commits intofreifunk:mainfrom
freifunkMUC:auto-dark-mode

Conversation

@grische
Copy link
Copy Markdown
Contributor

@grische grische commented Apr 18, 2026

Description

This changes the current behaviour and decouples dark mode from the map layer.
The user can now select either dark, light or automatic by using the button on the top right.
The automatic theme switches depending on the system theme (without a reload).

Motivation and Context

As the map layer is not saved, it bothered my how my browser switched between dark and bright mode somewhat random.
This mode behaves similar to other apps (e.g. Github) and should be a lot more intuitive.

How Has This Been Tested?

Tested locally in Chrome with all variants of Themes.

Screenshots/links:

See it live here: https://map.ffmuc.net/

image or image

Checklist:

  • My code follows the code style of this project. (CI will test it anyway and also needs approval)
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly.

Comment thread lib/theme.ts Outdated
@maurerle
Copy link
Copy Markdown
Member

maurerle commented May 6, 2026

I merged main into this to use the svg icons folder as well:
https://github.com/freifunk/meshviewer/compare/auto-dark-mode

Can you pleas update this PR to this?

grische and others added 7 commits May 10, 2026 23:46
Add button.theme.{light,dark,auto} across en/de/fr/cz/ru/tr.
Each label names the current theme and the next click's target
("Light (next: dark)"), so sighted and screen-reader users get
the same state-plus-action signal.

Polyglot is configured with allowMissing: true, so missing keys
would render as raw dotted strings to non-English users -- all
locales need them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Theme is currently implied by the selected tile layer (the
baselayerchange handler in map.ts adds theme_<mode> on <html>).
Make theme an explicit user preference so dark mode is reachable
without switching the basemap and persists across sessions. The
implicit coupling is removed in the next commit.

The new module lib/theme.ts owns theme state, storage, DOM class
mutation, and matchMedia subscription. Exposes initTheme,
getTheme, cycleTheme, and themeIconSVG.

Persist the user's choice in localStorage under meshviewer.theme.
All access is wrapped in try/catch so sandboxed iframes, Safari
private mode, and file:// contexts fall back to in-memory state.

On apply, toggle the theme_night class on <html> and dispatch a
"themechange" CustomEvent on document.documentElement so the
map's label canvas can re-render without a direct dependency on
this module.

Auto mode subscribes to prefers-color-scheme so the UI flips
live without a reload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove the theme_* class manipulation from the baselayerchange
handler. Theme is now a user preference owned by lib/theme.ts
and no longer implicitly tied to tile-layer config.

The label canvas derives colors from getComputedStyle on body,
so it still needs to refresh on theme change. LabelLayer
subscribes to the themechange CustomEvent in its onAdd / onRemove
hooks, tying listener lifetime to the layer; map.ts keeps no
theme-related code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a button that cycles light -> dark -> auto. The icon shows
the current theme; the aria-label carries "Light (next: dark)"
from the locale file, giving screen-reader users the same
state-plus-action signal as the icon.

The aria-label also feeds the styled tooltip via the existing
::after { content: attr(aria-label) } rule on .content button.
No title attribute is set, which would stack a native browser
tooltip on top of the styled one.

Call initTheme() before rendering so the matchMedia listener
and storage state are live before the first click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Read the stored theme preference synchronously in <head> and
set theme_night on <html> before any CSS resolves, so dark-mode
users don't see a light flash while Vite loads the main bundle.
Read-only and wrapped in try/catch; the theme module still owns
writes and listeners.

If localStorage throws (sandboxed iframes, storage disabled,
some file:// contexts), a nested try/catch falls back to
prefers-color-scheme alone, so OS-dark users still get the
correct first paint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move sun/moon/auto SVGs from theme.ts string constants into
assets/icons/svg/ and register them in icon.scss. The toggle
button now swaps ion-<state> via classList instead of injecting
innerHTML, dropping themeIconSVG() and the svg-sizing rule in
_button.scss — the shared ion-* ::before pipeline handles
loading and sizing already.

Co-Authored-By: Grische <github@grische.xyz>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Align the CSS class name with the public-facing terminology
introduced earlier in this branch (light / dark / auto). The
DARK_CLASS constant in lib/theme.ts and the inline pre-paint
script in index.html now use "dark"; scss/night.scss is renamed
to scss/dark.scss for symmetry.

No behavior change. The class is set only by lib/theme.ts and
the pre-paint script; nothing else consumed the old name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@maurerle maurerle merged commit f4135d3 into freifunk:main May 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants