@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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "packageManager": "pnpm@10.34.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
- "version": "1.6.0",
7
+ "version": "1.7.0",
8
8
  "type": "module",
9
9
  "sideEffects": [
10
10
  "*.css"
package/src/App.tsx CHANGED
@@ -65,6 +65,8 @@ import {
65
65
  LoaderShowcase,
66
66
  ThemeProviderShowcase,
67
67
  } from "./showcases/FeedbackShowcases";
68
+ import JsonRendererShowcase from "./showcases/JsonRendererShowcase";
69
+ import JsonEditorShowcase from "./showcases/JsonEditorShowcase";
68
70
 
69
71
  // Sandbox routing / landing (dev-only, never published)
70
72
  import {
@@ -75,6 +77,7 @@ import { useHashRoute } from "./sandbox/useHashRoute";
75
77
  import TopbarSearch from "./sandbox/TopbarSearch";
76
78
  import { LandingShowcase } from "./sandbox/LandingShowcase";
77
79
  import NotFoundShowcase from "./sandbox/NotFoundShowcase";
80
+ import { AxzyLogo } from "./sandbox/AxzyLogo";
78
81
  import { sandboxMessages, translateLabel } from "./sandbox/i18n";
79
82
  import { SandboxLocaleProvider, useSandboxLocale } from "./sandbox/locale";
80
83
  import { SelectorIdioma, SelectorTema } from "./sandbox/controls";
@@ -172,6 +175,9 @@ function AppShell() {
172
175
  };
173
176
 
174
177
  const topBarProps = {
178
+ // La marca axzydev sustituye al nombre suelto: el wordmark ya dice a quién
179
+ // pertenece el catálogo y el texto se mantiene como respaldo accesible.
180
+ logo: <AxzyLogo variante="icono" tamano={22} />,
175
181
  logoText: "AXZY UI System",
176
182
  centerContent: (
177
183
  <TopbarSearch
@@ -309,6 +315,10 @@ function AppShell() {
309
315
  return <LoaderShowcase />;
310
316
  case "themeprovider":
311
317
  return <ThemeProviderShowcase />;
318
+ case "jsonrenderer":
319
+ return <JsonRendererShowcase />;
320
+ case "jsoneditor":
321
+ return <JsonEditorShowcase />;
312
322
  default:
313
323
  return (
314
324
  <NotFoundShowcase
@@ -0,0 +1,235 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import clsx from "clsx";
3
+ import { FaChevronDown, FaChevronRight, FaCrosshairs } from "react-icons/fa";
4
+ import { useITTranslation } from "@/i18n";
5
+ import { pathKey, previewValue, valueKind, type ITCodeAnalysis } from "./json-editor.utils";
6
+
7
+ /** Un nodo del árbol ya resuelto, listo para pintar. */
8
+ interface TreeNode {
9
+ /** Clave o índice con el que se muestra (vacío en la raíz). */
10
+ label: string;
11
+ /** Ruta absoluta dentro del documento, como clave estable. */
12
+ key: string;
13
+ /** Ruta absoluta troceada (evita re-parsear la clave: las claves pueden llevar puntos). */
14
+ path: (string | number)[];
15
+ /** Profundidad de anidación (la raíz es 0). */
16
+ level: number;
17
+ /** Valor del nodo. */
18
+ value: unknown;
19
+ /** Tipo del valor. */
20
+ kind: ReturnType<typeof valueKind>;
21
+ /** Número de hijos directos. */
22
+ childCount: number;
23
+ /** Hijos, solo en objetos y arrays. */
24
+ children: TreeNode[];
25
+ }
26
+
27
+ /** Props de `ITCodeTreeView`. */
28
+ export interface ITCodeTreeViewProps {
29
+ /** Valor ya parseado que hay que representar. */
30
+ value: unknown;
31
+ /** Análisis del texto, para poder saltar a la línea de cada nodo. */
32
+ analysis: ITCodeAnalysis;
33
+ /** Muestra la línea de cada nodo y permite saltar a ella. @default false */
34
+ showLines?: boolean;
35
+ /** Se dispara al pulsar el botón de salto de un nodo. */
36
+ onJumpToLine?: (line: number) => void;
37
+ /** Clases del contenedor. */
38
+ className?: string;
39
+ }
40
+
41
+ /** Número máximo de filas pintadas; evita colgar la vista con JSON enormes. */
42
+ const MAX_ROWS = 1500;
43
+ /** Niveles que vienen desplegados al abrir el árbol. */
44
+ const AUTO_EXPANDED_LEVELS = 2;
45
+
46
+ /** Construye el árbol de nodos a partir de un valor ya parseado. */
47
+ function buildTree(
48
+ value: unknown,
49
+ label: string,
50
+ path: (string | number)[],
51
+ level: number
52
+ ): TreeNode {
53
+ const kind = valueKind(value);
54
+ const children: TreeNode[] = [];
55
+ if (kind === "object") {
56
+ for (const [childKey, nested] of Object.entries(value as Record<string, unknown>)) {
57
+ children.push(buildTree(nested, childKey, [...path, childKey], level + 1));
58
+ }
59
+ } else if (kind === "array") {
60
+ (value as unknown[]).forEach((nested, index) => {
61
+ children.push(buildTree(nested, String(index), [...path, index], level + 1));
62
+ });
63
+ }
64
+ return {
65
+ label,
66
+ key: pathKey(path),
67
+ path,
68
+ level,
69
+ value,
70
+ kind,
71
+ childCount: children.length,
72
+ children,
73
+ };
74
+ }
75
+
76
+ /**
77
+ * `ITCodeTreeView` — árbol navegable de un JSON ya parseado.
78
+ *
79
+ * Pieza visual interna de `ITJSONEditor`: no valida ni parsea, solo pinta lo que
80
+ * recibe. Se exporta aparte por si hace falta reutilizarla en paneles o en
81
+ * documentación.
82
+ *
83
+ * @example
84
+ * ```tsx
85
+ * <ITCodeTreeView value={{ a: 1 }} analysis={analysis} showLines onJumpToLine={setLine} />
86
+ * ```
87
+ */
88
+ export default function ITCodeTreeView({
89
+ value,
90
+ analysis,
91
+ showLines = false,
92
+ onJumpToLine,
93
+ className,
94
+ }: ITCodeTreeViewProps) {
95
+ const { t } = useITTranslation();
96
+ const root = useMemo(() => buildTree(value, "", [], 0), [value]);
97
+ /** Nodos plegados por el usuario; el resto sigue la heurística de niveles. */
98
+ const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
99
+
100
+ /** Filas visibles ya aplanadas, con el corte por `MAX_ROWS` aplicado. */
101
+ const rows = useMemo(() => {
102
+ const result: TreeNode[] = [];
103
+ const isOpen = (node: TreeNode) => {
104
+ const explicit = collapsed[node.key];
105
+ if (explicit !== undefined) return !explicit;
106
+ return node.level < AUTO_EXPANDED_LEVELS;
107
+ };
108
+ const walk = (node: TreeNode) => {
109
+ if (result.length >= MAX_ROWS) return;
110
+ result.push(node);
111
+ if (node.childCount && isOpen(node)) node.children.forEach(walk);
112
+ };
113
+ if (root.kind === "object" || root.kind === "array") root.children.forEach(walk);
114
+ else result.push(root);
115
+ return result;
116
+ }, [root, collapsed]);
117
+
118
+ const truncated = rows.length >= MAX_ROWS;
119
+
120
+ const toggle = (key: string, level: number) =>
121
+ setCollapsed((previous) => ({
122
+ ...previous,
123
+ // Sin decisión previa, la fila estaba abierta si su nivel se auto-despliega.
124
+ [key]: previous[key] === undefined ? level < AUTO_EXPANDED_LEVELS : !previous[key],
125
+ }));
126
+
127
+ return (
128
+ <div tabIndex={0} className={clsx("it-json-editor-tree overflow-auto", className)}>
129
+ {rows.map((node) => {
130
+ const line = analysis.nodeLines.get(pathKey(node.path))?.line;
131
+ const isRoot = node.key === "";
132
+ const isCollapsed =
133
+ collapsed[node.key] ?? (node.childCount > 0 && node.level >= AUTO_EXPANDED_LEVELS);
134
+ const hasChildren = node.childCount > 0;
135
+ const isPrimitive = node.kind !== "object" && node.kind !== "array";
136
+
137
+ return (
138
+ <div
139
+ key={node.key || "root"}
140
+ className="it-json-editor-tree-row"
141
+ style={{ paddingLeft: node.level * 14 }}
142
+ >
143
+ {hasChildren ? (
144
+ <button
145
+ type="button"
146
+ onClick={() => toggle(node.key, node.level)}
147
+ aria-expanded={!isCollapsed}
148
+ aria-label={isCollapsed ? t("jsonEditor.expandNode") : t("jsonEditor.collapseNode")}
149
+ className="it-json-editor-tree-toggle"
150
+ >
151
+ {isCollapsed ? <FaChevronRight size={8} /> : <FaChevronDown size={8} />}
152
+ </button>
153
+ ) : (
154
+ <span className="it-json-editor-tree-spacer" aria-hidden="true" />
155
+ )}
156
+
157
+ {!isRoot && (
158
+ <>
159
+ <span className="it-json-editor-tk-key">{node.label}</span>
160
+ <span className="it-json-editor-tk-punct">:</span>
161
+ </>
162
+ )}
163
+
164
+ {hasChildren ? (
165
+ <>
166
+ <span className="it-json-editor-tk-punct">{node.kind === "array" ? "[" : "{"}</span>
167
+ {isCollapsed && (
168
+ <span className="it-json-editor-tk-punct">
169
+ {node.kind === "array" ? "]" : "}"}
170
+ </span>
171
+ )}
172
+ <span className="it-json-editor-tree-badge">
173
+ {t("jsonEditor.childCount", { count: node.childCount })}
174
+ </span>
175
+ </>
176
+ ) : (
177
+ <span
178
+ className={clsx(
179
+ "it-json-editor-tk-value",
180
+ node.kind === "string" && "it-json-editor-tk-string",
181
+ node.kind === "number" && "it-json-editor-tk-number",
182
+ node.kind === "boolean" && "it-json-editor-tk-keyword",
183
+ node.kind === "null" && "it-json-editor-tk-null"
184
+ )}
185
+ >
186
+ {previewValue(node.value)}
187
+ </span>
188
+ )}
189
+
190
+ {isPrimitive && isRoot && (
191
+ <span className="it-json-editor-tree-badge">{kindLabel(node.kind, t)}</span>
192
+ )}
193
+
194
+ {showLines && line !== undefined && (
195
+ <button
196
+ type="button"
197
+ onClick={() => onJumpToLine?.(line)}
198
+ title={t("jsonEditor.jumpToLine", { line })}
199
+ aria-label={t("jsonEditor.jumpToLine", { line })}
200
+ className="it-json-editor-tree-jump"
201
+ >
202
+ <FaCrosshairs size={8} />
203
+ <span>{line}</span>
204
+ </button>
205
+ )}
206
+ </div>
207
+ );
208
+ })}
209
+
210
+ {truncated && (
211
+ <p className="it-json-editor-tree-badge m-2 inline-block px-2 py-1">
212
+ {t("jsonEditor.treeTruncated")}
213
+ </p>
214
+ )}
215
+ </div>
216
+ );
217
+ }
218
+
219
+ /** Etiqueta corta y traducible del tipo de un valor. */
220
+ function kindLabel(kind: TreeNode["kind"], t: (key: string) => string): string {
221
+ switch (kind) {
222
+ case "object":
223
+ return t("jsonEditor.kindObject");
224
+ case "array":
225
+ return t("jsonEditor.kindArray");
226
+ case "string":
227
+ return t("jsonEditor.kindString");
228
+ case "number":
229
+ return t("jsonEditor.kindNumber");
230
+ case "boolean":
231
+ return t("jsonEditor.kindBoolean");
232
+ default:
233
+ return t("jsonEditor.kindNull");
234
+ }
235
+ }
@@ -0,0 +1,2 @@
1
+ export { default as ITJSONEditor } from "./json-editor";
2
+ export type { ITJSONEditorProps } from "./json-editor.props";
@@ -0,0 +1,82 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ /**
4
+ * Modo de presentación del documento.
5
+ *
6
+ * - `"text"`: editor de código con resaltado, gutter y plegado.
7
+ * - `"tree"`: árbol navegable de solo lectura sobre el mismo JSON.
8
+ */
9
+ export type ITJSONEditorMode = "text" | "tree";
10
+
11
+ /** Paleta del lienzo de código. */
12
+ export type ITJSONEditorTheme = "ink" | "light";
13
+
14
+ /**
15
+ * Props de `ITJSONEditor`.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <ITJSONEditor value={json} onChange={setJson} height={420} mode="text" />
20
+ * ```
21
+ */
22
+ export interface ITJSONEditorProps {
23
+ /** Documento JSON como cadena. Si es `undefined`, el editor arranca vacío. @default "" */
24
+ value?: string;
25
+ /** Se dispara con el texto completo cada vez que cambia (teclado, pegado o barra de acciones). */
26
+ onChange?: (value: string) => void;
27
+ /** Se dispara tras cada validación con el valor parseado y su estado. */
28
+ onParse?: (parsed: unknown, isValid: boolean, error?: string) => void;
29
+ /** Texto de ayuda que se muestra cuando el documento está vacío. */
30
+ placeholder?: string;
31
+ /** Bloquea la edición: desaparecen las acciones que modifican el texto. @default false */
32
+ readOnly?: boolean;
33
+ /** Modo inicial: `"text"` (código) o `"tree"` (árbol navegable). @default "text" */
34
+ mode?: ITJSONEditorMode;
35
+ /** Permite alternar entre texto y árbol desde la barra. @default true */
36
+ showModeSwitch?: boolean;
37
+ /** Muestra la barra superior de acciones. @default true */
38
+ showToolbar?: boolean;
39
+ /** Muestra el indicador de válido/ inválido con línea y columna del error. @default true */
40
+ showValidation?: boolean;
41
+ /** Estadísticas del pie: líneas, nodos, profundidad y tamaño. @default true */
42
+ showStatusBar?: boolean;
43
+ /** Muestra los números de línea del gutter. @default true */
44
+ showLineNumbers?: boolean;
45
+ /** Muestra el gutter de plegado de bloques `{…}` / `[…]`. @default true */
46
+ showFoldGutter?: boolean;
47
+ /** Habilita las acciones de la barra (formatear, minificar, ordenar, escapar). @default true */
48
+ showTransformActions?: boolean;
49
+ /** Habilita el botón de copiar el documento completo. @default true */
50
+ showCopyButton?: boolean;
51
+ /** Habilita la carga de un `.json` desde disco. @default true */
52
+ showImportButton?: boolean;
53
+ /** Habilita la descarga del documento como `.json`. @default true */
54
+ showDownloadButton?: boolean;
55
+ /** Muestra la fila de ajustes de vista (ajuste de línea, números, plegado, indentación). @default true */
56
+ showViewSettings?: boolean;
57
+ /** Espacios por nivel de indentación, entre 0 y 8. @default 2 */
58
+ indentSize?: number;
59
+ /** Ajusta las líneas largas al ancho del editor. Desactivado mantiene el scroll horizontal y el gutter sincronizado. @default false */
60
+ wrapLines?: boolean;
61
+ /** Ofrece números de línea y plegado también en la vista de árbol. @default false */
62
+ treeShowLines?: boolean;
63
+ /** Paleta del lienzo: `"ink"` (tinta oscura, por defecto) o `"light"`. @default "ink" */
64
+ canvasTheme?: ITJSONEditorTheme;
65
+ /** Altura del área de edición: número en píxeles o cualquier medida CSS. @default 340 */
66
+ height?: number | string;
67
+ /** Ancho mínimo del editor. Útil dentro de layouts que comprimen. */
68
+ minWidth?: number | string;
69
+ /** Acciones extra que se añaden al final de la barra, antes de copiar/descargar. */
70
+ actions?: ReactNode;
71
+ /** Clases para el contenedor raíz. */
72
+ className?: string;
73
+ /** Identificador HTML del contenedor raíz, para anclarlo desde fuera o en pruebas. */
74
+ id?: string;
75
+ /** Clases para el `<textarea>`, por si hay que afinar la tipografía. */
76
+ textareaClassName?: string;
77
+ /**
78
+ * Línea (1-based) que hay que marcar como errónea cuando el fallo lo detecta
79
+ * la aplicación y no el propio editor.
80
+ */
81
+ errorLine?: number;
82
+ }