mirror of
https://github.com/home-assistant/frontend.git
synced 2026-11-01 10:35:31 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0932f068bc | ||
|
|
589c5281e6 | ||
|
|
6134056776 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: ha-frontend-components
|
||||
description: Home Assistant frontend component patterns. Use when implementing or reviewing dialogs, ha-form, ha-alert, keyboard shortcuts, tooltips, panels, Lovelace cards, or ha-button usage.
|
||||
description: Home Assistant frontend component patterns. Use when implementing or reviewing dialogs, ha-form, ha-alert, loading states, keyboard shortcuts, tooltips, panels, Lovelace cards, or ha-button usage.
|
||||
---
|
||||
|
||||
# HA Frontend Components
|
||||
@@ -91,6 +91,36 @@ html`
|
||||
`;
|
||||
```
|
||||
|
||||
## Loading States
|
||||
|
||||
Pick the indicator by what is loading, following the [Loading pattern](https://contributors.home-assistant.io/design/patterns/loading/):
|
||||
|
||||
- Use a skeleton from `src/components/skeleton/` (`ha-skeleton`, `ha-skeleton-text`, `ha-skeleton-icon`) when content loads into a layout you already know, such as table rows, tile text, or a log. It holds the layout, so nothing jumps when the content arrives.
|
||||
- Use `ha-spinner` only when a whole area loads and its shape is unknown.
|
||||
- Use `ha-button` with `loading`, or `ha-progress-button`, for an action started from a button. Don't put a separate spinner next to it.
|
||||
|
||||
Wrap the indicator in `ha-fade-in` from `src/components/animation/ha-fade-in.ts`:
|
||||
|
||||
- Fade a skeleton in straight away over about 300ms (`.duration=${300}`), as the data table does. It stands in for the content, so a delay would leave an empty gap.
|
||||
- Delay a spinner or progress bar, usually by `.delay=${500}`, so a load that finishes within that time never flashes it. Some slower areas, such as backups and updates, use `${1000}`.
|
||||
- Don't fade in the loaded content itself.
|
||||
|
||||
Give a skeleton group the `progressbar` role and a translated label, as `ha-spinner` has, rather than visible "Loading" text. A loading state always ends in the content, an empty state, or an error.
|
||||
|
||||
```ts
|
||||
html`
|
||||
<ha-fade-in .duration=${300} easing="ease-in">
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-label=${this._i18n.localize("ui.common.loading")}
|
||||
>
|
||||
<ha-skeleton-text></ha-skeleton-text>
|
||||
</div>
|
||||
</ha-fade-in>
|
||||
<ha-fade-in .delay=${500}><ha-spinner></ha-spinner></ha-fade-in>
|
||||
`;
|
||||
```
|
||||
|
||||
## Shortcuts And Tooltips
|
||||
|
||||
Use `ShortcutManager` from `src/common/keyboard/shortcuts.ts` for keyboard shortcuts. It blocks shortcuts in input fields, can prevent shortcuts during text selection, and supports character and KeyCode shortcuts for non-latin keyboards. See `src/state/quick-bar-mixin.ts` for global shortcut examples.
|
||||
|
||||
@@ -40,7 +40,7 @@ Never run `tsc` or `pnpm lint:types` with file arguments. When `tsc` receives fi
|
||||
Detailed guidance lives in `.agents/skills/<name>/SKILL.md`. Load every matching skill before detailed implementation or review. For reviews, load `ha-frontend-review` alongside all companions that apply to the changed code or behavior:
|
||||
|
||||
- `ha-frontend-contexts`: Lit contexts, `hass` migration, and rerender-sensitive state access.
|
||||
- `ha-frontend-components`: dialogs, forms, alerts, shortcuts, tooltips, panels, and Lovelace cards.
|
||||
- `ha-frontend-components`: dialogs, forms, alerts, loading states, shortcuts, tooltips, panels, and Lovelace cards.
|
||||
- `ha-frontend-events`: event handler typing, custom event dispatch, and event-map declarations.
|
||||
- `ha-frontend-types`: backend data contracts, optional schemas, shared types, assertions, and lifecycle types.
|
||||
- `ha-frontend-lit`: reactive fields, DOM queries, lifecycle behavior, and render-derived state.
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { consume } from "../../../common/decorators/consume";
|
||||
@@ -38,10 +37,12 @@ import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button-next";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-tooltip";
|
||||
import "../../../components/item/ha-list-item-base";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import "../../../components/list/ha-list-nav";
|
||||
import type { AreaRegistryEntry } from "../../../data/area/area_registry";
|
||||
import {
|
||||
deleteAreaRegistryEntry,
|
||||
@@ -375,18 +376,22 @@ class HaConfigAreaPage extends LitElement {
|
||||
.header=${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
>${
|
||||
devices.length
|
||||
? html`<ha-list>
|
||||
? html`<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.devices.caption"
|
||||
)}
|
||||
>
|
||||
${devices.map(
|
||||
(device) => html`
|
||||
<a href="/config/devices/device/${device.id}">
|
||||
<ha-list-item hasMeta>
|
||||
<span>${device.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<ha-list-item-button
|
||||
.href=${`/config/devices/device/${device.id}`}
|
||||
.headline=${device.name}
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
)}
|
||||
</ha-list>`
|
||||
</ha-list-nav>`
|
||||
: html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize("ui.panel.config.devices.no_devices")}
|
||||
@@ -402,20 +407,23 @@ class HaConfigAreaPage extends LitElement {
|
||||
>
|
||||
${
|
||||
nonAutomatedEntities.length
|
||||
? html`<ha-list>
|
||||
? html`<ha-list-base
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.areas.editor.linked_entities_caption"
|
||||
)}
|
||||
>
|
||||
${nonAutomatedEntities.map(
|
||||
(entity) => html`
|
||||
<ha-list-item
|
||||
<ha-list-item-button
|
||||
@click=${this._openEntity}
|
||||
.entity=${entity}
|
||||
hasMeta
|
||||
.headline=${entity.name ?? undefined}
|
||||
>
|
||||
<span>${entity.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
)}</ha-list
|
||||
>`
|
||||
)}
|
||||
</ha-list-base>`
|
||||
: html`
|
||||
<div class="no-entries">
|
||||
${this.hass.localize(
|
||||
@@ -444,14 +452,19 @@ class HaConfigAreaPage extends LitElement {
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.areas.related_lists.automations_assigned",
|
||||
{ area: area.name }
|
||||
)}
|
||||
>
|
||||
${groupedAutomations.map((automation) =>
|
||||
this._renderAutomation(
|
||||
automation.name,
|
||||
automation.entity
|
||||
)
|
||||
)}</ha-list
|
||||
>`
|
||||
)}
|
||||
</ha-list-nav>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
@@ -461,14 +474,19 @@ class HaConfigAreaPage extends LitElement {
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.areas.related_lists.automations_targeting",
|
||||
{ area: area.name }
|
||||
)}
|
||||
>
|
||||
${relatedAutomations.map((automation) =>
|
||||
this._renderAutomation(
|
||||
automation.name,
|
||||
automation.entity
|
||||
)
|
||||
)}</ha-list
|
||||
>`
|
||||
)}
|
||||
</ha-list-nav>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
@@ -502,11 +520,16 @@ class HaConfigAreaPage extends LitElement {
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.areas.related_lists.scenes_assigned",
|
||||
{ area: area.name }
|
||||
)}
|
||||
>
|
||||
${groupedScenes.map((scene) =>
|
||||
this._renderScene(scene.name, scene.entity)
|
||||
)}</ha-list
|
||||
>`
|
||||
)}
|
||||
</ha-list-nav>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
@@ -516,11 +539,16 @@ class HaConfigAreaPage extends LitElement {
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.areas.related_lists.scenes_targeting",
|
||||
{ area: area.name }
|
||||
)}
|
||||
>
|
||||
${relatedScenes.map((scene) =>
|
||||
this._renderScene(scene.name, scene.entity)
|
||||
)}</ha-list
|
||||
>`
|
||||
)}
|
||||
</ha-list-nav>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
@@ -554,9 +582,16 @@ class HaConfigAreaPage extends LitElement {
|
||||
"ui.panel.config.areas.assigned_to_area"
|
||||
)}:
|
||||
</h3>
|
||||
${groupedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}`
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.areas.related_lists.scripts_assigned",
|
||||
{ area: area.name }
|
||||
)}
|
||||
>
|
||||
${groupedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}
|
||||
</ha-list-nav>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
@@ -566,9 +601,16 @@ class HaConfigAreaPage extends LitElement {
|
||||
"ui.panel.config.areas.targeting_area"
|
||||
)}:
|
||||
</h3>
|
||||
${relatedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}`
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.areas.related_lists.scripts_targeting",
|
||||
{ area: area.name }
|
||||
)}
|
||||
>
|
||||
${relatedScripts.map((script) =>
|
||||
this._renderScript(script.name, script.entity)
|
||||
)}
|
||||
</ha-list-nav>`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
@@ -753,47 +795,41 @@ class HaConfigAreaPage extends LitElement {
|
||||
}
|
||||
|
||||
private _renderScene(name: string, entityState: SceneEntity) {
|
||||
return html`<ha-tooltip
|
||||
.for="scene-${slugify(entityState.entity_id)}"
|
||||
.distance=${-4}
|
||||
.disabled=${!!entityState.attributes.id}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.devices.cant_edit")}
|
||||
</ha-tooltip>
|
||||
<a
|
||||
.id="scene-${slugify(entityState.entity_id)}"
|
||||
href=${ifDefined(
|
||||
entityState.attributes.id
|
||||
? `/config/scene/edit/${entityState.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<ha-list-item .disabled=${!entityState.attributes.id} hasMeta>
|
||||
<span>${name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a> `;
|
||||
return entityState.attributes.id
|
||||
? this._renderLink(
|
||||
name,
|
||||
`/config/scene/edit/${entityState.attributes.id}`
|
||||
)
|
||||
: this._renderNotEditable(
|
||||
name,
|
||||
`scene-${slugify(entityState.entity_id)}`
|
||||
);
|
||||
}
|
||||
|
||||
private _renderAutomation(name: string, entityState: AutomationEntity) {
|
||||
return html`<a
|
||||
id="automation-${slugify(entityState.entity_id)}"
|
||||
href=${ifDefined(
|
||||
entityState.attributes.id
|
||||
? `/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<ha-list-item .disabled=${!entityState.attributes.id} hasMeta>
|
||||
<span>${name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<ha-tooltip
|
||||
for="automation-${slugify(entityState.entity_id)}"
|
||||
.disabled=${!!entityState.attributes.id}
|
||||
.distance=${-4}
|
||||
>${this.hass.localize("ui.panel.config.devices.cant_edit")}
|
||||
return entityState.attributes.id
|
||||
? this._renderLink(
|
||||
name,
|
||||
`/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}`
|
||||
)
|
||||
: this._renderNotEditable(
|
||||
name,
|
||||
`automation-${slugify(entityState.entity_id)}`
|
||||
);
|
||||
}
|
||||
|
||||
private _renderLink(name: string, href: string) {
|
||||
return html`<ha-list-item-button .href=${href} .headline=${name}>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>`;
|
||||
}
|
||||
|
||||
private _renderNotEditable(name: string, id: string) {
|
||||
return html`<ha-list-item-base id=${id} .headline=${name}>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-base>
|
||||
<ha-tooltip .for=${id} .distance=${-4}>
|
||||
${this.hass.localize("ui.panel.config.devices.cant_edit")}
|
||||
</ha-tooltip>`;
|
||||
}
|
||||
|
||||
@@ -805,12 +841,7 @@ class HaConfigAreaPage extends LitElement {
|
||||
if (entry) {
|
||||
url = `/config/script/edit/${entry.unique_id}`;
|
||||
}
|
||||
return html`<a href=${url}>
|
||||
<ha-list-item hasMeta>
|
||||
<span>${name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`;
|
||||
return this._renderLink(name, url);
|
||||
}
|
||||
|
||||
private async _findRelated() {
|
||||
@@ -977,8 +1008,15 @@ class HaConfigAreaPage extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-card > a:first-child {
|
||||
display: block;
|
||||
ha-list-base,
|
||||
ha-list-nav {
|
||||
--ha-list-padding: var(--ha-space-2) 0;
|
||||
}
|
||||
ha-list-item-base {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
ha-list-item-button ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-card > *:first-child {
|
||||
margin-top: -16px;
|
||||
|
||||
@@ -7,7 +7,8 @@ import { getDeviceArea } from "../../../../common/entity/context/get_device_cont
|
||||
import { caseInsensitiveStringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/item/ha-list-item-button";
|
||||
import "../../../../components/list/ha-list-nav";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { DeviceRegistryEntry } from "../../../../data/device/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
@@ -69,41 +70,43 @@ export class HaDeviceChildDevicesCard extends LitElement {
|
||||
<h1 class="card-header">
|
||||
${this.hass.localize("ui.panel.config.devices.child_devices.heading")}
|
||||
</h1>
|
||||
${(this._showAll
|
||||
? childDevices
|
||||
: childDevices.slice(0, MAX_VISIBLE_CHILD_DEVICES)
|
||||
).map((childDevice) => {
|
||||
const area = getDeviceArea(
|
||||
childDevice,
|
||||
this.hass.areas,
|
||||
this.hass.devices
|
||||
);
|
||||
const entityCount = entityCounts[childDevice.id] ?? 0;
|
||||
const secondary = [
|
||||
area?.name,
|
||||
entityCount
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.common.quick_links.entities",
|
||||
{ count: entityCount }
|
||||
)
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" • ");
|
||||
return html`
|
||||
<a href=${`/config/devices/device/${childDevice.id}`}>
|
||||
<ha-list-item hasMeta .twoline=${!!secondary}>
|
||||
${computeDeviceNameDisplay(childDevice, this.hass.localize)}
|
||||
${
|
||||
secondary
|
||||
? html`<span slot="secondary">${secondary}</span>`
|
||||
: nothing
|
||||
}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})}
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.devices.child_devices.heading"
|
||||
)}
|
||||
>
|
||||
${(this._showAll
|
||||
? childDevices
|
||||
: childDevices.slice(0, MAX_VISIBLE_CHILD_DEVICES)
|
||||
).map((childDevice) => {
|
||||
const area = getDeviceArea(
|
||||
childDevice,
|
||||
this.hass.areas,
|
||||
this.hass.devices
|
||||
);
|
||||
const entityCount = entityCounts[childDevice.id] ?? 0;
|
||||
const secondary = [
|
||||
area?.name,
|
||||
entityCount
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.common.quick_links.entities",
|
||||
{ count: entityCount }
|
||||
)
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" • ");
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
.href=${`/config/devices/device/${childDevice.id}`}
|
||||
.headline=${computeDeviceNameDisplay(childDevice, this.hass.localize)}
|
||||
.supportingText=${secondary || undefined}
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`;
|
||||
})}
|
||||
</ha-list-nav>
|
||||
${
|
||||
!this._showAll && childDevices.length > MAX_VISIBLE_CHILD_DEVICES
|
||||
? html`
|
||||
@@ -133,9 +136,8 @@ export class HaDeviceChildDevicesCard extends LitElement {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
ha-list-item-button ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
button.show-more {
|
||||
|
||||
@@ -6,8 +6,9 @@ import { repeat } from "lit/directives/repeat";
|
||||
import { until } from "lit/directives/until";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-list";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/item/ha-list-item-button";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { computeEntityEntryName } from "../../../../common/entity/compute_entity_name_display";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import { entryIcon } from "../../../../data/icons";
|
||||
@@ -70,15 +71,15 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
enabledEntities.length
|
||||
? html`
|
||||
<div id="entities" class="move-up">
|
||||
<ha-list>
|
||||
<ha-list-base .ariaLabel=${this.header}>
|
||||
${this._renderEntities(mainEntities)}
|
||||
${
|
||||
mainEntities.length && additionalEntities.length
|
||||
? html`<div class="divider" role="separator"></div>`
|
||||
? html`<div class="divider" aria-hidden="true"></div>`
|
||||
: nothing
|
||||
}
|
||||
${this._renderEntities(additionalEntities)}
|
||||
</ha-list>
|
||||
</ha-list-base>
|
||||
</div>
|
||||
`
|
||||
: nothing
|
||||
@@ -102,11 +103,15 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<ha-list>
|
||||
<ha-list-base
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.devices.entities.disabled_entities_list"
|
||||
)}
|
||||
>
|
||||
${disabledEntities.map((entry) =>
|
||||
this._renderUnavailableEntity(entry)
|
||||
)}
|
||||
</ha-list>
|
||||
</ha-list-base>
|
||||
<button
|
||||
class="show-more"
|
||||
@click=${this._toggleShowHidden}
|
||||
@@ -146,7 +151,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
"ui.panel.config.devices.entities.hidden"
|
||||
)})`;
|
||||
}
|
||||
return html`<div>
|
||||
return html`<div role="listitem" @keydown=${stopPropagation}>
|
||||
${entityRowElement(entry.entity_id, name, this.hass)}
|
||||
</div>`;
|
||||
}
|
||||
@@ -159,15 +164,14 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
const icon = until(entryIcon(this.hass, entry));
|
||||
|
||||
return html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
<ha-list-item-button
|
||||
class="disabled-entry"
|
||||
.entry=${entry}
|
||||
@click=${this._openEditEntry}
|
||||
>
|
||||
<ha-icon slot="graphic" .icon=${icon}></ha-icon>
|
||||
<div class="name">${name}</div>
|
||||
</ha-list-item>
|
||||
<ha-icon slot="start" .icon=${icon}></ha-icon>
|
||||
<span slot="headline" class="name">${name}</span>
|
||||
</ha-list-item-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -180,9 +184,6 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-icon {
|
||||
margin-left: -8px;
|
||||
}
|
||||
.entity-id {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@@ -201,21 +202,25 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
.move-up {
|
||||
margin-top: -13px;
|
||||
}
|
||||
.move-up:has(> ha-list) {
|
||||
.move-up:has(> ha-list-base) {
|
||||
margin-top: -24px;
|
||||
}
|
||||
:not(.move-up) > ha-list {
|
||||
:not(.move-up) > ha-list-base {
|
||||
margin-top: -24px;
|
||||
}
|
||||
ha-list + button.show-more,
|
||||
.move-up + :not(:has(ha-list)) > button.show-more {
|
||||
ha-list-base + button.show-more,
|
||||
.move-up + :not(:has(ha-list-base)) > button.show-more {
|
||||
margin-top: -12px;
|
||||
}
|
||||
#entities > ha-list {
|
||||
ha-list-base {
|
||||
--ha-list-padding: var(--ha-space-4) 0;
|
||||
--ha-list-gap: var(--ha-space-2);
|
||||
}
|
||||
#entities > ha-list-base {
|
||||
margin: 0 16px 0 8px;
|
||||
}
|
||||
#entities > ha-list > ha-list-item {
|
||||
padding: 0 16px 0 12px;
|
||||
#entities ha-list-item-button {
|
||||
--ha-row-item-padding-inline: var(--ha-space-2) var(--ha-space-4);
|
||||
}
|
||||
.name {
|
||||
font-size: var(--ha-font-size-m);
|
||||
@@ -242,12 +247,10 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
outline: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
ha-list > * {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
ha-list-item {
|
||||
height: 40px;
|
||||
--mdc-ripple-color: transparent;
|
||||
ha-list-item-button {
|
||||
--ha-row-item-min-height: 40px;
|
||||
--ha-row-item-padding-block: 0;
|
||||
--ha-row-item-gap: var(--ha-space-6);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import { computeDeviceNameDisplay } from "../../../../common/entity/compute_devi
|
||||
import { caseInsensitiveStringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/item/ha-list-item-button";
|
||||
import "../../../../components/list/ha-list-nav";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../../data/device/device_registry";
|
||||
import { fetchLinkedDevices } from "../../../../data/device/device_registry";
|
||||
@@ -80,22 +81,26 @@ export class HaDeviceLinkedDevicesCard extends LitElement {
|
||||
"ui.panel.config.devices.linked_devices.description"
|
||||
)}
|
||||
</div>
|
||||
${linkedDevices.map((linkedDevice) => {
|
||||
const domain = this._deviceDomain(linkedDevice, entryLookup);
|
||||
const integrationName = domain
|
||||
? domainToName(this.hass.localize, domain)
|
||||
: undefined;
|
||||
return html`
|
||||
<a href=${`/config/devices/device/${linkedDevice.id}`}>
|
||||
<ha-list-item
|
||||
graphic=${domain ? "icon" : nothing}
|
||||
hasMeta
|
||||
.twoline=${!!integrationName}
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.devices.linked_devices.heading"
|
||||
)}
|
||||
>
|
||||
${linkedDevices.map((linkedDevice) => {
|
||||
const domain = this._deviceDomain(linkedDevice, entryLookup);
|
||||
const integrationName = domain
|
||||
? domainToName(this.hass.localize, domain)
|
||||
: undefined;
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
.href=${`/config/devices/device/${linkedDevice.id}`}
|
||||
.headline=${computeDeviceNameDisplay(linkedDevice, this.hass.localize)}
|
||||
.supportingText=${integrationName}
|
||||
>
|
||||
${
|
||||
domain
|
||||
? html`<img
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
loading="lazy"
|
||||
alt=""
|
||||
src=${brandsUrl(
|
||||
@@ -111,17 +116,11 @@ export class HaDeviceLinkedDevicesCard extends LitElement {
|
||||
/>`
|
||||
: nothing
|
||||
}
|
||||
${computeDeviceNameDisplay(linkedDevice, this.hass.localize)}
|
||||
${
|
||||
integrationName
|
||||
? html`<span slot="secondary">${integrationName}</span>`
|
||||
: nothing
|
||||
}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`;
|
||||
})}
|
||||
</ha-list-nav>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
@@ -169,12 +168,11 @@ export class HaDeviceLinkedDevicesCard extends LitElement {
|
||||
padding-bottom: var(--ha-space-2);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
ha-list-item-button ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-list-item img {
|
||||
ha-list-item-button img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { getDeviceArea } from "../../../../common/entity/context/get_device_cont
|
||||
import { caseInsensitiveStringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/item/ha-list-item-button";
|
||||
import "../../../../components/list/ha-list-nav";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { DeviceRegistryEntry } from "../../../../data/device/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
@@ -71,41 +72,43 @@ export class HaDeviceViaDevicesCard extends LitElement {
|
||||
"ui.panel.config.devices.connected_devices.heading"
|
||||
)}
|
||||
</h1>
|
||||
${(this._showAll
|
||||
? viaDevices
|
||||
: viaDevices.slice(0, MAX_VISIBLE_VIA_DEVICES)
|
||||
).map((viaDevice) => {
|
||||
const area = getDeviceArea(
|
||||
viaDevice,
|
||||
this.hass.areas,
|
||||
this.hass.devices
|
||||
);
|
||||
const entityCount = entityCounts[viaDevice.id] ?? 0;
|
||||
const secondary = [
|
||||
area?.name,
|
||||
entityCount
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.common.quick_links.entities",
|
||||
{ count: entityCount }
|
||||
)
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" • ");
|
||||
return html`
|
||||
<a href=${`/config/devices/device/${viaDevice.id}`}>
|
||||
<ha-list-item hasMeta .twoline=${!!secondary}>
|
||||
${computeDeviceNameDisplay(viaDevice, this.hass.localize)}
|
||||
${
|
||||
secondary
|
||||
? html`<span slot="secondary">${secondary}</span>`
|
||||
: nothing
|
||||
}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})}
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.devices.connected_devices.heading"
|
||||
)}
|
||||
>
|
||||
${(this._showAll
|
||||
? viaDevices
|
||||
: viaDevices.slice(0, MAX_VISIBLE_VIA_DEVICES)
|
||||
).map((viaDevice) => {
|
||||
const area = getDeviceArea(
|
||||
viaDevice,
|
||||
this.hass.areas,
|
||||
this.hass.devices
|
||||
);
|
||||
const entityCount = entityCounts[viaDevice.id] ?? 0;
|
||||
const secondary = [
|
||||
area?.name,
|
||||
entityCount
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.common.quick_links.entities",
|
||||
{ count: entityCount }
|
||||
)
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" • ");
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
.href=${`/config/devices/device/${viaDevice.id}`}
|
||||
.headline=${computeDeviceNameDisplay(viaDevice, this.hass.localize)}
|
||||
.supportingText=${secondary || undefined}
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`;
|
||||
})}
|
||||
</ha-list-nav>
|
||||
${
|
||||
!this._showAll && viaDevices.length > MAX_VISIBLE_VIA_DEVICES
|
||||
? html`
|
||||
@@ -135,9 +138,8 @@ export class HaDeviceViaDevicesCard extends LitElement {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
ha-list-item-button ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
button.show-more {
|
||||
|
||||
+31
-23
@@ -3,10 +3,11 @@ import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-list";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import type {
|
||||
MatterFabricData,
|
||||
MatterNodeDiagnostics,
|
||||
@@ -59,30 +60,36 @@ class DialogMatterManageFabrics extends LitElement {
|
||||
</p>
|
||||
${
|
||||
this._nodeDiagnostics
|
||||
? html`<ha-list>
|
||||
? html`<ha-list-base
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.matter.manage_fabrics.title"
|
||||
)}
|
||||
>
|
||||
${this._nodeDiagnostics.active_fabrics.map(
|
||||
(fabric) =>
|
||||
html`<ha-list-item
|
||||
noninteractive
|
||||
.hasMeta=${
|
||||
html`<ha-list-item-base
|
||||
.headline=${String(
|
||||
fabric.vendor_name ||
|
||||
fabric.fabric_label ||
|
||||
fabric.vendor_id
|
||||
)}
|
||||
>
|
||||
${
|
||||
this._nodeDiagnostics!.available &&
|
||||
fabric.fabric_index !==
|
||||
this._nodeDiagnostics!.active_fabric_index
|
||||
? html`<ha-icon-button
|
||||
slot="end"
|
||||
.label=${this.hass.localize("ui.common.remove")}
|
||||
.path=${mdiDelete}
|
||||
.fabric=${fabric}
|
||||
@click=${this._removeFabric}
|
||||
></ha-icon-button>`
|
||||
: nothing
|
||||
}
|
||||
>${
|
||||
fabric.vendor_name ||
|
||||
fabric.fabric_label ||
|
||||
fabric.vendor_id
|
||||
}
|
||||
<ha-icon-button
|
||||
@click=${this._removeFabric}
|
||||
slot="meta"
|
||||
.fabric=${fabric}
|
||||
.path=${mdiDelete}
|
||||
></ha-icon-button>
|
||||
</ha-list-item>`
|
||||
</ha-list-item-base>`
|
||||
)}
|
||||
</ha-list>`
|
||||
</ha-list-base>`
|
||||
: html`<div class="center">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>`
|
||||
@@ -161,9 +168,10 @@ class DialogMatterManageFabrics extends LitElement {
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
--mdc-list-side-padding: 24px;
|
||||
--mdc-list-side-padding-right: 16px;
|
||||
--mdc-list-item-meta-size: 48px;
|
||||
}
|
||||
ha-list-item-base {
|
||||
--ha-row-item-padding-inline: var(--ha-space-6) var(--ha-space-4);
|
||||
--ha-row-item-padding-block: 0;
|
||||
}
|
||||
p {
|
||||
margin: 8px 24px;
|
||||
|
||||
+33
-14
@@ -1,15 +1,21 @@
|
||||
import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCheckCircle,
|
||||
mdiCloseCircle,
|
||||
mdiContentCopy,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
|
||||
import "../../../../../components/ha-list";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import { pingMatterNode } from "../../../../../data/matter";
|
||||
import { haStyle, haStyleDialog } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
@@ -83,22 +89,29 @@ class DialogMatterPingNode extends LitElement {
|
||||
"ui.panel.config.matter.ping_node.ping_complete"
|
||||
)}
|
||||
</h2>
|
||||
<ha-list>
|
||||
<ha-list-base
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.matter.ping_node.ping_complete"
|
||||
)}
|
||||
>
|
||||
${this._pingResultEntries.map(
|
||||
([ip, success]) =>
|
||||
html`<ha-list-item
|
||||
hasMeta
|
||||
.ip=${ip}
|
||||
@click=${this._copyIpToClipboard}
|
||||
>${ip}
|
||||
html`<ha-list-item-base .headline=${ip}>
|
||||
<ha-svg-icon
|
||||
slot="meta"
|
||||
slot="end"
|
||||
.path=${success ? mdiCheckCircle : mdiAlertCircle}
|
||||
class=${success ? "success" : "failed"}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.label=${this.hass.localize("ui.common.copy")}
|
||||
.path=${mdiContentCopy}
|
||||
.ip=${ip}
|
||||
@click=${this._copyIpToClipboard}
|
||||
></ha-icon-button>
|
||||
</ha-list-item-base>`
|
||||
)}
|
||||
</ha-list>
|
||||
</ha-list-base>
|
||||
`
|
||||
: this._status === "started"
|
||||
? html`
|
||||
@@ -208,8 +221,14 @@ class DialogMatterPingNode extends LitElement {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
ha-list {
|
||||
--mdc-list-side-padding: 0;
|
||||
ha-list-base {
|
||||
--ha-list-padding: var(--ha-space-2) 0;
|
||||
}
|
||||
|
||||
ha-list-item-base {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
--ha-row-item-padding-block: 0;
|
||||
--ha-row-item-gap: var(--ha-space-2);
|
||||
}
|
||||
|
||||
.flex-container ha-spinner,
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import { stringCompare } from "../../../../../common/string/compare";
|
||||
@@ -20,9 +19,10 @@ import type { HaDropdownSelectEvent } from "../../../../../components/ha-dropdow
|
||||
import "../../../../../components/ha-dropdown-item";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import { getSignedPath } from "../../../../../data/auth";
|
||||
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../../../data/diagnostics";
|
||||
@@ -222,62 +222,67 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</h4>
|
||||
</div>
|
||||
${network.routers.map((router) => {
|
||||
const otbr =
|
||||
this._otbrInfo && this._otbrInfo[router.extended_address];
|
||||
const showDefaultRouter = !!network.dataset;
|
||||
const isDefaultRouter =
|
||||
showDefaultRouter &&
|
||||
router.extended_address ===
|
||||
network.dataset!.preferred_extended_address;
|
||||
const showOverflow = showDefaultRouter || otbr;
|
||||
return html`<ha-list-item
|
||||
class="router"
|
||||
twoline
|
||||
graphic=${ifDefined(router.brand ? "avatar" : undefined)}
|
||||
.hasMeta=${showOverflow}
|
||||
>
|
||||
${
|
||||
router.brand
|
||||
? html`<img
|
||||
slot="graphic"
|
||||
.src=${brandsUrl(
|
||||
{
|
||||
domain: router.brand,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
},
|
||||
this.hass.auth.data.hassUrl
|
||||
)}
|
||||
alt=${router.brand}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
router.instance_name ||
|
||||
router.model_name ||
|
||||
router.server?.replace(".local.", "") ||
|
||||
""
|
||||
}
|
||||
<span slot="secondary">${router.server}</span>
|
||||
${
|
||||
showOverflow
|
||||
? html`${
|
||||
isDefaultRouter
|
||||
? html`<ha-svg-icon
|
||||
.path=${mdiCellphoneKey}
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.thread.default_router"
|
||||
)}
|
||||
></ha-svg-icon>`
|
||||
: ""
|
||||
}
|
||||
<ha-dropdown
|
||||
slot="meta"
|
||||
<ha-list-base
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.thread.border_routers",
|
||||
{ count: network.routers.length }
|
||||
)}
|
||||
>
|
||||
${network.routers.map((router) => {
|
||||
const otbr =
|
||||
this._otbrInfo && this._otbrInfo[router.extended_address];
|
||||
const showDefaultRouter = !!network.dataset;
|
||||
const isDefaultRouter =
|
||||
showDefaultRouter &&
|
||||
router.extended_address ===
|
||||
network.dataset!.preferred_extended_address;
|
||||
const showOverflow = showDefaultRouter || otbr;
|
||||
return html`<ha-list-item-base
|
||||
class="router"
|
||||
.supportingText=${router.server ?? undefined}
|
||||
>
|
||||
<span slot="headline">
|
||||
${
|
||||
router.instance_name ||
|
||||
router.model_name ||
|
||||
router.server?.replace(".local.", "") ||
|
||||
""
|
||||
}
|
||||
${
|
||||
isDefaultRouter
|
||||
? html`<ha-svg-icon
|
||||
.path=${mdiCellphoneKey}
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.thread.default_router"
|
||||
)}
|
||||
></ha-svg-icon>`
|
||||
: nothing
|
||||
}
|
||||
</span>
|
||||
${
|
||||
router.brand
|
||||
? html`<img
|
||||
slot="start"
|
||||
.src=${brandsUrl(
|
||||
{
|
||||
domain: router.brand,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
},
|
||||
this.hass.auth.data.hassUrl
|
||||
)}
|
||||
alt=${router.brand}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
showOverflow
|
||||
? html`<ha-dropdown
|
||||
slot="end"
|
||||
.network=${network}
|
||||
.router=${router}
|
||||
.otbr=${otbr}
|
||||
@@ -334,10 +339,11 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
: ""
|
||||
}
|
||||
</ha-dropdown>`
|
||||
: ""
|
||||
}
|
||||
</ha-list-item>`;
|
||||
})}`
|
||||
: ""
|
||||
}
|
||||
</ha-list-item-base>`;
|
||||
})}
|
||||
</ha-list-base>`
|
||||
: html`<div class="card-content no-routers">
|
||||
<ha-svg-icon .path=${mdiDevices}></ha-svg-icon>
|
||||
${
|
||||
@@ -789,19 +795,11 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
|
||||
margin: 0 auto;
|
||||
direction: ltr;
|
||||
}
|
||||
ha-list-item.router {
|
||||
--mdc-list-item-meta-size: auto;
|
||||
--mdc-list-item-meta-display: flex;
|
||||
--mdc-list-side-padding: 16px;
|
||||
cursor: default;
|
||||
overflow: visible;
|
||||
}
|
||||
ha-list-item img {
|
||||
.router img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
ha-svg-icon[slot="meta"] {
|
||||
width: 24px;
|
||||
}
|
||||
ha-dropdown a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
+22
-21
@@ -7,11 +7,12 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import "../../../../../components/ha-markdown";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-base";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import {
|
||||
fetchZwaveNodeMetadata,
|
||||
@@ -148,24 +149,24 @@ class DialogZWaveJSRemoveNode extends LitElement {
|
||||
"ui.panel.config.zwave_js.remove_node.introduction"
|
||||
)}
|
||||
</p>
|
||||
<div class="menu-options">
|
||||
<ha-list-item hasMeta @click=${this._startExclusion}>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.remove_node.menu_exclude_device"
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
<ha-list-item hasMeta @click=${this._startRemoval}>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.remove_node.menu_remove_device"
|
||||
)}</span
|
||||
>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</div>
|
||||
<ha-list-base class="menu-options">
|
||||
<ha-list-item-button
|
||||
.headline=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.remove_node.menu_exclude_device"
|
||||
)}
|
||||
@click=${this._startExclusion}
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
.headline=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.remove_node.menu_remove_device"
|
||||
)}
|
||||
@click=${this._startRemoval}
|
||||
>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -434,8 +435,8 @@ class DialogZWaveJSRemoveNode extends LitElement {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
ha-list-item {
|
||||
--mdc-list-side-padding: var(--ha-space-2);
|
||||
ha-list-item-button {
|
||||
--ha-row-item-padding-inline: var(--ha-space-2);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
+19
-14
@@ -3,8 +3,8 @@ import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-list";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import "../../../../../components/item/ha-list-item-button";
|
||||
import "../../../../../components/list/ha-list-nav";
|
||||
import "../../../../../layouts/hass-loading-screen";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
@@ -68,20 +68,22 @@ class ZWaveJSConfigEntryPicker extends LitElement {
|
||||
"ui.panel.config.zwave_js.picker.title"
|
||||
)}
|
||||
>
|
||||
<ha-list>
|
||||
<ha-list-nav
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.picker.title"
|
||||
)}
|
||||
>
|
||||
${this._configEntries.map(
|
||||
(entry) => html`
|
||||
<a
|
||||
href="/config/zwave_js/dashboard?config_entry=${entry.entry_id}"
|
||||
<ha-list-item-button
|
||||
.href=${`/config/zwave_js/dashboard?config_entry=${entry.entry_id}`}
|
||||
.headline=${entry.title}
|
||||
>
|
||||
<ha-list-item hasMeta>
|
||||
<span>${entry.title}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
)}
|
||||
</ha-list>
|
||||
</ha-list-nav>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
@@ -127,9 +129,12 @@ class ZWaveJSConfigEntryPicker extends LitElement {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
ha-list-nav {
|
||||
--ha-list-padding: var(--ha-space-2) 0;
|
||||
}
|
||||
|
||||
ha-list-item-button ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -3547,6 +3547,14 @@
|
||||
"add_picture": "Add a picture",
|
||||
"assigned_to_area": "Assigned to this area",
|
||||
"targeting_area": "Targeting this area",
|
||||
"related_lists": {
|
||||
"automations_assigned": "Automations assigned to {area}",
|
||||
"automations_targeting": "Automations targeting {area}",
|
||||
"scenes_assigned": "Scenes assigned to {area}",
|
||||
"scenes_targeting": "Scenes targeting {area}",
|
||||
"scripts_assigned": "Scripts assigned to {area}",
|
||||
"scripts_targeting": "Scripts targeting {area}"
|
||||
},
|
||||
"area_not_found": "[%key:ui::card::area::area_not_found%]",
|
||||
"data_table": {
|
||||
"area": "Area",
|
||||
@@ -7478,6 +7486,7 @@
|
||||
"none": "This device has no entities",
|
||||
"show_less": "Show less",
|
||||
"disabled_entities": "+{count} disabled {count, plural,\n one {entity}\n other {entities}\n}",
|
||||
"disabled_entities_list": "Disabled entities",
|
||||
"hidden": "Hidden"
|
||||
},
|
||||
"confirm_disable_config_entry_title": "Disable config entry?",
|
||||
|
||||
Reference in new issue
Block a user