useTheme.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { reactive, computed, watchEffect } from 'vue';
  2. import { theme as antdTheme } from 'ant-design-vue';
  3. // Single shared theme state. `import { theme } from '@/composables/useTheme.js'`
  4. // from any component to read/toggle. Boot side-effects (apply current
  5. // theme to <body>/<html>) run once at module load so the page is in the
  6. // right theme before Vue mounts.
  7. const STORAGE_DARK = 'dark-mode';
  8. const STORAGE_ULTRA = 'isUltraDarkThemeEnabled';
  9. function readBool(key, fallback) {
  10. const raw = localStorage.getItem(key);
  11. if (raw === null) return fallback;
  12. return raw === 'true';
  13. }
  14. const isDark = readBool(STORAGE_DARK, true);
  15. const isUltra = readBool(STORAGE_ULTRA, true);
  16. export const theme = reactive({
  17. isDark,
  18. isUltra,
  19. });
  20. export const currentTheme = computed(() => (theme.isDark ? 'dark' : 'light'));
  21. // AD-Vue 4 theme config consumed by every page's <a-config-provider>.
  22. // Three modes — light / dark / ultra-dark — all share AD-Vue's vanilla
  23. // blue primary. Dark uses a neutral grey palette modelled on VS Code's
  24. // Dark+ chrome (`#1e1e1e` editor, `#252526` sidebar, `#2d2d30` panel),
  25. // so the panel reads as a familiar modern IDE rather than the older
  26. // navy shade. Ultra-dark stays pure-black on darkAlgorithm.
  27. const DARK_TOKENS = {
  28. colorBgBase: '#1e1e1e',
  29. colorBgLayout: '#1e1e1e',
  30. colorBgContainer: '#252526',
  31. colorBgElevated: '#2d2d30',
  32. };
  33. const ULTRA_DARK_TOKENS = {
  34. colorBgBase: '#000',
  35. colorBgLayout: '#000',
  36. colorBgContainer: '#0a0a0a',
  37. colorBgElevated: '#141414',
  38. };
  39. // AD-Vue 4 hardcodes navy `#001529` / `#002140` as the Layout sider
  40. // + trigger backgrounds and `#001529` / `#000c17` as the dark Menu item
  41. // backgrounds (see node_modules/ant-design-vue/es/{layout,menu}/style/
  42. // index.js). Override at the component-token level so the sider blends
  43. // with darkAlgorithm's neutral surfaces. Sider/trigger use the same
  44. // `#252526` / `#333333` tones VS Code does for its activity bar.
  45. const DARK_LAYOUT_TOKENS = {
  46. colorBgHeader: '#252526',
  47. colorBgTrigger: '#333333',
  48. colorBgBody: '#1e1e1e',
  49. };
  50. const ULTRA_DARK_LAYOUT_TOKENS = {
  51. colorBgHeader: '#0a0a0a',
  52. colorBgTrigger: '#141414',
  53. colorBgBody: '#000',
  54. };
  55. const DARK_MENU_TOKENS = {
  56. colorItemBg: '#252526',
  57. colorSubItemBg: '#1e1e1e',
  58. menuSubMenuBg: '#252526',
  59. };
  60. const ULTRA_DARK_MENU_TOKENS = {
  61. colorItemBg: '#0a0a0a',
  62. colorSubItemBg: '#000',
  63. menuSubMenuBg: '#0a0a0a',
  64. };
  65. export const antdThemeConfig = computed(() => {
  66. if (!theme.isDark) {
  67. return { algorithm: antdTheme.defaultAlgorithm };
  68. }
  69. return {
  70. algorithm: antdTheme.darkAlgorithm,
  71. token: theme.isUltra ? ULTRA_DARK_TOKENS : DARK_TOKENS,
  72. components: {
  73. Layout: theme.isUltra ? ULTRA_DARK_LAYOUT_TOKENS : DARK_LAYOUT_TOKENS,
  74. Menu: theme.isUltra ? ULTRA_DARK_MENU_TOKENS : DARK_MENU_TOKENS,
  75. },
  76. };
  77. });
  78. export function toggleTheme() {
  79. theme.isDark = !theme.isDark;
  80. }
  81. export function toggleUltra() {
  82. theme.isUltra = !theme.isUltra;
  83. }
  84. // Briefly disable theme transition animations while a toggle is in
  85. // flight, then re-enable on mouseleave. Mirrors the legacy panel's
  86. // behavior of preventing flicker when hovering the theme menu.
  87. export function pauseAnimationsUntilLeave(elementId) {
  88. document.documentElement.setAttribute('data-theme-animations', 'off');
  89. const el = document.getElementById(elementId);
  90. if (!el) return;
  91. const restore = () => {
  92. document.documentElement.removeAttribute('data-theme-animations');
  93. el.removeEventListener('mouseleave', restore);
  94. el.removeEventListener('touchend', restore);
  95. };
  96. el.addEventListener('mouseleave', restore);
  97. el.addEventListener('touchend', restore);
  98. }
  99. // Apply theme to DOM and persist whenever it changes.
  100. watchEffect(() => {
  101. document.body.setAttribute('class', theme.isDark ? 'dark' : 'light');
  102. localStorage.setItem(STORAGE_DARK, String(theme.isDark));
  103. if (theme.isUltra) {
  104. document.documentElement.setAttribute('data-theme', 'ultra-dark');
  105. } else {
  106. document.documentElement.removeAttribute('data-theme');
  107. }
  108. localStorage.setItem(STORAGE_ULTRA, String(theme.isUltra));
  109. // Keep the global #message container's class in sync so AD-Vue toasts
  110. // pick up the right styling.
  111. const msg = document.getElementById('message');
  112. if (msg) msg.className = theme.isDark ? 'dark' : 'light';
  113. });