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"]),
+ })
+ ),
+});