diff --git a/src/panels/lovelace/card-features/hui-card-features.ts b/src/panels/lovelace/card-features/hui-card-features.ts index 49e578a697..7253bb7895 100644 --- a/src/panels/lovelace/card-features/hui-card-features.ts +++ b/src/panels/lovelace/card-features/hui-card-features.ts @@ -20,40 +20,31 @@ export class HuiCardFeatures extends LitElement { return nothing; } return html` -
- ${this.features.map( - (feature) => html` - - ` - )} -
+ ${this.features.map( + (feature) => html` + + ` + )} `; } static styles = css` :host { --feature-color: var(--state-icon-color); - --feature-padding: 12px; --feature-height: 42px; --feature-border-radius: 12px; --feature-button-spacing: 12px; position: relative; width: 100%; - } - .container { - position: relative; display: flex; flex-direction: column; - padding: var(--feature-padding); - padding-top: 0px; - gap: var(--feature-padding); + gap: 12px; width: 100%; - height: 100%; box-sizing: border-box; justify-content: space-evenly; } diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts index 3a6921f091..826952292e 100644 --- a/src/panels/lovelace/cards/hui-humidifier-card.ts +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -256,6 +256,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { hui-card-features { width: 100%; flex: none; + padding: 0 12px 12px 12px; } `; } diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts index c2154bb148..57655de210 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -248,6 +248,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { hui-card-features { width: 100%; flex: none; + padding: 0 12px 12px 12px; } `; } diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index a6a789b3e7..c69b3c4ecf 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -100,10 +100,13 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } public getCardSize(): number { + const featuresPosition = + this._config && this._featurePosition(this._config); + const featuresCount = this._config?.features?.length || 0; return ( 1 + (this._config?.vertical ? 1 : 0) + - (this._config?.features?.length || 0) + (featuresPosition === "inline" ? 0 : featuresCount) ); } @@ -111,9 +114,16 @@ export class HuiTileCard extends LitElement implements LovelaceCard { const columns = 6; let min_columns = 6; let rows = 1; - if (this._config?.features?.length) { - rows += this._config.features.length; + const featurePosition = this._config && this._featurePosition(this._config); + const featuresCount = this._config?.features?.length || 0; + if (featuresCount) { + if (featurePosition === "inline") { + min_columns = 12; + } else { + rows += featuresCount; + } } + if (this._config?.vertical) { rows++; min_columns = 3; @@ -210,6 +220,23 @@ export class HuiTileCard extends LitElement implements LovelaceCard { ); } + private _featurePosition = memoizeOne((config: TileCardConfig) => { + if (config.vertical) { + return "bottom"; + } + return config.features_position || "bottom"; + }); + + private _displayedFeatures = memoizeOne((config: TileCardConfig) => { + const features = config.features || []; + const featurePosition = this._featurePosition(config); + + if (featurePosition === "inline") { + return features.slice(0, 1); + } + return features; + }); + protected render() { if (!this._config || !this.hass) { return nothing; @@ -263,6 +290,12 @@ export class HuiTileCard extends LitElement implements LovelaceCard { ? this._getImageUrl(stateObj) : undefined; + const featurePosition = this._featurePosition(this._config); + const features = this._displayedFeatures(this._config); + + const containerOrientationClass = + featurePosition === "inline" ? "horizontal" : ""; + return html`
-
+
- ${this._config.features + ${features.length > 0 ? html` ` : nothing} @@ -372,6 +405,10 @@ export class HuiTileCard extends LitElement implements LovelaceCard { flex-direction: column; flex: 1; } + .container.horizontal { + flex-direction: row; + } + .content { position: relative; display: flex; @@ -383,6 +420,11 @@ export class HuiTileCard extends LitElement implements LovelaceCard { pointer-events: none; gap: 10px; } + + .container.horizontal .content { + width: 50%; + } + .vertical { flex-direction: column; text-align: center; @@ -413,6 +455,13 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } hui-card-features { --feature-color: var(--tile-color); + padding: 0 12px 12px 12px; + } + .container.horizontal hui-card-features { + width: 50%; + --feature-height: 36px; + padding: 10px; + padding-inline-start: 0; } ha-tile-icon[data-domain="alarm_control_panel"][data-state="pending"], diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 01ddaf2f53..c1c3f856f1 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -533,6 +533,7 @@ export interface TileCardConfig extends LovelaceCardConfig { icon_hold_action?: ActionConfig; icon_double_tap_action?: ActionConfig; features?: LovelaceCardFeatureConfig[]; + features_position?: "bottom" | "inline"; } export interface HeadingCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts index f599b1ee1e..e2507047fc 100644 --- a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts @@ -8,6 +8,7 @@ import { assert, assign, boolean, + enums, object, optional, string, @@ -15,6 +16,7 @@ import { } from "superstruct"; import type { HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-form/ha-form"; import type { @@ -54,6 +56,7 @@ const cardConfigStruct = assign( icon_hold_action: optional(actionConfigStruct), icon_double_tap_action: optional(actionConfigStruct), features: optional(array(any())), + features_position: optional(enums(["bottom", "inline"])), }) ); @@ -109,8 +112,10 @@ export class HuiTileCardEditor private _schema = memoizeOne( ( + localize: LocalizeFunc, entityId: string | undefined, hideState: boolean, + vertical: boolean, displayActions: AdvancedActions[] = [] ) => [ @@ -148,12 +153,6 @@ export class HuiTileCardEditor boolean: {}, }, }, - { - name: "vertical", - selector: { - boolean: {}, - }, - }, { name: "hide_state", selector: { @@ -175,6 +174,43 @@ export class HuiTileCardEditor }, ] as const satisfies readonly HaFormSchema[]) : []), + { + name: "", + type: "grid", + schema: [ + { + name: "content_layout", + required: true, + selector: { + select: { + mode: "dropdown", + options: ["horizontal", "vertical"].map((value) => ({ + label: localize( + `ui.panel.lovelace.editor.card.tile.content_layout_options.${value}` + ), + value, + })), + }, + }, + }, + { + name: "features_position", + required: true, + selector: { + select: { + mode: "dropdown", + options: ["bottom", "inline"].map((value) => ({ + label: localize( + `ui.panel.lovelace.editor.card.tile.features_position_options.${value}` + ), + value, + disabled: vertical && value === "inline", + })), + }, + }, + }, + ], + }, ], }, { @@ -223,12 +259,22 @@ export class HuiTileCardEditor const stateObj = entityId ? this.hass!.states[entityId] : undefined; const schema = this._schema( + this.hass.localize, entityId, - this._config!.hide_state ?? false, + this._config.hide_state ?? false, + this._config.vertical ?? false, this._displayActions ); - const data = this._config; + const data = { + ...this._config, + content_layout: this._config.vertical ? "vertical" : "horizontal", + }; + + // Default features position to bottom and force it to bottom in vertical mode + if (!data.features_position || data.vertical) { + data.features_position = "bottom"; + } return html`