Mercurial
comparison design_system/src/components/icon.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 SVG_NAMESPACE = "http://www.w3.org/2000/svg"; | |
| 2 | |
| 3 const paths = { | |
| 4 add: ["M12 5v14", "M5 12h14"], | |
| 5 alert: ["M12 3 2.8 20h18.4L12 3Z", "M12 9v5", "M12 17h.01"], | |
| 6 archive: ["M4 7h16v13H4Z", "M3 3h18v4H3Z", "M9 11h6"], | |
| 7 "arrow-down": ["M12 4v16", "m6 14-6 6-6-6"], | |
| 8 "arrow-left": ["M20 12H4", "m10 6-6-6 6-6"], | |
| 9 "arrow-right": ["M4 12h16", "m-6-6 6 6-6 6"], | |
| 10 "arrow-up": ["M12 20V4", "m-6 6 6-6 6 6"], | |
| 11 check: ["m5 12 4 4L19 6"], | |
| 12 "chevron-down": ["m5 9 7 7 7-7"], | |
| 13 "chevron-left": ["m15 5-7 7 7 7"], | |
| 14 "chevron-right": ["m9 5 7 7-7 7"], | |
| 15 "chevron-up": ["m5 15 7-7 7 7"], | |
| 16 close: ["M6 6l12 12", "M18 6 6 18"], | |
| 17 code: ["m9 6-6 6 6 6", "m6-12 6 6-6 6"], | |
| 18 copy: ["M8 8h12v12H8Z", "M4 16V4h12"], | |
| 19 download: ["M12 3v12", "m7-7 5 5 5-5", "M5 21h14"], | |
| 20 error: ["M5 5l14 14", "M19 5 5 19"], | |
| 21 external: ["M14 5h5v5", "m19 5-8 8", "M19 13v6H5V5h6"], | |
| 22 file: ["M6 3h8l4 4v14H6Z", "M14 3v5h4"], | |
| 23 folder: ["M3 6h7l2 2h9v12H3Z"], | |
| 24 branch: ["M7 5v10a4 4 0 0 0 4 4h6", "M7 9h7a3 3 0 0 0 3-3V5"], | |
| 25 calendar: ["M5 4h14v17H5Z", "M8 2v4", "M16 2v4", "M5 9h14"], | |
| 26 history: ["M4 5v5h5", "M5 9a8 8 0 1 0 2-4", "M12 7v5l3 2"], | |
| 27 info: ["M12 11v6", "M12 7h.01", "M3 12a9 9 0 1 0 18 0 9 9 0 1 0-18 0"], | |
| 28 menu: ["M4 6h16", "M4 12h16", "M4 18h16"], | |
| 29 moon: ["M20 15a8 8 0 0 1-11-11 9 9 0 1 0 11 11Z"], | |
| 30 more: ["M5 12h.01", "M12 12h.01", "M19 12h.01"], | |
| 31 pause: ["M8 5v14", "M16 5v14"], | |
| 32 play: ["m8 5 11 7-11 7Z"], | |
| 33 repository: ["M4 4h13a3 3 0 0 1 3 3v13H7a3 3 0 0 1-3-3Z", "M8 4v16"], | |
| 34 retry: ["M4 4v6h6", "M5 9a8 8 0 1 1 2 8"], | |
| 35 search: ["M4 11a7 7 0 1 0 14 0 7 7 0 1 0-14 0", "m16 16 4 4"], | |
| 36 settings: [ | |
| 37 "M9.5 4.2 10 2h4l.5 2.2 2 .8 2-1.2 2.8 2.8-1.2 2 .8 2L22 10v4l-2.2.5-.8 2 1.2 2-2.8 2.8-2-1.2-2 .8-.5 2.2h-4L9.5 20l-2-.8-2 1.2-2.8-2.8 1.2-2-.8-2L2 14v-4l2.2-.5.8-2-1.2-2 2.8-2.8 2 1.2Z", | |
| 38 "M9 12a3 3 0 1 0 6 0 3 3 0 1 0-6 0", | |
| 39 ], | |
| 40 sun: ["M12 2v3", "M12 19v3", "M2 12h3", "M19 12h3", "m5 5 2 2", "m17 17 2 2", "m19 5-2 2", "M7 17l-2 2", "M8 12a4 4 0 1 0 8 0 4 4 0 1 0-8 0"], | |
| 41 trash: ["M4 7h16", "M9 3h6l1 4H8Z", "m6 7 1 14h10l1-14", "M10 11v6", "M14 11v6"], | |
| 42 upload: ["M12 21V9", "m5 14 5-5 5 5", "M5 3h14"], | |
| 43 user: ["M8 8a4 4 0 1 0 8 0 4 4 0 1 0-8 0", "M4 21a8 8 0 0 1 16 0"], | |
| 44 }; | |
| 45 | |
| 46 /** @const {!ReadonlyArray<string>} Names in the first-party icon registry. */ | |
| 47 export const ICON_NAMES = Object.freeze(Object.keys(paths).sort()); | |
| 48 | |
| 49 function safeSize(value) { | |
| 50 if (/^\d+(?:\.\d+)?(?:px|rem|em)?$/.test(value || "")) return value; | |
| 51 return "1em"; | |
| 52 } | |
| 53 | |
| 54 /** | |
| 55 * Renders validated first-party SVG geometry with inherited color. | |
| 56 * | |
| 57 * @extends {HTMLElement} | |
| 58 */ | |
| 59 export class ZenIcon extends HTMLElement { | |
| 60 static get observedAttributes() { | |
| 61 return ["label", "name", "size"]; | |
| 62 } | |
| 63 | |
| 64 connectedCallback() { | |
| 65 this.render(); | |
| 66 } | |
| 67 | |
| 68 attributeChangedCallback() { | |
| 69 if (this.isConnected) this.render(); | |
| 70 } | |
| 71 | |
| 72 render() { | |
| 73 const requested = this.getAttribute("name") || "info"; | |
| 74 const known = Object.hasOwn(paths, requested); | |
| 75 const definition = known ? paths[requested] : paths.alert; | |
| 76 const svg = document.createElementNS(SVG_NAMESPACE, "svg"); | |
| 77 svg.setAttribute("viewBox", "0 0 24 24"); | |
| 78 svg.setAttribute("fill", "none"); | |
| 79 svg.setAttribute("stroke", "currentColor"); | |
| 80 svg.setAttribute("stroke-width", "2"); | |
| 81 svg.setAttribute("stroke-linecap", "round"); | |
| 82 svg.setAttribute("stroke-linejoin", "round"); | |
| 83 svg.setAttribute("focusable", "false"); | |
| 84 | |
| 85 for (const data of definition) { | |
| 86 const path = document.createElementNS(SVG_NAMESPACE, "path"); | |
| 87 path.setAttribute("d", data); | |
| 88 svg.append(path); | |
| 89 } | |
| 90 | |
| 91 const label = this.getAttribute("label"); | |
| 92 if (label) { | |
| 93 this.setAttribute("role", "img"); | |
| 94 this.setAttribute("aria-label", label); | |
| 95 this.removeAttribute("aria-hidden"); | |
| 96 } else { | |
| 97 this.removeAttribute("role"); | |
| 98 this.removeAttribute("aria-label"); | |
| 99 this.setAttribute("aria-hidden", "true"); | |
| 100 } | |
| 101 this.dataset.zenIcon = known ? requested : "alert"; | |
| 102 this.style.setProperty("--zen-icon-size", safeSize(this.getAttribute("size"))); | |
| 103 this.replaceChildren(svg); | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 if (!customElements.get("zen-icon")) { | |
| 108 customElements.define("zen-icon", ZenIcon); | |
| 109 } |