diff --git a/src/panels/lovelace/card-features/hui-card-features.ts b/src/panels/lovelace/card-features/hui-card-features.ts index 101561c78d..50f3251725 100644 --- a/src/panels/lovelace/card-features/hui-card-features.ts +++ b/src/panels/lovelace/card-features/hui-card-features.ts @@ -10,6 +10,7 @@ import { import { customElement, property } from "lit/decorators"; import { HomeAssistant } from "../../../types"; import type { HuiErrorCard } from "../cards/hui-error-card"; +import { LovelaceCardFeatureLayout } from "../cards/types"; import { createCardFeatureElement } from "../create-element/create-card-feature-element"; import type { LovelaceCardFeature } from "../types"; import type { LovelaceCardFeatureConfig } from "./types"; @@ -22,6 +23,8 @@ export class HuiCardFeatures extends LitElement { @property({ attribute: false }) public features?: LovelaceCardFeatureConfig[]; + @property({ attribute: false }) public layout?: LovelaceCardFeatureLayout; + @property({ attribute: false }) public color?: string; private _featuresElements = new WeakMap< @@ -58,10 +61,15 @@ export class HuiCardFeatures extends LitElement { if (!this.features) { return nothing; } + + const containerClass = this.layout?.type ? ` ${this.layout.type}` : ""; + return html` - ${this.features.map((featureConf) => - this.renderFeature(featureConf, this.stateObj) - )} +
+ ${this.features.map((featureConf) => + this.renderFeature(featureConf, this.stateObj) + )} +
`; } @@ -69,8 +77,21 @@ export class HuiCardFeatures extends LitElement { return css` :host { --feature-color: var(--state-icon-color); + --feature-padding: 12px; + } + .container { display: flex; flex-direction: column; + padding: var(--feature-padding); + padding-top: 0px; + gap: var(--feature-padding); + } + .container.horizontal { + display: flex; + flex-direction: row; + } + .container.horizontal > * { + flex: 1; } `; } diff --git a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts index 3eab79b85e..16db6acb5c 100644 --- a/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-hvac-modes-card-feature.ts @@ -208,7 +208,6 @@ class HuiClimateHvacModesCardFeature --control-select-button-border-radius: 10px; } .container { - padding: 0 12px 12px 12px; width: auto; } `; diff --git a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts index 5cd1e233e5..b81915af3f 100644 --- a/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-climate-preset-modes-card-feature.ts @@ -216,7 +216,6 @@ class HuiClimatePresetModesCardFeature --control-select-button-border-radius: 10px; } .container { - padding: 0 12px 12px 12px; width: auto; } `; diff --git a/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts b/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts index 7eeeb3d3bd..01217a3e4a 100644 --- a/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-light-brightness-card-feature.ts @@ -94,7 +94,6 @@ class HuiLightBrightnessCardFeature --control-slider-border-radius: 10px; } .container { - padding: 0 12px 12px 12px; width: auto; } `; diff --git a/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts b/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts index fa7ed6b9d9..400e728eda 100644 --- a/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-light-color-temp-card-feature.ts @@ -120,7 +120,6 @@ class HuiLightColorTempCardFeature --control-slider-border-radius: 10px; } .container { - padding: 0 12px 12px 12px; width: auto; } `; diff --git a/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts b/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts index 638eeab585..1296e01f42 100644 --- a/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-target-temperature-card-feature.ts @@ -285,7 +285,6 @@ class HuiTargetTemperatureCardFeature static get styles() { return css` ha-control-button-group { - margin: 0 12px 12px 12px; --control-button-group-spacing: 12px; } `; diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 634b52ca14..69d94ac11a 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -428,6 +428,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { .stateObj=${stateObj} .color=${this._config.color} .features=${this._config.features} + .layout=${this._config.feature_layout} > ` : nothing} diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 95efb79366..d8f568e34e 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -24,6 +24,10 @@ export type AlarmPanelCardConfigState = | "arm_vacation" | "arm_custom_bypass"; +export type LovelaceCardFeatureLayout = { + type?: "vertical" | "horizontal" | "compact"; +}; + export interface AlarmPanelCardConfig extends LovelaceCardConfig { entity: string; name?: string; @@ -506,4 +510,5 @@ export interface TileCardConfig extends LovelaceCardConfig { double_tap_action?: ActionConfig; icon_tap_action?: ActionConfig; features?: LovelaceCardFeatureConfig[]; + feature_layout?: LovelaceCardFeatureLayout; } diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index 288a6976b8..e6869a3036 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -3,10 +3,16 @@ import { HassEntity } from "home-assistant-js-websocket"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../common/dom/stop_propagation"; +import { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-button"; +import { + HaFormSchema, + SchemaUnion, +} from "../../../../components/ha-form/types"; import "../../../../components/ha-icon-button"; import "../../../../components/ha-list-item"; import "../../../../components/ha-sortable"; @@ -45,7 +51,9 @@ import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuum-commands-card-feature"; import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature"; import { LovelaceCardFeatureConfig } from "../../card-features/types"; +import { LovelaceCardFeatureLayout } from "../../cards/types"; import { getCardFeatureElementClass } from "../../create-element/create-card-feature-element"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; export type FeatureType = LovelaceCardFeatureConfig["type"]; type SupportsFeature = (stateObj: HassEntity) => boolean; @@ -142,6 +150,9 @@ declare global { "features-changed": { features: LovelaceCardFeatureConfig[]; }; + "layout-changed": { + layout: LovelaceCardFeatureLayout; + }; } } @@ -154,6 +165,9 @@ export class HuiCardFeaturesEditor extends LitElement { @property({ attribute: false }) public features?: LovelaceCardFeatureConfig[]; + @property({ attribute: false }) + public layout?: LovelaceCardFeatureLayout; + @property({ attribute: false }) public featuresTypes?: FeatureType[]; @@ -162,6 +176,37 @@ export class HuiCardFeaturesEditor extends LitElement { private _featuresKeys = new WeakMap(); + private _optionsSchema = memoizeOne( + (_localize: LocalizeFunc) => + [ + { + name: "type", + selector: { + select: { + mode: "dropdown", + options: ["vertical", "horizontal", "compact"].map((layout) => ({ + label: capitalizeFirstLetter(layout), + value: layout, + })), + }, + }, + }, + ] as const satisfies readonly HaFormSchema[] + ); + + private _computeLabelCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "type": + return "Layout"; + default: + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + } + }; +) private _supportsFeatureType(type: string): boolean { if (!this.stateObj) return false; @@ -235,6 +280,14 @@ export class HuiCardFeaturesEditor extends LitElement { isCustomType(type) ); + const schema = this._optionsSchema(this.hass.localize); + + const data = { ...this.layout }; + + if (!data.type) { + data.type = "vertical"; + } + return html`

@@ -251,6 +304,17 @@ export class HuiCardFeaturesEditor extends LitElement { ` : nothing} + ${supportedFeaturesType.length > 0 + ? html` + + ` + : nothing} `; @@ -351,6 +356,21 @@ export class HuiTileCardEditor fireEvent(this, "config-changed", { config }); } + private _layoutChanged(ev: CustomEvent) { + ev.stopPropagation(); + if (!this._config || !this.hass) { + return; + } + + const layout = ev.detail.layout as LovelaceCardFeatureLayout; + const config: TileCardConfig = { + ...this._config, + feature_layout: layout, + }; + + fireEvent(this, "config-changed", { config }); + } + private subElementChanged(ev: CustomEvent): void { ev.stopPropagation(); if (!this._config || !this.hass) { diff --git a/src/panels/lovelace/editor/structs/card-feature-struct.ts b/src/panels/lovelace/editor/structs/card-feature-struct.ts new file mode 100644 index 0000000000..e7c7fe59cc --- /dev/null +++ b/src/panels/lovelace/editor/structs/card-feature-struct.ts @@ -0,0 +1,10 @@ +import { any, array, enums, object, optional } from "superstruct"; + +export const cardFeatureConfig = object({ + features: optional(array(any())), + feature_layout: optional( + object({ + type: enums(["vertical", "horizontal", "compact"]), + }) + ), +});