@sfperusacdev/sf-ui 0.2.4 → 0.2.6

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.
Files changed (42) hide show
  1. package/dist/sf-ui.css +1 -1
  2. package/dist/sf-ui.js +2934 -1874
  3. package/dist/sf-ui.js.map +1 -1
  4. package/dist/sf-ui.umd.cjs +8 -8
  5. package/dist/sf-ui.umd.cjs.map +1 -1
  6. package/dist/theme/dark.css +1 -0
  7. package/dist/theme/sf-tokens.css +4 -0
  8. package/dist/types/src/adapters/maps/google/google.test.d.ts +1 -0
  9. package/dist/types/src/adapters/maps/google/index.d.ts +24 -1
  10. package/dist/types/src/components/dashboard/CardNote.d.ts +13 -0
  11. package/dist/types/src/components/dashboard/DashboardBoard.d.ts +37 -0
  12. package/dist/types/src/components/dashboard/DashboardBoard.test.d.ts +1 -0
  13. package/dist/types/src/components/dashboard/DashboardCard.d.ts +10 -2
  14. package/dist/types/src/components/dashboard/DashboardPageFrame.d.ts +43 -0
  15. package/dist/types/src/components/dashboard/DashboardPageFrame.test.d.ts +1 -0
  16. package/dist/types/src/components/dashboard/DashboardPlaceholder.d.ts +18 -0
  17. package/dist/types/src/components/dashboard/DetailPanel.d.ts +38 -0
  18. package/dist/types/src/components/dashboard/DetailPanel.test.d.ts +1 -0
  19. package/dist/types/src/components/dashboard/DrilldownCards.d.ts +68 -0
  20. package/dist/types/src/components/dashboard/detailTypes.d.ts +31 -0
  21. package/dist/types/src/components/dashboard/index.d.ts +12 -0
  22. package/dist/types/src/components/inputs/AsyncSearchSelect.d.ts +4 -1
  23. package/dist/types/src/components/inputs/FieldLeadingIcon.d.ts +6 -0
  24. package/dist/types/src/components/inputs/Input.d.ts +8 -1
  25. package/dist/types/src/components/inputs/SearchSelect.d.ts +4 -1
  26. package/dist/types/src/components/inputs/fieldClassName.d.ts +3 -1
  27. package/dist/types/src/components/maps/base/MapPointCard.d.ts +22 -0
  28. package/dist/types/src/components/maps/base/MapPointCard.test.d.ts +1 -0
  29. package/dist/types/src/components/maps/base/index.d.ts +2 -0
  30. package/dist/types/src/components/maps/forms/AddressSearchInput.test.d.ts +1 -0
  31. package/dist/types/src/components/maps/renderers/geometria.d.ts +5 -0
  32. package/dist/types/src/components/maps/renderers/google/GoogleMapRenderer.d.ts +1 -1
  33. package/dist/types/src/components/maps/renderers/leaflet/LeafletMapRenderer.d.ts +1 -1
  34. package/dist/types/src/components/maps/views/MapView.d.ts +20 -3
  35. package/dist/types/src/components/maps/views/MapView.test.d.ts +1 -0
  36. package/dist/types/src/constants/maps/index.d.ts +3 -0
  37. package/dist/types/src/contracts/maps/index.d.ts +2 -0
  38. package/dist/types/src/hooks/maps/index.d.ts +1 -0
  39. package/dist/types/src/hooks/maps/useReverseGeocode.d.ts +14 -0
  40. package/dist/types/src/services/maps/googleMapsLoader.d.ts +20 -0
  41. package/dist/types/src/types/maps/index.d.ts +61 -0
  42. package/package.json +1 -1
@@ -77,6 +77,7 @@
77
77
  --sf-modal-close-hover-text: #f1f5f9;
78
78
 
79
79
  --sf-card-bg: #1e293b;
80
+ --sf-section-panel-bg: #334155;
80
81
  --sf-card-border: #334155;
81
82
  --sf-card-title: #f1f5f9;
82
83
  --sf-card-subtitle: #94a3b8;
@@ -111,6 +111,7 @@
111
111
 
112
112
  /* Cards / layout */
113
113
  --sf-card-bg: #ffffff;
114
+ --sf-section-panel-bg: #e5e7eb;
114
115
  --sf-card-border: #e5e7eb;
115
116
  --sf-card-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
116
117
  --sf-card-shadow-hover: 0 4px 6px rgb(0 0 0 / 0.07);
@@ -480,6 +481,9 @@
480
481
  --sf-map-area-fill: var(--sf-info);
481
482
  --sf-map-route-stroke: #0f766e;
482
483
  --sf-map-route-fill: #14b8a6;
484
+ --sf-map-circle-stroke: var(--sf-success);
485
+ --sf-map-circle-fill: var(--sf-success);
486
+ --sf-map-pin-bg: var(--sf-success);
483
487
  --sf-map-panel-bg: var(--sf-network-panel-bg);
484
488
  --sf-map-vertex-border: var(--sf-white);
485
489
  --sf-map-vertex-shadow: var(--sf-network-marker-shadow);
@@ -1,13 +1,22 @@
1
- import { SfLatLng, SfPlace } from '../../../types/maps';
1
+ import { SfAddressParts, SfLatLng, SfPlace } from '../../../types/maps';
2
2
  export type GoogleMapsLikeLatLng = {
3
3
  lat: () => number;
4
4
  lng: () => number;
5
5
  };
6
+ export type GoogleMapsLikeAddressComponent = {
7
+ long_name?: string;
8
+ short_name?: string;
9
+ types?: string[];
10
+ };
6
11
  export type GoogleMapsLikeGeocoderResult = {
7
12
  place_id?: string;
8
13
  formatted_address?: string;
14
+ types?: string[];
15
+ address_components?: GoogleMapsLikeAddressComponent[];
9
16
  geometry?: {
10
17
  location?: GoogleMapsLikeLatLng;
18
+ /** ROOFTOP | RANGE_INTERPOLATED | GEOMETRIC_CENTER | APPROXIMATE */
19
+ location_type?: string;
11
20
  };
12
21
  };
13
22
  export declare const googleLatLngToSfLatLng: (point: GoogleMapsLikeLatLng) => SfLatLng;
@@ -15,4 +24,18 @@ export declare const sfLatLngToGoogleLiteral: (point: SfLatLng) => {
15
24
  lat: number;
16
25
  lng: number;
17
26
  };
27
+ /**
28
+ * Partes de la dirección a partir de uno o varios resultados de Google (los de una geocodificación inversa vienen
29
+ * del más al menos preciso). Vía y número salen del mismo resultado; el resto, del primero que lo traiga.
30
+ * En Perú Google da el distrito como `locality`, la provincia como nivel 2 y el departamento como nivel 1
31
+ * (en Lima Metropolitana el nivel 1 es "Provincia de Lima").
32
+ */
33
+ export declare const googleAddressParts: (results: GoogleMapsLikeGeocoderResult[], conVia?: GoogleMapsLikeGeocoderResult | undefined) => SfAddressParts;
18
34
  export declare const googleGeocoderResultToSfPlace: (result: GoogleMapsLikeGeocoderResult) => SfPlace | null;
35
+ /**
36
+ * Lo que hay en un punto según la geocodificación inversa de Google. La dirección sale del resultado con vía
37
+ * **más cercano** al punto: Google devuelve la dirección con número más cercana que conoce, que puede estar a una
38
+ * cuadra (calles sin numeración), y no siempre la pone primero. `parts.lineDistanceM` dice a cuántos metros está,
39
+ * para que quien la muestre la presente como referencia si no es exacta. La ubicación es el punto pedido.
40
+ */
41
+ export declare const googleReverseResultsToSfPlace: (point: SfLatLng, results: GoogleMapsLikeGeocoderResult[]) => SfPlace | null;
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ export type CardNoteTone = "info" | "warning";
3
+ export type CardNoteProps = {
4
+ /** Cómo leer el dato de la tarjeta, en una frase. */
5
+ text: ReactNode;
6
+ tone?: CardNoteTone;
7
+ className?: string;
8
+ };
9
+ /**
10
+ * Nota al pie de una tarjeta o de un panel: explica cómo leer el dato con el mismo tintado que el pie de
11
+ * `MetricBarList`. Para procedencia de datos (período, fuentes, confianza) está `EvidenceFootnote`.
12
+ */
13
+ export declare const CardNote: ({ text, tone, className }: CardNoteProps) => import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { ReactNode } from 'react';
2
+ /** Una fila del tablero: bloque ancho a la izquierda y, si viene, uno angosto a la derecha (2/3 · 1/3). */
3
+ export type DashboardBoardBlock = {
4
+ left?: ReactNode;
5
+ right?: ReactNode;
6
+ };
7
+ /** Pieza que no aplica a esta empresa o a este contexto, con el motivo que se le explica al usuario. */
8
+ export type DashboardBoardHiddenPiece = {
9
+ title: string;
10
+ reason: string;
11
+ };
12
+ export type DashboardBoardProps = {
13
+ /** Indicadores de la fila superior; los `null`/`false` se descartan y la grilla se ajusta a 3, 4 o 5 columnas. */
14
+ kpis?: ReactNode[];
15
+ /** Avisos informativos que van arriba de todo (uno por `Alert`). */
16
+ notes?: string[];
17
+ /** Piezas ocultas: se listan en un solo aviso de advertencia. */
18
+ hidden?: DashboardBoardHiddenPiece[];
19
+ hiddenTitle?: (count: number) => string;
20
+ /** Panel de detalle abierto (p. ej. `DetailPanel`); se pinta entre los indicadores y el bloque principal. */
21
+ detail?: ReactNode;
22
+ primary?: DashboardBoardBlock;
23
+ secondary?: DashboardBoardBlock;
24
+ };
25
+ /**
26
+ * Disposición de un tablero: avisos, fila de indicadores, panel de detalle y hasta dos filas de bloques
27
+ * (principal y secundario) con la proporción 2/3 · 1/3 cuando la fila tiene dos bloques. Va dentro de
28
+ * `DashboardLayout` o de `DashboardPageFrame`, que ponen el espacio vertical entre piezas.
29
+ */
30
+ export declare const DashboardBoard: ({ kpis, notes, hidden, hiddenTitle, detail, primary, secondary, }: DashboardBoardProps) => import("react").JSX.Element;
31
+ export type DashboardCardRowProps = {
32
+ /** Tarjetas de la fila; los `null`/`false` se descartan. Con tres o más van a 3 columnas, si no a 2. */
33
+ children: ReactNode[];
34
+ className?: string;
35
+ };
36
+ /** Tarjetas del mismo alto dentro de una sección (p. ej. Sexo · Edad · Antigüedad). */
37
+ export declare const DashboardCardRow: ({ children, className }: DashboardCardRowProps) => import("react").JSX.Element;
@@ -15,6 +15,11 @@ export type DashboardCardProps = {
15
15
  * una sola fila, en vez de recortar con `truncate` (acabado actual, `false` por defecto). Útil con
16
16
  * títulos largos + `headerAction` ancho (ej. un selector de rango de fechas). */
17
17
  headerWrap?: boolean;
18
+ /** `false` quita la línea entre la cabecera y el cuerpo (tarjetas de tablero con título grande). */
19
+ headerDivider?: boolean;
20
+ /** "board" = la tarjeta de los tableros: cabecera `md` que pasa a dos líneas y sin divisor. Cada una de esas
21
+ * tres props se puede seguir fijando aparte; `look` solo cambia sus valores por defecto. */
22
+ look?: "default" | "board";
18
23
  /** Contenido opcional bajo el cuerpo, separado por un borde — ej. un resumen o un link "ver todo". */
19
24
  footer?: ReactNode;
20
25
  /** Estado del cuerpo. "idle" (por defecto) = `children` tal cual. Ver `DashboardCardState`. */
@@ -34,7 +39,7 @@ export type DashboardCardProps = {
34
39
  className?: string;
35
40
  children?: ReactNode;
36
41
  };
37
- export declare const DashboardCard: ({ title, subtitle, icon, headerAction, variant, span, padding, headerSize, headerWrap, footer, state, loadingMessage, errorMessage, emptyMessage, onRetry, retryLabel, minHeight, bodyOverflow, className, children, }: DashboardCardProps) => import("react").JSX.Element;
42
+ export declare const DashboardCard: ({ title, subtitle, icon, headerAction, variant, span, padding, look, headerSize, headerWrap, headerDivider, footer, state, loadingMessage, errorMessage, emptyMessage, onRetry, retryLabel, minHeight, bodyOverflow, className, children, }: DashboardCardProps) => import("react").JSX.Element;
38
43
  type DashboardGridProps = {
39
44
  cols?: 2 | 3 | 4 | 5 | 6;
40
45
  gap?: "sm" | "md" | "lg";
@@ -58,10 +63,13 @@ type DashboardSectionProps = {
58
63
  icon?: ReactNode;
59
64
  /** Tamaño del tile de `icon`. "md" (por defecto) = 1.75rem, acabado actual. "lg" = 2.5rem. */
60
65
  iconSize?: "md" | "lg";
66
+ /** "plain" (por defecto) = sin fondo, acabado actual. "panel" la encierra en un recuadro gris redondeado
67
+ * (`--sf-section-panel-bg`) que agrupa visualmente sus tarjetas, como las secciones de los tableros. */
68
+ variant?: "plain" | "panel";
61
69
  className?: string;
62
70
  children?: ReactNode;
63
71
  };
64
- export declare const DashboardSection: ({ title, subtitle, action, icon, iconSize, className, children, }: DashboardSectionProps) => import("react").JSX.Element;
72
+ export declare const DashboardSection: ({ title, subtitle, action, icon, iconSize, variant, className, children, }: DashboardSectionProps) => import("react").JSX.Element;
65
73
  type DashboardStatBarProps = {
66
74
  items: StatBarItem[];
67
75
  className?: string;
@@ -0,0 +1,43 @@
1
+ import { ReactNode } from 'react';
2
+ export type DashboardDateRange = {
3
+ from: string;
4
+ to: string;
5
+ };
6
+ export type DashboardPageFrameProps = {
7
+ title: string;
8
+ /** La pregunta que responde el tablero, debajo del título. */
9
+ subtitle?: string;
10
+ /** Rango aplicado (YYYY-MM-DD). El marco guarda un borrador aparte hasta que el usuario pulsa «Aplicar». */
11
+ range: DashboardDateRange;
12
+ onApply: (range: DashboardDateRange) => void;
13
+ /** Devuelve el mensaje de error si el borrador no es válido; `null` si se puede aplicar. */
14
+ validate?: (range: DashboardDateRange) => string | null;
15
+ /** Error del rango ya aplicado (p. ej. venía inválido en la URL): se muestra y no se pintan los hijos. */
16
+ error?: string | null;
17
+ /** Hay consultas en curso: el botón «Aplicar» gira y el chip de estado cuenta los segundos. */
18
+ loading?: boolean;
19
+ /** Momento (epoch ms) del dato más antiguo en pantalla; alimenta el chip «datos de las HH:MM». */
20
+ updatedAt?: number | null;
21
+ /** El corte es el día de hoy: el chip dice «datos en vivo». */
22
+ live?: boolean;
23
+ /** Texto del corte a la derecha del chip, ya formateado (p. ej. «corte 30 set. 2026»). */
24
+ cutoffLabel?: string;
25
+ onRefresh?: () => void;
26
+ refreshLabel?: string;
27
+ /** Texto del tercer campo del filtro cuando el borrador coincide con lo aplicado. */
28
+ compareLabel?: string;
29
+ /** Texto del tercer campo cuando el borrador cambió y falta pulsar «Aplicar». */
30
+ pendingLabel?: string;
31
+ applyLabel?: string;
32
+ filterAriaLabel?: string;
33
+ /** Segundos de espera antes de mostrar el contador en el chip. */
34
+ slowAfterSeconds?: number;
35
+ className?: string;
36
+ children?: ReactNode;
37
+ };
38
+ /**
39
+ * Marco de una página de tablero: título y pregunta, chip de estado (en vivo / calculando / hora del dato),
40
+ * botón de actualizar y el filtro de rango de fechas con borrador y validación. Los hijos van dentro de
41
+ * `DashboardLayout`, así que `DashboardBoard` encaja directamente.
42
+ */
43
+ export declare const DashboardPageFrame: ({ title, subtitle, range, onApply, validate, error, loading, updatedAt, live, cutoffLabel, onRefresh, refreshLabel, compareLabel, pendingLabel, applyLabel, filterAriaLabel, slowAfterSeconds, className, children, }: DashboardPageFrameProps) => import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ export type DashboardPlaceholderShape = "kpi" | "bars" | "chart" | "list";
2
+ export type DashboardPlaceholderState = "loading" | "error";
3
+ export type DashboardPlaceholderProps = {
4
+ /** Título de la pieza que ocupará este lugar cuando llegue su dato. */
5
+ title: string;
6
+ /** Silueta que se dibuja mientras carga: la misma forma que tendrá la pieza, para que la página no salte. */
7
+ shape?: DashboardPlaceholderShape;
8
+ state?: DashboardPlaceholderState;
9
+ errorMessage?: string;
10
+ onRetry?: () => void;
11
+ className?: string;
12
+ };
13
+ /**
14
+ * Lugar de una pieza de tablero que todavía no tiene dato: en carga dibuja una silueta con la forma de la
15
+ * pieza (la de `shape="kpi"` mide lo mismo que `KPICard size="lg"`); en error muestra el mensaje y el botón de
16
+ * reintento de `DashboardCard`.
17
+ */
18
+ export declare const DashboardPlaceholder: ({ title, shape, state, errorMessage, onRetry, className, }: DashboardPlaceholderProps) => import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ import { ReactNode } from 'react';
2
+ import { DetailController, DetailSpec } from './detailTypes';
3
+ export type { DetailColumn, DetailController, DetailGroup, DetailSpec } from './detailTypes';
4
+ /** Estado del `DetailPanel` de un tablero: una sola cifra abierta a la vez; volver a pulsarla la cierra. */
5
+ export declare const useDetailPanel: <Row>() => DetailController<Row>;
6
+ type FlatRow = Record<string, unknown>;
7
+ export type DetailPanelProps<Row> = {
8
+ detail: DetailController<Row>;
9
+ /** Convierte cada fila al objeto plano que leen las columnas (`column.key`). Por defecto la fila tal cual. */
10
+ rowMapper?: (row: Row) => FlatRow;
11
+ subtitle?: string;
12
+ /** Texto del contador de la cabecera, con el total ya formateado por quien llama. */
13
+ countLabel?: (total: number) => string;
14
+ /** Filas que se muestran en pantalla; el resto sale por Excel, para no convertir el tablero en una tabla. */
15
+ previewRows?: number;
16
+ /** Nota bajo la tabla cuando hay más filas que `previewRows`. */
17
+ previewNote?: (shown: number, total: number) => string;
18
+ /** Nota bajo la tabla cuando caben todas. */
19
+ totalNote?: (total: number) => string;
20
+ emptyMessage?: string;
21
+ closeLabel?: string;
22
+ exportLabel?: string;
23
+ /** Nombre del archivo exportado, sin extensión. */
24
+ exportFilename?: (spec: DetailSpec<Row>, groupId: string | null) => string;
25
+ /** Acciones extra junto a «Exportar Excel» (p. ej. abrir la lista en otro módulo); reciben las filas visibles. */
26
+ actions?: (context: {
27
+ spec: DetailSpec<Row>;
28
+ groupId: string | null;
29
+ groupTitle: string | null;
30
+ rows: Row[];
31
+ }) => ReactNode;
32
+ };
33
+ /**
34
+ * Panel de «qué hay detrás de la cifra»: título, contador, frase que explica la lista, una pestaña por caso
35
+ * con su acción, las primeras filas en una `CustomTable` y la exportación completa a Excel. Se abre desde
36
+ * `DrilldownKPICard`, `DrilldownAlertList` o cualquier pieza que llame a `detail.toggle(spec)`.
37
+ */
38
+ export declare const DetailPanel: <Row>({ detail, rowMapper, subtitle, countLabel, previewRows, previewNote, totalNote, emptyMessage, closeLabel, exportLabel, exportFilename, actions, }: DetailPanelProps<Row>) => import("react").JSX.Element | null;
@@ -0,0 +1,68 @@
1
+ import { ReactNode } from 'react';
2
+ import { KPICardAccentTone, KPICardTrendDirection } from '../cards/KPICard';
3
+ import { DetailController, DetailSpec } from './detailTypes';
4
+ import { EventStatus } from './types';
5
+ /** Hay algo que mostrar: filas sueltas o filas dentro de algún caso. */
6
+ export declare const detailHasRows: <Row>(spec: DetailSpec<Row> | null | undefined) => spec is DetailSpec<Row>;
7
+ export type DrilldownKPICardProps<Row> = {
8
+ label: string;
9
+ /** Valor ya formateado (la librería no decide el separador de miles). */
10
+ value: string | number;
11
+ sublabel?: string;
12
+ icon?: ReactNode;
13
+ accentTone?: KPICardAccentTone;
14
+ /** El indicador destacado de la fila (`KPICard variant="filled"`). */
15
+ primary?: boolean;
16
+ /** Variación porcentual contra el período anterior; `null` cuando no hay base para comparar. */
17
+ trend?: number | null;
18
+ trendDirection?: KPICardTrendDirection;
19
+ trendLabel?: string;
20
+ detail: DetailController<Row>;
21
+ /** Lo que se abre al hacer clic; sin filas, la tarjeta no es clicable. */
22
+ spec?: DetailSpec<Row> | null;
23
+ };
24
+ /** `KPICard` de tablero que abre su detalle en el `DetailPanel` compartido y se marca cuando está abierto. */
25
+ export declare const DrilldownKPICard: <Row>({ label, value, sublabel, icon, accentTone, primary, trend, trendDirection, trendLabel, detail, spec, }: DrilldownKPICardProps<Row>) => import("react").JSX.Element;
26
+ export type DrilldownAlertItem<Row> = {
27
+ label: string;
28
+ /** Valor ya formateado; con `count` en 0 la fila toma `zeroTone`. */
29
+ value: ReactNode;
30
+ count: number;
31
+ tone: EventStatus;
32
+ description?: string;
33
+ spec?: DetailSpec<Row> | null;
34
+ };
35
+ export type DrilldownAlertListProps<Row> = {
36
+ items: DrilldownAlertItem<Row>[];
37
+ detail: DetailController<Row>;
38
+ /** Tono de una fila cuya cifra es 0 (nada pendiente). */
39
+ zeroTone?: EventStatus;
40
+ emptyMessage?: ReactNode;
41
+ };
42
+ /** Lista de pendientes en una tarjeta: cada fila abre su detalle en el `DetailPanel` compartido. */
43
+ export declare const DrilldownAlertList: <Row>({ items, detail, zeroTone, emptyMessage, }: DrilldownAlertListProps<Row>) => import("react").JSX.Element;
44
+ export type DetailPreviewItem = {
45
+ key: string;
46
+ /** Código o documento, en monoespaciada delante del texto principal. */
47
+ code?: string;
48
+ primary: string;
49
+ /** Texto corto a la derecha (p. ej. la situación o los días). */
50
+ meta?: string;
51
+ };
52
+ export type DetailPreviewListProps = {
53
+ items: DetailPreviewItem[];
54
+ /** Total real de la lista; se muestra grande arriba de la vista previa. */
55
+ total: number;
56
+ /** Total ya formateado; por defecto `String(total)`. */
57
+ totalLabel?: string;
58
+ /** Sustantivo junto al total (p. ej. «personas»). */
59
+ unit?: string;
60
+ /** Cuántos elementos se muestran antes del botón. */
61
+ previewCount?: number;
62
+ emptyText: string;
63
+ /** Abre el detalle completo; el botón dice «Ver las N» o `openLabel`. */
64
+ onOpen: () => void;
65
+ openLabel?: string;
66
+ };
67
+ /** Las primeras filas de una lista con su total y el acceso al detalle completo. */
68
+ export declare const DetailPreviewList: ({ items, total, totalLabel, unit, previewCount, emptyText, onOpen, openLabel, }: DetailPreviewListProps) => import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ /** Columna del panel de detalle: `key` indexa la fila ya aplanada (ver `DetailPanel.rowMapper`). */
2
+ export type DetailColumn = {
3
+ key: string;
4
+ header: string;
5
+ };
6
+ /** Un caso distinto dentro de la misma cifra: su pestaña, la acción que se sugiere y sus filas. */
7
+ export type DetailGroup<Row> = {
8
+ id: string;
9
+ title: string;
10
+ note?: string;
11
+ rows: Row[];
12
+ };
13
+ /** Lo que hay detrás de una cifra: las filas, cómo se leen (frase, columnas) y, si hay casos, una pestaña por caso. */
14
+ export type DetailSpec<Row> = {
15
+ id: string;
16
+ title: string;
17
+ rows: Row[];
18
+ columns: DetailColumn[];
19
+ /** Qué es la lista y qué hacer con ella, en una frase. */
20
+ description?: string;
21
+ groups?: DetailGroup<Row>[];
22
+ /** Nota al pie, p. ej. quiénes quedaron fuera de la lista y por qué. */
23
+ footnote?: string;
24
+ };
25
+ /** Estado del panel de detalle compartido por las piezas de un tablero: qué cifra está abierta. */
26
+ export type DetailController<Row> = {
27
+ open: DetailSpec<Row> | null;
28
+ /** Abre esa cifra, o la cierra si ya estaba abierta. */
29
+ toggle: (spec: DetailSpec<Row>) => void;
30
+ close: () => void;
31
+ };
@@ -22,6 +22,18 @@ export { DashboardCard, DashboardGrid, DashboardSection, DashboardStatBar, Dashb
22
22
  export type { DashboardCardProps } from './DashboardCard';
23
23
  export { DetailSidePanel } from './DetailSidePanel';
24
24
  export type { DetailSidePanelProps, DetailSidePanelRow, DetailSidePanelSecondaryItem } from './DetailSidePanel';
25
+ export { CardNote } from './CardNote';
26
+ export type { CardNoteProps, CardNoteTone } from './CardNote';
27
+ export { DashboardBoard, DashboardCardRow } from './DashboardBoard';
28
+ export type { DashboardBoardBlock, DashboardBoardHiddenPiece, DashboardBoardProps, DashboardCardRowProps } from './DashboardBoard';
29
+ export { DashboardPageFrame } from './DashboardPageFrame';
30
+ export type { DashboardDateRange, DashboardPageFrameProps } from './DashboardPageFrame';
31
+ export { DashboardPlaceholder } from './DashboardPlaceholder';
32
+ export type { DashboardPlaceholderProps, DashboardPlaceholderShape, DashboardPlaceholderState } from './DashboardPlaceholder';
33
+ export { DetailPanel, useDetailPanel } from './DetailPanel';
34
+ export type { DetailPanelProps, DetailColumn, DetailController, DetailGroup, DetailSpec } from './DetailPanel';
35
+ export { DrilldownKPICard, DrilldownAlertList, DetailPreviewList, detailHasRows } from './DrilldownCards';
36
+ export type { DrilldownKPICardProps, DrilldownAlertItem, DrilldownAlertListProps, DetailPreviewItem, DetailPreviewListProps, } from './DrilldownCards';
25
37
  export { DetailSection } from './DetailSection';
26
38
  export type { DetailSectionProps, DetailSectionRow, DetailSectionTone, DetailSectionDensity } from './DetailSection';
27
39
  export type { StepStatus, StepItem, CalendarBadgeVariant, CalendarDayBadge, DonutGaugeSize, DashboardCardVariant, DashboardCardState, EventStatus, EventItem, AlertRowItem, AlertRowSeverity, AlertRowListHeaderLabels, AlertActionItem, AlertDensity, StatBarItem, } from './types';
@@ -1,4 +1,5 @@
1
1
  import { FieldValues, Path, UseFormReturn } from 'react-hook-form';
2
+ import { IconType } from 'react-icons';
2
3
  import { SelectOption } from './SelectOption';
3
4
  type AsyncSearchSelectProps<TFormValues extends FieldValues> = {
4
5
  form: UseFormReturn<TFormValues>;
@@ -15,6 +16,8 @@ type AsyncSearchSelectProps<TFormValues extends FieldValues> = {
15
16
  readOnly?: boolean;
16
17
  small?: boolean;
17
18
  onChange?: (value: string | string[]) => void;
19
+ /** Icono a la izquierda, en su propio compartimento. */
20
+ leadingIcon?: IconType;
18
21
  };
19
- export declare const AsyncSearchSelect: <TFormValues extends FieldValues>({ form, name, loadOptions, label, info, required, multiple, className, placeholder, searchPlaceholder, emptyMessage, readOnly, small, onChange, }: AsyncSearchSelectProps<TFormValues>) => import("react").JSX.Element;
22
+ export declare const AsyncSearchSelect: <TFormValues extends FieldValues>({ form, name, loadOptions, label, info, required, multiple, className, placeholder, searchPlaceholder, emptyMessage, readOnly, small, onChange, leadingIcon, }: AsyncSearchSelectProps<TFormValues>) => import("react").JSX.Element;
20
23
  export {};
@@ -0,0 +1,6 @@
1
+ import { IconType } from 'react-icons';
2
+ /** Icono a la izquierda del campo, en su propio compartimento con línea divisoria. */
3
+ export declare const FieldLeadingIcon: ({ icon: Icon, small }: {
4
+ icon: IconType;
5
+ small?: boolean;
6
+ }) => import("react").JSX.Element;
@@ -18,6 +18,8 @@ type InputProps<TFormValues extends FieldValues> = {
18
18
  step?: number | string;
19
19
  onChange?: (value: string) => void;
20
20
  icon?: IconType;
21
+ /** Icono a la izquierda, en su propio compartimento. `icon` sigue siendo el de la derecha. */
22
+ leadingIcon?: IconType;
21
23
  small?: boolean;
22
24
  onIconClick?: () => Promise<void> | void;
23
25
  onRefresh?: () => Promise<void> | void;
@@ -26,6 +28,11 @@ type InputProps<TFormValues extends FieldValues> = {
26
28
  * para experiencias de una pregunta por pantalla. Usa clases propias de este componente; NO
27
29
  * modifica `fieldClassName.ts` ni afecta a ningún otro input. */
28
30
  variant?: InputVariant;
31
+ /**
32
+ * Valores que se ofrecen mientras se escribe (lista nativa del navegador, `<datalist>`). El texto sigue siendo
33
+ * libre: sirve para que un mismo valor no se escriba de tres formas ("Urb Primavera", "Urb. Primavera"…).
34
+ */
35
+ suggestions?: string[];
29
36
  };
30
- export declare const Input: <TFormValues extends FieldValues>({ form, name, label, info, required, type, className, placeholder, readOnly, min, max, step, onChange, icon, small, onIconClick, onRefresh, variant, }: InputProps<TFormValues>) => React.JSX.Element;
37
+ export declare const Input: <TFormValues extends FieldValues>({ form, name, label, info, required, type, className, placeholder, readOnly, min, max, step, onChange, icon, leadingIcon, small, onIconClick, onRefresh, variant, suggestions, }: InputProps<TFormValues>) => React.JSX.Element;
31
38
  export {};
@@ -1,4 +1,5 @@
1
1
  import { FieldValues, Path, UseFormReturn } from 'react-hook-form';
2
+ import { IconType } from 'react-icons';
2
3
  import { SelectOption } from './SelectOption';
3
4
  type SearchSelectProps<TFormValues extends FieldValues> = {
4
5
  form: UseFormReturn<TFormValues>;
@@ -19,6 +20,8 @@ type SearchSelectProps<TFormValues extends FieldValues> = {
19
20
  onRefresh?: () => Promise<void> | void;
20
21
  loading?: boolean;
21
22
  onOpen?: () => void;
23
+ /** Icono a la izquierda, en su propio compartimento. */
24
+ leadingIcon?: IconType;
22
25
  };
23
- export declare const SearchSelect: <TFormValues extends FieldValues>({ form, name, options, label, info, required, multiple, className, placeholder, searchPlaceholder, emptyMessage, readOnly, small, maxChips, onChange, onRefresh, onOpen, loading, }: SearchSelectProps<TFormValues>) => import("react").JSX.Element;
26
+ export declare const SearchSelect: <TFormValues extends FieldValues>({ form, name, options, label, info, required, multiple, className, placeholder, searchPlaceholder, emptyMessage, readOnly, small, maxChips, onChange, onRefresh, onOpen, loading, leadingIcon, }: SearchSelectProps<TFormValues>) => import("react").JSX.Element;
24
27
  export {};
@@ -1,8 +1,10 @@
1
- export declare const fieldClassName: ({ small, error, className, withRightIcon, disabled, readOnly, }: {
1
+ export declare const fieldClassName: ({ small, error, className, withRightIcon, withLeftIcon, disabled, readOnly, }: {
2
2
  small?: boolean;
3
3
  error?: boolean;
4
4
  className?: string;
5
5
  withRightIcon?: boolean;
6
+ /** Deja sitio al icono de la izquierda (`FieldLeadingIcon`). */
7
+ withLeftIcon?: boolean;
6
8
  disabled?: boolean;
7
9
  readOnly?: boolean;
8
10
  }) => string;
@@ -0,0 +1,22 @@
1
+ import { ReactNode } from 'react';
2
+ export type MapPointCardItem = {
3
+ key: string;
4
+ label: string;
5
+ /** Sin valor la fila no se pinta. */
6
+ value?: ReactNode;
7
+ /** Botón al lado del valor, p. ej. "Usar" cuando el campo ya tiene algo escrito a mano. */
8
+ actionLabel?: string;
9
+ onAction?: () => void;
10
+ };
11
+ export type MapPointCardProps = {
12
+ title?: string;
13
+ items: MapPointCardItem[];
14
+ loading?: boolean;
15
+ error?: string | null;
16
+ /** Aviso destacado al pie (p. ej. otro registro muy cerca). */
17
+ warning?: ReactNode;
18
+ onClose?: () => void;
19
+ className?: string;
20
+ };
21
+ /** Tarjeta con lo que hay en el punto pulsado de un mapa. Va en `MapView.panel`. */
22
+ export declare const MapPointCard: ({ title, items, loading, error, warning, onClose, className, }: MapPointCardProps) => import("react").JSX.Element;
@@ -8,3 +8,5 @@ export { MapToolbar } from './MapToolbar';
8
8
  export type { MapToolbarMapType, MapToolbarProps } from './MapToolbar';
9
9
  export { MapVehiclePopup } from './MapVehiclePopup';
10
10
  export type { MapVehiclePopupProps } from './MapVehiclePopup';
11
+ export { MapPointCard } from './MapPointCard';
12
+ export type { MapPointCardItem, MapPointCardProps } from './MapPointCard';
@@ -0,0 +1,5 @@
1
+ import { SfLatLng, SfMapCircle } from '../../../types/maps';
2
+ /** Punto del borde inferior-derecho del círculo, donde se ancla su etiqueta (igual en Leaflet y Google). */
3
+ export declare const sfCircleLabelAnchor: (circle: SfMapCircle) => SfLatLng;
4
+ /** Contorno cerrado del círculo (para trazarlo punteado donde el motor no sabe hacerlo). */
5
+ export declare const sfCircleRing: (circle: SfMapCircle, lados?: number) => SfLatLng[];
@@ -2,5 +2,5 @@ import { SfGoogleMapsEngineConfig, SfMapRendererProps } from '../../../../types/
2
2
  type GoogleMapRendererProps = SfMapRendererProps & {
3
3
  config?: SfGoogleMapsEngineConfig;
4
4
  };
5
- export declare const GoogleMapRenderer: ({ config, center, zoom, markers, routes, areas, height, className, readOnly, onClick, onMarkerClick, onMarkerDoubleClick, onMarkerDrag, onMarkerDragEnd, }: GoogleMapRendererProps) => import("react").JSX.Element;
5
+ export declare const GoogleMapRenderer: ({ config, center, zoom, markers, routes, areas, circles, mapType, view, onViewChange, zoomControl, height, className, readOnly, onClick, onMarkerClick, onMarkerDoubleClick, onMarkerDrag, onMarkerDragEnd, }: GoogleMapRendererProps) => import("react").JSX.Element;
6
6
  export {};
@@ -3,5 +3,5 @@ type LeafletMapRendererProps = SfMapRendererProps & {
3
3
  tileUrl?: string;
4
4
  attribution?: string;
5
5
  };
6
- export declare const LeafletMapRenderer: ({ center, zoom, markers, routes, areas, height, className, readOnly, tileUrl, attribution, onClick, onMarkerClick, onMarkerDoubleClick, onMarkerDrag, onMarkerDragEnd, }: LeafletMapRendererProps) => import("react").JSX.Element;
6
+ export declare const LeafletMapRenderer: ({ center, zoom, markers, routes, areas, circles, mapType, view, onViewChange, zoomControl, height, className, readOnly, tileUrl, attribution, onClick, onMarkerClick, onMarkerDoubleClick, onMarkerDrag, onMarkerDragEnd, }: LeafletMapRendererProps) => import("react").JSX.Element;
7
7
  export {};
@@ -1,9 +1,26 @@
1
- import { SfMapMarker, SfMapRendererProps, SfMapShape } from '../../../types/maps';
2
- export type MapViewProps = Omit<SfMapRendererProps, "markers"> & {
1
+ import { ReactNode } from 'react';
2
+ import { SfLatLng, SfMapCircle, SfMapMarker, SfMapRendererProps, SfMapShape, SfMapType } from '../../../types/maps';
3
+ export type MapViewProps = Omit<SfMapRendererProps, "markers" | "view" | "onViewChange" | "zoomControl"> & {
3
4
  title?: string;
4
5
  description?: string;
5
6
  markers?: SfMapMarker[];
6
7
  routes?: SfMapShape[];
7
8
  areas?: SfMapShape[];
9
+ circles?: SfMapCircle[];
10
+ /** Con `onMapTypeChange` se pinta el selector Mapa / Satélite arriba a la izquierda. */
11
+ mapType?: SfMapType;
12
+ onMapTypeChange?: (mapType: SfMapType) => void;
13
+ /** Controles propios arriba a la derecha: zoom, recentrar (si hay `recenterTo`) y capas (si hay `onMapTypeChange`). */
14
+ showControls?: boolean;
15
+ /** Destino del botón recentrar. */
16
+ recenterTo?: SfLatLng | null;
17
+ /** Cuando cambia, el mapa se centra ahí (p. ej. el resultado de una búsqueda). */
18
+ focus?: SfLatLng | null;
19
+ /** Zoom mínimo al recentrar o al ir a `focus`. */
20
+ focusZoom?: number;
21
+ /** Tarjeta abajo a la derecha (leyenda). */
22
+ legend?: ReactNode;
23
+ /** Panel arriba a la izquierda, bajo el selector de capas (p. ej. `MapPointCard`). */
24
+ panel?: ReactNode;
8
25
  };
9
- export declare const MapView: ({ title, description, center, zoom, markers, routes, areas, height, className, readOnly, onClick, onMarkerClick, onMarkerDoubleClick, onMarkerDrag, onMarkerDragEnd, }: MapViewProps) => import("react").JSX.Element;
26
+ export declare const MapView: ({ title, description, center, zoom, markers, routes, areas, circles, mapType, onMapTypeChange, showControls, recenterTo, focus, focusZoom, legend, panel, height, className, readOnly, onClick, onMarkerClick, onMarkerDoubleClick, onMarkerDrag, onMarkerDragEnd, }: MapViewProps) => import("react").JSX.Element;
@@ -4,5 +4,8 @@ export declare const SF_DEFAULT_MAP_CENTER: SfLatLng;
4
4
  export declare const SF_DEFAULT_MAP_ZOOM = 13;
5
5
  export declare const SF_OSM_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
6
6
  export declare const SF_OSM_ATTRIBUTION = "&copy; <a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\" rel=\"noreferrer\">OpenStreetMap</a>";
7
+ /** Imagen satelital de Esri World Imagery: sin clave, exige la atribución de abajo. */
8
+ export declare const SF_SATELLITE_TILE_URL = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}";
9
+ export declare const SF_SATELLITE_ATTRIBUTION = "Imagen &copy; <a href=\"https://www.esri.com\" target=\"_blank\" rel=\"noreferrer\">Esri</a>, Maxar, Earthstar Geographics";
7
10
  export declare const SF_LEAFLET_ENGINE_CAPABILITIES: SfMapEngineCapabilities;
8
11
  export declare const SF_GOOGLE_ENGINE_CAPABILITIES: SfMapEngineCapabilities;
@@ -9,6 +9,8 @@ export interface IGeocoderService {
9
9
  capabilities: SfGeocodingCapabilities;
10
10
  geocode(query: string): Promise<SfPlace[]>;
11
11
  reverseGeocode(point: SfLatLng): Promise<SfPlace | null>;
12
+ /** Nombre y dirección de un lugar del mapa base (`SfMapClickEvent.placeId`). Solo lo ofrece Google. */
13
+ placeDetails?(placeId: string): Promise<SfPlace | null>;
12
14
  }
13
15
  export interface IRouteService {
14
16
  id: string;
@@ -1,3 +1,4 @@
1
1
  export { useAddressSearch } from './useAddressSearch';
2
2
  export { useMapEngine } from './useMapEngine';
3
3
  export { useMapsConfig } from './useMapsConfig';
4
+ export { useReverseGeocode } from './useReverseGeocode';
@@ -0,0 +1,14 @@
1
+ import { SfLatLng, SfPlace } from '../../types/maps';
2
+ /**
3
+ * Qué hay en un punto del mapa: dirección y partes (geocodificación inversa) y, si se pulsó un lugar del mapa
4
+ * base (`SfMapClickEvent.placeId`), su nombre. Solo vale la última consulta: `lookup` resuelve `null` si otra
5
+ * más nueva la reemplazó, así quien llama no pisa datos con una respuesta vieja.
6
+ */
7
+ export declare const useReverseGeocode: () => {
8
+ place: SfPlace | null;
9
+ loading: boolean;
10
+ error: string | null;
11
+ lookup: (point: SfLatLng, placeId?: string) => Promise<SfPlace | null>;
12
+ show: (next: SfPlace | null) => void;
13
+ clear: () => void;
14
+ };