88061bac10
New installs land on plain dark instead of ultra-dark. The cycle button icon now has an explicit colour so it stays visible inside the mobile drawer (the previous color:inherit didn't cascade through the teleported node), and hover/focus matches the menu's blue across sidebar, login, and sub pages.
129 lines
4.2 KiB
JavaScript
129 lines
4.2 KiB
JavaScript
import { reactive, computed, watchEffect } from 'vue';
|
|
import { theme as antdTheme } from 'ant-design-vue';
|
|
|
|
// Single shared theme state. `import { theme } from '@/composables/useTheme.js'`
|
|
// from any component to read/toggle. Boot side-effects (apply current
|
|
// theme to <body>/<html>) run once at module load so the page is in the
|
|
// right theme before Vue mounts.
|
|
|
|
const STORAGE_DARK = 'dark-mode';
|
|
const STORAGE_ULTRA = 'isUltraDarkThemeEnabled';
|
|
|
|
function readBool(key, fallback) {
|
|
const raw = localStorage.getItem(key);
|
|
if (raw === null) return fallback;
|
|
return raw === 'true';
|
|
}
|
|
|
|
const isDark = readBool(STORAGE_DARK, true);
|
|
const isUltra = readBool(STORAGE_ULTRA, false);
|
|
|
|
export const theme = reactive({
|
|
isDark,
|
|
isUltra,
|
|
});
|
|
|
|
export const currentTheme = computed(() => (theme.isDark ? 'dark' : 'light'));
|
|
|
|
// AD-Vue 4 theme config consumed by every page's <a-config-provider>.
|
|
// Three modes — light / dark / ultra-dark — all share AD-Vue's vanilla
|
|
// blue primary. Dark uses a neutral grey palette modelled on VS Code's
|
|
// Dark+ chrome (`#1e1e1e` editor, `#252526` sidebar, `#2d2d30` panel),
|
|
// so the panel reads as a familiar modern IDE rather than the older
|
|
// navy shade. Ultra-dark stays pure-black on darkAlgorithm.
|
|
const DARK_TOKENS = {
|
|
colorBgBase: '#1e1e1e',
|
|
colorBgLayout: '#1e1e1e',
|
|
colorBgContainer: '#252526',
|
|
colorBgElevated: '#2d2d30',
|
|
};
|
|
const ULTRA_DARK_TOKENS = {
|
|
colorBgBase: '#000',
|
|
colorBgLayout: '#000',
|
|
colorBgContainer: '#0a0a0a',
|
|
colorBgElevated: '#141414',
|
|
};
|
|
|
|
// AD-Vue 4 hardcodes navy `#001529` / `#002140` as the Layout sider
|
|
// + trigger backgrounds and `#001529` / `#000c17` as the dark Menu item
|
|
// backgrounds (see node_modules/ant-design-vue/es/{layout,menu}/style/
|
|
// index.js). Override at the component-token level so the sider blends
|
|
// with darkAlgorithm's neutral surfaces. Sider/trigger use the same
|
|
// `#252526` / `#333333` tones VS Code does for its activity bar.
|
|
const DARK_LAYOUT_TOKENS = {
|
|
colorBgHeader: '#252526',
|
|
colorBgTrigger: '#333333',
|
|
colorBgBody: '#1e1e1e',
|
|
};
|
|
const ULTRA_DARK_LAYOUT_TOKENS = {
|
|
colorBgHeader: '#0a0a0a',
|
|
colorBgTrigger: '#141414',
|
|
colorBgBody: '#000',
|
|
};
|
|
const DARK_MENU_TOKENS = {
|
|
colorItemBg: '#252526',
|
|
colorSubItemBg: '#1e1e1e',
|
|
menuSubMenuBg: '#252526',
|
|
};
|
|
const ULTRA_DARK_MENU_TOKENS = {
|
|
colorItemBg: '#0a0a0a',
|
|
colorSubItemBg: '#000',
|
|
menuSubMenuBg: '#0a0a0a',
|
|
};
|
|
|
|
export const antdThemeConfig = computed(() => {
|
|
if (!theme.isDark) {
|
|
return { algorithm: antdTheme.defaultAlgorithm };
|
|
}
|
|
return {
|
|
algorithm: antdTheme.darkAlgorithm,
|
|
token: theme.isUltra ? ULTRA_DARK_TOKENS : DARK_TOKENS,
|
|
components: {
|
|
Layout: theme.isUltra ? ULTRA_DARK_LAYOUT_TOKENS : DARK_LAYOUT_TOKENS,
|
|
Menu: theme.isUltra ? ULTRA_DARK_MENU_TOKENS : DARK_MENU_TOKENS,
|
|
},
|
|
};
|
|
});
|
|
|
|
export function toggleTheme() {
|
|
theme.isDark = !theme.isDark;
|
|
}
|
|
|
|
export function toggleUltra() {
|
|
theme.isUltra = !theme.isUltra;
|
|
}
|
|
|
|
// Briefly disable theme transition animations while a toggle is in
|
|
// flight, then re-enable on mouseleave. Mirrors the legacy panel's
|
|
// behavior of preventing flicker when hovering the theme menu.
|
|
export function pauseAnimationsUntilLeave(elementId) {
|
|
document.documentElement.setAttribute('data-theme-animations', 'off');
|
|
const el = document.getElementById(elementId);
|
|
if (!el) return;
|
|
const restore = () => {
|
|
document.documentElement.removeAttribute('data-theme-animations');
|
|
el.removeEventListener('mouseleave', restore);
|
|
el.removeEventListener('touchend', restore);
|
|
};
|
|
el.addEventListener('mouseleave', restore);
|
|
el.addEventListener('touchend', restore);
|
|
}
|
|
|
|
// Apply theme to DOM and persist whenever it changes.
|
|
watchEffect(() => {
|
|
document.body.setAttribute('class', theme.isDark ? 'dark' : 'light');
|
|
localStorage.setItem(STORAGE_DARK, String(theme.isDark));
|
|
|
|
if (theme.isUltra) {
|
|
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
|
} else {
|
|
document.documentElement.removeAttribute('data-theme');
|
|
}
|
|
localStorage.setItem(STORAGE_ULTRA, String(theme.isUltra));
|
|
|
|
// Keep the global #message container's class in sync so AD-Vue toasts
|
|
// pick up the right styling.
|
|
const msg = document.getElementById('message');
|
|
if (msg) msg.className = theme.isDark ? 'dark' : 'light';
|
|
});
|