@envisiongroup/porygon 1.0.1 → 1.0.2

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.
@@ -0,0 +1,24 @@
1
+ import { EFWTimelineItem, EFWTimelineProps } from './EFWTimeline.types';
2
+
3
+ /**
4
+ * `EFWTimeline` — timeline data-driven, flexible y orientable.
5
+ *
6
+ * Renderiza una línea de tiempo a partir de `items`, soportando orientación
7
+ * vertical (por defecto) u horizontal, resolución de iconos por múltiples
8
+ * criterios (callback > icono del item > mapa por estado > default), badges y
9
+ * contenido por defecto o totalmente custom (`render`/`renderItem`), y derivación
10
+ * automática del progreso vía `activeIndex`/`activeId` (por ejemplo, para marcar
11
+ * "hasta qué etapa llegó" un evento).
12
+ *
13
+ * @example
14
+ * <EFWTimeline
15
+ * orientation="vertical"
16
+ * activeIndex={2}
17
+ * items={[
18
+ * { id: 'a', title: 'Descarga', timestamp: '2026-07-01T09:00:00Z' },
19
+ * { id: 'b', title: 'Parseo', status: 'error', description: 'Falló' },
20
+ * { id: 'c', title: 'Chunk' },
21
+ * ]}
22
+ * />
23
+ */
24
+ export declare function EFWTimeline<TItem extends EFWTimelineItem = EFWTimelineItem>(props: EFWTimelineProps<TItem>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,392 @@
1
+ import { jsx as r, jsxs as T, Fragment as J } from "react/jsx-runtime";
2
+ import { makeStyles as Q, tokens as e, mergeClasses as f, Spinner as Y, Caption1 as G, Text as Z, Badge as ee } from "@fluentui/react-components";
3
+ import { CircleRegular as te, ErrorCircleFilled as oe, RecordRegular as ne, CheckmarkCircleFilled as ie } from "@fluentui/react-icons";
4
+ import { resolveActiveIndex as re, resolveItemStatus as ae, isKnownStatus as A, defaultFormatTimestamp as le, DEFAULT_STATUS_COLORS as ce } from "./EFWTimeline.utils.js";
5
+ import { useTimelineLocaleText as se } from "../../i18n/PorygonI18nProvider.js";
6
+ const me = Q({
7
+ // --- Raíz ---
8
+ root: {
9
+ display: "flex",
10
+ minWidth: 0,
11
+ // Permite que el timeline nunca desborde su contenedor en móviles.
12
+ maxWidth: "100%"
13
+ },
14
+ rootVertical: {
15
+ flexDirection: "column"
16
+ },
17
+ rootHorizontal: {
18
+ flexDirection: "row",
19
+ alignItems: "flex-start",
20
+ columnGap: 0,
21
+ overflowX: "auto",
22
+ // Scroll horizontal suave en dispositivos táctiles.
23
+ WebkitOverflowScrolling: "touch"
24
+ },
25
+ // --- Item ---
26
+ item: {
27
+ display: "grid",
28
+ minWidth: 0
29
+ },
30
+ itemVertical: {
31
+ gridTemplateColumns: "auto minmax(0, 1fr)"
32
+ },
33
+ itemVerticalSmall: { columnGap: e.spacingHorizontalS },
34
+ itemVerticalMedium: { columnGap: e.spacingHorizontalM },
35
+ itemVerticalLarge: { columnGap: e.spacingHorizontalL },
36
+ itemHorizontal: {
37
+ gridTemplateRows: "auto minmax(0, 1fr)",
38
+ rowGap: e.spacingVerticalS,
39
+ flexGrow: 1,
40
+ flexShrink: 1,
41
+ flexBasis: 0,
42
+ minWidth: "120px"
43
+ },
44
+ itemInteractive: {
45
+ cursor: "pointer",
46
+ borderRadius: e.borderRadiusMedium,
47
+ ":hover": {
48
+ backgroundColor: e.colorNeutralBackground1Hover
49
+ },
50
+ ":focus-visible": {
51
+ outlineWidth: e.strokeWidthThick,
52
+ outlineStyle: "solid",
53
+ outlineColor: e.colorStrokeFocus2
54
+ }
55
+ },
56
+ // --- Riel (icono + conector) ---
57
+ rail: {
58
+ display: "flex",
59
+ alignItems: "center"
60
+ },
61
+ railVertical: {
62
+ flexDirection: "column",
63
+ // Sin gap: el conector queda a ras del icono para una línea continua y
64
+ // simétrica entre puntos.
65
+ rowGap: 0
66
+ },
67
+ railHorizontal: {
68
+ flexDirection: "row",
69
+ columnGap: 0,
70
+ width: "100%"
71
+ },
72
+ // --- Icono ---
73
+ icon: {
74
+ display: "inline-flex",
75
+ alignItems: "center",
76
+ justifyContent: "center",
77
+ flexShrink: 0,
78
+ color: e.colorNeutralForeground4,
79
+ lineHeight: 0
80
+ },
81
+ iconDone: { color: e.colorStatusSuccessForeground1 },
82
+ iconCurrent: { color: e.colorBrandForeground1 },
83
+ iconError: { color: e.colorStatusDangerForeground1 },
84
+ iconPending: { color: e.colorNeutralForeground4 },
85
+ // --- Conector ---
86
+ connector: {
87
+ flexGrow: 1,
88
+ backgroundColor: e.colorNeutralStroke2,
89
+ borderRadius: e.borderRadiusCircular
90
+ },
91
+ connectorVertical: {
92
+ width: e.strokeWidthThin
93
+ },
94
+ connectorHorizontal: {
95
+ height: e.strokeWidthThin
96
+ },
97
+ // Longitud mínima del conector según tamaño.
98
+ connectorVSmall: {
99
+ minHeight: e.spacingVerticalS,
100
+ margin: e.spacingVerticalXS + " 0"
101
+ },
102
+ connectorVMedium: {
103
+ minHeight: e.spacingVerticalM,
104
+ margin: e.spacingVerticalXS + " 0"
105
+ },
106
+ connectorVLarge: {
107
+ minHeight: e.spacingVerticalL,
108
+ margin: e.spacingVerticalXS + " 0"
109
+ },
110
+ connectorHSmall: {
111
+ minWidth: e.spacingHorizontalS,
112
+ // Gap horizontal (izq/der) para separar el conector de ambos iconos.
113
+ margin: "0 " + e.spacingHorizontalXS
114
+ },
115
+ connectorHMedium: {
116
+ minWidth: e.spacingHorizontalM,
117
+ margin: "0 " + e.spacingHorizontalS
118
+ },
119
+ connectorHLarge: {
120
+ minWidth: e.spacingHorizontalL,
121
+ margin: "0 " + e.spacingHorizontalS
122
+ },
123
+ connectorDone: {
124
+ backgroundColor: e.colorStatusSuccessBorderActive
125
+ },
126
+ // --- Contenido ---
127
+ // El padding del contenido es lo que separa un item del siguiente. Como el
128
+ // conector (flex-grow) llena todo el alto/ancho del riel, la línea llega
129
+ // hasta el icono siguiente sin cortes.
130
+ content: {
131
+ display: "grid",
132
+ rowGap: e.spacingVerticalXS,
133
+ minWidth: 0
134
+ },
135
+ contentVertical: {},
136
+ contentVerticalSmall: { paddingBottom: e.spacingVerticalS },
137
+ contentVerticalMedium: { paddingBottom: e.spacingVerticalL },
138
+ contentVerticalLarge: { paddingBottom: e.spacingVerticalXXL },
139
+ contentHorizontal: {},
140
+ contentHorizontalSmall: { paddingRight: e.spacingHorizontalS },
141
+ contentHorizontalMedium: { paddingRight: e.spacingHorizontalL },
142
+ contentHorizontalLarge: { paddingRight: e.spacingHorizontalXXL },
143
+ timestamp: { color: e.colorNeutralForeground3 },
144
+ titleRow: {
145
+ display: "flex",
146
+ flexWrap: "wrap",
147
+ alignItems: "center",
148
+ columnGap: e.spacingHorizontalS,
149
+ rowGap: e.spacingVerticalXXS
150
+ },
151
+ description: { color: e.colorNeutralForeground2 },
152
+ // --- Tamaños de icono ---
153
+ sizeSmall: { fontSize: "16px", width: "16px", height: "16px" },
154
+ sizeMedium: { fontSize: "20px", width: "20px", height: "20px" },
155
+ sizeLarge: { fontSize: "24px", width: "24px", height: "24px" }
156
+ }), de = {
157
+ done: /* @__PURE__ */ r(ie, {}),
158
+ current: /* @__PURE__ */ r(ne, {}),
159
+ error: /* @__PURE__ */ r(oe, {}),
160
+ pending: /* @__PURE__ */ r(te, {})
161
+ };
162
+ function ue(t, o) {
163
+ switch (t) {
164
+ case "done":
165
+ return o.iconDone;
166
+ case "current":
167
+ return o.iconCurrent;
168
+ case "error":
169
+ return o.iconError;
170
+ case "pending":
171
+ return o.iconPending;
172
+ default:
173
+ return "";
174
+ }
175
+ }
176
+ function ge(t, o, l) {
177
+ const p = {
178
+ small: t.sizeSmall,
179
+ medium: t.sizeMedium,
180
+ large: t.sizeLarge
181
+ }[o], m = {
182
+ small: t.itemVerticalSmall,
183
+ medium: t.itemVerticalMedium,
184
+ large: t.itemVerticalLarge
185
+ }[o], z = l ? {
186
+ small: t.connectorVSmall,
187
+ medium: t.connectorVMedium,
188
+ large: t.connectorVLarge
189
+ }[o] : {
190
+ small: t.connectorHSmall,
191
+ medium: t.connectorHMedium,
192
+ large: t.connectorHLarge
193
+ }[o], V = l ? {
194
+ small: t.contentVerticalSmall,
195
+ medium: t.contentVerticalMedium,
196
+ large: t.contentVerticalLarge
197
+ }[o] : {
198
+ small: t.contentHorizontalSmall,
199
+ medium: t.contentHorizontalMedium,
200
+ large: t.contentHorizontalLarge
201
+ }[o];
202
+ return { sizeClass: p, itemColClass: m, connectorSizeClass: z, contentSizeClass: V };
203
+ }
204
+ function He(t) {
205
+ const {
206
+ items: o,
207
+ orientation: l = "vertical",
208
+ size: p = "medium",
209
+ activeIndex: m,
210
+ activeId: z,
211
+ statusIcons: V,
212
+ statusColors: k,
213
+ statusLabels: N,
214
+ renderIcon: C,
215
+ renderItem: I,
216
+ renderBadge: i,
217
+ formatTimestamp: d,
218
+ hideBadges: W = !1,
219
+ hideConnectors: F = !1,
220
+ spinnerOnCurrent: u = !1,
221
+ itemSpacing: a = e.spacingVerticalXL,
222
+ onItemClick: h,
223
+ className: v,
224
+ style: M,
225
+ slotClassNames: S,
226
+ labels: D
227
+ } = t, n = me(), c = l === "vertical", B = re(o, m, z), X = se(
228
+ D?.noTimestamp != null ? { noTimestamp: D.noTimestamp } : void 0
229
+ ), { sizeClass: E, itemColClass: O, connectorSizeClass: U, contentSizeClass: _ } = ge(n, p, c), j = "extra-tiny", x = typeof a == "number" ? `${a}px` : a, y = X.noTimestamp;
230
+ return /* @__PURE__ */ r(
231
+ "div",
232
+ {
233
+ role: "list",
234
+ className: f(
235
+ n.root,
236
+ c ? n.rootVertical : n.rootHorizontal,
237
+ S?.root,
238
+ v
239
+ ),
240
+ style: M,
241
+ children: o.map((s, H) => {
242
+ const g = ae(s, H, B), K = H === 0, L = H === o.length - 1, b = {
243
+ item: s,
244
+ index: H,
245
+ status: g,
246
+ isFirst: K,
247
+ isLast: L,
248
+ isActive: B === H,
249
+ orientation: l
250
+ }, P = C?.(b) ?? s.icon ?? (u && g === "current" ? /* @__PURE__ */ r(Y, { size: j }) : void 0) ?? V?.[g] ?? (A(g) ? de[g] : null), $ = !F && !L, w = typeof h == "function", q = (R) => {
251
+ w && (R.key === "Enter" || R.key === " ") && (R.preventDefault(), h?.(b));
252
+ };
253
+ return /* @__PURE__ */ T(
254
+ "div",
255
+ {
256
+ role: w ? "button" : "listitem",
257
+ className: f(
258
+ n.item,
259
+ c ? n.itemVertical : n.itemHorizontal,
260
+ c && O,
261
+ w && n.itemInteractive,
262
+ S?.item
263
+ ),
264
+ ...w ? {
265
+ tabIndex: 0,
266
+ "aria-label": typeof s.title == "string" ? s.title : void 0,
267
+ onClick: () => h?.(b),
268
+ onKeyDown: q
269
+ } : {},
270
+ children: [
271
+ /* @__PURE__ */ T(
272
+ "div",
273
+ {
274
+ className: f(
275
+ n.rail,
276
+ c ? n.railVertical : n.railHorizontal,
277
+ S?.rail
278
+ ),
279
+ children: [
280
+ /* @__PURE__ */ r(
281
+ "span",
282
+ {
283
+ "aria-hidden": "true",
284
+ className: f(
285
+ n.icon,
286
+ E,
287
+ ue(g, n),
288
+ S?.icon
289
+ ),
290
+ children: P
291
+ }
292
+ ),
293
+ $ ? /* @__PURE__ */ r(
294
+ "span",
295
+ {
296
+ className: f(
297
+ n.connector,
298
+ c ? n.connectorVertical : n.connectorHorizontal,
299
+ U,
300
+ g === "done" && n.connectorDone,
301
+ S?.connector
302
+ )
303
+ }
304
+ ) : null
305
+ ]
306
+ }
307
+ ),
308
+ /* @__PURE__ */ r(
309
+ "div",
310
+ {
311
+ className: f(
312
+ n.content,
313
+ c ? n.contentVertical : n.contentHorizontal,
314
+ // Sin spacing custom, la distancia la da la clase por tamaño.
315
+ // El último item no necesita separación final.
316
+ !L && x == null && _,
317
+ S?.content,
318
+ s.className
319
+ ),
320
+ style: {
321
+ ...!L && x != null ? c ? { paddingBottom: x } : { paddingRight: x } : null,
322
+ ...s.style
323
+ },
324
+ children: pe({
325
+ ctx: b,
326
+ renderItem: I,
327
+ renderBadge: i,
328
+ styles: n,
329
+ statusColors: k,
330
+ statusLabels: N,
331
+ localeStatusLabels: X.statusLabels,
332
+ hideBadges: W,
333
+ formatTimestamp: d,
334
+ noTimestampLabel: y
335
+ })
336
+ }
337
+ )
338
+ ]
339
+ },
340
+ s.id
341
+ );
342
+ })
343
+ }
344
+ );
345
+ }
346
+ function pe(t) {
347
+ const {
348
+ ctx: o,
349
+ renderItem: l,
350
+ renderBadge: p,
351
+ styles: m,
352
+ statusColors: z,
353
+ statusLabels: V,
354
+ localeStatusLabels: k,
355
+ hideBadges: N,
356
+ formatTimestamp: C,
357
+ noTimestampLabel: I
358
+ } = t, { item: i, status: d } = o;
359
+ if (i.render)
360
+ return i.render(o);
361
+ if (l) {
362
+ const a = l(o);
363
+ if (a != null) return a;
364
+ }
365
+ const F = i.timestamp != null ? C ? C(i.timestamp, o) : le(i.timestamp) : null;
366
+ let u = null;
367
+ if (i.badge !== void 0)
368
+ u = i.badge;
369
+ else {
370
+ const a = p ? p(o) : void 0;
371
+ if (a !== void 0)
372
+ u = a;
373
+ else if (!N) {
374
+ const h = A(d), v = V?.[d] ?? (h ? k[d] : void 0);
375
+ if (v != null) {
376
+ const M = z?.[d] ?? (h ? ce[d] : void 0);
377
+ u = /* @__PURE__ */ r(ee, { shape: "rounded", size: "small", appearance: "tint", color: M, children: v });
378
+ }
379
+ }
380
+ }
381
+ return /* @__PURE__ */ T(J, { children: [
382
+ i.timestamp !== void 0 ? /* @__PURE__ */ r(G, { className: m.timestamp, children: F ?? I }) : null,
383
+ i.title != null || u != null ? /* @__PURE__ */ T("div", { className: m.titleRow, children: [
384
+ i.title != null ? /* @__PURE__ */ r(Z, { weight: "semibold", children: i.title }) : null,
385
+ u
386
+ ] }) : null,
387
+ i.description != null ? /* @__PURE__ */ r(G, { className: m.description, children: i.description }) : null
388
+ ] });
389
+ }
390
+ export {
391
+ He as EFWTimeline
392
+ };
@@ -0,0 +1,208 @@
1
+ import { BadgeProps } from '@fluentui/react-components';
2
+ import { CSSProperties, ReactNode } from 'react';
3
+
4
+ /**
5
+ * Orientación del timeline.
6
+ *
7
+ * - `vertical`: los items se apilan de arriba hacia abajo (por defecto), con el
8
+ * riel de iconos a la izquierda y el contenido a la derecha. Ideal para
9
+ * historiales/etapas de un proceso.
10
+ * - `horizontal`: los items se disponen de izquierda a derecha, con el icono
11
+ * arriba y el contenido debajo. Ideal para wizards/steppers.
12
+ */
13
+ export type EFWTimelineOrientation = 'vertical' | 'horizontal';
14
+ /**
15
+ * Densidad visual del timeline. Ajusta de forma coordinada el tamaño del icono,
16
+ * la separación entre items, el largo mínimo del conector y el padding del
17
+ * contenido.
18
+ *
19
+ * - `small`: compacto, ideal para paneles laterales, drawers, celdas o vistas
20
+ * móviles donde el espacio es escaso.
21
+ * - `medium`: densidad por defecto.
22
+ * - `large`: más aireado, para vistas destacadas.
23
+ */
24
+ export type EFWTimelineSize = 'small' | 'medium' | 'large';
25
+ /**
26
+ * Estados semánticos de un item. Se acepta además cualquier `string` para
27
+ * estados de dominio propios (el consumidor debe proveer icono/color/label vía
28
+ * las props de personalización).
29
+ */
30
+ export type EFWTimelineItemStatus = 'done' | 'current' | 'error' | 'pending' | (string & {});
31
+ /** Estados "conocidos" con defaults incluidos (icono, color y label). */
32
+ export type EFWTimelineKnownStatus = 'done' | 'current' | 'error' | 'pending';
33
+ /**
34
+ * Contexto entregado a todos los render callbacks. Describe la posición y el
35
+ * estado ya resuelto del item dentro del timeline.
36
+ */
37
+ export interface EFWTimelineItemContext<TItem extends EFWTimelineItem> {
38
+ /** Item original tal como fue provisto. */
39
+ item: TItem;
40
+ /** Índice del item dentro de `items`. */
41
+ index: number;
42
+ /** Estado ya resuelto (item.status ?? derivado de `activeIndex`). */
43
+ status: EFWTimelineItemStatus;
44
+ /** `true` si es el primer item. */
45
+ isFirst: boolean;
46
+ /** `true` si es el último item (no dibuja conector). */
47
+ isLast: boolean;
48
+ /** `true` si el item coincide con `activeIndex`/`activeId`. */
49
+ isActive: boolean;
50
+ /** Orientación efectiva del timeline. */
51
+ orientation: EFWTimelineOrientation;
52
+ }
53
+ /**
54
+ * Un item del timeline. Es data-driven: describe el contenido con campos
55
+ * comunes (title/description/timestamp/badge) y permite reemplazar el contenido
56
+ * completo con `render`. Extiende esta interfaz para tipar `data`.
57
+ *
58
+ * @example
59
+ * type MyItem = EFWTimelineItem & { data: { userId: string } };
60
+ */
61
+ export interface EFWTimelineItem {
62
+ /** Identidad estable del item (para React keys y `activeId`). */
63
+ id: string | number;
64
+ /**
65
+ * Estado del item. Si se omite y se pasa `activeIndex`/`activeId`, el
66
+ * timeline lo deriva (antes → `done`, igual → `current`, después →
67
+ * `pending`).
68
+ */
69
+ status?: EFWTimelineItemStatus;
70
+ /** Título principal del item. */
71
+ title?: ReactNode;
72
+ /** Descripción secundaria del item. */
73
+ description?: ReactNode;
74
+ /** Marca de tiempo; se formatea con `formatTimestamp`. */
75
+ timestamp?: string | number | Date | null;
76
+ /**
77
+ * Badge a mostrar. Si se omite y el estado es conocido, se muestra un badge
78
+ * por defecto con el color/label del estado. Pasa `null` para ocultarlo solo
79
+ * en este item.
80
+ */
81
+ badge?: ReactNode;
82
+ /**
83
+ * Icono explícito de este item. Precedencia:
84
+ * `renderIcon` > `item.icon` > `statusIcons[status]` > default por estado.
85
+ */
86
+ icon?: ReactNode;
87
+ /** Clase CSS aplicada al contenido de este item. */
88
+ className?: string;
89
+ /** Estilos inline aplicados al contenido de este item. */
90
+ style?: CSSProperties;
91
+ /**
92
+ * Reemplaza TODO el contenido de este item por lo que retorne. Tiene
93
+ * prioridad sobre `renderItem` a nivel timeline y sobre los campos comunes.
94
+ */
95
+ render?: (ctx: EFWTimelineItemContext<this>) => ReactNode;
96
+ /** Payload de dominio arbitrario (tipar extendiendo la interfaz). */
97
+ data?: unknown;
98
+ }
99
+ /**
100
+ * Clases CSS por "slot" interno, para overrides finos sin romper el layout.
101
+ */
102
+ export interface EFWTimelineSlotClassNames {
103
+ /** Contenedor raíz del timeline. */
104
+ root?: string;
105
+ /** Contenedor de cada item. */
106
+ item?: string;
107
+ /** Riel (columna/fila del icono + conector). */
108
+ rail?: string;
109
+ /** Envoltorio del icono. */
110
+ icon?: string;
111
+ /** Conector entre items. */
112
+ connector?: string;
113
+ /** Contenedor del contenido. */
114
+ content?: string;
115
+ }
116
+ /** Iconos por estado. Sobrescribe (parcialmente) los defaults por estado. */
117
+ export type EFWTimelineStatusIcons = Partial<Record<EFWTimelineItemStatus, ReactNode>>;
118
+ /** Color de Badge por estado (Fluent Badge `color`). */
119
+ export type EFWTimelineStatusColors = Partial<Record<EFWTimelineItemStatus, BadgeProps['color']>>;
120
+ /** Label de Badge por estado. */
121
+ export type EFWTimelineStatusLabels = Partial<Record<EFWTimelineItemStatus, string>>;
122
+ /** Textos internos localizables del componente. */
123
+ export interface EFWTimelineLabels {
124
+ /** Texto cuando `timestamp` es `null`/vacío. Default: "Sin fecha registrada". */
125
+ noTimestamp?: string;
126
+ }
127
+ /**
128
+ * Props del componente `EFWTimeline`.
129
+ *
130
+ * @template TItem Tipo del item (extiende `EFWTimelineItem` para tipar `data`).
131
+ */
132
+ export interface EFWTimelineProps<TItem extends EFWTimelineItem = EFWTimelineItem> {
133
+ /** Items a renderizar, en orden. */
134
+ items: readonly TItem[];
135
+ /** Orientación del timeline. @default 'vertical' */
136
+ orientation?: EFWTimelineOrientation;
137
+ /** Densidad visual (icono, espaciado, conector y padding). @default 'medium' */
138
+ size?: EFWTimelineSize;
139
+ /**
140
+ * Índice del item "actual" (0-based). Cuando un item no define `status`,
141
+ * el timeline deriva: `index < activeIndex` → `done`, `index === activeIndex`
142
+ * → `current`, `index > activeIndex` → `pending`.
143
+ */
144
+ activeIndex?: number;
145
+ /** Igual que `activeIndex` pero por `id` (tiene prioridad si ambos existen). */
146
+ activeId?: TItem['id'];
147
+ /** Iconos por estado (override parcial de los defaults). */
148
+ statusIcons?: EFWTimelineStatusIcons;
149
+ /** Colores de badge por estado. */
150
+ statusColors?: EFWTimelineStatusColors;
151
+ /** Labels de badge por estado. */
152
+ statusLabels?: EFWTimelineStatusLabels;
153
+ /**
154
+ * Resuelve el icono de cada item. Máxima prioridad. Retorna `undefined`/
155
+ * `null` para caer en la cadena `item.icon` > `statusIcons` > default.
156
+ */
157
+ renderIcon?: (ctx: EFWTimelineItemContext<TItem>) => ReactNode;
158
+ /**
159
+ * Reemplaza el contenido de cada item. `item.render` tiene prioridad sobre
160
+ * esto. Retornar `undefined` cae en el render por defecto (campos comunes).
161
+ */
162
+ renderItem?: (ctx: EFWTimelineItemContext<TItem>) => ReactNode;
163
+ /**
164
+ * Personaliza por completo el badge que acompaña al título, para cada item.
165
+ * Recibe el contexto y decide qué renderizar, sin depender de los labels/
166
+ * colores por defecto (útil para evitar textos por defecto o usar tu propio
167
+ * componente de badge).
168
+ *
169
+ * Precedencia: `item.badge` (si está definido) tiene prioridad sobre esto.
170
+ *
171
+ * Semántica del valor retornado:
172
+ * - `undefined` → cae en el badge por defecto (respetando `hideBadges`).
173
+ * - `null` → oculta el badge para ese item.
174
+ * - cualquier `ReactNode` → se usa tal cual como badge.
175
+ */
176
+ renderBadge?: (ctx: EFWTimelineItemContext<TItem>) => ReactNode;
177
+ /** Formatea el timestamp. Default: `Date.toLocaleString()`. */
178
+ formatTimestamp?: (value: NonNullable<EFWTimelineItem['timestamp']>, ctx: EFWTimelineItemContext<TItem>) => ReactNode;
179
+ /** Oculta todos los badges por defecto. @default false */
180
+ hideBadges?: boolean;
181
+ /**
182
+ * Renderiza un spinner en el icono del item en curso (el elemento activo,
183
+ * es decir `status === 'current'`). Útil para indicar que ese paso está en
184
+ * progreso. No afecta a `renderIcon` ni a `item.icon`, que siguen teniendo
185
+ * prioridad. @default false
186
+ */
187
+ spinnerOnCurrent?: boolean;
188
+ /** Oculta los conectores entre items. @default false */
189
+ hideConnectors?: boolean;
190
+ /**
191
+ * Distancia entre items del timeline. Sobrescribe la separación por defecto
192
+ * derivada de `size`. En vertical controla el espacio vertical (y por tanto
193
+ * el largo del conector); en horizontal, el espacio horizontal. Un `number`
194
+ * se interpreta como px; también acepta cualquier unidad CSS válida
195
+ * (`'1.5rem'`, `'24px'`, un token de spacing, etc.).
196
+ */
197
+ itemSpacing?: number | string;
198
+ /** Callback al hacer click en un item (lo hace interactivo/accesible). */
199
+ onItemClick?: (ctx: EFWTimelineItemContext<TItem>) => void;
200
+ /** Clase del contenedor raíz. */
201
+ className?: string;
202
+ /** Estilos inline del contenedor raíz. */
203
+ style?: CSSProperties;
204
+ /** Clases por slot interno. */
205
+ slotClassNames?: EFWTimelineSlotClassNames;
206
+ /** Textos internos localizables. */
207
+ labels?: EFWTimelineLabels;
208
+ }
@@ -0,0 +1,30 @@
1
+ import { BadgeProps } from '@fluentui/react-components';
2
+ import { EFWTimelineItem, EFWTimelineItemStatus, EFWTimelineKnownStatus } from './EFWTimeline.types';
3
+
4
+ /**
5
+ * Helpers puros de `EFWTimeline`. Sin dependencias de React ni JSX para poder
6
+ * testearlos de forma aislada.
7
+ */
8
+ /** Colores de badge por estado conocido (Fluent Badge `color`). */
9
+ export declare const DEFAULT_STATUS_COLORS: Record<EFWTimelineKnownStatus, BadgeProps['color']>;
10
+ /** Labels de badge por estado conocido (español, localizable vía props). */
11
+ export declare const DEFAULT_STATUS_LABELS: Record<EFWTimelineKnownStatus, string>;
12
+ /** Type guard: ¿el estado es uno de los conocidos con defaults? */
13
+ export declare function isKnownStatus(status: EFWTimelineItemStatus): status is EFWTimelineKnownStatus;
14
+ /**
15
+ * Resuelve el índice activo a partir de `activeId` (prioritario) o `activeIndex`.
16
+ * Retorna `undefined` si no hay ninguno resoluble.
17
+ */
18
+ export declare function resolveActiveIndex(items: readonly EFWTimelineItem[], activeIndex: number | undefined, activeId: EFWTimelineItem['id'] | undefined): number | undefined;
19
+ /**
20
+ * Deriva un estado desde la posición relativa al índice activo.
21
+ * `index < active` → `done`; `index === active` → `current`; resto → `pending`.
22
+ */
23
+ export declare function deriveStatusFromActive(index: number, activeIndex: number): EFWTimelineKnownStatus;
24
+ /**
25
+ * Estado efectivo de un item: usa `item.status` explícito; si falta, deriva de
26
+ * `activeIndex`; si tampoco hay, cae en `pending`.
27
+ */
28
+ export declare function resolveItemStatus(item: EFWTimelineItem, index: number, activeIndex: number | undefined): EFWTimelineItemStatus;
29
+ /** Formateo por defecto de timestamps (fecha/hora local). */
30
+ export declare function defaultFormatTimestamp(value: NonNullable<EFWTimelineItem['timestamp']>): string;
@@ -0,0 +1,45 @@
1
+ const s = {
2
+ done: "success",
3
+ current: "brand",
4
+ error: "danger",
5
+ pending: "informative"
6
+ }, c = {
7
+ done: "Completada",
8
+ current: "En curso",
9
+ error: "Con error",
10
+ pending: "Pendiente"
11
+ }, i = [
12
+ "done",
13
+ "current",
14
+ "error",
15
+ "pending"
16
+ ];
17
+ function d(n) {
18
+ return i.includes(n);
19
+ }
20
+ function f(n, r, t) {
21
+ if (t != null) {
22
+ const e = n.findIndex((u) => u.id === t);
23
+ if (e >= 0) return e;
24
+ }
25
+ if (r != null && Number.isFinite(r)) return r;
26
+ }
27
+ function o(n, r) {
28
+ return n < r ? "done" : n === r ? "current" : "pending";
29
+ }
30
+ function S(n, r, t) {
31
+ return n.status != null ? n.status : t != null ? o(r, t) : "pending";
32
+ }
33
+ function a(n) {
34
+ const r = n instanceof Date ? n : new Date(n);
35
+ return Number.isNaN(r.getTime()) ? String(n) : r.toLocaleString();
36
+ }
37
+ export {
38
+ s as DEFAULT_STATUS_COLORS,
39
+ c as DEFAULT_STATUS_LABELS,
40
+ a as defaultFormatTimestamp,
41
+ o as deriveStatusFromActive,
42
+ d as isKnownStatus,
43
+ f as resolveActiveIndex,
44
+ S as resolveItemStatus
45
+ };