diff --git a/src/data/lovelace/config/view.ts b/src/data/lovelace/config/view.ts index a8d5ecbedd..28e6b17e79 100644 --- a/src/data/lovelace/config/view.ts +++ b/src/data/lovelace/config/view.ts @@ -1,7 +1,10 @@ import type { MediaSelectorValue } from "../../selector"; import type { LovelaceBadgeConfig } from "./badge"; import type { LovelaceCardConfig } from "./card"; -import type { LovelaceSectionRawConfig } from "./section"; +import type { + LovelaceSectionConfig, + LovelaceSectionRawConfig, +} from "./section"; import type { LovelaceStrategyConfig } from "./strategy"; export interface ShowViewConfig { @@ -33,6 +36,12 @@ export interface LovelaceViewHeaderConfig { badges_wrap?: "wrap" | "scroll"; } +export interface LovelaceViewSidebarConfig { + sections?: LovelaceSectionConfig[]; + content_label?: string; + sidebar_label?: string; +} + export interface LovelaceBaseViewConfig { index?: number; title?: string; @@ -56,6 +65,8 @@ export interface LovelaceViewConfig extends LovelaceBaseViewConfig { cards?: LovelaceCardConfig[]; sections?: LovelaceSectionRawConfig[]; header?: LovelaceViewHeaderConfig; + // Only used for section view, it should move to a section view config type when the views will have dedicated editor. + sidebar?: LovelaceViewSidebarConfig; } export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig { diff --git a/src/panels/lovelace/strategies/home/home-overview-view-strategy.ts b/src/panels/lovelace/strategies/home/home-overview-view-strategy.ts index 13fe357aa6..d9be846de8 100644 --- a/src/panels/lovelace/strategies/home/home-overview-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-overview-view-strategy.ts @@ -1,5 +1,6 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; +import { getAreasFloorHierarchy } from "../../../../common/areas/areas-floor-hierarchy"; import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; import { findEntities, @@ -23,8 +24,8 @@ import type { WeatherForecastCardConfig, } from "../../cards/types"; import type { CommonControlSectionStrategyConfig } from "../usage_prediction/common-controls-section-strategy"; -import { getAreasFloorHierarchy } from "../../../../common/areas/areas-floor-hierarchy"; import { HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries"; +import type { Condition } from "../../common/validate-condition"; export interface HomeOverviewViewStrategyConfig { type: "home-overview"; @@ -70,8 +71,12 @@ export class HomeOverviewViewStrategy extends ReactiveElement { const floorCount = home.floors.length + (home.areas.length ? 1 : 0); - // Allow between 2 and 3 columns (the max should be set to define the width of the header) - const maxColumns = 2; + const maxColumns = 3; + + const largeScreenCondition: Condition = { + condition: "screen", + media_query: "(min-width: 871px)", + }; const floorsSections: LovelaceSectionConfig[] = []; for (const floorStructure of home.floors) { @@ -126,12 +131,6 @@ export class HomeOverviewViewStrategy extends ReactiveElement { }); } - const favoriteSection: LovelaceSectionConfig = { - type: "grid", - column_span: maxColumns, - cards: [], - }; - const favoriteEntities = (config.favorite_entities || []).filter( (entityId) => hass.states[entityId] !== undefined ); @@ -176,74 +175,70 @@ export class HomeOverviewViewStrategy extends ReactiveElement { ({ type: "home-summary", summary: "light", - vertical: true, tap_action: { action: "navigate", navigation_path: "/light?historyBack=1", }, grid_options: { - rows: 2, - columns: 4, + columns: 12, }, } satisfies HomeSummaryCard), hasClimate && ({ type: "home-summary", summary: "climate", - vertical: true, tap_action: { action: "navigate", navigation_path: "/climate?historyBack=1", }, grid_options: { - rows: 2, - columns: 4, + columns: 12, }, } satisfies HomeSummaryCard), hasSecurity && ({ type: "home-summary", summary: "security", - vertical: true, tap_action: { action: "navigate", navigation_path: "/security?historyBack=1", }, grid_options: { - rows: 2, - columns: 4, + columns: 12, }, } satisfies HomeSummaryCard), hasMediaPlayers && ({ type: "home-summary", summary: "media_players", - vertical: true, tap_action: { action: "navigate", navigation_path: "media-players", }, grid_options: { - rows: 2, - columns: 4, + columns: 12, }, } satisfies HomeSummaryCard), ].filter(Boolean) as LovelaceCardConfig[]; - const summarySection: LovelaceSectionConfig = { + const forYouSection: LovelaceSectionConfig = { type: "grid", - column_span: maxColumns, + cards: [ + { + type: "heading", + heading: hass.localize("ui.panel.lovelace.strategy.home.for_you"), + heading_style: "title", + visibility: [largeScreenCondition], + }, + ], + }; + + const widgetSection: LovelaceSectionConfig = { cards: [], }; if (summaryCards.length) { - summarySection.cards!.push( - { - type: "heading", - heading: hass.localize("ui.panel.lovelace.strategy.home.summaries"), - }, - ...summaryCards - ); + widgetSection.cards!.push(...summaryCards); } const weatherFilter = generateEntityFilter(hass, { @@ -251,28 +246,16 @@ export class HomeOverviewViewStrategy extends ReactiveElement { entity_category: "none", }); - const widgetSection: LovelaceSectionConfig = { - type: "grid", - column_span: maxColumns, - cards: [], - }; const weatherEntity = Object.keys(hass.states) .filter(weatherFilter) .sort()[0]; if (weatherEntity) { - widgetSection.cards!.push( - { - type: "heading", - heading: "", - heading_style: "subtitle", - }, - { - type: "weather-forecast", - entity: weatherEntity, - forecast_type: "daily", - } as WeatherForecastCardConfig - ); + widgetSection.cards!.push({ + type: "weather-forecast", + entity: weatherEntity, + forecast_type: "daily", + } as WeatherForecastCardConfig); } const energyPrefs = isComponentLoaded(hass, "energy") @@ -299,11 +282,19 @@ export class HomeOverviewViewStrategy extends ReactiveElement { const sections = ( [ - favoriteSection.cards && favoriteSection, + { + type: "grid", + cards: [ + // Heading to add some spacing on large screens + { + type: "heading", + heading_style: "subtitle", + visibility: [largeScreenCondition], + }, + ], + }, commonControlsSection, - summarySection.cards && summarySection, ...floorsSections, - widgetSection.cards && widgetSection, ] satisfies (LovelaceSectionRawConfig | undefined)[] ).filter(Boolean) as LovelaceSectionRawConfig[]; @@ -319,6 +310,11 @@ export class HomeOverviewViewStrategy extends ReactiveElement { content: `## ${hass.localize("ui.panel.lovelace.strategy.home.welcome_user", { user: "{{ user }}" })}`, } satisfies MarkdownCardConfig, }, + sidebar: { + sections: [forYouSection, widgetSection], + content_label: hass.localize("ui.panel.lovelace.strategy.home.home"), + sidebar_label: hass.localize("ui.panel.lovelace.strategy.home.for_you"), + }, }; } } diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts index 27f4895d73..7c24baac8f 100644 --- a/src/panels/lovelace/views/hui-sections-view.ts +++ b/src/panels/lovelace/views/hui-sections-view.ts @@ -31,6 +31,7 @@ import { import type { HuiSection } from "../sections/hui-section"; import type { Lovelace } from "../types"; import "./hui-view-header"; +import "./hui-view-sidebar"; export const DEFAULT_MAX_COLUMNS = 4; @@ -46,6 +47,8 @@ export class SectionsView extends LitElement implements LovelaceViewElement { @property({ attribute: false }) public isStrategy = false; + @property({ type: Boolean }) public narrow = false; + @property({ attribute: false }) public sections: HuiSection[] = []; @property({ attribute: false }) public cards: HuiCard[] = []; @@ -58,6 +61,12 @@ export class SectionsView extends LitElement implements LovelaceViewElement { @state() _dragging = false; + @state() private _showSidebar = false; + + private _contentScrollTop = 0; + + private _sidebarScrollTop = 0; + private _columnsController = new ResizeController(this, { callback: (entries) => { const totalWidth = entries[0]?.contentRect.width; @@ -135,16 +144,31 @@ export class SectionsView extends LitElement implements LovelaceViewElement { const sections = this.sections; const totalSectionCount = - this._sectionColumnCount + (this.lovelace?.editMode ? 1 : 0); + this._sectionColumnCount + + (this.lovelace?.editMode ? 1 : 0) + + (this._config?.sidebar ? 1 : 0); const editMode = this.lovelace.editMode; const maxColumnCount = this._columnsController.value ?? 1; + const columnCount = Math.min(maxColumnCount, totalSectionCount); + // On mobile with sidebar, use full width for whichever view is active + const contentColumnCount = + this._config?.sidebar && !this.narrow + ? Math.max(1, columnCount - 1) + : columnCount; + return html`
-
- ${this.hass.localize( - "ui.panel.lovelace.editor.section.imported_cards_description" - )} -
-
+
+ ${this.hass.localize( + "ui.panel.lovelace.editor.section.imported_cards_description" + )} +