pxengine 0.1.4 → 0.1.5
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 +8879 -2083
- package/dist/index.d.cts +100 -5
- package/dist/index.d.ts +100 -5
- package/dist/index.js +8880 -2087
- package/dist/registry.json +1388 -4364
- package/package.json +4 -4
- package/src/atoms/CardAtom.tsx +7 -7
- package/src/atoms/FormInputAtom.tsx +97 -0
- package/src/atoms/FormSelectAtom.tsx +106 -0
- package/src/atoms/FormTextareaAtom.tsx +89 -0
- package/src/atoms/LayoutAtom.tsx +5 -3
- package/src/atoms/index.ts +4 -0
- package/src/components/ui/index.ts +54 -0
- package/src/render/PXEngineRenderer.tsx +240 -39
- package/src/types/atoms.ts +8 -1
|
@@ -3,17 +3,179 @@ import { UIComponent, UISchema } from "../types/schema";
|
|
|
3
3
|
import * as Atoms from "../atoms";
|
|
4
4
|
import * as Molecules from "../molecules/index";
|
|
5
5
|
|
|
6
|
+
// Import all shadcn UI components
|
|
7
|
+
import * as UIComponents from "../components/ui/index";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Components that require specific React Context and cannot be rendered in isolation.
|
|
11
|
+
* These components will show helpful error messages if used directly in schemas.
|
|
12
|
+
*/
|
|
13
|
+
const CONTEXT_DEPENDENT_COMPONENTS = new Set([
|
|
14
|
+
// Form components - require FormField + FormItem context
|
|
15
|
+
"FormLabel",
|
|
16
|
+
"FormControl",
|
|
17
|
+
"FormDescription",
|
|
18
|
+
"FormMessage",
|
|
19
|
+
"FormItem",
|
|
20
|
+
"FormField",
|
|
21
|
+
// Select components - require Select parent
|
|
22
|
+
"SelectContent",
|
|
23
|
+
"SelectItem",
|
|
24
|
+
"SelectValue",
|
|
25
|
+
"SelectTrigger",
|
|
26
|
+
"SelectGroup",
|
|
27
|
+
"SelectLabel",
|
|
28
|
+
"SelectSeparator",
|
|
29
|
+
// Accordion components - require Accordion parent
|
|
30
|
+
"AccordionContent",
|
|
31
|
+
"AccordionItem",
|
|
32
|
+
"AccordionTrigger",
|
|
33
|
+
// Tabs components - require Tabs parent
|
|
34
|
+
"TabsContent",
|
|
35
|
+
"TabsList",
|
|
36
|
+
"TabsTrigger",
|
|
37
|
+
// Dialog components - require Dialog parent
|
|
38
|
+
"DialogContent",
|
|
39
|
+
"DialogHeader",
|
|
40
|
+
"DialogFooter",
|
|
41
|
+
"DialogTitle",
|
|
42
|
+
"DialogDescription",
|
|
43
|
+
"DialogClose",
|
|
44
|
+
// Sheet components - require Sheet parent
|
|
45
|
+
"SheetContent",
|
|
46
|
+
"SheetHeader",
|
|
47
|
+
"SheetFooter",
|
|
48
|
+
"SheetTitle",
|
|
49
|
+
"SheetDescription",
|
|
50
|
+
"SheetClose",
|
|
51
|
+
// AlertDialog components - require AlertDialog parent
|
|
52
|
+
"AlertDialogContent",
|
|
53
|
+
"AlertDialogHeader",
|
|
54
|
+
"AlertDialogFooter",
|
|
55
|
+
"AlertDialogTitle",
|
|
56
|
+
"AlertDialogDescription",
|
|
57
|
+
"AlertDialogAction",
|
|
58
|
+
"AlertDialogCancel",
|
|
59
|
+
// Dropdown components - require DropdownMenu parent
|
|
60
|
+
"DropdownMenuContent",
|
|
61
|
+
"DropdownMenuItem",
|
|
62
|
+
"DropdownMenuLabel",
|
|
63
|
+
"DropdownMenuSeparator",
|
|
64
|
+
"DropdownMenuCheckboxItem",
|
|
65
|
+
"DropdownMenuRadioItem",
|
|
66
|
+
"DropdownMenuRadioGroup",
|
|
67
|
+
// Popover components - require Popover parent
|
|
68
|
+
"PopoverContent",
|
|
69
|
+
// Tooltip components - require TooltipProvider parent
|
|
70
|
+
"TooltipContent",
|
|
71
|
+
// Context Menu components
|
|
72
|
+
"ContextMenuContent",
|
|
73
|
+
"ContextMenuItem",
|
|
74
|
+
// Navigation Menu components
|
|
75
|
+
"NavigationMenuContent",
|
|
76
|
+
"NavigationMenuItem",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Mapping of problematic components to their Atom alternatives
|
|
81
|
+
*/
|
|
82
|
+
const COMPONENT_SUGGESTIONS: Record<string, string> = {
|
|
83
|
+
FormLabel: "FormInputAtom (with label prop)",
|
|
84
|
+
FormControl: "FormInputAtom",
|
|
85
|
+
FormItem: "FormInputAtom",
|
|
86
|
+
FormField: "FormInputAtom",
|
|
87
|
+
Select: "FormSelectAtom or InputAtom with inputType='select'",
|
|
88
|
+
SelectContent: "FormSelectAtom or InputAtom with inputType='select'",
|
|
89
|
+
SelectItem: "FormSelectAtom or InputAtom with inputType='select'",
|
|
90
|
+
Tabs: "TabsAtom",
|
|
91
|
+
TabsContent: "TabsAtom",
|
|
92
|
+
TabsList: "TabsAtom",
|
|
93
|
+
Dialog: "DialogAtom",
|
|
94
|
+
DialogContent: "DialogAtom",
|
|
95
|
+
Sheet: "SheetAtom",
|
|
96
|
+
SheetContent: "SheetAtom",
|
|
97
|
+
AlertDialog: "AlertDialogAtom",
|
|
98
|
+
AlertDialogContent: "AlertDialogAtom",
|
|
99
|
+
Accordion: "AccordionAtom",
|
|
100
|
+
AccordionItem: "AccordionAtom",
|
|
101
|
+
Input: "InputAtom",
|
|
102
|
+
Textarea: "InputAtom with inputType='textarea'",
|
|
103
|
+
};
|
|
104
|
+
|
|
6
105
|
/**
|
|
7
106
|
* PXEngineRenderer
|
|
8
107
|
*
|
|
9
108
|
* Handles both the full schema { version, root } and individual components.
|
|
10
|
-
* Dynamically resolves components from
|
|
109
|
+
* Dynamically resolves components from Atoms/Molecules/UI Components registry.
|
|
110
|
+
* Prevents rendering of context-dependent components to avoid React errors.
|
|
11
111
|
*/
|
|
12
112
|
interface PXEngineRendererProps {
|
|
13
113
|
schema: UISchema | UIComponent;
|
|
14
114
|
onAction?: (action: string, payload?: any) => void;
|
|
15
115
|
}
|
|
16
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Renders an error message for context-dependent components
|
|
119
|
+
*/
|
|
120
|
+
const renderContextDependentError = (
|
|
121
|
+
componentName: string,
|
|
122
|
+
normalizedName: string,
|
|
123
|
+
key: string,
|
|
124
|
+
): React.ReactNode => {
|
|
125
|
+
const suggestion =
|
|
126
|
+
COMPONENT_SUGGESTIONS[normalizedName] ||
|
|
127
|
+
`${componentName}Atom (if available)`;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
key={key}
|
|
132
|
+
className="p-4 border-2 border-amber-500/50 rounded-lg bg-amber-50/80 space-y-2 my-2"
|
|
133
|
+
>
|
|
134
|
+
<div className="flex items-start gap-2">
|
|
135
|
+
<span className="text-amber-600 font-bold text-lg">⚠️</span>
|
|
136
|
+
<div className="flex-1">
|
|
137
|
+
<p className="text-sm font-semibold text-amber-900">
|
|
138
|
+
Invalid Component: {componentName}
|
|
139
|
+
</p>
|
|
140
|
+
<p className="text-xs text-amber-700 mt-1">
|
|
141
|
+
This component requires React Context and cannot be rendered
|
|
142
|
+
directly in schemas.
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="bg-white/60 p-3 rounded border border-amber-200">
|
|
147
|
+
<p className="text-xs font-semibold text-gray-700 mb-1.5">
|
|
148
|
+
✓ Use instead:
|
|
149
|
+
</p>
|
|
150
|
+
<code className="text-xs text-blue-700 bg-blue-50 px-2 py-1 rounded">
|
|
151
|
+
{suggestion}
|
|
152
|
+
</code>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Renders an error message for components not found in any registry
|
|
160
|
+
*/
|
|
161
|
+
const renderNotFoundError = (
|
|
162
|
+
componentName: string,
|
|
163
|
+
key: string,
|
|
164
|
+
): React.ReactNode => {
|
|
165
|
+
return (
|
|
166
|
+
<div
|
|
167
|
+
key={key}
|
|
168
|
+
className="p-3 border border-dashed border-red-500/50 text-red-500 text-xs rounded bg-red-50/30 my-2"
|
|
169
|
+
>
|
|
170
|
+
<span className="font-semibold">❌ Unknown Component:</span>{" "}
|
|
171
|
+
{componentName}
|
|
172
|
+
<p className="text-[10px] text-red-400 mt-1">
|
|
173
|
+
Component not found in Atoms, Molecules, or UI Components registry.
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
17
179
|
export const PXEngineRenderer: React.FC<PXEngineRendererProps> = ({
|
|
18
180
|
schema,
|
|
19
181
|
onAction,
|
|
@@ -25,69 +187,108 @@ export const PXEngineRenderer: React.FC<PXEngineRendererProps> = ({
|
|
|
25
187
|
|
|
26
188
|
const renderRecursive = (
|
|
27
189
|
component: UIComponent | string | any,
|
|
190
|
+
index?: number,
|
|
28
191
|
): React.ReactNode => {
|
|
29
192
|
// 1. Handle text nodes (string or number)
|
|
30
193
|
if (typeof component === "string" || typeof component === "number") {
|
|
31
194
|
return component;
|
|
32
195
|
}
|
|
33
196
|
|
|
34
|
-
|
|
197
|
+
// 2. Handle already rendered React elements
|
|
198
|
+
if (React.isValidElement(component)) {
|
|
199
|
+
return component;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!component || typeof component !== "object") return null;
|
|
35
203
|
|
|
36
204
|
const { type, name, props = {}, children = [], id } = component;
|
|
37
205
|
|
|
38
206
|
// Determine the component name to search for
|
|
39
207
|
const componentName = name || type;
|
|
40
|
-
if (!componentName) return null;
|
|
208
|
+
if (!componentName || typeof componentName !== "string") return null;
|
|
41
209
|
|
|
42
|
-
//
|
|
210
|
+
// Generate a unique key from id, index, or random
|
|
211
|
+
const uniqueKey =
|
|
212
|
+
id ||
|
|
213
|
+
`${componentName}-${index || Math.random().toString(36).substr(2, 9)}`;
|
|
214
|
+
|
|
215
|
+
// Normalize name to PascalCase
|
|
43
216
|
const normalizedName =
|
|
44
217
|
componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
|
45
218
|
const atomName = normalizedName.endsWith("Atom")
|
|
46
219
|
? normalizedName
|
|
47
220
|
: `${normalizedName}Atom`;
|
|
48
221
|
|
|
49
|
-
//
|
|
50
|
-
|
|
222
|
+
// 3. Resolve Component from registries (PRIORITY ORDER - safest first)
|
|
223
|
+
|
|
224
|
+
// Priority 1: Atoms (schema-safe, self-contained)
|
|
225
|
+
let TargetComponent =
|
|
51
226
|
(Atoms as any)[atomName] ||
|
|
52
227
|
(Atoms as any)[normalizedName] ||
|
|
53
|
-
(Atoms as any)[componentName]
|
|
54
|
-
(Molecules as any)[normalizedName] ||
|
|
55
|
-
(Molecules as any)[componentName];
|
|
228
|
+
(Atoms as any)[componentName];
|
|
56
229
|
|
|
230
|
+
// Priority 2: Molecules (schema-safe, composite)
|
|
57
231
|
if (!TargetComponent) {
|
|
58
|
-
|
|
232
|
+
TargetComponent =
|
|
233
|
+
(Molecules as any)[normalizedName] || (Molecules as any)[componentName];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Priority 3: UI Components (ONLY if NOT context-dependent)
|
|
237
|
+
if (!TargetComponent && !CONTEXT_DEPENDENT_COMPONENTS.has(normalizedName)) {
|
|
238
|
+
TargetComponent =
|
|
239
|
+
(UIComponents as any)[normalizedName] ||
|
|
240
|
+
(UIComponents as any)[componentName];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 4. Handle component not found or context-dependent
|
|
244
|
+
if (!TargetComponent) {
|
|
245
|
+
if (CONTEXT_DEPENDENT_COMPONENTS.has(normalizedName)) {
|
|
246
|
+
// Show helpful error for context-dependent components
|
|
247
|
+
if (process.env.NODE_ENV === "development") {
|
|
248
|
+
console.error(
|
|
249
|
+
`[PXEngineRenderer] Cannot render context-dependent component: ${componentName}. ` +
|
|
250
|
+
`Use ${COMPONENT_SUGGESTIONS[normalizedName] || `${componentName}Atom`} instead.`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
return renderContextDependentError(
|
|
254
|
+
componentName,
|
|
255
|
+
normalizedName,
|
|
256
|
+
uniqueKey,
|
|
257
|
+
);
|
|
258
|
+
} else {
|
|
259
|
+
// Show generic "not found" error
|
|
260
|
+
console.warn(
|
|
261
|
+
`[PXEngineRenderer] Component not found: ${componentName}`,
|
|
262
|
+
);
|
|
263
|
+
return renderNotFoundError(componentName, uniqueKey);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 4. Determine if component expects 'renderComponent' prop (for Atoms with children management)
|
|
268
|
+
const isAtomWithRenderProp = atomName in Atoms;
|
|
269
|
+
|
|
270
|
+
// 5. Render Component
|
|
271
|
+
if (isAtomWithRenderProp) {
|
|
272
|
+
// Atoms handle their own children via renderComponent
|
|
59
273
|
return (
|
|
60
|
-
<
|
|
61
|
-
key={
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
274
|
+
<TargetComponent
|
|
275
|
+
key={uniqueKey}
|
|
276
|
+
{...props}
|
|
277
|
+
onAction={onAction}
|
|
278
|
+
renderComponent={renderRecursive}
|
|
279
|
+
children={children}
|
|
280
|
+
/>
|
|
281
|
+
);
|
|
282
|
+
} else {
|
|
283
|
+
// Standard shadcn components - pass children as React children
|
|
284
|
+
return (
|
|
285
|
+
<TargetComponent key={uniqueKey} {...props}>
|
|
286
|
+
{Array.isArray(children)
|
|
287
|
+
? children.map((child, idx) => renderRecursive(child, idx))
|
|
288
|
+
: children}
|
|
289
|
+
</TargetComponent>
|
|
66
290
|
);
|
|
67
291
|
}
|
|
68
|
-
|
|
69
|
-
// 3. Render Component
|
|
70
|
-
// We pass 'renderComponent' for components that handle their own child rendering (like Layout/Card)
|
|
71
|
-
// We also pass children as props for standard React children behavior
|
|
72
|
-
return (
|
|
73
|
-
<TargetComponent
|
|
74
|
-
key={id || Math.random().toString(36).substr(2, 9)}
|
|
75
|
-
{...props}
|
|
76
|
-
onAction={onAction}
|
|
77
|
-
renderComponent={renderRecursive}
|
|
78
|
-
children={Array.isArray(children) ? children : undefined}
|
|
79
|
-
>
|
|
80
|
-
{Array.isArray(children)
|
|
81
|
-
? children.map((child, idx) => (
|
|
82
|
-
<React.Fragment key={idx}>
|
|
83
|
-
{renderRecursive(child)}
|
|
84
|
-
</React.Fragment>
|
|
85
|
-
))
|
|
86
|
-
: typeof children === "string"
|
|
87
|
-
? children
|
|
88
|
-
: null}
|
|
89
|
-
</TargetComponent>
|
|
90
|
-
);
|
|
91
292
|
};
|
|
92
293
|
|
|
93
294
|
return <div className="px-engine-root">{renderRecursive(root)}</div>;
|
package/src/types/atoms.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { FormInputAtomType } from "../atoms/FormInputAtom";
|
|
2
|
+
import type { FormSelectAtomType } from "../atoms/FormSelectAtom";
|
|
3
|
+
import type { FormTextareaAtomType } from "../atoms/FormTextareaAtom";
|
|
4
|
+
|
|
1
5
|
export type LayoutDirection = "vertical" | "horizontal" | "grid";
|
|
2
6
|
export type GapSize = "none" | "sm" | "md" | "lg" | "xl";
|
|
3
7
|
export type TextVariant = "h1" | "h2" | "h3" | "h4" | "p" | "small" | "muted" | "label";
|
|
@@ -291,4 +295,7 @@ export type UIAtom =
|
|
|
291
295
|
| BreadcrumbAtomType
|
|
292
296
|
| CalendarAtomType
|
|
293
297
|
| PaginationAtomType
|
|
294
|
-
| CommandAtomType
|
|
298
|
+
| CommandAtomType
|
|
299
|
+
| FormInputAtomType
|
|
300
|
+
| FormSelectAtomType
|
|
301
|
+
| FormTextareaAtomType;
|