1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 00:27:49 +01:00

Add scrollbar support for cards with fixed grid row height (#30209)

* Add scrollbar support for cards with fixed grid row height

* Fix default sizing

* Add warning for card that doesn't support resizing well

* Remove not used explanation
This commit is contained in:
Paul Bottein
2026-03-18 16:11:29 +01:00
committed by GitHub
parent 41ed7d2877
commit a794a80228
7 changed files with 144 additions and 98 deletions

View File

@@ -24,6 +24,7 @@ import type {
LovelaceHeaderFooter,
} from "../types";
import type { EntitiesCardConfig } from "./types";
import { haStyleScrollbar } from "../../../resources/styles";
export const computeShowHeaderToggle = <
T extends EntityConfig | LovelaceRowConfig,
@@ -242,7 +243,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
`}
</h1>
`}
<div id="states" class="card-content">
<div id="states" class="card-content ha-scrollbar">
${this._configEntities!.map((entityConf) =>
this._renderEntity(entityConf)
)}
@@ -255,71 +256,76 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
`;
}
static styles = css`
ha-card {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card-header {
display: flex;
justify-content: space-between;
}
static styles = [
haStyleScrollbar,
css`
ha-card {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card-header {
display: flex;
justify-content: space-between;
}
.card-header .name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-header .name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#states {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--entities-card-row-gap, var(--card-row-gap, 8px));
}
#states {
flex: 1;
min-height: 0;
overflow-x: hidden;
display: flex;
flex-direction: column;
gap: var(--entities-card-row-gap, var(--card-row-gap, 8px));
}
#states > div > * {
overflow: clip visible;
}
#states > div > * {
overflow: clip visible;
}
#states > div {
position: relative;
}
#states > div {
position: relative;
}
.icon {
padding: 0px 18px 0px 8px;
}
.icon {
padding: 0px 18px 0px 8px;
}
.header {
border-top-left-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
border-top-right-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
overflow: hidden;
}
.header:not(:has(> hui-buttons-header-footer)) {
margin-bottom: var(--ha-space-4);
}
.header {
border-top-left-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
border-top-right-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
overflow: hidden;
}
.header:not(:has(> hui-buttons-header-footer)) {
margin-bottom: var(--ha-space-4);
}
.footer {
border-bottom-left-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
border-bottom-right-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
margin-top: -16px;
overflow: hidden;
}
`;
.footer {
border-bottom-left-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
border-bottom-right-radius: var(
--ha-card-border-radius,
var(--ha-border-radius-lg)
);
margin-top: -16px;
overflow: hidden;
}
`,
];
private _renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
const element = createRowElement(

View File

@@ -75,7 +75,14 @@ class HuiGridCard extends HuiStackCard<GridCardConfig> {
return [
super.sharedStyles,
css`
:host {
display: flex;
flex-direction: column;
height: 100%;
}
#root {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: repeat(
var(--grid-card-column-count, ${DEFAULT_COLUMNS}),

View File

@@ -26,9 +26,15 @@ export class HuiHorizontalStackCard extends HuiStackCard {
return [
super.sharedStyles,
css`
:host {
display: flex;
flex-direction: column;
height: 100%;
}
#root {
display: flex;
height: 100%;
flex: 1;
min-height: 0;
gap: var(--horizontal-stack-card-gap, var(--stack-card-gap, 8px));
}
#root > hui-card {

View File

@@ -2,6 +2,7 @@ import { LitElement, css, html, nothing } from "lit";
import { property, state } from "lit/decorators";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import { haStyleScrollbar } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import type {
LovelaceCard,
@@ -116,31 +117,38 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
${this._config.title
? html`<h1 class="card-header">${this._config.title}</h1>`
: ""}
<div id="root" dir=${this.hass ? computeRTLDirection(this.hass) : "ltr"}>
<div
id="root"
class="ha-scrollbar"
dir=${this.hass ? computeRTLDirection(this.hass) : "ltr"}
>
${this._cards}
${this.preview && this._errorCard ? this._errorCard : nothing}
</div>
`;
}
static sharedStyles = css`
.card-header {
color: var(--ha-card-header-color, var(--primary-text-color));
text-align: var(--ha-stack-title-text-align, start);
font-family: var(--ha-card-header-font-family, inherit);
font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl));
font-weight: var(--ha-font-weight-normal);
margin-block-start: 0px;
margin-block-end: 0px;
letter-spacing: -0.012em;
line-height: var(--ha-line-height-condensed);
display: block;
padding: 24px 16px 16px;
}
:host([ispanel]) #root {
--ha-card-border-radius: var(--restore-card-border-radius);
--ha-card-border-width: var(--restore-card-border-width);
--ha-card-box-shadow: var(--restore-card-box-shadow);
}
`;
static sharedStyles = [
haStyleScrollbar,
css`
.card-header {
color: var(--ha-card-header-color, var(--primary-text-color));
text-align: var(--ha-stack-title-text-align, start);
font-family: var(--ha-card-header-font-family, inherit);
font-size: var(--ha-card-header-font-size, var(--ha-font-size-2xl));
font-weight: var(--ha-font-weight-normal);
margin-block-start: 0px;
margin-block-end: 0px;
letter-spacing: -0.012em;
line-height: var(--ha-line-height-condensed);
display: block;
padding: 24px 16px 16px;
}
:host([ispanel]) #root {
--ha-card-border-radius: var(--restore-card-border-radius);
--ha-card-border-width: var(--restore-card-border-width);
--ha-card-box-shadow: var(--restore-card-box-shadow);
}
`,
];
}

View File

@@ -26,10 +26,16 @@ class HuiVerticalStackCard extends HuiStackCard {
return [
super.sharedStyles,
css`
#root {
:host {
display: flex;
flex-direction: column;
height: 100%;
}
#root {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
gap: var(--vertical-stack-card-gap, var(--stack-card-gap, 8px));
}
`,

View File

@@ -1,10 +1,11 @@
import { mdiDotsVertical, mdiPlaylistEdit } from "@mdi/js";
import type { PropertyValues } from "lit";
import { css, html, LitElement } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import "../../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
@@ -24,6 +25,7 @@ import type { HuiCard } from "../../cards/hui-card";
import type { CardGridSize } from "../../common/compute-card-grid-size";
import {
computeCardGridSize,
DEFAULT_GRID_SIZE,
GRID_COLUMN_MULTIPLIER,
isPreciseMode,
migrateLayoutToGridOptions,
@@ -50,6 +52,7 @@ export class HuiCardLayoutEditor extends LitElement {
private _mergedOptions = memoizeOne(
(options?: LovelaceGridOptions, defaultOptions?: LovelaceGridOptions) => ({
...DEFAULT_GRID_SIZE,
...defaultOptions,
...options,
})
@@ -86,12 +89,17 @@ export class HuiCardLayoutEditor extends LitElement {
const gridTotalColumns = 12 * columnSpan;
return html`
${this._defaultGridOptions &&
Object.keys(this._defaultGridOptions).length === 0
? html`
<ha-alert alert-type="info">
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.layout.no_grid_support"
)}
</ha-alert>
`
: nothing}
<div class="header">
<p class="intro">
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.layout.explanation"
)}
</p>
<ha-dropdown
slot="icons"
@wa-select=${this._handleAction}
@@ -284,13 +292,17 @@ export class HuiCardLayoutEditor extends LitElement {
const checked = ev.target.checked;
let columns: number | "full" | undefined;
const defaultGridOptions = {
...DEFAULT_GRID_SIZE,
...this._defaultGridOptions,
};
if (checked) {
columns = "full";
} else if (this._defaultGridOptions?.columns === "full") {
} else if (defaultGridOptions.columns === "full") {
// Default is full width, so we need to set a specific value
const columnSpan = this.sectionConfig.column_span ?? 1;
const gridTotalColumns = 12 * columnSpan;
columns = this._defaultGridOptions?.max_columns ?? gridTotalColumns;
columns = defaultGridOptions.max_columns ?? gridTotalColumns;
} else {
columns = undefined;
}
@@ -306,11 +318,15 @@ export class HuiCardLayoutEditor extends LitElement {
const checked = ev.target.checked;
let rows: number | "auto" | undefined;
const defaultGridOptions = {
...DEFAULT_GRID_SIZE,
...this._defaultGridOptions,
};
if (checked) {
rows = "auto";
} else if (this._defaultGridOptions?.rows === "auto") {
} else if (defaultGridOptions.rows === "auto") {
// Default is auto height, so we need to set a specific value
rows = this._defaultGridOptions?.min_rows ?? 1;
rows = defaultGridOptions.min_rows ?? 1;
} else {
rows = undefined;
}
@@ -368,11 +384,7 @@ export class HuiCardLayoutEditor extends LitElement {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.header .intro {
flex: 1;
margin: 0;
color: var(--secondary-text-color);
justify-content: flex-end;
}
.header ha-dropdown {
--mdc-theme-text-primary-on-background: var(--primary-text-color);

View File

@@ -8637,7 +8637,8 @@
"auto_height": "Auto height",
"auto_height_helper": "Adjust the card height based on its content",
"precise_mode": "Precise mode",
"precise_mode_helper": "Change the card width with more precision"
"precise_mode_helper": "Change the card width with more precision",
"no_grid_support": "This card does not fully support resizing yet and may not display correctly with custom sizes."
}
},
"edit_badge": {