1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-02-15 07:25:54 +00:00

Animate app side bar (#29026)

This commit is contained in:
uptimeZERO_
2026-02-03 14:55:10 +00:00
committed by GitHub
parent 22a7aa8f8e
commit c925053bb8
17 changed files with 264 additions and 39 deletions

View File

@@ -19,6 +19,7 @@ import { styleMap } from "lit/directives/style-map";
import { ensureArray } from "../../common/array/ensure-array";
import { getAllGraphColors } from "../../common/color/colors";
import { fireEvent } from "../../common/dom/fire_event";
import type { HASSDomEvent } from "../../common/dom/fire_event";
import { listenMediaQuery } from "../../common/dom/media_query";
import { themesContext } from "../../data/context";
import type { Themes } from "../../data/ws-themes";
@@ -93,10 +94,18 @@ export class HaChartBase extends LitElement {
private _resizeAnimationDuration?: number;
private _suspendResize = false;
private _layoutTransitionActive = false;
// @ts-ignore
private _resizeController = new ResizeController(this, {
callback: () => {
if (this.chart) {
if (this._suspendResize) {
this._shouldResizeChart = true;
return;
}
if (!this.chart.getZr().animation.isFinished()) {
this._shouldResizeChart = true;
} else {
@@ -191,6 +200,26 @@ export class HaChartBase extends LitElement {
() => window.removeEventListener("keyup", handleKeyUp)
);
}
const handleLayoutTransition: EventListener = (ev) => {
const event = ev as HASSDomEvent<HASSDomEvents["hass-layout-transition"]>;
this._layoutTransitionActive = Boolean(event.detail?.active);
this.toggleAttribute(
"layout-transition-active",
this._layoutTransitionActive
);
this._suspendResize = this._layoutTransitionActive;
if (!this._suspendResize) {
this._resizeChartIfNeeded();
}
};
window.addEventListener("hass-layout-transition", handleLayoutTransition);
this._listeners.push(() =>
window.removeEventListener(
"hass-layout-transition",
handleLayoutTransition
)
);
}
protected firstUpdated() {
@@ -998,19 +1027,29 @@ export class HaChartBase extends LitElement {
}
private _handleChartRenderFinished = () => {
if (this._shouldResizeChart) {
this.chart?.resize({
animation:
this._reducedMotion ||
typeof this._resizeAnimationDuration !== "number"
? undefined
: { duration: this._resizeAnimationDuration },
});
this._shouldResizeChart = false;
this._resizeAnimationDuration = undefined;
}
this._resizeChartIfNeeded();
};
private _resizeChartIfNeeded() {
if (!this.chart || !this._shouldResizeChart) {
return;
}
if (this._suspendResize) {
return;
}
if (!this.chart.getZr().animation.isFinished()) {
return;
}
this.chart.resize({
animation:
this._reducedMotion || typeof this._resizeAnimationDuration !== "number"
? undefined
: { duration: this._resizeAnimationDuration },
});
this._shouldResizeChart = false;
this._resizeAnimationDuration = undefined;
}
private _compareCustomLegendOptions(
oldOptions: ECOption | undefined,
newOptions: ECOption | undefined
@@ -1032,11 +1071,18 @@ export class HaChartBase extends LitElement {
display: block;
position: relative;
letter-spacing: normal;
overflow: visible;
}
:host([layout-transition-active]),
:host([layout-transition-active]) .container,
:host([layout-transition-active]) .chart-container {
overflow: hidden;
}
.container {
display: flex;
flex-direction: column;
position: relative;
overflow: visible;
}
.container.has-height {
max-height: var(--chart-max-height, 350px);
@@ -1044,6 +1090,7 @@ export class HaChartBase extends LitElement {
.chart-container {
width: 100%;
max-height: var(--chart-max-height, 350px);
overflow: visible;
}
.has-height .chart-container {
flex: 1;

View File

@@ -4,6 +4,18 @@ import type { PropertyValues } from "lit";
import { css } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import type { HASSDomEvent } from "../common/dom/fire_event";
declare global {
interface HASSDomEvents {
"hass-layout-transition": { active: boolean; reason?: string };
}
interface HTMLElementEventMap {
"hass-layout-transition": HASSDomEvent<
HASSDomEvents["hass-layout-transition"]
>;
}
}
const blockingElements = (document as any).$blockingElements;
@@ -15,6 +27,30 @@ export class HaDrawer extends DrawerBase {
private _rtlStyle?: HTMLElement;
private _sidebarTransitionActive = false;
private _handleDrawerTransitionStart = (ev: TransitionEvent) => {
if (ev.propertyName !== "width" || this._sidebarTransitionActive) {
return;
}
this._sidebarTransitionActive = true;
fireEvent(window, "hass-layout-transition", {
active: true,
reason: "sidebar",
});
};
private _handleDrawerTransitionEnd = (ev: TransitionEvent) => {
if (ev.propertyName !== "width" || !this._sidebarTransitionActive) {
return;
}
this._sidebarTransitionActive = false;
fireEvent(window, "hass-layout-transition", {
active: false,
reason: "sidebar",
});
};
protected createAdapter() {
return {
...super.createAdapter(),
@@ -63,6 +99,38 @@ export class HaDrawer extends DrawerBase {
}
}
protected firstUpdated() {
super.firstUpdated();
this.mdcRoot?.addEventListener(
"transitionstart",
this._handleDrawerTransitionStart
);
this.mdcRoot?.addEventListener(
"transitionend",
this._handleDrawerTransitionEnd
);
this.mdcRoot?.addEventListener(
"transitioncancel",
this._handleDrawerTransitionEnd
);
}
public disconnectedCallback() {
super.disconnectedCallback();
this.mdcRoot?.removeEventListener(
"transitionstart",
this._handleDrawerTransitionStart
);
this.mdcRoot?.removeEventListener(
"transitionend",
this._handleDrawerTransitionEnd
);
this.mdcRoot?.removeEventListener(
"transitioncancel",
this._handleDrawerTransitionEnd
);
}
private async _setupSwipe() {
const hammer = await import("../resources/hammer");
this._mc = new hammer.Manager(document, {
@@ -90,6 +158,16 @@ export class HaDrawer extends DrawerBase {
border-color: var(--divider-color, rgba(0, 0, 0, 0.12));
inset-inline-start: 0 !important;
inset-inline-end: initial !important;
transition-property: transform, width;
transition-duration:
var(--mdc-drawer-transition-duration, 0.2s),
var(--ha-animation-duration-normal);
transition-timing-function:
var(
--mdc-drawer-transition-timing-function,
cubic-bezier(0.4, 0, 0.2, 1)
),
ease;
}
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
z-index: 200;
@@ -103,6 +181,15 @@ export class HaDrawer extends DrawerBase {
direction: var(--direction);
width: 100%;
box-sizing: border-box;
transition:
padding-left var(--ha-animation-duration-normal) ease,
padding-inline-start var(--ha-animation-duration-normal) ease;
}
@media (prefers-reduced-motion: reduce) {
.mdc-drawer,
.mdc-drawer-app-content {
transition: none;
}
}
`,
];

View File

@@ -492,19 +492,22 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
@mouseleave=${this._itemMouseLeave}
>
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
${!this.alwaysExpand &&
(this._updatesCount > 0 || this._issuesCount > 0)
? html`<span class="badge" slot="start"
>${this._updatesCount + this._issuesCount}</span
>`
${this._updatesCount > 0 || this._issuesCount > 0
? html`
<span class="badge" slot="start">
${this._updatesCount + this._issuesCount}
</span>
`
: nothing}
<span class="item-text" slot="headline"
>${this.hass.localize("panel.config")}</span
>
${this.alwaysExpand && (this._updatesCount > 0 || this._issuesCount > 0)
? html`<span class="badge" slot="end"
>${this._updatesCount + this._issuesCount}</span
>`
${this._updatesCount > 0 || this._issuesCount > 0
? html`
<span class="badge" slot="end"
>${this._updatesCount + this._issuesCount}</span
>
`
: nothing}
</ha-md-list-item>
`;
@@ -524,13 +527,15 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
type="button"
>
<ha-svg-icon slot="start" .path=${mdiBell}></ha-svg-icon>
${!this.alwaysExpand && notificationCount > 0
? html`<span class="badge" slot="start">${notificationCount}</span>`
${notificationCount > 0
? html`
<span class="badge" slot="start"> ${notificationCount} </span>
`
: nothing}
<span class="item-text" slot="headline"
>${this.hass.localize("ui.notification_drawer.title")}</span
>
${this.alwaysExpand && notificationCount > 0
${notificationCount > 0
? html`<span class="badge" slot="end">${notificationCount}</span>`
: nothing}
</ha-md-list-item>
@@ -739,6 +744,8 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
);
font-size: var(--ha-font-size-xl);
align-items: center;
overflow: hidden;
width: calc(56px + var(--safe-area-inset-left, 0px));
padding-left: calc(
var(--ha-space-1) + var(--safe-area-inset-left, 0px)
);
@@ -747,6 +754,7 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
);
padding-inline-end: initial;
padding-top: var(--safe-area-inset-top, 0px);
transition: width var(--ha-animation-duration-normal) ease;
}
:host([expanded]) .menu {
width: calc(256px + var(--safe-area-inset-left, 0px));
@@ -761,15 +769,22 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
margin-left: 3px;
margin-inline-start: 3px;
margin-inline-end: initial;
width: 100%;
display: none;
flex: 1;
min-width: 0;
max-width: 0;
opacity: 0;
transition:
max-width var(--ha-animation-duration-normal) ease,
opacity var(--ha-animation-duration-normal) ease;
}
:host([narrow]) .title {
margin: 0;
padding: 0 var(--ha-space-4);
}
:host([expanded]) .title {
display: initial;
max-width: 100%;
opacity: 1;
transition-delay: 0ms, 80ms;
}
.panels-list {
@@ -827,6 +842,7 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
--md-list-item-leading-space: var(--ha-space-3);
--md-list-item-trailing-space: var(--ha-space-3);
--md-list-item-leading-icon-size: var(--ha-space-6);
transition: width var(--ha-animation-duration-normal) ease;
}
:host([expanded]) ha-md-list-item {
width: 248px;
@@ -867,11 +883,22 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
}
ha-md-list-item .item-text {
display: none;
display: block;
max-width: 0;
opacity: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: var(--ha-font-size-m);
font-weight: var(--ha-font-weight-medium);
transition:
max-width var(--ha-animation-duration-normal) ease,
opacity var(--ha-animation-duration-normal) ease;
}
:host([expanded]) ha-md-list-item .item-text {
max-width: 100%;
opacity: 1;
transition-delay: 0ms, 80ms;
display: block;
overflow: hidden;
text-overflow: ellipsis;
@@ -889,6 +916,9 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
background-color: var(--accent-color);
padding: 2px 6px;
color: var(--text-accent-color, var(--text-primary-color));
transition:
opacity var(--ha-animation-duration-normal) ease,
transform var(--ha-animation-duration-normal) ease;
}
ha-svg-icon + .badge {
@@ -900,6 +930,12 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
line-height: var(--ha-line-height-expanded);
padding: 0 var(--ha-space-1);
}
:host([expanded]) .badge[slot="start"],
:host(:not([expanded])) .badge[slot="end"] {
opacity: 0;
transform: scale(0.8);
pointer-events: none;
}
ha-md-list-item.user {
--md-list-item-leading-icon-size: var(--ha-space-10);
@@ -938,6 +974,15 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
-webkit-transform: scaleX(var(--scale-direction));
transform: scaleX(var(--scale-direction));
}
@media (prefers-reduced-motion: reduce) {
.menu,
ha-md-list-item,
ha-md-list-item .item-text,
.title {
transition: none;
}
}
`,
];
}

View File

@@ -36,10 +36,19 @@ export class HaTopAppBarFixed extends TopAppBarFixedBase {
);
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
transition:
width var(--ha-animation-duration-normal) ease,
padding-left var(--ha-animation-duration-normal) ease,
padding-right var(--ha-animation-duration-normal) ease;
}
:host([narrow]) .mdc-top-app-bar {
padding-left: var(--safe-area-inset-left);
}
@media (prefers-reduced-motion: reduce) {
.mdc-top-app-bar {
transition: none;
}
}
.mdc-top-app-bar__title {
font-size: var(--ha-font-size-xl);
padding-inline-start: var(--ha-space-6);

View File

@@ -288,10 +288,19 @@ export class TopAppBarBaseBase extends BaseElement {
);
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
transition:
width var(--ha-animation-duration-normal) ease,
padding-left var(--ha-animation-duration-normal) ease,
padding-right var(--ha-animation-duration-normal) ease;
}
:host([narrow]) .mdc-top-app-bar {
padding-left: var(--safe-area-inset-left);
}
@media (prefers-reduced-motion: reduce) {
.mdc-top-app-bar {
transition: none;
}
}
.mdc-top-app-bar--pane.mdc-top-app-bar--fixed-scrolled {
box-shadow: none;
}

View File

@@ -29,11 +29,11 @@
}
}
::view-transition-group(launch-screen) {
animation-duration: var(--ha-animation-base-duration, 350ms);
animation-duration: var(--ha-animation-duration-slow, 350ms);
animation-timing-function: ease-out;
}
::view-transition-old(launch-screen) {
animation: fade-out var(--ha-animation-base-duration, 350ms) ease-out;
animation: fade-out var(--ha-animation-duration-slow, 350ms) ease-out;
}
html {
background-color: var(--primary-background-color, #fafafa);

View File

@@ -186,7 +186,6 @@ class PanelClimate extends LitElement {
);
padding-top: var(--safe-area-inset-top);
z-index: 4;
transition: box-shadow 200ms linear;
display: flex;
flex-direction: row;
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);

View File

@@ -242,7 +242,7 @@ export class StorageBreakdownChart extends LitElement {
}
.chart-container {
transition: height var(--ha-animation-base-duration) ease;
transition: height var(--ha-animation-duration-slow) ease;
overflow: hidden;
}
@@ -264,7 +264,7 @@ export class StorageBreakdownChart extends LitElement {
ha-segmented-bar,
ha-sunburst-chart {
animation: fade-in var(--ha-animation-base-duration) ease;
animation: fade-in var(--ha-animation-duration-slow) ease;
}
@keyframes fade-in {

View File

@@ -699,6 +699,11 @@ class PanelEnergy extends LitElement {
var(--safe-area-inset-left, 0px)
);
inset-inline-end: var(--safe-area-inset-right, 0);
transition:
left var(--ha-animation-duration-normal) ease,
right var(--ha-animation-duration-normal) ease,
inset-inline-start var(--ha-animation-duration-normal) ease,
inset-inline-end var(--ha-animation-duration-normal) ease;
margin: 0 auto;
max-width: calc(min(470px, 100% - var(--ha-space-4)));
box-sizing: border-box;

View File

@@ -186,7 +186,6 @@ class PanelLight extends LitElement {
);
padding-top: var(--safe-area-inset-top);
z-index: 4;
transition: box-shadow 200ms linear;
display: flex;
flex-direction: row;
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);

View File

@@ -545,7 +545,7 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
);
pointer-events: none;
opacity: 0;
transition: opacity var(--ha-animation-base-duration) ease-in-out;
transition: opacity var(--ha-animation-duration-slow) ease-in-out;
}
.datepicker-open .backdrop {
opacity: 1;

View File

@@ -1299,7 +1299,6 @@ class HUIRoot extends LitElement {
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
z-index: 4;
transition: box-shadow 200ms linear;
}
.narrow .header {
width: calc(

View File

@@ -669,10 +669,19 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
);
border-top: 1px solid var(--divider-color);
margin-right: var(--safe-area-inset-right);
transition:
width var(--ha-animation-duration-normal) ease,
margin-left var(--ha-animation-duration-normal) ease,
margin-right var(--ha-animation-duration-normal) ease;
}
:host([narrow]) {
margin-left: var(--safe-area-inset-left);
}
@media (prefers-reduced-motion: reduce) {
:host {
transition: none;
}
}
ha-slider {
width: 100%;

View File

@@ -186,7 +186,6 @@ class PanelSecurity extends LitElement {
);
padding-top: var(--safe-area-inset-top);
z-index: 4;
transition: box-shadow 200ms linear;
display: flex;
flex-direction: row;
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);

View File

@@ -34,6 +34,20 @@ export const haStyle = css`
margin-inline-end: initial;
}
.header {
transition:
box-shadow 200ms linear,
width var(--ha-animation-duration-normal) ease,
padding-left var(--ha-animation-duration-normal) ease,
padding-right var(--ha-animation-duration-normal) ease;
}
@media (prefers-reduced-motion: reduce) {
.header {
transition: box-shadow 200ms linear;
}
}
h1 {
font-family: var(--ha-font-family-heading);
-webkit-font-smoothing: var(--ha-font-smoothing);

View File

@@ -55,12 +55,16 @@ export const coreStyles = css`
--ha-shadow-spread-md: 0;
--ha-shadow-spread-lg: 0;
--ha-animation-base-duration: 350ms;
--ha-animation-duration-fast: 150ms;
--ha-animation-duration-normal: 250ms;
--ha-animation-duration-slow: 350ms;
}
@media (prefers-reduced-motion: reduce) {
html {
--ha-animation-base-duration: 0ms;
--ha-animation-duration-fast: 0ms;
--ha-animation-duration-normal: 0ms;
--ha-animation-duration-slow: 0ms;
}
}
`;

View File

@@ -18,7 +18,7 @@ export const removeLaunchScreen = () => {
launchScreenElement.classList.add("removing");
const durationFromCss = getComputedStyle(document.documentElement)
.getPropertyValue("--ha-animation-base-duration")
.getPropertyValue("--ha-animation-duration-slow")
.trim();
setTimeout(() => {