@khipu/design-system 0.3.5-alpha.2 → 0.3.5-alpha.20

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.
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
15
15
  * Source: design-system/src/tokens/tokens.json
16
- * Generated: 2026-08-13T15:55:41.056Z
16
+ * Generated: 2026-09-04T16:12:10.133Z
17
17
  *
18
18
  * To regenerate:
19
19
  * cd design-system && npm run tokens:generate
@@ -397,13 +397,13 @@
397
397
  /* ============================================================================
398
398
  DARK MODE COLOR TOKENS
399
399
  Override only color variables; everything else inherits from :root.
400
- Activated via [data-theme="dark"] on <html>, <body>, or a wrapper
401
- or via the BeerCSS-style body.dark class. Both selectors are emitted so
402
- a body.dark app gets the FULL dark theme (core + extended tokens); a
403
- single source flipping only partially is how mixed light/dark bugs arise.
400
+ Activated ONLY via [data-theme="dark"] on <html>, <body>, or a wrapper.
401
+ body.dark is deliberately NOT a trigger: BeerCSS adds that class on its
402
+ own when the OS reports a dark preference, so honouring it turns dark on
403
+ for consumers that never opted in. Dark stays opt-in until an app sets
404
+ data-theme explicitly.
404
405
  ============================================================================ */
405
406
 
406
- body.dark,
407
407
  [data-theme="dark"] {
408
408
 
409
409
  /* Primary palette - Khipu brand */
@@ -879,11 +879,10 @@ body.dark,
879
879
  values so selected states / rows / hovers adapt. Additive: the light
880
880
  :root / body.light values above are unchanged. Tokens already defined as
881
881
  var() refs (warning-soft, danger-soft) adapt on their own and are omitted.
882
- body.dark is included on purpose: the light group above also matches
883
- body.dark (specificity 0,1,1), so a bare [data-theme] override (0,1,0)
884
- would lose on a body that carries the transitional dark class.
882
+ body.dark is deliberately NOT a trigger: BeerCSS adds that class on its own
883
+ when the OS reports a dark preference, so honouring it turns dark on for
884
+ consumers that never opted in. Dark stays opt-in via data-theme.
885
885
  ======================================== */
886
- body.dark,
887
886
  [data-theme="dark"] {
888
887
  --kds-color-primary-faint: #1E1030; /* dark brand-tinted subtle surface (selected rows/cards) */
889
888
  --kds-color-info-soft: var(--kds-alert-info-bg); /* dark translucent blue */
@@ -917,7 +916,7 @@ body.dark,
917
916
  opt-out for Chrome/WebView "Auto Dark Theme" on Android: without it, an
918
917
  OS-dark device heuristically darkens SOME elements of a light page (mixed
919
918
  light/dark rendering in production apps). Dark is reached only by an
920
- explicit [data-theme="dark"] / body.dark opt-in. */
919
+ explicit [data-theme="dark"] opt-in. */
921
920
  :root,
922
921
  html,
923
922
  body {
@@ -936,8 +935,7 @@ body {
936
935
  }
937
936
 
938
937
  /* Explicit dark theme: let native UI (form controls, scrollbars) render dark. */
939
- [data-theme="dark"],
940
- body.dark {
938
+ [data-theme="dark"] {
941
939
  color-scheme: dark;
942
940
  }
943
941
 
@@ -999,6 +997,20 @@ footer {
999
997
  background-color: transparent !important;
1000
998
  }
1001
999
 
1000
+ /* Familia base del arbol del DS.
1001
+ BeerCSS la pone en `body { font-family: var(--font) }`, y `--font` es un alias
1002
+ declarado en :root. Una custom property se sustituye en el elemento que la declara,
1003
+ asi que ese alias se congela con la familia de :root: una marca que define
1004
+ --kds-font-family-primary mas abajo en el arbol no lo mueve, y el texto que solo
1005
+ hereda de body se queda con la familia por defecto.
1006
+
1007
+ Resolviendo el token aca, en la raiz del arbol del DS, hereda todo lo de adentro con
1008
+ la familia vigente en ese punto. Sin marca definida el valor es el mismo de siempre,
1009
+ asi que no cambia nada para quien no la configura. */
1010
+ .kds-theme-root {
1011
+ font-family: var(--kds-font-family-primary);
1012
+ }
1013
+
1002
1014
  /* ========================================
1003
1015
  TEXT UTILITIES OVERRIDE
1004
1016
  Fix BeerCSS classes that use wrong color tokens
@@ -2456,12 +2468,21 @@ a.kds-btn-google:disabled {
2456
2468
  /* Llena y centra dentro de contenedores flex (pantalla de carga); en flujo
2457
2469
  normal (contenedor no-flex) se reduce a su contenido. */
2458
2470
  flex: 1 1 auto;
2459
- /* Alto propio: el loader es contenido minimo por definicion, asi que sin esto la
2460
- card queda del alto del spinner y se lee como un recorte. Ocupa el body completo
2461
- de un telefono para que la pantalla de carga se lea como una pantalla y no como
2462
- una franja. Va aca y no en la card para que el minimo aplique solo a la carga:
2463
- el resto de las pantallas crece con su contenido. */
2464
- min-height: 36rem;
2471
+ /* Alto propio: el loader es contenido minimo por definicion, asi que sin esto la card
2472
+ queda del alto del spinner y se lee como un recorte. Va aca y no en la card para que
2473
+ el minimo aplique solo a la carga: el resto de las pantallas crece con su contenido.
2474
+ El valor de escritorio es moderado a proposito: el minimo de telefono (36rem) dejaba
2475
+ la card de la carga mucho mas alta que cualquier otra pantalla, y al entrar y salir
2476
+ del loader el widget crecia y volvia a encoger. */
2477
+ min-height: 18rem;
2478
+ }
2479
+
2480
+ /* En telefono la carga si ocupa el alto util de la pantalla: ahi la card va a ancho
2481
+ completo y un loader corto se lee como una franja perdida en el medio. */
2482
+ @media (max-width: 767px) {
2483
+ .kds-secure-loader {
2484
+ min-height: 36rem;
2485
+ }
2465
2486
  }
2466
2487
 
2467
2488
  .kds-secure-loader-text {
@@ -3271,12 +3292,33 @@ label.radio span {
3271
3292
  cursor: pointer;
3272
3293
  }
3273
3294
 
3274
- /* Checked state */
3295
+ /* Checked state.
3296
+ accent-color aplica al input nativo, que BeerCSS deja con opacity 0: no se ve.
3297
+ Se mantiene igual por si algun consumidor no usa el markup de BeerCSS. */
3275
3298
  label.checkbox input[type="checkbox"]:checked,
3276
3299
  label.radio input[type="radio"]:checked {
3277
3300
  accent-color: var(--kds-color-accent, var(--primary));
3278
3301
  }
3279
3302
 
3303
+ /* El control que SI se ve lo dibuja BeerCSS como una ligadura de Material Symbols en
3304
+ `> span::before`, coloreada con var(--primary). Estas dos reglas reproducen sus
3305
+ selectores para que ese pseudo-elemento siga al rol accent.
3306
+
3307
+ Misma especificidad que el original y sin !important: este archivo va ultimo en el
3308
+ bundle, asi que gana por orden. El fallback deja el valor anterior intacto cuando la
3309
+ marca no define accent, que es el caso de todo consumidor actual.
3310
+
3311
+ No se toca `.switch`: es un control de encendido/apagado, no de seleccion, y el
3312
+ contrato de marca no lo incluye. */
3313
+ :is(.checkbox, .radio) > span::before {
3314
+ color: var(--kds-color-accent, var(--primary));
3315
+ }
3316
+
3317
+ :is(.checkbox, .radio) > input:checked + span::before,
3318
+ :is(.checkbox, .radio).icon > input:checked + span > i {
3319
+ color: var(--kds-color-accent, var(--primary));
3320
+ }
3321
+
3280
3322
  /* Disabled state */
3281
3323
  label.checkbox input[type="checkbox"]:disabled,
3282
3324
  label.radio input[type="radio"]:disabled {
@@ -6279,13 +6321,39 @@ button.kds-fab:active:not(:disabled) {
6279
6321
  alto que el mark de Khipu. Sirve para <img> o un elemento con .kds-psp-mark. */
6280
6322
  .kds-secure-footer img,
6281
6323
  .kds-secure-footer .kds-psp-mark {
6282
- height: 11px;
6283
6324
  width: auto;
6284
6325
  flex: 0 0 auto;
6285
6326
  display: block;
6286
6327
  object-fit: contain;
6287
6328
  }
6288
6329
 
6330
+ /* Logo del PSP: dos perillas, las dos calibradas contra el asset real que sube un PSP
6331
+ hoy (klap.png, 60x30, con 2px de aire transparente arriba y abajo).
6332
+
6333
+ Se nivela por LINEA DE BASE, no por caja ni por borde inferior de la tinta: los dos
6334
+ wordmarks tienen descendente -la "p" de Khipu baja un 20% de su caja- asi que igualar
6335
+ los bordes de abajo deja al logo del PSP flotando sobre la linea de Khipu.
6336
+
6337
+ - --kds-psp-mark-height: el alto de la caja. El ojo lee la tinta, no la caja, y el
6338
+ aire del PNG se come parte del alto: a 11px la tinta de klap medía 9.5px contra los
6339
+ 11px de Khipu y se veia mas chico. A 15px la tinta llega a 13px y se leen iguales.
6340
+ - --kds-psp-mark-top: cuanto baja para que su linea de base calce con la de Khipu.
6341
+ Va como valor y no como formula a proposito: depende del aire y de la anatomia del
6342
+ asset de cada PSP, y se termina de ajustar mirando la pantalla. */
6343
+ .kds-secure-footer {
6344
+ --kds-psp-mark-height: 15px;
6345
+ --kds-psp-mark-top: 1px;
6346
+ }
6347
+
6348
+ .kds-secure-footer img,
6349
+ .kds-secure-footer .kds-psp-mark {
6350
+ height: var(--kds-psp-mark-height);
6351
+ position: relative;
6352
+ top: var(--kds-psp-mark-top);
6353
+ }
6354
+
6355
+
6356
+
6289
6357
  .kds-secure-footer-code {
6290
6358
  color: var(--kds-color-text-hint);
6291
6359
  }
@@ -6541,14 +6609,37 @@ button.kds-fab:active:not(:disabled) {
6541
6609
  color: var(--kds-color-success-main);
6542
6610
  }
6543
6611
 
6544
- /* Variant: pending (animated blue spinner, sin icono interno) */
6612
+ /* Variant: pending (animated spinner, sin icono interno).
6613
+ El acento sale de --kds-status-pending-accent, con el azul de info como
6614
+ fallback: el valor por defecto no cambia, y una pantalla puede pedir otro
6615
+ acento sin redefinir la regla ni recurrir a estilos inline. */
6545
6616
  .kds-status-block[data-status="pending"] .kds-status-block-icon {
6546
6617
  background: transparent;
6547
6618
  border: 3px solid var(--kds-color-divider);
6548
- border-top-color: var(--kds-color-info-main);
6619
+ border-top-color: var(--kds-status-pending-accent, var(--kds-color-info-main));
6549
6620
  animation: kds-spin 1s linear infinite;
6550
6621
  }
6551
6622
 
6623
+ /* Spinner neutro, para esperas que acompañan al contenido sin reclamar
6624
+ atención: el azul de info lee como un aviso y compite con la información
6625
+ de la pantalla. */
6626
+ .kds-status-block.kds-accent-neutral {
6627
+ --kds-status-pending-accent: var(--kds-color-text-primary);
6628
+ }
6629
+
6630
+ /* Spinner fino, para bandas de una línea donde el trazo de 3px pesa demasiado.
6631
+ El tamaño se pide por token y no por regla sobre el icono: así no compite en
6632
+ especificidad con `.kds-status-block.inline .kds-status-block-icon`, que
6633
+ marca igual y viene después. */
6634
+ .kds-status-block.kds-spinner-sm {
6635
+ --kds-status-icon-size: var(--kds-spacing-3);
6636
+ --kds-status-icon-size-sm: var(--kds-spacing-3);
6637
+ }
6638
+
6639
+ .kds-status-block.kds-spinner-sm .kds-status-block-icon {
6640
+ border-width: 2px;
6641
+ }
6642
+
6552
6643
  /* Variant: warn */
6553
6644
  .kds-status-block[data-status="warn"] .kds-status-block-icon {
6554
6645
  background: var(--kds-color-warning-soft);
@@ -6592,6 +6683,31 @@ button.kds-fab:active:not(:disabled) {
6592
6683
  font-size: var(--kds-font-size-xl);
6593
6684
  }
6594
6685
 
6686
+ /* Banda: el bloque de estado sobre una superficie sutil, para cuando acompaña
6687
+ al contenido en lugar de encabezar la pantalla. Va después de `.inline`
6688
+ porque marca igual en especificidad y necesita ganarle el padding. */
6689
+ .kds-status-block.kds-banded {
6690
+ /* 0.75rem = 12px, el valor que --kds-font-size-xs toma desde tablet. Va plano y no
6691
+ sigue la escala responsiva a proposito: a 11px el mensaje de espera se parte en dos
6692
+ lineas y el diseno lo quiere en una. Como custom property de la variante, siguiendo
6693
+ el patron de kds-spinner-sm con --kds-status-icon-size. */
6694
+ --kds-status-band-font-size: 0.75rem;
6695
+ background: var(--kds-color-background-muted);
6696
+ border-radius: var(--kds-radius-md);
6697
+ padding: var(--kds-spacing-1-5) var(--kds-spacing-2);
6698
+ }
6699
+
6700
+ /* La banda de espera lleva su propia tipografia, por spec de Figma: 11px en bold.
6701
+ Va scopeada a la variante y no a .kds-status-block-description, que la comparten
6702
+ los bloques de resultado, donde 11px seria ilegible. El color se deja en el token
6703
+ heredado: un #000 fijo se volveria invisible bajo modo oscuro, donde text-primary
6704
+ pasa a blanco. */
6705
+ .kds-status-block.kds-banded .kds-status-block-description {
6706
+ font-size: var(--kds-status-band-font-size);
6707
+ font-weight: var(--kds-font-weight-bold);
6708
+ line-height: normal;
6709
+ }
6710
+
6595
6711
  @keyframes kds-spin {
6596
6712
  to { transform: rotate(360deg); }
6597
6713
  }
@@ -7989,6 +8105,36 @@ svg[fill='none'] {
7989
8105
  }
7990
8106
  }
7991
8107
 
8108
+ /* Widget embebido: dentro de un iframe los media queries de ancho evalúan el viewport
8109
+ DEL IFRAME (~420px), no la pantalla, así que el flujo se ve siempre como mobile
8110
+ aunque el usuario esté en desktop — y el tope plano, pensado para cuando la card se
8111
+ pega al borde del teléfono, queda cortando una card que en realidad flota centrada.
8112
+ `hover`/`pointer` describen el dispositivo de entrada y no el viewport, así que
8113
+ sobreviven al iframe: con mouse las cards conservan las esquinas superiores. */
8114
+ @media (hover: hover) and (pointer: fine) {
8115
+ .kds-card-elevated.kds-invoice-sticky {
8116
+ border-radius: var(--kds-radius-card);
8117
+ /* El clip-path del colapso recorta con `round 0 0 R R` y eso PISA al
8118
+ border-radius: aunque la card declare las cuatro esquinas, el recorte las
8119
+ deja rectas arriba. Hay que redondearlas también acá o el fix no se ve. */
8120
+ clip-path: inset(
8121
+ 0 0
8122
+ calc(
8123
+ var(--collapse-collapsible-h) * var(--collapse-progress)
8124
+ + var(--kds-spacing-1) * var(--collapse-progress)
8125
+ )
8126
+ 0
8127
+ round var(--kds-radius-card)
8128
+ );
8129
+ }
8130
+
8131
+ .kds-screen > .kds-card-elevated.kds-card-elevated--flush-top,
8132
+ .kds-card-elevated.kds-card-elevated--flush-top {
8133
+ border-top-left-radius: var(--kds-radius-card);
8134
+ border-top-right-radius: var(--kds-radius-card);
8135
+ }
8136
+ }
8137
+
7992
8138
  /* Brand logo row (external, outside cards) */
7993
8139
  .kds-brand-row {
7994
8140
  padding: 0 var(--kds-spacing-0-5);
@@ -895,70 +895,15 @@ const ui = _context.ui;
895
895
  initHideOnScroll();
896
896
  initBankModal();
897
897
  initMerchantLogoBackdrop();
898
- initCardHeightTransition();
899
898
 
900
899
  console.log('Material Design initialization complete!');
901
900
  }
902
901
 
903
- /**
904
- * Initialize body card height transition (KTUF-239)
905
- * La body card cambia de alto al pasar de una pantalla a otra (loader ↔ formulario)
906
- * y el salto se nota, sobre todo desde que la card ya no lleva un alto mínimo fijo.
907
- * No se puede resolver con CSS: un cambio de altura provocado por contenido no
908
- * dispara transiciones, porque el valor declarado sigue siendo `auto` antes y
909
- * después (ni `interpolate-size` lo cubre — ese habilita animar hacia `auto` cuando
910
- * el valor declarado cambia, que no es el caso). Por eso se mide y se anima acá.
911
- * @param {Element} root - Root element to scope the query (default: document)
912
- */
913
- function initCardHeightTransition(root) {
914
- root = root || document;
915
- var MIN_DELTA_PX = 8;
916
- var DURATION_MS = 280;
917
-
918
- if (typeof ResizeObserver === 'undefined' || typeof Element.prototype.animate !== 'function') {
919
- return;
920
- }
921
- if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
922
- return;
923
- }
924
-
925
- var cards = root.querySelectorAll('.kds-screen > .kds-card-elevated');
926
- Array.prototype.forEach.call(cards, function(card) {
927
- if (card.getAttribute('data-kds-height-transition') === 'on') {
928
- return;
929
- }
930
- card.setAttribute('data-kds-height-transition', 'on');
931
-
932
- var previous = card.offsetHeight;
933
- var animating = false;
934
-
935
- var observer = new ResizeObserver(function() {
936
- // La animación cambia el alto y volveria a disparar el observer.
937
- if (animating) {
938
- return;
939
- }
940
- var next = card.offsetHeight;
941
- // Umbral: ignora reflows menores (fuentes que cargan, scrollbars).
942
- if (Math.abs(next - previous) < MIN_DELTA_PX) {
943
- previous = next;
944
- return;
945
- }
946
- animating = true;
947
- var animation = card.animate(
948
- [{ height: previous + 'px' }, { height: next + 'px' }],
949
- { duration: DURATION_MS, easing: 'ease-out' }
950
- );
951
- previous = next;
952
- animation.finished.then(function() {
953
- animating = false;
954
- previous = card.offsetHeight;
955
- }).catch(function() {
956
- animating = false;
957
- });
958
- });
959
- observer.observe(card);
960
- });
961
- }
902
+ // El alto de la body card cambia de golpe entre pantallas, sin animar. Hubo una
903
+ // transición acá (KTUF-239) y se quitó en KTUF-314: ver el comentario en
904
+ // KdsThemeProvider.tsx animar el alto obliga a fijar `height` en píxeles, y con el
905
+ // alto fijado no hay forma de medir el alto natural del contenido para saber cuándo
906
+ // soltarlo, así que la card quedaba con espacio muerto abajo.
962
907
 
963
908
  /**
964
909
  * Initialize merchant logo backdrop (KTUF-204)
@@ -1798,7 +1743,6 @@ const ui = _context.ui;
1798
1743
  window.Khipu.initBankModal = initBankModal;
1799
1744
  window.Khipu.initStickyInvoice = initStickyInvoice;
1800
1745
  window.Khipu.initHideOnScroll = initHideOnScroll;
1801
- window.Khipu.initCardHeightTransition = initCardHeightTransition;
1802
1746
 
1803
1747
  // Also export showSnackbar to global scope for backward compatibility
1804
1748
  window.showSnackbar = showSnackbar;