comparison 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
comparison
equal deleted inserted replaced
253:fdf3816959cb 254:2b6e732087ff
1 /** @const {!ReadonlyArray<string>} Registered presentation-only elements. */
2 export const ZEN_PRIMITIVE_ELEMENTS = [
3 "zen-aspect-ratio",
4 "zen-attachment",
5 "zen-avatar",
6 "zen-badge",
7 "zen-breadcrumb",
8 "zen-bubble",
9 "zen-button-group",
10 "zen-chart",
11 "zen-direction",
12 "zen-empty",
13 "zen-input",
14 "zen-input-group",
15 "zen-item",
16 "zen-kbd",
17 "zen-label",
18 "zen-marker",
19 "zen-message",
20 "zen-native-select",
21 "zen-pagination",
22 "zen-progress",
23 "zen-scroll-area",
24 "zen-separator",
25 "zen-skeleton",
26 "zen-spinner",
27 "zen-table",
28 "zen-textarea",
29 "zen-typography",
30 ];
31
32 function parseRatio(value) {
33 const [width, height] = String(value || "16/9")
34 .split("/")
35 .map(Number);
36 if (!Number.isFinite(width) || !Number.isFinite(height) ||
37 width <= 0 || height <= 0) return "16 / 9";
38 return `${width} / ${height}`;
39 }
40
41 /**
42 * Applies small semantic enhancements shared by presentation primitives.
43 *
44 * @extends {HTMLElement}
45 */
46 export class ZenPrimitive extends HTMLElement {
47 connectedCallback() {
48 this.sync();
49 }
50
51 static get observedAttributes() {
52 return ["dir", "ratio", "value", "width"];
53 }
54
55 attributeChangedCallback() {
56 if (this.isConnected) this.sync();
57 }
58
59 sync() {
60 switch (this.localName) {
61 case "zen-aspect-ratio":
62 this.style.setProperty(
63 "--zen-aspect-ratio",
64 parseRatio(this.getAttribute("ratio")),
65 );
66 break;
67 case "zen-breadcrumb": {
68 const navigation = this.querySelector(":scope > nav");
69 if (navigation && !navigation.hasAttribute("aria-label")) {
70 navigation.setAttribute("aria-label", "Breadcrumb");
71 }
72 break;
73 }
74 case "zen-chart":
75 if (!this.hasAttribute("role")) this.setAttribute("role", "img");
76 break;
77 case "zen-direction":
78 if (!this.hasAttribute("dir") && this.hasAttribute("value")) {
79 this.dir = this.getAttribute("value");
80 }
81 break;
82 case "zen-pagination": {
83 const navigation = this.querySelector(":scope > nav");
84 if (navigation && !navigation.hasAttribute("aria-label")) {
85 navigation.setAttribute("aria-label", "Pagination");
86 }
87 break;
88 }
89 case "zen-native-select": {
90 if (!this.querySelector("select")) break;
91 let icon = this.querySelector(
92 ":scope > zen-icon[data-zen-select-icon]",
93 );
94 if (!icon) {
95 icon = document.createElement("zen-icon");
96 icon.dataset.zenSelectIcon = "";
97 icon.dataset.zenGenerated = "true";
98 icon.setAttribute("name", "chevron-down");
99 this.append(icon);
100 }
101 break;
102 }
103 case "zen-separator":
104 if (!this.querySelector(":scope > hr")) {
105 if (!this.hasAttribute("role")) {
106 this.setAttribute("role", "separator");
107 }
108 if (!this.hasAttribute("aria-orientation")) {
109 this.setAttribute(
110 "aria-orientation",
111 this.getAttribute("orientation") === "vertical"
112 ? "vertical"
113 : "horizontal",
114 );
115 }
116 }
117 break;
118 case "zen-skeleton":
119 if (this.hasAttribute("width")) {
120 this.style.setProperty(
121 "--zen-skeleton-width",
122 this.getAttribute("width"),
123 );
124 } else {
125 this.style.removeProperty("--zen-skeleton-width");
126 }
127 if (!this.hasAttribute("role")) this.setAttribute("aria-hidden", "true");
128 break;
129 case "zen-spinner":
130 if (!this.hasAttribute("role")) this.setAttribute("role", "status");
131 if (!this.hasAttribute("aria-label")) {
132 this.setAttribute("aria-label", "Loading");
133 }
134 break;
135 }
136 }
137 }
138
139 for (const name of ZEN_PRIMITIVE_ELEMENTS) {
140 if (!customElements.get(name)) {
141 customElements.define(name, class extends ZenPrimitive {});
142 }
143 }