Kdots dark mode

This commit is contained in:
nathan 2024-07-10 15:48:50 -06:00
parent 844eed2eee
commit c06b1aafda
10 changed files with 213 additions and 10 deletions

View File

@ -1229,11 +1229,11 @@ abstract class Framework extends Framework\Extra
$topmenu_info_items = [ $topmenu_info_items = [
'user_avatar' => $this->_user_avatar_menu(), 'user_avatar' => $this->_user_avatar_menu(),
'update' => ($update = Framework\Updates::notification()) ? $update : null, 'update' => ($update = Framework\Updates::notification()) ? $update : null,
'logout' => (Header\UserAgent::mobile()) ? self::_logout_menu() : null, 'logout' => (Header\UserAgent::mobile()) ? static::_logout_menu() : null,
'notifications' => ($GLOBALS['egw_info']['user']['apps']['notifications']) ? self::_get_notification_bell() : null, 'notifications' => ($GLOBALS['egw_info']['user']['apps']['notifications']) ? static::_get_notification_bell() : null,
'quick_add' => $vars['quick_add'], 'quick_add' => $vars['quick_add'],
'print_title' => $this->_print_menu(), 'print_title' => $this->_print_menu(),
'darkmode' => self::_darkmode_menu() 'darkmode' => static::_darkmode_menu()
]; ];
// array of topmenu items (orders of the items matter) // array of topmenu items (orders of the items matter)

View File

@ -1,3 +1,29 @@
/**
* kDots main styles
*
* Note that light / dark colors should go in framework_light.less & framework_dark.less
*/
/** Theme customisations **/
html[data-darkmode="true"] body {
background-color: black;
color: var(--sl-color-neutral-700);
/*** HEADER ***/
/*** APPLICATION ***/
/*** End APPLICATION ***/
/*** WIDGETS ***/
/* This should mostly go away with webcomponents */
/** End WIDGETS **/
}
html[data-darkmode="true"] body #egw_fw_topmenu_info_items #topmenu_info_timer:before {
filter: initial;
}
html[data-darkmode="true"] body egw-app {
--application-header-text-color: var(--sl-color-neutral-700);
}
html[data-darkmode="true"] body .nextmatch_sortheader {
color: #96bcd9;
}
/** End theme customisations **/
html, html,
body { body {
width: 100vw; width: 100vw;
@ -118,6 +144,7 @@ egw-framework#egw_fw_basecontainer .egw_fw_ui_sidemenu_entry_header {
#egw_fw_topmenu_info_items #topmenu_info_timer #topmenu_timer { #egw_fw_topmenu_info_items #topmenu_info_timer #topmenu_timer {
display: block; display: block;
width: 100%; width: 100%;
z-index: 2;
} }
#egw_fw_topmenu_info_items #topmenu_info_timer:hover { #egw_fw_topmenu_info_items #topmenu_info_timer:hover {
cursor: pointer; cursor: pointer;

View File

@ -1,3 +1,14 @@
/**
* kDots main styles
*
* Note that light / dark colors should go in framework_light.less & framework_dark.less
*/
/** Theme customisations **/
@import "./framework_dark.less";
/** End theme customisations **/
html, body { html, body {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
@ -6,7 +17,6 @@ html, body {
margin: 0px; margin: 0px;
/** Messages **/ /** Messages **/
.sl-toast-stack { .sl-toast-stack {
top: auto; top: auto;
bottom: 0px; bottom: 0px;
@ -140,6 +150,7 @@ egw-framework#egw_fw_basecontainer {
#topmenu_timer { #topmenu_timer {
display: block; display: block;
width: 100%; width: 100%;
z-index: 2;
} }
&:hover { &:hover {
@ -397,5 +408,4 @@ div.et2_nextmatch {
} }
/*** END WIDGETS ***/ /*** END WIDGETS ***/

View File

@ -0,0 +1,28 @@
html[data-darkmode="true"] body {
background-color: black;
color: var(--sl-color-neutral-700);
/*** HEADER ***/
#egw_fw_topmenu_info_items {
#topmenu_info_timer:before {
filter:initial;
}
}
/*** APPLICATION ***/
egw-app {
--application-header-text-color: var(--sl-color-neutral-700);
}
/*** End APPLICATION ***/
/*** WIDGETS ***/
/* This should mostly go away with webcomponents */
.nextmatch_sortheader {
color: #96bcd9;
}
/** End WIDGETS **/
}

View File

@ -34,7 +34,7 @@
{include_wz_tooltip} {include_wz_tooltip}
<!-- END head --> <!-- END head -->
<!-- BEGIN framework --> <!-- BEGIN framework -->
<egw-framework id="egw_fw_basecontainer" class="sl-theme-light " <egw-framework id="egw_fw_basecontainer" class=" "
application-list="{application-list}" application-list="{application-list}"
> >
<a slot="logo" href="{logo_url}" target="_blank"><img src="{logo_header}" title="{logo_title}" alt="Site logo"/></a> <a slot="logo" href="{logo_url}" target="_blank"><img src="{logo_header}" title="{logo_title}" alt="Site logo"/></a>

View File

@ -76,6 +76,9 @@ class kdots_framework extends Api\Framework\Ajax
case 'user_avatar': case 'user_avatar':
$vars['topmenu_info_items'] .= "<sl-dropdown class=\"topmenu_info_item\" id=\"topmenu_info_{$id}\" aria-label='" . lang("User menu") . "' tabindex='0'><div slot='trigger'>$item</div> {$vars['topmenu_items']}</sl-dropdown>"; $vars['topmenu_info_items'] .= "<sl-dropdown class=\"topmenu_info_item\" id=\"topmenu_info_{$id}\" aria-label='" . lang("User menu") . "' tabindex='0'><div slot='trigger'>$item</div> {$vars['topmenu_items']}</sl-dropdown>";
break; break;
case 'darkmode':
$vars['topmenu_info_items'] .= $item;
break;
default: default:
$vars['topmenu_info_items'] .= '<button class="topmenu_info_item"' . $vars['topmenu_info_items'] .= '<button class="topmenu_info_item"' .
(is_numeric($id) ? '' : ' id="topmenu_info_' . $id . '"') . '>' . $item . "</button>\n"; (is_numeric($id) ? '' : ' id="topmenu_info_' . $id . '"') . '>' . $item . "</button>\n";
@ -174,4 +177,17 @@ class kdots_framework extends Api\Framework\Ajax
return $ret; return $ret;
} }
/**
* Returns darkmode menu
*
* @return string
*/
protected static function _darkmode_menu()
{
$mode = $GLOBALS['egw_info']['user']['preferences']['common']['darkmode'] == 1 ? 'dark' : 'light';
return '<egw-darkmode-toggle title="' . lang("%1 mode", $mode) . '" class="' .
($mode == 'dark' ? 'darkmode_on' : '') . '"' . ($mode == 'dark' ? 'darkmode' : '') . '> </egw-darkmode-toggle>';
}
} }

View File

@ -0,0 +1,94 @@
import {css, html, LitElement} from "lit";
import {customElement} from "lit/decorators/custom-element.js";
import {property} from "lit/decorators/property.js";
/**
* @summary System message
*
* @dependency sl-alert
* @dependency sl-icon
*
* @slot - Content
* @slot icon - An icon to show in the message
*
* @csspart base - Wraps it all.
* @csspart icon -
*/
@customElement('egw-darkmode-toggle')
export class EgwDarkmodeToggle extends LitElement
{
static get styles()
{
return [
css`
sl-icon-button::part(base) {
padding: 0;
}
`
];
}
@property({type: Boolean})
darkmode = false;
private _initialValue = false;
constructor()
{
super();
this._initialValue = window.matchMedia("(prefers-color-scheme: dark)").matches;
this.handleDarkmodeChange = this.handleDarkmodeChange.bind(this);
}
connectedCallback()
{
super.connectedCallback();
this.toggleDarkmode(this.hasAttribute("darkmode") ? this.darkmode : this._initialValue);
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", this.handleDarkmodeChange);
}
disconnectedCallback()
{
super.disconnectedCallback();
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", this.handleDarkmodeChange);
}
public toggleDarkmode(force = null)
{
if(force == null)
{
force = !(document.documentElement.getAttribute("data-darkmode") == "true");
}
this.darkmode = force;
if(force)
{
document.documentElement.setAttribute("data-darkmode", "true");
}
else
{
document.documentElement.setAttribute("data-darkmode", "0");
}
// Set class for Shoelace
document.documentElement.classList.toggle("sl-theme-dark", this.darkmode);
this.requestUpdate("darkmode")
this.updateComplete.then(() =>
{
this.dispatchEvent(new CustomEvent("egw-darkmode-change", {bubbles: true}));
});
}
handleDarkmodeChange(e)
{
this.toggleDarkmode(e.matches ? "dark" : "light");
}
render() : unknown
{
return html`
<sl-icon-button name="${this.darkmode ? "sun" : "moon"}"
@click=${(e) => {this.toggleDarkmode()}}
></sl-icon-button>
`;
}
}

View File

@ -111,6 +111,11 @@ export class EgwFramework extends LitElement
private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");} private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");}
constructor()
{
super();
this.handleDarkmodeChange = this.handleDarkmodeChange.bind(this);
}
connectedCallback() connectedCallback()
{ {
super.connectedCallback(); super.connectedCallback();
@ -124,6 +129,15 @@ export class EgwFramework extends LitElement
// Override framework setSidebox, use arrow function to force context // Override framework setSidebox, use arrow function to force context
this.egw.framework.setSidebox = (applicationName, sideboxData, hash?) => this.setSidebox(applicationName, sideboxData, hash); this.egw.framework.setSidebox = (applicationName, sideboxData, hash?) => this.setSidebox(applicationName, sideboxData, hash);
} }
document.body.addEventListener("egw-darkmode-change", this.handleDarkmodeChange);
}
disconnectedCallback()
{
super.disconnectedCallback();
document.body.removeEventListener("egw-darkmode-change", this.handleDarkmodeChange);
} }
protected firstUpdated(_changedProperties : PropertyValues) protected firstUpdated(_changedProperties : PropertyValues)
@ -572,6 +586,15 @@ export class EgwFramework extends LitElement
protected getBaseUrl() {return "";} protected getBaseUrl() {return "";}
protected handleDarkmodeChange(event)
{
// Update CSS classes
this.classList.toggle("sl-theme-light", !event.target.darkmode);
this.classList.toggle("sl-theme-dark", event.target.darkmode);
// Update preference
this.egw.set_preference("common", "darkmode", (event.target.darkmode ? "1" : "0"));
}
/** /**
* An application tab is chosen, show the app * An application tab is chosen, show the app
* *

View File

@ -39,13 +39,13 @@ export default css`
max-height: 3em; max-height: 3em;
background-color: var(--application-color, --primary-background-color); background-color: var(--application-color, --primary-background-color);
color: var(--application-header-text-color, white); color: var(--application-header-text-color, var(--sl-color-neutral-0));
font-size: 1.8em; font-size: 1.8em;
} }
.egw_fw_app__header sl-icon-button::part(base), .egw_fw_app__header et2-button-icon { .egw_fw_app__header sl-icon-button::part(base), .egw_fw_app__header et2-button-icon {
font-size: inherit; font-size: inherit;
color: var(--application-header-text-color, white); color: var(--application-header-text-color, var(--sl-color-neutral-0));
} }
.egw_fw_app__header et2-button-icon { .egw_fw_app__header et2-button-icon {
@ -53,7 +53,7 @@ export default css`
} }
.egw_fw_app__header sl-icon-button::part(base):hover, .egw_fw_app__header et2-button-icon::part(base):hover { .egw_fw_app__header sl-icon-button::part(base):hover, .egw_fw_app__header et2-button-icon::part(base):hover {
color: var(--application-header-text-color, white); color: var(--application-header-text-color, var(--sl-color-neutral-0));
filter: brightness(70%); filter: brightness(70%);
} }

View File

@ -4,11 +4,12 @@
import {EgwFramework} from "./EgwFramework"; import {EgwFramework} from "./EgwFramework";
import {EgwFrameworkApp} from "./EgwFrameworkApp"; import {EgwFrameworkApp} from "./EgwFrameworkApp";
import {EgwDarkmodeToggle} from "./EgwDarkmodeToggle";
document.addEventListener('DOMContentLoaded', () => document.addEventListener('DOMContentLoaded', () =>
{ {
// Not sure what's up here // Not sure what's up here, but it makes sure everything is loaded
if(!window.customElements.get("egw-framework")) if(!window.customElements.get("egw-framework"))
{ {
window.customElements.define("egw-framework", EgwFramework); window.customElements.define("egw-framework", EgwFramework);
@ -17,6 +18,10 @@ document.addEventListener('DOMContentLoaded', () =>
{ {
window.customElements.define("egw-app", EgwFrameworkApp); window.customElements.define("egw-app", EgwFrameworkApp);
} }
if(!window.customElements.get("egw-darkmode-toggle"))
{
window.customElements.define("egw-darkmode-toggle", EgwDarkmodeToggle);
}
/* Set up listener on avatar menu */ /* Set up listener on avatar menu */
const avatarMenu = document.querySelector("#topmenu_info_user_avatar"); const avatarMenu = document.querySelector("#topmenu_info_user_avatar");
if(avatarMenu) if(avatarMenu)