Add view header (#24237)

* Create basic section heading

* Update badge position

* Don't allow to move card

* Remove view badges in section

* Improve heading section UX

* Add basic editor for heading section

* Add badges position option

* Add layout and badge position

* Improve button

* Use layout

* Simplify edit mode

* Fix CSS

* Fix add heading button

* Improve badges

* Rename to extra space

* Fix add top badge position

* Add migration

* Fix delete section confirmation

* Add translations

* Update comment

* Add header config to view

* Add edit card overlay

* Remove badge support for sections

* Remove section badges

* Clean section

* Fix header visibility

* Update translations

* Add view header editor

* Use new markdown card option

* Revert useless changes

* Add options translations

* Use edit dialog

* Unify font

* Update default text

* Share default between editor and view

* Update src/panels/lovelace/editor/view-header/hui-dialog-edit-view-header.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/lovelace/editor/view-header/hui-dialog-edit-view-header.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Fix comment

* Use select box for view header editor

* Remove extra space option

* Update select box

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paul Bottein 2025-02-25 11:43:54 +01:00 committed by GitHub
parent a906285a03
commit a3e24a3dc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1102 additions and 47 deletions

View File

@ -0,0 +1,11 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="94" height="56" rx="8" fill="white"/>
<rect x="0.5" y="0.5" width="93" height="55" rx="7.5" stroke="black" stroke-opacity="0.12" stroke-dasharray="4 4"/>
<path d="M8 14C8 10.6863 10.6863 8 14 8H33C36.3137 8 39 10.6863 39 14C39 17.3137 36.3137 20 33 20H14C10.6863 20 8 17.3137 8 14Z" fill="black" fill-opacity="0.12"/>
<path d="M8 27C8 25.3431 9.34315 24 11 24H83C84.6569 24 86 25.3431 86 27V29C86 30.6569 84.6569 32 83 32H11C9.34315 32 8 30.6569 8 29V27Z" fill="black" fill-opacity="0.12"/>
<path d="M8 44C8 46.2091 9.79086 48 12 48H16C18.2091 48 20 46.2091 20 44C20 41.7909 18.2091 40 16 40H12C9.79086 40 8 41.7909 8 44Z" fill="black" fill-opacity="0.32"/>
<path d="M24.5 44C24.5 46.2091 26.2909 48 28.5 48H32.5C34.7091 48 36.5 46.2091 36.5 44C36.5 41.7909 34.7091 40 32.5 40H28.5C26.2909 40 24.5 41.7909 24.5 44Z" fill="black" fill-opacity="0.32"/>
<path d="M41 44C41 46.2091 42.7909 48 45 48H49C51.2091 48 53 46.2091 53 44C53 41.7909 51.2091 40 49 40H45C42.7909 40 41 41.7909 41 44Z" fill="black" fill-opacity="0.32"/>
<path d="M57.5 44C57.5 46.2091 59.2909 48 61.5 48H65.5C67.7091 48 69.5 46.2091 69.5 44C69.5 41.7909 67.7091 40 65.5 40H61.5C59.2909 40 57.5 41.7909 57.5 44Z" fill="black" fill-opacity="0.32"/>
<path d="M74 44C74 46.2091 75.7909 48 78 48H82C84.2091 48 86 46.2091 86 44C86 41.7909 84.2091 40 82 40H78C75.7909 40 74 41.7909 74 44Z" fill="black" fill-opacity="0.32"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,11 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8C0 3.58172 3.58172 0 8 0H86C90.4183 0 94 3.58172 94 8V48C94 52.4183 90.4183 56 86 56H8C3.58172 56 0 52.4183 0 48V8Z" fill="black"/>
<path d="M1.34748 52.4449C0.772837 51.5866 0.359906 50.6109 0.152272 49.5613L0.642766 49.4643C0.549158 48.9911 0.5 48.5015 0.5 48V46H0V42H0.5V38H0V34H0.5V30H0V26H0.5V22H0V18H0.5V14H0V10H0.5V8C0.5 7.49847 0.549158 7.00892 0.642766 6.53574L0.152272 6.4387C0.359906 5.38915 0.772837 4.41341 1.34748 3.55508L1.76296 3.83324C2.31067 3.01513 3.01513 2.31067 3.83323 1.76296L3.55507 1.34748C4.41341 0.772837 5.38915 0.359906 6.4387 0.152272L6.53574 0.642766C7.00892 0.549158 7.49847 0.5 8 0.5H9.94999V0H13.85V0.5H17.75V0H21.65V0.5H25.55V0H29.45V0.5H33.35V0H37.25V0.5H41.15V0H45.05V0.5H48.95V0H52.85V0.5H56.75V0H60.65V0.5H64.55V0H68.45V0.5H72.35V0H76.25V0.5H80.15V0H84.05V0.5H86C86.5015 0.5 86.9911 0.549158 87.4643 0.642766L87.5613 0.152273C88.6108 0.359907 89.5866 0.772837 90.4449 1.34747L90.1668 1.76296C90.9849 2.31067 91.6893 3.01513 92.237 3.83323L92.6525 3.55507C93.2272 4.41341 93.6401 5.38915 93.8477 6.4387L93.3572 6.53574C93.4508 7.00892 93.5 7.49847 93.5 8V10H94V14H93.5V18H94V22H93.5V26H94V30H93.5V34H94V38H93.5V42H94V46H93.5V48C93.5 48.5015 93.4508 48.9911 93.3572 49.4643L93.8477 49.5613C93.6401 50.6109 93.2272 51.5866 92.6525 52.4449L92.237 52.1668C91.6893 52.9849 90.9849 53.6893 90.1668 54.237L90.4449 54.6525C89.5866 55.2272 88.6108 55.6401 87.5613 55.8477L87.4643 55.3572C86.9911 55.4508 86.5015 55.5 86 55.5H84.05V56H80.15V55.5H76.25V56H72.35V55.5H68.45V56H64.55V55.5H60.65V56H56.75V55.5H52.85V56H48.95V55.5H45.05V56H41.15V55.5H37.25V56H33.35V55.5H29.45V56H25.55V55.5H21.65V56H17.75V55.5H13.85V56H9.95V55.5H8C7.49847 55.5 7.00892 55.4508 6.53574 55.3572L6.4387 55.8477C5.38915 55.6401 4.41341 55.2272 3.55508 54.6525L3.83323 54.237C3.01513 53.6893 2.31067 52.9849 1.76296 52.1668L1.34748 52.4449Z" stroke="white" stroke-opacity="0.24" stroke-dasharray="4 4"/>
<path d="M8 14C8 10.6863 10.6863 8 14 8H33C36.3137 8 39 10.6863 39 14C39 17.3137 36.3137 20 33 20H14C10.6863 20 8 17.3137 8 14Z" fill="white" fill-opacity="0.24"/>
<path d="M8 27C8 25.3431 9.34315 24 11 24H83C84.6569 24 86 25.3431 86 27V29C86 30.6569 84.6569 32 83 32H11C9.34315 32 8 30.6569 8 29V27Z" fill="white" fill-opacity="0.24"/>
<path d="M8 44C8 46.2091 9.79086 48 12 48H16C18.2091 48 20 46.2091 20 44C20 41.7909 18.2091 40 16 40H12C9.79086 40 8 41.7909 8 44Z" fill="white" fill-opacity="0.48"/>
<path d="M24.5 44C24.5 46.2091 26.2909 48 28.5 48H32.5C34.7091 48 36.5 46.2091 36.5 44C36.5 41.7909 34.7091 40 32.5 40H28.5C26.2909 40 24.5 41.7909 24.5 44Z" fill="white" fill-opacity="0.48"/>
<path d="M41 44C41 46.2091 42.7909 48 45 48H49C51.2091 48 53 46.2091 53 44C53 41.7909 51.2091 40 49 40H45C42.7909 40 41 41.7909 41 44Z" fill="white" fill-opacity="0.48"/>
<path d="M57.5 44C57.5 46.2091 59.2909 48 61.5 48H65.5C67.7091 48 69.5 46.2091 69.5 44C69.5 41.7909 67.7091 40 65.5 40H61.5C59.2909 40 57.5 41.7909 57.5 44Z" fill="white" fill-opacity="0.48"/>
<path d="M74 44C74 46.2091 75.7909 48 78 48H82C84.2091 48 86 46.2091 86 44C86 41.7909 84.2091 40 82 40H78C75.7909 40 74 41.7909 74 44Z" fill="white" fill-opacity="0.48"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,11 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="94" height="56" rx="8" fill="white"/>
<rect x="0.5" y="0.5" width="93" height="55" rx="7.5" stroke="black" stroke-opacity="0.12" stroke-dasharray="4 4"/>
<path d="M8 12C8 14.2091 9.79086 16 12 16H16C18.2091 16 20 14.2091 20 12C20 9.79086 18.2091 8 16 8H12C9.79086 8 8 9.79086 8 12Z" fill="black" fill-opacity="0.32"/>
<path d="M24.5 12C24.5 14.2091 26.2909 16 28.5 16H32.5C34.7091 16 36.5 14.2091 36.5 12C36.5 9.79086 34.7091 8 32.5 8H28.5C26.2909 8 24.5 9.79086 24.5 12Z" fill="black" fill-opacity="0.32"/>
<path d="M41 12C41 14.2091 42.7909 16 45 16H49C51.2091 16 53 14.2091 53 12C53 9.79086 51.2091 8 49 8H45C42.7909 8 41 9.79086 41 12Z" fill="black" fill-opacity="0.32"/>
<path d="M57.5 12C57.5 14.2091 59.2909 16 61.5 16H65.5C67.7091 16 69.5 14.2091 69.5 12C69.5 9.79086 67.7091 8 65.5 8H61.5C59.2909 8 57.5 9.79086 57.5 12Z" fill="black" fill-opacity="0.32"/>
<path d="M74 12C74 14.2091 75.7909 16 78 16H82C84.2091 16 86 14.2091 86 12C86 9.79086 84.2091 8 82 8H78C75.7909 8 74 9.79086 74 12Z" fill="black" fill-opacity="0.32"/>
<path d="M8 30C8 26.6863 10.6863 24 14 24H33C36.3137 24 39 26.6863 39 30C39 33.3137 36.3137 36 33 36H14C10.6863 36 8 33.3137 8 30Z" fill="black" fill-opacity="0.12"/>
<path d="M8 43C8 41.3431 9.34315 40 11 40H83C84.6569 40 86 41.3431 86 43V45C86 46.6569 84.6569 48 83 48H11C9.34315 48 8 46.6569 8 45V43Z" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,11 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8C0 3.58172 3.58172 0 8 0H86C90.4183 0 94 3.58172 94 8V48C94 52.4183 90.4183 56 86 56H8C3.58172 56 0 52.4183 0 48V8Z" fill="black"/>
<path d="M1.34748 52.4449C0.772837 51.5866 0.359906 50.6109 0.152272 49.5613L0.642766 49.4643C0.549158 48.9911 0.5 48.5015 0.5 48V46H0V42H0.5V38H0V34H0.5V30H0V26H0.5V22H0V18H0.5V14H0V10H0.5V8C0.5 7.49847 0.549158 7.00892 0.642766 6.53574L0.152272 6.4387C0.359906 5.38915 0.772837 4.41341 1.34748 3.55508L1.76296 3.83324C2.31067 3.01513 3.01513 2.31067 3.83323 1.76296L3.55507 1.34748C4.41341 0.772837 5.38915 0.359906 6.4387 0.152272L6.53574 0.642766C7.00892 0.549158 7.49847 0.5 8 0.5H9.94999V0H13.85V0.5H17.75V0H21.65V0.5H25.55V0H29.45V0.5H33.35V0H37.25V0.5H41.15V0H45.05V0.5H48.95V0H52.85V0.5H56.75V0H60.65V0.5H64.55V0H68.45V0.5H72.35V0H76.25V0.5H80.15V0H84.05V0.5H86C86.5015 0.5 86.9911 0.549158 87.4643 0.642766L87.5613 0.152273C88.6108 0.359907 89.5866 0.772837 90.4449 1.34747L90.1668 1.76296C90.9849 2.31067 91.6893 3.01513 92.237 3.83323L92.6525 3.55507C93.2272 4.41341 93.6401 5.38915 93.8477 6.4387L93.3572 6.53574C93.4508 7.00892 93.5 7.49847 93.5 8V10H94V14H93.5V18H94V22H93.5V26H94V30H93.5V34H94V38H93.5V42H94V46H93.5V48C93.5 48.5015 93.4508 48.9911 93.3572 49.4643L93.8477 49.5613C93.6401 50.6109 93.2272 51.5866 92.6525 52.4449L92.237 52.1668C91.6893 52.9849 90.9849 53.6893 90.1668 54.237L90.4449 54.6525C89.5866 55.2272 88.6108 55.6401 87.5613 55.8477L87.4643 55.3572C86.9911 55.4508 86.5015 55.5 86 55.5H84.05V56H80.15V55.5H76.25V56H72.35V55.5H68.45V56H64.55V55.5H60.65V56H56.75V55.5H52.85V56H48.95V55.5H45.05V56H41.15V55.5H37.25V56H33.35V55.5H29.45V56H25.55V55.5H21.65V56H17.75V55.5H13.85V56H9.95V55.5H8C7.49847 55.5 7.00892 55.4508 6.53574 55.3572L6.4387 55.8477C5.38915 55.6401 4.41341 55.2272 3.55508 54.6525L3.83323 54.237C3.01513 53.6893 2.31067 52.9849 1.76296 52.1668L1.34748 52.4449Z" stroke="white" stroke-opacity="0.24" stroke-dasharray="4 4"/>
<path d="M8 12C8 14.2091 9.79086 16 12 16H16C18.2091 16 20 14.2091 20 12C20 9.79086 18.2091 8 16 8H12C9.79086 8 8 9.79086 8 12Z" fill="white" fill-opacity="0.48"/>
<path d="M24.5 12C24.5 14.2091 26.2909 16 28.5 16H32.5C34.7091 16 36.5 14.2091 36.5 12C36.5 9.79086 34.7091 8 32.5 8H28.5C26.2909 8 24.5 9.79086 24.5 12Z" fill="white" fill-opacity="0.48"/>
<path d="M41 12C41 14.2091 42.7909 16 45 16H49C51.2091 16 53 14.2091 53 12C53 9.79086 51.2091 8 49 8H45C42.7909 8 41 9.79086 41 12Z" fill="white" fill-opacity="0.48"/>
<path d="M57.5 12C57.5 14.2091 59.2909 16 61.5 16H65.5C67.7091 16 69.5 14.2091 69.5 12C69.5 9.79086 67.7091 8 65.5 8H61.5C59.2909 8 57.5 9.79086 57.5 12Z" fill="white" fill-opacity="0.48"/>
<path d="M74 12C74 14.2091 75.7909 16 78 16H82C84.2091 16 86 14.2091 86 12C86 9.79086 84.2091 8 82 8H78C75.7909 8 74 9.79086 74 12Z" fill="white" fill-opacity="0.48"/>
<path d="M8 30C8 26.6863 10.6863 24 14 24H33C36.3137 24 39 26.6863 39 30C39 33.3137 36.3137 36 33 36H14C10.6863 36 8 33.3137 8 30Z" fill="white" fill-opacity="0.24"/>
<path d="M8 43C8 41.3431 9.34315 40 11 40H83C84.6569 40 86 41.3431 86 43V45C86 46.6569 84.6569 48 83 48H11C9.34315 48 8 46.6569 8 45V43Z" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,11 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="94" height="56" rx="8" fill="white"/>
<rect x="0.5" y="0.5" width="93" height="55" rx="7.5" stroke="black" stroke-opacity="0.12" stroke-dasharray="4 4"/>
<path d="M31.5 14C31.5 10.6863 34.1863 8 37.5 8H56.5C59.8137 8 62.5 10.6863 62.5 14C62.5 17.3137 59.8137 20 56.5 20H37.5C34.1863 20 31.5 17.3137 31.5 14Z" fill="black" fill-opacity="0.32"/>
<path d="M23 27C23 25.3431 24.3431 24 26 24H68C69.6569 24 71 25.3431 71 27V29C71 30.6569 69.6569 32 68 32H26C24.3431 32 23 30.6569 23 29V27Z" fill="black" fill-opacity="0.12"/>
<path d="M9 44C9 41.7909 10.7909 40 13 40H17C19.2091 40 21 41.7909 21 44C21 46.2091 19.2091 48 17 48H13C10.7909 48 9 46.2091 9 44Z" fill="black" fill-opacity="0.32"/>
<path d="M25 44C25 41.7909 26.7909 40 29 40H33C35.2091 40 37 41.7909 37 44C37 46.2091 35.2091 48 33 48H29C26.7909 48 25 46.2091 25 44Z" fill="black" fill-opacity="0.12"/>
<path d="M41 44C41 41.7909 42.7909 40 45 40H49C51.2091 40 53 41.7909 53 44C53 46.2091 51.2091 48 49 48H45C42.7909 48 41 46.2091 41 44Z" fill="black" fill-opacity="0.12"/>
<path d="M57 44C57 41.7909 58.7909 40 61 40H65C67.2091 40 69 41.7909 69 44C69 46.2091 67.2091 48 65 48H61C58.7909 48 57 46.2091 57 44Z" fill="black" fill-opacity="0.12"/>
<path d="M73 44C73 41.7909 74.7909 40 77 40H81C83.2091 40 85 41.7909 85 44C85 46.2091 83.2091 48 81 48H77C74.7909 48 73 46.2091 73 44Z" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,11 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8C0 3.58172 3.58172 0 8 0H86C90.4183 0 94 3.58172 94 8V48C94 52.4183 90.4183 56 86 56H8C3.58172 56 0 52.4183 0 48V8Z" fill="black"/>
<path d="M1.34748 52.4449C0.772837 51.5866 0.359906 50.6109 0.152272 49.5613L0.642766 49.4643C0.549158 48.9911 0.5 48.5015 0.5 48V46H0V42H0.5V38H0V34H0.5V30H0V26H0.5V22H0V18H0.5V14H0V10H0.5V8C0.5 7.49847 0.549158 7.00892 0.642766 6.53574L0.152272 6.4387C0.359906 5.38915 0.772837 4.41341 1.34748 3.55508L1.76296 3.83324C2.31067 3.01513 3.01513 2.31067 3.83323 1.76296L3.55507 1.34748C4.41341 0.772837 5.38915 0.359906 6.4387 0.152272L6.53574 0.642766C7.00892 0.549158 7.49847 0.5 8 0.5H9.94999V0H13.85V0.5H17.75V0H21.65V0.5H25.55V0H29.45V0.5H33.35V0H37.25V0.5H41.15V0H45.05V0.5H48.95V0H52.85V0.5H56.75V0H60.65V0.5H64.55V0H68.45V0.5H72.35V0H76.25V0.5H80.15V0H84.05V0.5H86C86.5015 0.5 86.9911 0.549158 87.4643 0.642766L87.5613 0.152273C88.6108 0.359907 89.5866 0.772837 90.4449 1.34747L90.1668 1.76296C90.9849 2.31067 91.6893 3.01513 92.237 3.83323L92.6525 3.55507C93.2272 4.41341 93.6401 5.38915 93.8477 6.4387L93.3572 6.53574C93.4508 7.00892 93.5 7.49847 93.5 8V10H94V14H93.5V18H94V22H93.5V26H94V30H93.5V34H94V38H93.5V42H94V46H93.5V48C93.5 48.5015 93.4508 48.9911 93.3572 49.4643L93.8477 49.5613C93.6401 50.6109 93.2272 51.5866 92.6525 52.4449L92.237 52.1668C91.6893 52.9849 90.9849 53.6893 90.1668 54.237L90.4449 54.6525C89.5866 55.2272 88.6108 55.6401 87.5613 55.8477L87.4643 55.3572C86.9911 55.4508 86.5015 55.5 86 55.5H84.05V56H80.15V55.5H76.25V56H72.35V55.5H68.45V56H64.55V55.5H60.65V56H56.75V55.5H52.85V56H48.95V55.5H45.05V56H41.15V55.5H37.25V56H33.35V55.5H29.45V56H25.55V55.5H21.65V56H17.75V55.5H13.85V56H9.95V55.5H8C7.49847 55.5 7.00892 55.4508 6.53574 55.3572L6.4387 55.8477C5.38915 55.6401 4.41341 55.2272 3.55508 54.6525L3.83323 54.237C3.01513 53.6893 2.31067 52.9849 1.76296 52.1668L1.34748 52.4449Z" stroke="white" stroke-opacity="0.24" stroke-dasharray="4 4"/>
<path d="M31.5 14C31.5 10.6863 34.1863 8 37.5 8H56.5C59.8137 8 62.5 10.6863 62.5 14C62.5 17.3137 59.8137 20 56.5 20H37.5C34.1863 20 31.5 17.3137 31.5 14Z" fill="white" fill-opacity="0.48"/>
<path d="M23 27C23 25.3431 24.3431 24 26 24H68C69.6569 24 71 25.3431 71 27V29C71 30.6569 69.6569 32 68 32H26C24.3431 32 23 30.6569 23 29V27Z" fill="white" fill-opacity="0.24"/>
<path d="M9 44C9 41.7909 10.7909 40 13 40H17C19.2091 40 21 41.7909 21 44C21 46.2091 19.2091 48 17 48H13C10.7909 48 9 46.2091 9 44Z" fill="white" fill-opacity="0.48"/>
<rect x="25" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
<rect x="41" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
<rect x="57" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
<rect x="73" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,24 @@
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_887_2968)">
<path d="M0 39H55V64C55 68.4183 51.4183 72 47 72H8C3.58172 72 0 68.4183 0 64V39Z" fill="white"/>
<path d="M1.34748 68.4449C0.772837 67.5866 0.359906 66.6109 0.152272 65.5613L0.642766 65.4643C0.549158 64.9911 0.5 64.5015 0.5 64V61.9167H0V57.75H0.5V53.5833H0V49.4167H0.5V45.25H0V41.0833H0.5V39.5H1.96429V39H5.89286V39.5H9.82143V39H13.75V39.5H17.6786V39H21.6071V39.5H25.5357V39H29.4643V39.5H33.3929V39H37.3214V39.5H41.25V39H45.1786V39.5H49.1071V39H53.0357V39.5H54.5V41.0833H55V45.25H54.5V49.4167H55V53.5833H54.5V57.75H55V61.9167H54.5V64C54.5 64.5015 54.4508 64.9911 54.3572 65.4643L54.8477 65.5613C54.6401 66.6109 54.2272 67.5866 53.6525 68.4449L53.237 68.1668C52.6893 68.9849 51.9849 69.6893 51.1668 70.237L51.4449 70.6525C50.5866 71.2272 49.6109 71.6401 48.5613 71.8477L48.4643 71.3572C47.9911 71.4508 47.5015 71.5 47 71.5H45.05V72H41.15V71.5H37.25V72H33.35V71.5H29.45V72H25.55V71.5H21.65V72H17.75V71.5H13.85V72H9.95V71.5H8C7.49847 71.5 7.00892 71.4508 6.53574 71.3572L6.4387 71.8477C5.38915 71.6401 4.41341 71.2272 3.55507 70.6525L3.83323 70.237C3.01513 69.6893 2.31067 68.9849 1.76296 68.1668L1.34748 68.4449Z" stroke="black" stroke-opacity="0.12" stroke-dasharray="4 4"/>
<rect x="8" y="47" width="12" height="8" rx="4" fill="black" fill-opacity="0.32"/>
<rect x="24" y="47" width="12" height="8" rx="4" fill="black" fill-opacity="0.12"/>
<rect x="8" y="59" width="12" height="8" rx="4" fill="black" fill-opacity="0.12"/>
<path d="M54 0H86C90.4183 0 94 3.58172 94 8V32C94 36.4183 90.4183 40 86 40H54V0Z" fill="white"/>
<path d="M84 39.5V40H80V39.5H76V40H72V39.5H68V40H64V39.5H60V40H56V39.5H54.5V38H54V34H54.5V30H54V26H54.5V22H54V18H54.5V14H54V10H54.5V6H54V2H54.5V0.5H56V0H60V0.5H64V0H68V0.5H72V0H76V0.5H80V0H84V0.5H86C86.5015 0.5 86.9911 0.549158 87.4643 0.642766L87.5613 0.152272C88.6109 0.359906 89.5866 0.772836 90.4449 1.34748L90.1668 1.76296C90.9849 2.31067 91.6893 3.01513 92.237 3.83323L92.6525 3.55507C93.2272 4.41341 93.6401 5.38915 93.8477 6.4387L93.3572 6.53574C93.4508 7.00892 93.5 7.49847 93.5 8V10H94V14H93.5V18H94V22H93.5V26H94V30H93.5V32C93.5 32.5015 93.4508 32.9911 93.3572 33.4643L93.8477 33.5613C93.6401 34.6109 93.2272 35.5866 92.6525 36.4449L92.237 36.1668C91.6893 36.9849 90.9849 37.6893 90.1668 38.237L90.4449 38.6525C89.5866 39.2272 88.6109 39.6401 87.5613 39.8477L87.4643 39.3572C86.9911 39.4508 86.5015 39.5 86 39.5H84Z" stroke="black" stroke-opacity="0.12" stroke-dasharray="4 4"/>
<path d="M58 28C58 30.2091 59.7909 32 62 32H66C68.2091 32 70 30.2091 70 28C70 25.7909 68.2091 24 66 24H62C59.7909 24 58 25.7909 58 28Z" fill="black" fill-opacity="0.12"/>
<path d="M74 28C74 30.2091 75.7909 32 78 32H82C84.2091 32 86 30.2091 86 28C86 25.7909 84.2091 24 82 24H78C75.7909 24 74 25.7909 74 28Z" fill="black" fill-opacity="0.32"/>
<path d="M74 16C74 18.2091 75.7909 20 78 20H82C84.2091 20 86 18.2091 86 16C86 13.7909 84.2091 12 82 12H78C75.7909 12 74 13.7909 74 16Z" fill="black" fill-opacity="0.12"/>
<path d="M0 8C0 3.58172 3.58172 0 8 0H55V40H0V8Z" fill="white"/>
<path d="M3.55507 1.34748C4.41341 0.772837 5.38915 0.359906 6.4387 0.152272L6.53574 0.642766C7.00892 0.549158 7.49847 0.5 8 0.5H9.95833V0H13.875V0.5H17.7917V0H21.7083V0.5H25.625V0H29.5417V0.5H33.4583V0H37.375V0.5H41.2917V0H45.2083V0.5H49.125V0H53.0417V0.5H54.5V2H55V6H54.5V10H55V14H54.5V18H55V22H54.5V26H55V30H54.5V34H55V38H54.5V39.5H53.0357V40H49.1071V39.5H45.1786V40H41.25V39.5H37.3214V40H33.3929V39.5H29.4643V40H25.5357V39.5H21.6071V40H17.6786V39.5H13.75V40H9.82143V39.5H5.89286V40H1.96429V39.5H0.5V38H0V34H0.5V30H0V26H0.5V22H0V18H0.5V14H0V10H0.5V8C0.5 7.49847 0.549158 7.00892 0.642766 6.53574L0.152272 6.4387C0.359906 5.38915 0.772837 4.41341 1.34748 3.55508L1.76296 3.83324C2.31067 3.01513 3.01513 2.31067 3.83323 1.76296L3.55507 1.34748Z" stroke="black" stroke-opacity="0.12" stroke-dasharray="4 4"/>
<path d="M8 14C8 10.6863 10.6863 8 14 8H33C36.3137 8 39 10.6863 39 14C39 17.3137 36.3137 20 33 20H14C10.6863 20 8 17.3137 8 14Z" fill="black" fill-opacity="0.32"/>
<path d="M8 27C8 25.3431 9.34315 24 11 24H44C45.6569 24 47 25.3431 47 27V29C47 30.6569 45.6569 32 44 32H11C9.34315 32 8 30.6569 8 29V27Z" fill="black" fill-opacity="0.12"/>
<path d="M79 48V54.5C79 58.09 76.09 61 72.5 61H66.83L69.92 64.09L68.5 65.5L63 60L68.5 54.5L69.91 55.91L66.83 59H72.5C75 59 77 57 77 54.5V48H79Z" fill="black" fill-opacity="0.32"/>
</g>
<defs>
<clipPath id="clip0_887_2968">
<rect width="94" height="72" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,17 @@
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 39H55V64C55 68.4183 51.4183 72 47 72H8C3.58172 72 0 68.4183 0 64V39Z" fill="black"/>
<path d="M1.34748 68.4449C0.772837 67.5866 0.359906 66.6109 0.152272 65.5613L0.642766 65.4643C0.549158 64.9911 0.5 64.5015 0.5 64V61.9167H0V57.75H0.5V53.5833H0V49.4167H0.5V45.25H0V41.0833H0.5V39.5H1.96429V39H5.89286V39.5H9.82143V39H13.75V39.5H17.6786V39H21.6071V39.5H25.5357V39H29.4643V39.5H33.3929V39H37.3214V39.5H41.25V39H45.1786V39.5H49.1071V39H53.0357V39.5H54.5V41.0833H55V45.25H54.5V49.4167H55V53.5833H54.5V57.75H55V61.9167H54.5V64C54.5 64.5015 54.4508 64.9911 54.3572 65.4643L54.8477 65.5613C54.6401 66.6109 54.2272 67.5866 53.6525 68.4449L53.237 68.1668C52.6893 68.9849 51.9849 69.6893 51.1668 70.237L51.4449 70.6525C50.5866 71.2272 49.6109 71.6401 48.5613 71.8477L48.4643 71.3572C47.9911 71.4508 47.5015 71.5 47 71.5H45.05V72H41.15V71.5H37.25V72H33.35V71.5H29.45V72H25.55V71.5H21.65V72H17.75V71.5H13.85V72H9.95V71.5H8C7.49847 71.5 7.00892 71.4508 6.53574 71.3572L6.4387 71.8477C5.38915 71.6401 4.41341 71.2272 3.55507 70.6525L3.83323 70.237C3.01513 69.6893 2.31067 68.9849 1.76296 68.1668L1.34748 68.4449Z" stroke="white" stroke-opacity="0.24" stroke-dasharray="4 4"/>
<path d="M8 51C8 48.7909 9.79086 47 12 47H16C18.2091 47 20 48.7909 20 51C20 53.2091 18.2091 55 16 55H12C9.79086 55 8 53.2091 8 51Z" fill="white" fill-opacity="0.48"/>
<path d="M24 51C24 48.7909 25.7909 47 28 47H32C34.2091 47 36 48.7909 36 51C36 53.2091 34.2091 55 32 55H28C25.7909 55 24 53.2091 24 51Z" fill="white" fill-opacity="0.24"/>
<path d="M8 63C8 60.7909 9.79086 59 12 59H16C18.2091 59 20 60.7909 20 63C20 65.2091 18.2091 67 16 67H12C9.79086 67 8 65.2091 8 63Z" fill="white" fill-opacity="0.24"/>
<path d="M54 0H86C90.4183 0 94 3.58172 94 8V32C94 36.4183 90.4183 40 86 40H54V0Z" fill="black"/>
<path d="M84 39.5V40H80V39.5H76V40H72V39.5H68V40H64V39.5H60V40H56V39.5H54.5V38H54V34H54.5V30H54V26H54.5V22H54V18H54.5V14H54V10H54.5V6H54V2H54.5V0.5H56V0H60V0.5H64V0H68V0.5H72V0H76V0.5H80V0H84V0.5H86C86.5015 0.5 86.9911 0.549158 87.4643 0.642766L87.5613 0.152272C88.6109 0.359906 89.5866 0.772836 90.4449 1.34748L90.1668 1.76296C90.9849 2.31067 91.6893 3.01513 92.237 3.83323L92.6525 3.55507C93.2272 4.41341 93.6401 5.38915 93.8477 6.4387L93.3572 6.53574C93.4508 7.00892 93.5 7.49847 93.5 8V10H94V14H93.5V18H94V22H93.5V26H94V30H93.5V32C93.5 32.5015 93.4508 32.9911 93.3572 33.4643L93.8477 33.5613C93.6401 34.6109 93.2272 35.5866 92.6525 36.4449L92.237 36.1668C91.6893 36.9849 90.9849 37.6893 90.1668 38.237L90.4449 38.6525C89.5866 39.2272 88.6109 39.6401 87.5613 39.8477L87.4643 39.3572C86.9911 39.4508 86.5015 39.5 86 39.5H84Z" stroke="white" stroke-opacity="0.24" stroke-dasharray="4 4"/>
<path d="M58 28C58 30.2091 59.7909 32 62 32H66C68.2091 32 70 30.2091 70 28C70 25.7909 68.2091 24 66 24H62C59.7909 24 58 25.7909 58 28Z" fill="white" fill-opacity="0.24"/>
<path d="M74 28C74 30.2091 75.7909 32 78 32H82C84.2091 32 86 30.2091 86 28C86 25.7909 84.2091 24 82 24H78C75.7909 24 74 25.7909 74 28Z" fill="white" fill-opacity="0.48"/>
<path d="M74 16C74 18.2091 75.7909 20 78 20H82C84.2091 20 86 18.2091 86 16C86 13.7909 84.2091 12 82 12H78C75.7909 12 74 13.7909 74 16Z" fill="white" fill-opacity="0.24"/>
<path d="M0 8C0 3.58172 3.58172 0 8 0H55V40H0V8Z" fill="black"/>
<path d="M3.55507 1.34748C4.41341 0.772837 5.38915 0.359906 6.4387 0.152272L6.53574 0.642766C7.00892 0.549158 7.49847 0.5 8 0.5H9.95833V0H13.875V0.5H17.7917V0H21.7083V0.5H25.625V0H29.5417V0.5H33.4583V0H37.375V0.5H41.2917V0H45.2083V0.5H49.125V0H53.0417V0.5H54.5V2H55V6H54.5V10H55V14H54.5V18H55V22H54.5V26H55V30H54.5V34H55V38H54.5V39.5H53.0357V40H49.1071V39.5H45.1786V40H41.25V39.5H37.3214V40H33.3929V39.5H29.4643V40H25.5357V39.5H21.6071V40H17.6786V39.5H13.75V40H9.82143V39.5H5.89286V40H1.96429V39.5H0.5V38H0V34H0.5V30H0V26H0.5V22H0V18H0.5V14H0V10H0.5V8C0.5 7.49847 0.549158 7.00892 0.642766 6.53574L0.152272 6.4387C0.359906 5.38915 0.772837 4.41341 1.34748 3.55508L1.76296 3.83324C2.31067 3.01513 3.01513 2.31067 3.83323 1.76296L3.55507 1.34748Z" stroke="white" stroke-opacity="0.24" stroke-dasharray="4 4"/>
<path d="M8 14C8 10.6863 10.6863 8 14 8H33C36.3137 8 39 10.6863 39 14C39 17.3137 36.3137 20 33 20H14C10.6863 20 8 17.3137 8 14Z" fill="white" fill-opacity="0.48"/>
<path d="M8 27C8 25.3431 9.34315 24 11 24H44C45.6569 24 47 25.3431 47 27V29C47 30.6569 45.6569 32 44 32H11C9.34315 32 8 30.6569 8 29V27Z" fill="white" fill-opacity="0.24"/>
<path d="M79 48V54.5C79 58.09 76.09 61 72.5 61H66.83L69.92 64.09L68.5 65.5L63 60L68.5 54.5L69.91 55.91L66.83 59H72.5C75 59 77 57 77 54.5V48H79Z" fill="white" fill-opacity="0.48"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,9 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="94" height="56" rx="8" fill="white"/>
<rect x="0.5" y="0.5" width="93" height="55" rx="7.5" stroke="black" stroke-opacity="0.12" stroke-dasharray="4 4"/>
<path d="M8 14C8 10.6863 10.6863 8 14 8H33C36.3137 8 39 10.6863 39 14C39 17.3137 36.3137 20 33 20H14C10.6863 20 8 17.3137 8 14Z" fill="black" fill-opacity="0.32"/>
<path d="M8 27C8 25.3431 9.34315 24 11 24H53C54.6569 24 56 25.3431 56 27V29C56 30.6569 54.6569 32 53 32H11C9.34315 32 8 30.6569 8 29V27Z" fill="black" fill-opacity="0.12"/>
<path d="M8 44C8 41.7909 9.79086 40 12 40H16C18.2091 40 20 41.7909 20 44C20 46.2091 18.2091 48 16 48H12C9.79086 48 8 46.2091 8 44Z" fill="black" fill-opacity="0.32"/>
<path d="M24 44C24 41.7909 25.7909 40 28 40H32C34.2091 40 36 41.7909 36 44C36 46.2091 34.2091 48 32 48H28C25.7909 48 24 46.2091 24 44Z" fill="black" fill-opacity="0.12"/>
<path d="M40 44C40 41.7909 41.7909 40 44 40H48C50.2091 40 52 41.7909 52 44C52 46.2091 50.2091 48 48 48H44C41.7909 48 40 46.2091 40 44Z" fill="black" fill-opacity="0.12"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,11 @@
<svg width="94" height="56" viewBox="0 0 94 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8C0 3.58172 3.58172 0 8 0H86C90.4183 0 94 3.58172 94 8V48C94 52.4183 90.4183 56 86 56H8C3.58172 56 0 52.4183 0 48V8Z" fill="black"/>
<path d="M1.34748 52.4449C0.772837 51.5866 0.359906 50.6109 0.152272 49.5613L0.642766 49.4643C0.549158 48.9911 0.5 48.5015 0.5 48V46H0V42H0.5V38H0V34H0.5V30H0V26H0.5V22H0V18H0.5V14H0V10H0.5V8C0.5 7.49847 0.549158 7.00892 0.642766 6.53574L0.152272 6.4387C0.359906 5.38915 0.772837 4.41341 1.34748 3.55508L1.76296 3.83324C2.31067 3.01513 3.01513 2.31067 3.83323 1.76296L3.55507 1.34748C4.41341 0.772837 5.38915 0.359906 6.4387 0.152272L6.53574 0.642766C7.00892 0.549158 7.49847 0.5 8 0.5H9.94999V0H13.85V0.5H17.75V0H21.65V0.5H25.55V0H29.45V0.5H33.35V0H37.25V0.5H41.15V0H45.05V0.5H48.95V0H52.85V0.5H56.75V0H60.65V0.5H64.55V0H68.45V0.5H72.35V0H76.25V0.5H80.15V0H84.05V0.5H86C86.5015 0.5 86.9911 0.549158 87.4643 0.642766L87.5613 0.152273C88.6108 0.359907 89.5866 0.772837 90.4449 1.34747L90.1668 1.76296C90.9849 2.31067 91.6893 3.01513 92.237 3.83323L92.6525 3.55507C93.2272 4.41341 93.6401 5.38915 93.8477 6.4387L93.3572 6.53574C93.4508 7.00892 93.5 7.49847 93.5 8V10H94V14H93.5V18H94V22H93.5V26H94V30H93.5V34H94V38H93.5V42H94V46H93.5V48C93.5 48.5015 93.4508 48.9911 93.3572 49.4643L93.8477 49.5613C93.6401 50.6109 93.2272 51.5866 92.6525 52.4449L92.237 52.1668C91.6893 52.9849 90.9849 53.6893 90.1668 54.237L90.4449 54.6525C89.5866 55.2272 88.6108 55.6401 87.5613 55.8477L87.4643 55.3572C86.9911 55.4508 86.5015 55.5 86 55.5H84.05V56H80.15V55.5H76.25V56H72.35V55.5H68.45V56H64.55V55.5H60.65V56H56.75V55.5H52.85V56H48.95V55.5H45.05V56H41.15V55.5H37.25V56H33.35V55.5H29.45V56H25.55V55.5H21.65V56H17.75V55.5H13.85V56H9.95V55.5H8C7.49847 55.5 7.00892 55.4508 6.53574 55.3572L6.4387 55.8477C5.38915 55.6401 4.41341 55.2272 3.55508 54.6525L3.83323 54.237C3.01513 53.6893 2.31067 52.9849 1.76296 52.1668L1.34748 52.4449Z" stroke="white" stroke-opacity="0.24" stroke-dasharray="4 4"/>
<path d="M8 14C8 10.6863 10.6863 8 14 8H33C36.3137 8 39 10.6863 39 14C39 17.3137 36.3137 20 33 20H14C10.6863 20 8 17.3137 8 14Z" fill="white" fill-opacity="0.48"/>
<path d="M8 27C8 25.3431 9.34315 24 11 24H53C54.6569 24 56 25.3431 56 27V29C56 30.6569 54.6569 32 53 32H11C9.34315 32 8 30.6569 8 29V27Z" fill="white" fill-opacity="0.24"/>
<path d="M8 44C8 41.7909 9.79086 40 12 40H16C18.2091 40 20 41.7909 20 44C20 46.2091 18.2091 48 16 48H12C9.79086 48 8 46.2091 8 44Z" fill="white" fill-opacity="0.48"/>
<rect x="24" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
<rect x="40" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
<rect x="56" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
<rect x="72" y="40" width="12" height="8" rx="4" fill="white" fill-opacity="0.24"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -25,6 +25,12 @@ export interface LovelaceViewBackgroundConfig {
attachment?: "scroll" | "fixed";
}
export interface LovelaceViewHeaderConfig {
card?: LovelaceCardConfig;
layout?: "start" | "center" | "responsive";
badges_position?: "bottom" | "top";
}
export interface LovelaceBaseViewConfig {
index?: number;
title?: string;
@ -46,6 +52,7 @@ export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
badges?: (string | Partial<LovelaceBadgeConfig>)[]; // Badge can be just an entity_id or without type
cards?: LovelaceCardConfig[];
sections?: LovelaceSectionRawConfig[];
header?: LovelaceViewHeaderConfig;
}
export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig {

View File

@ -12,6 +12,7 @@ import "../../../components/ha-svg-icon";
import type { HomeAssistant } from "../../../types";
import "../components/hui-badge-edit-mode";
import { moveBadge } from "../editor/config-util";
import type { LovelaceCardPath } from "../editor/lovelace-path";
import type { Lovelace } from "../types";
import type { HuiBadge } from "./hui-badge";
@ -89,6 +90,20 @@ export class HuiViewBadges extends LitElement {
this.lovelace!.saveConfig(newConfig);
}
private _badgeAdded(ev) {
ev.stopPropagation();
const { index, data } = ev.detail;
const oldPath = data as LovelaceCardPath;
const newPath = [this.viewIndex!, index] as LovelaceCardPath;
const newConfig = moveBadge(this.lovelace!.config, oldPath, newPath);
this.lovelace!.saveConfig(newConfig);
}
private _badgeRemoved(ev) {
ev.stopPropagation();
// Do nothing, it's handled by the "item-added" event from the new parent.
}
private _dragStart() {
this._dragging = true;
}
@ -114,6 +129,8 @@ export class HuiViewBadges extends LitElement {
<ha-sortable
.disabled=${!editMode}
@item-moved=${this._badgeMoved}
@item-added=${this._badgeAdded}
@item-removed=${this._badgeRemoved}
@drag-start=${this._dragStart}
@drag-end=${this._dragEnd}
group="badge"
@ -126,21 +143,25 @@ export class HuiViewBadges extends LitElement {
${repeat(
badges,
(badge) => this._getBadgeKey(badge),
(badge, idx) => html`
(badge, idx) => {
const badgePath = [this.viewIndex, idx] as LovelaceCardPath;
return html`
${editMode
? html`
<hui-badge-edit-mode
data-sortable
.hass=${this.hass}
.lovelace=${this.lovelace}
.path=${[this.viewIndex, idx]}
.path=${badgePath}
.hiddenOverlay=${this._dragging}
.sortableData=${badgePath}
>
${badge}
</hui-badge-edit-mode>
`
: badge}
`
`;
}
)}
${editMode
? html`
@ -179,8 +200,8 @@ export class HuiViewBadges extends LitElement {
.badges {
display: flex;
align-items: flex-start;
flex-wrap: wrap;
justify-content: center;
flex-wrap: var(--badges-wrap, wrap);
justify-content: var(--badges-aligmnent, center);
gap: 8px;
margin: 0;
}
@ -197,6 +218,7 @@ export class HuiViewBadges extends LitElement {
display: flex;
flex-direction: row;
align-items: center;
outline: none;
gap: 8px;
height: 36px;
padding: 6px 20px 6px 20px;

View File

@ -50,6 +50,9 @@ export class HuiCardEditMode extends LitElement {
@property({ type: Boolean, attribute: "no-duplicate" })
public noDuplicate = false;
@property({ type: Boolean, attribute: "no-move" })
public noMove = false;
@state()
public _menuOpened = false;
@ -179,23 +182,39 @@ export class HuiCardEditMode extends LitElement {
)}
</ha-list-item>
`}
${this.noMove
? nothing
: html`
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"copy"}
>
<ha-svg-icon slot="graphic" .path=${mdiContentCopy}></ha-svg-icon>
${this.hass.localize("ui.panel.lovelace.editor.edit_card.copy")}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCopy}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.copy"
)}
</ha-list-item>
<ha-list-item
graphic="icon"
@click=${this._handleAction}
.action=${"cut"}
>
<ha-svg-icon slot="graphic" .path=${mdiContentCut}></ha-svg-icon>
${this.hass.localize("ui.panel.lovelace.editor.edit_card.cut")}
<ha-svg-icon
slot="graphic"
.path=${mdiContentCut}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_card.cut"
)}
</ha-list-item>
<li divider role="separator"></li>
`}
${this.noDuplicate && this.noEdit && this.noMove
? nothing
: html`<li divider role="separator"></li>`}
<ha-list-item
graphic="icon"
class="warning"

View File

@ -0,0 +1,245 @@
import type { ActionDetail } from "@material/mwc-list";
import { mdiClose, mdiDotsVertical, mdiPlaylistEdit } from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { deepEqual } from "../../../../common/util/deep-equal";
import "../../../../components/ha-button";
import "../../../../components/ha-circular-progress";
import "../../../../components/ha-dialog";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import type { LovelaceViewHeaderConfig } from "../../../../data/lovelace/config/view";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import "./hui-view-header-settings-editor";
import type { EditViewHeaderDialogParams } from "./show-edit-view-header-dialog";
@customElement("hui-dialog-edit-view-header")
export class HuiDialogEditViewHeader extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _params?: EditViewHeaderDialogParams;
@state() private _config?: LovelaceViewHeaderConfig;
@state() private _saving = false;
@state() private _dirty = false;
@state() private _yamlMode = false;
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
protected updated(changedProperties: PropertyValues) {
if (this._yamlMode && changedProperties.has("_yamlMode")) {
const config = {
...this._config,
};
this._editor?.setValue(config);
}
}
public showDialog(params: EditViewHeaderDialogParams): void {
this._params = params;
this._dirty = false;
this._config = this._params.config;
}
public closeDialog(): void {
this._params = undefined;
this._config = undefined;
this._yamlMode = false;
this._dirty = false;
this._saving = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
if (!this._params || !this.hass) {
return nothing;
}
let content;
if (this._yamlMode) {
content = html`
<ha-yaml-editor
.hass=${this.hass}
dialogInitialFocus
@value-changed=${this._viewYamlChanged}
></ha-yaml-editor>
`;
} else {
content = html`
<hui-view-header-settings-editor
.hass=${this.hass}
.config=${this._config}
@config-changed=${this._configChanged}
></hui-view-header-settings-editor>
`;
}
const title = this.hass.localize(
"ui.panel.lovelace.editor.edit_view_header.header"
);
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
@closed=${this.closeDialog}
.heading=${title}
class=${classMap({
"yaml-mode": this._yamlMode,
})}
>
<ha-dialog-header show-border slot="heading">
<ha-icon-button
slot="navigationIcon"
dialogAction="cancel"
.label=${this.hass!.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<h2 slot="title">${title}</h2>
<ha-button-menu
slot="actionItems"
fixed
corner="BOTTOM_END"
menu-corner="END"
@action=${this._handleAction}
@closed=${stopPropagation}
>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon">
${this.hass!.localize(
`ui.panel.lovelace.editor.edit_view_header.edit_${!this._yamlMode ? "yaml" : "ui"}`
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPlaylistEdit}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
</ha-dialog-header>
${content}
<ha-button
slot="primaryAction"
.disabled=${!this._config || this._saving || !this._dirty}
@click=${this._save}
>
${this._saving
? html`<ha-circular-progress
indeterminate
size="small"
aria-label="Saving"
></ha-circular-progress>`
: nothing}
${this.hass!.localize("ui.common.save")}</ha-button
>
</ha-dialog>
`;
}
private async _handleAction(ev: CustomEvent<ActionDetail>) {
ev.stopPropagation();
ev.preventDefault();
switch (ev.detail.index) {
case 0:
this._yamlMode = !this._yamlMode;
break;
}
}
private _configChanged(ev: CustomEvent): void {
if (
ev.detail &&
ev.detail.config &&
!deepEqual(this._config, ev.detail.config)
) {
this._config = ev.detail.config;
this._dirty = true;
}
}
private _viewYamlChanged(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.isValid) {
return;
}
this._config = ev.detail.value;
this._dirty = true;
}
private async _save(): Promise<void> {
if (!this._params || !this._config) {
return;
}
this._saving = true;
try {
await this._params.saveConfig(this._config);
this.closeDialog();
} catch (err: any) {
showAlertDialog(this, {
text: `${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view_header.saving_failed"
)}: ${err.message}`,
});
} finally {
this._saving = false;
}
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-dialog {
/* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */
--vertical-align-dialog: flex-start;
--dialog-surface-margin-top: 40px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
/* When in fullscreen dialog should be attached to top */
ha-dialog {
--dialog-surface-margin-top: 0px;
}
}
ha-dialog.yaml-mode {
--dialog-content-padding: 0;
}
h2 {
margin: 0;
font-size: inherit;
font-weight: inherit;
}
@media all and (min-width: 600px) {
ha-dialog {
--mdc-dialog-min-width: 600px;
}
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-dialog-edit-view-header": HuiDialogEditViewHeader;
}
}

View File

@ -0,0 +1,155 @@
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import { computeRTL } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-form/ha-form";
import type {
HaFormSchema,
SchemaUnion,
} from "../../../../components/ha-form/types";
import type {
LovelaceViewConfig,
LovelaceViewHeaderConfig,
} from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import {
DEFAULT_VIEW_HEADER_BADGES_POSITION,
DEFAULT_VIEW_HEADER_LAYOUT,
} from "../../views/hui-view-header";
import { listenMediaQuery } from "../../../../common/dom/media_query";
@customElement("hui-view-header-settings-editor")
export class HuiViewHeaderSettingsEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public config?: LovelaceViewHeaderConfig;
@state({ attribute: false }) private narrow = false;
private _unsubMql?: () => void;
connectedCallback(): void {
super.connectedCallback();
this._unsubMql = listenMediaQuery("(max-width: 600px)", (matches) => {
this.narrow = matches;
});
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubMql?.();
this._unsubMql = undefined;
}
private _schema = memoizeOne(
(localize: LocalizeFunc, isRTL: boolean, narrow: boolean) =>
[
{
name: "layout",
selector: {
select: {
mode: "box",
box_max_columns: narrow ? 1 : 3,
options: ["responsive", "start", "center"].map((value) => {
const labelKey =
value === "start" && isRTL ? `${value}_rtl` : value;
return {
value,
label: localize(
`ui.panel.lovelace.editor.edit_view_header.settings.layout_options.${labelKey}`
),
description: localize(
`ui.panel.lovelace.editor.edit_view_header.settings.layout_options.${value}_description`
),
image: {
src: `/static/images/form/view_header_layout_${value}.svg`,
src_dark: `/static/images/form/view_header_layout_${value}_dark.svg`,
flip_rtl: true,
},
};
}),
},
},
},
{
name: "badges_position",
selector: {
select: {
mode: "box",
options: ["bottom", "top"].map((value) => ({
value,
label: localize(
`ui.panel.lovelace.editor.edit_view_header.settings.badges_position_options.${value}`
),
image: {
src: `/static/images/form/view_header_badges_position_${value}.svg`,
src_dark: `/static/images/form/view_header_badges_position_${value}_dark.svg`,
flip_rtl: true,
},
})),
},
},
},
] as const satisfies HaFormSchema[]
);
protected render() {
if (!this.hass) {
return nothing;
}
const data = {
layout: this.config?.layout || DEFAULT_VIEW_HEADER_LAYOUT,
badges_position:
this.config?.badges_position || DEFAULT_VIEW_HEADER_BADGES_POSITION,
};
const narrow = this.narrow;
const isRTL = computeRTL(this.hass);
const schema = this._schema(this.hass.localize, isRTL, narrow);
return html`
<ha-form
.hass=${this.hass}
.data=${data}
.schema=${schema}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const newData = ev.detail.value as LovelaceViewConfig;
const config: LovelaceViewHeaderConfig = {
...this.config,
...newData,
};
fireEvent(this, "config-changed", { config });
}
private _computeLabel = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
switch (schema.name) {
case "layout":
case "badges_position":
return this.hass.localize(
`ui.panel.lovelace.editor.edit_view_header.settings.${schema.name}`
);
default:
return "";
}
};
}
declare global {
interface HTMLElementTagNameMap {
"hui-view-header-settings-editor": HuiViewHeaderSettingsEditor;
}
}

View File

@ -0,0 +1,18 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LovelaceViewHeaderConfig } from "../../../../data/lovelace/config/view";
export interface EditViewHeaderDialogParams {
saveConfig: (config: LovelaceViewHeaderConfig) => void;
config: LovelaceViewHeaderConfig;
}
export const showEditViewHeaderDialog = (
element: HTMLElement,
dialogParams: EditViewHeaderDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "hui-dialog-edit-view-header",
dialogImport: () => import("./hui-dialog-edit-view-header"),
dialogParams: dialogParams,
});
};

View File

@ -193,7 +193,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
private _cardRemoved(ev) {
ev.stopPropagation();
// Do nothing, it's handle by the "card-added" event from the new parent.
// Do nothing, it's handled by the "item-added" event from the new parent.
}
private _dragStart() {

View File

@ -25,7 +25,7 @@ import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types";
import type { HuiBadge } from "../badges/hui-badge";
import "../badges/hui-view-badges";
import "./hui-view-header";
import type { HuiCard } from "../cards/hui-card";
import "../components/hui-badge-edit-mode";
import {
@ -61,10 +61,10 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
@property({ attribute: false }) public sections: HuiSection[] = [];
@property({ attribute: false }) public badges: HuiBadge[] = [];
@property({ attribute: false }) public cards: HuiCard[] = [];
@property({ attribute: false }) public badges: HuiBadge[] = [];
@state() private _config?: LovelaceViewConfig;
@state() private _sectionColumnCount = 0;
@ -154,15 +154,16 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const maxColumnCount = this._columnsController.value ?? 1;
return html`
<hui-view-badges
<hui-view-header
.hass=${this.hass}
.badges=${this.badges}
.lovelace=${this.lovelace}
.viewIndex=${this.index}
.config=${this._config?.header}
style=${styleMap({
"--max-column-count": maxColumnCount,
})}
></hui-view-badges>
></hui-view-header>
<ha-sortable
.disabled=${!editMode}
@item-moved=${this._sectionMoved}
@ -184,13 +185,11 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
sections,
(section) => this._getSectionKey(section),
(section, idx) => {
const sectionConfig = this._config?.sections?.[idx];
const columnSpan = Math.min(
sectionConfig?.column_span || 1,
section.config.column_span || 1,
maxColumnCount
);
const rowSpan = sectionConfig?.row_span || 1;
const rowSpan = section.config.row_span || 1;
return html`
<div
@ -551,7 +550,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
border-radius: var(--ha-card-border-radius, 12px);
}
hui-view-badges {
hui-view-header {
display: block;
text-align: center;
padding: 0 var(--column-gap);

View File

@ -0,0 +1,440 @@
import { mdiPencil, mdiPlus } from "@mdi/js";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import "../../../components/ha-ripple";
import "../../../components/ha-sortable";
import "../../../components/ha-svg-icon";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type {
LovelaceViewConfig,
LovelaceViewHeaderConfig,
} from "../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../types";
import type { HuiBadge } from "../badges/hui-badge";
import "../badges/hui-view-badges";
import type { HuiCard } from "../cards/hui-card";
import "../components/hui-badge-edit-mode";
import { replaceView } from "../editor/config-util";
import { showEditViewHeaderDialog } from "../editor/view-header/show-edit-view-header-dialog";
import type { Lovelace } from "../types";
import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog";
export const DEFAULT_VIEW_HEADER_LAYOUT = "center";
export const DEFAULT_VIEW_HEADER_BADGES_POSITION = "bottom";
@customElement("hui-view-header")
export class HuiViewHeader extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace!: Lovelace;
@property({ attribute: false }) public card?: HuiCard;
@property({ attribute: false }) public badges: HuiBadge[] = [];
@property({ attribute: false }) public config?: LovelaceViewHeaderConfig;
@property({ attribute: false }) public viewIndex!: number;
private _checkHidden() {
const allHidden =
!this.card &&
!this.lovelace.editMode &&
this.badges.every((badges) => badges.hidden);
this.toggleAttribute("hidden", allHidden);
}
private _badgeVisibilityChanged = () => {
this._checkHidden();
};
connectedCallback(): void {
super.connectedCallback();
this.addEventListener(
"badge-visibility-changed",
this._badgeVisibilityChanged
);
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.removeEventListener(
"badge-visibility-changed",
this._badgeVisibilityChanged
);
}
willUpdate(changedProperties: PropertyValues<typeof this>): void {
if (
changedProperties.has("badges") ||
changedProperties.has("lovelace") ||
changedProperties.has("card")
) {
this._checkHidden();
}
if (changedProperties.has("config")) {
if (this.config?.card) {
this.card = this._createCardElement(this.config.card);
} else {
this.card = undefined;
}
return;
}
if (this.card) {
if (changedProperties.has("hass")) {
this.card.hass = this.hass;
}
if (changedProperties.has("lovelace")) {
this.card.preview = this.lovelace.editMode;
}
}
}
private _createCardElement(cardConfig: LovelaceCardConfig) {
const element = document.createElement("hui-card");
element.hass = this.hass;
element.preview = this.lovelace.editMode;
element.config = cardConfig;
element.load();
return element;
}
private _addCard() {
const cardConfig: LovelaceCardConfig = {
type: "markdown",
text_only: true,
content: this.hass.localize(
"ui.panel.lovelace.editor.edit_view_header.default_title",
{ user: "{{ user }}" }
),
};
showEditCardDialog(this, {
cardConfig,
lovelaceConfig: this.lovelace.config,
saveCardConfig: (newCardConfig: LovelaceCardConfig) => {
const newConfig = { ...this.config };
newConfig.card = newCardConfig;
this._saveHeaderConfig(newConfig);
},
isNew: true,
});
}
private _deleteCard(ev) {
ev.stopPropagation();
const newConfig = { ...this.config };
delete newConfig.card;
this._saveHeaderConfig(newConfig);
}
private _editCard(ev) {
ev.stopPropagation();
const cardConfig = this.config!.card;
if (!cardConfig) {
return;
}
showEditCardDialog(this, {
cardConfig,
lovelaceConfig: this.lovelace.config,
saveCardConfig: (newCardConfig: LovelaceCardConfig) => {
const newConfig = { ...this.config };
newConfig.card = newCardConfig;
this._saveHeaderConfig(newConfig);
},
});
}
private _saveHeaderConfig(headerConfig: LovelaceViewHeaderConfig) {
const viewConfig = this.lovelace.config.views[
this.viewIndex
] as LovelaceViewConfig;
const config = { ...viewConfig };
config.header = headerConfig;
const updatedConfig = replaceView(
this.hass,
this.lovelace.config,
this.viewIndex,
config
);
this.lovelace.saveConfig(updatedConfig);
}
private _configure = () => {
showEditViewHeaderDialog(this, {
config: this.config!,
saveConfig: (config: LovelaceViewHeaderConfig) => {
this._saveHeaderConfig(config);
},
});
};
render() {
if (!this.lovelace) return nothing;
const editMode = Boolean(this.lovelace?.editMode);
const card = this.card;
const layout = this.config?.layout ?? DEFAULT_VIEW_HEADER_LAYOUT;
const badgesPosition =
this.config?.badges_position ?? DEFAULT_VIEW_HEADER_BADGES_POSITION;
const hasHeading = card !== undefined;
const hasBadges = this.badges.length > 0;
return html`
${editMode
? html`
<div class="actions-container">
<div class="actions">
<ha-icon-button
.label=${this.hass.localize("ui.common.edit")}
@click=${this._configure}
.path=${mdiPencil}
></ha-icon-button>
</div>
</div>
`
: nothing}
<div class="container ${editMode ? "edit-mode" : ""}">
<div
class="layout ${classMap({
[layout]: true,
[`badges-${badgesPosition}`]: true,
"has-heading": hasHeading,
"has-badges": hasBadges,
})}"
>
${card || editMode
? html`
<div class="heading">
${editMode
? card
? html`
<hui-card-edit-mode
@ll-edit-card=${this._editCard}
@ll-delete-card=${this._deleteCard}
.hass=${this.hass}
.lovelace=${this.lovelace!}
.path=${[0]}
no-duplicate
no-move
>
${card}
</hui-card-edit-mode>
`
: html`
<button class="add" @click=${this._addCard}>
<ha-ripple></ha-ripple>
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize(
"ui.panel.lovelace.editor.edit_view_header.add_title"
)}
</button>
`
: card}
</div>
`
: nothing}
${this.lovelace && (editMode || this.badges.length > 0)
? html`
<div class="badges ${badgesPosition}">
<hui-view-badges
.badges=${this.badges}
.hass=${this.hass}
.lovelace=${this.lovelace!}
.viewIndex=${this.viewIndex!}
.showAddLabel=${this.badges.length === 0}
></hui-view-badges>
</div>
`
: nothing}
</div>
</div>
`;
}
static styles = css`
:host([hidden]) {
display: none !important;
}
.container {
position: relative;
}
.actions-container {
position: relative;
height: 34px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.actions {
z-index: 1;
position: absolute;
height: 36px;
bottom: -2px;
right: 0;
inset-inline-end: 0;
inset-inline-start: initial;
opacity: 1;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s ease-in-out;
border-radius: var(--ha-card-border-radius, 12px);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
background: var(--secondary-background-color);
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--primary-text-color);
}
.layout {
position: relative;
display: flex;
flex-direction: column;
gap: 24px 8px;
--spacing: 24px;
}
.layout.has-heading {
margin-top: var(--spacing);
}
.heading {
position: relative;
flex: 1;
width: 100%;
max-width: 700px;
display: flex;
}
.heading > * {
width: 100%;
height: 100%;
}
.badges {
position: relative;
flex: 1;
display: flex;
}
hui-view-badges {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
--badges-aligmnent: flex-start;
}
/* Layout */
.layout {
align-items: flex-start;
}
.layout.center {
align-items: center;
}
.layout .heading {
text-align: start;
}
.layout.center .heading {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.layout.center hui-view-badges {
--badges-aligmnent: center;
}
@media (min-width: 768px) {
.layout.responsive.has-heading {
flex-direction: row;
align-items: flex-end;
}
.layout.responsive.has-heading hui-view-badges {
--badges-aligmnent: flex-end;
}
}
.layout.badges-top {
flex-direction: column-reverse;
}
.layout.badges-top.has-badges {
margin-top: 0;
}
@media (min-width: 768px) {
.layout.responsive.badges-top.has-heading {
flex-direction: row;
align-items: flex-start;
margin-top: var(--spacing);
}
}
.container.edit-mode {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
border: 2px dashed var(--divider-color);
border-start-end-radius: 0;
}
.container.edit-mode .content {
min-height: 36px;
}
.add {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
outline: none;
gap: 8px;
height: 36px;
padding: 6px 20px 6px 20px;
box-sizing: border-box;
width: auto;
border-radius: var(--ha-card-border-radius, 12px);
background-color: transparent;
border-width: 2px;
border-style: dashed;
border-color: var(--primary-color);
--mdc-icon-size: 18px;
cursor: pointer;
font-size: 14px;
color: var(--primary-text-color);
--ha-ripple-color: var(--primary-color);
--ha-ripple-hover-opacity: 0.04;
--ha-ripple-pressed-opacity: 0.12;
}
.add:focus {
border-style: solid;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"hui-view-header": HuiViewHeader;
}
}

View File

@ -6575,6 +6575,31 @@
"error_same_url": "You cannot save a view with the same URL as a different existing view.",
"move_to_dashboard": "Move to dashboard"
},
"edit_view_header": {
"add_title": "Add title",
"header": "Header settings",
"edit_ui": "[%key:ui::panel::lovelace::editor::edit_view::edit_ui%]",
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
"saving_failed": "[%key:ui::panel::lovelace::editor::edit_view::saving_failed%]",
"default_title": "# Hello {user}\nAdd your text here, template variables are supported ✨",
"settings": {
"layout": "Layout",
"layout_options": {
"responsive": "Responsive",
"responsive_description": "Stacked on mobile",
"start": "Left aligned",
"start_description": "Always stacked",
"start_rtl": "Right aligned",
"center": "Centered",
"center_description": "Always stacked"
},
"badges_position": "Badges position",
"badges_position_options": {
"top": "Top",
"bottom": "Bottom"
}
}
},
"edit_badges": {
"view_no_badges": "Badges are not be supported by the current view type."
},
@ -6694,8 +6719,6 @@
"edit_ui": "[%key:ui::panel::lovelace::editor::edit_view::edit_ui%]",
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
"settings": {
"title": "Title",
"title_helper": "The title will appear at the top of section. Leave empty to hide the title.",
"column_span": "Width",
"column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)"
},