@khipu/design-system 0.2.0-alpha.83 → 0.2.0-alpha.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
13
13
  * Source: design-system/src/tokens/tokens.json
14
- * Generated: 2026-07-01T02:24:50.156Z
14
+ * Generated: 2026-07-01T17:49:00.275Z
15
15
  *
16
16
  * To regenerate:
17
17
  * cd design-system && npm run tokens:generate
@@ -5508,6 +5508,125 @@ dialog#surveyModal button.circle {
5508
5508
  box-shadow: 0 -4px 16px rgba(16,24,40,.08);
5509
5509
  }
5510
5510
 
5511
+ /* ========================================
5512
+ 08b - FAB (.kds-fab)
5513
+ Circular, icon-only floating action button with an animated hide state.
5514
+ Presentational: drive `.kds-fab--hidden` from useHideOnScroll for scroll-away.
5515
+ ======================================== */
5516
+
5517
+ .kds-fab {
5518
+ display: inline-flex;
5519
+ align-items: center;
5520
+ justify-content: center;
5521
+ width: var(--kds-spacing-5);
5522
+ height: var(--kds-spacing-5);
5523
+ padding: 0;
5524
+ border: none;
5525
+ border-radius: var(--kds-radius-full);
5526
+ background: var(--kds-color-background-paper);
5527
+ color: var(--kds-color-text-primary);
5528
+ box-shadow: var(--kds-shadow-elevated);
5529
+ cursor: pointer;
5530
+ z-index: 100;
5531
+ /* Neutralize native button rendering so the pressed state doesn't flash a square. */
5532
+ -webkit-appearance: none;
5533
+ appearance: none;
5534
+ -webkit-tap-highlight-color: transparent;
5535
+ transition: opacity var(--kds-transition-short) ease, transform var(--kds-transition-short) ease,
5536
+ background-color var(--kds-transition-shorter) ease;
5537
+ }
5538
+
5539
+ /* Keep the circle in EVERY interaction state. Uses `button.kds-fab` (element + class) to
5540
+ out-specify BeerCSS's base `button`/`button:active` rules — same trick as `.kds-btn`. */
5541
+ button.kds-fab,
5542
+ button.kds-fab:hover,
5543
+ button.kds-fab:active,
5544
+ button.kds-fab:focus,
5545
+ button.kds-fab:focus-visible {
5546
+ border-radius: var(--kds-radius-full);
5547
+ background: var(--kds-color-background-paper);
5548
+ }
5549
+
5550
+ button.kds-fab:hover:not(:disabled) {
5551
+ background: var(--kds-color-background-muted);
5552
+ }
5553
+
5554
+ /* Subtle press feedback that preserves the round shape. */
5555
+ button.kds-fab:active:not(:disabled) {
5556
+ transform: translateY(var(--kds-spacing-0-25));
5557
+ }
5558
+
5559
+ button.kds-fab:focus-visible {
5560
+ outline: var(--kds-spacing-0-25) solid var(--kds-color-primary-main);
5561
+ outline-offset: var(--kds-spacing-0-25);
5562
+ }
5563
+
5564
+ .kds-fab > i.material-symbols-outlined {
5565
+ font-size: var(--kds-spacing-button-icon-size);
5566
+ line-height: 1;
5567
+ }
5568
+
5569
+ /* Pinned corners — fixed to the viewport (stays visible on scroll-up, iframe-friendly).
5570
+ Horizontally the top-right FAB hugs the right edge of the centered content column set via
5571
+ `--kds-fab-column-width` (default 100% → viewport edge). The consumer sets that var to its
5572
+ widget width so on desktop the FAB overlaps the header's top-right corner instead of drifting
5573
+ to the far viewport edge. `max(...)` keeps at least a token inset on narrow (mobile) viewports. */
5574
+ .kds-fab--top-right,
5575
+ .kds-fab--top-left,
5576
+ .kds-fab--bottom-right,
5577
+ .kds-fab--bottom-left {
5578
+ position: fixed;
5579
+ }
5580
+
5581
+ .kds-fab--top-right {
5582
+ top: var(--kds-spacing-2);
5583
+ right: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5584
+ }
5585
+
5586
+ .kds-fab--top-left {
5587
+ top: var(--kds-spacing-2);
5588
+ left: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5589
+ }
5590
+
5591
+ .kds-fab--bottom-right {
5592
+ bottom: var(--kds-spacing-2);
5593
+ right: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5594
+ }
5595
+
5596
+ .kds-fab--bottom-left {
5597
+ bottom: var(--kds-spacing-2);
5598
+ left: max(var(--kds-spacing-2), calc((100% - var(--kds-fab-column-width, 100%)) / 2 + var(--kds-spacing-2)));
5599
+ }
5600
+
5601
+ /* Desktop: nudge the top-right FAB up so it sits partially over the header's top edge. */
5602
+ @media (min-width: 768px) {
5603
+ .kds-fab--top-right {
5604
+ top: var(--kds-spacing-1);
5605
+ }
5606
+ }
5607
+
5608
+ /* Hidden (scroll-away) state — fade + slide toward the nearest edge */
5609
+ .kds-fab--hidden {
5610
+ opacity: 0;
5611
+ transform: translateY(calc(-1 * var(--kds-spacing-1))) scale(0.9);
5612
+ pointer-events: none;
5613
+ }
5614
+
5615
+ .kds-fab--bottom-right.kds-fab--hidden,
5616
+ .kds-fab--bottom-left.kds-fab--hidden {
5617
+ transform: translateY(var(--kds-spacing-1)) scale(0.9);
5618
+ }
5619
+
5620
+ @media (prefers-reduced-motion: reduce) {
5621
+ .kds-fab {
5622
+ transition: opacity var(--kds-transition-short) ease;
5623
+ }
5624
+
5625
+ .kds-fab--hidden {
5626
+ transform: none;
5627
+ }
5628
+ }
5629
+
5511
5630
  /* ========================================
5512
5631
  09 - SUCCESS MARK (.kds-success-mark)
5513
5632
  Large success icon for completion screens
@@ -892,6 +892,7 @@ const ui = _context.ui;
892
892
  initCountdown();
893
893
  initSegmentedTabs();
894
894
  initStickyInvoice();
895
+ initHideOnScroll();
895
896
  initBankModal();
896
897
 
897
898
  console.log('Material Design initialization complete!');
@@ -1429,6 +1430,67 @@ const ui = _context.ui;
1429
1430
  onScroll();
1430
1431
  }
1431
1432
 
1433
+ /**
1434
+ * Initialize hide-on-scroll for floating elements (vanilla equivalent of the React
1435
+ * `useHideOnScroll` hook). Any element with `[data-hide-on-scroll]` gets its hide class
1436
+ * (default `kds-fab--hidden`, override with `data-hide-class`) toggled: hidden when
1437
+ * scrolling down past the threshold, shown when scrolling up, always shown within
1438
+ * `data-hide-top-offset` px of the top. Tune with `data-hide-threshold` (default 8).
1439
+ *
1440
+ * Works standalone (`window.scrollY`) and embedded in an iframe, where the parent posts
1441
+ * `postMessage({ type: 'VIEWPORT_OFFSET', offsetTop })`.
1442
+ * @param {Element} root - Root element to scope the query (default: document)
1443
+ */
1444
+ function initHideOnScroll(root) {
1445
+ root = root || document;
1446
+ var els = root.querySelectorAll('[data-hide-on-scroll]');
1447
+ if (!els.length) return;
1448
+
1449
+ var viewportOffset = 0;
1450
+ var ticking = false;
1451
+ var lastYs = new Map();
1452
+
1453
+ function currentY() {
1454
+ return Math.max(window.scrollY || window.pageYOffset || 0, viewportOffset);
1455
+ }
1456
+
1457
+ function apply() {
1458
+ ticking = false;
1459
+ var y = currentY();
1460
+ els.forEach(function(el) {
1461
+ var threshold = parseInt(el.getAttribute('data-hide-threshold'), 10) || 8;
1462
+ var topOffset = parseInt(el.getAttribute('data-hide-top-offset'), 10) || 0;
1463
+ var hideClass = el.getAttribute('data-hide-class') || 'kds-fab--hidden';
1464
+ var lastY = lastYs.has(el) ? lastYs.get(el) : y;
1465
+ if (y <= topOffset) {
1466
+ el.classList.remove(hideClass);
1467
+ lastYs.set(el, y);
1468
+ return;
1469
+ }
1470
+ var delta = y - lastY;
1471
+ if (Math.abs(delta) < threshold) return;
1472
+ el.classList.toggle(hideClass, delta > 0);
1473
+ lastYs.set(el, y);
1474
+ });
1475
+ }
1476
+
1477
+ function onScroll() {
1478
+ if (ticking) return;
1479
+ ticking = true;
1480
+ requestAnimationFrame(apply);
1481
+ }
1482
+
1483
+ window.addEventListener('scroll', onScroll, { passive: true });
1484
+ window.addEventListener('resize', onScroll);
1485
+ window.addEventListener('message', function(event) {
1486
+ if (event.data && event.data.type === 'VIEWPORT_OFFSET') {
1487
+ viewportOffset = Math.max(0, event.data.offsetTop || 0);
1488
+ onScroll();
1489
+ }
1490
+ });
1491
+ apply();
1492
+ }
1493
+
1432
1494
  /**
1433
1495
  * Initialize copyable table rows
1434
1496
  * Delegated click on .kds-copyable-table-row[data-copy] copies value and shows feedback
@@ -1599,6 +1661,7 @@ const ui = _context.ui;
1599
1661
  window.Khipu.initInfoTip = initInfoTip;
1600
1662
  window.Khipu.initBankModal = initBankModal;
1601
1663
  window.Khipu.initStickyInvoice = initStickyInvoice;
1664
+ window.Khipu.initHideOnScroll = initHideOnScroll;
1602
1665
 
1603
1666
  // Also export showSnackbar to global scope for backward compatibility
1604
1667
  window.showSnackbar = showSnackbar;