comparison design_system/src/components/notifications.js @ 258:60a876c4587a

[ui] Add semantic primitive ownership Build a layered Zenbu token and sizing system, make authored controls use native-underneath primitives, migrate mrjunejune without imposing visual surfaces, and document/enforce HTML ownership in the catalog and wiki. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Wed, 05 Aug 2026 05:25:40 -0700
parents 2b6e732087ff
children
comparison
equal deleted inserted replaced
257:609d3c6aff4e 258:60a876c4587a
9 9
10 function isShortString(value, maximum, allowEmpty = false) { 10 function isShortString(value, maximum, allowEmpty = false) {
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 }
15
16 function resolveCssLength(element, property, fallback) {
17 const probe = document.createElement("span");
18 probe.style.position = "absolute";
19 probe.style.inlineSize = `var(${property})`;
20 probe.style.visibility = "hidden";
21 element.append(probe);
22 const value = Number.parseFloat(getComputedStyle(probe).inlineSize);
23 probe.remove();
24 return Number.isFinite(value) ? value : fallback;
14 } 25 }
15 26
16 /** 27 /**
17 * Validates and normalizes a notification event record. 28 * Validates and normalizes a notification event record.
18 * 29 *
83 view: null, 94 view: null,
84 }; 95 };
85 } 96 }
86 97
87 /** 98 /**
88 * Renders a bounded, scoped, Sonner-like notification queue. 99 * Renders a bounded, scoped notification queue.
89 * 100 *
90 * @extends {HTMLElement} 101 * @extends {HTMLElement}
91 */ 102 */
92 export class ZenNotifications extends HTMLElement { 103 export class ZenNotifications extends HTMLElement {
93 static version = 1; 104 static version = 1;
494 layoutStack() { 505 layoutStack() {
495 if (!this.stack) return; 506 if (!this.stack) return;
496 const visible = [...this.#records.values()] 507 const visible = [...this.#records.values()]
497 .slice(-MAX_VISIBLE) 508 .slice(-MAX_VISIBLE)
498 .filter(record => record.view?.article.isConnected); 509 .filter(record => record.view?.article.isConnected);
499 const styles = getComputedStyle(this.stack); 510 const gap = resolveCssLength(
500 const configuredGap = Number.parseFloat( 511 this.stack,
501 styles.getPropertyValue("--zen-notification-gap"), 512 "--zenbu-notification-gap",
502 ); 513 12,
503 const gap = Number.isFinite(configuredGap) 514 );
504 ? configuredGap
505 : 12;
506 let expandedHeight = 0; 515 let expandedHeight = 0;
507 let newestHeight = 0; 516 let newestHeight = 0;
508 517
509 for (let index = visible.length - 1; index >= 0; index--) { 518 for (let index = visible.length - 1; index >= 0; index--) {
510 const record = visible[index]; 519 const record = visible[index];
511 const article = record.view.article; 520 const article = record.view.article;
512 const depth = visible.length - 1 - index; 521 const depth = visible.length - 1 - index;
513 const height = article.offsetHeight; 522 const height = article.offsetHeight;
514 if (depth === 0) newestHeight = height; 523 if (depth === 0) newestHeight = height;
515 article.style.setProperty( 524 article.style.setProperty(
516 "--zen-notification-depth", 525 "--zenbu-notification-depth",
517 String(depth), 526 String(depth),
518 ); 527 );
519 article.style.setProperty( 528 article.style.setProperty(
520 "--zen-notification-collapsed-offset", 529 "--zenbu-notification-collapsed-offset",
521 `${-depth * 10}px`, 530 `${-depth * 10}px`,
522 ); 531 );
523 article.style.setProperty( 532 article.style.setProperty(
524 "--zen-notification-collapsed-opacity", 533 "--zenbu-notification-collapsed-opacity",
525 String(Math.max(0.55, 1 - depth * 0.18)), 534 String(Math.max(0.55, 1 - depth * 0.18)),
526 ); 535 );
527 article.dataset.depth = String(depth); 536 article.dataset.depth = String(depth);
528 article.style.setProperty( 537 article.style.setProperty(
529 "--zen-notification-offset", 538 "--zenbu-notification-offset",
530 `${-expandedHeight}px`, 539 `${-expandedHeight}px`,
531 ); 540 );
532 article.style.setProperty( 541 article.style.setProperty(
533 "--zen-notification-scale", 542 "--zenbu-notification-scale",
534 String(Math.max(0.88, 1 - depth * 0.055)), 543 String(Math.max(0.88, 1 - depth * 0.055)),
535 ); 544 );
536 article.style.zIndex = String(visible.length - depth); 545 article.style.zIndex = String(visible.length - depth);
537 expandedHeight += height + gap; 546 expandedHeight += height + gap;
538 } 547 }
539 548
540 this.stack.dataset.count = String(visible.length); 549 this.stack.dataset.count = String(visible.length);
541 this.stack.style.setProperty( 550 this.stack.style.setProperty(
542 "--zen-notification-collapsed-height", 551 "--zenbu-notification-collapsed-height",
543 `${newestHeight + Math.max(0, visible.length - 1) * 10}px`, 552 `${newestHeight + Math.max(0, visible.length - 1) * 10}px`,
544 ); 553 );
545 this.stack.style.setProperty( 554 this.stack.style.setProperty(
546 "--zen-notification-expanded-height", 555 "--zenbu-notification-expanded-height",
547 `${Math.max(0, expandedHeight - gap)}px`, 556 `${Math.max(0, expandedHeight - gap)}px`,
548 ); 557 );
549 } 558 }
550 559
551 render() { 560 render() {