diff --git a/src/components/ha-sidebar.js b/src/components/ha-sidebar.js
deleted file mode 100644
index a3d87db4c7..0000000000
--- a/src/components/ha-sidebar.js
+++ /dev/null
@@ -1,341 +0,0 @@
-import "@polymer/app-layout/app-toolbar/app-toolbar";
-import "@polymer/iron-flex-layout/iron-flex-layout-classes";
-import "@polymer/paper-icon-button/paper-icon-button";
-import "@polymer/paper-item/paper-icon-item";
-import "@polymer/paper-item/paper-item";
-import "@polymer/paper-listbox/paper-listbox";
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-import "./ha-icon";
-
-import "../util/hass-translation";
-import LocalizeMixin from "../mixins/localize-mixin";
-import isComponentLoaded from "../common/config/is_component_loaded";
-
-/*
- * @appliesMixin LocalizeMixin
- */
-class HaSidebar extends LocalizeMixin(PolymerElement) {
- static get template() {
- return html`
-
-
-
- Home Assistant
-
-
-
- [[_initials]]
-
-
-
-
-
-
-
-
- [[localize('panel.states')]]
-
-
-
-
-
-
-
- [[_computePanelName(localize, item)]]
-
-
-
-
-
-
-
- [[localize('ui.sidebar.log_out')]]
-
-
-
-
-
-`;
- }
-
- static get properties() {
- return {
- hass: {
- type: Object,
- },
- menuShown: {
- type: Boolean,
- },
- menuSelected: {
- type: String,
- },
- narrow: Boolean,
- panels: {
- type: Array,
- computed: "computePanels(hass)",
- },
- defaultPage: String,
- _initials: {
- type: String,
- computed: "_computeUserInitials(hass.user.name)",
- },
- };
- }
-
- _computeUserInitials(name) {
- if (!name) return "user";
- return (
- name
- .trim()
- // Split by space and take first 3 words
- .split(" ")
- .slice(0, 3)
- // Of each word, take first letter
- .map((s) => s.substr(0, 1))
- .join("")
- );
- }
-
- _computeBadgeClass(initials) {
- return `profile-badge ${initials.length > 2 ? "long" : ""}`;
- }
-
- _mqttLoaded(hass) {
- return isComponentLoaded(hass, "mqtt");
- }
-
- _computeUserName(user) {
- return user && (user.name || "Unnamed User");
- }
-
- _computePanelName(localize, panel) {
- return localize(`panel.${panel.title}`) || panel.title;
- }
-
- computePanels(hass) {
- var panels = hass.panels;
- var sortValue = {
- map: 1,
- logbook: 2,
- history: 3,
- };
- var result = [];
-
- Object.keys(panels).forEach(function(key) {
- if (panels[key].title) {
- result.push(panels[key]);
- }
- });
-
- result.sort(function(a, b) {
- var aBuiltIn = a.component_name in sortValue;
- var bBuiltIn = b.component_name in sortValue;
-
- if (aBuiltIn && bBuiltIn) {
- return sortValue[a.component_name] - sortValue[b.component_name];
- }
- if (aBuiltIn) {
- return -1;
- }
- if (bBuiltIn) {
- return 1;
- }
- // both not built in, sort by title
- if (a.title < b.title) {
- return -1;
- }
- if (a.title > b.title) {
- return 1;
- }
- return 0;
- });
-
- return result;
- }
-
- _computeUrl(urlPath) {
- return `/${urlPath}`;
- }
-
- _handleLogOut() {
- this.fire("hass-logout");
- }
-}
-
-customElements.define("ha-sidebar", HaSidebar);
diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts
new file mode 100644
index 0000000000..202576d044
--- /dev/null
+++ b/src/components/ha-sidebar.ts
@@ -0,0 +1,379 @@
+import {
+ LitElement,
+ html,
+ CSSResult,
+ css,
+ PropertyDeclarations,
+ PropertyValues,
+} from "lit-element";
+import { classMap } from "lit-html/directives/class-map";
+import "@polymer/app-layout/app-toolbar/app-toolbar";
+import "@polymer/paper-icon-button/paper-icon-button";
+import "@polymer/paper-item/paper-icon-item";
+import "@polymer/paper-item/paper-item";
+import "@polymer/paper-listbox/paper-listbox";
+import "./ha-icon";
+
+import isComponentLoaded from "../common/config/is_component_loaded";
+import { HomeAssistant, Panel } from "../types";
+import { fireEvent } from "../common/dom/fire_event";
+
+const computeInitials = (name: string) => {
+ if (!name) {
+ return "user";
+ }
+ return (
+ name
+ .trim()
+ // Split by space and take first 3 words
+ .split(" ")
+ .slice(0, 3)
+ // Of each word, take first letter
+ .map((s) => s.substr(0, 1))
+ .join("")
+ );
+};
+
+const computeUrl = (urlPath) => `/${urlPath}`;
+
+const computePanels = (hass: HomeAssistant) => {
+ const panels = hass.panels;
+ const sortValue = {
+ map: 1,
+ logbook: 2,
+ history: 3,
+ };
+ const result: Panel[] = [];
+
+ Object.keys(panels).forEach((key) => {
+ if (panels[key].title) {
+ result.push(panels[key]);
+ }
+ });
+
+ result.sort((a, b) => {
+ const aBuiltIn = a.component_name in sortValue;
+ const bBuiltIn = b.component_name in sortValue;
+
+ if (aBuiltIn && bBuiltIn) {
+ return sortValue[a.component_name] - sortValue[b.component_name];
+ }
+ if (aBuiltIn) {
+ return -1;
+ }
+ if (bBuiltIn) {
+ return 1;
+ }
+ // both not built in, sort by title
+ if (a.title! < b.title!) {
+ return -1;
+ }
+ if (a.title! > b.title!) {
+ return 1;
+ }
+ return 0;
+ });
+
+ return result;
+};
+
+/*
+ * @appliesMixin LocalizeMixin
+ */
+class HaSidebar extends LitElement {
+ public hass?: HomeAssistant;
+ public defaultPage?: string;
+
+ protected render() {
+ const hass = this.hass;
+
+ if (!hass) {
+ return html``;
+ }
+
+ const initials = hass.user ? computeInitials(hass.user.name) : "";
+
+ return html`
+
+ Home Assistant
+ ${hass.user
+ ? html`
+ 2,
+ })}"
+ >
+
+ ${initials}
+
+ `
+ : ""}
+
+
+
+
+
+
+ ${hass.localize("panel.states")}
+
+
+
+ ${computePanels(hass).map(
+ (panel) => html`
+
+
+
+ ${hass.localize(`panel.${panel.title}`) || panel.title}
+
+
+ `
+ )}
+ ${!hass.user
+ ? html`
+
+
+ ${hass.localize("ui.sidebar.log_out")}
+
+ `
+ : html``}
+
+
+
+ `;
+ }
+
+ static get properties(): PropertyDeclarations {
+ return {
+ hass: {},
+ defaultPage: {},
+ };
+ }
+
+ protected shouldUpdate(changedProps: PropertyValues): boolean {
+ if (!this.hass || !changedProps.has("hass")) {
+ return false;
+ }
+ const oldHass = changedProps.get("hass") as HomeAssistant;
+ if (!oldHass) {
+ return true;
+ }
+ const hass = this.hass;
+ return (
+ hass.panels !== oldHass.panels ||
+ hass.panelUrl !== oldHass.panelUrl ||
+ hass.config.components !== oldHass.config.components ||
+ hass.user !== oldHass.user ||
+ hass.localize !== oldHass.localize
+ );
+ }
+
+ private _handleLogOut() {
+ fireEvent(this, "hass-logout");
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ :host {
+ height: 100%;
+ display: block;
+ overflow: auto;
+ -ms-user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ border-right: 1px solid var(--divider-color);
+ background-color: var(
+ --sidebar-background-color,
+ var(--primary-background-color)
+ );
+ }
+
+ app-toolbar {
+ font-weight: 400;
+ color: var(--primary-text-color);
+ border-bottom: 1px solid var(--divider-color);
+ background-color: var(--primary-background-color);
+ }
+
+ app-toolbar a {
+ color: var(--primary-text-color);
+ }
+
+ paper-listbox {
+ padding: 0;
+ }
+
+ paper-listbox > a {
+ color: var(--sidebar-text-color);
+ font-weight: 500;
+ font-size: 14px;
+ text-decoration: none;
+ }
+
+ paper-icon-item {
+ margin: 8px;
+ padding-left: 9px;
+ border-radius: 4px;
+ --paper-item-min-height: 40px;
+ }
+
+ a ha-icon {
+ color: var(--sidebar-icon-color);
+ }
+
+ .iron-selected paper-icon-item:before {
+ border-radius: 4px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ pointer-events: none;
+ content: "";
+ background-color: var(--sidebar-selected-icon-color);
+ opacity: 0.12;
+ transition: opacity 15ms linear;
+ will-change: opacity;
+ }
+
+ .iron-selected paper-icon-item[pressed]:before {
+ opacity: 0.37;
+ }
+
+ paper-icon-item span {
+ color: var(--sidebar-text-color);
+ font-weight: 500;
+ font-size: 14px;
+ }
+
+ a.iron-selected paper-icon-item ha-icon {
+ color: var(--sidebar-selected-icon-color);
+ }
+
+ a.iron-selected .item-text {
+ color: var(--sidebar-selected-text-color);
+ }
+
+ paper-icon-item.logout {
+ margin-top: 16px;
+ }
+
+ .divider {
+ height: 1px;
+ background-color: var(--divider-color);
+ margin: 4px 0;
+ }
+
+ .subheader {
+ color: var(--sidebar-text-color);
+ font-weight: 500;
+ font-size: 14px;
+ padding: 16px;
+ }
+
+ .dev-tools {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 0 8px;
+ }
+
+ .dev-tools a {
+ color: var(--sidebar-icon-color);
+ }
+
+ .profile-badge {
+ /* for ripple */
+ position: relative;
+ box-sizing: border-box;
+ width: 40px;
+ line-height: 40px;
+ border-radius: 50%;
+ text-align: center;
+ background-color: var(--light-primary-color);
+ text-decoration: none;
+ color: var(--primary-text-color);
+ }
+
+ .profile-badge.long {
+ font-size: 80%;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-sidebar": HaSidebar;
+ }
+}
+
+customElements.define("ha-sidebar", HaSidebar);
diff --git a/src/layouts/home-assistant-main.js b/src/layouts/home-assistant-main.js
index 4f99bf5958..768b3968d8 100644
--- a/src/layouts/home-assistant-main.js
+++ b/src/layouts/home-assistant-main.js
@@ -61,7 +61,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
persistent="[[dockedSidebar]]"
>
diff --git a/src/polymer-types.ts b/src/polymer-types.ts
index 9ebb6e1bcf..693c2db105 100644
--- a/src/polymer-types.ts
+++ b/src/polymer-types.ts
@@ -4,6 +4,7 @@ export {};
declare global {
// for fire event
interface HASSDomEvents {
+ "hass-logout": undefined;
"iron-resize": undefined;
"config-refresh": undefined;
"ha-refresh-cloud-status": undefined;