mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 02:38:53 +00:00
Add sidebar to home dashboard (#28084)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
<div
|
||||
class="wrapper ${classMap({
|
||||
"top-margin": Boolean(this._config?.top_margin),
|
||||
"has-sidebar": Boolean(this._config?.sidebar),
|
||||
narrow: this.narrow,
|
||||
})}"
|
||||
style=${styleMap({
|
||||
"--column-count": columnCount,
|
||||
"--content-column-count": contentColumnCount,
|
||||
})}
|
||||
>
|
||||
<hui-view-header
|
||||
.hass=${this.hass}
|
||||
@@ -152,38 +176,54 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
.lovelace=${this.lovelace}
|
||||
.viewIndex=${this.index}
|
||||
.config=${this._config?.header}
|
||||
style=${styleMap({
|
||||
"--max-column-count": maxColumnCount,
|
||||
})}
|
||||
></hui-view-header>
|
||||
<ha-sortable
|
||||
.disabled=${!editMode}
|
||||
@item-moved=${this._sectionMoved}
|
||||
group="section"
|
||||
handle-selector=".handle"
|
||||
draggable-selector=".section"
|
||||
.rollback=${false}
|
||||
>
|
||||
<div
|
||||
class="container ${classMap({
|
||||
dense: Boolean(this._config?.dense_section_placement),
|
||||
})}"
|
||||
style=${styleMap({
|
||||
"--total-section-count": totalSectionCount,
|
||||
"--max-column-count": maxColumnCount,
|
||||
})}
|
||||
${this.narrow && this._config?.sidebar
|
||||
? html`
|
||||
<div class="mobile-tabs">
|
||||
<ha-control-select
|
||||
.value=${this._showSidebar ? "sidebar" : "content"}
|
||||
@value-changed=${this._viewChanged}
|
||||
.options=${[
|
||||
{
|
||||
value: "content",
|
||||
label: this._config.sidebar.content_label,
|
||||
},
|
||||
{
|
||||
value: "sidebar",
|
||||
label: this._config.sidebar.sidebar_label,
|
||||
},
|
||||
]}
|
||||
>
|
||||
</ha-control-select>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
<div class="container">
|
||||
<ha-sortable
|
||||
.disabled=${!editMode}
|
||||
@item-moved=${this._sectionMoved}
|
||||
group="section"
|
||||
handle-selector=".handle"
|
||||
draggable-selector=".section"
|
||||
.rollback=${false}
|
||||
>
|
||||
${repeat(
|
||||
sections,
|
||||
(section) => this._getSectionKey(section),
|
||||
(section, idx) => {
|
||||
const columnSpan = Math.min(
|
||||
section.config.column_span || 1,
|
||||
maxColumnCount
|
||||
);
|
||||
const rowSpan = section.config.row_span || 1;
|
||||
<div
|
||||
class="content ${classMap({
|
||||
dense: Boolean(this._config?.dense_section_placement),
|
||||
"mobile-hidden": this.narrow && this._showSidebar,
|
||||
})}"
|
||||
>
|
||||
${repeat(
|
||||
sections,
|
||||
(section) => this._getSectionKey(section),
|
||||
(section, idx) => {
|
||||
const columnSpan = Math.min(
|
||||
section.config.column_span || 1,
|
||||
contentColumnCount
|
||||
);
|
||||
const rowSpan = section.config.row_span || 1;
|
||||
|
||||
return html`
|
||||
return html`
|
||||
<div
|
||||
class="section"
|
||||
style=${styleMap({
|
||||
@@ -208,72 +248,89 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${editMode
|
||||
? html`
|
||||
<ha-sortable
|
||||
group="card"
|
||||
@item-added=${this._handleCardAdded}
|
||||
draggable-selector=".card"
|
||||
.rollback=${false}
|
||||
>
|
||||
<div class="create-section-container">
|
||||
<div class="drop-helper" aria-hidden="true">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.drop_card_create_section"
|
||||
}
|
||||
)}
|
||||
${editMode
|
||||
? html`
|
||||
<ha-sortable
|
||||
group="card"
|
||||
@item-added=${this._handleCardAdded}
|
||||
draggable-selector=".card"
|
||||
.rollback=${false}
|
||||
>
|
||||
<div class="create-section-container">
|
||||
<div class="drop-helper" aria-hidden="true">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.drop_card_create_section"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="create-section"
|
||||
@click=${this._createSection}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.create_section"
|
||||
)}
|
||||
</p>
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.create_section"
|
||||
)}
|
||||
>
|
||||
<ha-ripple></ha-ripple>
|
||||
<ha-svg-icon .path=${mdiViewGridPlus}></ha-svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="create-section"
|
||||
@click=${this._createSection}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.create_section"
|
||||
)}
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.create_section"
|
||||
)}
|
||||
>
|
||||
<ha-ripple></ha-ripple>
|
||||
<ha-svg-icon .path=${mdiViewGridPlus}></ha-svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
`
|
||||
: nothing}
|
||||
${editMode && this._config?.cards?.length
|
||||
? html`
|
||||
<div class="section imported-cards">
|
||||
<div class="imported-card-header">
|
||||
<p class="title">
|
||||
<ha-svg-icon .path=${mdiEyeOff}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.imported_cards_title"
|
||||
)}
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.imported_cards_description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<hui-section
|
||||
.lovelace=${this.lovelace}
|
||||
.hass=${this.hass}
|
||||
.config=${this._importedCardSectionConfig(
|
||||
this._config.cards
|
||||
</ha-sortable>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
${this._config?.sidebar
|
||||
? html`
|
||||
<hui-view-sidebar
|
||||
class=${classMap({
|
||||
"mobile-hidden": this.narrow && !this._showSidebar,
|
||||
})}
|
||||
.hass=${this.hass}
|
||||
.badges=${this.badges}
|
||||
.lovelace=${this.lovelace}
|
||||
.viewIndex=${this.index}
|
||||
.config=${this._config.sidebar}
|
||||
></hui-view-sidebar>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="imported-cards-section">
|
||||
${editMode && this._config?.cards?.length
|
||||
? html`
|
||||
<div class="section imported-cards">
|
||||
<div class="imported-card-header">
|
||||
<p class="title">
|
||||
<ha-svg-icon .path=${mdiEyeOff}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.imported_cards_title"
|
||||
)}
|
||||
.viewIndex=${this.index}
|
||||
preview
|
||||
import-only
|
||||
></hui-section>
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.imported_cards_description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<hui-section
|
||||
.lovelace=${this.lovelace}
|
||||
.hass=${this.hass}
|
||||
.config=${this._importedCardSectionConfig(
|
||||
this._config.cards
|
||||
)}
|
||||
.viewIndex=${this.index}
|
||||
preview
|
||||
import-only
|
||||
></hui-section>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -352,6 +409,34 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
this.lovelace!.saveConfig(newConfig);
|
||||
}
|
||||
|
||||
private _viewChanged(ev: CustomEvent) {
|
||||
const newValue = ev.detail.value;
|
||||
const shouldShowSidebar = newValue === "sidebar";
|
||||
|
||||
if (shouldShowSidebar !== this._showSidebar) {
|
||||
this._toggleView();
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleView() {
|
||||
// Save current scroll position
|
||||
if (this._showSidebar) {
|
||||
this._sidebarScrollTop = window.scrollY;
|
||||
} else {
|
||||
this._contentScrollTop = window.scrollY;
|
||||
}
|
||||
|
||||
this._showSidebar = !this._showSidebar;
|
||||
|
||||
// Restore scroll position after view updates
|
||||
this.updateComplete.then(() => {
|
||||
const scrollY = this._showSidebar
|
||||
? this._sidebarScrollTop
|
||||
: this._contentScrollTop;
|
||||
window.scrollTo(0, scrollY);
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--row-height: var(--ha-view-sections-row-height, 56px);
|
||||
@@ -369,14 +454,19 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper.top-margin {
|
||||
.wrapper {
|
||||
display: block;
|
||||
margin-top: var(--top-margin);
|
||||
padding: var(--row-gap) var(--column-gap);
|
||||
box-sizing: content-box;
|
||||
margin: 0 auto;
|
||||
max-width: calc(
|
||||
var(--column-count) * var(--column-max-width) +
|
||||
(var(--column-count) - 1) * var(--column-gap)
|
||||
);
|
||||
}
|
||||
|
||||
.container > * {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.wrapper.top-margin {
|
||||
margin-top: var(--top-margin);
|
||||
}
|
||||
|
||||
.section {
|
||||
@@ -390,22 +480,92 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
}
|
||||
|
||||
.container {
|
||||
--column-count: min(var(--max-column-count), var(--total-section-count));
|
||||
display: grid;
|
||||
grid-template-columns: [content-start] repeat(
|
||||
var(--content-column-count),
|
||||
1fr
|
||||
);
|
||||
gap: var(--row-gap) var(--column-gap);
|
||||
padding: var(--row-gap) 0;
|
||||
}
|
||||
|
||||
.wrapper.has-sidebar .container {
|
||||
grid-template-columns:
|
||||
[content-start] repeat(var(--content-column-count), 1fr)
|
||||
[sidebar-start] 1fr;
|
||||
}
|
||||
|
||||
/* On mobile with sidebar, content and sidebar both take full width */
|
||||
.wrapper.narrow.has-sidebar .container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
hui-view-sidebar {
|
||||
grid-column: sidebar-start / -1;
|
||||
}
|
||||
|
||||
.wrapper.narrow hui-view-sidebar {
|
||||
grid-column: 1 / -1;
|
||||
padding-bottom: calc(
|
||||
var(--ha-space-4) + 56px + var(--ha-space-4) +
|
||||
env(safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
.mobile-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-tabs {
|
||||
position: fixed;
|
||||
bottom: calc(var(--ha-space-4) + env(safe-area-inset-bottom));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 0;
|
||||
z-index: 1;
|
||||
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.15))
|
||||
drop-shadow(0 4px 16px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
.mobile-tabs ha-control-select {
|
||||
width: max-content;
|
||||
min-width: 280px;
|
||||
max-width: 90%;
|
||||
--control-select-thickness: 56px;
|
||||
--control-select-border-radius: var(--ha-border-radius-6xl);
|
||||
--control-select-background: var(--card-background-color);
|
||||
--control-select-background-opacity: 1;
|
||||
--control-select-color: var(--primary-color);
|
||||
--control-select-padding: 6px;
|
||||
}
|
||||
|
||||
ha-sortable {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: content-start / sidebar-start;
|
||||
grid-row: 1 / -1;
|
||||
display: grid;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
grid-template-columns: repeat(var(--column-count), 1fr);
|
||||
grid-template-columns: repeat(var(--content-column-count), 1fr);
|
||||
grid-auto-flow: row;
|
||||
gap: var(--row-gap) var(--column-gap);
|
||||
padding: var(--row-gap) var(--column-gap);
|
||||
box-sizing: content-box;
|
||||
margin: 0 auto;
|
||||
max-width: calc(
|
||||
var(--column-count) * var(--column-max-width) +
|
||||
(var(--column-count) - 1) * var(--column-gap)
|
||||
}
|
||||
|
||||
.wrapper.narrow .content {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.wrapper.narrow.has-sidebar .content {
|
||||
padding-bottom: calc(
|
||||
var(--ha-space-4) + 56px + var(--ha-space-4) +
|
||||
env(safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
.container.dense {
|
||||
|
||||
.content.dense {
|
||||
grid-auto-flow: row dense;
|
||||
}
|
||||
|
||||
@@ -483,13 +643,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
hui-view-header {
|
||||
display: block;
|
||||
padding: 0 var(--column-gap);
|
||||
padding-top: var(--row-gap);
|
||||
margin: auto;
|
||||
max-width: calc(
|
||||
var(--max-column-count) * var(--column-max-width) +
|
||||
(var(--max-column-count) - 1) * var(--column-gap)
|
||||
);
|
||||
}
|
||||
|
||||
.imported-cards {
|
||||
|
||||
57
src/panels/lovelace/views/hui-view-sidebar.ts
Normal file
57
src/panels/lovelace/views/hui-view-sidebar.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { LovelaceViewSidebarConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../sections/hui-section";
|
||||
import type { Lovelace } from "../types";
|
||||
|
||||
export const DEFAULT_VIEW_SIDEBAR_LAYOUT = "start";
|
||||
|
||||
@customElement("hui-view-sidebar")
|
||||
export class HuiViewSidebar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public lovelace!: Lovelace;
|
||||
|
||||
@property({ attribute: false }) public config?: LovelaceViewSidebarConfig;
|
||||
|
||||
@property({ attribute: false }) public viewIndex!: number;
|
||||
|
||||
render() {
|
||||
if (!this.lovelace) return nothing;
|
||||
|
||||
// Use preview mode instead of setting lovelace to avoid the sections to be
|
||||
// editable as it is not yet supported
|
||||
return html`
|
||||
<div class="container">
|
||||
${repeat(
|
||||
this.config?.sections || [],
|
||||
(section) => html`
|
||||
<hui-section
|
||||
.config=${section}
|
||||
.hass=${this.hass}
|
||||
.preview=${this.lovelace.editMode}
|
||||
.viewIndex=${this.viewIndex}
|
||||
></hui-section>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--row-gap, 8px);
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-view-sidebar": HuiViewSidebar;
|
||||
}
|
||||
}
|
||||
@@ -7088,7 +7088,9 @@
|
||||
"unamed_device": "Unnamed device",
|
||||
"others": "Others",
|
||||
"scenes": "Scenes",
|
||||
"automations": "Automations"
|
||||
"automations": "Automations",
|
||||
"for_you": "For you",
|
||||
"home": "Home"
|
||||
},
|
||||
"common_controls": {
|
||||
"not_loaded": "Usage Prediction integration is not loaded.",
|
||||
@@ -7360,6 +7362,8 @@
|
||||
"header": "View configuration",
|
||||
"header_name": "{name} view configuration",
|
||||
"add": "Add view",
|
||||
"show_sidebar": "Show sidebar",
|
||||
"show_content": "Show content",
|
||||
"background": {
|
||||
"settings": "Background settings",
|
||||
"image": "Background image",
|
||||
|
||||
Reference in New Issue
Block a user