diff --git a/build-scripts/gulp/gallery.js b/build-scripts/gulp/gallery.js
index 16848ec723..6e45f045cb 100644
--- a/build-scripts/gulp/gallery.js
+++ b/build-scripts/gulp/gallery.js
@@ -17,25 +17,33 @@ require("./entry-html.js");
require("./rollup.js");
gulp.task("gather-gallery-demos", async function gatherDemos() {
- const files = await fs.promises.readdir(
- path.resolve(paths.gallery_dir, "src/demos")
- );
+ const demoDir = path.resolve(paths.gallery_dir, "src/demos");
+ const files = await fs.promises.readdir(demoDir);
const galleryBuild = path.resolve(paths.gallery_dir, "build");
fs.mkdirSync(galleryBuild, { recursive: true });
let content = "export const DEMOS = {\n";
+ const processed = new Set();
+
for (const file of files) {
- if (!file.endsWith(".ts")) {
+ let demoId = path.basename(
+ file,
+ file.endsWith(".ts") ? ".ts" : ".markdown"
+ );
+
+ // Can be processed if we saw demo or description before.
+ if (processed.has(demoId)) {
continue;
}
- const demoId = path.basename(file, ".ts");
- const descriptionFile = path.resolve(
- paths.gallery_dir,
- "src/demos",
- `${demoId}.markdown`
- );
+
+ processed.add(demoId);
+
+ const demoFile = path.resolve(demoDir, `${demoId}.ts`);
+
+ const descriptionFile = path.resolve(demoDir, `${demoId}.markdown`);
+ const hasDemo = fs.existsSync(demoFile);
const hasDescription = fs.existsSync(descriptionFile);
if (hasDescription) {
const descriptionContent = fs.readFileSync(descriptionFile, "utf-8");
@@ -55,7 +63,8 @@ gulp.task("gather-gallery-demos", async function gatherDemos() {
? `description: () => import("${descriptionPath}").then(m => m.default),`
: ""
}
- load: () => import("${demoPath}")
+ ${hasDemo ? `load: () => import("${demoPath}")` : ""}
+
},\n`;
}
@@ -84,7 +93,17 @@ gulp.task(
),
"copy-static-gallery",
"gen-index-gallery-dev",
- env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
+ gulp.parallel(
+ env.useRollup()
+ ? "rollup-dev-server-gallery"
+ : "webpack-dev-server-gallery",
+ async function watchMarkdownFiles() {
+ gulp.watch(
+ path.resolve(paths.gallery_dir, "src/demos/*.markdown"),
+ gulp.series("gather-gallery-demos")
+ );
+ }
+ )
)
);
diff --git a/gallery/src/demos/demo-introduction.markdown b/gallery/src/demos/demo-introduction.markdown
new file mode 100644
index 0000000000..d80033b05e
--- /dev/null
+++ b/gallery/src/demos/demo-introduction.markdown
@@ -0,0 +1,8 @@
+Lovelace has many different cards. Each card allows the user to tell
+a different story about what is going on in their house. These cards
+are very customizable, as no household is the same.
+
+This gallery helps our developers and designers to see all the
+different states that each card can be in.
+
+Check [the Lovelace documentation](https://www.home-assistant.io/lovelace) for instructions on how to get started with Lovelace.
diff --git a/gallery/src/demos/demo-introduction.ts b/gallery/src/demos/demo-introduction.ts
deleted file mode 100644
index 29e306bbff..0000000000
--- a/gallery/src/demos/demo-introduction.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { css, html, LitElement, TemplateResult } from "lit";
-import { customElement } from "lit/decorators";
-import "../../../src/components/ha-card";
-import "../../../src/components/ha-markdown";
-
-@customElement("demo-introduction")
-export class DemoIntroduction extends LitElement {
- protected render(): TemplateResult {
- return html`
-
-
-
-
-
- `;
- }
-
- static get styles() {
- return css`
- ha-card {
- max-width: 600px;
- margin: 24px auto;
- }
- `;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "demo-introduction": DemoIntroduction;
- }
-}
diff --git a/gallery/src/ha-gallery.ts b/gallery/src/ha-gallery.ts
index 4f044096cb..2aa583ca12 100644
--- a/gallery/src/ha-gallery.ts
+++ b/gallery/src/ha-gallery.ts
@@ -4,55 +4,13 @@ import "@material/mwc-top-app-bar-fixed";
import { html, css, LitElement, PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators";
import { until } from "lit/directives/until";
+import "../../src/components/ha-card";
import "../../src/components/ha-icon-button";
import "../../src/managers/notification-manager";
import { haStyle } from "../../src/resources/styles";
-// eslint-disable-next-line import/extensions
import { DEMOS } from "../build/import-demos";
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
-
-const DEMOS_GROUPED: {
- header?: string;
- demos?: string[];
- demoStart?: string;
-}[] = [
- {
- demos: ["introduction"],
- },
- {
- header: "Lovelace",
- demoStart: "hui-",
- },
- {
- header: "Automation",
- demoStart: "automation-",
- },
- {
- header: "Rest",
- demoStart: "",
- },
-];
-
-const demosToProcess = new Set(Object.keys(DEMOS));
-
-for (const group of Object.values(DEMOS_GROUPED)) {
- if (group.demos) {
- for (const demo of group.demos) {
- demosToProcess.delete(demo);
- }
- }
- if (!group.demos) {
- group.demos = [];
- }
- if (group.demoStart !== undefined) {
- for (const demo of demosToProcess) {
- if (demo.startsWith(group.demoStart)) {
- group.demos.push(demo);
- demosToProcess.delete(demo);
- }
- }
- }
-}
+import { SIDEBAR } from "./sidebar";
const FAKE_HASS = {
// Just enough for computeRTL for notification-manager
@@ -65,7 +23,7 @@ const FAKE_HASS = {
@customElement("ha-gallery")
class HaGallery extends LitElement {
@property() private _demo =
- document.location.hash.substring(1) || DEMOS_GROUPED[0].demos![0];
+ document.location.hash.substring(1) || SIDEBAR[0].demos![0];
@query("notification-manager")
private _notifications!: HTMLElementTagNameMap["notification-manager"];
@@ -73,23 +31,51 @@ class HaGallery extends LitElement {
@query("mwc-drawer")
private _drawer!: HTMLElementTagNameMap["mwc-drawer"];
+ private _narrow = window.matchMedia("(max-width: 600px)").matches;
+
render() {
+ const sidebar: unknown[] = [];
+
+ for (const group of SIDEBAR) {
+ let sectionOpen = false;
+ const links: unknown[] = [];
+
+ for (const demo of group.demos!) {
+ const active = this._demo === demo;
+ if (active) {
+ sectionOpen = true;
+ }
+
+ links.push(html`
+ ${group.demoStart === undefined
+ ? demo
+ : demo.substring(group.demoStart.length)}
+ `);
+ }
+
+ sidebar.push(
+ group.header
+ ? html`
+
+ ${group.header}
+ ${links}
+
+ `
+ : links
+ );
+ }
+
return html`
-
+
Home Assistant Design
-
+
html`
-
+
${content}
`
@@ -126,21 +112,12 @@ class HaGallery extends LitElement {
`;
}
- private _renderDemo(demo: string, demoStart?: string) {
- return html`
- ${demoStart === undefined ? demo : demo.substring(demoStart.length)}
- `;
- }
-
firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("show-notification", (ev) =>
this._notifications.showDialog({ message: ev.detail.message })
);
-
this.addEventListener("alert-dismissed-clicked", () =>
this._notifications.showDialog({ message: "Alert dismissed clicked" })
);
@@ -156,12 +133,15 @@ class HaGallery extends LitElement {
window.addEventListener("hashchange", () => {
this._demo = document.location.hash.substring(1);
+ if (this._narrow) {
+ this._drawer.open = false;
+ }
});
}
updated(changedProps: PropertyValues) {
super.updated(changedProps);
- if (changedProps.has("_demo") && this._demo) {
+ if (changedProps.has("_demo") && DEMOS[this._demo].load) {
DEMOS[this._demo].load();
}
}
@@ -179,16 +159,18 @@ class HaGallery extends LitElement {
-moz-user-select: initial;
}
- .section {
- font-weight: bold;
- }
-
.sidebar {
padding: 4px;
}
- .sidebar p {
- margin: 1em 12px;
+ .sidebar details {
+ margin-top: 1em;
+ }
+
+ .sidebar summary {
+ cursor: pointer;
+ font-weight: bold;
+ margin-bottom: 8px;
}
.sidebar a {
diff --git a/gallery/src/sidebar.ts b/gallery/src/sidebar.ts
new file mode 100644
index 0000000000..71222f59a0
--- /dev/null
+++ b/gallery/src/sidebar.ts
@@ -0,0 +1,69 @@
+import { DEMOS } from "../build/import-demos";
+
+export const SIDEBAR: SidebarSection[] = [
+ {
+ demos: ["introduction"],
+ },
+
+ {
+ // Each section has a header
+ header: "Lovelace",
+ // Specify demos to make sure they are put on top.
+ demos: [],
+ // Add a demoStart to automatically gather demos based on their name
+ demoStart: "hui-",
+ },
+ {
+ header: "Automation",
+ demoStart: "automation-",
+ },
+ {
+ header: "Components",
+ demos: [
+ "ha-alert",
+ "ha-bar",
+ "ha-chips",
+ "ha-faded",
+ "ha-form",
+ "ha-label-badge",
+ "ha-selector",
+ ],
+ },
+ {
+ header: "More Info",
+ demoStart: "more-info-",
+ },
+ {
+ header: "Rest",
+ demoStart: "", // empty string matches all.
+ },
+];
+
+interface SidebarSection {
+ header?: string;
+ demos?: string[];
+ demoStart?: string;
+}
+
+const demosToProcess = new Set(Object.keys(DEMOS));
+
+for (const group of Object.values(SIDEBAR)) {
+ // Any pre-defined groups will not be sorted.
+ if (group.demos) {
+ for (const demo of group.demos) {
+ demosToProcess.delete(demo);
+ }
+ } else {
+ group.demos = [];
+ }
+}
+for (const group of Object.values(SIDEBAR)) {
+ if (group.demoStart !== undefined) {
+ for (const demo of demosToProcess) {
+ if (demo.startsWith(group.demoStart)) {
+ group.demos!.push(demo);
+ demosToProcess.delete(demo);
+ }
+ }
+ }
+}