mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Add badges for tile card (#14231)
This commit is contained in:
parent
ebc0edac10
commit
9778c0731c
51
src/components/tile/ha-tile-badge.ts
Normal file
51
src/components/tile/ha-tile-badge.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../ha-icon";
|
||||
|
||||
@customElement("ha-tile-badge")
|
||||
export class HaTileBadge extends LitElement {
|
||||
@property() public iconPath?: string;
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="badge">
|
||||
${this.icon
|
||||
? html`<ha-icon .icon=${this.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon .path=${this.iconPath}></ha-svg-icon>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
--tile-badge-background-color: rgb(var(--rgb-primary-color));
|
||||
--tile-badge-icon-color: rgb(var(--rgb-white-color));
|
||||
--mdc-icon-size: 12px;
|
||||
}
|
||||
.badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--tile-badge-background-color);
|
||||
transition: background-color 280ms ease-in-out;
|
||||
}
|
||||
.badge ha-icon,
|
||||
.badge ha-svg-icon {
|
||||
color: var(--tile-badge-icon-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-tile-badge": HaTileBadge;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateIconPath } from "../../../common/entity/state_icon_path";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/tile/ha-tile-badge";
|
||||
import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-image";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
@ -21,6 +22,7 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { computeTileBadge } from "./tile/badges/tile-badge";
|
||||
import { ThermostatCardConfig, TileCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-tile-card")
|
||||
@ -116,7 +118,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
return html`
|
||||
<ha-card class="disabled">
|
||||
<div class="tile">
|
||||
<ha-tile-icon .iconPath=${mdiHelp}></ha-tile-icon>
|
||||
<div class="icon-container">
|
||||
<ha-tile-icon .iconPath=${mdiHelp}></ha-tile-icon>
|
||||
</div>
|
||||
<ha-tile-info
|
||||
.primary=${entityId}
|
||||
secondary=${this.hass.localize("ui.card.tile.not_found")}
|
||||
@ -147,32 +151,45 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
const imageUrl = this._config.show_entity_picture
|
||||
? this._getImageUrl(entity)
|
||||
: undefined;
|
||||
const badge = computeTileBadge(entity, this.hass);
|
||||
|
||||
return html`
|
||||
<ha-card style=${styleMap(style)}>
|
||||
<div class="tile">
|
||||
${imageUrl
|
||||
? html`
|
||||
<ha-tile-image
|
||||
class="icon"
|
||||
.imageUrl=${imageUrl}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@action=${this._handleIconAction}
|
||||
.actionHandler=${actionHandler()}
|
||||
></ha-tile-image>
|
||||
`
|
||||
: html`
|
||||
<ha-tile-icon
|
||||
class="icon"
|
||||
.icon=${icon}
|
||||
.iconPath=${iconPath}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@action=${this._handleIconAction}
|
||||
.actionHandler=${actionHandler()}
|
||||
></ha-tile-icon>
|
||||
`}
|
||||
<div
|
||||
class="icon-container"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@action=${this._handleIconAction}
|
||||
.actionHandler=${actionHandler()}
|
||||
>
|
||||
${imageUrl
|
||||
? html`
|
||||
<ha-tile-image
|
||||
class="icon"
|
||||
.imageUrl=${imageUrl}
|
||||
></ha-tile-image>
|
||||
`
|
||||
: html`
|
||||
<ha-tile-icon
|
||||
class="icon"
|
||||
.icon=${icon}
|
||||
.iconPath=${iconPath}
|
||||
></ha-tile-icon>
|
||||
`}
|
||||
${badge
|
||||
? html`
|
||||
<ha-tile-badge
|
||||
class="badge"
|
||||
.icon=${badge.icon}
|
||||
.iconPath=${badge.iconPath}
|
||||
style=${styleMap({
|
||||
"--tile-badge-background-color": `rgb(${badge.color})`,
|
||||
})}
|
||||
></ha-tile-badge>
|
||||
`
|
||||
: null}
|
||||
</div>
|
||||
<ha-tile-info
|
||||
.primary=${name}
|
||||
.secondary=${stateDisplay}
|
||||
@ -198,32 +215,39 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
ha-card.disabled {
|
||||
background: rgba(var(--rgb-disabled-color), 0.1);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
.tile {
|
||||
padding: calc(12px - var(--tile-tap-padding));
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.icon {
|
||||
.icon-container {
|
||||
position: relative;
|
||||
padding: var(--tile-tap-padding);
|
||||
flex: none;
|
||||
margin-right: calc(12px - 2 * var(--tile-tap-padding));
|
||||
margin-inline-end: calc(12px - 2 * var(--tile-tap-padding));
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
--color: var(--tile-color);
|
||||
transition: transform 180ms ease-in-out;
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
.icon-container .icon {
|
||||
--icon-color: rgb(var(--tile-color));
|
||||
--shape-color: rgba(var(--tile-color), 0.2);
|
||||
}
|
||||
.icon[role="button"]:focus {
|
||||
outline: none;
|
||||
.icon-container .badge {
|
||||
position: absolute;
|
||||
top: calc(-3px + var(--tile-tap-padding));
|
||||
right: calc(-3px + var(--tile-tap-padding));
|
||||
}
|
||||
.icon[role="button"]:focus-visible {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
.icon[role="button"]:active {
|
||||
.icon-container[role="button"]:focus-visible,
|
||||
.icon-container[role="button"]:active {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
ha-tile-info {
|
||||
@ -234,9 +258,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
border-radius: calc(var(--ha-card-border-radius, 10px) - 2px);
|
||||
transition: background-color 180ms ease-in-out;
|
||||
}
|
||||
ha-tile-info:focus {
|
||||
outline: none;
|
||||
}
|
||||
ha-tile-info:focus-visible {
|
||||
background-color: rgba(var(--tile-color), 0.1);
|
||||
}
|
||||
|
38
src/panels/lovelace/cards/tile/badges/tile-badge-climate.ts
Normal file
38
src/panels/lovelace/cards/tile/badges/tile-badge-climate.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import {
|
||||
mdiClockOutline,
|
||||
mdiFire,
|
||||
mdiPower,
|
||||
mdiSnowflake,
|
||||
mdiWaterPercent,
|
||||
} from "@mdi/js";
|
||||
import { ClimateEntity, HvacAction } from "../../../../../data/climate";
|
||||
import { ComputeBadgeFunction } from "./tile-badge";
|
||||
|
||||
export const CLIMATE_HVAC_ACTION_COLORS: Record<HvacAction, string> = {
|
||||
cooling: "var(--rgb-state-climate-cool-color)",
|
||||
drying: "var(--rgb-state-climate-dry-color)",
|
||||
heating: "var(--rgb-state-climate-heat-color)",
|
||||
idle: "var(--rgb-state-climate-idle-color)",
|
||||
off: "var(--rgb-state-climate-off-color)",
|
||||
};
|
||||
|
||||
export const CLIMATE_HVAC_ACTION_ICONS: Record<HvacAction, string> = {
|
||||
cooling: mdiSnowflake,
|
||||
drying: mdiWaterPercent,
|
||||
heating: mdiFire,
|
||||
idle: mdiClockOutline,
|
||||
off: mdiPower,
|
||||
};
|
||||
|
||||
export const computeClimateBadge: ComputeBadgeFunction = (stateObj) => {
|
||||
const hvacAction = (stateObj as ClimateEntity).attributes.hvac_action;
|
||||
|
||||
if (!hvacAction || hvacAction === "off") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
iconPath: CLIMATE_HVAC_ACTION_ICONS[hvacAction],
|
||||
color: CLIMATE_HVAC_ACTION_COLORS[hvacAction],
|
||||
};
|
||||
};
|
44
src/panels/lovelace/cards/tile/badges/tile-badge-person.ts
Normal file
44
src/panels/lovelace/cards/tile/badges/tile-badge-person.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { mdiHelp, mdiHome, mdiHomeExportOutline } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE_STATES } from "../../../../../data/entity";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ComputeBadgeFunction } from "./tile-badge";
|
||||
|
||||
function getZone(entity: HassEntity, hass: HomeAssistant) {
|
||||
const state = entity.state;
|
||||
if (state === "home" || state === "not_home") return undefined;
|
||||
|
||||
const zones = Object.values(hass.states).filter((stateObj) =>
|
||||
stateObj.entity_id.startsWith("zone.")
|
||||
);
|
||||
|
||||
return zones.find((z) => state === z.attributes.friendly_name);
|
||||
}
|
||||
|
||||
function personBadgeIcon(entity: HassEntity) {
|
||||
const state = entity.state;
|
||||
if (UNAVAILABLE_STATES.includes(state)) {
|
||||
return mdiHelp;
|
||||
}
|
||||
return state === "not_home" ? mdiHomeExportOutline : mdiHome;
|
||||
}
|
||||
|
||||
function personBadgeColor(entity: HassEntity, inZone?: boolean) {
|
||||
if (inZone) {
|
||||
return "var(--rgb-state-person-zone-color)";
|
||||
}
|
||||
const state = entity.state;
|
||||
return state === "not_home"
|
||||
? "var(--rgb-state-person-not-home-color)"
|
||||
: "var(--rgb-state-person-home-color)";
|
||||
}
|
||||
|
||||
export const computePersonBadge: ComputeBadgeFunction = (stateObj, hass) => {
|
||||
const zone = getZone(stateObj, hass);
|
||||
|
||||
return {
|
||||
iconPath: personBadgeIcon(stateObj),
|
||||
icon: zone?.attributes.icon,
|
||||
color: personBadgeColor(stateObj, Boolean(zone)),
|
||||
};
|
||||
};
|
29
src/panels/lovelace/cards/tile/badges/tile-badge.ts
Normal file
29
src/panels/lovelace/cards/tile/badges/tile-badge.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { computeClimateBadge } from "./tile-badge-climate";
|
||||
import { computePersonBadge } from "./tile-badge-person";
|
||||
|
||||
export type TileBadge = {
|
||||
color: string;
|
||||
icon?: string;
|
||||
iconPath?: string;
|
||||
};
|
||||
|
||||
export type ComputeBadgeFunction = (
|
||||
stateObj: HassEntity,
|
||||
hass: HomeAssistant
|
||||
) => TileBadge | undefined;
|
||||
|
||||
export const computeTileBadge: ComputeBadgeFunction = (stateObj, hass) => {
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
switch (domain) {
|
||||
case "person":
|
||||
case "device_tracker":
|
||||
return computePersonBadge(stateObj, hass);
|
||||
case "climate":
|
||||
return computeClimateBadge(stateObj, hass);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
@ -152,6 +152,9 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-state-lock-unlocked-color: var(--rgb-red-color);
|
||||
--rgb-state-media-player-color: var(--rgb-indigo-color);
|
||||
--rgb-state-person-color: var(--rgb-blue-grey-color);
|
||||
--rgb-state-person-home-color: var(--rgb-green-color);
|
||||
--rgb-state-person-not-home-color: var(--rgb-red-color);
|
||||
--rgb-state-person-zone-color: var(--rgb-blue-color);
|
||||
--rgb-state-sensor-battery-high-color: var(--rgb-green-color);
|
||||
--rgb-state-sensor-battery-low-color: var(--rgb-red-color);
|
||||
--rgb-state-sensor-battery-medium-color: var(--rgb-orange-color);
|
||||
@ -173,6 +176,7 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-state-climate-fan-only-color: var(--rgb-teal-color);
|
||||
--rgb-state-climate-heat-color: var(--rgb-deep-orange-color);
|
||||
--rgb-state-climate-heat-cool-color: var(--rgb-green-color);
|
||||
--rgb-state-climate-idle-color: var(--rgb-disabled-color);
|
||||
|
||||
/* input components */
|
||||
--input-idle-line-color: rgba(0, 0, 0, 0.42);
|
||||
|
Loading…
x
Reference in New Issue
Block a user