Always show compact header for dashboards (#26706)

This commit is contained in:
Paul Bottein
2025-08-26 20:09:57 +02:00
committed by GitHub
parent 51840b88b3
commit 317149e51e
4 changed files with 217 additions and 55 deletions

View File

@@ -148,6 +148,10 @@ export class HaDialog extends DialogBase {
white-space: nowrap;
display: block;
padding-left: 4px;
padding-right: 4px;
margin-right: 12px;
margin-inline-end: 12px;
margin-inline-start: initial;
}
.header_button {
text-decoration: none;

View File

@@ -0,0 +1,106 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-icon";
import "../../components/ha-md-list";
import "../../components/ha-md-list-item";
import "../../components/ha-svg-icon";
import type { HomeAssistant } from "../../types";
import type { HassDialog } from "../make-dialog-manager";
import type { ListItemsDialogParams } from "./show-list-items-dialog";
@customElement("dialog-list-items")
export class ListItemsDialog
extends LitElement
implements HassDialog<ListItemsDialogParams>
{
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _params?: ListItemsDialogParams;
public async showDialog(params: ListItemsDialogParams): Promise<void> {
this._params = params;
}
private _dialogClosed(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
private _itemClicked(ev: CustomEvent): void {
const item = (ev.currentTarget as any).item;
if (!item) return;
item.action();
this._dialogClosed();
}
protected render() {
if (!this._params || !this.hass) {
return nothing;
}
return html`
<ha-dialog
open
.heading=${createCloseHeading(this.hass, this._params.title ?? " ")}
@closed=${this._dialogClosed}
hideActions
>
<div class="container">
<ha-md-list>
${this._params.items.map(
(item) => html`
<ha-md-list-item
type="button"
@click=${this._itemClicked}
.item=${item}
>
${item.iconPath
? html`
<ha-svg-icon
.path=${item.iconPath}
slot="start"
class="item-icon"
></ha-svg-icon>
`
: item.icon
? html`
<ha-icon
icon=${item.icon}
slot="start"
class="item-icon"
></ha-icon>
`
: nothing}
<span class="headline">${item.label}</span>
${item.description
? html`
<span class="supporting-text">${item.description}</span>
`
: nothing}
</ha-md-list-item>
`
)}
</ha-md-list>
</div>
</ha-dialog>
`;
}
static styles = css`
ha-dialog {
/* Place above other dialogs */
--dialog-z-index: 104;
--dialog-content-padding: 0;
--md-list-item-leading-space: 24px;
--md-list-item-trailing-space: 24px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"dialog-list-items": ListItemsDialog;
}
}

View File

@@ -0,0 +1,24 @@
import { fireEvent } from "../../common/dom/fire_event";
interface ListItem {
icon?: string;
iconPath?: string;
label: string;
description?: string;
action: () => any;
}
export interface ListItemsDialogParams {
title?: string;
items: ListItem[];
}
export const showListItemsDialog = (
element: HTMLElement,
params: ListItemsDialogParams
) =>
fireEvent(element, "show-dialog", {
dialogTag: "dialog-list-items",
dialogImport: () => import("./dialog-list-items"),
dialogParams: params,
});

View File

@@ -46,6 +46,7 @@ import "../../components/ha-list-item";
import "../../components/ha-menu-button";
import "../../components/ha-svg-icon";
import "../../components/sl-tab-group";
import { createAreaRegistryEntry } from "../../data/area_registry";
import type { LovelacePanelConfig } from "../../data/lovelace";
import type { LovelaceConfig } from "../../data/lovelace/config/types";
import { isStrategyDashboard } from "../../data/lovelace/config/types";
@@ -57,6 +58,7 @@ import {
} from "../../data/lovelace/dashboard";
import { getPanelTitle } from "../../data/panel";
import { createPerson } from "../../data/person";
import { showListItemsDialog } from "../../dialogs/dialog-list-items/show-list-items-dialog";
import {
showAlertDialog,
showConfirmationDialog,
@@ -71,6 +73,8 @@ import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-
import { haStyle } from "../../resources/styles";
import type { HomeAssistant, PanelInfo } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { showToast } from "../../util/toast";
import { showAreaRegistryDetailDialog } from "../config/areas/show-dialog-area-registry-detail";
import { showNewAutomationDialog } from "../config/automation/show-dialog-new-automation";
import { showAddIntegrationDialog } from "../config/integrations/show-add-integration-dialog";
import { showDashboardDetailDialog } from "../config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail";
@@ -86,9 +90,6 @@ import "./views/hui-view";
import type { HUIView } from "./views/hui-view";
import "./views/hui-view-background";
import "./views/hui-view-container";
import { showAreaRegistryDetailDialog } from "../config/areas/show-dialog-area-registry-detail";
import { createAreaRegistryEntry } from "../../data/area_registry";
import { showToast } from "../../util/toast";
interface ActionItem {
icon: string;
@@ -105,6 +106,7 @@ interface ActionItem {
interface SubActionItem {
icon: string;
key: LocalizeKeys;
overflowAction?: any;
action?: any;
visible: boolean | undefined;
}
@@ -208,31 +210,35 @@ class HUIRoot extends LitElement {
icon: mdiPlus,
key: "ui.panel.lovelace.menu.add",
visible: !this._editMode && this.hass.user?.is_admin,
overflow: false,
overflow: this.narrow,
subItems: [
{
icon: mdiDevices,
key: "ui.panel.lovelace.menu.add_device",
visible: true,
action: this._handleAddDevice,
action: this._addDevice,
overflowAction: this._handleAddDevice,
},
{
icon: mdiRobot,
key: "ui.panel.lovelace.menu.create_automation",
visible: true,
action: this._handleACreateAutomation,
action: this._createAutomation,
overflowAction: this._handleCreateAutomation,
},
{
icon: mdiSofa,
key: "ui.panel.lovelace.menu.add_area",
visible: true,
action: this._handleAddArea,
action: this._addArea,
overflowAction: this._handleAddArea,
},
{
icon: mdiAccount,
key: "ui.panel.lovelace.menu.add_person",
visible: true,
action: this._handleInvitePerson,
action: this._addPerson,
overflowAction: this._handleAddPerson,
},
],
},
@@ -242,7 +248,7 @@ class HUIRoot extends LitElement {
buttonAction: this._showQuickBar,
overflowAction: this._handleShowQuickBar,
visible: !this._editMode,
overflow: false,
overflow: this.narrow,
suffix: this.hass.enableShortcuts ? "(E)" : undefined,
},
{
@@ -252,7 +258,7 @@ class HUIRoot extends LitElement {
overflowAction: this._handleShowVoiceCommandDialog,
visible:
!this._editMode && this._conversation(this.hass.config.components),
overflow: false,
overflow: this.narrow,
suffix: this.hass.enableShortcuts ? "(A)" : undefined,
},
{
@@ -321,7 +327,7 @@ class HUIRoot extends LitElement {
<ha-list-item
graphic="icon"
.key=${subItem.key}
@request-selected=${subItem.action}
@request-selected=${subItem.overflowAction}
>
${this.hass!.localize(subItem.key)}
<ha-svg-icon
@@ -347,26 +353,37 @@ class HUIRoot extends LitElement {
if (overflowItems.length && !overflowCanPromote) {
const listItems: TemplateResult[] = [];
overflowItems.forEach((i) => {
const title = [this.hass!.localize(i.key), i.suffix].join(" ");
const action = i.subItems
? () => {
showListItemsDialog(this, {
title: title,
items: i.subItems!.map((si) => ({
iconPath: si.icon,
label: this.hass!.localize(si.key),
action: si.action,
})),
});
}
: i.overflowAction;
listItems.push(
html`<ha-list-item
graphic="icon"
@request-selected=${i.overflowAction}
>
${[this.hass!.localize(i.key), i.suffix].join(" ")}
html`<ha-list-item graphic="icon" @request-selected=${action}>
${title}
<ha-svg-icon slot="graphic" .path=${i.icon}></ha-svg-icon>
</ha-list-item>`
);
});
result.push(
html`<ha-button-menu slot="actionItems">
result.push(html`
<ha-button-menu slot="actionItems">
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize("ui.panel.lovelace.editor.menu.open")}
.path=${mdiDotsVertical}
></ha-icon-button>
${listItems}
</ha-button-menu>`
);
</ha-button-menu>
`);
}
return html`${result}`;
}
@@ -458,8 +475,6 @@ class HUIRoot extends LitElement {
const isSubview = curViewConfig?.subview;
const hasTabViews = views.filter((view) => !view.subview).length > 1;
const showTabBar =
this._editMode || (!isSubview && hasTabViews && this.narrow);
return html`
<div
@@ -504,35 +519,35 @@ class HUIRoot extends LitElement {
`}
${isSubview
? html`<div class="main-title">${curViewConfig.title}</div>`
: hasTabViews && !showTabBar
: hasTabViews
? tabs
: html`
<div class="main-title">
${curViewConfig?.title ?? dashboardTitle}
${views[0]?.title ?? dashboardTitle}
</div>
`}
<div class="action-items">${this._renderActionItems()}</div>
`}
</div>
${showTabBar
? html`<div class="tab-bar">
${tabs}
${this._editMode
? html`<ha-icon-button
slot="nav"
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>`
: nothing}
</div>`
${this._editMode
? html`
<div class="tab-bar">
${tabs}
<ha-icon-button
slot="nav"
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>
</div>
`
: nothing}
</div>
<hui-view-container
class=${showTabBar ? "has-tab-bar" : ""}
class=${this._editMode ? "has-tab-bar" : ""}
.hass=${this.hass}
.theme=${curViewConfig?.theme}
id="view"
@@ -788,36 +803,47 @@ class HUIRoot extends LitElement {
}
}
private async _handleAddDevice(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
private _handleAddDevice(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._addDevice();
}
private _addDevice = async () => {
await this.hass.loadFragmentTranslation("config");
showAddIntegrationDialog(this);
}
};
private async _handleACreateAutomation(
private _handleCreateAutomation(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._createAutomation();
}
private _createAutomation = async () => {
await this.hass.loadFragmentTranslation("config");
showNewAutomationDialog(this, { mode: "automation" });
}
};
private async _handleAddArea(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
private _handleAddArea(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._addArea();
}
private _addArea = async () => {
await this.hass.loadFragmentTranslation("config");
showAreaRegistryDetailDialog(this, {
createEntry: async (values) => {
const area = await createAreaRegistryEntry(this.hass, values);
if (isStrategyDashboard(this.lovelace!.rawConfig)) {
fireEvent(this, "config-refresh");
}
showToast(this, {
message: this.hass.localize(
"ui.panel.lovelace.menu.add_area_success"
@@ -831,14 +857,16 @@ class HUIRoot extends LitElement {
});
},
});
}
};
private async _handleInvitePerson(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
private _handleAddPerson(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this._addPerson();
}
private _addPerson = async () => {
await this.hass.loadFragmentTranslation("config");
showPersonDetailDialog(this, {
users: [],
@@ -859,7 +887,7 @@ class HUIRoot extends LitElement {
});
},
});
}
};
private _handleRawEditor(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {