Add inline features position for tile card (#24199)

* Add side features position for tile card

* Add translations

* Rename to inline

* Simplify editor with 2 dropdowns

* Use 50% width

* Update src/translations/en.json

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>

---------

Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
This commit is contained in:
Paul Bottein 2025-02-18 06:33:17 +01:00 committed by GitHub
parent 94a5e737cc
commit 0b64861297
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 150 additions and 39 deletions

View File

@ -20,40 +20,31 @@ export class HuiCardFeatures extends LitElement {
return nothing;
}
return html`
<div class="container">
${this.features.map(
(feature) => html`
<hui-card-feature
.hass=${this.hass}
.stateObj=${this.stateObj}
.color=${this.color}
.feature=${feature}
></hui-card-feature>
`
)}
</div>
${this.features.map(
(feature) => html`
<hui-card-feature
.hass=${this.hass}
.stateObj=${this.stateObj}
.color=${this.color}
.feature=${feature}
></hui-card-feature>
`
)}
`;
}
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;
}

View File

@ -256,6 +256,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
hui-card-features {
width: 100%;
flex: none;
padding: 0 12px 12px 12px;
}
`;
}

View File

@ -248,6 +248,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
hui-card-features {
width: 100%;
flex: none;
padding: 0 12px 12px 12px;
}
`;
}

View File

@ -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`
<ha-card style=${styleMap(style)} class=${classMap({ active })}>
<div
@ -278,7 +311,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
>
<ha-ripple .disabled=${!this._hasCardAction}></ha-ripple>
</div>
<div class="container">
<div class="container ${containerOrientationClass}">
<div class="content ${classMap(contentClasses)}">
<ha-tile-icon
role=${ifDefined(this._hasIconAction ? "button" : undefined)}
@ -308,13 +341,13 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
.secondary=${stateDisplay}
></ha-tile-info>
</div>
${this._config.features
${features.length > 0
? html`
<hui-card-features
.hass=${this.hass}
.stateObj=${stateObj}
.color=${this._config.color}
.features=${this._config.features}
.features=${features}
></hui-card-features>
`
: 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"],

View File

@ -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 {

View File

@ -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`
<ha-form
@ -280,6 +326,12 @@ export class HuiTileCardEditor
delete config.state_content;
}
// Convert content_layout to vertical
if (config.content_layout) {
config.vertical = config.content_layout === "vertical";
delete config.content_layout;
}
fireEvent(this, "config-changed", { config });
}
@ -337,11 +389,11 @@ export class HuiTileCardEditor
case "icon_hold_action":
case "icon_double_tap_action":
case "show_entity_picture":
case "vertical":
case "hide_state":
case "state_content":
case "content_layout":
case "appearance":
case "interactions":
case "features_position":
return this.hass!.localize(
`ui.panel.lovelace.editor.card.tile.${schema.name}`
);
@ -377,6 +429,14 @@ export class HuiTileCardEditor
display: block;
margin-bottom: 24px;
}
.info {
color: var(--secondary-text-color);
margin-top: 0;
margin-bottom: 8px;
}
.features-form {
margin-bottom: 8px;
}
`,
];
}

View File

@ -7119,12 +7119,20 @@
"icon_tap_action": "Icon tap behavior",
"icon_hold_action": "Icon hold behavior",
"icon_double_tap_action": "Icon double tap behavior",
"interactions": "Interactions",
"appearance": "Appearance",
"show_entity_picture": "Show entity picture",
"vertical": "Vertical",
"hide_state": "Hide state",
"state_content": "State content"
"state_content": "State content",
"features_position": "Features position",
"features_position_options": {
"bottom": "Bottom",
"inline": "Inline"
},
"content_layout": "Content layout",
"content_layout_options": {
"horizontal": "Horizontal",
"vertical": "Vertical"
}
},
"vertical-stack": {
"name": "Vertical stack",