@axzydev/axzy_ui_system 1.6.0 → 1.7.1

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 (71) hide show
  1. package/dist/index.cjs +7842 -4676
  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 +883 -3
  6. package/dist/index.d.ts +883 -3
  7. package/dist/index.js +9118 -5971
  8. package/dist/index.js.map +1 -1
  9. package/dist/layered.css +1 -1
  10. package/package.json +14 -7
  11. package/src/App.tsx +70 -2
  12. package/src/components/atoms/image/image.props.ts +2 -0
  13. package/src/components/atoms/image/image.tsx +3 -2
  14. package/src/components/molecules/json-editor/json-editor-tree.tsx +235 -0
  15. package/src/components/molecules/json-editor/json-editor.index.ts +2 -0
  16. package/src/components/molecules/json-editor/json-editor.props.ts +82 -0
  17. package/src/components/molecules/json-editor/json-editor.tsx +972 -0
  18. package/src/components/molecules/json-editor/json-editor.utils.ts +717 -0
  19. package/src/components/molecules/json-editor/useJsonEditorKeyboard.ts +256 -0
  20. package/src/components/molecules/json-renderer/json-renderer.index.ts +2 -0
  21. package/src/components/molecules/json-renderer/json-renderer.props.ts +31 -0
  22. package/src/components/molecules/json-renderer/json-renderer.tsx +79 -0
  23. package/src/components/organisms/dialog/dialog.props.ts +2 -0
  24. package/src/components/organisms/dialog/dialog.tsx +4 -3
  25. package/src/dev.css +30 -1
  26. package/src/hooks/useJsonRender.tsx +9 -0
  27. package/src/i18n/locales/en.ts +54 -0
  28. package/src/i18n/locales/es.ts +54 -0
  29. package/src/index.css +414 -0
  30. package/src/index.ts +99 -2
  31. package/src/json-renderer/catalog/axzyCatalog.ts +362 -0
  32. package/src/json-renderer/catalog/catalog.props.ts +128 -0
  33. package/src/json-renderer/catalog/resolve.ts +225 -0
  34. package/src/json-renderer/index.ts +70 -0
  35. package/src/json-renderer/kernel/NodeBoundary.tsx +53 -0
  36. package/src/json-renderer/kernel/SchemaRenderer.tsx +197 -0
  37. package/src/json-renderer/kernel/kernel.test.tsx +341 -0
  38. package/src/json-renderer/kernel/partial.ts +92 -0
  39. package/src/json-renderer/kernel/renderNode.tsx +113 -0
  40. package/src/json-renderer/kernel/schemaRenderer.props.ts +42 -0
  41. package/src/json-renderer/kernel/types.ts +159 -0
  42. package/src/json-renderer/shared/errors.ts +92 -0
  43. package/src/json-renderer/shared/limits.ts +87 -0
  44. package/src/json-renderer/shared/paths.ts +66 -0
  45. package/src/json-renderer/shared/shared.test.ts +108 -0
  46. package/src/json-renderer/stream/tolerantParse.test.ts +152 -0
  47. package/src/json-renderer/stream/tolerantParse.ts +449 -0
  48. package/src/json-renderer/test/setup.ts +38 -0
  49. package/src/json-renderer/validation/migrate.ts +83 -0
  50. package/src/json-renderer/validation/validate.ts +161 -0
  51. package/src/json-renderer/validation/validation.test.ts +165 -0
  52. package/src/json-renderer/vocabulary/buildContract.ts +158 -0
  53. package/src/sandbox/AxzyLogo.tsx +139 -0
  54. package/src/sandbox/axzyConfig.test.ts +40 -0
  55. package/src/sandbox/axzyConfig.ts +77 -0
  56. package/src/sandbox/i18n.ts +220 -0
  57. package/src/sandbox/navigation.ts +3 -0
  58. package/src/sandbox/useHashRoute.ts +39 -9
  59. package/src/showcases/DemoJsonDashboard.tsx +489 -0
  60. package/src/showcases/DemoJsonDocs.tsx +339 -0
  61. package/src/showcases/DemoJsonPortfolio.tsx +257 -0
  62. package/src/showcases/FeedbackShowcases.tsx +16 -5
  63. package/src/showcases/HomeShowcase.tsx +16 -9
  64. package/src/showcases/JsonEditorShowcase.tsx +150 -0
  65. package/src/showcases/JsonRendererShowcase.tsx +70 -0
  66. package/src/showcases/JsonScreenShowcase.tsx +341 -0
  67. package/src/showcases/StructureShowcases.tsx +6 -1
  68. package/src/showcases/demoApp/demoData.ts +181 -0
  69. package/src/showcases/demoApp/nodeTree.test.ts +39 -0
  70. package/src/showcases/demoApp/nodeTree.ts +35 -0
  71. package/src/showcases/demoApp/schemas.test.ts +54 -0
@@ -27,6 +27,7 @@ import {
27
27
  VolverArriba,
28
28
  } from "./portfolioUi";
29
29
  import { useITTranslation } from "@/i18n";
30
+ import { AxzyLogo } from "@/sandbox/AxzyLogo";
30
31
 
31
32
  const CV_URL = "/Martin_Amaro_CV_ES_2026.pdf";
32
33
  const GITHUB_URL = "https://github.com/maag070208";
@@ -125,9 +126,7 @@ const PortfolioNav = () => {
125
126
  <nav aria-label={t("showcase.home.sectionNav")} className={HOJA}>
126
127
  <div className="flex h-14 items-center gap-4">
127
128
  <a href="#sobre-mi" className="flex items-center gap-2.5">
128
- <span className="grid h-6 w-6 place-items-center rounded-[6px] bg-primary-700 font-mono !text-[10px] !font-bold text-white">
129
- AA
130
- </span>
129
+ <AxzyLogo variante="icono" tamano={24} />
131
130
  <ITText
132
131
  as="span"
133
132
  className="hidden font-display !text-[13px] !font-bold -tracking-[0.01em] text-slate-900 dark:text-white sm:block"
@@ -441,6 +440,10 @@ export const HomeShowcase = () => {
441
440
  </a>
442
441
  </ITFlex>
443
442
 
443
+ <ITFlex align="center" className="it-reveal !mt-8">
444
+ <AxzyLogo variante="wordmark" tamano={300} />
445
+ </ITFlex>
446
+
444
447
  <div className="mt-10 grid gap-10 lg:grid-cols-[minmax(0,1fr)_auto] lg:gap-14">
445
448
  <div className="min-w-0">
446
449
  <ITText
@@ -783,12 +786,16 @@ export const HomeShowcase = () => {
783
786
  <div
784
787
  className={`${HOJA} flex flex-col items-start justify-between gap-3 py-7 sm:flex-row sm:items-center`}
785
788
  >
786
- <ITText
787
- as="p"
788
- className="font-mono !text-[11px] uppercase !tracking-[0.14em] text-slate-500 dark:text-slate-400"
789
- >
790
- {`Asael Amaro · ${t("showcase.home.role")}`}
791
- </ITText>
789
+ <div className="flex items-center gap-4">
790
+ <AxzyLogo variante="wordmark" tamano={190} />
791
+ <span aria-hidden="true" className="h-8 w-px bg-slate-200 dark:bg-slate-800" />
792
+ <ITText
793
+ as="p"
794
+ className="font-mono !text-[11px] uppercase !tracking-[0.14em] text-slate-500 dark:text-slate-400"
795
+ >
796
+ {`Asael Amaro · ${t("showcase.home.role")}`}
797
+ </ITText>
798
+ </div>
792
799
  <div className="flex flex-wrap items-center gap-x-5 gap-y-2">
793
800
  <ITText
794
801
  as="span"
@@ -0,0 +1,150 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import { ITCard, ITJSONEditor, ITFlex } from "../index";
3
+ import { ShowcaseLayout } from "./ShowcaseLayout";
4
+
5
+ /** Documento de arranque de la demo. */
6
+ const initial = `[
7
+ {
8
+ "type": "card",
9
+ "props": { "title": "Demo JSON Editor" },
10
+ "children": [
11
+ { "type": "text", "text": "Edita este JSON y mira el preview" },
12
+ { "type": "button", "props": { "label": "Aceptar" } }
13
+ ]
14
+ }
15
+ ]`;
16
+
17
+ export const JsonEditorShowcase: React.FC = () => {
18
+ const [value, setValue] = useState(initial);
19
+
20
+ /** Un documento más grande para que luzcan el plegado y el árbol. */
21
+ const bigDocument = useMemo(
22
+ () =>
23
+ JSON.stringify(
24
+ {
25
+ app: "axzy-ui-system",
26
+ version: "1.6.0",
27
+ layers: {
28
+ atoms: ["button", "input", "text", "flex", "grid", "stack"],
29
+ molecules: ["card", "alert", "table", "json-editor"],
30
+ organisms: ["data-table", "dialog", "sidebar", "navbar"],
31
+ },
32
+ flags: { darkMode: true, i18n: true, a11y: true, treeView: true },
33
+ limits: { maxDepth: 12, maxNodes: 5000, timeout: null },
34
+ },
35
+ null,
36
+ 2
37
+ ),
38
+ []
39
+ );
40
+
41
+ const code = `<ITJSONEditor
42
+ value={json}
43
+ onChange={setJson}
44
+ onParse={handleParse}
45
+ mode="text" // "text" | "tree"
46
+ indentSize={2} // 0 | 2 | 4 | 8
47
+ height={420}
48
+ showToolbar
49
+ showViewSettings
50
+ showValidation
51
+ showStatusBar
52
+ showFoldGutter
53
+ showLineNumbers
54
+ />`;
55
+
56
+ return (
57
+ <ShowcaseLayout
58
+ title="ITJSONEditor"
59
+ description="Editor JSON con resaltado, gutter plegable, errores por línea/columna y árbol navegable. Sin dependencias nuevas."
60
+ demo={
61
+ <ITFlex direction="column" gap={4} className="w-full">
62
+ <ITJSONEditor id="json-editor-demo" value={value} onChange={setValue} height={420} indentSize={2} />
63
+ </ITFlex>
64
+ }
65
+ gallery={
66
+ <ITFlex direction="column" gap={4} className="w-full">
67
+ <ITCard title="Vista de árbol (mode=&quot;tree&quot;)">
68
+ <ITJSONEditor
69
+ value={bigDocument}
70
+ readOnly
71
+ mode="tree"
72
+ treeShowLines
73
+ height={320}
74
+ showToolbar={false}
75
+ showViewSettings={false}
76
+ />
77
+ </ITCard>
78
+ <ITCard title="Documento grande con plegado">
79
+ <ITJSONEditor value={bigDocument} onChange={() => undefined} height={320} />
80
+ </ITCard>
81
+ <ITCard title="Lienzo claro y ajuste de línea (canvasTheme=&quot;light&quot;, wrapLines)">
82
+ <ITJSONEditor
83
+ value={bigDocument}
84
+ readOnly
85
+ canvasTheme="light"
86
+ wrapLines
87
+ height={260}
88
+ showTransformActions={false}
89
+ showImportButton={false}
90
+ />
91
+ </ITCard>
92
+ <ITCard title="Estado de error: línea y columna señaladas">
93
+ <ITJSONEditor
94
+ value={'{\n "a": 1,\n "b": ,\n "c": 3\n}'}
95
+ height={220}
96
+ showStatusBar
97
+ />
98
+ </ITCard>
99
+ </ITFlex>
100
+ }
101
+ code={code}
102
+ doc={{
103
+ summary: "Editor JSON para prototipar con ITJsonRenderer",
104
+ description:
105
+ "El resaltado se pinta en una capa bajo un textarea nativo, así que conserva el deshacer del navegador y no añade dependencias. Valida con JSON.parse y traduce el error a línea y columna.",
106
+ examples: [
107
+ '<ITJSONEditor value={json} onChange={setJson} />',
108
+ '<ITJSONEditor value={json} mode="tree" treeShowLines />',
109
+ '<ITJSONEditor value={json} indentSize={4} wrapLines canvasTheme="light" />',
110
+ '<ITJSONEditor value={json} readOnly showToolbar={false} height="100%" />',
111
+ ],
112
+ props: [
113
+ { name: "value", type: "string", default: '""', description: "Documento JSON como cadena." },
114
+ { name: "onChange", type: "(v: string) => void", default: "—", description: "Se dispara con el texto completo en cada cambio." },
115
+ { name: "onParse", type: "(parsed, isValid, error?) => void", default: "—", description: "Valor parseado y estado tras cada validación." },
116
+ { name: "mode", type: '"text" | "tree"', default: '"text"', description: "Modo inicial del lienzo." },
117
+ { name: "showModeSwitch", type: "boolean", default: "true", description: "Conmutador texto/árbol en la barra." },
118
+ { name: "showToolbar", type: "boolean", default: "true", description: "Barra superior de acciones." },
119
+ { name: "showViewSettings", type: "boolean", default: "true", description: "Fila de ajustes de vista." },
120
+ { name: "showValidation", type: "boolean", default: "true", description: "Indicador válido/ inválido con L:C." },
121
+ { name: "showStatusBar", type: "boolean", default: "true", description: "Pie con líneas, nodos, profundidad y tamaño." },
122
+ { name: "showLineNumbers", type: "boolean", default: "true", description: "Números de línea en el gutter." },
123
+ { name: "showFoldGutter", type: "boolean", default: "true", description: "Gutter de plegado de bloques." },
124
+ { name: "showTransformActions", type: "boolean", default: "true", description: "Formatear, minificar, ordenar, escapar." },
125
+ { name: "showCopyButton", type: "boolean", default: "true", description: "Botón de copiar el documento." },
126
+ { name: "showImportButton", type: "boolean", default: "true", description: "Carga de un archivo .json." },
127
+ { name: "showDownloadButton", type: "boolean", default: "true", description: "Descarga del documento." },
128
+ { name: "indentSize", type: "number", default: "2", description: "Espacios por nivel (0, 2, 4 u 8)." },
129
+ { name: "wrapLines", type: "boolean", default: "false", description: "Ajuste de línea dentro del ancho." },
130
+ { name: "treeShowLines", type: "boolean", default: "false", description: "Muestra la línea de cada nodo y permite saltar." },
131
+ { name: "canvasTheme", type: '"ink" | "light"', default: '"ink"', description: "Paleta del lienzo de código." },
132
+ { name: "height", type: "number | string", default: "340", description: "Alto del área de edición." },
133
+ { name: "readOnly", type: "boolean", default: "false", description: "Solo lectura, sin acciones de edición." },
134
+ { name: "actions", type: "ReactNode", default: "—", description: "Acciones propias al final de la barra." },
135
+ { name: "className", type: "string", default: "—", description: "Clases del contenedor raíz." },
136
+ { name: "textareaClassName", type: "string", default: "—", description: "Clases extra del textarea." },
137
+ { name: "errorLine", type: "number", default: "—", description: "Línea a marcar como errónea desde fuera." },
138
+ ],
139
+ notes: [
140
+ "Atajos: Tab indenta el bloque, Mayús+Tab lo desangra, Mayús+Alt+F formatea, +M minifica y +S ordena.",
141
+ "Las llaves, corchetes y comillas se cierran solos y se borran por parejas.",
142
+ "Con wrapLines activo el resaltado de línea vigente se desactiva: la posición dependería de las vueltas de cada línea.",
143
+ "Es totalmente independiente de ITJsonRenderer: los une solo el JSON que tú les pases.",
144
+ ],
145
+ }}
146
+ />
147
+ );
148
+ };
149
+
150
+ export default JsonEditorShowcase;
@@ -0,0 +1,70 @@
1
+ import React, { useState } from "react";
2
+ import { ITCard, ITJsonRenderer } from "../index";
3
+ import { ShowcaseLayout } from "./ShowcaseLayout";
4
+
5
+ const exampleWithChildren = [
6
+ {
7
+ type: "alert" as const,
8
+ props: { variant: "info" as const, title: "Información" },
9
+ text: "Esto se renderiza desde JSON",
10
+ },
11
+ {
12
+ type: "card" as const,
13
+ props: { title: "Tarjeta desde JSON" },
14
+ children: [
15
+ { type: "text" as const, text: "Contenido dentro de la tarjeta" },
16
+ {
17
+ type: "flex" as const,
18
+ props: { gap: 2, justify: "end" as const },
19
+ children: [
20
+ { type: "button" as const, props: { label: "Cancelar", color: "gray" as const, variant: "outlined" as const } },
21
+ { type: "button" as const, props: { label: "Aceptar", color: "primary" as const } },
22
+ ],
23
+ },
24
+ ],
25
+ },
26
+ ];
27
+
28
+ export const JsonRendererShowcase: React.FC = () => {
29
+ const [jsonText, setJsonText] = useState(JSON.stringify(exampleWithChildren, null, 2));
30
+
31
+ let parsed: any = exampleWithChildren;
32
+ try {
33
+ parsed = JSON.parse(jsonText);
34
+ } catch {
35
+ parsed = exampleWithChildren;
36
+ }
37
+
38
+ return (
39
+ <ShowcaseLayout
40
+ title="ITJsonRenderer"
41
+ description="Renderiza componentes del UI System a partir de un JSON."
42
+ doc={{
43
+ summary: "PoC: Builder JSON para AXZY UI System",
44
+ description:
45
+ "Permite construir interfaces pasándole un árbol JSON con type, props, children/text.",
46
+ examples: [
47
+ '<ITJsonRenderer json={{ type: "button", props: { label: "Click" } }} />',
48
+ ],
49
+ props: [],
50
+ }}
51
+ demo={
52
+ <ITCard title="Preview">
53
+ <ITJsonRenderer json={parsed} />
54
+ </ITCard>
55
+ }
56
+ code={jsonText}
57
+ controls={
58
+ <ITCard title="JSON Editor (editable)">
59
+ <textarea
60
+ value={jsonText}
61
+ onChange={(e) => setJsonText(e.target.value)}
62
+ className="w-full h-64 font-mono text-sm border rounded p-3"
63
+ />
64
+ </ITCard>
65
+ }
66
+ />
67
+ );
68
+ };
69
+
70
+ export default JsonRendererShowcase;
@@ -0,0 +1,341 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { FaBolt, FaBug, FaCheck, FaTrashAlt } from "react-icons/fa";
3
+ import {
4
+ ITAlert,
5
+ ITBadget,
6
+ ITButton,
7
+ ITCard,
8
+ ITFlex,
9
+ ITJSONEditor,
10
+ ITSchemaRenderer,
11
+ ITText,
12
+ type JsonEngineIssue,
13
+ type ScreenSchema,
14
+ parsePartialSchema,
15
+ axzyCatalogTypes,
16
+ } from "../index";
17
+ import { ShowcaseLayout } from "./ShowcaseLayout";
18
+
19
+ /** Esquema de ejemplo: usa el catálogo real y muestra permisos y estado. */
20
+ const EJEMPLO: ScreenSchema = {
21
+ version: 1,
22
+ id: "laboratorio",
23
+ title: "Pantalla de ejemplo",
24
+ content: [
25
+ {
26
+ type: "ITFlex",
27
+ props: { align: "center", justify: "between", wrap: "wrap", gap: 3 },
28
+ children: [
29
+ {
30
+ type: "ITFlex",
31
+ props: { align: "center", gap: 2 },
32
+ children: [
33
+ { type: "ITBadget", props: { label: "Motor", color: "primary" } },
34
+ { type: "ITText", props: { muted: true }, text: "Esquema interpretado en vivo" },
35
+ ],
36
+ },
37
+ {
38
+ type: "ITButton",
39
+ props: { label: "Acción declarada", color: "primary", size: "sm", onClick: { action: "saludar" } },
40
+ },
41
+ ],
42
+ },
43
+ {
44
+ type: "ITGrid",
45
+ props: { container: true, spacing: 3 },
46
+ children: [
47
+ {
48
+ type: "ITGrid",
49
+ props: { item: true, md: 6 },
50
+ children: [
51
+ {
52
+ type: "ITCard",
53
+ props: { title: "Listo para pintar" },
54
+ children: [
55
+ { type: "ITText", text: "Este árbol viene de un JSON, no de TSX." },
56
+ { type: "ITProgress", props: { value: 64, max: 100, size: "sm" } },
57
+ ],
58
+ },
59
+ ],
60
+ },
61
+ {
62
+ type: "ITGrid",
63
+ props: { item: true, md: 6 },
64
+ children: [
65
+ {
66
+ type: "ITAlert",
67
+ props: { variant: "info", title: "Casos cubiertos" },
68
+ children: [{ type: "ITText", text: "Si algo no está en el catálogo, se avisa y no se pinta." }],
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ },
74
+ ],
75
+ };
76
+
77
+ /** Texto inicial del editor: el esquema anterior, formateado. */
78
+ const TEXTO_INICIAL = JSON.stringify(EJEMPLO, null, 2);
79
+
80
+ /** Velocidad del stream simulado (caracteres por tick). */
81
+ const CHUNK = 6;
82
+
83
+ /**
84
+ * Laboratorio del motor de pantallas declarativas.
85
+ *
86
+ * Izquierda: el esquema en `ITJSONEditor`. Derecha: lo que pinta el motor. El
87
+ * botón «emitir como stream» escribe el esquema carácter a carácter para ver el
88
+ * render progresivo y comprobar que lo pendiente no se pinta a medias.
89
+ *
90
+ * @example
91
+ * // Ruta del sandbox: #ui-system/feedback/jsonscreen
92
+ */
93
+ export const JsonScreenShowcase: React.FC = () => {
94
+ const [texto, setTexto] = useState(TEXTO_INICIAL);
95
+ const [streaming, setStreaming] = useState(false);
96
+ const [emitido, setEmitido] = useState(TEXTO_INICIAL.length);
97
+ const [avisos, setAvisos] = useState<JsonEngineIssue[]>([]);
98
+ const timer = useRef<number | null>(null);
99
+
100
+ /** Estado del esquema: mientras se emite, se parsea en modo tolerante. */
101
+ const estado = useMemo(() => {
102
+ if (!streaming) {
103
+ try {
104
+ const completo = JSON.parse(texto) as ScreenSchema;
105
+ return { tree: completo, status: "complete" as const, pending: 0, roto: false };
106
+ } catch {
107
+ return { tree: null, status: "complete" as const, pending: 0, roto: true };
108
+ }
109
+ }
110
+ const parcial = parsePartialSchema(texto);
111
+ return {
112
+ tree: parcial.tree,
113
+ status: parcial.status,
114
+ pending: parcial.pendingNodes,
115
+ roto: parcial.broken,
116
+ };
117
+ }, [texto, streaming]);
118
+
119
+ /** Detiene el stream y limpia el temporizador. */
120
+ const detener = useCallback(() => {
121
+ if (timer.current !== null) {
122
+ window.clearInterval(timer.current);
123
+ timer.current = null;
124
+ }
125
+ setStreaming(false);
126
+ }, []);
127
+
128
+ useEffect(() => () => detener(), [detener]);
129
+
130
+ /** Emite el esquema carácter a carácter, como haría un modelo. */
131
+ const emitirStream = useCallback(() => {
132
+ detener();
133
+ setAvisos([]);
134
+ setEmitido(0);
135
+ setTexto("");
136
+ setStreaming(true);
137
+
138
+ const total = TEXTO_INICIAL;
139
+ let cursor = 0;
140
+
141
+ timer.current = window.setInterval(() => {
142
+ cursor = Math.min(total.length, cursor + CHUNK);
143
+ setTexto(total.slice(0, cursor));
144
+ setEmitido(cursor);
145
+ if (cursor >= total.length) {
146
+ window.clearInterval(timer.current as number);
147
+ timer.current = null;
148
+ setStreaming(false);
149
+ }
150
+ }, 24);
151
+ }, [detener]);
152
+
153
+ /** Registro de acciones del host: el esquema solo puede pedir estos nombres. */
154
+ const resolveAction = useCallback(
155
+ () => () => {
156
+ window.alert("Acción declarada ejecutada por el host.");
157
+ },
158
+ []
159
+ );
160
+
161
+ const registrarAviso = useCallback((issue: JsonEngineIssue) => {
162
+ setAvisos((previos) =>
163
+ previos.some((item) => item.code === issue.code && item.path === issue.path)
164
+ ? previos
165
+ : [...previos, issue].slice(-8)
166
+ );
167
+ }, []);
168
+
169
+ const codigo = `<ITSchemaRenderer
170
+ schema={parsePartialSchema(texto).tree}
171
+ onIssue={(issue) => registrar(issue)}
172
+ resolveAction={() => actions[accion]}
173
+ pendingFallback={<ITSkeleton />}
174
+ />`;
175
+
176
+ return (
177
+ <ShowcaseLayout
178
+ title="Motor de pantallas"
179
+ description="ITSchemaRenderer interpreta un esquema JSON contra un catálogo de componentes con lista blanca. Pinta mientras el esquema llega por trozos, avisa con código y ruta, y aísla los nodos que fallan."
180
+ demo={
181
+ <div className="grid w-full gap-4 xl:grid-cols-[minmax(0,380px)_minmax(0,1fr)]">
182
+ <div className="min-w-0">
183
+ <ITCard title="Esquema (JSON)">
184
+ <ITJSONEditor value={texto} onChange={setTexto} height={420} readOnly={streaming} />
185
+ </ITCard>
186
+ </div>
187
+ <div className="min-w-0">
188
+ <ITCard title="Pantalla resultante">
189
+ <div className="min-h-[420px]">
190
+ {estado.roto && !estado.tree ? (
191
+ <ITAlert variant="error" title="El esquema no es JSON válido">
192
+ Corrige el texto de la izquierda para volver a pintar.
193
+ </ITAlert>
194
+ ) : (
195
+ <ITSchemaRenderer
196
+ schema={estado.tree}
197
+ onIssue={registrarAviso}
198
+ resolveAction={resolveAction}
199
+ pendingFallback={<ITText muted>·</ITText>}
200
+ />
201
+ )}
202
+ </div>
203
+
204
+ <ITFlex align="center" gap={2} wrap="wrap" className="mt-4 border-t border-slate-200 pt-3 dark:border-slate-800">
205
+ <ITButton
206
+ label={streaming ? "Emitiendo…" : "Emitir como stream"}
207
+ icon={<FaBolt size={10} />}
208
+ size="sm"
209
+ color="primary"
210
+ onClick={emitirStream}
211
+ disabled={streaming}
212
+ />
213
+ <ITButton
214
+ label="Documentación del motor"
215
+ icon={<FaBolt size={10} />}
216
+ size="sm"
217
+ variant="text"
218
+ color="gray"
219
+ onClick={() => {
220
+ window.location.hash = "#demo/app/docs";
221
+ }}
222
+ />
223
+ <ITButton
224
+ label="Ver panel (sistema completo)"
225
+ icon={<FaBolt size={10} />}
226
+ size="sm"
227
+ color="primary"
228
+ onClick={() => {
229
+ window.location.hash = "#demo/app/inicio";
230
+ }}
231
+ />
232
+ <ITButton
233
+ label="Ver portafolio de demo"
234
+ icon={<FaBolt size={10} />}
235
+ size="sm"
236
+ variant="outlined"
237
+ color="gray"
238
+ onClick={() => {
239
+ window.location.hash = "#demo";
240
+ }}
241
+ />
242
+ <ITButton
243
+ label="Reiniciar"
244
+ icon={<FaTrashAlt size={10} />}
245
+ size="sm"
246
+ variant="outlined"
247
+ color="gray"
248
+ onClick={() => {
249
+ detener();
250
+ setTexto(TEXTO_INICIAL);
251
+ setEmitido(TEXTO_INICIAL.length);
252
+ setAvisos([]);
253
+ }}
254
+ />
255
+ <ITText
256
+ as="span"
257
+ className="font-mono !text-[10px] uppercase !tracking-[0.12em]"
258
+ muted
259
+ >
260
+ {streaming
261
+ ? `stream ${emitido}/${TEXTO_INICIAL.length} · ${estado.pending} nodos pendientes`
262
+ : `esquema completo · ${axzyCatalogTypes.length} tipos en el catálogo`}
263
+ </ITText>
264
+ </ITFlex>
265
+
266
+ {avisos.length > 0 && (
267
+ <div className="mt-3 space-y-1.5">
268
+ <ITFlex align="center" gap={1.5}>
269
+ <FaBug size={10} className="text-amber-500" />
270
+ <ITText
271
+ as="span"
272
+ className="font-mono !text-[10px] uppercase !tracking-[0.12em]"
273
+ muted
274
+ >
275
+ Avisos del motor
276
+ </ITText>
277
+ </ITFlex>
278
+ {avisos.map((aviso) => (
279
+ <ITFlex
280
+ key={`${aviso.code}-${aviso.path}`}
281
+ align="start"
282
+ gap={2}
283
+ className="rounded-[6px] border border-slate-200 px-2 py-1.5 dark:border-slate-800"
284
+ >
285
+ <ITBadget
286
+ label={aviso.code}
287
+ color={aviso.severity === "error" ? "danger" : "warning"}
288
+ size="sm"
289
+ />
290
+ <div className="min-w-0">
291
+ <p className="text-[11px] leading-snug text-slate-700 dark:text-slate-300">{aviso.message}</p>
292
+ <p className="font-mono text-[10px] text-slate-500 dark:text-slate-400">{aviso.path || "raíz"}</p>
293
+ </div>
294
+ </ITFlex>
295
+ ))}
296
+ </div>
297
+ )}
298
+
299
+ {!streaming && avisos.length === 0 && (
300
+ <ITFlex align="center" gap={1.5} className="mt-3">
301
+ <FaCheck size={10} className="text-emerald-500" />
302
+ <ITText as="span" className="font-mono !text-[10px] uppercase !tracking-[0.12em]" muted>
303
+ Sin avisos: el esquema cabe entero en el catálogo
304
+ </ITText>
305
+ </ITFlex>
306
+ )}
307
+ </ITCard>
308
+ </div>
309
+ </div>
310
+ }
311
+ code={codigo}
312
+ doc={{
313
+ summary: "Ejecuta un esquema JSON contra el catálogo de componentes, con lista blanca y avisos.",
314
+ description:
315
+ "El esquema se trata como entrada no confiable: un tipo fuera del catálogo no rompe la pantalla, una prop prohibida se ignora con aviso y un componente que lanza queda aislado. Durante el stream, los nodos pendientes no se pintan a medias.",
316
+ examples: [
317
+ '<ITSchemaRenderer schema={{ version: 1, id: "demo", content: { type: "ITText", text: "Hola" } }} />',
318
+ '<ITSchemaRenderer schema={parsePartialSchema(tokens).tree} pendingFallback={<ITSkeleton />} />',
319
+ '<ITSchemaRenderer schema={schema} onIssue={(issue) => console.warn(issue.code, issue.path)} />',
320
+ ],
321
+ props: [
322
+ { name: "schema", type: "ScreenSchema | null", required: true, description: "Esquema de pantalla; admite árbol parcial." },
323
+ { name: "catalog", type: "Catalog", default: "axzyCatalog", description: "Componentes y props permitidos." },
324
+ { name: "limits", type: "Partial<EngineLimits>", default: "—", description: "Presupuesto de nodos, profundidad y tiempos." },
325
+ { name: "resolveValue", type: "(value, path) => unknown", default: "—", description: "Resuelve referencias ($state, $param…)." },
326
+ { name: "resolveAction", type: "(node, prop, action, path) => fn", default: "—", description: "Traduce acciones declaradas a handlers del host." },
327
+ { name: "pendingFallback", type: "ReactNode", default: "—", description: "Qué pintar mientras un nodo está pendiente." },
328
+ { name: "onIssue", type: "(issue) => void", default: "—", description: "Avisos con código y ruta." },
329
+ { name: "className", type: "string", default: "—", description: "Clases del contenedor raíz." },
330
+ ],
331
+ notes: [
332
+ `El catálogo de esta fase expone ${axzyCatalogTypes.length} tipos con lista blanca de props.`,
333
+ "Códigos de aviso: JR010 tipo fuera del catálogo · JR011 prop ignorada · JR012 nodo aislado · JR013 presupuesto excedido.",
334
+ "Pulsa «Emitir como stream» para ver el render progresivo: lo pendiente no se pinta a medias.",
335
+ ],
336
+ }}
337
+ />
338
+ );
339
+ };
340
+
341
+ export default JsonScreenShowcase;
@@ -37,6 +37,7 @@ import type { ITSidebarProps } from "../index";
37
37
  import { ShowcaseLayout, CodeViewer } from "./ShowcaseLayout";
38
38
  import { EjemploContenedor } from "@/sandbox/ui";
39
39
  import { useITTranslation } from "@/i18n";
40
+ import { AxzyLogo } from "@/sandbox/AxzyLogo";
40
41
 
41
42
  // ─────────────────────────────────────────
42
43
  // 1. ITCard Showcase
@@ -972,7 +973,7 @@ export const LayoutShowcase = () => {
972
973
  const [navActiveId, setNavActiveId] = useState<string | null>("1");
973
974
 
974
975
  const topBar = {
975
- logo: <div className="w-9 h-9 rounded-lg bg-primary-500 flex items-center justify-center text-white font-bold">A</div>,
976
+ logo: <AxzyLogo variante="icono" tamano={36} etiqueta="" />,
976
977
  logoText: "AXZY Console",
977
978
  centerContent: (
978
979
  <input
@@ -1155,6 +1156,7 @@ export const LayoutShowcase = () => {
1155
1156
  numero="02"
1156
1157
  titulo={t("showcase.structure.galleryNavbarStandalone")}
1157
1158
  codigo={`<ITNavbar
1159
+ logo={<AxzyLogo variante="icono" tamano={28} etiqueta="" />}
1158
1160
  logoText="AXZY"
1159
1161
  navigationItems={[
1160
1162
  { id: "1", label: "Inicio", icon: <FaHome />, isActive: true },
@@ -1169,6 +1171,7 @@ export const LayoutShowcase = () => {
1169
1171
  >
1170
1172
  <div className="h-[380px] overflow-hidden [&>div]:!h-full">
1171
1173
  <ITNavbar
1174
+ logo={<AxzyLogo variante="icono" tamano={28} etiqueta="" />}
1172
1175
  logoText="AXZY"
1173
1176
  navigationItems={navbarItems}
1174
1177
  userMenu={{
@@ -1197,6 +1200,7 @@ export const LayoutShowcase = () => {
1197
1200
  numero="03"
1198
1201
  titulo={t("showcase.structure.galleryNavbarSubmenus")}
1199
1202
  codigo={`<ITNavbar
1203
+ logo={<AxzyLogo variante="icono" tamano={28} etiqueta="" />}
1200
1204
  logoText="Admin"
1201
1205
  navigationItems={[
1202
1206
  { id: "u", label: "Usuarios", icon: <FaUsers /> },
@@ -1215,6 +1219,7 @@ export const LayoutShowcase = () => {
1215
1219
  >
1216
1220
  <div className="h-[380px] overflow-hidden [&>div]:!h-full">
1217
1221
  <ITNavbar
1222
+ logo={<AxzyLogo variante="icono" tamano={28} etiqueta="" />}
1218
1223
  logoText="Admin"
1219
1224
  navigationItems={[
1220
1225
  { id: "u", label: t("showcase.structure.navUsers"), icon: <FaUsers /> },