Compare commits

...

7 Commits

Author SHA1 Message Date
Wendelin
30c5f4c5c3 Remove unused styles 2025-12-04 17:11:17 +01:00
Wendelin
6e8e3c1c7b Remove unused code 2025-12-04 17:10:37 +01:00
Wendelin
e4dc7b2460 Fixes 2025-12-04 17:08:09 +01:00
copilot-swe-agent[bot]
481dc3f4c0 Fix ha-data-table-labels migration to use proper @wa-select pattern
- Add HaDropdownItem type import
- Use @wa-show instead of @click for menu opening
- Use @wa-select for item selection with proper event handler
- Update comment to reference ha-dropdown instead of ha-button-menu

Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
2025-12-04 09:24:02 +00:00
copilot-swe-agent[bot]
948c10b320 Migrate ha-button-menu to ha-dropdown in 3 files
- Migrate ha-config-logs.ts: Replace ha-button-menu with ha-dropdown for log provider selection
- Migrate ha-qr-scanner.ts: Replace ha-button-menu with ha-dropdown for camera selection
- Migrate ha-data-table-labels.ts: Replace ha-button-menu with ha-dropdown for overflow labels

Following the migration pattern from PR #28293

Co-authored-by: wendevlin <12148533+wendevlin@users.noreply.github.com>
2025-12-04 09:21:04 +00:00
copilot-swe-agent[bot]
2893cc0b64 Initial plan 2025-12-04 09:11:27 +00:00
Paul Bottein
1400398422 Move reorder areas and floors to floor overflow (#28335) 2025-12-04 10:58:27 +02:00
6 changed files with 122 additions and 76 deletions

View File

@@ -2,15 +2,17 @@ import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import type { LabelRegistryEntry } from "../../data/label_registry";
import { computeCssColor } from "../../common/color/compute-color";
import { fireEvent } from "../../common/dom/fire_event";
import "../ha-label";
import { stopPropagation } from "../../common/dom/stop_propagation";
import { stringCompare } from "../../common/string/compare";
import type { LabelRegistryEntry } from "../../data/label_registry";
import "../chips/ha-chip-set";
import "../ha-button-menu";
import "../ha-dropdown";
import "../ha-dropdown-item";
import type { HaDropdownItem } from "../ha-dropdown-item";
import "../ha-icon";
import "../ha-list-item";
import "../ha-label";
@customElement("ha-data-table-labels")
class HaDataTableLabels extends LitElement {
@@ -26,12 +28,11 @@ class HaDataTableLabels extends LitElement {
(label) => this._renderLabel(label, true)
)}
${labels.length > 2
? html`<ha-button-menu
absolute
? html`<ha-dropdown
role="button"
tabindex="0"
@click=${this._handleIconOverflowMenuOpened}
@closed=${this._handleIconOverflowMenuClosed}
@click=${stopPropagation}
@wa-select=${this._handleDropdownSelect}
>
<ha-label slot="trigger" class="plus" dense>
+${labels.length - 2}
@@ -40,12 +41,12 @@ class HaDataTableLabels extends LitElement {
labels.slice(2),
(label) => label.label_id,
(label) => html`
<ha-list-item @click=${this._labelClicked} .item=${label}>
<ha-dropdown-item .value=${label.label_id} .item=${label}>
${this._renderLabel(label, false)}
</ha-list-item>
</ha-dropdown-item>
`
)}
</ha-button-menu>`
</ha-dropdown>`
: nothing}
</ha-chip-set>
`;
@@ -81,21 +82,12 @@ class HaDataTableLabels extends LitElement {
fireEvent(this, "label-clicked", { label });
}
protected _handleIconOverflowMenuOpened(e) {
e.stopPropagation();
// If this component is used inside a data table, the z-index of the row
// needs to be increased. Otherwise the ha-button-menu would be displayed
// underneath the next row in the table.
const row = this.closest(".mdc-data-table__row") as HTMLDivElement | null;
if (row) {
row.style.zIndex = "1";
}
}
protected _handleIconOverflowMenuClosed() {
const row = this.closest(".mdc-data-table__row") as HTMLDivElement | null;
if (row) {
row.style.zIndex = "";
private _handleDropdownSelect(
ev: CustomEvent<{ item: HaDropdownItem & { item?: LabelRegistryEntry } }>
) {
const label = ev.detail?.item?.item;
if (label) {
fireEvent(this, "label-clicked", { label });
}
}
@@ -114,9 +106,6 @@ class HaDataTableLabels extends LitElement {
--ha-label-background-color: var(--color, var(--grey-color));
--ha-label-background-opacity: 0.5;
}
ha-button-menu {
border-radius: 10px;
}
.plus {
--ha-label-background-color: transparent;
border: 1px solid var(--divider-color);

View File

@@ -9,13 +9,13 @@ import { customElement, property, query, state } from "lit/decorators";
import { prepareZXingModule } from "barcode-detector";
import type QrScanner from "qr-scanner";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
import type { HomeAssistant } from "../types";
import "./ha-alert";
import "./ha-button";
import "./ha-button-menu";
import "./ha-list-item";
import "./ha-dropdown";
import "./ha-dropdown-item";
import type { HaDropdownItem } from "./ha-dropdown-item";
import "./ha-spinner";
import "./ha-textfield";
import type { HaTextField } from "./ha-textfield";
@@ -52,6 +52,8 @@ class HaQrScanner extends LitElement {
@state() private _warning?: string;
@state() private _selectedCamera?: string;
private _qrScanner?: QrScanner;
private _qrNotFoundCount = 0;
@@ -121,7 +123,7 @@ class HaQrScanner extends LitElement {
!this._error &&
this._cameras &&
this._cameras.length > 1
? html`<ha-button-menu fixed @closed=${stopPropagation}>
? html`<ha-dropdown @wa-select=${this._handleDropdownSelect}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize(
@@ -131,15 +133,17 @@ class HaQrScanner extends LitElement {
></ha-icon-button>
${this._cameras!.map(
(camera) => html`
<ha-list-item
<ha-dropdown-item
.value=${camera.id}
@click=${this._cameraChanged}
class=${this._selectedCamera === camera.id
? "selected"
: ""}
>
${camera.label}
</ha-list-item>
</ha-dropdown-item>
`
)}
</ha-button-menu>`
</ha-dropdown>`
: nothing}
</div>`
: html`<ha-alert alert-type="warning">
@@ -205,6 +209,9 @@ class HaQrScanner extends LitElement {
private async _listCameras(qrScanner: typeof QrScanner): Promise<void> {
this._cameras = await qrScanner.listCameras(true);
if (this._cameras.length > 0) {
this._selectedCamera = this._cameras[0].id;
}
}
private _qrCodeError = (err: any) => {
@@ -252,8 +259,12 @@ class HaQrScanner extends LitElement {
this._qrCodeScanned(this._manualInput!.value);
}
private _cameraChanged(ev: CustomEvent): void {
this._qrScanner?.setCamera((ev.target as any).value);
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const cameraId = ev.detail?.item?.value;
if (cameraId) {
this._selectedCamera = cameraId;
this._qrScanner?.setCamera(cameraId);
}
}
private _openExternalScanner() {
@@ -359,7 +370,7 @@ class HaQrScanner extends LitElement {
#canvas-container {
position: relative;
}
ha-button-menu {
ha-icon-button {
position: absolute;
bottom: 8px;
right: 8px;
@@ -369,6 +380,9 @@ class HaQrScanner extends LitElement {
color: white;
border-radius: var(--ha-border-radius-circle);
}
ha-dropdown-item.selected {
font-weight: var(--ha-font-weight-bold);
}
.row {
display: flex;
align-items: center;

View File

@@ -81,8 +81,11 @@ class DialogAreasFloorsOrder extends LitElement {
return nothing;
}
const hasFloors = this._hierarchy.floors.length > 0;
const dialogTitle = this.hass.localize(
"ui.panel.config.areas.dialog.reorder_title"
hasFloors
? "ui.panel.config.areas.dialog.reorder_floors_areas_title"
: "ui.panel.config.areas.dialog.reorder_areas_title"
);
return html`
@@ -418,7 +421,6 @@ class DialogAreasFloorsOrder extends LitElement {
}
.floor.unassigned {
border-style: dashed;
margin-top: 16px;
}

View File

@@ -175,21 +175,12 @@ export class HaConfigAreasDashboard extends LitElement {
.route=${this.route}
has-fab
>
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon" @click=${this._showReorderDialog}>
<ha-svg-icon .path=${mdiSort} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.panel.config.areas.picker.reorder")}
</ha-list-item>
<ha-list-item graphic="icon" @click=${this._showHelp}>
<ha-svg-icon .path=${mdiHelpCircle} slot="graphic"></ha-svg-icon>
${this.hass.localize("ui.common.help")}
</ha-list-item>
</ha-button-menu>
<ha-icon-button
slot="toolbar-icon"
.label=${this.hass.localize("ui.common.help")}
.path=${mdiHelpCircle}
@click=${this._showHelp}
></ha-icon-button>
<div class="container">
<div class="floors">
${this._hierarchy.floors.map(({ areas, id }) => {
@@ -213,6 +204,16 @@ export class HaConfigAreasDashboard extends LitElement {
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
<li divider role="separator"></li>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiPencil}
@@ -266,9 +267,30 @@ export class HaConfigAreasDashboard extends LitElement {
<div class="header">
<h2>
${this.hass.localize(
"ui.panel.config.areas.picker.other_areas"
this._hierarchy.floors.length
? "ui.panel.config.areas.picker.other_areas"
: "ui.panel.config.areas.picker.header"
)}
</h2>
<div class="actions">
<ha-button-menu
@action=${this._handleUnassignedAreasAction}
>
<ha-icon-button
slot="trigger"
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon"
><ha-svg-icon
.path=${mdiSort}
slot="graphic"
></ha-svg-icon
>${this.hass.localize(
"ui.panel.config.areas.picker.reorder"
)}</ha-list-item
>
</ha-button-menu>
</div>
</div>
<ha-sortable
handle-selector="a"
@@ -515,14 +537,23 @@ export class HaConfigAreasDashboard extends LitElement {
const floor = (ev.currentTarget as any).floor;
switch (ev.detail.index) {
case 0:
this._editFloor(floor);
this._showReorderDialog();
break;
case 1:
this._editFloor(floor);
break;
case 2:
this._deleteFloor(floor);
break;
}
}
private _handleUnassignedAreasAction(ev: CustomEvent<ActionDetail>) {
if (ev.detail.index === 0) {
this._showReorderDialog();
}
}
private _createFloor() {
this._openFloorDialog();
}

View File

@@ -4,10 +4,12 @@ import { css, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { navigate } from "../../../common/navigate";
import { stringCompare } from "../../../common/string/compare";
import { extractSearchParam } from "../../../common/url/search-params";
import "../../../components/ha-button";
import "../../../components/ha-button-menu";
import "../../../components/ha-list-item";
import "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/search-input";
import type { LogProvider } from "../../../data/error_log";
import { fetchHassioAddonsInfo } from "../../../data/hassio/addon";
@@ -18,7 +20,6 @@ import type { HomeAssistant, Route } from "../../../types";
import "./error-log-card";
import "./system-log-card";
import type { SystemLogCard } from "./system-log-card";
import { stringCompare } from "../../../common/string/compare";
const logProviders: LogProvider[] = [
{
@@ -117,7 +118,10 @@ export class HaConfigLogs extends LitElement {
>
${isComponentLoaded(this.hass, "hassio")
? html`
<ha-button-menu slot="toolbar-icon">
<ha-dropdown
slot="toolbar-icon"
@wa-select=${this._handleDropdownSelect}
>
<ha-button slot="trigger" appearance="filled">
<ha-svg-icon slot="end" .path=${mdiChevronDown}></ha-svg-icon>
${this._logProviders.find(
@@ -126,16 +130,17 @@ export class HaConfigLogs extends LitElement {
</ha-button>
${this._logProviders.map(
(provider) => html`
<ha-list-item
?selected=${provider.key === this._selectedLogProvider}
.provider=${provider.key}
@click=${this._selectProvider}
<ha-dropdown-item
.value=${provider.key}
class=${provider.key === this._selectedLogProvider
? "selected"
: ""}
>
${provider.name}
</ha-list-item>
</ha-dropdown-item>
`
)}
</ha-button-menu>
</ha-dropdown>
`
: ""}
${search}
@@ -170,8 +175,12 @@ export class HaConfigLogs extends LitElement {
this._detail = !this._detail;
}
private _selectProvider(ev) {
this._selectedLogProvider = (ev.currentTarget as any).provider;
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
const provider = ev.detail?.item?.value;
if (!provider) {
return;
}
this._selectedLogProvider = provider;
this._filter = "";
navigate(`/config/logs?provider=${this._selectedLogProvider}`);
}
@@ -254,7 +263,7 @@ export class HaConfigLogs extends LitElement {
direction: ltr;
}
@media all and (max-width: 870px) {
ha-button-menu {
ha-dropdown {
max-width: 50%;
}
ha-button {
@@ -265,8 +274,8 @@ export class HaConfigLogs extends LitElement {
white-space: nowrap;
}
}
ha-list-item[selected] {
color: var(--primary-color);
ha-dropdown-item.selected {
font-weight: var(--ha-font-weight-bold);
}
`,
];

View File

@@ -2475,10 +2475,11 @@
"area_reorder_failed": "Failed to reorder areas",
"area_move_failed": "Failed to move area",
"floor_reorder_failed": "Failed to reorder floors",
"reorder": "Reorder floors and areas"
"reorder": "Reorder"
},
"dialog": {
"reorder_title": "Reorder floors and areas",
"reorder_areas_title": "Reorder areas",
"reorder_floors_areas_title": "Reorder floors and areas",
"other_areas": "Other areas",
"reorder_failed": "Failed to save order",
"empty_floor": "No areas on this floor",