comparison design_system/src/components/notifications.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 fdf3816959cb
children 60a876c4587a
comparison
equal deleted inserted replaced
253:fdf3816959cb 254:2b6e732087ff
11 return typeof value === "string" && 11 return typeof value === "string" &&
12 value.length <= maximum && 12 value.length <= maximum &&
13 (allowEmpty || value.trim().length > 0); 13 (allowEmpty || value.trim().length > 0);
14 } 14 }
15 15
16 /**
17 * Validates and normalizes a notification event record.
18 *
19 * @param {*} value Candidate event detail.
20 * @return {?Object} Frozen normalized record, or null when invalid.
21 */
16 export function validateNotification(value) { 22 export function validateNotification(value) {
17 if (!value || 23 if (!value ||
18 value.version !== 1 || 24 value.version !== 1 ||
19 !isShortString(value.id, 128) || 25 !isShortString(value.id, 128) ||
20 !TONES.has(value.tone) || 26 !TONES.has(value.tone) ||
51 persistent: value.persistent === true, 57 persistent: value.persistent === true,
52 action, 58 action,
53 }); 59 });
54 } 60 }
55 61
62 /**
63 * Validates a programmatic notification dismissal record.
64 *
65 * @param {*} value Candidate event detail.
66 * @return {?string} Valid notification ID, or null when invalid.
67 */
56 export function validateNotificationDismiss(value) { 68 export function validateNotificationDismiss(value) {
57 if (!value || 69 if (!value ||
58 value.version !== 1 || 70 value.version !== 1 ||
59 !isShortString(value.id, 128)) return null; 71 !isShortString(value.id, 128)) return null;
60 return value.id; 72 return value.id;
70 timer: null, 82 timer: null,
71 view: null, 83 view: null,
72 }; 84 };
73 } 85 }
74 86
87 /**
88 * Renders a bounded, scoped, Sonner-like notification queue.
89 *
90 * @extends {HTMLElement}
91 */
75 export class ZenNotifications extends HTMLElement { 92 export class ZenNotifications extends HTMLElement {
76 static version = 1; 93 static version = 1;
77 94
78 #records = new Map(); 95 #records = new Map();
79 #announcementQueues = { 96 #announcementQueues = {
361 article.dataset.zenNotificationId = record.id; 378 article.dataset.zenNotificationId = record.id;
362 379
363 const tone = document.createElement("span"); 380 const tone = document.createElement("span");
364 tone.className = "zen-notification-tone"; 381 tone.className = "zen-notification-tone";
365 tone.setAttribute("aria-hidden", "true"); 382 tone.setAttribute("aria-hidden", "true");
383 const toneIcon = document.createElement("zen-icon");
384 tone.append(toneIcon);
366 385
367 const content = document.createElement("div"); 386 const content = document.createElement("div");
368 content.className = "zen-notification-content"; 387 content.className = "zen-notification-content";
369 const message = document.createElement("p"); 388 const message = document.createElement("p");
370 message.className = "zen-notification-message"; 389 message.className = "zen-notification-message";
371 content.append(message); 390 content.append(message);
372 391
373 const dismiss = document.createElement("button"); 392 const dismiss = document.createElement("button");
374 dismiss.type = "button"; 393 dismiss.type = "button";
375 dismiss.className = "zen-notification-dismiss"; 394 dismiss.className = "zen-notification-dismiss";
376 dismiss.textContent = "×"; 395 const dismissIcon = document.createElement("zen-icon");
396 dismissIcon.setAttribute("name", "close");
397 dismiss.append(dismissIcon);
377 dismiss.addEventListener("click", () => { 398 dismiss.addEventListener("click", () => {
378 this.dismiss(record.id, "dismissed"); 399 this.dismiss(record.id, "dismissed");
379 }); 400 });
380 401
381 article.addEventListener("pointerenter", () => { 402 article.addEventListener("pointerenter", () => {
400 content, 421 content,
401 description: null, 422 description: null,
402 dismiss, 423 dismiss,
403 message, 424 message,
404 tone, 425 tone,
426 toneIcon,
405 }; 427 };
406 this.#resizeObserver?.observe(article); 428 this.#resizeObserver?.observe(article);
407 } 429 }
408 430
409 const view = record.view; 431 const view = record.view;
410 view.article.dataset.tone = record.tone; 432 view.article.dataset.tone = record.tone;
411 view.tone.textContent = { 433 view.toneIcon.setAttribute("name", {
412 info: "i", 434 info: "info",
413 success: "✓", 435 success: "check",
414 warning: "!", 436 warning: "alert",
415 error: "×", 437 error: "error",
416 }[record.tone]; 438 }[record.tone]);
417 view.message.textContent = record.message; 439 view.message.textContent = record.message;
418 view.dismiss.setAttribute( 440 view.dismiss.setAttribute(
419 "aria-label", 441 "aria-label",
420 this.getAttribute("dismiss-label") || "Dismiss notification", 442 this.getAttribute("dismiss-label") || "Dismiss notification",
421 ); 443 );