[FEATURE] Dark Theme

This commit is contained in:
Pit Friedrich
2026-07-11 22:31:08 +02:00
parent aa8fbb4838
commit 9df764d065
7 changed files with 140 additions and 52 deletions
@@ -24,16 +24,6 @@ public final class DialectTheme {
private static final String FONT_FAMILY =
"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
/**
* Mirrors {@code --dialect-border} in styles.css. Charts render via JS/SVG,
* not CSS, so this can't read the custom property directly — keep both in sync
* by hand if the token changes.
*/
private static final String BORDER_COLOR = "#E6EAF0";
/** Mirrors {@code --dialect-ink} in styles.css. See {@link #BORDER_COLOR} note. */
private static final String INK_COLOR = "#14171F";
private DialectTheme() {
}
@@ -50,20 +40,13 @@ public final class DialectTheme {
options.put("colors", COLORS);
options.put("grid", Map.of(
"borderColor", BORDER_COLOR,
"strokeDashArray", 4
));
options.put("legend", Map.of(
"position", "bottom",
"labels", Map.of("colors", INK_COLOR)
));
// grid.borderColor, legend.labels.colors and tooltip.theme are theme-varying
// (light vs. dark) and get overlaid client-side from the --dialect-* CSS
// tokens — see applyThemeOverlay() in apex-chart.ts.
options.put("grid", Map.of("strokeDashArray", 4));
options.put("legend", Map.of("position", "bottom"));
options.put("dataLabels", Map.of("enabled", false));
options.put("tooltip", Map.of("theme", "light"));
if ("line".equals(chartType)) {
options.put("stroke", Map.of("curve", "smooth", "width", 3));
} else if ("bar".equals(chartType)) {
@@ -0,0 +1,25 @@
package com.example.js;
public final class CommonJS {
private CommonJS() {}
/** Sets color-scheme on <html> from the saved choice, falling back to the OS
* preference (prefers-color-scheme) when no choice was saved, and
* returns "light"/"dark" so the button's initial icon can match. */
public static final String INIT_THEME_JS = """
const saved = localStorage.getItem('dialect-theme');
const theme = saved
|| (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.style.colorScheme = theme;
return theme;
""";
public static final String TOGGLE_THEME_JS = """
const next = document.documentElement.style.colorScheme === 'dark' ? 'light' : 'dark';
document.documentElement.style.colorScheme = next;
localStorage.setItem('dialect-theme', next);
window.dispatchEvent(new CustomEvent('dialect-theme-change'));
return next;
""";
}
@@ -1,7 +1,10 @@
package com.example.views;
import com.example.js.CommonJS;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.sidenav.SideNav;
@@ -11,13 +14,25 @@ import com.vaadin.flow.router.Layout;
@Layout
public class MainLayout extends AppLayout {
private final Button themeToggle = new Button(VaadinIcon.MOON.create());
public MainLayout() {
DrawerToggle toggle = new DrawerToggle();
Span title = new Span("Chart App");
title.addClassName("dialect-title");
addToNavbar(toggle, title);
Span spacer = new Span();
spacer.getStyle().set("flex-grow", "1");
themeToggle.addThemeVariants(ButtonVariant.TERTIARY, ButtonVariant.LUMO_ICON);
themeToggle.setAriaLabel("Farbschema wechseln");
themeToggle.addClickListener(e ->
getElement().executeJs(CommonJS.TOGGLE_THEME_JS).then(String.class, this::applyIconFor));
addToNavbar(toggle, title, spacer, themeToggle);
getElement().executeJs(CommonJS.INIT_THEME_JS).then(String.class, this::applyIconFor);
SideNav nav = new SideNav();
nav.addItem(new SideNavItem("Dashboard", DashboardView.class,
@@ -27,4 +42,8 @@ public class MainLayout extends AppLayout {
addToDrawer(nav);
}
private void applyIconFor(String theme) {
themeToggle.setIcon("dark".equals(theme) ? VaadinIcon.SUN_O.create() : VaadinIcon.MOON.create());
}
}