diff design_system/src/components/primitives.js @ 254:2b6e732087ff

[ui] Add complete native component catalog Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Tue, 04 Aug 2026 15:12:09 -0700
parents
children 60a876c4587a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/design_system/src/components/primitives.js	Tue Aug 04 15:12:09 2026 -0700
@@ -0,0 +1,143 @@
+/** @const {!ReadonlyArray<string>} Registered presentation-only elements. */
+export const ZEN_PRIMITIVE_ELEMENTS = [
+  "zen-aspect-ratio",
+  "zen-attachment",
+  "zen-avatar",
+  "zen-badge",
+  "zen-breadcrumb",
+  "zen-bubble",
+  "zen-button-group",
+  "zen-chart",
+  "zen-direction",
+  "zen-empty",
+  "zen-input",
+  "zen-input-group",
+  "zen-item",
+  "zen-kbd",
+  "zen-label",
+  "zen-marker",
+  "zen-message",
+  "zen-native-select",
+  "zen-pagination",
+  "zen-progress",
+  "zen-scroll-area",
+  "zen-separator",
+  "zen-skeleton",
+  "zen-spinner",
+  "zen-table",
+  "zen-textarea",
+  "zen-typography",
+];
+
+function parseRatio(value) {
+  const [width, height] = String(value || "16/9")
+    .split("/")
+    .map(Number);
+  if (!Number.isFinite(width) || !Number.isFinite(height) ||
+      width <= 0 || height <= 0) return "16 / 9";
+  return `${width} / ${height}`;
+}
+
+/**
+ * Applies small semantic enhancements shared by presentation primitives.
+ *
+ * @extends {HTMLElement}
+ */
+export class ZenPrimitive extends HTMLElement {
+  connectedCallback() {
+    this.sync();
+  }
+
+  static get observedAttributes() {
+    return ["dir", "ratio", "value", "width"];
+  }
+
+  attributeChangedCallback() {
+    if (this.isConnected) this.sync();
+  }
+
+  sync() {
+    switch (this.localName) {
+      case "zen-aspect-ratio":
+        this.style.setProperty(
+          "--zen-aspect-ratio",
+          parseRatio(this.getAttribute("ratio")),
+        );
+        break;
+      case "zen-breadcrumb": {
+        const navigation = this.querySelector(":scope > nav");
+        if (navigation && !navigation.hasAttribute("aria-label")) {
+          navigation.setAttribute("aria-label", "Breadcrumb");
+        }
+        break;
+      }
+      case "zen-chart":
+        if (!this.hasAttribute("role")) this.setAttribute("role", "img");
+        break;
+      case "zen-direction":
+        if (!this.hasAttribute("dir") && this.hasAttribute("value")) {
+          this.dir = this.getAttribute("value");
+        }
+        break;
+      case "zen-pagination": {
+        const navigation = this.querySelector(":scope > nav");
+        if (navigation && !navigation.hasAttribute("aria-label")) {
+          navigation.setAttribute("aria-label", "Pagination");
+        }
+        break;
+      }
+      case "zen-native-select": {
+        if (!this.querySelector("select")) break;
+        let icon = this.querySelector(
+          ":scope > zen-icon[data-zen-select-icon]",
+        );
+        if (!icon) {
+          icon = document.createElement("zen-icon");
+          icon.dataset.zenSelectIcon = "";
+          icon.dataset.zenGenerated = "true";
+          icon.setAttribute("name", "chevron-down");
+          this.append(icon);
+        }
+        break;
+      }
+      case "zen-separator":
+        if (!this.querySelector(":scope > hr")) {
+          if (!this.hasAttribute("role")) {
+            this.setAttribute("role", "separator");
+          }
+          if (!this.hasAttribute("aria-orientation")) {
+            this.setAttribute(
+              "aria-orientation",
+              this.getAttribute("orientation") === "vertical"
+                ? "vertical"
+                : "horizontal",
+            );
+          }
+        }
+        break;
+      case "zen-skeleton":
+        if (this.hasAttribute("width")) {
+          this.style.setProperty(
+            "--zen-skeleton-width",
+            this.getAttribute("width"),
+          );
+        } else {
+          this.style.removeProperty("--zen-skeleton-width");
+        }
+        if (!this.hasAttribute("role")) this.setAttribute("aria-hidden", "true");
+        break;
+      case "zen-spinner":
+        if (!this.hasAttribute("role")) this.setAttribute("role", "status");
+        if (!this.hasAttribute("aria-label")) {
+          this.setAttribute("aria-label", "Loading");
+        }
+        break;
+    }
+  }
+}
+
+for (const name of ZEN_PRIMITIVE_ELEMENTS) {
+  if (!customElements.get(name)) {
+    customElements.define(name, class extends ZenPrimitive {});
+  }
+}