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

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-04T20:24:13.733Z
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
@@ -1210,6 +1222,29 @@ a.kds-btn-outlined-white:hover:not(:disabled) {
1210
1222
  opacity: 0.9;
1211
1223
  }
1212
1224
 
1225
+ /* Outlined success — compone con .kds-btn-outlined, igual que .kds-btn-outlined-white:
1226
+ solo cambia superficie y color, la caja la aporta la variante base. */
1227
+ button.kds-btn-outlined-success,
1228
+ a.kds-btn-outlined-success {
1229
+ background: transparent;
1230
+ color: var(--kds-color-success-main);
1231
+ border-color: var(--kds-color-success-main);
1232
+ }
1233
+
1234
+ button.kds-btn-outlined-success:hover:not(:disabled),
1235
+ a.kds-btn-outlined-success:hover:not(:disabled) {
1236
+ background: var(--kds-color-success-soft);
1237
+ border-color: var(--kds-color-success-dark);
1238
+ color: var(--kds-color-success-dark);
1239
+ }
1240
+
1241
+ button.kds-btn-outlined-success:disabled,
1242
+ a.kds-btn-outlined-success:disabled {
1243
+ background: transparent;
1244
+ color: var(--kds-color-action-disabled);
1245
+ border-color: var(--kds-color-action-disabled);
1246
+ }
1247
+
1213
1248
  /* Text button — mismo tamaño que el resto de variantes (hereda min-height/padding
1214
1249
  del base): solo cambia la superficie, no la caja. */
1215
1250
  button.kds-btn-text,
@@ -2456,12 +2491,21 @@ a.kds-btn-google:disabled {
2456
2491
  /* Llena y centra dentro de contenedores flex (pantalla de carga); en flujo
2457
2492
  normal (contenedor no-flex) se reduce a su contenido. */
2458
2493
  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;
2494
+ /* Alto propio: el loader es contenido minimo por definicion, asi que sin esto la card
2495
+ queda del alto del spinner y se lee como un recorte. Va aca y no en la card para que
2496
+ el minimo aplique solo a la carga: el resto de las pantallas crece con su contenido.
2497
+ El valor de escritorio es moderado a proposito: el minimo de telefono (36rem) dejaba
2498
+ la card de la carga mucho mas alta que cualquier otra pantalla, y al entrar y salir
2499
+ del loader el widget crecia y volvia a encoger. */
2500
+ min-height: 18rem;
2501
+ }
2502
+
2503
+ /* En telefono la carga si ocupa el alto util de la pantalla: ahi la card va a ancho
2504
+ completo y un loader corto se lee como una franja perdida en el medio. */
2505
+ @media (max-width: 767px) {
2506
+ .kds-secure-loader {
2507
+ min-height: 36rem;
2508
+ }
2465
2509
  }
2466
2510
 
2467
2511
  .kds-secure-loader-text {
@@ -3271,12 +3315,33 @@ label.radio span {
3271
3315
  cursor: pointer;
3272
3316
  }
3273
3317
 
3274
- /* Checked state */
3318
+ /* Checked state.
3319
+ accent-color aplica al input nativo, que BeerCSS deja con opacity 0: no se ve.
3320
+ Se mantiene igual por si algun consumidor no usa el markup de BeerCSS. */
3275
3321
  label.checkbox input[type="checkbox"]:checked,
3276
3322
  label.radio input[type="radio"]:checked {
3277
3323
  accent-color: var(--kds-color-accent, var(--primary));
3278
3324
  }
3279
3325
 
3326
+ /* El control que SI se ve lo dibuja BeerCSS como una ligadura de Material Symbols en
3327
+ `> span::before`, coloreada con var(--primary). Estas dos reglas reproducen sus
3328
+ selectores para que ese pseudo-elemento siga al rol accent.
3329
+
3330
+ Misma especificidad que el original y sin !important: este archivo va ultimo en el
3331
+ bundle, asi que gana por orden. El fallback deja el valor anterior intacto cuando la
3332
+ marca no define accent, que es el caso de todo consumidor actual.
3333
+
3334
+ No se toca `.switch`: es un control de encendido/apagado, no de seleccion, y el
3335
+ contrato de marca no lo incluye. */
3336
+ :is(.checkbox, .radio) > span::before {
3337
+ color: var(--kds-color-accent, var(--primary));
3338
+ }
3339
+
3340
+ :is(.checkbox, .radio) > input:checked + span::before,
3341
+ :is(.checkbox, .radio).icon > input:checked + span > i {
3342
+ color: var(--kds-color-accent, var(--primary));
3343
+ }
3344
+
3280
3345
  /* Disabled state */
3281
3346
  label.checkbox input[type="checkbox"]:disabled,
3282
3347
  label.radio input[type="radio"]:disabled {
@@ -4330,14 +4395,16 @@ footer > .kds-container-center {
4330
4395
 
4331
4396
  /* Code block for displaying code snippets */
4332
4397
  .kds-code-block {
4333
- background: var(--kds-color-background-elevated);
4334
- padding: var(--kds-spacing-4);
4398
+ background: var(--kds-color-background-muted);
4399
+ border: 1px solid var(--kds-color-divider);
4400
+ padding: var(--kds-spacing-2);
4335
4401
  border-radius: var(--kds-radius-md);
4336
4402
  overflow-x: auto;
4337
- font-family: 'Courier New', monospace;
4403
+ font-family: var(--kds-font-family-mono);
4338
4404
  font-size: var(--kds-font-size-sm);
4339
4405
  line-height: var(--kds-line-height-relaxed);
4340
4406
  color: var(--kds-color-text-primary);
4407
+ tab-size: 4;
4341
4408
  }
4342
4409
 
4343
4410
  /* ============================================
@@ -6279,13 +6346,39 @@ button.kds-fab:active:not(:disabled) {
6279
6346
  alto que el mark de Khipu. Sirve para <img> o un elemento con .kds-psp-mark. */
6280
6347
  .kds-secure-footer img,
6281
6348
  .kds-secure-footer .kds-psp-mark {
6282
- height: 11px;
6283
6349
  width: auto;
6284
6350
  flex: 0 0 auto;
6285
6351
  display: block;
6286
6352
  object-fit: contain;
6287
6353
  }
6288
6354
 
6355
+ /* Logo del PSP: dos perillas, las dos calibradas contra el asset real que sube un PSP
6356
+ hoy (klap.png, 60x30, con 2px de aire transparente arriba y abajo).
6357
+
6358
+ Se nivela por LINEA DE BASE, no por caja ni por borde inferior de la tinta: los dos
6359
+ wordmarks tienen descendente -la "p" de Khipu baja un 20% de su caja- asi que igualar
6360
+ los bordes de abajo deja al logo del PSP flotando sobre la linea de Khipu.
6361
+
6362
+ - --kds-psp-mark-height: el alto de la caja. El ojo lee la tinta, no la caja, y el
6363
+ aire del PNG se come parte del alto: a 11px la tinta de klap medía 9.5px contra los
6364
+ 11px de Khipu y se veia mas chico. A 15px la tinta llega a 13px y se leen iguales.
6365
+ - --kds-psp-mark-top: cuanto baja para que su linea de base calce con la de Khipu.
6366
+ Va como valor y no como formula a proposito: depende del aire y de la anatomia del
6367
+ asset de cada PSP, y se termina de ajustar mirando la pantalla. */
6368
+ .kds-secure-footer {
6369
+ --kds-psp-mark-height: 15px;
6370
+ --kds-psp-mark-top: 1px;
6371
+ }
6372
+
6373
+ .kds-secure-footer img,
6374
+ .kds-secure-footer .kds-psp-mark {
6375
+ height: var(--kds-psp-mark-height);
6376
+ position: relative;
6377
+ top: var(--kds-psp-mark-top);
6378
+ }
6379
+
6380
+
6381
+
6289
6382
  .kds-secure-footer-code {
6290
6383
  color: var(--kds-color-text-hint);
6291
6384
  }
@@ -6541,14 +6634,37 @@ button.kds-fab:active:not(:disabled) {
6541
6634
  color: var(--kds-color-success-main);
6542
6635
  }
6543
6636
 
6544
- /* Variant: pending (animated blue spinner, sin icono interno) */
6637
+ /* Variant: pending (animated spinner, sin icono interno).
6638
+ El acento sale de --kds-status-pending-accent, con el azul de info como
6639
+ fallback: el valor por defecto no cambia, y una pantalla puede pedir otro
6640
+ acento sin redefinir la regla ni recurrir a estilos inline. */
6545
6641
  .kds-status-block[data-status="pending"] .kds-status-block-icon {
6546
6642
  background: transparent;
6547
6643
  border: 3px solid var(--kds-color-divider);
6548
- border-top-color: var(--kds-color-info-main);
6644
+ border-top-color: var(--kds-status-pending-accent, var(--kds-color-info-main));
6549
6645
  animation: kds-spin 1s linear infinite;
6550
6646
  }
6551
6647
 
6648
+ /* Spinner neutro, para esperas que acompañan al contenido sin reclamar
6649
+ atención: el azul de info lee como un aviso y compite con la información
6650
+ de la pantalla. */
6651
+ .kds-status-block.kds-accent-neutral {
6652
+ --kds-status-pending-accent: var(--kds-color-text-primary);
6653
+ }
6654
+
6655
+ /* Spinner fino, para bandas de una línea donde el trazo de 3px pesa demasiado.
6656
+ El tamaño se pide por token y no por regla sobre el icono: así no compite en
6657
+ especificidad con `.kds-status-block.inline .kds-status-block-icon`, que
6658
+ marca igual y viene después. */
6659
+ .kds-status-block.kds-spinner-sm {
6660
+ --kds-status-icon-size: var(--kds-spacing-3);
6661
+ --kds-status-icon-size-sm: var(--kds-spacing-3);
6662
+ }
6663
+
6664
+ .kds-status-block.kds-spinner-sm .kds-status-block-icon {
6665
+ border-width: 2px;
6666
+ }
6667
+
6552
6668
  /* Variant: warn */
6553
6669
  .kds-status-block[data-status="warn"] .kds-status-block-icon {
6554
6670
  background: var(--kds-color-warning-soft);
@@ -6592,6 +6708,31 @@ button.kds-fab:active:not(:disabled) {
6592
6708
  font-size: var(--kds-font-size-xl);
6593
6709
  }
6594
6710
 
6711
+ /* Banda: el bloque de estado sobre una superficie sutil, para cuando acompaña
6712
+ al contenido en lugar de encabezar la pantalla. Va después de `.inline`
6713
+ porque marca igual en especificidad y necesita ganarle el padding. */
6714
+ .kds-status-block.kds-banded {
6715
+ /* 0.75rem = 12px, el valor que --kds-font-size-xs toma desde tablet. Va plano y no
6716
+ sigue la escala responsiva a proposito: a 11px el mensaje de espera se parte en dos
6717
+ lineas y el diseno lo quiere en una. Como custom property de la variante, siguiendo
6718
+ el patron de kds-spinner-sm con --kds-status-icon-size. */
6719
+ --kds-status-band-font-size: 0.75rem;
6720
+ background: var(--kds-color-background-muted);
6721
+ border-radius: var(--kds-radius-md);
6722
+ padding: var(--kds-spacing-1-5) var(--kds-spacing-2);
6723
+ }
6724
+
6725
+ /* La banda de espera lleva su propia tipografia, por spec de Figma: 11px en bold.
6726
+ Va scopeada a la variante y no a .kds-status-block-description, que la comparten
6727
+ los bloques de resultado, donde 11px seria ilegible. El color se deja en el token
6728
+ heredado: un #000 fijo se volveria invisible bajo modo oscuro, donde text-primary
6729
+ pasa a blanco. */
6730
+ .kds-status-block.kds-banded .kds-status-block-description {
6731
+ font-size: var(--kds-status-band-font-size);
6732
+ font-weight: var(--kds-font-weight-bold);
6733
+ line-height: normal;
6734
+ }
6735
+
6595
6736
  @keyframes kds-spin {
6596
6737
  to { transform: rotate(360deg); }
6597
6738
  }
@@ -7989,6 +8130,36 @@ svg[fill='none'] {
7989
8130
  }
7990
8131
  }
7991
8132
 
8133
+ /* Widget embebido: dentro de un iframe los media queries de ancho evalúan el viewport
8134
+ DEL IFRAME (~420px), no la pantalla, así que el flujo se ve siempre como mobile
8135
+ aunque el usuario esté en desktop — y el tope plano, pensado para cuando la card se
8136
+ pega al borde del teléfono, queda cortando una card que en realidad flota centrada.
8137
+ `hover`/`pointer` describen el dispositivo de entrada y no el viewport, así que
8138
+ sobreviven al iframe: con mouse las cards conservan las esquinas superiores. */
8139
+ @media (hover: hover) and (pointer: fine) {
8140
+ .kds-card-elevated.kds-invoice-sticky {
8141
+ border-radius: var(--kds-radius-card);
8142
+ /* El clip-path del colapso recorta con `round 0 0 R R` y eso PISA al
8143
+ border-radius: aunque la card declare las cuatro esquinas, el recorte las
8144
+ deja rectas arriba. Hay que redondearlas también acá o el fix no se ve. */
8145
+ clip-path: inset(
8146
+ 0 0
8147
+ calc(
8148
+ var(--collapse-collapsible-h) * var(--collapse-progress)
8149
+ + var(--kds-spacing-1) * var(--collapse-progress)
8150
+ )
8151
+ 0
8152
+ round var(--kds-radius-card)
8153
+ );
8154
+ }
8155
+
8156
+ .kds-screen > .kds-card-elevated.kds-card-elevated--flush-top,
8157
+ .kds-card-elevated.kds-card-elevated--flush-top {
8158
+ border-top-left-radius: var(--kds-radius-card);
8159
+ border-top-right-radius: var(--kds-radius-card);
8160
+ }
8161
+ }
8162
+
7992
8163
  /* Brand logo row (external, outside cards) */
7993
8164
  .kds-brand-row {
7994
8165
  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;