@kaufmann-lab/ui 0.1.3 → 0.1.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaufmann-lab/ui",
3
- "version": "0.1.3",
3
+ "version": "0.1.9",
4
4
  "description": "Shared Angular component library for the Kaufmann frontends",
5
5
  "license": "MIT",
6
6
  "author": "Kaufmann Lab",
@@ -8,6 +8,7 @@
8
8
  "access": "public"
9
9
  },
10
10
  "peerDependencies": {
11
+ "@angular/cdk": "^22.1.0",
11
12
  "@angular/common": "^22.1.0",
12
13
  "@angular/core": "^22.1.0"
13
14
  },
@@ -106,6 +106,26 @@
106
106
  --kf-font-body: 'Roboto', system-ui, -apple-system, sans-serif;
107
107
  --kf-font-mono: 'Roboto Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
108
108
 
109
+ /*
110
+ * Escala de tamaño (usada por `kf-text`, ver variant `title-lg|md|sm` y
111
+ * `body|body-muted|caption`). Antes de esta escala, cada pantalla que
112
+ * consume la librería definía su propio `font-size` suelto para el mismo
113
+ * rol semántico — auditoría sobre `web-portal-latam-frontend` encontró 9
114
+ * valores distintos entre login y home para títulos/subtítulos/texto
115
+ * secundario (0.6875rem a 2.125rem), sin ningún paso de escala en común.
116
+ * Estos 6 pasos cubren esos casos reales, no son arbitrarios.
117
+ */
118
+ --kf-font-size-title-lg: 1.75rem;
119
+ --kf-font-size-title-md: 1.5rem;
120
+ --kf-font-size-title-sm: 1.125rem;
121
+ --kf-font-size-body: 1rem;
122
+ --kf-font-size-body-sm: 0.875rem;
123
+ --kf-font-size-caption: 0.75rem;
124
+
125
+ /* ---------- Peso de fuente ---------- */
126
+ --kf-font-weight-title: 700;
127
+ --kf-font-weight-body: 400;
128
+
109
129
  /* ---------- Espaciado (escala 4/8px) ---------- */
110
130
  --kf-space-xs: 4px;
111
131
  --kf-space-sm: 8px;
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { AfterContentInit, TemplateRef, AfterViewInit } from '@angular/core';
2
+ import { AfterContentInit, TemplateRef, ElementRef, AfterViewInit } from '@angular/core';
3
3
  import { ControlValueAccessor } from '@angular/forms';
4
4
 
5
5
  /**
@@ -95,10 +95,16 @@ declare class AdminBadge {
95
95
  *
96
96
  * `(click)` is not forwarded — it bubbles from the inner button to the host.
97
97
  *
98
+ * `loading` replaces the projected content with `kf-spinner` and forces
99
+ * `disabled` — for the common "Ingresando…"/"Enviando…" submit pattern,
100
+ * which before this prop existed as plain text swaps with no visual
101
+ * indicator (see `web-portal-latam-frontend`'s login submit buttons).
102
+ *
98
103
  * <kf-button (click)="save()">Save</kf-button>
99
104
  * <kf-button variant="ghost" disabled>Cancel</kf-button>
100
105
  * <kf-button variant="danger" (click)="remove()">Delete</kf-button>
101
106
  * <kf-button size="sm" icon="calendar" block>Reservar hora</kf-button>
107
+ * <kf-button type="submit" [loading]="submitting()">Ingresar</kf-button>
102
108
  */
103
109
  declare class Button {
104
110
  readonly variant: _angular_core.InputSignal<"primary" | "secondary" | "ghost" | "outline" | "danger">;
@@ -110,8 +116,12 @@ declare class Button {
110
116
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
111
117
  /** Stretches the button to the full width of its container. */
112
118
  readonly block: _angular_core.InputSignalWithTransform<boolean, unknown>;
119
+ /** Replaces the projected content with `kf-spinner` and forces `disabled`. */
120
+ readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
121
+ /** `ghost`/`outline` have no solid background — the default spinner ring already reads fine there. */
122
+ protected readonly hasSolidFill: _angular_core.Signal<boolean>;
113
123
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Button, never>;
114
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Button, "kf-button", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "block": { "alias": "block"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
124
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Button, "kf-button", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "block": { "alias": "block"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
115
125
  }
116
126
 
117
127
  type AlertTone = 'info' | 'ok' | 'warn' | 'error';
@@ -591,10 +601,16 @@ declare class DataTable<T = unknown> {
591
601
 
592
602
  type DividerOrientation = 'horizontal' | 'vertical';
593
603
  /**
594
- * Línea separadora decorativa entre bloques de contenido (ej. columnas de
595
- * metadata en `kf-company-selector`). `vertical` necesita que el contenedor
596
- * padre sea flex/grid con altura definida — se estira con `align-self:
597
- * stretch`; sin eso no tiene alto propio y no se ve.
604
+ * Separador visual simple entre bloques de contenido. `orientation`
605
+ * controla si es una línea horizontal (para separar secciones apiladas) o
606
+ * vertical (para separar elementos en una fila, ej. acciones en una toolbar,
607
+ * o las columnas de metadata en `kf-company-selector`).
608
+ *
609
+ * En `vertical`, el divisor necesita una altura del contexto donde vive —
610
+ * `:host { align-self: stretch }` lo hace ocupar la altura del contenedor
611
+ * flex/grid padre sin que el consumidor tenga que fijarla a mano. `flex:
612
+ * none` evita que el propio divisor se achique a 0 dentro de ese contenedor
613
+ * flex cuando hay contenido hermano compitiendo por espacio.
598
614
  *
599
615
  * <kf-divider />
600
616
  * <kf-divider orientation="vertical" />
@@ -605,6 +621,110 @@ declare class Divider {
605
621
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Divider, "kf-divider", never, { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
606
622
  }
607
623
 
624
+ type DropdownItemTone = 'neutral' | 'error';
625
+ /**
626
+ * Ítem de acción dentro de un `kf-dropdown-panel`. Es un `<button>` nativo
627
+ * por dentro (no un `<a>`): las acciones de un menú de este tipo disparan
628
+ * comportamiento en la pantalla actual (cerrar sesión, ejecutar un
629
+ * comando), no navegan a otra URL.
630
+ *
631
+ * <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
632
+ */
633
+ declare class DropdownItem {
634
+ readonly tone: _angular_core.InputSignal<DropdownItemTone>;
635
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownItem, never>;
636
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownItem, "kf-dropdown-item", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
637
+ }
638
+ /**
639
+ * Contenido del panel flotante de un `kf-dropdown`. Es solo una plantilla
640
+ * (`<ng-template>` por dentro, capturada vía `TemplateRef`) — `Dropdown` la
641
+ * conecta al overlay real cuando `open` pasa a `true`, no la renderiza en su
642
+ * propio lugar del DOM. Esto es lo que permite que el panel aparezca pegado
643
+ * al host de `Dropdown` sin importar dónde esté ese host en la página.
644
+ *
645
+ * <kf-dropdown [(open)]="menuOpen">
646
+ * <kf-dropdown-panel>
647
+ * <kf-dropdown-item (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
648
+ * </kf-dropdown-panel>
649
+ * </kf-dropdown>
650
+ */
651
+ declare class DropdownPanel {
652
+ readonly templateRef: _angular_core.Signal<TemplateRef<any>>;
653
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownPanel, never>;
654
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownPanel, "kf-dropdown-panel", never, {}, {}, never, ["*"], true, never>;
655
+ }
656
+ /**
657
+ * Menú de acciones flotante, conectado a un elemento disparador vía
658
+ * `@angular/cdk/overlay` — la misma pieza que usa Angular Material para
659
+ * `mat-menu`/`mat-select`. Resuelve el patrón que antes se reimplementaba a
660
+ * mano en cada consumidor (ver el session-menu de `web-portal-latam-frontend`,
661
+ * ~80 líneas de HTML/TS/SCSS para toggle + click-afuera + Escape + animación
662
+ * de entrada, acopladas a una clase interna no pública de `PortalHeader`).
663
+ *
664
+ * Se apoya en el CDK en vez de un `position: absolute` casero (como sí hace
665
+ * `SelectMenu`, que puede permitírselo porque su trigger vive siempre dentro
666
+ * del propio componente) porque acá el disparador real puede vivir en OTRO
667
+ * componente por completo — ej. el avatar dentro de `kf-portal-header` — y
668
+ * el panel tiene que aparecer pegado a ESE elemento sin importar dónde el
669
+ * consumidor ubique al `Dropdown` en su HTML. El Overlay CDK calcula esa
670
+ * posición (con fallback si no entra en pantalla) en vez de que cada
671
+ * consumidor lo recalcule a mano.
672
+ *
673
+ * `open` es two-way, igual que `Alert`/`Modal`: el consumidor decide qué
674
+ * dispara la apertura (puede ser un `(click)` en un botón propio, o un
675
+ * evento de otro componente como `PortalHeader.sessionClick`) — `Dropdown`
676
+ * en sí no renderiza ningún trigger, su propio host es el punto de anclaje
677
+ * visual del panel.
678
+ *
679
+ * <kf-dropdown [(open)]="menuOpen">
680
+ * <kf-dropdown-panel>
681
+ * <div class="mi-header">Sesión iniciada como {{ email() }}</div>
682
+ * <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
683
+ * </kf-dropdown-panel>
684
+ * </kf-dropdown>
685
+ */
686
+ declare class Dropdown {
687
+ /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
688
+ readonly open: _angular_core.ModelSignal<boolean>;
689
+ /**
690
+ * Elemento al que se conecta el overlay, si es distinto del propio host
691
+ * de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es visualmente
692
+ * el disparador (caso normal: el consumidor arma su propio botón como
693
+ * contenido, y `Dropdown` envuelve ese contenido). Cuando el disparador
694
+ * real vive dentro de OTRO componente que no se puede envolver sin
695
+ * ensanchar el área de click a algo más grande que el botón (ej. el
696
+ * avatar de sesión dentro de `PortalHeader`, que convive con logo y
697
+ * navegación en el mismo host), `anchor` permite apuntar el overlay a la
698
+ * referencia real de ese componente en vez de al host vacío de
699
+ * `Dropdown`.
700
+ *
701
+ * <kf-dropdown [(open)]="menuOpen" [anchor]="headerRef()">
702
+ */
703
+ readonly anchor: _angular_core.InputSignal<ElementRef<HTMLElement> | undefined>;
704
+ private readonly panel;
705
+ private readonly overlayService;
706
+ private readonly host;
707
+ private readonly viewContainerRef;
708
+ private readonly destroyRef;
709
+ private overlayRef;
710
+ constructor();
711
+ /**
712
+ * Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo
713
+ * ve teclas presionadas con el foco DENTRO del pane del overlay. Si el
714
+ * usuario abre el menú y presiona Escape con el foco en otro lado de la
715
+ * página (caso común — el foco puede seguir en el botón disparador, que
716
+ * vive fuera del overlay), `keydownEvents()` nunca dispara. Este listener
717
+ * a nivel de documento cubre ese caso; el de `keydownEvents()` sigue
718
+ * activo para cuando el foco sí está dentro del panel.
719
+ */
720
+ onDocumentEscape(): void;
721
+ private attach;
722
+ private detach;
723
+ private getOrCreateOverlayRef;
724
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Dropdown, never>;
725
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Dropdown, "kf-dropdown", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "anchor": { "alias": "anchor"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, ["panel"], ["*"], true, never>;
726
+ }
727
+
608
728
  /**
609
729
  * Placeholder para una sección sin contenido. Dos tonos, con anatomía
610
730
  * distinta (páginas 27-28 del design system, que los trata como piezas
@@ -761,6 +881,21 @@ interface FolderTabOption {
761
881
  label: string;
762
882
  icon?: IconName;
763
883
  }
884
+ /**
885
+ * Marca el contenido que `kf-folder-tabs` debe proyectar dentro de su
886
+ * panel. Sin lógica propia — existe solo como token de tipo para que
887
+ * `contentChild(FolderTabsPanel)` pueda detectar si el consumidor proyectó
888
+ * algo (mismo mecanismo que `kf-dropdown` usa con `contentChild(DropdownPanel)`;
889
+ * acá no hace falta un componente completo, alcanza con una directiva de
890
+ * atributo porque el panel no necesita comportamiento propio, solo marcar
891
+ * su presencia).
892
+ *
893
+ * <div kf-folder-tabs-panel>...</div>
894
+ */
895
+ declare class FolderTabsPanel {
896
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabsPanel, never>;
897
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FolderTabsPanel, "[kf-folder-tabs-panel]", never, {}, {}, never, never, true, never>;
898
+ }
764
899
  /**
765
900
  * Tabs con forma de "carpeta": el borde superior queda redondeado y el borde
766
901
  * inferior de la pestaña activa se funde visualmente con el panel de
@@ -771,20 +906,61 @@ interface FolderTabOption {
771
906
  * `--active` se mueve de un botón a otro, y eso vuelve a disparar el keyframe
772
907
  * sin tocar JS.
773
908
  *
774
- * El consumidor es responsable de renderizar el panel de contenido debajo;
775
- * este componente solo resuelve la fila de pestañas y emite `valueChange`.
776
- *
909
+ * El panel de contenido es un `<ng-content>` OPCIONAL — si el consumidor no
910
+ * proyecta nada, `kf-folder-tabs` renderiza solo la fila de pestañas (mismo
911
+ * comportamiento que antes de que existiera este slot, detectado con
912
+ * `contentChild()` sin `.required`, igual patrón que usa `kf-dropdown` para
913
+ * saber si hay un `kf-dropdown-panel` proyectado). Si SÍ proyecta contenido,
914
+ * se envuelve en un panel con la esquina superior-izquierda recta (nace ahí
915
+ * la primera pestaña, siempre pegada al borde izquierdo de la fila — un
916
+ * radio ahí competiría visualmente con el propio borde redondeado de esa
917
+ * pestaña) y las otras tres redondeadas. El `border-bottom` de 2px de la
918
+ * pestaña activa (ver `.kf-folder-tabs__tab--active` más abajo) es la señal
919
+ * visual de conexión con el panel para CUALQUIER pestaña activa, sin
920
+ * necesitar mover ni medir nada.
921
+ *
922
+ * El borde izquierdo de toda la fila (y por lo tanto de la esquina recta
923
+ * del panel) depende de que ninguna pestaña se corra al cambiar de estado
924
+ * — ver `transform-origin: bottom left` en `.kf-folder-tabs__tab`: con el
925
+ * origen centrado (como estaba antes), el `scale()` de la pestaña inactiva
926
+ * encogía el borde izquierdo unos px, y ese corrimiento sub-pixel es lo que
927
+ * hacía que este mismo diseño (esquina fija) se viera "roto" cuando la
928
+ * pestaña activa no era la primera. Con el origen anclado a la izquierda,
929
+ * el corrimiento desaparece y la esquina fija vuelve a ser una solución
930
+ * válida y simple — sin necesitar medir la pestaña activa por JS.
931
+ *
932
+ * Antes de este slot, el patrón era que el consumidor pusiera su propia
933
+ * card como HERMANO de `kf-folder-tabs` y anulara a mano el radio superior
934
+ * completo (`border-top-left-radius: 0`, `border-top-right-radius: 0` — ver
935
+ * el caso original de `web-portal-latam-frontend`). Encapsular esto acá
936
+ * evita que cada consumidor repita esas reglas a mano.
937
+ *
938
+ * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event">
939
+ * <div kf-folder-tabs-panel>contenido de la pestaña activa</div>
940
+ * </kf-folder-tabs>
941
+ *
942
+ * <!-- o, sin contenido, como antes: -->
777
943
  * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event" />
778
944
  */
779
945
  declare class FolderTabs {
780
946
  readonly options: _angular_core.InputSignal<FolderTabOption[]>;
781
947
  readonly value: _angular_core.InputSignal<string>;
782
948
  readonly valueChange: _angular_core.OutputEmitterRef<string>;
949
+ /**
950
+ * Detecta si el consumidor proyectó contenido marcado con
951
+ * `kf-folder-tabs-panel` — sin `.required`, así que es `undefined`
952
+ * cuando no hay nada proyectado (mismo patrón que usa `kf-dropdown` con
953
+ * `contentChild(DropdownPanel)` para saber si hay un panel que
954
+ * attachear). `hasPanel()` decide si el wrapper del panel participa del
955
+ * layout (`display: contents` si no).
956
+ */
957
+ private readonly projectedPanel;
958
+ protected readonly hasPanel: _angular_core.Signal<boolean>;
783
959
  select(value: string): void;
784
960
  /** Navegación estándar de ARIA tabs: flechas mueven el foco y activan la pestaña. */
785
961
  onKeydown(event: KeyboardEvent, currentValue: string): void;
786
962
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabs, never>;
787
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FolderTabs, "kf-folder-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
963
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FolderTabs, "kf-folder-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, ["projectedPanel"], ["[kf-folder-tabs-panel]"], true, never>;
788
964
  }
789
965
 
790
966
  /**
@@ -877,6 +1053,36 @@ declare class Input implements ControlValueAccessor, AfterViewInit {
877
1053
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Input, "kf-input", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "suffix": { "alias": "suffix"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "toggleable": { "alias": "toggleable"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
878
1054
  }
879
1055
 
1056
+ type LinkVariant = 'default' | 'muted';
1057
+ /**
1058
+ * Acción de bajo énfasis con apariencia de texto — "¿Olvidaste tu
1059
+ * contraseña?", "Volver a iniciar sesión". Antes de este componente,
1060
+ * `web-portal-latam-frontend` reimplementaba esto dos veces (`.login__forgot`
1061
+ * como `<a href="#">` con `preventDefault()`, `.login__back` como
1062
+ * `<button>`), cada una con su propio `:focus-visible` copiado literal.
1063
+ *
1064
+ * Es siempre un `<button type="button">` por dentro, nunca un `<a>`: ninguno
1065
+ * de los dos casos reales navega a otra URL, ambos disparan una acción en la
1066
+ * misma pantalla (cambiar de vista, someter un flujo). Si en el futuro un
1067
+ * consumidor necesita navegación real con href, esa es una discusión de API
1068
+ * distinta (`kf-link` con `[routerLink]`/`href`), no se fuerza acá sin caso
1069
+ * de uso confirmado.
1070
+ *
1071
+ * `variant="default"` (color primario, subrayado en hover) es para la
1072
+ * acción de mayor jerarquía relativa de la pantalla (ej. "¿Olvidaste tu
1073
+ * contraseña?"); `variant="muted"` (gris que pasa a primario en hover) es
1074
+ * para acciones de retroceso/salida (ej. "← Volver a iniciar sesión").
1075
+ *
1076
+ * <kf-link (click)="onForgotPassword()">¿Olvidaste tu contraseña?</kf-link>
1077
+ * <kf-link variant="muted" (click)="onBack()">← Volver a iniciar sesión</kf-link>
1078
+ */
1079
+ declare class Link {
1080
+ readonly variant: _angular_core.InputSignal<LinkVariant>;
1081
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1082
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Link, never>;
1083
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Link, "kf-link", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1084
+ }
1085
+
880
1086
  /**
881
1087
  * Card de estadística/KPI: label, valor grande, ícono opcional en la esquina
882
1088
  * superior derecha, badge de esquina opcional (pegado al valor) y una nota
@@ -1108,12 +1314,29 @@ interface PortalHeaderAdminAction {
1108
1314
  * Es distinto de `kf-page-header`/`kf-page-head` (título de una vista
1109
1315
  * interna) — este es el chrome global del portal, se monta una sola vez.
1110
1316
  *
1317
+ * `:host` es `position: sticky; top: 0` — el header queda pegado arriba al
1318
+ * hacer scroll. No es `fixed`: como no se superpone al contenido en el
1319
+ * primer render, no hace falta que el consumidor compense con
1320
+ * `padding-top`.
1321
+ *
1322
+ * Bajo los 768px, el `<nav>` deja de mostrarse en línea junto al logo y
1323
+ * colapsa detrás de un botón de menú (ícono `menu`/`x`) que despliega un
1324
+ * drawer lateral (desde el borde derecho, `position: fixed` + overlay de
1325
+ * fondo) con los mismos `navItems` en columna. Ese drawer expone un slot
1326
+ * (`<ng-content select="[kf-portal-header-extra]">`) al final, pensado
1327
+ * para que el consumidor proyecte contenido propio que en escritorio vive
1328
+ * en otro lugar (ej. el menú de sesión) — `PortalHeader` no sabe qué es
1329
+ * ese contenido ni cómo se abre, solo le da un lugar dentro del drawer ya
1330
+ * desplegado. El overlay cierra el drawer al tocarlo, y `Escape` también.
1331
+ *
1111
1332
  * <kf-portal-header
1112
1333
  * [isAdmin]="true"
1113
1334
  * [adminActions]="[{ label: 'Consultar deuda clientes', href: '/deuda' }, { label: 'Cambiar DNI', href: '/dni' }]"
1114
1335
  * [navItems]="[{ label: 'Home', href: '/', active: true }, { label: 'Vehículos', href: '/vehiculos' }]"
1115
1336
  * userInitials="TS"
1116
- * />
1337
+ * >
1338
+ * <div kf-portal-header-extra>contenido propio, solo visible en el panel mobile</div>
1339
+ * </kf-portal-header>
1117
1340
  */
1118
1341
  declare class PortalHeader {
1119
1342
  readonly brand: _angular_core.InputSignal<string>;
@@ -1122,8 +1345,23 @@ declare class PortalHeader {
1122
1345
  readonly navItems: _angular_core.InputSignal<PortalHeaderNavItem[]>;
1123
1346
  readonly userInitials: _angular_core.InputSignal<string>;
1124
1347
  readonly sessionClick: _angular_core.OutputEmitterRef<void>;
1348
+ /** Visibilidad del drawer de navegación mobile (bajo los 768px). */
1349
+ protected readonly mobileMenuOpen: _angular_core.WritableSignal<boolean>;
1350
+ /** Cierra el drawer con Escape — mismo patrón que cualquier drawer/modal. */
1351
+ protected onEscape(): void;
1352
+ /**
1353
+ * `ElementRef` real del botón de sesión (el avatar en la esquina
1354
+ * derecha), no de todo el header. Pensado para anclar un `kf-dropdown`
1355
+ * externo — sin esto, el único `ElementRef` disponible desde afuera es el
1356
+ * del host de `PortalHeader`, que ocupa el ancho completo de la pantalla
1357
+ * (logo + nav + sesión), y el overlay del CDK termina "pegado" a la
1358
+ * esquina del header entero en vez de al botón que el usuario clickeó.
1359
+ *
1360
+ * <kf-dropdown [(open)]="menuOpen" [anchor]="header().sessionButtonRef()">
1361
+ */
1362
+ readonly sessionButtonRef: _angular_core.Signal<ElementRef<any>>;
1125
1363
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PortalHeader, never>;
1126
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PortalHeader, "kf-portal-header", never, { "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "isAdmin": { "alias": "isAdmin"; "required": false; "isSignal": true; }; "adminActions": { "alias": "adminActions"; "required": false; "isSignal": true; }; "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "userInitials": { "alias": "userInitials"; "required": true; "isSignal": true; }; }, { "sessionClick": "sessionClick"; }, never, never, true, never>;
1364
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PortalHeader, "kf-portal-header", never, { "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "isAdmin": { "alias": "isAdmin"; "required": false; "isSignal": true; }; "adminActions": { "alias": "adminActions"; "required": false; "isSignal": true; }; "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "userInitials": { "alias": "userInitials"; "required": true; "isSignal": true; }; }, { "sessionClick": "sessionClick"; }, never, ["[kf-portal-header-extra]"], true, never>;
1127
1365
  }
1128
1366
 
1129
1367
  /**
@@ -1161,10 +1399,17 @@ declare class ReserveBlock {
1161
1399
  * `<ng-content select="[actions]">` para no acoplar el componente a un set
1162
1400
  * fijo de botones.
1163
1401
  *
1164
- * `size="sm"` reduce tipografía y espaciado para usarse dentro de cards ya
1165
- * compactas. `solid` rellena el chip de ícono con el color primario sólido
1166
- * en vez del tratamiento "soft" (fondo tenue) — pensado para superficies que
1167
- * ya tienen un tinte de color propio, donde el chip "soft" perdería contraste.
1402
+ * `size` sigue la misma escala `'sm'|'md'|'lg'` que `Button`/`Icon`/`Spinner`
1403
+ * (`md` es el default): `sm` reduce tipografía y espaciado para usarse
1404
+ * dentro de cards ya compactas, `lg` los agranda para encabezar un bloque
1405
+ * más prominente. Antes de la `0.2.0`, `size` solo aceptaba `'sm'` —
1406
+ * placeholder incompleto de esta misma escala; los valores de `lg` son una
1407
+ * extrapolación proporcional de la escala existente (sin un caso de uso
1408
+ * real todavía que los valide, a diferencia de `sm`, que sí viene de un
1409
+ * patrón real de uso dentro de cards). `solid` rellena el chip de ícono con
1410
+ * el color primario sólido en vez del tratamiento "soft" (fondo tenue) —
1411
+ * pensado para superficies que ya tienen un tinte de color propio, donde el
1412
+ * chip "soft" perdería contraste.
1168
1413
  *
1169
1414
  * <kf-section-header icon="settings" title="Preferencias"
1170
1415
  * subtitle="Configuración general de la cuenta">
@@ -1172,13 +1417,15 @@ declare class ReserveBlock {
1172
1417
  * </kf-section-header>
1173
1418
  *
1174
1419
  * <kf-section-header title="Detalle" size="sm" [solid]="true" />
1420
+ * <kf-section-header title="Resumen de flota" size="lg" />
1175
1421
  */
1176
1422
  declare class SectionHeader {
1177
1423
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
1178
1424
  readonly title: _angular_core.InputSignal<string>;
1179
1425
  readonly subtitle: _angular_core.InputSignal<string | undefined>;
1180
- readonly size: _angular_core.InputSignal<"sm" | undefined>;
1426
+ readonly size: _angular_core.InputSignal<"sm" | "md" | "lg">;
1181
1427
  readonly solid: _angular_core.InputSignalWithTransform<boolean, unknown>;
1428
+ protected readonly iconSize: _angular_core.Signal<"sm" | "md" | "lg">;
1182
1429
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SectionHeader, never>;
1183
1430
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SectionHeader, "kf-section-header", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "solid": { "alias": "solid"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1184
1431
  }
@@ -1217,18 +1464,25 @@ interface SelectOption {
1217
1464
  * Dropdown de selección simple con panel propio — NUNCA usa el `<select>`
1218
1465
  * nativo del navegador. Un `<select>` real siempre delega el popup de
1219
1466
  * opciones al sistema operativo, y ningún CSS puede controlar cómo se ve
1220
- * ese popup (ver el mismo problema ya resuelto en `SelectMenu`, del que
1221
- * este componente reutiliza el patrón completo: trigger + panel flotante
1222
- * propio, cerrado con click-afuera y Escape).
1467
+ * ese popup.
1223
1468
  *
1224
- * Pensado para sets simples y cortos. Para listas más ricas o con más
1225
- * opciones, es el mismo componente que `SelectMenu` — la diferencia es
1226
- * solo de estilo (label uppercase, fondo gris que pasa a blanco al foco,
1227
- * a tono con `Input`) y que acepta `options` como `string[]` además de
1228
- * `{ value, label }[]`.
1469
+ * Hasta la `0.1.5` existía además `kf-select-menu`, un segundo componente
1470
+ * prácticamente idéntico (mismo trigger, mismo panel, mismo CSS) que solo
1471
+ * se diferenciaba en aceptar `options` únicamente como `{ value, label }[]`
1472
+ * (sin el atajo `string[]` que `Select` ya tenía). Se fusionaron en este
1473
+ * componente en la `0.2.0` — mantener dos componentes con la misma
1474
+ * interacción y el mismo aspecto visual, cuya única diferencia real era la
1475
+ * forma de un input, no se sostenía como dos piezas separadas del catálogo.
1476
+ *
1477
+ * El panel flotante se conecta al trigger vía `@angular/cdk/overlay` — el
1478
+ * mismo mecanismo que `Dropdown` (ver `../dropdown/Dropdown.ts`).
1229
1479
  *
1230
1480
  * <kf-select label="Sede" [options]="['Lima', 'Arequipa', 'Trujillo']" placeholder="Selecciona…" />
1231
1481
  * <kf-select [options]="[{ value: 'pe', label: 'Perú' }]" (valueChange)="onCountry($event)" />
1482
+ *
1483
+ * Pendiente: el prop `icon` de otros componentes del design system requiere
1484
+ * un componente Icon / sprite SVG que esta librería todavía no tiene — este
1485
+ * componente no usa iconos, así que no aplica acá.
1232
1486
  */
1233
1487
  declare class Select {
1234
1488
  readonly label: _angular_core.InputSignal<string | undefined>;
@@ -1244,60 +1498,29 @@ declare class Select {
1244
1498
  /** Resolves the union (`string[] | { value; label }[]`) into a single shape for the template. */
1245
1499
  readonly normalizedOptions: _angular_core.Signal<SelectOption[]>;
1246
1500
  readonly selectedOption: _angular_core.Signal<SelectOption | undefined>;
1247
- private readonly host;
1501
+ private readonly triggerRef;
1502
+ private readonly panelTemplate;
1503
+ private readonly overlayService;
1504
+ private readonly viewContainerRef;
1505
+ private readonly destroyRef;
1506
+ private overlayRef;
1507
+ constructor();
1508
+ /**
1509
+ * Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo ve
1510
+ * teclas presionadas con el foco DENTRO del pane del overlay. Si el foco
1511
+ * sigue en el trigger (o en cualquier otro lado de la página) mientras el
1512
+ * panel está abierto, `keydownEvents()` nunca dispara.
1513
+ */
1514
+ onDocumentEscape(): void;
1248
1515
  toggle(): void;
1249
1516
  select(option: SelectOption): void;
1250
- onDocumentClick(event: MouseEvent): void;
1251
- onEscape(): void;
1517
+ private attach;
1518
+ private detach;
1519
+ private getOrCreateOverlayRef;
1252
1520
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Select, never>;
1253
1521
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Select, "kf-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1254
1522
  }
1255
1523
 
1256
- interface SelectMenuOption {
1257
- value: string;
1258
- label: string;
1259
- }
1260
- /**
1261
- * Dropdown de selección simple pero con panel propio (no usa `<select>` nativo),
1262
- * para cuando se necesita más control visual que un `<select>` estándar permite.
1263
- *
1264
- * El panel flotante se implementa con `position: absolute` sobre el host
1265
- * (sin Angular CDK Overlay, que no es dependencia del proyecto). Se abre y
1266
- * cierra por click en el disparador, y se cierra al hacer click afuera o al
1267
- * presionar Escape.
1268
- *
1269
- * <kf-select-menu
1270
- * label="País"
1271
- * [options]="countries"
1272
- * [value]="selectedCountry()"
1273
- * (valueChange)="selectedCountry.set($event)"
1274
- * />
1275
- *
1276
- * Pendiente: el prop `icon` de otros componentes del design system requiere
1277
- * un componente Icon / sprite SVG que esta librería todavía no tiene — este
1278
- * componente no usa iconos, así que no aplica acá.
1279
- */
1280
- declare class SelectMenu {
1281
- readonly label: _angular_core.InputSignal<string | undefined>;
1282
- readonly options: _angular_core.InputSignal<SelectMenuOption[]>;
1283
- readonly value: _angular_core.InputSignal<string | undefined>;
1284
- readonly placeholder: _angular_core.InputSignal<string>;
1285
- readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1286
- readonly valueChange: _angular_core.OutputEmitterRef<string>;
1287
- private readonly instanceId;
1288
- readonly triggerId: string;
1289
- readonly listboxId: string;
1290
- readonly isOpen: _angular_core.WritableSignal<boolean>;
1291
- readonly selectedOption: _angular_core.Signal<SelectMenuOption | undefined>;
1292
- private readonly host;
1293
- toggle(): void;
1294
- select(option: SelectMenuOption): void;
1295
- onDocumentClick(event: MouseEvent): void;
1296
- onEscape(): void;
1297
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<SelectMenu, never>;
1298
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SelectMenu, "kf-select-menu", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1299
- }
1300
-
1301
1524
  /** Forma del placeholder. */
1302
1525
  type SkeletonVariant = 'text' | 'circle' | 'rect';
1303
1526
  /**
@@ -1426,6 +1649,44 @@ declare class Tabs {
1426
1649
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Tabs, "kf-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1427
1650
  }
1428
1651
 
1652
+ type TextAs = 'h1' | 'h2' | 'h3' | 'p' | 'span';
1653
+ type TextVariant = 'title-lg' | 'title-md' | 'title-sm' | 'body' | 'body-muted' | 'caption';
1654
+ type TextTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
1655
+ /**
1656
+ * Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
1657
+ * texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
1658
+ * ve). Antes de este componente, cada pantalla que consume la librería
1659
+ * definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
1660
+ * (título de página, subtítulo, texto secundario) — auditoría sobre
1661
+ * `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
1662
+ * home sin ningún paso de escala en común. `variant` resuelve eso contra
1663
+ * la escala de `tokens/base.css` (`--kf-font-size-*`).
1664
+ *
1665
+ * `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
1666
+ * escape hatch de color libre. Si un caso necesita un color que `tone` no
1667
+ * cubre, es una discusión de design system, no una prop nueva acá.
1668
+ *
1669
+ * <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
1670
+ * <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
1671
+ * <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
1672
+ */
1673
+ declare class Text {
1674
+ /** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
1675
+ readonly as: _angular_core.InputSignal<TextAs>;
1676
+ readonly variant: _angular_core.InputSignal<TextVariant>;
1677
+ readonly tone: _angular_core.InputSignal<TextTone>;
1678
+ /**
1679
+ * Copia de `as` para uso dentro del template: `as` es palabra reservada
1680
+ * en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
1681
+ * no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
1682
+ * atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
1683
+ */
1684
+ protected readonly tag: _angular_core.Signal<TextAs>;
1685
+ protected hostClass(): string;
1686
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Text, never>;
1687
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Text, "kf-text", never, { "as": { "alias": "as"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1688
+ }
1689
+
1429
1690
  /**
1430
1691
  * Visual tone of a toast. Maps to the state tokens in
1431
1692
  * projects/ui/src/tokens/base.css. `loading` has no auto-dismiss and shows an
@@ -1581,5 +1842,5 @@ declare class ViewToggle {
1581
1842
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ViewToggle, "kf-view-toggle", never, { "view": { "alias": "view"; "required": true; "isSignal": true; }; }, { "viewChange": "viewChange"; }, never, never, true, never>;
1582
1843
  }
1583
1844
 
1584
- export { ActionTile, AdminBadge, Alert, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, Icon, Input, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, SelectMenu, Skeleton, Spinner, StatTile, StatusPill, Tabs, ToastHost, ToastService, VehicleHeader, ViewToggle };
1585
- export type { AlertTone, BreadcrumbItem, CompanySelectorMeta, ContentStateKind, ContentStateLoad, DataTableCardRole, DataTableColumn, DataTableView, DividerOrientation, FolderTabOption, IconName, IconSize, MultiSelectOption, PaginationItem, PortalHeaderAdminAction, PortalHeaderNavItem, SegmentedOption, SelectMenuOption, SkeletonVariant, SpinnerSize, TabOption, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, VehicleHeaderStatus, ViewToggleOption };
1845
+ export { ActionTile, AdminBadge, Alert, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, Dropdown, DropdownItem, DropdownPanel, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, FolderTabsPanel, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, VehicleHeader, ViewToggle };
1846
+ export type { AlertTone, BreadcrumbItem, CompanySelectorMeta, ContentStateKind, ContentStateLoad, DataTableCardRole, DataTableColumn, DataTableView, DividerOrientation, DropdownItemTone, FolderTabOption, IconName, IconSize, LinkVariant, MultiSelectOption, PaginationItem, PortalHeaderAdminAction, PortalHeaderNavItem, SegmentedOption, SkeletonVariant, SpinnerSize, TabOption, TextAs, TextTone, TextVariant, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, VehicleHeaderStatus, ViewToggleOption };