IT Assets: add dark mode matching portal color scheme
- Inline <script> in <head> applies edp_theme from localStorage before first paint — no flash of unstyled content - Bootstrap 5.3 data-bs-theme + custom data-edp-theme attribute for overrides - Dark palette mirrors portal (bg #0d1117, surface #161b22, card #1c2128, accent-blue #388bfd, text #e6edf3, borders #30363d) - CSS tokens for both themes via :root and [data-edp-theme='dark'] - All Bootstrap components overridden in dark mode: cards, tables, forms, modals, alerts, breadcrumbs, code, badges, inputs - Moon/sun toggle button in topbar; sun shown in dark mode, moon in light - Shared localStorage key 'edp_theme' — setting persists across all EDP apps
This commit is contained in:
@@ -4,18 +4,54 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}IT Asset Management{% endblock %}</title>
|
<title>{% block title %}IT Asset Management{% endblock %}</title>
|
||||||
|
<!-- Apply theme BEFORE paint to avoid flash -->
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var t = localStorage.getItem('edp_theme') || 'light';
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', t);
|
||||||
|
document.documentElement.setAttribute('data-edp-theme', t);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<!-- Bootstrap 5 -->
|
<!-- Bootstrap 5 -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||||||
<!-- Bootstrap Icons -->
|
<!-- Bootstrap Icons -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
<style>
|
<style>
|
||||||
|
/* ── Light-mode tokens ──────────────────────────────────────────────── */
|
||||||
:root {
|
:root {
|
||||||
--sidebar-bg: #1a3a5c;
|
--sidebar-bg: #1a3a5c;
|
||||||
--sidebar-text: #cce0f5;
|
--sidebar-text: #cce0f5;
|
||||||
--sidebar-active: #2e6da4;
|
--sidebar-active: #2e6da4;
|
||||||
--accent: #2e86de;
|
--accent: #2e86de;
|
||||||
|
--page-bg: #f0f4f8;
|
||||||
|
--topbar-bg: #ffffff;
|
||||||
|
--topbar-border: #dde3ea;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--card-border: #e2e8f0;
|
||||||
|
--text-primary: #1a202c;
|
||||||
|
--text-muted: #718096;
|
||||||
|
--hover-row: #f5f8ff;
|
||||||
|
--header-color: #1a3a5c;
|
||||||
}
|
}
|
||||||
body { background: #f0f4f8; font-size: .92rem; }
|
|
||||||
|
/* ── Dark-mode tokens (portal palette) ─────────────────────────────── */
|
||||||
|
[data-edp-theme="dark"] {
|
||||||
|
--sidebar-bg: #0d1117;
|
||||||
|
--sidebar-text: #8b949e;
|
||||||
|
--sidebar-active: #1c2128;
|
||||||
|
--accent: #388bfd;
|
||||||
|
--page-bg: #0d1117;
|
||||||
|
--topbar-bg: #161b22;
|
||||||
|
--topbar-border: #30363d;
|
||||||
|
--card-bg: #1c2128;
|
||||||
|
--card-border: #30363d;
|
||||||
|
--text-primary: #e6edf3;
|
||||||
|
--text-muted: #8b949e;
|
||||||
|
--hover-row: #21262d;
|
||||||
|
--header-color: #e6edf3;
|
||||||
|
}
|
||||||
|
|
||||||
|
body { background: var(--page-bg); font-size: .92rem; color: var(--text-primary); transition: background .2s, color .2s; }
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
#sidebar {
|
#sidebar {
|
||||||
@@ -26,7 +62,7 @@
|
|||||||
top: 0; left: 0;
|
top: 0; left: 0;
|
||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
transition: width .2s;
|
transition: background .2s;
|
||||||
}
|
}
|
||||||
#sidebar .brand {
|
#sidebar .brand {
|
||||||
padding: 1.1rem 1.2rem;
|
padding: 1.1rem 1.2rem;
|
||||||
@@ -41,7 +77,7 @@
|
|||||||
font-size: .68rem;
|
font-size: .68rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: .08em;
|
letter-spacing: .08em;
|
||||||
color: rgba(255,255,255,.4);
|
color: rgba(255,255,255,.35);
|
||||||
padding: .75rem 1.2rem .25rem;
|
padding: .75rem 1.2rem .25rem;
|
||||||
}
|
}
|
||||||
#sidebar .nav-link {
|
#sidebar .nav-link {
|
||||||
@@ -68,16 +104,30 @@
|
|||||||
/* Main content */
|
/* Main content */
|
||||||
#main-wrapper { margin-left: 240px; min-height: 100vh; display: flex; flex-direction: column; }
|
#main-wrapper { margin-left: 240px; min-height: 100vh; display: flex; flex-direction: column; }
|
||||||
#topbar {
|
#topbar {
|
||||||
background: #fff;
|
background: var(--topbar-bg);
|
||||||
border-bottom: 1px solid #dde3ea;
|
border-bottom: 1px solid var(--topbar-border);
|
||||||
padding: .55rem 1.5rem;
|
padding: .55rem 1.5rem;
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
position: sticky; top: 0; z-index: 900;
|
position: sticky; top: 0; z-index: 900;
|
||||||
box-shadow: 0 1px 4px rgba(0,0,0,.06);
|
box-shadow: 0 1px 4px rgba(0,0,0,.06);
|
||||||
|
transition: background .2s, border-color .2s;
|
||||||
}
|
}
|
||||||
#topbar .breadcrumb { margin: 0; background: none; padding: 0; font-size: .85rem; }
|
#topbar .breadcrumb { margin: 0; background: none; padding: 0; font-size: .85rem; }
|
||||||
#page-content { flex: 1; padding: 1.5rem; }
|
#page-content { flex: 1; padding: 1.5rem; }
|
||||||
|
|
||||||
|
/* Theme toggle button */
|
||||||
|
#themeToggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: .25rem .4rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: color .15s;
|
||||||
|
}
|
||||||
|
#themeToggle:hover { color: var(--accent); }
|
||||||
|
|
||||||
/* Cards */
|
/* Cards */
|
||||||
.stat-card { border: none; border-radius: .6rem; box-shadow: 0 2px 8px rgba(0,0,0,.07); }
|
.stat-card { border: none; border-radius: .6rem; box-shadow: 0 2px 8px rgba(0,0,0,.07); }
|
||||||
.stat-card .card-body { padding: 1.1rem 1.3rem; }
|
.stat-card .card-body { padding: 1.1rem 1.3rem; }
|
||||||
@@ -94,7 +144,7 @@
|
|||||||
.badge-masked { background:#6f42c1 !important; }
|
.badge-masked { background:#6f42c1 !important; }
|
||||||
|
|
||||||
/* Tables */
|
/* Tables */
|
||||||
.table-hover tbody tr:hover { background:#f5f8ff; }
|
.table-hover tbody tr:hover { background: var(--hover-row); }
|
||||||
|
|
||||||
/* Masked row */
|
/* Masked row */
|
||||||
tr.masked-row { opacity: .65; font-style: italic; }
|
tr.masked-row { opacity: .65; font-style: italic; }
|
||||||
@@ -103,7 +153,41 @@
|
|||||||
.search-bar { max-width: 360px; }
|
.search-bar { max-width: 360px; }
|
||||||
|
|
||||||
/* Page header */
|
/* Page header */
|
||||||
.page-header h1 { font-size: 1.35rem; font-weight: 700; color: #1a3a5c; margin: 0; }
|
.page-header h1 { font-size: 1.35rem; font-weight: 700; color: var(--header-color); margin: 0; }
|
||||||
|
|
||||||
|
/* ── Dark-mode Bootstrap overrides ──────────────────────────────────── */
|
||||||
|
[data-edp-theme="dark"] .card { background: var(--card-bg); border-color: var(--card-border); }
|
||||||
|
[data-edp-theme="dark"] .card-header,
|
||||||
|
[data-edp-theme="dark"] .card-footer { background: var(--card-bg) !important; border-color: var(--card-border) !important; }
|
||||||
|
[data-edp-theme="dark"] #topbar { box-shadow: 0 1px 4px rgba(0,0,0,.4); }
|
||||||
|
[data-edp-theme="dark"] .table { --bs-table-hover-bg: var(--hover-row); color: var(--text-primary); }
|
||||||
|
[data-edp-theme="dark"] .table-light { --bs-table-bg: #161b22; color: var(--text-muted); }
|
||||||
|
[data-edp-theme="dark"] .table { border-color: var(--card-border); }
|
||||||
|
[data-edp-theme="dark"] .form-control,
|
||||||
|
[data-edp-theme="dark"] .form-select { background-color: #0d1117; color: var(--text-primary); border-color: var(--card-border); }
|
||||||
|
[data-edp-theme="dark"] .form-control:focus,
|
||||||
|
[data-edp-theme="dark"] .form-select:focus { background-color: #0d1117; color: var(--text-primary); border-color: var(--accent); box-shadow: 0 0 0 .25rem rgba(56,139,253,.2); }
|
||||||
|
[data-edp-theme="dark"] .form-control::placeholder { color: var(--text-muted); }
|
||||||
|
[data-edp-theme="dark"] .input-group-text { background-color: #161b22; border-color: var(--card-border); color: var(--text-muted); }
|
||||||
|
[data-edp-theme="dark"] .breadcrumb-item,
|
||||||
|
[data-edp-theme="dark"] .breadcrumb-item.active { color: var(--text-secondary, #8b949e); }
|
||||||
|
[data-edp-theme="dark"] .breadcrumb-item a { color: var(--accent); }
|
||||||
|
[data-edp-theme="dark"] .text-muted { color: var(--text-muted) !important; }
|
||||||
|
[data-edp-theme="dark"] .bg-white { background-color: var(--card-bg) !important; }
|
||||||
|
[data-edp-theme="dark"] .bg-light { background-color: #161b22 !important; }
|
||||||
|
[data-edp-theme="dark"] .border,
|
||||||
|
[data-edp-theme="dark"] .border-bottom { border-color: var(--card-border) !important; }
|
||||||
|
[data-edp-theme="dark"] .modal-content { background: var(--card-bg); border-color: var(--card-border); }
|
||||||
|
[data-edp-theme="dark"] .modal-header,
|
||||||
|
[data-edp-theme="dark"] .modal-footer { border-color: var(--card-border); }
|
||||||
|
[data-edp-theme="dark"] .alert-success { background: rgba(63,185,80,.12); border-color: rgba(63,185,80,.3); color: #3fb950; }
|
||||||
|
[data-edp-theme="dark"] .alert-info { background: rgba(56,139,253,.12); border-color: rgba(56,139,253,.3); color: #79c0ff; }
|
||||||
|
[data-edp-theme="dark"] .alert-warning { background: rgba(210,153,34,.12); border-color: rgba(210,153,34,.3); color: #e3b341; }
|
||||||
|
[data-edp-theme="dark"] .alert-danger { background: rgba(248,81,73,.12); border-color: rgba(248,81,73,.3); color: #f85149; }
|
||||||
|
[data-edp-theme="dark"] .btn-close { filter: invert(1); }
|
||||||
|
[data-edp-theme="dark"] code { background: rgba(56,139,253,.12); color: #79c0ff; padding: 1px 5px; border-radius: 3px; }
|
||||||
|
[data-edp-theme="dark"] .page-header h1 { color: var(--text-primary); }
|
||||||
|
[data-edp-theme="dark"] .badge.bg-secondary { background-color: #30363d !important; }
|
||||||
</style>
|
</style>
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
@@ -215,6 +299,9 @@
|
|||||||
<i class="bi bi-calendar3"></i>
|
<i class="bi bi-calendar3"></i>
|
||||||
{{ now.strftime('%d %b %Y') if now else '' }}
|
{{ now.strftime('%d %b %Y') if now else '' }}
|
||||||
</span>
|
</span>
|
||||||
|
<button id="themeToggle" title="Toggle dark / light mode">
|
||||||
|
<i class="bi bi-moon-stars-fill" id="themeIcon"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -236,6 +323,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
/* ── Theme toggle (shared edp_theme key with portal) ── */
|
||||||
|
(function () {
|
||||||
|
var icon = document.getElementById('themeIcon');
|
||||||
|
var btn = document.getElementById('themeToggle');
|
||||||
|
|
||||||
|
function applyTheme(t) {
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', t);
|
||||||
|
document.documentElement.setAttribute('data-edp-theme', t);
|
||||||
|
if (icon) {
|
||||||
|
icon.className = t === 'dark' ? 'bi bi-sun-fill' : 'bi bi-moon-stars-fill';
|
||||||
|
}
|
||||||
|
localStorage.setItem('edp_theme', t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init icon from current theme
|
||||||
|
var current = localStorage.getItem('edp_theme') || 'light';
|
||||||
|
applyTheme(current);
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
var next = document.documentElement.getAttribute('data-edp-theme') === 'dark' ? 'light' : 'dark';
|
||||||
|
applyTheme(next);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% block extra_js %}{% endblock %}
|
{% block extra_js %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user