@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +64 -56
- package/dist/react-components/i18n/PorygonI18nProvider.d.ts +3 -2
- package/dist/react-components/i18n/PorygonI18nProvider.js +169 -76
- package/dist/react-components/i18n/defaultLocaleText.d.ts +2 -1
- package/dist/react-components/i18n/defaultLocaleText.js +14 -5
- package/dist/react-components/i18n/index.d.ts +4 -4
- package/dist/react-components/i18n/index.js +14 -12
- package/dist/react-components/i18n/localePresets.js +53 -34
- package/dist/react-components/i18n/porygonI18n.types.d.ts +15 -0
- package/dist/react-components/tables/EFWTable/EFWTable.js +821 -461
- package/dist/react-components/timeline/EFWTimeline/EFWTimeline.d.ts +24 -0
- package/dist/react-components/timeline/EFWTimeline/EFWTimeline.js +392 -0
- package/dist/react-components/timeline/EFWTimeline/EFWTimeline.types.d.ts +208 -0
- package/dist/react-components/timeline/EFWTimeline/EFWTimeline.utils.d.ts +30 -0
- package/dist/react-components/timeline/EFWTimeline/EFWTimeline.utils.js +45 -0
- package/dist/react-components/timeline/EFWTimeline/index.d.ts +3 -0
- package/dist/react-components/timeline/EFWTimeline/index.js +8 -0
- package/package.json +6 -1
|
@@ -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
|
+
};
|