mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-25 07:27:04 +00:00
Add theme attribute for each web component, which can be used for forcing dark or light theme (in case the application doesn't have dark mode support it might be odd if just menubar turns dark)
This commit is contained in:
@@ -12,9 +12,64 @@ class PuterWebComponent extends (globalThis.HTMLElement || Object) {
|
||||
}
|
||||
|
||||
connectedCallback () {
|
||||
this._setupThemeWatchers();
|
||||
this._applyTheme();
|
||||
this._rerender();
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
this._teardownThemeWatchers();
|
||||
}
|
||||
|
||||
_setupThemeWatchers () {
|
||||
if ( typeof globalThis.MutationObserver !== 'undefined' && ! this._themeObserver ) {
|
||||
this._themeObserver = new globalThis.MutationObserver(() => this._applyTheme());
|
||||
this._themeObserver.observe(this, { attributes: true, attributeFilter: ['theme'] });
|
||||
}
|
||||
if ( typeof globalThis.matchMedia === 'function' && ! this._themeMediaQuery ) {
|
||||
this._themeMediaQuery = globalThis.matchMedia('(prefers-color-scheme: dark)');
|
||||
this._themeMediaListener = () => this._applyTheme();
|
||||
if ( this._themeMediaQuery.addEventListener ) {
|
||||
this._themeMediaQuery.addEventListener('change', this._themeMediaListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_teardownThemeWatchers () {
|
||||
if ( this._themeObserver ) {
|
||||
this._themeObserver.disconnect();
|
||||
this._themeObserver = null;
|
||||
}
|
||||
if ( this._themeMediaQuery && this._themeMediaListener && this._themeMediaQuery.removeEventListener ) {
|
||||
this._themeMediaQuery.removeEventListener('change', this._themeMediaListener);
|
||||
}
|
||||
this._themeMediaQuery = null;
|
||||
this._themeMediaListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the effective theme from the `theme` attribute and the system
|
||||
* preference, then toggles a `.puter-theme-dark` class on the host so
|
||||
* components can style with `:host(.puter-theme-dark) ...`.
|
||||
* theme="dark" → always dark
|
||||
* theme="light" → always light
|
||||
* unset / other → follow system (prefers-color-scheme)
|
||||
*/
|
||||
_applyTheme () {
|
||||
if ( typeof this.getAttribute !== 'function' ) return;
|
||||
const themeAttr = this.getAttribute('theme');
|
||||
let isDark;
|
||||
if ( themeAttr === 'dark' ) {
|
||||
isDark = true;
|
||||
} else if ( themeAttr === 'light' ) {
|
||||
isDark = false;
|
||||
} else {
|
||||
isDark = typeof globalThis.matchMedia === 'function'
|
||||
&& globalThis.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
this.classList.toggle('puter-theme-dark', isDark);
|
||||
}
|
||||
|
||||
_rerender () {
|
||||
if ( ! this.shadowRoot ) return;
|
||||
this.shadowRoot.innerHTML = `<style>${this.getStyles()}</style>${this.render()}`;
|
||||
|
||||
@@ -340,7 +340,25 @@ All components render inside Shadow DOM and match puter.com's native GUI appeara
|
||||
|
||||
## Dark mode
|
||||
|
||||
Components with `@media (prefers-color-scheme: dark)` rules auto-adapt when the OS is in dark mode: `<puter-notification>`, `<puter-context-menu>`, `<puter-menubar>`, `<puter-color-picker>`, `<puter-font-picker>`.
|
||||
By default, components that ship dark-mode styles auto-adapt to the OS setting (`prefers-color-scheme: dark`): `<puter-notification>`, `<puter-context-menu>`, `<puter-menubar>`, `<puter-color-picker>`, `<puter-font-picker>`.
|
||||
|
||||
To force a specific theme regardless of the OS preference, set the `theme` attribute on the component:
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| `dark` | Always render in dark theme |
|
||||
| `light` | Always render in light theme |
|
||||
| _unset_ | Follow the system `prefers-color-scheme` (default) |
|
||||
|
||||
```html
|
||||
<!-- Force dark, even in an OS configured for light mode -->
|
||||
<puter-menubar theme="dark"></puter-menubar>
|
||||
|
||||
<!-- Force light, even when the OS is in dark mode -->
|
||||
<puter-notification theme="light" title="Hello"></puter-notification>
|
||||
```
|
||||
|
||||
The attribute is live — changing it at runtime re-paints the component immediately. For `<puter-menubar>` and `<puter-context-menu>`, the `theme` is forwarded to any dropdowns and submenus they spawn, so the whole tree stays in sync.
|
||||
|
||||
## Responsive / mobile
|
||||
|
||||
|
||||
@@ -176,47 +176,45 @@ class PuterColorPicker extends PuterWebComponent {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.picker-body {
|
||||
background-color: rgba(40, 44, 52, .95);
|
||||
color: #e6e6e6;
|
||||
box-shadow: 0px 0px 15px #000000aa;
|
||||
}
|
||||
.preview {
|
||||
border-color: #555;
|
||||
}
|
||||
.header-label {
|
||||
color: #aaa;
|
||||
}
|
||||
.hex-input {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #555;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.native-color-row {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: #555;
|
||||
}
|
||||
.native-color-row label {
|
||||
color: #aaa;
|
||||
}
|
||||
input[type="color"] {
|
||||
border-color: #555;
|
||||
}
|
||||
.swatch {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.btn {
|
||||
color: #e6e6e6;
|
||||
border-color: #555;
|
||||
background: linear-gradient(#4a4a4a, #3a3a3a);
|
||||
box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%);
|
||||
}
|
||||
.btn:active {
|
||||
background-color: #333;
|
||||
border-color: #444;
|
||||
color: #999;
|
||||
}
|
||||
:host(.puter-theme-dark) .picker-body {
|
||||
background-color: rgba(40, 44, 52, .95);
|
||||
color: #e6e6e6;
|
||||
box-shadow: 0px 0px 15px #000000aa;
|
||||
}
|
||||
:host(.puter-theme-dark) .preview {
|
||||
border-color: #555;
|
||||
}
|
||||
:host(.puter-theme-dark) .header-label {
|
||||
color: #aaa;
|
||||
}
|
||||
:host(.puter-theme-dark) .hex-input {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #555;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .native-color-row {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: #555;
|
||||
}
|
||||
:host(.puter-theme-dark) .native-color-row label {
|
||||
color: #aaa;
|
||||
}
|
||||
:host(.puter-theme-dark) input[type="color"] {
|
||||
border-color: #555;
|
||||
}
|
||||
:host(.puter-theme-dark) .swatch {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
:host(.puter-theme-dark) .btn {
|
||||
color: #e6e6e6;
|
||||
border-color: #555;
|
||||
background: linear-gradient(#4a4a4a, #3a3a3a);
|
||||
box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%);
|
||||
}
|
||||
:host(.puter-theme-dark) .btn:active {
|
||||
background-color: #333;
|
||||
border-color: #444;
|
||||
color: #999;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -345,99 +345,100 @@ class PuterContextMenu extends PuterWebComponent {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.context-menu {
|
||||
background: #2d2d2d;
|
||||
background-color: rgb(45 45 45 / 94%);
|
||||
color: #e6e6e6;
|
||||
border-color: #00000080;
|
||||
box-shadow: 0px 0px 15px #000000aa;
|
||||
}
|
||||
.menu-item {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
/* Inactive items: icon/check/shortcut/arrow tones */
|
||||
.icon,
|
||||
.check {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.submenu-arrow {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
.shortcut {
|
||||
color: #888;
|
||||
}
|
||||
.icon img {
|
||||
filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230));
|
||||
}
|
||||
/* Inactive icon SVGs use currentColor already; nothing to invert */
|
||||
/* Dark theme — applied when system prefers dark and no light
|
||||
override is set, or when theme="dark" is forced. The base
|
||||
class toggles .puter-theme-dark on the host accordingly. */
|
||||
:host(.puter-theme-dark) .context-menu {
|
||||
background: #2d2d2d;
|
||||
background-color: rgb(45 45 45 / 94%);
|
||||
color: #e6e6e6;
|
||||
border-color: #00000080;
|
||||
box-shadow: 0px 0px 15px #000000aa;
|
||||
}
|
||||
:host(.puter-theme-dark) .menu-item {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
/* Inactive items: icon/check/shortcut/arrow tones */
|
||||
:host(.puter-theme-dark) .icon,
|
||||
:host(.puter-theme-dark) .check {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .submenu-arrow {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
:host(.puter-theme-dark) .shortcut {
|
||||
color: #888;
|
||||
}
|
||||
:host(.puter-theme-dark) .icon img {
|
||||
filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230));
|
||||
}
|
||||
/* Inactive icon SVGs use currentColor already; nothing to invert */
|
||||
|
||||
/* Safe-triangle / keyboard-nav: non-active hover restored colors should match dark */
|
||||
.context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider),
|
||||
.context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon,
|
||||
.context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check,
|
||||
.context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow,
|
||||
.context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label,
|
||||
.context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon,
|
||||
.context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check,
|
||||
.context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow,
|
||||
.context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut,
|
||||
.context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut {
|
||||
color: #888;
|
||||
}
|
||||
.context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img,
|
||||
.context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img {
|
||||
filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230));
|
||||
}
|
||||
/* Safe-triangle / keyboard-nav: non-active hover restored colors should match dark */
|
||||
:host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider),
|
||||
:host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon,
|
||||
:host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check,
|
||||
:host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow,
|
||||
:host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label,
|
||||
:host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon,
|
||||
:host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check,
|
||||
:host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow,
|
||||
:host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut,
|
||||
:host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut {
|
||||
color: #888;
|
||||
}
|
||||
:host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img,
|
||||
:host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img {
|
||||
filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230));
|
||||
}
|
||||
|
||||
/* Submenu-open parent (no hover): subtle dark highlight */
|
||||
.menu-item.has-open-submenu:not(:hover) {
|
||||
background-color: #3f3f3f;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.menu-item.has-open-submenu:not(:hover) .icon,
|
||||
.menu-item.has-open-submenu:not(:hover) .icon svg,
|
||||
.menu-item.has-open-submenu:not(:hover) .icon img {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
/* Submenu-open parent (no hover): subtle dark highlight */
|
||||
:host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) {
|
||||
background-color: #3f3f3f;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) .icon,
|
||||
:host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) .icon svg,
|
||||
:host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) .icon img {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
/* Danger items */
|
||||
.menu-item.danger,
|
||||
.menu-item.danger .icon {
|
||||
color: #ff7b72;
|
||||
}
|
||||
/* Danger items */
|
||||
:host(.puter-theme-dark) .menu-item.danger,
|
||||
:host(.puter-theme-dark) .menu-item.danger .icon {
|
||||
color: #ff7b72;
|
||||
}
|
||||
|
||||
/* Divider */
|
||||
.divider hr {
|
||||
background: #444;
|
||||
}
|
||||
/* Divider */
|
||||
:host(.puter-theme-dark) .divider hr {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
/* Sheet mode (mobile) */
|
||||
:host(.sheet-mode) .context-menu {
|
||||
background-color: rgb(40 40 40 / 96%);
|
||||
box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
:host(.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) {
|
||||
background-color: rgba(0, 122, 255, 0.22);
|
||||
}
|
||||
:host(.sheet-mode) .menu-item:active:not(.disabled):not(.divider) {
|
||||
background-color: rgba(0, 122, 255, 0.35);
|
||||
}
|
||||
:host(.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .label,
|
||||
:host(.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .icon,
|
||||
:host(.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .label,
|
||||
:host(.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .icon {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.sheet-mode) .divider hr {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
/* Sheet mode (mobile) */
|
||||
:host(.puter-theme-dark.sheet-mode) .context-menu {
|
||||
background-color: rgb(40 40 40 / 96%);
|
||||
box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
:host(.puter-theme-dark.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) {
|
||||
background-color: rgba(0, 122, 255, 0.22);
|
||||
}
|
||||
:host(.puter-theme-dark.sheet-mode) .menu-item:active:not(.disabled):not(.divider) {
|
||||
background-color: rgba(0, 122, 255, 0.35);
|
||||
}
|
||||
:host(.puter-theme-dark.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .label,
|
||||
:host(.puter-theme-dark.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .icon,
|
||||
:host(.puter-theme-dark.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .label,
|
||||
:host(.puter-theme-dark.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .icon {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark.sheet-mode) .divider hr {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -930,6 +931,9 @@ class PuterContextMenu extends PuterWebComponent {
|
||||
const submenu = document.createElement('puter-context-menu');
|
||||
submenu.setAttribute('data-submenu', '');
|
||||
submenu.setAttribute('data-parent-managed', '');
|
||||
// Forward any forced theme so the submenu paints in the same theme.
|
||||
const themeAttr = this.getAttribute('theme');
|
||||
if ( themeAttr ) submenu.setAttribute('theme', themeAttr);
|
||||
submenu.items = items;
|
||||
submenu._parentMenu = this;
|
||||
submenu._parentItemEl = parentEl;
|
||||
@@ -1184,6 +1188,7 @@ class PuterContextMenu extends PuterWebComponent {
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
super.disconnectedCallback();
|
||||
if ( this._outsideClickHandler ) {
|
||||
document.removeEventListener('pointerdown', this._outsideClickHandler, true);
|
||||
}
|
||||
|
||||
@@ -185,50 +185,48 @@ class PuterFontPicker extends PuterWebComponent {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.picker-body {
|
||||
background-color: rgba(40, 44, 52, .95);
|
||||
color: #e6e6e6;
|
||||
box-shadow: 0px 0px 15px #000000aa;
|
||||
}
|
||||
.title {
|
||||
color: #e6e6e6;
|
||||
text-shadow: none;
|
||||
}
|
||||
.search {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #555;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.font-list {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #555;
|
||||
}
|
||||
.font-item {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.font-item:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.font-name-label {
|
||||
color: #888;
|
||||
}
|
||||
.preview {
|
||||
background: #1f1f1f;
|
||||
border-color: #555;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.btn {
|
||||
color: #e6e6e6;
|
||||
border-color: #555;
|
||||
background: linear-gradient(#4a4a4a, #3a3a3a);
|
||||
box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%);
|
||||
}
|
||||
.btn:active {
|
||||
background-color: #333;
|
||||
border-color: #444;
|
||||
color: #999;
|
||||
}
|
||||
:host(.puter-theme-dark) .picker-body {
|
||||
background-color: rgba(40, 44, 52, .95);
|
||||
color: #e6e6e6;
|
||||
box-shadow: 0px 0px 15px #000000aa;
|
||||
}
|
||||
:host(.puter-theme-dark) .title {
|
||||
color: #e6e6e6;
|
||||
text-shadow: none;
|
||||
}
|
||||
:host(.puter-theme-dark) .search {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #555;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .font-list {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #555;
|
||||
}
|
||||
:host(.puter-theme-dark) .font-item {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .font-item:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
:host(.puter-theme-dark) .font-name-label {
|
||||
color: #888;
|
||||
}
|
||||
:host(.puter-theme-dark) .preview {
|
||||
background: #1f1f1f;
|
||||
border-color: #555;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .btn {
|
||||
color: #e6e6e6;
|
||||
border-color: #555;
|
||||
background: linear-gradient(#4a4a4a, #3a3a3a);
|
||||
box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%);
|
||||
}
|
||||
:host(.puter-theme-dark) .btn:active {
|
||||
background-color: #333;
|
||||
border-color: #444;
|
||||
color: #999;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -91,22 +91,23 @@ class PuterMenubar extends PuterWebComponent {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.menubar {
|
||||
background-color: #2a2a2a;
|
||||
border-bottom-color: #3a3a3a;
|
||||
}
|
||||
.menu-button {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.menu-button:hover,
|
||||
.menu-button.active,
|
||||
.menu-button.focused {
|
||||
background-color: #3a3a3a;
|
||||
}
|
||||
.menubar.keyboard-nav .menu-button:hover:not(.focused):not(.active) {
|
||||
background-color: transparent;
|
||||
}
|
||||
/* Dark theme — applied when system prefers dark and no light
|
||||
override is set, or when theme="dark" is forced. The base
|
||||
class toggles .puter-theme-dark on the host accordingly. */
|
||||
:host(.puter-theme-dark) .menubar {
|
||||
background-color: #2a2a2a;
|
||||
border-bottom-color: #3a3a3a;
|
||||
}
|
||||
:host(.puter-theme-dark) .menu-button {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .menu-button:hover,
|
||||
:host(.puter-theme-dark) .menu-button.active,
|
||||
:host(.puter-theme-dark) .menu-button.focused {
|
||||
background-color: #3a3a3a;
|
||||
}
|
||||
:host(.puter-theme-dark) .menubar.keyboard-nav .menu-button:hover:not(.focused):not(.active) {
|
||||
background-color: transparent;
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -352,6 +353,9 @@ class PuterMenubar extends PuterWebComponent {
|
||||
const rect = buttonEl.getBoundingClientRect();
|
||||
const dropdown = document.createElement('puter-context-menu');
|
||||
dropdown.setAttribute('data-submenu', ''); // skip mobile sheet behavior
|
||||
// Forward any forced theme so the dropdown paints in the same theme.
|
||||
const themeAttr = this.getAttribute('theme');
|
||||
if ( themeAttr ) dropdown.setAttribute('theme', themeAttr);
|
||||
dropdown.items = item.items;
|
||||
dropdown.setAttribute('x', String(rect.left));
|
||||
dropdown.setAttribute('y', String(rect.bottom));
|
||||
@@ -410,6 +414,7 @@ class PuterMenubar extends PuterWebComponent {
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
super.disconnectedCallback();
|
||||
this._closeDropdown();
|
||||
clearTimeout(this._suppressClickTimer);
|
||||
this._suppressClickFor = null;
|
||||
|
||||
@@ -175,30 +175,28 @@ class PuterNotification extends PuterWebComponent {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.notification {
|
||||
background: #2d2d2dcd;
|
||||
border-color: #3a3a3a;
|
||||
box-shadow: 0px 0px 17px -6px #000;
|
||||
}
|
||||
.close-btn {
|
||||
background: #3a3a3a;
|
||||
color: #ccc;
|
||||
filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230));
|
||||
}
|
||||
.close-btn:hover {
|
||||
background: #4a4a4a;
|
||||
color: #fff;
|
||||
}
|
||||
.icon-area {
|
||||
filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230));
|
||||
}
|
||||
.title {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
.text {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
:host(.puter-theme-dark) .notification {
|
||||
background: #2d2d2dcd;
|
||||
border-color: #3a3a3a;
|
||||
box-shadow: 0px 0px 17px -6px #000;
|
||||
}
|
||||
:host(.puter-theme-dark) .close-btn {
|
||||
background: #3a3a3a;
|
||||
color: #ccc;
|
||||
filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230));
|
||||
}
|
||||
:host(.puter-theme-dark) .close-btn:hover {
|
||||
background: #4a4a4a;
|
||||
color: #fff;
|
||||
}
|
||||
:host(.puter-theme-dark) .icon-area {
|
||||
filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230));
|
||||
}
|
||||
:host(.puter-theme-dark) .title {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
:host(.puter-theme-dark) .text {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -280,6 +278,7 @@ class PuterNotification extends PuterWebComponent {
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
super.disconnectedCallback();
|
||||
if ( this._dismissTimer ) clearTimeout(this._dismissTimer);
|
||||
const idx = activeNotifications.indexOf(this);
|
||||
if ( idx !== -1 ) activeNotifications.splice(idx, 1);
|
||||
|
||||
Reference in New Issue
Block a user