Update more-info dialog layout for weather entities (#22818)

* Update weather more-info style

* Cleanup unused var

* Use badges for attributes

* Remove unnecessary flex class

* CSS cleanup

* Wrap badges

* Revert "Cleanup unused var"

This reverts commit 89ab0f6ad05e1e669b84e69f4c263e3d302794f2.

* Revert badges for attributes

* Scroll long forecasts

* Use nothing instead of empty strings

* Cleanup
This commit is contained in:
Charles Garwood 2025-02-06 01:11:30 -05:00 committed by GitHub
parent 35face602b
commit e9fef1f873
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 346 additions and 134 deletions

View File

@ -33,6 +33,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
"switch", "switch",
"valve", "valve",
"water_heater", "water_heater",
"weather",
]; ];
/** Domains with full height more info dialog */ /** Domains with full height more info dialog */
export const DOMAINS_FULL_HEIGHT_MORE_INFO = ["update"]; export const DOMAINS_FULL_HEIGHT_MORE_INFO = ["update"];

View File

@ -1,18 +1,13 @@
import "@material/mwc-tab"; import "@material/mwc-tab";
import "@material/mwc-tab-bar"; import "@material/mwc-tab-bar";
import { import { mdiEye, mdiGauge, mdiWaterPercent, mdiWeatherWindy } from "@mdi/js";
mdiEye, import type { CSSResultGroup, PropertyValues } from "lit";
mdiGauge,
mdiThermometer,
mdiWaterPercent,
mdiWeatherWindy,
} from "@mdi/js";
import type { PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit"; import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { formatDateWeekdayDay } from "../../../common/datetime/format_date"; import { formatDateWeekdayShort } from "../../../common/datetime/format_date";
import { formatTimeWeekday } from "../../../common/datetime/format_time"; import { formatTime } from "../../../common/datetime/format_time";
import { formatNumber } from "../../../common/number/format_number";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import type { import type {
ForecastEvent, ForecastEvent,
@ -23,11 +18,16 @@ import {
getDefaultForecastType, getDefaultForecastType,
getForecast, getForecast,
getSupportedForecastTypes, getSupportedForecastTypes,
getSecondaryWeatherAttribute,
getWeatherStateIcon,
getWeatherUnit,
getWind, getWind,
subscribeForecast, subscribeForecast,
weatherIcons, weatherSVGStyles,
} from "../../../data/weather"; } from "../../../data/weather";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import "../../../components/ha-relative-time";
import "../../../components/ha-state-icon";
@customElement("more-info-weather") @customElement("more-info-weather")
class MoreInfoWeather extends LitElement { class MoreInfoWeather extends LitElement {
@ -137,23 +137,90 @@ class MoreInfoWeather extends LitElement {
const hourly = forecastData?.type === "hourly"; const hourly = forecastData?.type === "hourly";
const dayNight = forecastData?.type === "twice_daily"; const dayNight = forecastData?.type === "twice_daily";
const weatherStateIcon = getWeatherStateIcon(this.stateObj.state, this);
return html` return html`
${this._showValue(this.stateObj.attributes.temperature) <div class="content">
? html` <div class="icon-image">
<div class="flex"> ${weatherStateIcon ||
<ha-svg-icon .path=${mdiThermometer}></ha-svg-icon> html`
<div class="main"> <ha-state-icon
${this.hass.localize("ui.card.weather.attributes.temperature")} class="weather-icon"
</div> .stateObj=${this.stateObj}
<div> .hass=${this.hass}
${this.hass.formatEntityAttributeValue( ></ha-state-icon>
this.stateObj, `}
"temperature" </div>
)} <div class="info">
</div> <div class="name-state">
<div class="state">
${this.hass.formatEntityState(this.stateObj)}
</div> </div>
` <div class="time-ago">
: ""} <ha-relative-time
id="last_changed"
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
<simple-tooltip animation-delay="0" for="last_changed">
<div>
<div class="row">
<span class="column-name">
${this.hass.localize(
"ui.dialogs.more_info_control.last_changed"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.dialogs.more_info_control.last_updated"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
capitalize
></ha-relative-time>
</div>
</div>
</simple-tooltip>
</div>
</div>
<div class="temp-attribute">
<div class="temp">
${this.stateObj.attributes.temperature !== undefined &&
this.stateObj.attributes.temperature !== null
? html`
${formatNumber(
this.stateObj.attributes.temperature,
this.hass.locale
)}&nbsp;<span
>${getWeatherUnit(
this.hass.config,
this.stateObj,
"temperature"
)}</span
>
`
: nothing}
</div>
<div class="attribute">
${getSecondaryWeatherAttribute(
this.hass,
this.stateObj,
forecast!
)}
</div>
</div>
</div>
</div>
${this._showValue(this.stateObj.attributes.pressure) ${this._showValue(this.stateObj.attributes.pressure)
? html` ? html`
<div class="flex"> <div class="flex">
@ -169,7 +236,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${this._showValue(this.stateObj.attributes.humidity) ${this._showValue(this.stateObj.attributes.humidity)
? html` ? html`
<div class="flex"> <div class="flex">
@ -185,7 +252,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${this._showValue(this.stateObj.attributes.wind_speed) ${this._showValue(this.stateObj.attributes.wind_speed)
? html` ? html`
<div class="flex"> <div class="flex">
@ -203,7 +270,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${this._showValue(this.stateObj.attributes.visibility) ${this._showValue(this.stateObj.attributes.visibility)
? html` ? html`
<div class="flex"> <div class="flex">
@ -219,7 +286,7 @@ class MoreInfoWeather extends LitElement {
</div> </div>
</div> </div>
` `
: ""} : nothing}
${forecast ${forecast
? html` ? html`
<div class="section"> <div class="section">
@ -242,76 +309,90 @@ class MoreInfoWeather extends LitElement {
)} )}
</mwc-tab-bar>` </mwc-tab-bar>`
: nothing} : nothing}
${forecast.map((item) => <div class="forecast">
this._showValue(item.templow) || this._showValue(item.temperature) ${forecast.map((item) =>
? html`<div class="flex"> this._showValue(item.templow) ||
${item.condition this._showValue(item.temperature)
? html` ? html`
<ha-svg-icon <div>
.path=${weatherIcons[item.condition]} <div>
></ha-svg-icon> ${dayNight
` ? html`
: ""} ${formatDateWeekdayShort(
<div class="main"> new Date(item.datetime),
${dayNight this.hass!.locale,
? html` this.hass!.config
${formatDateWeekdayDay( )}
new Date(item.datetime), <div class="daynight">
this.hass!.locale, ${item.is_daytime !== false
this.hass!.config ? this.hass!.localize("ui.card.weather.day")
)} : this.hass!.localize(
(${item.is_daytime !== false "ui.card.weather.night"
? this.hass!.localize("ui.card.weather.day") )}<br />
: this.hass!.localize("ui.card.weather.night")}) </div>
` `
: hourly : hourly
? html`
${formatTime(
new Date(item.datetime),
this.hass!.locale,
this.hass!.config
)}
`
: html`
${formatDateWeekdayShort(
new Date(item.datetime),
this.hass!.locale,
this.hass!.config
)}
`}
</div>
${this._showValue(item.condition)
? html` ? html`
${formatTimeWeekday( <div class="forecast-image-icon">
new Date(item.datetime), ${getWeatherStateIcon(
this.hass!.locale, item.condition!,
this.hass!.config this,
)} !(
item.is_daytime ||
item.is_daytime === undefined
)
)}
</div>
` `
: html` : nothing}
${formatDateWeekdayDay( <div class="temp">
new Date(item.datetime), ${this._showValue(item.temperature)
this.hass!.locale, ? html`${formatNumber(
this.hass!.config item.temperature,
)} this.hass!.locale
`} )}°`
</div> : "—"}
<div class="templow"> </div>
${this._showValue(item.templow) <div class="templow">
? this.hass.formatEntityAttributeValue( ${this._showValue(item.templow)
this.stateObj!, ? html`${formatNumber(
"templow", item.templow!,
item.templow this.hass!.locale
) )}°`
: hourly : hourly
? "" ? nothing
: "—"} : "—"}
</div> </div>
<div class="temp"> </div>
${this._showValue(item.temperature) `
? this.hass.formatEntityAttributeValue( : nothing
this.stateObj!, )}
"temperature", </div>
item.temperature
)
: "—"}
</div>
</div>`
: ""
)}
` `
: ""} : nothing}
${this.stateObj.attributes.attribution ${this.stateObj.attributes.attribution
? html` ? html`
<div class="attribution"> <div class="attribution">
${this.stateObj.attributes.attribution} ${this.stateObj.attributes.attribution}
</div> </div>
` `
: ""} : nothing}
`; `;
} }
@ -321,56 +402,186 @@ class MoreInfoWeather extends LitElement {
]; ];
} }
static styles = css` static get styles(): CSSResultGroup {
ha-svg-icon { return [
color: var(--paper-item-icon-color); weatherSVGStyles,
margin-left: 8px; css`
margin-inline-start: 8px; ha-svg-icon {
margin-inline-end: initial; color: var(--paper-item-icon-color);
} margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
}
mwc-tab-bar { mwc-tab-bar {
margin-bottom: 4px; margin-bottom: 4px;
} }
.section { .section {
margin: 16px 0 8px 0; margin: 16px 0 8px 0;
font-size: 1.2em; font-size: 1.2em;
} }
.flex { .flex {
display: flex; display: flex;
height: 32px; height: 32px;
align-items: center; align-items: center;
} }
.flex > div:last-child { .flex > div:last-child {
direction: ltr; direction: ltr;
} }
.main { .main {
flex: 1; flex: 1;
margin-left: 24px; margin-left: 24px;
margin-inline-start: 24px; margin-inline-start: 24px;
margin-inline-end: initial; margin-inline-end: initial;
} }
.temp, .attribution {
.templow { text-align: center;
min-width: 48px; margin-top: 16px;
text-align: right; }
direction: ltr;
}
.templow { .time-ago,
margin: 0 16px; .attribute {
color: var(--secondary-text-color); white-space: nowrap;
} overflow: hidden;
text-overflow: ellipsis;
}
.attribution { .attribution,
color: var(--secondary-text-color); .templow,
text-align: center; .daynight,
} .attribute,
`; .time-ago {
color: var(--secondary-text-color);
}
.content {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.icon-image {
display: flex;
align-items: center;
min-width: 64px;
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
}
.icon-image > * {
flex: 0 0 64px;
height: 64px;
}
.weather-icon {
--mdc-icon-size: 64px;
}
.info {
display: flex;
justify-content: space-between;
flex-grow: 1;
overflow: hidden;
}
.temp-attribute {
text-align: var(--float-end);
}
.temp-attribute .temp {
position: relative;
margin-right: 24px;
direction: ltr;
}
.temp-attribute .temp span {
position: absolute;
font-size: 24px;
top: 1px;
}
.state,
.temp-attribute .temp {
font-size: 28px;
line-height: 1.2;
}
.attribute {
font-size: 14px;
line-height: 1;
}
.name-state {
overflow: hidden;
padding-right: 12px;
padding-inline-end: 12px;
padding-inline-start: initial;
width: 100%;
}
.state {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.forecast {
display: flex;
justify-content: space-around;
padding: 16px;
padding-bottom: 0px;
overflow-x: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
mask-image: linear-gradient(
90deg,
transparent 0%,
black 5%,
black 94%,
transparent 100%
);
}
.forecast > div {
text-align: center;
padding: 0 10px;
}
.forecast .icon,
.forecast .temp {
margin: 4px 0;
}
.forecast .temp {
font-size: 16px;
}
.forecast-image-icon {
padding-top: 4px;
padding-bottom: 4px;
display: flex;
justify-content: center;
}
.forecast-image-icon > * {
width: 40px;
height: 40px;
--mdc-icon-size: 40px;
}
.forecast-icon {
--mdc-icon-size: 40px;
}
`,
];
}
private _showValue(item: number | string | undefined): boolean { private _showValue(item: number | string | undefined): boolean {
return typeof item !== "undefined" && item !== null; return typeof item !== "undefined" && item !== null;