Spaces:
Sleeping
Sleeping
File size: 6,757 Bytes
e66cfb4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | import dash
from dash import Dash, html, dcc
from dash import Input, Output, State, callback, clientside_callback
import dash_mantine_components as dmc
from dash_iconify import DashIconify
def generate_toggle_navbar():
@callback(
Output("appshell", "navbar"),
Input("mobile-burger", "opened"),
Input("desktop-burger", "opened"),
State("appshell", "navbar"),
)
def toggle_navbar(mobile_opened, desktop_opened, navbar):
navbar["collapsed"] = {
"mobile": not mobile_opened,
"desktop": not desktop_opened,
}
return navbar
return toggle_navbar
def switch_light_dark():
"""
前端切換 Mantine 亮/暗主題 + 修正文字/背景的自訂 inline 色彩
依據邏輯:
1) 先決定 desired('light' 或 'dark'),優先用 Switch,其次讀 localStorage
2) 套用到 <html>、<body>,並同步 localStorage
3) ★ 套用到根部的 MantineProvider(需要在 app.py: MantineProvider 加 id="mantine-provider")
4) 同步 Switch 外觀,避免與實際狀態不同步
5) 僅對「原本就有 inline color/background」或帶 data-theme-aware 的元素做對應轉色
"""
return clientside_callback(
"""
function(switchOn, pathname) {
// 0) 決定這次要套用的主題
let desired = null;
if (typeof switchOn === 'boolean') {
desired = switchOn ? 'dark' : 'light';
} else {
try { desired = localStorage.getItem('mantine-color-scheme') || 'dark'; }
catch(e) { desired = 'dark'; }
}
const isDark = (desired === 'dark');
// 1) 設定 <html>/<body> 屬性 + localStorage(Mantine 會吃到)
document.documentElement.setAttribute('data-mantine-color-scheme', desired);
if (document.body) document.body.setAttribute('data-mantine-color-scheme', desired);
try {
document.documentElement.dataset.mantineColorScheme = desired;
if (document.body) document.body.dataset.mantineColorScheme = desired;
localStorage.setItem('mantine-color-scheme', desired);
} catch (e) {}
// 2) ★ 套用到 MantineProvider 的根節點(請在 app.py 的 MantineProvider 加 id="mantine-provider")
// 這能讓 Mantine/DMC 的元件樣式(文字、背景、Border)整體跟著切換
const root = document.querySelector('#mantine-provider');
if (root) {
root.setAttribute('data-mantine-color-scheme', desired);
try { root.dataset.mantineColorScheme = desired; } catch (e) {}
}
// 3) 同步 Switch 外觀(避免 UI 與實際主題不同步)
const sw = document.getElementById('color-scheme-switch');
if (sw && typeof switchOn !== 'boolean') {
try { sw.checked = isDark; } catch (e) {}
}
// 4) 文字色的亮<->暗對照(只轉「原本就有 inline color」的元素)
const textLightToDark = {
'black': '#f2f2f2', '#000000': '#f2f2f2',
'gray': '#cccccc', '#808080': '#cccccc',
'blue': '#90caf9', 'darkblue': '#64b5f6',
'teal': '#80cbc4',
'red': '#ef9a9a',
'orange': '#ffcc80',
'#ffa6ff': '#cc66cc'
};
const textDarkToLight = {};
Object.keys(textLightToDark).forEach(k => { textDarkToLight[textLightToDark[k]] = k; });
// 5) 背景暗色 fallback(僅對「原本就有 inline 背景」的元素覆蓋)
const DARK_BG_FALLBACK = 'var(--mantine-color-body)';
const norm = (s) => (s || '').trim().toLowerCase();
// 6) 只處理三種元素:
// - 有 inline color
// - 有 inline background/background-color
// - 或者主動標記 data-theme-aware 的元素(即使沒有 inline style 也會處理)
const candidates = document.querySelectorAll(
'[style*="color:"], [style*="background:"], [style*="background-color:"], [data-theme-aware]'
);
candidates.forEach(el => {
const cs = getComputedStyle(el);
const styleAttr = el.getAttribute('style') || '';
// A) 記錄原始文字色(第一次遇到時才記)
const hadInlineColor = /(^|;)\\s*color\\s*:/.test(styleAttr) || el.hasAttribute('data-theme-aware');
if (hadInlineColor && !el.dataset.originalColor) {
// 優先記 inline,其次 computed
el.dataset.originalColor = norm(el.style.color || cs.color || '');
}
// B) 記錄原始背景(第一次遇到時才記)
const hadInlineBg = /(^|;)\\s*background(-color)?\\s*:/.test(styleAttr) || el.hasAttribute('data-theme-aware');
if (hadInlineBg && !el.dataset.hadInlineBg) {
el.dataset.hadInlineBg = '1';
el.dataset.originalInlineBg = norm(el.style.backgroundColor || cs.backgroundColor || '');
} else if (!el.dataset.hadInlineBg) {
el.dataset.hadInlineBg = '0';
}
// === 文字色反轉:僅動「原來就有 inline color」或 data-theme-aware 的元素 ===
if (el.dataset.originalColor) {
const cur = norm(el.style.color || cs.color || '');
let nextColor;
if (isDark) {
nextColor = textLightToDark[cur] || textLightToDark[el.dataset.originalColor] || cur;
} else {
nextColor = textDarkToLight[cur] || el.dataset.originalColor || cur;
}
el.style.setProperty('color', nextColor, 'important');
}
// === 背景色反轉:僅動「原來就有 inline 背景」或 data-theme-aware ===
if (el.dataset.hadInlineBg === '1') {
if (isDark) {
el.style.setProperty('background-color', DARK_BG_FALLBACK, 'important');
} else {
const restore = el.dataset.originalInlineBg || '';
if (restore) {
el.style.setProperty('background-color', restore, 'important');
} else {
el.style.removeProperty('background-color');
}
}
}
});
// 回傳字串避免 undefined(觸發 dash 更新)
return desired + ':' + Date.now();
}
""",
Output("theme-dummy", "children"),
# 兩個輸入:切換開關 + 換頁/首次載入
[Input("color-scheme-switch", "checked"),
Input("_pages_location", "pathname")]
)
|