diff --git a/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts b/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts
new file mode 100644
index 000000000000..95be20ac85f8
--- /dev/null
+++ b/src/dialogs/more-info/components/climate/ha-more-info-climate-humidity.ts
@@ -0,0 +1,331 @@
+import { mdiMinus, mdiPlus } from "@mdi/js";
+import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { styleMap } from "lit/directives/style-map";
+import { stateActive } from "../../../../common/entity/state_active";
+import { domainStateColorProperties } from "../../../../common/entity/state_color";
+import { supportsFeature } from "../../../../common/entity/supports-feature";
+import { clamp } from "../../../../common/number/clamp";
+import { formatNumber } from "../../../../common/number/format_number";
+import { blankBeforePercent } from "../../../../common/translations/blank_before_percent";
+import { debounce } from "../../../../common/util/debounce";
+import "../../../../components/ha-control-circular-slider";
+import "../../../../components/ha-outlined-icon-button";
+import "../../../../components/ha-svg-icon";
+import { ClimateEntity, ClimateEntityFeature } from "../../../../data/climate";
+import { UNAVAILABLE } from "../../../../data/entity";
+import { computeCssVariable } from "../../../../resources/css-variables";
+import { HomeAssistant } from "../../../../types";
+
+@customElement("ha-more-info-climate-humidity")
+export class HaMoreInfoClimateHumidity extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ attribute: false }) public stateObj!: ClimateEntity;
+
+ @state() private _targetHumidity?: number;
+
+ protected willUpdate(changedProp: PropertyValues): void {
+ super.willUpdate(changedProp);
+ if (changedProp.has("stateObj")) {
+ this._targetHumidity = this.stateObj.attributes.humidity;
+ }
+ }
+
+ private get _step() {
+ return 1;
+ }
+
+ private get _min() {
+ return this.stateObj.attributes.min_humidity ?? 0;
+ }
+
+ private get _max() {
+ return this.stateObj.attributes.max_humidity ?? 100;
+ }
+
+ private _valueChanged(ev: CustomEvent) {
+ const value = (ev.detail as any).value;
+ if (isNaN(value)) return;
+ this._targetHumidity = value;
+ this._callService();
+ }
+
+ private _valueChanging(ev: CustomEvent) {
+ const value = (ev.detail as any).value;
+ if (isNaN(value)) return;
+ this._targetHumidity = value;
+ }
+
+ private _debouncedCallService = debounce(() => this._callService(), 1000);
+
+ private _callService() {
+ this.hass.callService("climate", "set_humidity", {
+ entity_id: this.stateObj!.entity_id,
+ humidity: this._targetHumidity,
+ });
+ }
+
+ private _handleButton(ev) {
+ const step = ev.currentTarget.step as number;
+
+ let humidity = this._targetHumidity ?? this._min;
+ humidity += step;
+ humidity = clamp(humidity, this._min, this._max);
+
+ this._targetHumidity = humidity;
+ this._debouncedCallService();
+ }
+
+ private _renderLabel() {
+ return html`
+
+ ${this.hass.localize(
+ "ui.dialogs.more_info_control.climate.humidity_target"
+ )}
+
+ `;
+ }
+
+ private _renderButtons() {
+ return html`
+
+
+
+
+
+
+
+
+ `;
+ }
+
+ private _renderTarget(humidity: number) {
+ const formatted = formatNumber(humidity, this.hass.locale, {
+ maximumFractionDigits: 0,
+ });
+
+ return html`
+
+
+ ${formatted}%
+
+
+ ${formatted}${blankBeforePercent(this.hass.locale)}%
+
+
+ `;
+ }
+
+ protected render() {
+ const supportsTargetHumidity = supportsFeature(
+ this.stateObj,
+ ClimateEntityFeature.TARGET_HUMIDITY
+ );
+ const active = stateActive(this.stateObj);
+
+ // Use humidifier state color
+ const mainColor = computeCssVariable(
+ domainStateColorProperties(
+ "humidifier",
+ this.stateObj,
+ active ? "on" : "off"
+ )
+ );
+
+ const targetHumidity = this._targetHumidity;
+ const currentHumidity = this.stateObj.attributes.current_humidity;
+
+ if (supportsTargetHumidity && targetHumidity != null) {
+ return html`
+
+
+
+
+
${this._renderLabel()}
+
+ ${this._renderTarget(targetHumidity)}
+
+
+ ${this._renderButtons()}
+
+ `;
+ }
+
+ return html`
+
+
+
+
+ `;
+ }
+
+ static get styles(): CSSResultGroup {
+ return css`
+ /* Layout */
+ .container {
+ position: relative;
+ }
+ .info {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+ font-size: 16px;
+ line-height: 24px;
+ letter-spacing: 0.1px;
+ }
+ .info * {
+ margin: 0;
+ pointer-events: auto;
+ }
+ /* Elements */
+ .target-container {
+ margin-bottom: 30px;
+ }
+ .target .value {
+ font-size: 56px;
+ line-height: 1;
+ letter-spacing: -0.25px;
+ }
+ .target .value .unit {
+ font-size: 0.4em;
+ line-height: 1;
+ margin-left: 2px;
+ }
+ .action-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 200px;
+ height: 48px;
+ margin-bottom: 6px;
+ }
+ .action {
+ font-weight: 500;
+ text-align: center;
+ color: var(--action-color, inherit);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+ .dual {
+ display: flex;
+ flex-direction: row;
+ gap: 24px;
+ margin-bottom: 40px;
+ }
+
+ .dual button {
+ outline: none;
+ background: none;
+ color: inherit;
+ font-family: inherit;
+ -webkit-tap-highlight-color: transparent;
+ border: none;
+ opacity: 0.5;
+ padding: 0;
+ transition:
+ opacity 180ms ease-in-out,
+ transform 180ms ease-in-out;
+ cursor: pointer;
+ }
+ .dual button:focus-visible {
+ transform: scale(1.1);
+ }
+ .dual button.selected {
+ opacity: 1;
+ }
+ .buttons {
+ position: absolute;
+ bottom: 10px;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ width: 120px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ }
+ .buttons ha-outlined-icon-button {
+ --md-outlined-icon-button-container-size: 48px;
+ --md-outlined-icon-button-icon-size: 24px;
+ }
+ /* Accessibility */
+ .visually-hidden {
+ position: absolute;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ width: 1px;
+ margin: -1px;
+ padding: 0;
+ border: 0;
+ }
+ /* Slider */
+ ha-control-circular-slider {
+ --control-circular-slider-color: var(
+ --main-color,
+ var(--disabled-color)
+ );
+ }
+ ha-control-circular-slider::after {
+ display: block;
+ content: "";
+ position: absolute;
+ top: -10%;
+ left: -10%;
+ right: -10%;
+ bottom: -10%;
+ background: radial-gradient(
+ 50% 50% at 50% 50%,
+ var(--action-color, transparent) 0%,
+ transparent 100%
+ );
+ opacity: 0.15;
+ pointer-events: none;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-more-info-climate-humidity": HaMoreInfoClimateHumidity;
+ }
+}
diff --git a/src/dialogs/more-info/controls/more-info-climate.ts b/src/dialogs/more-info/controls/more-info-climate.ts
index 962bb6e2875d..4382a3a6eaa1 100644
--- a/src/dialogs/more-info/controls/more-info-climate.ts
+++ b/src/dialogs/more-info/controls/more-info-climate.ts
@@ -1,4 +1,5 @@
import "@material/mwc-list/mwc-list-item";
+import { mdiThermometer, mdiWaterPercent } from "@mdi/js";
import {
CSSResultGroup,
LitElement,
@@ -7,7 +8,7 @@ import {
html,
nothing,
} from "lit";
-import { property } from "lit/decorators";
+import { property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
@@ -19,24 +20,30 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa
import { supportsFeature } from "../../../common/entity/supports-feature";
import { formatNumber } from "../../../common/number/format_number";
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
-import { computeRTLDirection } from "../../../common/util/compute_rtl";
+import "../../../components/ha-icon-button-group";
+import "../../../components/ha-icon-button-toggle";
import "../../../components/ha-select";
-import "../../../components/ha-slider";
import "../../../components/ha-switch";
import {
ClimateEntity,
ClimateEntityFeature,
compareClimateHvacModes,
} from "../../../data/climate";
+import { UNAVAILABLE } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
+import "../components/climate/ha-more-info-climate-humidity";
import "../components/climate/ha-more-info-climate-temperature";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
+type MainControl = "temperature" | "humidity";
+
class MoreInfoClimate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj?: ClimateEntity;
+ @state() private _mainControl: MainControl = "temperature";
+
private _resizeDebounce?: number;
protected render() {
@@ -79,56 +86,92 @@ class MoreInfoClimate extends LitElement {
const currentTemperature = this.stateObj.attributes.current_temperature;
const currentHumidity = this.stateObj.attributes.current_humidity;
- const rtlDirection = computeRTLDirection(hass);
-
return html`
- ${currentTemperature || currentHumidity
- ? html`
- ${currentTemperature != null
- ? html`
-
-
- ${computeAttributeNameDisplay(
- this.hass.localize,
- this.stateObj,
- this.hass.entities,
- "current_temperature"
- )}
-
-
- ${formatNumber(currentTemperature, this.hass.locale)}
- ${this.hass.config.unit_system.temperature}
-
-
- `
- : nothing}
- ${currentHumidity != null
- ? html`
-
-
- ${computeAttributeNameDisplay(
- this.hass.localize,
- this.stateObj,
- this.hass.entities,
- "current_humidity"
- )}
-
-
- ${formatNumber(
- currentHumidity,
- this.hass.locale
- )}${blankBeforePercent(this.hass.locale)}%
-
-
- `
- : nothing}
-
`
- : nothing}
+
+ ${currentTemperature != null
+ ? html`
+
+
+ ${computeAttributeNameDisplay(
+ this.hass.localize,
+ this.stateObj,
+ this.hass.entities,
+ "current_temperature"
+ )}
+
+
+ ${formatNumber(currentTemperature, this.hass.locale)}
+ ${this.hass.config.unit_system.temperature}
+
+
+ `
+ : nothing}
+ ${currentHumidity != null
+ ? html`
+
+
+ ${computeAttributeNameDisplay(
+ this.hass.localize,
+ this.stateObj,
+ this.hass.entities,
+ "current_humidity"
+ )}
+
+
+ ${formatNumber(
+ currentHumidity,
+ this.hass.locale
+ )}${blankBeforePercent(this.hass.locale)}%
+
+
+ `
+ : nothing}
+
-
+ ${this._mainControl === "temperature"
+ ? html`
+
+ `
+ : nothing}
+ ${this._mainControl === "humidity"
+ ? html`
+
+ `
+ : nothing}
+ ${supportTargetHumidity
+ ? html`
+
+
+
+
+
+
+
+
+ `
+ : nothing}
- ${supportTargetHumidity
- ? html`
-
-
- ${computeAttributeNameDisplay(
- hass.localize,
- stateObj,
- hass.entities,
- "humidity"
- )}
-
-
-
- ${stateObj.attributes.humidity} %
-
-
-
-
-
- `
- : ""}
-