diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 6e17d5e885..0fb94447dd 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -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; diff --git a/src/dialogs/dialog-list-items/dialog-list-items.ts b/src/dialogs/dialog-list-items/dialog-list-items.ts new file mode 100644 index 0000000000..9d09bb0785 --- /dev/null +++ b/src/dialogs/dialog-list-items/dialog-list-items.ts @@ -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 +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _params?: ListItemsDialogParams; + + public async showDialog(params: ListItemsDialogParams): Promise { + 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` + +
+ + ${this._params.items.map( + (item) => html` + + ${item.iconPath + ? html` + + ` + : item.icon + ? html` + + ` + : nothing} + ${item.label} + ${item.description + ? html` + ${item.description} + ` + : nothing} + + ` + )} + +
+
+ `; + } + + 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; + } +} diff --git a/src/dialogs/dialog-list-items/show-list-items-dialog.ts b/src/dialogs/dialog-list-items/show-list-items-dialog.ts new file mode 100644 index 0000000000..c04a9bdaa7 --- /dev/null +++ b/src/dialogs/dialog-list-items/show-list-items-dialog.ts @@ -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, + }); diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index d4fe68e1f0..1813177fea 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -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 { ${this.hass!.localize(subItem.key)} { + 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` - ${[this.hass!.localize(i.key), i.suffix].join(" ")} + html` + ${title} ` ); }); - result.push( - html` + result.push(html` + ${listItems} - ` - ); + + `); } 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`
${curViewConfig.title}
` - : hasTabViews && !showTabBar + : hasTabViews ? tabs : html`
- ${curViewConfig?.title ?? dashboardTitle} + ${views[0]?.title ?? dashboardTitle}
`}
${this._renderActionItems()}
`} - ${showTabBar - ? html`
- ${tabs} - ${this._editMode - ? html`` - : nothing} -
` + ${this._editMode + ? html` +
+ ${tabs} + +
+ ` : nothing} - ): Promise { + private _handleAddDevice(ev: CustomEvent): void { if (!shouldHandleRequestSelectedEvent(ev)) { return; } + this._addDevice(); + } + + private _addDevice = async () => { await this.hass.loadFragmentTranslation("config"); showAddIntegrationDialog(this); - } + }; - private async _handleACreateAutomation( + private _handleCreateAutomation( ev: CustomEvent - ): Promise { + ): void { if (!shouldHandleRequestSelectedEvent(ev)) { return; } + this._createAutomation(); + } + + private _createAutomation = async () => { await this.hass.loadFragmentTranslation("config"); showNewAutomationDialog(this, { mode: "automation" }); - } + }; - private async _handleAddArea( - ev: CustomEvent - ): Promise { + private _handleAddArea(ev: CustomEvent): 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 - ): Promise { + private _handleAddPerson(ev: CustomEvent): 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): void { if (!shouldHandleRequestSelectedEvent(ev)) {