@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.
- package/dist/index.cjs +7842 -4676
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +883 -3
- package/dist/index.d.ts +883 -3
- package/dist/index.js +9118 -5971
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +14 -7
- package/src/App.tsx +70 -2
- package/src/components/atoms/image/image.props.ts +2 -0
- package/src/components/atoms/image/image.tsx +3 -2
- package/src/components/molecules/json-editor/json-editor-tree.tsx +235 -0
- package/src/components/molecules/json-editor/json-editor.index.ts +2 -0
- package/src/components/molecules/json-editor/json-editor.props.ts +82 -0
- package/src/components/molecules/json-editor/json-editor.tsx +972 -0
- package/src/components/molecules/json-editor/json-editor.utils.ts +717 -0
- package/src/components/molecules/json-editor/useJsonEditorKeyboard.ts +256 -0
- package/src/components/molecules/json-renderer/json-renderer.index.ts +2 -0
- package/src/components/molecules/json-renderer/json-renderer.props.ts +31 -0
- package/src/components/molecules/json-renderer/json-renderer.tsx +79 -0
- package/src/components/organisms/dialog/dialog.props.ts +2 -0
- package/src/components/organisms/dialog/dialog.tsx +4 -3
- package/src/dev.css +30 -1
- package/src/hooks/useJsonRender.tsx +9 -0
- package/src/i18n/locales/en.ts +54 -0
- package/src/i18n/locales/es.ts +54 -0
- package/src/index.css +414 -0
- package/src/index.ts +99 -2
- package/src/json-renderer/catalog/axzyCatalog.ts +362 -0
- package/src/json-renderer/catalog/catalog.props.ts +128 -0
- package/src/json-renderer/catalog/resolve.ts +225 -0
- package/src/json-renderer/index.ts +70 -0
- package/src/json-renderer/kernel/NodeBoundary.tsx +53 -0
- package/src/json-renderer/kernel/SchemaRenderer.tsx +197 -0
- package/src/json-renderer/kernel/kernel.test.tsx +341 -0
- package/src/json-renderer/kernel/partial.ts +92 -0
- package/src/json-renderer/kernel/renderNode.tsx +113 -0
- package/src/json-renderer/kernel/schemaRenderer.props.ts +42 -0
- package/src/json-renderer/kernel/types.ts +159 -0
- package/src/json-renderer/shared/errors.ts +92 -0
- package/src/json-renderer/shared/limits.ts +87 -0
- package/src/json-renderer/shared/paths.ts +66 -0
- package/src/json-renderer/shared/shared.test.ts +108 -0
- package/src/json-renderer/stream/tolerantParse.test.ts +152 -0
- package/src/json-renderer/stream/tolerantParse.ts +449 -0
- package/src/json-renderer/test/setup.ts +38 -0
- package/src/json-renderer/validation/migrate.ts +83 -0
- package/src/json-renderer/validation/validate.ts +161 -0
- package/src/json-renderer/validation/validation.test.ts +165 -0
- package/src/json-renderer/vocabulary/buildContract.ts +158 -0
- package/src/sandbox/AxzyLogo.tsx +139 -0
- package/src/sandbox/axzyConfig.test.ts +40 -0
- package/src/sandbox/axzyConfig.ts +77 -0
- package/src/sandbox/i18n.ts +220 -0
- package/src/sandbox/navigation.ts +3 -0
- package/src/sandbox/useHashRoute.ts +39 -9
- package/src/showcases/DemoJsonDashboard.tsx +489 -0
- package/src/showcases/DemoJsonDocs.tsx +339 -0
- package/src/showcases/DemoJsonPortfolio.tsx +257 -0
- package/src/showcases/FeedbackShowcases.tsx +16 -5
- package/src/showcases/HomeShowcase.tsx +16 -9
- package/src/showcases/JsonEditorShowcase.tsx +150 -0
- package/src/showcases/JsonRendererShowcase.tsx +70 -0
- package/src/showcases/JsonScreenShowcase.tsx +341 -0
- package/src/showcases/StructureShowcases.tsx +6 -1
- package/src/showcases/demoApp/demoData.ts +181 -0
- package/src/showcases/demoApp/nodeTree.test.ts +39 -0
- package/src/showcases/demoApp/nodeTree.ts +35 -0
- package/src/showcases/demoApp/schemas.test.ts +54 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuración global del sandbox, leída de `/axzy.json`.
|
|
3
|
+
*
|
|
4
|
+
* El archivo es la fuente de verdad de lo que **no** es pantalla: tema, densidad,
|
|
5
|
+
* radios, sombras y apariencia. Se aplica al `ITThemeProvider` del host, así que
|
|
6
|
+
* cambiar un valor en el JSON cambia todo el sistema sin tocar código.
|
|
7
|
+
*
|
|
8
|
+
* El merge es puro y tolerante: lo que falte o venga mal se queda con el default.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ITThemeAppearance, ITThemePalette } from "../index";
|
|
12
|
+
|
|
13
|
+
/** Lo que se puede configurar globalmente desde `axzy.json`. */
|
|
14
|
+
export interface AxzyConfig {
|
|
15
|
+
/** Paleta parcial: se mezcla con la del sistema. */
|
|
16
|
+
theme: Partial<ITThemePalette>;
|
|
17
|
+
/** Densidad global (0.5–1.5). */
|
|
18
|
+
density: number;
|
|
19
|
+
/** Radio global en px (0–24). */
|
|
20
|
+
radius: number;
|
|
21
|
+
/** Nivel de sombra (0–3). */
|
|
22
|
+
shadow: number;
|
|
23
|
+
/** Lenguaje visual. */
|
|
24
|
+
appearance: ITThemeAppearance;
|
|
25
|
+
/** Muestra el botón flotante del diseñador de tema. */
|
|
26
|
+
showFab: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Configuración por defecto del sandbox (lo que se aplica si no hay archivo). */
|
|
30
|
+
export const DEFAULT_AXZY_CONFIG: AxzyConfig = {
|
|
31
|
+
theme: {},
|
|
32
|
+
density: 0.98,
|
|
33
|
+
radius: 10,
|
|
34
|
+
shadow: 2,
|
|
35
|
+
appearance: "soft",
|
|
36
|
+
showFab: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Recorta un número a un rango; si no es número, devuelve el default. */
|
|
40
|
+
function clamp(value: unknown, min: number, max: number, fallback: number, decimals = 2): number {
|
|
41
|
+
if (typeof value !== "number" || Number.isNaN(value)) return fallback;
|
|
42
|
+
const factor = 10 ** decimals;
|
|
43
|
+
return Math.round(Math.min(max, Math.max(min, value)) * factor) / factor;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Mezcla la configuración del archivo con los defaults.
|
|
48
|
+
*
|
|
49
|
+
* Nunca lanza: un archivo vacío, roto o con tipos raros deja los defaults.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* mergeAxzyConfig({ radius: 0, density: 1.4 }); // → radius 0, density 1.4, resto default
|
|
53
|
+
*/
|
|
54
|
+
export function mergeAxzyConfig(raw: unknown): AxzyConfig {
|
|
55
|
+
const entrada = (raw && typeof raw === "object" ? raw : {}) as Partial<AxzyConfig>;
|
|
56
|
+
const tema = (entrada.theme && typeof entrada.theme === "object" ? entrada.theme : {}) as Partial<ITThemePalette>;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
theme: {
|
|
60
|
+
...(typeof tema.primary === "string" ? { primary: tema.primary } : {}),
|
|
61
|
+
...(typeof tema.secondary === "string" ? { secondary: tema.secondary } : {}),
|
|
62
|
+
...(typeof tema.ternary === "string" ? { ternary: tema.ternary } : {}),
|
|
63
|
+
...(typeof tema.danger === "string" ? { danger: tema.danger } : {}),
|
|
64
|
+
...(typeof tema.success === "string" ? { success: tema.success } : {}),
|
|
65
|
+
...(typeof tema.info === "string" ? { info: tema.info } : {}),
|
|
66
|
+
...(typeof tema.alert === "string" ? { alert: tema.alert } : {}),
|
|
67
|
+
...(typeof tema.warning === "string" ? { warning: tema.warning } : {}),
|
|
68
|
+
...(tema.layout && typeof tema.layout === "object" ? { layout: tema.layout } : {}),
|
|
69
|
+
...(tema.table && typeof tema.table === "object" ? { table: tema.table } : {}),
|
|
70
|
+
},
|
|
71
|
+
density: clamp(entrada.density, 0.5, 1.5, DEFAULT_AXZY_CONFIG.density),
|
|
72
|
+
radius: clamp(entrada.radius, 0, 24, DEFAULT_AXZY_CONFIG.radius, 0),
|
|
73
|
+
shadow: clamp(entrada.shadow, 0, 3, DEFAULT_AXZY_CONFIG.shadow, 0),
|
|
74
|
+
appearance: entrada.appearance === "flat" || entrada.appearance === "soft" ? entrada.appearance : DEFAULT_AXZY_CONFIG.appearance,
|
|
75
|
+
showFab: typeof entrada.showFab === "boolean" ? entrada.showFab : DEFAULT_AXZY_CONFIG.showFab,
|
|
76
|
+
};
|
|
77
|
+
}
|
package/src/sandbox/i18n.ts
CHANGED
|
@@ -17,9 +17,119 @@ export const sandboxMessages: ITMessagesByLocale = {
|
|
|
17
17
|
"sandbox.themeSystem": "Sistema",
|
|
18
18
|
"sandbox.notifications": "Notificaciones",
|
|
19
19
|
"sandbox.nav.portfolio": "Portafolio",
|
|
20
|
+
"sandbox.nav.demo": "Demo en JSON",
|
|
20
21
|
"sandbox.userMenu.portfolio": "Ir al Portafolio",
|
|
21
22
|
"sandbox.search.label": "Buscar componentes",
|
|
22
23
|
"sandbox.search.placeholder": "Buscar componente…",
|
|
24
|
+
"sandbox.loading": "Cargando…",
|
|
25
|
+
// Portafolio de demostración (pintado solo con JSON)
|
|
26
|
+
"sandbox.app.toast.saved": "Guardado",
|
|
27
|
+
"sandbox.app.toast.deleted": "Eliminado",
|
|
28
|
+
"sandbox.app.form.create": "Nuevo usuario",
|
|
29
|
+
"sandbox.app.form.edit": "Editar usuario",
|
|
30
|
+
"sandbox.app.form.name": "Nombre",
|
|
31
|
+
"sandbox.app.form.email": "Correo",
|
|
32
|
+
"sandbox.app.form.role": "Rol",
|
|
33
|
+
"sandbox.app.form.status": "Estado",
|
|
34
|
+
"sandbox.app.form.age": "Edad",
|
|
35
|
+
"sandbox.app.form.company": "Empresa",
|
|
36
|
+
"sandbox.app.form.since": "Alta",
|
|
37
|
+
"sandbox.app.form.shift": "Turno",
|
|
38
|
+
"sandbox.app.form.save": "Guardar",
|
|
39
|
+
"sandbox.app.form.cancel": "Cancelar",
|
|
40
|
+
"sandbox.app.form.delete": "Eliminar",
|
|
41
|
+
"sandbox.app.title": "Panel de demo",
|
|
42
|
+
"sandbox.app.exit": "Ver portafolio JSON",
|
|
43
|
+
"sandbox.app.nav.aria": "Páginas del dashboard",
|
|
44
|
+
"sandbox.app.sub.todos": "Todos",
|
|
45
|
+
"sandbox.app.sub.activos": "Activos",
|
|
46
|
+
"sandbox.app.sub.inactivos": "Inactivos",
|
|
47
|
+
"sandbox.app.sub.pendientes": "Pendientes",
|
|
48
|
+
"sandbox.app.nav.docs": "Documentación",
|
|
49
|
+
"sandbox.app.docs.pick": "Esquema",
|
|
50
|
+
"sandbox.app.docs.copy": "Copiar JSON",
|
|
51
|
+
"sandbox.app.docs.copied": "JSON copiado",
|
|
52
|
+
"sandbox.app.docs.copyError": "No se pudo copiar",
|
|
53
|
+
"sandbox.app.docs.steps": "Cómo se pinta un esquema",
|
|
54
|
+
"sandbox.app.docs.node": "Nodo",
|
|
55
|
+
"sandbox.app.docs.whole": "Esquema completo",
|
|
56
|
+
"sandbox.app.docs.readOnly": "Inspector · solo lectura",
|
|
57
|
+
"sandbox.app.docs.editable": "Editable",
|
|
58
|
+
"sandbox.app.docs.rules": "Reglas que no se negocian",
|
|
59
|
+
"sandbox.app.docs.rule1": "El esquema nunca ejecuta código, ni hace red, ni pinta HTML.",
|
|
60
|
+
"sandbox.app.docs.rule2": "Todo componente y toda acción salen de listas blancas del host.",
|
|
61
|
+
"sandbox.app.docs.rule3": "Un componente que falla se aísla: el resto de la pantalla sigue.",
|
|
62
|
+
"sandbox.app.nav.inicio": "Inicio",
|
|
63
|
+
"sandbox.app.nav.usuarios": "Usuarios",
|
|
64
|
+
"sandbox.app.nav.pedidos": "Pedidos",
|
|
65
|
+
"sandbox.app.nav.productos": "Productos",
|
|
66
|
+
"sandbox.app.nav.reportes": "Reportes",
|
|
67
|
+
"sandbox.app.sub.pagados": "Pagados",
|
|
68
|
+
"sandbox.app.productos.title": "Productos",
|
|
69
|
+
"sandbox.app.productos.lead": "Catálogo con SKU, precio, stock y categoría; la tabla pagina y ordena desde el host.",
|
|
70
|
+
"sandbox.app.reportes.title": "Reportes",
|
|
71
|
+
"sandbox.app.reportes.lead": "Indicadores del periodo y el detalle de pedidos que los sostiene.",
|
|
72
|
+
"sandbox.app.reportes.ticket": "Ticket promedio",
|
|
73
|
+
"sandbox.app.reportes.conversion": "Conversión",
|
|
74
|
+
"sandbox.app.valid": "Esquema válido",
|
|
75
|
+
"sandbox.app.partial": "Esquema parcial",
|
|
76
|
+
"sandbox.app.invalid": "Esquema inválido",
|
|
77
|
+
"sandbox.app.loading": "Cargando la página…",
|
|
78
|
+
"sandbox.app.loadError": "No se pudo cargar la página",
|
|
79
|
+
"sandbox.app.back": "Volver al inicio",
|
|
80
|
+
"sandbox.app.statUsers": "Usuarios",
|
|
81
|
+
"sandbox.app.statOrders": "Pedidos",
|
|
82
|
+
"sandbox.app.statRevenue": "Ingresos",
|
|
83
|
+
"sandbox.app.statPending": "Pendientes",
|
|
84
|
+
"sandbox.app.inicio.title": "Resumen operativo",
|
|
85
|
+
"sandbox.app.inicio.lead": "Cada página es un JSON: esta trae métricas y la tabla de pedidos recientes, que pide los datos al host con `$source`.",
|
|
86
|
+
"sandbox.app.inicio.ctaUsers": "Gestionar usuarios",
|
|
87
|
+
"sandbox.app.inicio.ctaOrders": "Ver pedidos",
|
|
88
|
+
"sandbox.app.inicio.recent": "Pedidos recientes",
|
|
89
|
+
"sandbox.app.inicio.recentLead": "Paginación, orden y filtros los resuelve la tabla; los datos, el host.",
|
|
90
|
+
"sandbox.app.usuarios.title": "Usuarios",
|
|
91
|
+
"sandbox.app.usuarios.lead": "La columna Nombre filtra y ordena; Rol y Estado filtran por catálogo. Todo declarado en el JSON.",
|
|
92
|
+
"sandbox.app.usuarios.new": "Nuevo usuario",
|
|
93
|
+
"sandbox.app.usuarios.active": "Activos",
|
|
94
|
+
"sandbox.app.usuarios.admins": "Admins",
|
|
95
|
+
"sandbox.app.usuarios.inactive": "Inactivos",
|
|
96
|
+
"sandbox.app.pedidos.title": "Pedidos",
|
|
97
|
+
"sandbox.app.pedidos.lead": "La columna Total usa el formateo de moneda de la tabla; el estado se filtra por catálogo.",
|
|
98
|
+
"sandbox.app.pedidos.new": "Nuevo pedido",
|
|
99
|
+
"sandbox.app.pedidos.paid": "Pagados",
|
|
100
|
+
"sandbox.app.pedidos.pending": "Pendientes",
|
|
101
|
+
"sandbox.app.pedidos.cancelled": "Cancelados",
|
|
102
|
+
"sandbox.demo.brand": "axzydev · portafolio en JSON",
|
|
103
|
+
"sandbox.demo.back": "Volver al sandbox",
|
|
104
|
+
"sandbox.demo.eyebrow": "Demo · motor de pantallas",
|
|
105
|
+
"sandbox.demo.title": "Del API a la interfaz: producto completo, en producción.",
|
|
106
|
+
"sandbox.demo.lead":
|
|
107
|
+
"Esta pantalla no tiene ni una línea de TSX: se carga un archivo JSON, se valida contra el catálogo y el motor la pinta. Todo lo que ves —layout, tarjetas, acciones e idioma— viene del esquema.",
|
|
108
|
+
"sandbox.demo.ctaProjects": "Ver proyectos",
|
|
109
|
+
"sandbox.demo.ctaContact": "Contacto",
|
|
110
|
+
"sandbox.demo.ctaPanel": "Abrir el panel",
|
|
111
|
+
"sandbox.demo.statExperience": "Experiencia",
|
|
112
|
+
"sandbox.demo.statProducts": "Productos",
|
|
113
|
+
"sandbox.demo.statTech": "Tecnologías",
|
|
114
|
+
"sandbox.demo.statMode": "Modalidad",
|
|
115
|
+
"sandbox.demo.statModeValue": "Remoto",
|
|
116
|
+
"sandbox.demo.projectsTitle": "Proyectos",
|
|
117
|
+
"sandbox.demo.projectsLead": "Cada tarjeta es un nodo del esquema; el botón pide una acción registrada por el host.",
|
|
118
|
+
"sandbox.demo.viewProject": "Ver detalle",
|
|
119
|
+
"sandbox.demo.p1.text": "Operación de seguridad: rondas con QR, incidencias y analítica de desempeño.",
|
|
120
|
+
"sandbox.demo.p1.alt": "CheckApp: panel de analítica de seguridad",
|
|
121
|
+
"sandbox.demo.p2.text": "63 componentes accesibles, temables y con i18n, publicados en npm.",
|
|
122
|
+
"sandbox.demo.p2.alt": "AXZY UI System: componentes en ejecución",
|
|
123
|
+
"sandbox.demo.p3.text": "Cliente HTTP para la terminal, con requests versionables en YAML y modo CI.",
|
|
124
|
+
"sandbox.demo.p3.alt": "axzy: cliente HTTP en la terminal",
|
|
125
|
+
"sandbox.demo.stackTitle": "Stack",
|
|
126
|
+
"sandbox.demo.stackLead": "Las etiquetas son nodos ITChip; añadir una es añadir un objeto al array.",
|
|
127
|
+
"sandbox.demo.contactTitle": "Contacto",
|
|
128
|
+
"sandbox.demo.contactLead": "Los botones no llevan URLs: piden acciones por nombre y el host decide a dónde van.",
|
|
129
|
+
"sandbox.demo.sendMail": "Escribir un correo",
|
|
130
|
+
"sandbox.demo.footer": "Asael Amaro · Fullstack Developer · Remoto, México",
|
|
131
|
+
"sandbox.demo.badge": "JSON válido",
|
|
132
|
+
|
|
23
133
|
"sandbox.search.clear": "Limpiar búsqueda",
|
|
24
134
|
"sandbox.search.results": "{count} resultados",
|
|
25
135
|
|
|
@@ -964,9 +1074,119 @@ export const sandboxMessages: ITMessagesByLocale = {
|
|
|
964
1074
|
"sandbox.themeSystem": "System",
|
|
965
1075
|
"sandbox.notifications": "Notifications",
|
|
966
1076
|
"sandbox.nav.portfolio": "Portfolio",
|
|
1077
|
+
"sandbox.nav.demo": "JSON demo",
|
|
967
1078
|
"sandbox.userMenu.portfolio": "Go to portfolio",
|
|
968
1079
|
"sandbox.search.label": "Search components",
|
|
969
1080
|
"sandbox.search.placeholder": "Search component…",
|
|
1081
|
+
"sandbox.loading": "Loading…",
|
|
1082
|
+
// Demo portfolio (rendered from JSON only)
|
|
1083
|
+
"sandbox.app.toast.saved": "Saved",
|
|
1084
|
+
"sandbox.app.toast.deleted": "Deleted",
|
|
1085
|
+
"sandbox.app.form.create": "New user",
|
|
1086
|
+
"sandbox.app.form.edit": "Edit user",
|
|
1087
|
+
"sandbox.app.form.name": "Name",
|
|
1088
|
+
"sandbox.app.form.email": "Email",
|
|
1089
|
+
"sandbox.app.form.role": "Role",
|
|
1090
|
+
"sandbox.app.form.status": "Status",
|
|
1091
|
+
"sandbox.app.form.age": "Age",
|
|
1092
|
+
"sandbox.app.form.company": "Company",
|
|
1093
|
+
"sandbox.app.form.since": "Since",
|
|
1094
|
+
"sandbox.app.form.shift": "Shift",
|
|
1095
|
+
"sandbox.app.form.save": "Save",
|
|
1096
|
+
"sandbox.app.form.cancel": "Cancel",
|
|
1097
|
+
"sandbox.app.form.delete": "Delete",
|
|
1098
|
+
"sandbox.app.title": "Demo panel",
|
|
1099
|
+
"sandbox.app.exit": "Open JSON portfolio",
|
|
1100
|
+
"sandbox.app.nav.aria": "Dashboard pages",
|
|
1101
|
+
"sandbox.app.sub.todos": "All",
|
|
1102
|
+
"sandbox.app.sub.activos": "Active",
|
|
1103
|
+
"sandbox.app.sub.inactivos": "Inactive",
|
|
1104
|
+
"sandbox.app.sub.pendientes": "Pending",
|
|
1105
|
+
"sandbox.app.nav.docs": "Documentation",
|
|
1106
|
+
"sandbox.app.docs.pick": "Schema",
|
|
1107
|
+
"sandbox.app.docs.copy": "Copy JSON",
|
|
1108
|
+
"sandbox.app.docs.copied": "JSON copied",
|
|
1109
|
+
"sandbox.app.docs.copyError": "Could not copy",
|
|
1110
|
+
"sandbox.app.docs.steps": "How a schema is rendered",
|
|
1111
|
+
"sandbox.app.docs.node": "Node",
|
|
1112
|
+
"sandbox.app.docs.whole": "Whole schema",
|
|
1113
|
+
"sandbox.app.docs.readOnly": "Inspector · read only",
|
|
1114
|
+
"sandbox.app.docs.editable": "Editable",
|
|
1115
|
+
"sandbox.app.docs.rules": "Rules that are not negotiable",
|
|
1116
|
+
"sandbox.app.docs.rule1": "The schema never runs code, never fetches and never renders HTML.",
|
|
1117
|
+
"sandbox.app.docs.rule2": "Every component and action comes from host whitelists.",
|
|
1118
|
+
"sandbox.app.docs.rule3": "A component that fails is isolated: the rest of the screen keeps going.",
|
|
1119
|
+
"sandbox.app.nav.inicio": "Home",
|
|
1120
|
+
"sandbox.app.nav.usuarios": "Users",
|
|
1121
|
+
"sandbox.app.nav.pedidos": "Orders",
|
|
1122
|
+
"sandbox.app.nav.productos": "Products",
|
|
1123
|
+
"sandbox.app.nav.reportes": "Reports",
|
|
1124
|
+
"sandbox.app.sub.pagados": "Paid",
|
|
1125
|
+
"sandbox.app.productos.title": "Products",
|
|
1126
|
+
"sandbox.app.productos.lead": "Catalog with SKU, price, stock and category; the table paginates and sorts from the host.",
|
|
1127
|
+
"sandbox.app.reportes.title": "Reports",
|
|
1128
|
+
"sandbox.app.reportes.lead": "Period indicators and the order detail behind them.",
|
|
1129
|
+
"sandbox.app.reportes.ticket": "Average ticket",
|
|
1130
|
+
"sandbox.app.reportes.conversion": "Conversion",
|
|
1131
|
+
"sandbox.app.valid": "Valid schema",
|
|
1132
|
+
"sandbox.app.partial": "Partial schema",
|
|
1133
|
+
"sandbox.app.invalid": "Invalid schema",
|
|
1134
|
+
"sandbox.app.loading": "Loading the page…",
|
|
1135
|
+
"sandbox.app.loadError": "The page could not be loaded",
|
|
1136
|
+
"sandbox.app.back": "Back to home",
|
|
1137
|
+
"sandbox.app.statUsers": "Users",
|
|
1138
|
+
"sandbox.app.statOrders": "Orders",
|
|
1139
|
+
"sandbox.app.statRevenue": "Revenue",
|
|
1140
|
+
"sandbox.app.statPending": "Pending",
|
|
1141
|
+
"sandbox.app.inicio.title": "Operations overview",
|
|
1142
|
+
"sandbox.app.inicio.lead": "Every page is JSON: this one ships metrics and the recent orders table, which asks the host for data through `$source`.",
|
|
1143
|
+
"sandbox.app.inicio.ctaUsers": "Manage users",
|
|
1144
|
+
"sandbox.app.inicio.ctaOrders": "See orders",
|
|
1145
|
+
"sandbox.app.inicio.recent": "Recent orders",
|
|
1146
|
+
"sandbox.app.inicio.recentLead": "Pagination, sorting and filters are the table's job; the data is the host's.",
|
|
1147
|
+
"sandbox.app.usuarios.title": "Users",
|
|
1148
|
+
"sandbox.app.usuarios.lead": "The Name column filters and sorts; Role and Status filter by catalog. All declared in JSON.",
|
|
1149
|
+
"sandbox.app.usuarios.new": "New user",
|
|
1150
|
+
"sandbox.app.usuarios.active": "Active",
|
|
1151
|
+
"sandbox.app.usuarios.admins": "Admins",
|
|
1152
|
+
"sandbox.app.usuarios.inactive": "Inactive",
|
|
1153
|
+
"sandbox.app.pedidos.title": "Orders",
|
|
1154
|
+
"sandbox.app.pedidos.lead": "The Total column uses the table's currency formatting; status filters by catalog.",
|
|
1155
|
+
"sandbox.app.pedidos.new": "New order",
|
|
1156
|
+
"sandbox.app.pedidos.paid": "Paid",
|
|
1157
|
+
"sandbox.app.pedidos.pending": "Pending",
|
|
1158
|
+
"sandbox.app.pedidos.cancelled": "Cancelled",
|
|
1159
|
+
"sandbox.demo.brand": "axzydev · portfolio in JSON",
|
|
1160
|
+
"sandbox.demo.back": "Back to the sandbox",
|
|
1161
|
+
"sandbox.demo.eyebrow": "Demo · screen engine",
|
|
1162
|
+
"sandbox.demo.title": "From API to interface: a complete product, in production.",
|
|
1163
|
+
"sandbox.demo.lead":
|
|
1164
|
+
"This screen has no TSX at all: a JSON file is loaded, validated against the catalog and rendered by the engine. Everything you see —layout, cards, actions and language— comes from the schema.",
|
|
1165
|
+
"sandbox.demo.ctaProjects": "See projects",
|
|
1166
|
+
"sandbox.demo.ctaContact": "Contact",
|
|
1167
|
+
"sandbox.demo.ctaPanel": "Open the panel",
|
|
1168
|
+
"sandbox.demo.statExperience": "Experience",
|
|
1169
|
+
"sandbox.demo.statProducts": "Products",
|
|
1170
|
+
"sandbox.demo.statTech": "Technologies",
|
|
1171
|
+
"sandbox.demo.statMode": "Work mode",
|
|
1172
|
+
"sandbox.demo.statModeValue": "Remote",
|
|
1173
|
+
"sandbox.demo.projectsTitle": "Projects",
|
|
1174
|
+
"sandbox.demo.projectsLead": "Every card is a schema node; the button asks for an action registered by the host.",
|
|
1175
|
+
"sandbox.demo.viewProject": "View details",
|
|
1176
|
+
"sandbox.demo.p1.text": "Security operations: QR rounds, incidents and performance analytics.",
|
|
1177
|
+
"sandbox.demo.p1.alt": "CheckApp: security analytics dashboard",
|
|
1178
|
+
"sandbox.demo.p2.text": "63 accessible, themeable components with i18n, published on npm.",
|
|
1179
|
+
"sandbox.demo.p2.alt": "AXZY UI System: components running",
|
|
1180
|
+
"sandbox.demo.p3.text": "HTTP client for the terminal with YAML-versioned requests and a CI mode.",
|
|
1181
|
+
"sandbox.demo.p3.alt": "axzy: HTTP client in the terminal",
|
|
1182
|
+
"sandbox.demo.stackTitle": "Stack",
|
|
1183
|
+
"sandbox.demo.stackLead": "Tags are ITChip nodes; adding one means adding an object to the array.",
|
|
1184
|
+
"sandbox.demo.contactTitle": "Contact",
|
|
1185
|
+
"sandbox.demo.contactLead": "Buttons carry no URLs: they ask for named actions and the host decides where they go.",
|
|
1186
|
+
"sandbox.demo.sendMail": "Write an email",
|
|
1187
|
+
"sandbox.demo.footer": "Asael Amaro · Fullstack Developer · Remote, Mexico",
|
|
1188
|
+
"sandbox.demo.badge": "Valid JSON",
|
|
1189
|
+
|
|
970
1190
|
"sandbox.search.clear": "Clear search",
|
|
971
1191
|
"sandbox.search.results": "{count} results",
|
|
972
1192
|
|
|
@@ -104,6 +104,9 @@ 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: "jsonscreen", label: "Motor de pantallas (nuevo)" },
|
|
108
|
+
{ id: "jsonrenderer", label: "ITJsonRenderer (PoC)" },
|
|
109
|
+
{ id: "jsoneditor", label: "ITJSONEditor" },
|
|
107
110
|
],
|
|
108
111
|
},
|
|
109
112
|
];
|
|
@@ -3,7 +3,7 @@ import { GROUP_SLUGS, groupBySlug, isValidItem } from "./navigation";
|
|
|
3
3
|
import { PRODUCTOS } from "../showcases/productos";
|
|
4
4
|
|
|
5
5
|
/** Top-level sandbox views. `home` is the portfolio, `producto` a project detail page. */
|
|
6
|
-
export type SandboxView = "home" | "ui-system" | "producto";
|
|
6
|
+
export type SandboxView = "home" | "ui-system" | "producto" | "demo";
|
|
7
7
|
|
|
8
8
|
/** Parsed representation of `window.location.hash`. */
|
|
9
9
|
export interface HashRoute {
|
|
@@ -17,6 +17,8 @@ export interface HashRoute {
|
|
|
17
17
|
producto: string | null;
|
|
18
18
|
/** Raw hash exactly as read from the URL (used by the 404 page). */
|
|
19
19
|
raw: string;
|
|
20
|
+
/** Parámetros `?clave=valor` del hash (filtros persistentes). */
|
|
21
|
+
query: Record<string, string>;
|
|
20
22
|
/** Whether the route resolved to a real page. `false` → render 404. */
|
|
21
23
|
valid: boolean;
|
|
22
24
|
}
|
|
@@ -39,7 +41,17 @@ function isValidProducto(id: string): boolean {
|
|
|
39
41
|
*/
|
|
40
42
|
export function parseHash(hash: string): HashRoute {
|
|
41
43
|
const raw = hash;
|
|
42
|
-
const
|
|
44
|
+
const body = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
45
|
+
const [clean, queryString = ""] = body.split("?");
|
|
46
|
+
const query: Record<string, string> = Object.fromEntries(
|
|
47
|
+
queryString
|
|
48
|
+
.split("&")
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.map((par) => {
|
|
51
|
+
const [clave, valor = ""] = par.split("=");
|
|
52
|
+
return [decodeURIComponent(clave), decodeURIComponent(valor)];
|
|
53
|
+
})
|
|
54
|
+
);
|
|
43
55
|
const parts = clean.split("/").filter(Boolean);
|
|
44
56
|
|
|
45
57
|
if (parts[0] === "proyecto") {
|
|
@@ -50,24 +62,32 @@ export function parseHash(hash: string): HashRoute {
|
|
|
50
62
|
item: null,
|
|
51
63
|
producto,
|
|
52
64
|
raw,
|
|
65
|
+
query,
|
|
53
66
|
valid: Boolean(producto) && isValidProducto(producto as string),
|
|
54
67
|
};
|
|
55
68
|
}
|
|
56
69
|
|
|
70
|
+
if (parts[0] === "demo") {
|
|
71
|
+
if (parts[1] === "app") {
|
|
72
|
+
return { view: "demo", group: "app", item: parts[2] ?? "inicio", producto: null, raw, query, valid: parts.length <= 3 };
|
|
73
|
+
}
|
|
74
|
+
return { view: "demo", group: null, item: null, producto: null, raw, query, valid: parts.length === 1 };
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
if (parts.length === 0 || parts[0] !== "ui-system") {
|
|
58
|
-
return { view: "home", group: null, item: null, producto: null, raw, valid: true };
|
|
78
|
+
return { view: "home", group: null, item: null, producto: null, raw, query, valid: true };
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
if (parts.length === 1) {
|
|
62
|
-
return { view: "ui-system", group: null, item: null, producto: null, raw, valid: true };
|
|
82
|
+
return { view: "ui-system", group: null, item: null, producto: null, raw, query, valid: true };
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
if (parts.length === 2) {
|
|
66
86
|
const group = groupBySlug(parts[1]);
|
|
67
87
|
if (!group) {
|
|
68
|
-
return { view: "ui-system", group: null, item: null, producto: null, raw, valid: false };
|
|
88
|
+
return { view: "ui-system", group: null, item: null, producto: null, raw, query, valid: false };
|
|
69
89
|
}
|
|
70
|
-
return { view: "ui-system", group: group.id, item: null, producto: null, raw, valid: true };
|
|
90
|
+
return { view: "ui-system", group: group.id, item: null, producto: null, raw, query, valid: true };
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
if (parts.length === 3) {
|
|
@@ -79,13 +99,14 @@ export function parseHash(hash: string): HashRoute {
|
|
|
79
99
|
item: null,
|
|
80
100
|
producto: null,
|
|
81
101
|
raw,
|
|
102
|
+
query,
|
|
82
103
|
valid: false,
|
|
83
104
|
};
|
|
84
105
|
}
|
|
85
|
-
return { view: "ui-system", group: group.id, item: parts[2], producto: null, raw, valid: true };
|
|
106
|
+
return { view: "ui-system", group: group.id, item: parts[2], producto: null, raw, query, valid: true };
|
|
86
107
|
}
|
|
87
108
|
|
|
88
|
-
return { view: "ui-system", group: null, item: null, producto: null, raw, valid: false };
|
|
109
|
+
return { view: "ui-system", group: null, item: null, producto: null, raw, query, valid: false };
|
|
89
110
|
}
|
|
90
111
|
|
|
91
112
|
/** Build a hash for a group/item pair, translating the group id to its slug. */
|
|
@@ -117,6 +138,8 @@ export interface UseHashRouteResult {
|
|
|
117
138
|
goToProducto: (id: string, options?: GoToOptions) => void;
|
|
118
139
|
/** Navigate back to the portfolio (strips the hash). */
|
|
119
140
|
goToPortfolio: (options?: GoToOptions) => void;
|
|
141
|
+
/** Abre el portafolio de demostración pintado desde JSON (`#demo`). */
|
|
142
|
+
goToDemo: (options?: GoToOptions) => void;
|
|
120
143
|
}
|
|
121
144
|
|
|
122
145
|
/**
|
|
@@ -171,6 +194,13 @@ export function useHashRoute(): UseHashRouteResult {
|
|
|
171
194
|
[navigate],
|
|
172
195
|
);
|
|
173
196
|
|
|
197
|
+
const goToDemo = useCallback(
|
|
198
|
+
(options?: GoToOptions) => {
|
|
199
|
+
navigate("#demo", options);
|
|
200
|
+
},
|
|
201
|
+
[navigate],
|
|
202
|
+
);
|
|
203
|
+
|
|
174
204
|
const goToPortfolio = useCallback((options?: GoToOptions) => {
|
|
175
205
|
const url = `${window.location.pathname}${window.location.search}`;
|
|
176
206
|
if (options?.replace) {
|
|
@@ -181,5 +211,5 @@ export function useHashRoute(): UseHashRouteResult {
|
|
|
181
211
|
setRoute(parseHash(""));
|
|
182
212
|
}, []);
|
|
183
213
|
|
|
184
|
-
return { route, goToItem, goToLanding, goToProducto, goToPortfolio };
|
|
214
|
+
return { route, goToItem, goToLanding, goToProducto, goToPortfolio, goToDemo };
|
|
185
215
|
}
|