diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index 8cff31d891b..bc077b8f841 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -5,6 +5,7 @@ @import url(fonts.css); /* VARIABLE DEFINITIONS */ +html[data-theme="light"], :root { --primary: #79aec8; --secondary: #417690; @@ -900,7 +901,7 @@ a.deletelink:focus, a.deletelink:hover { } #branding { - float: left; + display: flex; } #branding h1 { diff --git a/django/contrib/admin/static/admin/css/dark_mode.css b/django/contrib/admin/static/admin/css/dark_mode.css index 547717cc6ba..7407447398b 100644 --- a/django/contrib/admin/static/admin/css/dark_mode.css +++ b/django/contrib/admin/static/admin/css/dark_mode.css @@ -31,3 +31,87 @@ --close-button-hover-bg: #666666; } } + + +html[data-theme="dark"] { + --primary: #264b5d; + --primary-fg: #f7f7f7; + + --body-fg: #eeeeee; + --body-bg: #121212; + --body-quiet-color: #e0e0e0; + --body-loud-color: #ffffff; + + --breadcrumbs-link-fg: #e0e0e0; + --breadcrumbs-bg: var(--primary); + + --link-fg: #81d4fa; + --link-hover-color: #4ac1f7; + --link-selected-fg: #6f94c6; + + --hairline-color: #272727; + --border-color: #353535; + + --error-fg: #e35f5f; + --message-success-bg: #006b1b; + --message-warning-bg: #583305; + --message-error-bg: #570808; + + --darkened-bg: #212121; + --selected-bg: #1b1b1b; + --selected-row: #00363a; + + --close-button-bg: #333333; + --close-button-hover-bg: #666666; +} + +/* THEME SWITCH */ +.theme-toggle { + cursor: pointer; + border: none; + padding: 0; + background: transparent; + vertical-align: middle; + margin-left: 5px; + margin-top: -1px; +} + +.theme-toggle svg { + vertical-align: middle; + height: 1rem; + width: 1rem; + display: none; +} + +/* ICONS */ +.theme-toggle svg.theme-icon-when-auto, +.theme-toggle svg.theme-icon-when-dark, +.theme-toggle svg.theme-icon-when-light { + fill: var(--header-link-color); + color: var(--header-bg); +} + +html[data-theme="auto"] .theme-toggle svg.theme-icon-when-auto { + display: block; +} + +html[data-theme="dark"] .theme-toggle svg.theme-icon-when-dark { + display: block; +} + +html[data-theme="light"] .theme-toggle svg.theme-icon-when-light { + display: block; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + white-space: nowrap; + border: 0; + color: var(--body-fg); + background-color: var(--body-bg); +} diff --git a/django/contrib/admin/static/admin/js/theme.js b/django/contrib/admin/static/admin/js/theme.js new file mode 100644 index 00000000000..794cd15f701 --- /dev/null +++ b/django/contrib/admin/static/admin/js/theme.js @@ -0,0 +1,56 @@ +'use strict'; +{ + window.addEventListener('load', function(e) { + + function setTheme(mode) { + if (mode !== "light" && mode !== "dark" && mode !== "auto") { + console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`); + mode = "auto"; + } + document.documentElement.dataset.theme = mode; + localStorage.setItem("theme", mode); + } + + function cycleTheme() { + const currentTheme = localStorage.getItem("theme") || "auto"; + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + + if (prefersDark) { + // Auto (dark) -> Light -> Dark + if (currentTheme === "auto") { + setTheme("light"); + } else if (currentTheme === "light") { + setTheme("dark"); + } else { + setTheme("auto"); + } + } else { + // Auto (light) -> Dark -> Light + if (currentTheme === "auto") { + setTheme("dark"); + } else if (currentTheme === "dark") { + setTheme("light"); + } else { + setTheme("auto"); + } + } + } + + function initTheme() { + // set theme defined in localStorage if there is one, or fallback to auto mode + const currentTheme = localStorage.getItem("theme"); + currentTheme ? setTheme(currentTheme) : setTheme("auto"); + } + + function setupTheme() { + // Attach event handlers for toggling themes + const buttons = document.getElementsByClassName("theme-toggle"); + Array.from(buttons).forEach((btn) => { + btn.addEventListener("click", cycleTheme); + }); + initTheme(); + } + + setupTheme(); + }); +} diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index cd4b7592569..19d89e8b47c 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -6,6 +6,7 @@ {% block dark-mode-vars %} + {% endblock %} {% if not is_popup and is_nav_sidebar_enabled %} @@ -59,6 +60,7 @@ {% csrf_token %} + {% include "admin/color_theme_toggle.html" %} {% endblock %} {% endif %} @@ -107,5 +109,13 @@ + + + + + + + + diff --git a/django/contrib/admin/templates/admin/base_site.html b/django/contrib/admin/templates/admin/base_site.html index 9e8cc604c59..d83ac0df9b6 100644 --- a/django/contrib/admin/templates/admin/base_site.html +++ b/django/contrib/admin/templates/admin/base_site.html @@ -4,6 +4,9 @@ {% block branding %}

{{ site_header|default:_('Django administration') }}

+{% if user.is_anonymous %} + {% include "admin/color_theme_toggle.html" %} +{% endif %} {% endblock %} {% block nav-global %}{% endblock %} diff --git a/django/contrib/admin/templates/admin/color_theme_toggle.html b/django/contrib/admin/templates/admin/color_theme_toggle.html new file mode 100644 index 00000000000..8df59f58b0f --- /dev/null +++ b/django/contrib/admin/templates/admin/color_theme_toggle.html @@ -0,0 +1,12 @@ + diff --git a/django/contrib/admin/templates/registration/password_change_done.html b/django/contrib/admin/templates/registration/password_change_done.html index cb017825f23..784ab37278c 100644 --- a/django/contrib/admin/templates/registration/password_change_done.html +++ b/django/contrib/admin/templates/registration/password_change_done.html @@ -6,6 +6,7 @@ {% csrf_token %} + {% include "admin/color_theme_toggle.html" %} {% endblock %} {% block breadcrumbs %}