diff --git a/src/components/ha-clickable-list-item.ts b/src/components/ha-clickable-list-item.ts
new file mode 100644
index 0000000000..fee471c8f4
--- /dev/null
+++ b/src/components/ha-clickable-list-item.ts
@@ -0,0 +1,52 @@
+import "./ha-clickable-ripple";
+import { ListItem } from "@material/mwc-list/mwc-list-item";
+// import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
+import { css, CSSResult, customElement } from "lit-element";
+import { html } from "lit-html";
+
+@customElement("ha-clickable-list-item")
+export class HaClickableListItem extends ListItem {
+ public href?: string;
+
+ public render() {
+ const r = super.render();
+ const href = this.href ? `/${this.href}` : "";
+
+ return html` ${this.renderRipple()}
+
+ ${r}
+ `;
+ }
+
+ static get styles(): CSSResult[] {
+ return [
+ super.styles,
+ css`
+ :host {
+ padding-left: 0px;
+ padding-right: 0px;
+ }
+
+ :host([graphic="avatar"]:not([twoLine])),
+ :host([graphic="icon"]:not([twoLine])) {
+ height: 48px;
+ }
+
+ a {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ padding-left: var(--mdc-list-side-padding, 16px);
+ padding-right: var(--mdc-list-side-padding, 16px);
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-clickable-list-item": HaClickableListItem;
+ }
+}
diff --git a/src/components/ha-clickable-ripple.ts b/src/components/ha-clickable-ripple.ts
new file mode 100644
index 0000000000..e33d6c106a
--- /dev/null
+++ b/src/components/ha-clickable-ripple.ts
@@ -0,0 +1,70 @@
+import { ListItem } from "@material/mwc-list/mwc-list-item";
+import { Ripple } from "@material/mwc-ripple";
+import { RippleBase } from "@material/mwc-ripple/mwc-ripple-base";
+// import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
+import { css, CSSResult, customElement } from "lit-element";
+import { html } from "lit-html";
+
+@customElement("ha-clickable-ripple")
+export class HaClickableRipple extends RippleBase {
+ public href?: string;
+
+ // public render() {
+ // const r = super.render();
+ // // prettier-ignore
+ // return html`
+ //
+ // ${r}
+ // `;
+ // }
+
+ public render() {
+ /** @classMap */
+ const classes = {
+ "mdc-ripple-upgraded--unbounded": this.unbounded,
+ "mdc-ripple-upgraded--background-focused": this.bgFocused,
+ "mdc-ripple-upgraded--foreground-activation": super.fgActivation,
+ "mdc-ripple-upgraded--foreground-deactivation": super.fgDeactivation,
+ hover: super.hovering,
+ primary: this.primary,
+ accent: this.accent,
+ disabled: this.disabled,
+ activated: this.activated,
+ selected: this.selected,
+ };
+ return html`
`;
+ }
+
+ static get styles(): CSSResult[] {
+ return [
+ super.styles,
+ css`
+ /* a {
+ width: 100%;
+ height: 100%;
+ border: 1px solid black;
+ vertical-align: middle;
+ } */
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-clickable-ripple": HaClickableRipple;
+ }
+}
diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts
index 83502db730..652da7a534 100644
--- a/src/components/ha-sidebar.ts
+++ b/src/components/ha-sidebar.ts
@@ -1,3 +1,6 @@
+import "./ha-clickable-list-item";
+import "@material/mwc-list/mwc-list-item";
+import "@material/mwc-list/mwc-list";
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button";
import {
@@ -9,10 +12,6 @@ import {
mdiPlus,
mdiViewDashboard,
} from "@mdi/js";
-import "@polymer/paper-item/paper-icon-item";
-import type { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
-import "@polymer/paper-item/paper-item";
-import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
@@ -210,10 +209,10 @@ class HaSidebar extends LitElement {
return html`
${this._renderHeader()}
${this._renderAllPanels()}
- ${this._renderDivider()}
+ ${this._renderUtilityPanels()}
${this._renderNotifications()}
${this._renderUserItem()}
-
+ ${this._renderSpacer()}
`;
}
@@ -338,10 +337,9 @@ class HaSidebar extends LitElement {
// prettier-ignore
return html`
-
+
`;
}
@@ -375,13 +373,14 @@ class HaSidebar extends LitElement {
if (!panel) {
return "";
}
- return html`
- `;
+ `;
})}
${this._renderSpacer()}`
: ""}`;
}
- private _renderDivider() {
- return html``;
- }
-
private _renderSpacer() {
- return html``;
+ return html``;
}
private _renderNotifications() {
@@ -419,20 +414,19 @@ class HaSidebar extends LitElement {
}
}
- return html`
-
-
+
${!this.expanded && notificationCount > 0
? html`
-
+
${notificationCount}
`
@@ -441,45 +435,48 @@ class HaSidebar extends LitElement {
${this.hass.localize("ui.notification_drawer.title")}
${this.expanded && notificationCount > 0
- ? html` ${notificationCount} `
+ ? html`
+ ${notificationCount}
+ `
: ""}
-
-
`;
+
+ `;
}
private _renderUserItem() {
- return html`
-
-
+
-
- ${this.hass.user ? this.hass.user.name : ""}
-
-
- `;
+
+ ${this.hass.user ? this.hass.user.name : ""}
+
+ `;
}
private _renderExternalConfiguration() {
return html`${this._externalConfig && this._externalConfig.hasSettingsScreen
? html`
-
-
-
-
- ${this.hass.localize("ui.sidebar.external_app_configuration")}
-
-
-
+
+
+ ${this.hass.localize("ui.sidebar.external_app_configuration")}
+
+
`
: ""}`;
}
@@ -520,7 +513,7 @@ class HaSidebar extends LitElement {
if (!Sortable) {
const [sortableImport, sortStylesImport] = await Promise.all([
import("sortablejs/modular/sortable.core.esm"),
- import("../resources/ha-sortable-style"),
+ import("../resources/ha-sortable-style-ha-clickable"),
]);
const style = document.createElement("style");
@@ -541,8 +534,9 @@ class HaSidebar extends LitElement {
this._sortable = new Sortable(this.shadowRoot!.getElementById("sortable"), {
animation: 150,
fallbackClass: "sortable-fallback",
+ // fallbackTolerance: 15,
dataIdAttr: "data-panel",
- handle: "paper-icon-item",
+ handle: "ha-clickable-list-item",
onSort: async () => {
this._panelOrder = this._sortable.toArray();
},
@@ -604,7 +598,7 @@ class HaSidebar extends LitElement {
clearTimeout(this._mouseLeaveTimeout);
this._mouseLeaveTimeout = undefined;
}
- this._showTooltip(ev.currentTarget as PaperIconItemElement);
+ this._showTooltip(ev.currentTarget);
}
private _itemMouseLeave() {
@@ -620,7 +614,7 @@ class HaSidebar extends LitElement {
if (this.expanded || ev.target.nodeName !== "A") {
return;
}
- this._showTooltip(ev.target.querySelector("paper-icon-item"));
+ this._showTooltip(ev.target.querySelector("ha-clickable-list-item"));
}
private _listboxFocusOut() {
@@ -644,13 +638,13 @@ class HaSidebar extends LitElement {
this._recentKeydownActiveUntil = new Date().getTime() + 100;
}
- private _showTooltip(item: PaperIconItemElement) {
+ private _showTooltip(item) {
if (this._tooltipHideTimeout) {
clearTimeout(this._tooltipHideTimeout);
this._tooltipHideTimeout = undefined;
}
const tooltip = this._tooltip;
- const listbox = this.shadowRoot!.querySelector("paper-listbox")!;
+ const listbox = this.shadowRoot!.querySelector("mwc-list")!;
let top = item.offsetTop + 11;
if (listbox.contains(item)) {
top -= listbox.scrollTop;
@@ -689,21 +683,77 @@ class HaSidebar extends LitElement {
fireEvent(this, "hass-toggle-menu");
}
- private _renderPanels(panels: PanelInfo[]) {
- return panels.map((panel) =>
- this._renderPanel(
- panel.url_path,
- panel.url_path === this.hass.defaultPanel
- ? panel.title || this.hass.localize("panel.states")
- : this.hass.localize(`panel.${panel.title}`) || panel.title,
- panel.icon,
- panel.url_path === this.hass.defaultPanel && !panel.icon
- ? mdiViewDashboard
- : undefined
- )
+ private _isUtilityPanel(panel: PanelInfo) {
+ return (
+ panel.component_name === "developer-tools" ||
+ panel.component_name === "config" ||
+ panel.component_name === "external-config"
);
}
+ private _renderPanels(panels: PanelInfo[]) {
+ return panels
+ .filter((panel) => !this._isUtilityPanel(panel))
+ .map((panel) =>
+ this._renderPanel(
+ panel.url_path,
+ panel.url_path === this.hass.defaultPanel
+ ? panel.title || this.hass.localize("panel.states")
+ : this.hass.localize(`panel.${panel.title}`) || panel.title,
+ panel.icon,
+ panel.url_path === this.hass.defaultPanel && !panel.icon
+ ? mdiViewDashboard
+ : undefined
+ )
+ );
+ }
+
+ private _renderUtilityPanels() {
+ const [, afterSpacer] = computePanels(
+ this.hass.panels,
+ this.hass.defaultPanel,
+ this._panelOrder,
+ this._hiddenPanels
+ );
+
+ // prettier-ignore
+ return html`
+
+ ${afterSpacer.map((panel) =>
+ this._renderPanel(
+ panel.url_path,
+ panel.url_path === this.hass.defaultPanel
+ ? panel.title || this.hass.localize("panel.states")
+ : this.hass.localize(`panel.${panel.title}`) || panel.title,
+ panel.icon,
+ panel.url_path === this.hass.defaultPanel && !panel.icon
+ ? mdiViewDashboard
+ : undefined
+ )
+ )}
+ ${afterSpacer.map((panel) =>
+ this._renderPanel(
+ panel.url_path,
+ panel.url_path === this.hass.defaultPanel
+ ? panel.title || this.hass.localize("panel.states")
+ : this.hass.localize(`panel.${panel.title}`) || panel.title,
+ panel.icon,
+ panel.url_path === this.hass.defaultPanel && !panel.icon
+ ? mdiViewDashboard
+ : undefined
+ )
+ )}
+
+
+ `;
+ }
+
private _renderPanel(
urlPath: string,
title: string | null,
@@ -711,23 +761,21 @@ class HaSidebar extends LitElement {
iconPath?: string | null
) {
return html`
-
-
- ${iconPath
- ? html``
- : html``}
- ${title}
-
+ ${iconPath
+ ? html``
+ : html``}
+
+ ${title}
+
${this.editMode
? html`
`
: ""}
-
+
`;
}
@@ -806,7 +854,6 @@ class HaSidebar extends LitElement {
}
:host([narrow]) .title {
margin: 0;
- padding: 0 16px;
}
:host([expanded]) .title {
display: initial;
@@ -822,21 +869,22 @@ class HaSidebar extends LitElement {
display: none;
}
- paper-listbox {
+ mwc-list.ha-scrollbar {
+ --mdc-list-vertical-padding: 4px 0;
padding: 4px 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
- height: calc(100% - var(--header-height) - 132px);
+ height: calc(100% - var(--header-height) - 350px);
height: calc(
- 100% - var(--header-height) - 132px - env(safe-area-inset-bottom)
+ 100% - var(--header-height) - 350px - env(safe-area-inset-bottom)
);
overflow-x: hidden;
background: none;
margin-left: env(safe-area-inset-left);
}
- :host([rtl]) paper-listbox {
+ :host([rtl]) mwc-list {
margin-left: initial;
margin-right: env(safe-area-inset-right);
}
@@ -847,32 +895,36 @@ class HaSidebar extends LitElement {
font-weight: 500;
font-size: 14px;
position: relative;
- display: block;
outline: 0;
+ border: 1px solid black;
}
- paper-icon-item {
+ mwc-list-item {
box-sizing: border-box;
margin: 4px;
- padding-left: 12px;
border-radius: 4px;
- --paper-item-min-height: 40px;
width: 48px;
+ --mdc-list-item-graphic-margin: 16px;
+ --mdc-list-item-meta-size: 32px;
}
:host([expanded]) paper-icon-item {
width: 248px;
}
- :host([rtl]) paper-icon-item {
+ :host([rtl]) mwc-list-item {
padding-left: auto;
padding-right: 12px;
}
- ha-icon[slot="item-icon"],
- ha-svg-icon[slot="item-icon"] {
+ ha-icon[slot="graphic"],
+ ha-svg-icon[slot="graphic"] {
color: var(--sidebar-icon-color);
}
- .iron-selected paper-icon-item::before,
+ [slot="graphic"] {
+ width: 100%;
+ }
+
+ .iron-selected mwc-list-item::before,
a:not(.iron-selected):focus::before {
border-radius: 4px;
position: absolute;
@@ -885,7 +937,7 @@ class HaSidebar extends LitElement {
transition: opacity 15ms linear;
will-change: opacity;
}
- .iron-selected paper-icon-item::before {
+ .iron-selected mwc-list-item::before {
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
}
@@ -894,23 +946,24 @@ class HaSidebar extends LitElement {
opacity: var(--dark-divider-opacity);
margin: 4px 8px;
}
- .iron-selected paper-icon-item:focus::before,
- .iron-selected:focus paper-icon-item::before {
+ .iron-selected mwc-list-item:focus::before,
+ .iron-selected:focus mwc-list-item::before {
opacity: 0.2;
}
- .iron-selected paper-icon-item[pressed]:before {
+ .iron-selected mwc-list-item[pressed]:before {
opacity: 0.37;
}
- paper-icon-item span {
+ mwc-list-item span {
color: var(--sidebar-text-color);
font-weight: 500;
font-size: 14px;
+ width: 100%;
}
- a.iron-selected paper-icon-item ha-icon,
- a.iron-selected paper-icon-item ha-svg-icon {
+ a.iron-selected mwc-list-item ha-icon,
+ a.iron-selected mwc-list-item ha-svg-icon {
color: var(--sidebar-selected-icon-color);
}
@@ -918,22 +971,21 @@ class HaSidebar extends LitElement {
color: var(--sidebar-selected-text-color);
}
- paper-icon-item .item-text {
+ mwc-list-item mwc-list-item .item-text,
+ mwc-list-item .item-text {
display: none;
max-width: calc(100% - 56px);
}
- :host([expanded]) paper-icon-item .item-text {
+ :host([expanded]) mwc-list-item .item-text {
display: block;
}
.divider {
- bottom: 112px;
padding: 10px 0;
}
.divider::before {
- content: " ";
display: block;
- height: 1px;
+ height: 100px;
background-color: var(--divider-color);
}
.notifications-container {
@@ -957,10 +1009,10 @@ class HaSidebar extends LitElement {
margin-left: initial;
margin-right: env(safe-area-inset-right);
}
- .profile paper-icon-item {
+ .profile mwc-list-item {
padding-left: 4px;
}
- :host([rtl]) .profile paper-icon-item {
+ :host([rtl]) .profile mwc-list-item {
padding-left: auto;
padding-right: 4px;
}
@@ -972,15 +1024,16 @@ class HaSidebar extends LitElement {
}
.notification-badge {
- min-width: 20px;
+ /* min-width: 20px;
box-sizing: border-box;
border-radius: 50%;
font-weight: 400;
background-color: var(--accent-color);
- line-height: 20px;
+ line-height: 30px;
text-align: center;
- padding: 0px 6px;
+ padding: 0px 4px;
color: var(--text-accent-color, var(--text-primary-color));
+ font-size: 14px; */
}
ha-svg-icon + .notification-badge {
position: absolute;
@@ -994,6 +1047,12 @@ class HaSidebar extends LitElement {
pointer-events: none;
}
+ .bottom-spacer {
+ flex: 1;
+ pointer-events: none;
+ height: 100%;
+ }
+
.subheader {
color: var(--sidebar-text-color);
font-weight: 500;
diff --git a/src/resources/ha-sortable-style-ha-clickable.ts b/src/resources/ha-sortable-style-ha-clickable.ts
new file mode 100644
index 0000000000..c3529c9030
--- /dev/null
+++ b/src/resources/ha-sortable-style-ha-clickable.ts
@@ -0,0 +1,99 @@
+import { css } from "lit-element";
+
+export const sortableStyles = css`
+ #sortable ha-clickable-list-item:nth-of-type(2n) {
+ animation-name: keyframes1;
+ animation-iteration-count: infinite;
+ transform-origin: 50% 10%;
+ animation-delay: -0.75s;
+ animation-duration: 0.25s;
+ }
+
+ #sortable ha-clickable-list-item:nth-of-type(2n-1) {
+ animation-name: keyframes2;
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+ transform-origin: 30% 5%;
+ animation-delay: -0.5s;
+ animation-duration: 0.33s;
+ }
+
+ #sortable a {
+ height: 48px;
+ display: flex;
+ }
+
+ #sortable {
+ outline: none;
+ display: block !important;
+ }
+
+ .hidden-panel {
+ display: flex !important;
+ }
+
+ .sortable-fallback {
+ display: none;
+ }
+
+ .sortable-ghost {
+ opacity: 0.4;
+ }
+
+ .sortable-fallback {
+ opacity: 0;
+ }
+
+ @keyframes keyframes1 {
+ 0% {
+ transform: rotate(-1deg);
+ animation-timing-function: ease-in;
+ }
+
+ 50% {
+ transform: rotate(1.5deg);
+ animation-timing-function: ease-out;
+ }
+ }
+
+ @keyframes keyframes2 {
+ 0% {
+ transform: rotate(1deg);
+ animation-timing-function: ease-in;
+ }
+
+ 50% {
+ transform: rotate(-1.5deg);
+ animation-timing-function: ease-out;
+ }
+ }
+
+ .show-panel,
+ .hide-panel {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ --mdc-icon-button-size: 40px;
+ }
+
+ .hide-panel {
+ top: 4px;
+ right: 8px;
+ }
+
+ :host([expanded]) .hide-panel {
+ display: block;
+ }
+
+ :host([expanded]) .show-panel {
+ display: inline-flex;
+ }
+
+ ha-clickable-list-item.hidden-panel,
+ ha-clickable-list-item.hidden-panel span,
+ ha-clickable-list-item.hidden-panel ha-icon[slot="item-icon"] {
+ color: var(--secondary-text-color);
+ cursor: pointer;
+ }
+`;
diff --git a/src/resources/ha-sortable-style-mwc.ts b/src/resources/ha-sortable-style-mwc.ts
new file mode 100644
index 0000000000..f4f92a57d5
--- /dev/null
+++ b/src/resources/ha-sortable-style-mwc.ts
@@ -0,0 +1,99 @@
+import { css } from "lit-element";
+
+export const sortableStyles = css`
+ #sortable mwc-list-item:nth-of-type(2n) {
+ animation-name: keyframes1;
+ animation-iteration-count: infinite;
+ transform-origin: 50% 10%;
+ animation-delay: -0.75s;
+ animation-duration: 0.25s;
+ }
+
+ #sortable mwc-list-item:nth-of-type(2n-1) {
+ animation-name: keyframes2;
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+ transform-origin: 30% 5%;
+ animation-delay: -0.5s;
+ animation-duration: 0.33s;
+ }
+
+ #sortable mwc-list-item {
+ height: 48px;
+ display: flex;
+ }
+
+ #sortable {
+ outline: none;
+ display: block !important;
+ }
+
+ .hidden-panel {
+ display: flex !important;
+ }
+
+ .sortable-fallback {
+ display: none;
+ }
+
+ .sortable-ghost {
+ opacity: 0.4;
+ }
+
+ .sortable-fallback {
+ opacity: 0;
+ }
+
+ @keyframes keyframes1 {
+ 0% {
+ transform: rotate(-1deg);
+ animation-timing-function: ease-in;
+ }
+
+ 50% {
+ transform: rotate(1.5deg);
+ animation-timing-function: ease-out;
+ }
+ }
+
+ @keyframes keyframes2 {
+ 0% {
+ transform: rotate(1deg);
+ animation-timing-function: ease-in;
+ }
+
+ 50% {
+ transform: rotate(-1.5deg);
+ animation-timing-function: ease-out;
+ }
+ }
+
+ .show-panel,
+ .hide-panel {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ --mdc-icon-button-size: 40px;
+ }
+
+ .hide-panel {
+ top: 4px;
+ right: 8px;
+ }
+
+ :host([expanded]) .hide-panel {
+ display: block;
+ }
+
+ :host([expanded]) .show-panel {
+ display: inline-flex;
+ }
+
+ mwc-list-item.hidden-panel,
+ mwc-list-item.hidden-panel span,
+ mwc-list-item.hidden-panel ha-icon[slot="item-icon"] {
+ color: var(--secondary-text-color);
+ cursor: pointer;
+ }
+`;