@axzydev/axzy_ui_system 1.6.0 → 1.7.0

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 (34) hide show
  1. package/dist/index.cjs +6053 -4317
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +1 -1
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +145 -1
  6. package/dist/index.d.ts +145 -1
  7. package/dist/index.js +4363 -2612
  8. package/dist/index.js.map +1 -1
  9. package/dist/layered.css +1 -1
  10. package/package.json +1 -1
  11. package/src/App.tsx +10 -0
  12. package/src/components/molecules/json-editor/json-editor-tree.tsx +235 -0
  13. package/src/components/molecules/json-editor/json-editor.index.ts +2 -0
  14. package/src/components/molecules/json-editor/json-editor.props.ts +82 -0
  15. package/src/components/molecules/json-editor/json-editor.tsx +972 -0
  16. package/src/components/molecules/json-editor/json-editor.utils.ts +717 -0
  17. package/src/components/molecules/json-editor/useJsonEditorKeyboard.ts +256 -0
  18. package/src/components/molecules/json-renderer/json-renderer.index.ts +2 -0
  19. package/src/components/molecules/json-renderer/json-renderer.props.ts +31 -0
  20. package/src/components/molecules/json-renderer/json-renderer.tsx +79 -0
  21. package/src/dev.css +24 -1
  22. package/src/hooks/useJsonRender.tsx +9 -0
  23. package/src/i18n/locales/en.ts +54 -0
  24. package/src/i18n/locales/es.ts +54 -0
  25. package/src/index.css +414 -0
  26. package/src/index.ts +10 -2
  27. package/src/sandbox/AxzyLogo.tsx +139 -0
  28. package/src/sandbox/i18n.ts +2 -0
  29. package/src/sandbox/navigation.ts +2 -0
  30. package/src/showcases/FeedbackShowcases.tsx +16 -5
  31. package/src/showcases/HomeShowcase.tsx +16 -9
  32. package/src/showcases/JsonEditorShowcase.tsx +150 -0
  33. package/src/showcases/JsonRendererShowcase.tsx +70 -0
  34. package/src/showcases/StructureShowcases.tsx +6 -1
package/src/index.css CHANGED
@@ -169,4 +169,418 @@
169
169
  :where(.it-table-pinned-right) {
170
170
  box-shadow: inset 1px 0 0 0 var(--color-secondary-200);
171
171
  }
172
+
173
+ /* ── ITJSONEditor ────────────────────────────────────────────────────────
174
+ Editor de código sobre <textarea>: una capa con el JSON resaltado se pinta
175
+ debajo del textarea transparente. Las dos capas comparten tipografía y
176
+ `line-height`, y el alto de línea lo mide el componente en el DOM
177
+ (`--it-json-editor-line`), de ahí que aquí solo se fije la métrica. */
178
+ :where(.it-json-editor-canvas) {
179
+ --it-json-editor-line: 21px;
180
+ background: #fbfcfd;
181
+ }
182
+ :where(.it-json-editor-canvas[data-canvas="ink"]) {
183
+ background: #0b1120;
184
+ }
185
+ :where(.dark .it-json-editor-canvas) {
186
+ background: #020617;
187
+ }
188
+
189
+ :where(.it-json-editor-code) {
190
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
191
+ font-size: 12.5px;
192
+ /* Misma métrica que el textarea: los dos consumen la variable que publica el
193
+ componente con el alto de línea real. */
194
+ line-height: var(--it-json-editor-line, 21px);
195
+ tab-size: 2;
196
+ color: #334155;
197
+ }
198
+ /* El relleno vive en `.it-json-editor-code`, que es la clase compartida por la
199
+ capa resaltada y el textarea: así las dos cajas de scroll tienen el mismo
200
+ contenido y desplazan igual, y el alto de cada línea es exactamente
201
+ `--it-json-editor-line` para que el gutter no se desincronice. */
202
+ :where(.it-json-editor-code) {
203
+ padding: 10px 12px;
204
+ }
205
+ :where(.it-json-editor-code-line) {
206
+ white-space: pre;
207
+ }
208
+ :where(.it-json-editor-highlight) {
209
+ position: absolute;
210
+ inset: 0;
211
+ z-index: 0;
212
+ pointer-events: none;
213
+ }
214
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-code) {
215
+ color: #cbd5e1;
216
+ }
217
+
218
+ /* Textarea: texto transparente y cursor visible, encima de la capa pintada.
219
+ Las métricas de texto van declaradas **aquí** y no heredadas de
220
+ `.it-json-editor-code`: el textarea no es hijo de esa capa, así que sin esto
221
+ usa la tipografía de la página y el caret del navegador deja de coincidir
222
+ con el glifo pintado (el clic cae en otro carácter). */
223
+ :where(.it-json-editor-textarea) {
224
+ position: absolute;
225
+ inset: 0;
226
+ z-index: 1;
227
+ width: 100%;
228
+ height: 100%;
229
+ margin: 0;
230
+ border: 0;
231
+ resize: none;
232
+ outline: none;
233
+ background: transparent;
234
+ color: transparent;
235
+ caret-color: #2563eb;
236
+ -webkit-text-fill-color: transparent;
237
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
238
+ font-size: 12.5px;
239
+ line-height: var(--it-json-editor-line, 21px);
240
+ tab-size: 2;
241
+ letter-spacing: normal;
242
+ padding: 10px 12px;
243
+ white-space: pre;
244
+ overflow-wrap: normal;
245
+ word-break: normal;
246
+ }
247
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-textarea) {
248
+ caret-color: #7dd3fc;
249
+ }
250
+ :where(.it-json-editor-textarea)::placeholder {
251
+ color: #94a3b8;
252
+ -webkit-text-fill-color: #94a3b8;
253
+ }
254
+ :where(.it-json-editor-textarea)::selection {
255
+ background: rgb(37 99 235 / 0.25);
256
+ }
257
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-textarea)::selection {
258
+ background: rgb(125 211 252 / 0.25);
259
+ }
260
+ :where(.it-json-editor-nowrap) {
261
+ white-space: pre;
262
+ overflow: auto;
263
+ }
264
+
265
+ /* Gutter: números y controles de plegado, con la misma rejilla que el código. */
266
+ :where(.it-json-editor-gutter),
267
+ :where(.it-json-editor-linenumbers) {
268
+ background: rgb(148 163 184 / 0.08);
269
+ border-right: 1px solid rgb(148 163 184 / 0.22);
270
+ }
271
+ :where(.it-json-editor-gutter-row) {
272
+ display: flex;
273
+ align-items: flex-start;
274
+ justify-content: flex-end;
275
+ }
276
+ :where(.it-json-editor-fold) {
277
+ display: inline-flex;
278
+ align-items: center;
279
+ justify-content: center;
280
+ width: 16px;
281
+ height: 16px;
282
+ margin: 2px 1px 0 0;
283
+ border: 0;
284
+ border-radius: 4px;
285
+ background: transparent;
286
+ color: #64748b;
287
+ cursor: pointer;
288
+ }
289
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-fold) {
290
+ color: #94a3b8;
291
+ }
292
+ :where(.it-json-editor-fold:hover) {
293
+ background: rgb(148 163 184 / 0.25);
294
+ color: #1d4ed8;
295
+ }
296
+ :where(.it-json-editor-fold:focus-visible) {
297
+ outline: 2px solid var(--color-primary-500);
298
+ outline-offset: 1px;
299
+ }
300
+ :where(.it-json-editor-linenumber) {
301
+ padding-right: 10px;
302
+ padding-left: 10px;
303
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
304
+ font-size: 11px;
305
+ line-height: var(--it-json-editor-line);
306
+ text-align: right;
307
+ color: #94a3b8;
308
+ white-space: pre;
309
+ }
310
+ :where(.it-json-editor-linenumber--active) {
311
+ color: #1d4ed8;
312
+ font-weight: 600;
313
+ }
314
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-linenumber) {
315
+ color: #64748b;
316
+ }
317
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-linenumber--active) {
318
+ color: #7dd3fc;
319
+ }
320
+
321
+ /* Fondos de línea (cursor y error). */
322
+ :where(.it-json-editor-active-line) {
323
+ z-index: 0;
324
+ background: rgb(37 99 235 / 0.06);
325
+ }
326
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-active-line) {
327
+ background: rgb(148 197 253 / 0.07);
328
+ }
329
+ :where(.it-json-editor-error-line) {
330
+ z-index: 0;
331
+ background: rgb(244 63 94 / 0.12);
332
+ box-shadow: inset 2px 0 0 0 var(--color-danger-500);
333
+ }
334
+
335
+ /* Tokens del resaltado. */
336
+ :where(.it-json-editor-tk-key) {
337
+ color: #0369a1;
338
+ }
339
+ :where(.it-json-editor-tk-string) {
340
+ color: #047857;
341
+ }
342
+ :where(.it-json-editor-tk-number) {
343
+ color: #b45309;
344
+ }
345
+ :where(.it-json-editor-tk-keyword),
346
+ :where(.it-json-editor-tk-null) {
347
+ color: #7c3aed;
348
+ }
349
+ :where(.it-json-editor-tk-punct) {
350
+ color: #64748b;
351
+ }
352
+ :where(.it-json-editor-tk-plain) {
353
+ color: #b91c1c;
354
+ text-decoration: underline wavy rgb(185 28 28 / 0.5);
355
+ }
356
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-key) {
357
+ color: #7dd3fc;
358
+ }
359
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-string) {
360
+ color: #86efac;
361
+ }
362
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-number) {
363
+ color: #fcd34d;
364
+ }
365
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-keyword),
366
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-null) {
367
+ color: #c4b5fd;
368
+ }
369
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-punct) {
370
+ color: #94a3b8;
371
+ }
372
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-plain) {
373
+ color: #fda4af;
374
+ text-decoration: underline wavy rgb(253 164 175 / 0.5);
375
+ }
376
+ :where(.it-json-editor-fold-marker) {
377
+ margin-left: 6px;
378
+ padding: 0 4px;
379
+ border-radius: 4px;
380
+ background: rgb(148 163 184 / 0.2);
381
+ font-size: 10.5px;
382
+ color: #64748b;
383
+ }
384
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-fold-marker) {
385
+ color: #94a3b8;
386
+ }
387
+
388
+ /* Botonera de la barra superior. */
389
+ :where(.it-json-editor-tool) {
390
+ display: inline-flex;
391
+ align-items: center;
392
+ gap: 5px;
393
+ height: 24px;
394
+ padding: 0 8px;
395
+ border: 1px solid var(--color-secondary-200);
396
+ border-radius: 6px;
397
+ background: transparent;
398
+ font-family: "JetBrains Mono", ui-monospace, monospace;
399
+ font-size: 10px;
400
+ font-weight: 500;
401
+ letter-spacing: 0.06em;
402
+ text-transform: uppercase;
403
+ color: var(--color-secondary-600);
404
+ cursor: pointer;
405
+ transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
406
+ }
407
+ :where(.dark .it-json-editor-tool) {
408
+ border-color: var(--color-secondary-800);
409
+ color: var(--color-secondary-300);
410
+ }
411
+ :where(.it-json-editor-tool:hover:not(:disabled)) {
412
+ border-color: var(--color-primary-400);
413
+ background: var(--color-primary-50);
414
+ color: var(--color-primary-700);
415
+ }
416
+ :where(.dark .it-json-editor-tool:hover:not(:disabled)) {
417
+ border-color: var(--color-primary-500);
418
+ background: rgb(37 99 235 / 0.15);
419
+ color: var(--color-primary-300);
420
+ }
421
+ :where(.it-json-editor-tool:disabled) {
422
+ opacity: 0.4;
423
+ cursor: not-allowed;
424
+ }
425
+ :where(.it-json-editor-tool:focus-visible) {
426
+ outline: 2px solid var(--color-primary-500);
427
+ outline-offset: 1px;
428
+ }
429
+
430
+ /* Conmutador texto/árbol. */
431
+ :where(.it-json-editor-segment) {
432
+ display: inline-flex;
433
+ align-items: center;
434
+ border: 1px solid var(--color-secondary-200);
435
+ border-radius: 6px;
436
+ overflow: hidden;
437
+ }
438
+ :where(.dark .it-json-editor-segment) {
439
+ border-color: var(--color-secondary-800);
440
+ }
441
+ :where(.it-json-editor-segment-item) {
442
+ display: inline-flex;
443
+ align-items: center;
444
+ gap: 5px;
445
+ height: 24px;
446
+ padding: 0 8px;
447
+ border: 0;
448
+ background: transparent;
449
+ font-family: "JetBrains Mono", ui-monospace, monospace;
450
+ font-size: 10px;
451
+ font-weight: 500;
452
+ letter-spacing: 0.06em;
453
+ text-transform: uppercase;
454
+ color: var(--color-secondary-500);
455
+ cursor: pointer;
456
+ }
457
+ :where(.it-json-editor-segment-item--active) {
458
+ background: var(--color-primary-600);
459
+ color: #ffffff;
460
+ }
461
+ :where(.it-json-editor-segment-item:focus-visible) {
462
+ outline: 2px solid var(--color-primary-500);
463
+ outline-offset: -2px;
464
+ }
465
+
466
+ /* Interruptores de la fila de ajustes. */
467
+ :where(.it-json-editor-toggle) {
468
+ display: inline-flex;
469
+ align-items: center;
470
+ gap: 5px;
471
+ border: 0;
472
+ background: transparent;
473
+ font-family: "JetBrains Mono", ui-monospace, monospace;
474
+ font-size: 10px;
475
+ letter-spacing: 0.1em;
476
+ text-transform: uppercase;
477
+ color: var(--color-secondary-400);
478
+ cursor: pointer;
479
+ }
480
+ :where(.it-json-editor-toggle--on) {
481
+ color: var(--color-primary-700);
482
+ }
483
+ :where(.dark .it-json-editor-toggle--on) {
484
+ color: var(--color-primary-300);
485
+ }
486
+ :where(.it-json-editor-toggle-dot) {
487
+ display: inline-block;
488
+ width: 6px;
489
+ height: 6px;
490
+ border-radius: 999px;
491
+ background: currentColor;
492
+ opacity: 0.4;
493
+ }
494
+ :where(.it-json-editor-toggle--on .it-json-editor-toggle-dot) {
495
+ opacity: 1;
496
+ }
497
+ :where(.it-json-editor-toggle:focus-visible) {
498
+ outline: 2px solid var(--color-primary-500);
499
+ outline-offset: 1px;
500
+ }
501
+
502
+ /* Árbol navegable. */
503
+ :where(.it-json-editor-tree) {
504
+ height: 100%;
505
+ }
506
+ :where(.it-json-editor-tree-spacer) {
507
+ display: inline-block;
508
+ width: 14px;
509
+ flex-shrink: 0;
510
+ }
511
+ :where(.it-json-editor-tk-value) {
512
+ color: #334155;
513
+ }
514
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tk-value) {
515
+ color: #cbd5e1;
516
+ }
517
+ :where(.it-json-editor-tree-row) {
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 4px;
521
+ min-height: 20px;
522
+ font-family: "JetBrains Mono", ui-monospace, monospace;
523
+ font-size: 11.5px;
524
+ line-height: 1.5;
525
+ color: #334155;
526
+ }
527
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tree-row) {
528
+ color: #cbd5e1;
529
+ }
530
+ :where(.it-json-editor-tree-row:hover) {
531
+ background: rgb(148 163 184 / 0.12);
532
+ }
533
+ :where(.it-json-editor-tree-toggle) {
534
+ display: inline-flex;
535
+ align-items: center;
536
+ justify-content: center;
537
+ width: 14px;
538
+ height: 14px;
539
+ border: 0;
540
+ border-radius: 3px;
541
+ background: transparent;
542
+ color: #64748b;
543
+ cursor: pointer;
544
+ }
545
+ :where(.it-json-editor-tree-badge) {
546
+ padding: 0 5px;
547
+ border-radius: 999px;
548
+ background: rgb(148 163 184 / 0.18);
549
+ font-size: 9.5px;
550
+ letter-spacing: 0.04em;
551
+ text-transform: uppercase;
552
+ color: #64748b;
553
+ }
554
+ :where(.it-json-editor-canvas[data-canvas="ink"] .it-json-editor-tree-badge) {
555
+ color: #94a3b8;
556
+ }
557
+ :where(.it-json-editor-tree-jump) {
558
+ display: inline-flex;
559
+ align-items: center;
560
+ gap: 3px;
561
+ margin-left: auto;
562
+ padding: 0 5px;
563
+ border: 0;
564
+ border-radius: 4px;
565
+ background: transparent;
566
+ font-family: "JetBrains Mono", ui-monospace, monospace;
567
+ font-size: 9.5px;
568
+ letter-spacing: 0.06em;
569
+ text-transform: uppercase;
570
+ color: #64748b;
571
+ cursor: pointer;
572
+ opacity: 0;
573
+ }
574
+ :where(.it-json-editor-tree-row:hover .it-json-editor-tree-jump),
575
+ :where(.it-json-editor-tree-jump:focus-visible) {
576
+ opacity: 1;
577
+ }
578
+ :where(.it-json-editor-tree-jump:hover) {
579
+ background: var(--color-primary-50);
580
+ color: var(--color-primary-700);
581
+ }
582
+ :where(.dark .it-json-editor-tree-jump:hover) {
583
+ background: rgb(37 99 235 / 0.2);
584
+ color: var(--color-primary-300);
585
+ }
172
586
  }
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import { useDebouncedSearch } from "./hooks/useDebouncedSearch";
6
6
  import { useEditableRow } from "./hooks/useEditableRow";
7
7
  import { useFloatingPanel } from "./hooks/useFloatingPanel";
8
8
  import { useTableState, UseTableStateOptions, UseTableStateResult } from "./hooks/useTableState";
9
+ import useJsonRender from "./hooks/useJsonRender";
9
10
 
10
11
  // COMPONENTES
11
12
  import ITAccordion from "./components/molecules/accordion/accordion";
@@ -62,6 +63,8 @@ import ITTabs from "./components/molecules/tabs/tabs";
62
63
  import ITTripleFilter from "./components/organisms/triple-filter/tripleFilter";
63
64
  import ITToast from "./components/organisms/toast/toast";
64
65
  import ITWysiwyg from "./components/molecules/wysiwyg/wysiwyg";
66
+ import ITJsonRenderer from "./components/molecules/json-renderer/json-renderer";
67
+ import ITJSONEditor from "./components/molecules/json-editor/json-editor";
65
68
 
66
69
  // PROPS
67
70
  import type { ITAlertProps } from "./components/molecules/alert/alert.props";
@@ -118,6 +121,8 @@ import type { ITTabsProps, ITTabItem } from "./components/molecules/tabs/tabs.pr
118
121
  import type { ITTripleFilterProps, ITTripleFilterOption } from "./components/organisms/triple-filter/tripleFilter.props";
119
122
  import type { ITToastProps } from "./components/organisms/toast/toast.props";
120
123
  import type { ITWysiwygProps } from "./components/molecules/wysiwyg/wysiwyg.props";
124
+ import type { ITJsonRendererProps, JsonComponentNode, JsonComponentType } from "./components/molecules/json-renderer/json-renderer.props";
125
+ import type { ITJSONEditorProps } from "./components/molecules/json-editor/json-editor.props";
121
126
  import type { FieldConfig, FieldConfigV2 } from "./types/field.types";
122
127
  import type { ColumnFilters, ColumnFilterValue, TableColors, TableDensity, TableDesign, TableDesignProps, TableDesignSlot } from "./types/table.types";
123
128
 
@@ -144,7 +149,7 @@ import type { ITI18nProviderProps, ITI18nValue, ITLocale, ITMessages, ITMessages
144
149
 
145
150
  export {
146
151
  // Hooks
147
- useClickOutside, useDebouncedSearch, useEditableRow, useFloatingPanel, useTableState,
152
+ useClickOutside, useDebouncedSearch, useEditableRow, useFloatingPanel, useTableState, useJsonRender,
148
153
  createValidationSchema,
149
154
  // Components
150
155
  ITAccordion,
@@ -162,7 +167,8 @@ export {
162
167
  useITTheme, useITThemeSafe, useITFlatAppearance, ITTimePicker, ITToast, ITWysiwyg,
163
168
  UploadStatus, FileTypeEnum,
164
169
  getContrastTextColor, isLightColor, resolveCssColor,
165
- ITI18nProvider, useITTranslation, esMessages, enMessages
170
+ ITI18nProvider, useITTranslation, esMessages, enMessages,
171
+ ITJsonRenderer, ITJSONEditor
166
172
  };
167
173
 
168
174
  export type {
@@ -195,6 +201,8 @@ export {
195
201
  ITTripleFilterProps, ITTripleFilterOption, ITThemeConfig, ITThemeProviderProps, ITThemePalette, ITThemeAppearance,
196
202
  ITTimePickerProps, ITToastProps,
197
203
  ITWysiwygProps,
204
+ ITJsonRendererProps, JsonComponentNode, JsonComponentType,
205
+ ITJSONEditorProps,
198
206
  UseTableStateOptions, UseTableStateResult
199
207
  };
200
208
 
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Marca axzydev para el sitio (sandbox + portafolio).
3
+ *
4
+ * Vive en `src/sandbox/` a propósito: es chrome del sitio, **no** un componente
5
+ * publicado. Los assets son los SVG del kit (`axzydev-brand-kit/logos/svg`)
6
+ * copiados a `public/brand/`; no hay dependencias nuevas ni animaciones Lottie.
7
+ *
8
+ * El wordmark elige su variante según el tema resuelto: `logo-color` (tinta
9
+ * navy sobre claro) o `logo-dark` (texto claro sobre oscuro), de modo que el
10
+ * logo se lee en ambos modos sin recolorear nada a mano.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <AxzyLogo variante="icono" tamano={24} /> // navbar / topbar
15
+ * <AxzyLogo variante="wordmark" tamano={260} /> // hero / footer
16
+ * <AxzyLogo variante="loader" tamano={40} etiqueta="Cargando" />
17
+ * ```
18
+ */
19
+
20
+ import { useITThemeSafe } from "@/theme/theme-context";
21
+
22
+ /** Forma del logo: solo ícono, wordmark completo o loader de marca. */
23
+ export type AxzyLogoVariante = "icono" | "wordmark" | "loader";
24
+
25
+ /** Props de `AxzyLogo`. */
26
+ export interface AxzyLogoProps {
27
+ /** Forma del logo. @default "wordmark" */
28
+ variante?: AxzyLogoVariante;
29
+ /** Ancho en píxeles. El alto se calcula solo por la proporción del SVG. @default 28 (icono/loader) · 240 (wordmark) */
30
+ tamano?: number;
31
+ /** Fuerza el tono del wordmark en lugar de seguir el tema. */
32
+ tono?: "claro" | "oscuro";
33
+ /**
34
+ * Texto alternativo del logo. Por defecto `"axzydev"` para el wordmark y
35
+ * `""` (decorativo) para el ícono, que suele ir acompañado del nombre.
36
+ */
37
+ etiqueta?: string;
38
+ /** Clases del elemento raíz. */
39
+ className?: string;
40
+ /** Anima el ícono con un latido suave cuando `variante="loader"`. Respeta `prefers-reduced-motion`. */
41
+ latido?: boolean;
42
+ }
43
+
44
+ /** Ruta del asset según la forma y el tono. */
45
+ const ASSETS: Record<AxzyLogoVariante, { claro: string; oscuro: string }> = {
46
+ icono: { claro: "/brand/icon.svg", oscuro: "/brand/icon.svg" },
47
+ wordmark: { claro: "/brand/logo-color.svg", oscuro: "/brand/logo-dark.svg" },
48
+ loader: { claro: "/brand/icon.svg", oscuro: "/brand/icon.svg" },
49
+ };
50
+
51
+ /** Alto por defecto según la forma (el wordmark es 621×208). */
52
+ const ALTO_POR_TAMANO: Record<AxzyLogoVariante, number> = {
53
+ icono: 28,
54
+ wordmark: 240,
55
+ loader: 40,
56
+ };
57
+
58
+ /**
59
+ * Marca axzydev del sitio: SVG del kit, conmutada por tema y sin dependencias.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * <AxzyLogo variante="icono" tamano={24} />
64
+ * <AxzyLogo variante="wordmark" tamano={260} className="mb-6" />
65
+ * ```
66
+ */
67
+ export const AxzyLogo = ({
68
+ variante = "wordmark",
69
+ tamano,
70
+ tono,
71
+ etiqueta,
72
+ className = "",
73
+ latido = true,
74
+ }: AxzyLogoProps) => {
75
+ const contexto = useITThemeSafe();
76
+ const modo = tono ?? (contexto?.resolvedTheme === "dark" ? "oscuro" : "claro");
77
+ const ancho = tamano ?? ALTO_POR_TAMANO[variante];
78
+
79
+ // El ícono mide 128×128; el wordmark, 621×208. El alto sale de la proporción.
80
+ const alto = variante === "icono" || variante === "loader" ? ancho : Math.round((ancho * 208) / 621);
81
+ const decorativo = etiqueta === "" || (etiqueta === undefined && variante !== "wordmark");
82
+ const alt = etiqueta ?? (variante === "wordmark" ? "axzydev" : "");
83
+
84
+ return (
85
+ <img
86
+ src={ASSETS[variante][modo]}
87
+ width={ancho}
88
+ height={alto}
89
+ alt={alt}
90
+ aria-hidden={decorativo || undefined}
91
+ draggable={false}
92
+ className={[
93
+ "select-none",
94
+ variante === "loader" && latido ? "it-logo-latido" : "",
95
+ className,
96
+ ]
97
+ .filter(Boolean)
98
+ .join(" ")}
99
+ style={{ width: ancho, height: alto }}
100
+ />
101
+ );
102
+ };
103
+
104
+ /**
105
+ * Loader de marca: el ícono latiendo con un rótulo mono al lado.
106
+ *
107
+ * Es la versión estática del `loader` del kit (que allí es una animación Lottie)
108
+ * y se apoya en `ITLoader` para la etiqueta y el rol accesible.
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * <AxzyLoader etiqueta="Cargando componentes" />
113
+ * ```
114
+ */
115
+ export const AxzyLoader = ({
116
+ etiqueta,
117
+ tamano = 32,
118
+ className = "",
119
+ }: {
120
+ /** Texto que acompaña al ícono. */
121
+ etiqueta?: string;
122
+ /** Ancho del ícono en píxeles. @default 32 */
123
+ tamano?: number;
124
+ /** Clases del contenedor. */
125
+ className?: string;
126
+ }) => (
127
+ <span
128
+ role="status"
129
+ aria-live="polite"
130
+ className={`inline-flex items-center gap-3 ${className}`}
131
+ >
132
+ <AxzyLogo variante="loader" tamano={tamano} />
133
+ {etiqueta ? (
134
+ <span className="font-mono text-[11px] uppercase tracking-[0.14em] text-slate-500 dark:text-slate-400">
135
+ {etiqueta}
136
+ </span>
137
+ ) : null}
138
+ </span>
139
+ );
@@ -20,6 +20,7 @@ export const sandboxMessages: ITMessagesByLocale = {
20
20
  "sandbox.userMenu.portfolio": "Ir al Portafolio",
21
21
  "sandbox.search.label": "Buscar componentes",
22
22
  "sandbox.search.placeholder": "Buscar componente…",
23
+ "sandbox.loading": "Cargando…",
23
24
  "sandbox.search.clear": "Limpiar búsqueda",
24
25
  "sandbox.search.results": "{count} resultados",
25
26
 
@@ -967,6 +968,7 @@ export const sandboxMessages: ITMessagesByLocale = {
967
968
  "sandbox.userMenu.portfolio": "Go to portfolio",
968
969
  "sandbox.search.label": "Search components",
969
970
  "sandbox.search.placeholder": "Search component…",
971
+ "sandbox.loading": "Loading…",
970
972
  "sandbox.search.clear": "Clear search",
971
973
  "sandbox.search.results": "{count} results",
972
974
 
@@ -104,6 +104,8 @@ export const SANDBOX_GROUPS: SandboxGroup[] = [
104
104
  { id: "toast", label: "ITToast" },
105
105
  { id: "loader", label: "ITLoader" },
106
106
  { id: "themeprovider", label: "ITThemeProvider" },
107
+ { id: "jsonrenderer", label: "ITJsonRenderer (PoC)" },
108
+ { id: "jsoneditor", label: "ITJSONEditor" },
107
109
  ],
108
110
  },
109
111
  ];
@@ -15,6 +15,7 @@ import {
15
15
  import { PRESETS } from "../components/theme-provider/themePresets";
16
16
  import { ShowcaseLayout } from "./ShowcaseLayout";
17
17
  import { useITTranslation } from "@/i18n";
18
+ import { AxzyLoader, AxzyLogo } from "@/sandbox/AxzyLogo";
18
19
 
19
20
  // 1. ITDialog Showcase
20
21
  export const DialogShowcase = () => {
@@ -237,11 +238,21 @@ export const LoaderShowcase = () => {
237
238
  </>
238
239
  }
239
240
  gallery={
240
- <div className="flex flex-wrap items-center gap-6">
241
- <ITLoader variant="spinner" size="md" color="primary" />
242
- <ITLoader variant="dots" size="md" color="success" />
243
- <div className="w-32"><ITLoader variant="bar" size="md" color="danger" /></div>
244
- <ITLoader variant="pulse" size="md" color="purple" />
241
+ <div className="space-y-6">
242
+ <div className="flex flex-wrap items-center gap-6">
243
+ <ITLoader variant="spinner" size="md" color="primary" />
244
+ <ITLoader variant="dots" size="md" color="success" />
245
+ <div className="w-32"><ITLoader variant="bar" size="md" color="danger" /></div>
246
+ <ITLoader variant="pulse" size="md" color="purple" />
247
+ </div>
248
+
249
+ {/* Loader de marca: el ícono axzydev con un latido suave. En el kit la
250
+ versión animada es Lottie; aquí se usa el SVG estático. */}
251
+ <div className="flex flex-wrap items-center gap-8 border-t border-slate-200 pt-6 dark:border-slate-800">
252
+ <AxzyLoader etiqueta={t("sandbox.loading")} />
253
+ <AxzyLoader etiqueta={t("sandbox.loading")} tamano={24} />
254
+ <AxzyLogo variante="loader" tamano={56} etiqueta="axzydev" />
255
+ </div>
245
256
  </div>
246
257
  }
247
258
  doc={{