feat(settings): preview theme and UI fonts on hover
This commit is contained in:
8 files changed
+194
-17
No files matched your search
@@ -108,6 +108,33 @@ describe("useTheme on Linux", () => {
|
||||
expect(setTheme).toHaveBeenLastCalledWith("light");
|
||||
});
|
||||
|
||||
it("previews a palette without persisting it and restores the saved palette when cancelled", async () => {
|
||||
const theme = await loadTheme("light");
|
||||
theme.setThemePalette("pearl");
|
||||
await flushDynamicImport();
|
||||
setTheme.mockClear();
|
||||
|
||||
theme.previewThemePalette("cobalt");
|
||||
|
||||
expect(theme.themePalette.value).toBe("cobalt");
|
||||
expect(document.documentElement.classList.contains("theme-cobalt")).toBe(true);
|
||||
expect(window.localStorage.getItem("dbx-theme-palette")).toBe("pearl");
|
||||
expect(setTheme).not.toHaveBeenCalled();
|
||||
|
||||
theme.clearThemePalettePreview();
|
||||
|
||||
expect(theme.themePalette.value).toBe("pearl");
|
||||
expect(document.documentElement.classList.contains("theme-cobalt")).toBe(false);
|
||||
expect(window.localStorage.getItem("dbx-theme-palette")).toBe("pearl");
|
||||
|
||||
theme.previewThemePalette("sage");
|
||||
theme.setThemePalette("sage");
|
||||
theme.clearThemePalettePreview();
|
||||
|
||||
expect(theme.themePalette.value).toBe("sage");
|
||||
expect(window.localStorage.getItem("dbx-theme-palette")).toBe("sage");
|
||||
});
|
||||
|
||||
it("injects custom UI colors as inline CSS variables and removes them when leaving the custom palette", async () => {
|
||||
const theme = await loadTheme();
|
||||
theme.setThemePalette("custom");
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { useUiFontFamilyPreview } from "@/composables/useUiFontFamilyPreview";
|
||||
|
||||
describe("useUiFontFamilyPreview", () => {
|
||||
beforeEach(() => {
|
||||
useUiFontFamilyPreview().clearUiFontFamilyPreview();
|
||||
});
|
||||
|
||||
it("keeps a temporary font preview separate from the saved setting", () => {
|
||||
const preview = useUiFontFamilyPreview();
|
||||
|
||||
preview.previewUiFontFamily("'Preview Sans', sans-serif");
|
||||
expect(preview.uiFontFamilyPreview.value).toBe("'Preview Sans', sans-serif");
|
||||
|
||||
preview.clearUiFontFamilyPreview();
|
||||
expect(preview.uiFontFamilyPreview.value).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores empty font values", () => {
|
||||
const preview = useUiFontFamilyPreview();
|
||||
|
||||
preview.previewUiFontFamily(" ");
|
||||
expect(preview.uiFontFamilyPreview.value).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -35,7 +35,11 @@ function isLinuxTauriRuntime() {
|
||||
const savedThemeMode = safeLocalStorageGet(APP_THEME_STORAGE_KEY);
|
||||
const themeMode = ref<AppThemeMode>(normalizeAppThemeMode(savedThemeMode));
|
||||
const savedThemePalette = safeLocalStorageGet(APP_THEME_PALETTE_STORAGE_KEY);
|
||||
const themePalette = ref<AppThemePalette>(normalizeAppThemePalette(savedThemePalette));
|
||||
const savedThemePaletteValue = ref<AppThemePalette>(normalizeAppThemePalette(savedThemePalette));
|
||||
const previewedThemePalette = ref<AppThemePalette | null>(null);
|
||||
// Consumers use the effective palette so editor and canvas surfaces preview in
|
||||
// lockstep with the document-level theme classes.
|
||||
const themePalette = computed<AppThemePalette>(() => previewedThemePalette.value ?? savedThemePaletteValue.value);
|
||||
function parseStoredCustomUiColors(raw: string | null): AppCustomUiColors | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
@@ -75,6 +79,16 @@ function setupSystemThemeListener() {
|
||||
isListeningForSystemTheme = true;
|
||||
}
|
||||
|
||||
function applyThemePalette() {
|
||||
if (typeof document === "undefined") return;
|
||||
|
||||
const doc = document.documentElement;
|
||||
for (const className of APP_THEME_PALETTE_CLASS_NAMES) doc.classList.remove(className);
|
||||
const paletteClass = getAppThemePaletteClass(themePalette.value);
|
||||
if (paletteClass) doc.classList.add(paletteClass);
|
||||
applyCustomUiColors();
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
if (typeof document === "undefined") return;
|
||||
|
||||
@@ -83,12 +97,9 @@ function applyTheme() {
|
||||
|
||||
doc.classList.add("disable-transitions");
|
||||
doc.classList.toggle("dark", dark);
|
||||
for (const className of APP_THEME_PALETTE_CLASS_NAMES) doc.classList.remove(className);
|
||||
const paletteClass = getAppThemePaletteClass(themePalette.value);
|
||||
if (paletteClass) doc.classList.add(paletteClass);
|
||||
applyThemePalette();
|
||||
doc.dataset.cornerStyle = cornerStyle.value;
|
||||
doc.style.colorScheme = dark ? "dark" : "light";
|
||||
applyCustomUiColors();
|
||||
|
||||
// force reflow so the class toggle takes effect before re-enabling transitions
|
||||
doc.offsetHeight; // eslint-disable-line @typescript-eslint/no-unused-expressions
|
||||
@@ -123,11 +134,26 @@ function setThemeMode(mode: AppThemeMode) {
|
||||
}
|
||||
|
||||
function setThemePalette(palette: AppThemePalette) {
|
||||
themePalette.value = palette;
|
||||
savedThemePaletteValue.value = palette;
|
||||
previewedThemePalette.value = null;
|
||||
safeLocalStorageSet(APP_THEME_PALETTE_STORAGE_KEY, palette);
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
function previewThemePalette(palette: AppThemePalette) {
|
||||
if (themePalette.value === palette) return;
|
||||
previewedThemePalette.value = palette;
|
||||
// Palette previews do not change the OS/window mode, so avoid the native
|
||||
// Tauri write performed by applyTheme().
|
||||
applyThemePalette();
|
||||
}
|
||||
|
||||
function clearThemePalettePreview() {
|
||||
if (previewedThemePalette.value === null) return;
|
||||
previewedThemePalette.value = null;
|
||||
applyThemePalette();
|
||||
}
|
||||
|
||||
function applyCustomUiColors() {
|
||||
if (typeof document === "undefined") return;
|
||||
const doc = document.documentElement;
|
||||
@@ -180,5 +206,5 @@ export function useTheme() {
|
||||
setThemeMode(isDark.value ? "light" : "dark");
|
||||
}
|
||||
|
||||
return { isDark, themeMode, themePalette, customUiColors, customUiColorsDark, activeCustomUiColors, cornerStyle, applyTheme, setThemeMode, setThemePalette, setCustomUiColors, resetCustomUiColors, setCornerStyle, toggleTheme };
|
||||
return { isDark, themeMode, themePalette, customUiColors, customUiColorsDark, activeCustomUiColors, cornerStyle, applyTheme, setThemeMode, setThemePalette, previewThemePalette, clearThemePalettePreview, setCustomUiColors, resetCustomUiColors, setCornerStyle, toggleTheme };
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
const previewedUiFontFamily = ref<string | null>(null);
|
||||
|
||||
export function useUiFontFamilyPreview() {
|
||||
const uiFontFamilyPreview = computed(() => previewedUiFontFamily.value);
|
||||
|
||||
function previewUiFontFamily(fontFamily: string) {
|
||||
const next = fontFamily.trim();
|
||||
if (!next) return;
|
||||
previewedUiFontFamily.value = next;
|
||||
}
|
||||
|
||||
function clearUiFontFamilyPreview() {
|
||||
previewedUiFontFamily.value = null;
|
||||
}
|
||||
|
||||
return { uiFontFamilyPreview, previewUiFontFamily, clearUiFontFamilyPreview };
|
||||
}
|
||||
Reference in new issue
Block a user