@axzydev/axzy_ui_system 1.7.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/index.cjs +1938 -491
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.cts +739 -3
  5. package/dist/index.d.ts +739 -3
  6. package/dist/index.js +1830 -417
  7. package/dist/index.js.map +1 -1
  8. package/dist/layered.css +1 -1
  9. package/package.json +14 -7
  10. package/src/App.tsx +60 -2
  11. package/src/components/atoms/image/image.props.ts +2 -0
  12. package/src/components/atoms/image/image.tsx +3 -2
  13. package/src/components/atoms/segmented-control/segmented-control.tsx +3 -0
  14. package/src/components/molecules/card/card.tsx +12 -0
  15. package/src/components/molecules/stat-card/stat-card.tsx +10 -0
  16. package/src/components/organisms/dialog/dialog.props.ts +2 -0
  17. package/src/components/organisms/dialog/dialog.tsx +4 -3
  18. package/src/dev.css +6 -0
  19. package/src/index.ts +90 -1
  20. package/src/json-renderer/catalog/axzyCatalog.ts +362 -0
  21. package/src/json-renderer/catalog/catalog.props.ts +128 -0
  22. package/src/json-renderer/catalog/resolve.ts +225 -0
  23. package/src/json-renderer/index.ts +70 -0
  24. package/src/json-renderer/kernel/NodeBoundary.tsx +53 -0
  25. package/src/json-renderer/kernel/SchemaRenderer.tsx +197 -0
  26. package/src/json-renderer/kernel/kernel.test.tsx +341 -0
  27. package/src/json-renderer/kernel/partial.ts +92 -0
  28. package/src/json-renderer/kernel/renderNode.tsx +113 -0
  29. package/src/json-renderer/kernel/schemaRenderer.props.ts +42 -0
  30. package/src/json-renderer/kernel/types.ts +159 -0
  31. package/src/json-renderer/shared/errors.ts +92 -0
  32. package/src/json-renderer/shared/limits.ts +87 -0
  33. package/src/json-renderer/shared/paths.ts +66 -0
  34. package/src/json-renderer/shared/shared.test.ts +108 -0
  35. package/src/json-renderer/stream/tolerantParse.test.ts +152 -0
  36. package/src/json-renderer/stream/tolerantParse.ts +449 -0
  37. package/src/json-renderer/test/setup.ts +38 -0
  38. package/src/json-renderer/validation/migrate.ts +83 -0
  39. package/src/json-renderer/validation/validate.ts +161 -0
  40. package/src/json-renderer/validation/validation.test.ts +165 -0
  41. package/src/json-renderer/vocabulary/buildContract.ts +158 -0
  42. package/src/sandbox/axzyConfig.test.ts +40 -0
  43. package/src/sandbox/axzyConfig.ts +77 -0
  44. package/src/sandbox/i18n.ts +218 -0
  45. package/src/sandbox/navigation.ts +1 -0
  46. package/src/sandbox/useHashRoute.ts +39 -9
  47. package/src/showcases/DemoJsonDashboard.tsx +489 -0
  48. package/src/showcases/DemoJsonDocs.tsx +339 -0
  49. package/src/showcases/DemoJsonPortfolio.tsx +257 -0
  50. package/src/showcases/JsonScreenShowcase.tsx +341 -0
  51. package/src/showcases/demoApp/demoData.ts +181 -0
  52. package/src/showcases/demoApp/nodeTree.test.ts +39 -0
  53. package/src/showcases/demoApp/nodeTree.ts +35 -0
  54. package/src/showcases/demoApp/schemas.test.ts +54 -0
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.7.0",
7
+ "version": "1.7.2",
8
8
  "type": "module",
9
9
  "sideEffects": [
10
10
  "*.css"
@@ -33,6 +33,8 @@
33
33
  "build:app": "tsc -b && vite build -c vite.app.config.ts",
34
34
  "build:css": "node scripts/build-css.mjs",
35
35
  "lint": "eslint . && node scripts/check-atomic-deps.mjs && node scripts/check-css-isolation.mjs",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
36
38
  "check:atomic": "node scripts/check-atomic-deps.mjs",
37
39
  "check:css": "node scripts/check-css-isolation.mjs",
38
40
  "preview": "vite preview",
@@ -44,7 +46,8 @@
44
46
  "ext:package": "node vscode-extension/esbuild.mjs && cd vscode-extension && npx --yes @vscode/vsce package --no-dependencies",
45
47
  "audit:sandbox": "node scripts/audit-sandbox.mjs",
46
48
  "docs:llm": "pnpm ext:catalog && node scripts/build-llm-docs.mjs && node scripts/build-ai-bundle.mjs",
47
- "docs:ai": "node scripts/build-ai-bundle.mjs"
49
+ "docs:ai": "node scripts/build-ai-bundle.mjs",
50
+ "docs:json-renderer": "node scripts/build-json-renderer-docs.mjs"
48
51
  },
49
52
  "dependencies": {
50
53
  "clsx": "^2.1.1",
@@ -61,22 +64,26 @@
61
64
  },
62
65
  "devDependencies": {
63
66
  "@eslint/js": "9.19.0",
67
+ "@tailwindcss/vite": "^4.0.0",
68
+ "@testing-library/dom": "^10.4.2",
69
+ "@testing-library/react": "^16.3.3",
64
70
  "@types/node": "^22.13.1",
65
71
  "@types/react": "19.0.8",
66
72
  "@types/react-dom": "19.0.3",
73
+ "@types/vscode": "1.85.0",
67
74
  "@vitejs/plugin-react-swc": "3.8.0",
75
+ "esbuild": "^0.25.0",
68
76
  "eslint": "9.19.0",
69
77
  "eslint-plugin-react-hooks": "5.0.0",
70
78
  "eslint-plugin-react-refresh": "0.4.18",
71
79
  "globals": "15.14.0",
80
+ "jsdom": "^26.1.0",
81
+ "tailwindcss": "^4.0.0",
82
+ "tsup": "^8.3.6",
72
83
  "typescript": "~5.7.2",
73
84
  "typescript-eslint": "8.22.0",
74
85
  "vite": "6.1.0",
75
- "@tailwindcss/vite": "^4.0.0",
76
- "tailwindcss": "^4.0.0",
77
- "tsup": "^8.3.6",
78
86
  "vite-plugin-dts": "^4.5.0",
79
- "@types/vscode": "1.85.0",
80
- "esbuild": "^0.25.0"
87
+ "vitest": "^3.2.7"
81
88
  }
82
89
  }
package/src/App.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useMemo, useState } from "react";
2
2
  import { ITLayout, ITThemeProvider, ITI18nProvider, useITTranslation, useDebouncedSearch } from "./index";
3
+ import { DEFAULT_AXZY_CONFIG, mergeAxzyConfig, type AxzyConfig } from "./sandbox/axzyConfig";
3
4
  import type { ITNavigationItem } from "./index";
4
5
 
5
6
  // Import Showcases
@@ -67,6 +68,9 @@ import {
67
68
  } from "./showcases/FeedbackShowcases";
68
69
  import JsonRendererShowcase from "./showcases/JsonRendererShowcase";
69
70
  import JsonEditorShowcase from "./showcases/JsonEditorShowcase";
71
+ import JsonScreenShowcase from "./showcases/JsonScreenShowcase";
72
+ import DemoJsonPortfolio from "./showcases/DemoJsonPortfolio";
73
+ import DemoJsonDashboard, { DEMO_APP_PAGES, type DemoAppPage } from "./showcases/DemoJsonDashboard";
70
74
 
71
75
  // Sandbox routing / landing (dev-only, never published)
72
76
  import {
@@ -90,7 +94,15 @@ import { GROUP_ICONS } from "./sandbox/icons";
90
94
  */
91
95
  function AppShell() {
92
96
  const { t } = useITTranslation();
93
- const { route, goToItem, goToLanding, goToPortfolio } = useHashRoute();
97
+ const { route, goToItem, goToLanding, goToPortfolio, goToDemo } = useHashRoute();
98
+ // Configuración global del sandbox: `public/axzy.json`.
99
+ const [configGlobal, setConfigGlobal] = useState<AxzyConfig>(DEFAULT_AXZY_CONFIG);
100
+ useEffect(() => {
101
+ fetch("/axzy.json")
102
+ .then((r) => r.json())
103
+ .then((json: unknown) => setConfigGlobal(mergeAxzyConfig(json)))
104
+ .catch(() => setConfigGlobal(DEFAULT_AXZY_CONFIG));
105
+ }, []);
94
106
  const [subitemConnector] = useState<"dot" | "|" | "none">("dot");
95
107
 
96
108
  // Debounced term drives filtering; `searchValue` is the live input value.
@@ -208,10 +220,16 @@ function AppShell() {
208
220
  label: t("sandbox.nav.portfolio"),
209
221
  action: () => goToPortfolio(),
210
222
  },
223
+ {
224
+ id: "demo",
225
+ label: t("sandbox.nav.demo"),
226
+ action: () => goToDemo(),
227
+ },
211
228
  ],
212
229
  onNavItemClick: (id: string) => {
213
230
  if (id === "ui-system") goToLanding();
214
231
  else if (id === "portfolio") goToPortfolio();
232
+ else if (id === "demo") goToDemo();
215
233
  },
216
234
  // Sin `userMenu`: el sandbox no tiene sesión. Antes mostraba un usuario
217
235
  // inventado ("Alex Dev / alex@axzy.dev") en un portafolio real.
@@ -319,6 +337,8 @@ function AppShell() {
319
337
  return <JsonRendererShowcase />;
320
338
  case "jsoneditor":
321
339
  return <JsonEditorShowcase />;
340
+ case "jsonscreen":
341
+ return <JsonScreenShowcase />;
322
342
  default:
323
343
  return (
324
344
  <NotFoundShowcase
@@ -331,7 +351,14 @@ function AppShell() {
331
351
  };
332
352
 
333
353
  return (
334
- <ITThemeProvider showFab={false} density={0.98} radius={10} shadow={2}>
354
+ <ITThemeProvider
355
+ showFab={configGlobal.showFab}
356
+ density={configGlobal.density}
357
+ radius={configGlobal.radius}
358
+ shadow={configGlobal.shadow}
359
+ appearance={configGlobal.appearance}
360
+ theme={configGlobal.theme}
361
+ >
335
362
  {route.view === "producto" ? (
336
363
  <div className="min-h-[100dvh] bg-[#fafafa] dark:bg-[#0b0d10]">
337
364
  {route.valid ? (
@@ -347,6 +374,37 @@ function AppShell() {
347
374
  />
348
375
  )}
349
376
  </div>
377
+ ) : route.view === "demo" ? (
378
+ route.group === "app" ? (
379
+ <DemoJsonDashboard
380
+ page={(DEMO_APP_PAGES.includes((route.item ?? "inicio") as DemoAppPage)
381
+ ? route.item
382
+ : "inicio") as DemoAppPage}
383
+ onNavigate={(pagina) => {
384
+ window.location.hash = `#demo/app/${pagina}`;
385
+ }}
386
+ onExit={() => {
387
+ window.location.hash = "#demo";
388
+ }}
389
+ filterEstado={route.query.estado ?? "Todos"}
390
+ onFilterChange={(estado) => {
391
+ const base = `#demo/app/${route.item ?? "inicio"}`;
392
+ window.location.hash = estado === "Todos" ? base : `${base}?estado=${encodeURIComponent(estado)}`;
393
+ }}
394
+ />
395
+ ) : (
396
+ <div className="min-h-[100dvh] bg-[#fafafa] dark:bg-[#0b0d10]">
397
+ <DemoJsonPortfolio
398
+ onBack={() => goToLanding()}
399
+ onOpenProject={(id) => {
400
+ window.location.hash = `#proyecto/${id}`;
401
+ }}
402
+ onOpenPanel={() => {
403
+ window.location.hash = "#demo/app/inicio";
404
+ }}
405
+ />
406
+ </div>
407
+ )
350
408
  ) : route.view === "home" ? (
351
409
  <div className="min-h-[100dvh] bg-[#fafafa] dark:bg-[#0b0d10]">
352
410
  <HomeShowcase />
@@ -7,6 +7,8 @@ export interface ITImageProps {
7
7
  className?: string;
8
8
  /** Fallback image URL shown when the primary `src` fails to load */
9
9
  fallback?: string;
10
+ /** How the image fills its box: "cover" crops to fill (default), "contain" shows the whole image */
11
+ fit?: "cover" | "contain";
10
12
  /** Click handler */
11
13
  onClick?: () => void;
12
14
  }
@@ -26,6 +26,7 @@ const ITImage = ({
26
26
  alt,
27
27
  className = "",
28
28
  fallback = "",
29
+ fit = "cover",
29
30
  }: ITImageProps) => {
30
31
  const [imageError, setImageError] = useState(false);
31
32
  const { t } = useITTranslation();
@@ -42,14 +43,14 @@ const ITImage = ({
42
43
  <img
43
44
  src={fallback}
44
45
  alt={t("image.fallbackAlt")}
45
- className="w-full h-full object-cover"
46
+ className="w-full h-full" style={{ objectFit: fit }}
46
47
  />
47
48
  ) : (
48
49
  <img
49
50
  src={src}
50
51
  alt={alt}
51
52
  onError={handleImageError}
52
- className="w-full h-full object-cover"
53
+ className="w-full h-full" style={{ objectFit: fit }}
53
54
  />
54
55
  )}
55
56
  </div>
@@ -34,6 +34,7 @@ export default function ITSegmentedControl({
34
34
 
35
35
  return (
36
36
  <div
37
+ role="group"
37
38
  className={clsx(
38
39
  "inline-flex rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700",
39
40
  container,
@@ -46,6 +47,8 @@ export default function ITSegmentedControl({
46
47
  return (
47
48
  <button
48
49
  key={opt.value}
50
+ type="button"
51
+ aria-pressed={isActive}
49
52
  onClick={() => !disabled && onChange(opt.value)}
50
53
  disabled={disabled}
51
54
  className={clsx(
@@ -73,6 +73,18 @@ export default function ITCard({
73
73
  onClick={onClick}
74
74
  onMouseEnter={() => onClick && setIsHovered(true)}
75
75
  onMouseLeave={() => onClick && setIsHovered(false)}
76
+ role={onClick ? "button" : undefined}
77
+ tabIndex={onClick ? 0 : undefined}
78
+ onKeyDown={
79
+ onClick
80
+ ? (e) => {
81
+ if (e.key === "Enter" || e.key === " ") {
82
+ e.preventDefault();
83
+ onClick();
84
+ }
85
+ }
86
+ : undefined
87
+ }
76
88
  className={clsx("overflow-hidden flex flex-col", className)}
77
89
  style={containerStyle}
78
90
  >
@@ -36,6 +36,16 @@ export default function ITStatCard({
36
36
  onClick={onClick}
37
37
  role={onClick ? "button" : undefined}
38
38
  tabIndex={onClick ? 0 : undefined}
39
+ onKeyDown={
40
+ onClick
41
+ ? (e) => {
42
+ if (e.key === "Enter" || e.key === " ") {
43
+ e.preventDefault();
44
+ onClick();
45
+ }
46
+ }
47
+ : undefined
48
+ }
39
49
  >
40
50
  <div className="flex items-start justify-between mb-1">
41
51
  <ITText as="span" className="text-xs font-medium text-slate-400 dark:text-slate-500">{label}</ITText>
@@ -11,6 +11,8 @@ export interface ITDialogProps {
11
11
  title?: string;
12
12
  /** When true and `title` is provided, renders an ITFormHeader instead of the default title bar. @default false */
13
13
  useFormHeader?: boolean;
14
+ /** When false, the card has no padding: the title row and body are flush to the edges, so the content can draw its own sections. @default true */
15
+ padded?: boolean;
14
16
  /** Makes the dialog fill the entire viewport. @default false */
15
17
  fullScreen?: boolean;
16
18
  }
@@ -42,6 +42,7 @@ export default function ITDialog({
42
42
  title,
43
43
  useFormHeader = false,
44
44
  fullScreen = false,
45
+ padded = true,
45
46
  }: ITDialogProps) {
46
47
  const { t } = useITTranslation();
47
48
  const modalRef = useRef<HTMLDivElement>(null);
@@ -79,7 +80,7 @@ export default function ITDialog({
79
80
  className={`overflow-hidden relative ${
80
81
  fullScreen
81
82
  ? "w-screen h-screen max-w-none rounded-none m-0 flex flex-col"
82
- : `${className || ""} ${useFormHeader ? "p-0" : "p-6"}`
83
+ : `${className || ""} ${!padded || useFormHeader ? "p-0" : "p-6"}`
83
84
  }`}
84
85
  style={{
85
86
  backgroundColor: theme.card.backgroundColor,
@@ -106,8 +107,8 @@ export default function ITDialog({
106
107
  >
107
108
  <FaRegTimesCircle />
108
109
  </button>
109
- {title && <ITText as="h2" className="text-xl font-semibold mb-4">{title}</ITText>}
110
- <div>{children}</div>
110
+ {title && (padded ? <ITText as="h2" className="text-xl font-semibold mb-4">{title}</ITText> : <div className="flex items-center justify-between gap-3 px-6 py-4 border-b border-slate-200"><ITText as="h2" className="text-lg font-medium">{title}</ITText></div>)}
111
+ <div className={padded ? undefined : "min-h-0 flex-1 flex flex-col"}>{children}</div>
111
112
  </>
112
113
  )}
113
114
  </div>
package/src/dev.css CHANGED
@@ -31,6 +31,12 @@
31
31
  --default-font-family: var(--font-sans);
32
32
  }
33
33
 
34
+ /* El portafolio de demostración vive en `public/demo/*.json` y sus clases
35
+ viajan en el JSON. Tailwind solo genera las clases que ve en el código, así
36
+ que hay que apuntarlo al archivo del esquema: el demo es dev-only y estas
37
+ utilidades no entran en `dist/index.css`. */
38
+ @source "../public/demo/*.json";
39
+
34
40
  /* ──────────────────────────────────────────────
35
41
  SANDBOX-ONLY ANIMATIONS (dev.css)
36
42
  Never shipped: only src/main.tsx imports this file and `pnpm bundle`
package/src/index.ts CHANGED
@@ -66,6 +66,80 @@ import ITWysiwyg from "./components/molecules/wysiwyg/wysiwyg";
66
66
  import ITJsonRenderer from "./components/molecules/json-renderer/json-renderer";
67
67
  import ITJSONEditor from "./components/molecules/json-editor/json-editor";
68
68
 
69
+ // MOTOR DE PANTALLAS DECLARATIVAS (json-renderer)
70
+ import {
71
+ ITSchemaRenderer,
72
+ NodeBoundary,
73
+ renderNode,
74
+ auditTree,
75
+ countPendingNodes,
76
+ axzyCatalog,
77
+ axzyCatalogTypes,
78
+ AXZY_CATALOG_ENTRIES,
79
+ AXZY_EXCLUDED_TYPES,
80
+ createCatalog,
81
+ getCatalogEntry,
82
+ isExcludedType,
83
+ getExclusionReason,
84
+ GLOBALLY_FORBIDDEN_PROPS,
85
+ resolveNode,
86
+ parsePartialSchema,
87
+ isNodePending,
88
+ JsonEngineError,
89
+ issue,
90
+ formatPath,
91
+ parsePath,
92
+ readPath,
93
+ hasPath,
94
+ pushPath,
95
+ propPath,
96
+ DEFAULT_LIMITS,
97
+ resolveLimits,
98
+ createLimitTracker,
99
+ validateSchema,
100
+ CURRENT_SCHEMA_VERSION,
101
+ migrateSchema,
102
+ DEFAULT_MIGRATIONS,
103
+ buildVocabularyContract,
104
+ formatVocabularyForPrompt,
105
+ } from "./json-renderer";
106
+ import type {
107
+ ITJsonRenderEngineProps,
108
+ ScreenSchema,
109
+ NodeSchema,
110
+ SchemaValue,
111
+ SchemaReference,
112
+ WhenSchema,
113
+ ActionSchema,
114
+ EffectSchema,
115
+ DataSourceSchema,
116
+ NodeStatus,
117
+ Catalog,
118
+ CatalogEntry,
119
+ PropSpec,
120
+ PropKind,
121
+ ResolvedNode,
122
+ UnresolvedNode,
123
+ PartialParseResult,
124
+ StreamState,
125
+ JsonEngineIssue,
126
+ JsonEngineCode,
127
+ JsonEngineSeverity,
128
+ SchemaPath,
129
+ PathSegment,
130
+ EngineLimits,
131
+ LimitTracker,
132
+ ValidationReport,
133
+ ValidationVerdict,
134
+ ValidateOptions,
135
+ SchemaMigration,
136
+ MigrationRegistry,
137
+ MigrationResult,
138
+ VocabularyContract,
139
+ ComponentContract,
140
+ PropContract,
141
+ } from "./json-renderer";
142
+
69
143
  // PROPS
70
144
  import type { ITAlertProps } from "./components/molecules/alert/alert.props";
71
145
  import type { ITAvatarProps } from "./components/atoms/avatar/avatar.props";
@@ -168,7 +242,16 @@ export {
168
242
  UploadStatus, FileTypeEnum,
169
243
  getContrastTextColor, isLightColor, resolveCssColor,
170
244
  ITI18nProvider, useITTranslation, esMessages, enMessages,
171
- ITJsonRenderer, ITJSONEditor
245
+ ITJsonRenderer, ITJSONEditor,
246
+ // Motor de pantallas declarativas
247
+ ITSchemaRenderer, NodeBoundary, renderNode, auditTree, countPendingNodes,
248
+ axzyCatalog, axzyCatalogTypes, AXZY_CATALOG_ENTRIES, AXZY_EXCLUDED_TYPES,
249
+ createCatalog, getCatalogEntry, isExcludedType, getExclusionReason, GLOBALLY_FORBIDDEN_PROPS,
250
+ resolveNode, parsePartialSchema, isNodePending,
251
+ JsonEngineError, issue, formatPath, parsePath, readPath, hasPath, pushPath, propPath,
252
+ DEFAULT_LIMITS, resolveLimits, createLimitTracker,
253
+ validateSchema, CURRENT_SCHEMA_VERSION, migrateSchema, DEFAULT_MIGRATIONS,
254
+ buildVocabularyContract, formatVocabularyForPrompt,
172
255
  };
173
256
 
174
257
  export type {
@@ -203,6 +286,12 @@ export {
203
286
  ITWysiwygProps,
204
287
  ITJsonRendererProps, JsonComponentNode, JsonComponentType,
205
288
  ITJSONEditorProps,
289
+ ITJsonRenderEngineProps, ScreenSchema, NodeSchema, SchemaValue, SchemaReference, WhenSchema,
290
+ ActionSchema, EffectSchema, DataSourceSchema, NodeStatus, Catalog, CatalogEntry, PropSpec, PropKind,
291
+ ResolvedNode, UnresolvedNode, PartialParseResult, StreamState, JsonEngineIssue, JsonEngineCode,
292
+ JsonEngineSeverity, SchemaPath, PathSegment, EngineLimits, LimitTracker,
293
+ ValidationReport, ValidationVerdict, ValidateOptions, SchemaMigration, MigrationRegistry, MigrationResult,
294
+ VocabularyContract, ComponentContract, PropContract,
206
295
  UseTableStateOptions, UseTableStateResult
207
296
  };
208
297