Mercurial
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 {}); + } +}