@ramme-io/create-app 1.2.2 → 1.2.6
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/package.json +9 -14
- package/template/package.json +5 -3
- package/template/pkg.json +11 -7
- package/template/src/App.tsx +11 -1
- package/template/src/components/AppHeader.tsx +10 -10
- package/template/src/components/dashboard/ChartLine.tsx +28 -0
- package/template/src/components/dashboard/StatCard.tsx +61 -0
- package/template/src/config/component-registry.tsx +57 -44
- package/template/src/engine/renderers/DynamicBlock.tsx +1 -1
- package/template/src/engine/renderers/DynamicPage.tsx +134 -98
- package/template/src/engine/runtime/ManifestContext.tsx +79 -0
- package/template/src/engine/runtime/MqttContext.tsx +16 -21
- package/template/src/engine/runtime/data-seeder.ts +9 -7
- package/template/src/engine/runtime/useAction.ts +7 -21
- package/template/src/engine/runtime/useDynamicSitemap.tsx +43 -0
- package/template/src/engine/runtime/useJustInTimeSeeder.ts +76 -0
- package/template/src/engine/runtime/useLiveBridge.ts +44 -0
- package/template/src/engine/runtime/useSignal.ts +10 -21
- package/template/src/engine/runtime/useWorkflowEngine.ts +23 -78
- package/template/src/features/datagrid/SmartTable.tsx +38 -20
- package/template/src/features/developer/GhostOverlay.tsx +67 -21
- package/template/src/features/visualizations/SmartChart.tsx +178 -0
- package/template/src/main.tsx +25 -13
- package/template/src/templates/dashboard/DashboardLayout.tsx +19 -18
- package/template/src/templates/dashboard/dashboard.sitemap.ts +1 -8
- package/template/tailwind.config.cjs +10 -9
- package/template/vite.config.ts +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramme-io/create-app",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-ramme-app": "./index.js"
|
|
@@ -9,14 +9,6 @@
|
|
|
9
9
|
"index.js",
|
|
10
10
|
"template"
|
|
11
11
|
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"dev": "cd template && vite",
|
|
14
|
-
"build": "cd template && tsc && vite build",
|
|
15
|
-
"preview": "cd template && vite preview",
|
|
16
|
-
"pack-tarball": "npm pack",
|
|
17
|
-
"bundle:ai": "repomix --ignore '**/*.lock,**/dist/**,**/template/dist/**,**/template/node_modules/**' --output 'ramme-starter-context.txt'",
|
|
18
|
-
"zip:builder": "cd template && zip -r ../base.zip . -x \"node_modules/*\" \"dist/*\" \".DS_Store\" && mv ../base.zip ../../ramme-app-builder/public/base.zip && echo '✅ base.zip updated in Builder!'"
|
|
19
|
-
},
|
|
20
12
|
"dependencies": {
|
|
21
13
|
"ag-grid-community": "^34.1.2",
|
|
22
14
|
"ag-grid-enterprise": "^34.1.2",
|
|
@@ -39,9 +31,12 @@
|
|
|
39
31
|
"typescript": "^5.2.2",
|
|
40
32
|
"vite": "^5.2.0"
|
|
41
33
|
},
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
"scripts": {
|
|
35
|
+
"dev": "cd template && vite",
|
|
36
|
+
"build": "cd template && tsc && vite build",
|
|
37
|
+
"preview": "cd template && vite preview",
|
|
38
|
+
"pack-tarball": "npm pack",
|
|
39
|
+
"bundle:ai": "repomix --ignore '**/*.lock,**/dist/**,**/template/dist/**,**/template/node_modules/**' --output 'ramme-starter-context.txt'",
|
|
40
|
+
"zip:builder": "cd template && zip -r ../base.zip . -x \"node_modules/*\" \"dist/*\" \".DS_Store\" && mv ../base.zip ../../ramme-app-builder/public/base.zip && echo '✅ base.zip updated in Builder!'"
|
|
46
41
|
}
|
|
47
|
-
}
|
|
42
|
+
}
|
package/template/package.json
CHANGED
|
@@ -10,19 +10,20 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@ai-sdk/google": "^0.0.10",
|
|
13
|
-
"@ramme-io/ui": "^1.2.
|
|
14
|
-
"@types/uuid": "^10.0.0",
|
|
13
|
+
"@ramme-io/ui": "^1.2.3",
|
|
15
14
|
"ag-grid-community": "^31.3.1",
|
|
16
15
|
"ag-grid-enterprise": "^31.3.1",
|
|
17
16
|
"ag-grid-react": "^31.3.1",
|
|
18
17
|
"ai": "^3.0.0",
|
|
19
18
|
"framer-motion": "^11.0.0",
|
|
20
19
|
"fs-extra": "^11.2.0",
|
|
20
|
+
"lucide-react": "^0.454.0",
|
|
21
21
|
"mqtt": "^5.3.5",
|
|
22
22
|
"react": "^18.2.0",
|
|
23
23
|
"react-dom": "^18.2.0",
|
|
24
24
|
"react-resizable-panels": "^2.0.9",
|
|
25
25
|
"react-router-dom": "^6.22.0",
|
|
26
|
+
"recharts": "^2.12.7",
|
|
26
27
|
"uuid": "^13.0.0",
|
|
27
28
|
"zod": "^3.22.0",
|
|
28
29
|
"zustand": "^4.5.7"
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
"@types/node": "^20.11.0",
|
|
32
33
|
"@types/react": "^18.2.0",
|
|
33
34
|
"@types/react-dom": "^18.2.0",
|
|
35
|
+
"@types/uuid": "^10.0.0",
|
|
34
36
|
"@vitejs/plugin-react": "^4.2.0",
|
|
35
37
|
"autoprefixer": "^10.4.19",
|
|
36
38
|
"postcss": "^8.4.38",
|
|
@@ -38,4 +40,4 @@
|
|
|
38
40
|
"typescript": "^5.2.0",
|
|
39
41
|
"vite": "^5.2.0"
|
|
40
42
|
}
|
|
41
|
-
}
|
|
43
|
+
}
|
package/template/pkg.json
CHANGED
|
@@ -9,26 +9,30 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@
|
|
13
|
-
"
|
|
14
|
-
"zustand": "^4.5.0",
|
|
12
|
+
"@ai-sdk/google": "^0.0.10",
|
|
13
|
+
"@ramme-io/ui": "^1.2.3",
|
|
15
14
|
"ag-grid-community": "^31.3.1",
|
|
16
15
|
"ag-grid-enterprise": "^31.3.1",
|
|
17
16
|
"ag-grid-react": "^31.3.1",
|
|
17
|
+
"ai": "^3.0.0",
|
|
18
18
|
"framer-motion": "^11.0.0",
|
|
19
19
|
"fs-extra": "^11.2.0",
|
|
20
|
+
"lucide-react": "^0.454.0",
|
|
21
|
+
"mqtt": "^5.3.5",
|
|
20
22
|
"react": "^18.2.0",
|
|
21
23
|
"react-dom": "^18.2.0",
|
|
22
|
-
"react-router-dom": "^6.22.0",
|
|
23
24
|
"react-resizable-panels": "^2.0.9",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"react-router-dom": "^6.22.0",
|
|
26
|
+
"recharts": "^2.12.7",
|
|
27
|
+
"uuid": "^13.0.0",
|
|
28
|
+
"zod": "^3.22.0",
|
|
29
|
+
"zustand": "^4.5.7"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@types/node": "^20.11.0",
|
|
30
33
|
"@types/react": "^18.2.0",
|
|
31
34
|
"@types/react-dom": "^18.2.0",
|
|
35
|
+
"@types/uuid": "^10.0.0",
|
|
32
36
|
"@vitejs/plugin-react": "^4.2.0",
|
|
33
37
|
"autoprefixer": "^10.4.19",
|
|
34
38
|
"postcss": "^8.4.38",
|
package/template/src/App.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { ThemeProvider } from '@ramme-io/ui';
|
|
|
5
5
|
import { AuthProvider } from './features/auth/AuthContext';
|
|
6
6
|
import { MqttProvider } from './engine/runtime/MqttContext';
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
// --- 1. IMPORT AUTH CLUSTER ---
|
|
9
10
|
import { AuthLayout } from './features/auth/pages/AuthLayout';
|
|
10
11
|
import LoginPage from './features/auth/pages/LoginPage';
|
|
@@ -13,6 +14,7 @@ import SignupPage from './features/auth/pages/SignupPage';
|
|
|
13
14
|
// --- 2. IMPORT TEMPLATES ---
|
|
14
15
|
import DashboardLayout from './templates/dashboard/DashboardLayout';
|
|
15
16
|
import { dashboardSitemap } from './templates/dashboard/dashboard.sitemap';
|
|
17
|
+
import { useDynamicSitemap } from './engine/runtime/useDynamicSitemap';
|
|
16
18
|
import DocsLayout from './templates/docs/DocsLayout';
|
|
17
19
|
import { docsSitemap } from './templates/docs/docs.sitemap';
|
|
18
20
|
import SettingsLayout from './templates/settings/SettingsLayout';
|
|
@@ -28,6 +30,7 @@ import { initializeDataLake } from './engine/runtime/data-seeder';
|
|
|
28
30
|
import ScrollToTop from './components/ScrollToTop';
|
|
29
31
|
import HashLinkScroll from './components/HashLinkScroll';
|
|
30
32
|
|
|
33
|
+
|
|
31
34
|
function App() {
|
|
32
35
|
|
|
33
36
|
// Trigger data seeding on mount
|
|
@@ -35,8 +38,14 @@ function App() {
|
|
|
35
38
|
initializeDataLake();
|
|
36
39
|
}, []);
|
|
37
40
|
|
|
41
|
+
const liveDashboardRoutes = useDynamicSitemap(dashboardSitemap);
|
|
42
|
+
|
|
43
|
+
console.log("🗺️ ROUTER MAP:", liveDashboardRoutes.map(r => ({ path: r.path, id: r.id })));
|
|
44
|
+
|
|
45
|
+
|
|
38
46
|
return (
|
|
39
47
|
<ThemeProvider>
|
|
48
|
+
|
|
40
49
|
<AuthProvider>
|
|
41
50
|
<MqttProvider>
|
|
42
51
|
<ScrollToTop />
|
|
@@ -60,7 +69,7 @@ function App() {
|
|
|
60
69
|
{/* Dashboard Template */}
|
|
61
70
|
<Route path="/dashboard/*" element={<DashboardLayout />}>
|
|
62
71
|
<Route index element={<Navigate to="welcome" replace />} />
|
|
63
|
-
{generateRoutes(
|
|
72
|
+
{generateRoutes(liveDashboardRoutes)}
|
|
64
73
|
</Route>
|
|
65
74
|
|
|
66
75
|
{/* Docs Template */}
|
|
@@ -79,6 +88,7 @@ function App() {
|
|
|
79
88
|
</Routes>
|
|
80
89
|
</MqttProvider>
|
|
81
90
|
</AuthProvider>
|
|
91
|
+
|
|
82
92
|
</ThemeProvider>
|
|
83
93
|
);
|
|
84
94
|
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @description This is the global application header.
|
|
5
5
|
*
|
|
6
6
|
* @developer_notes
|
|
7
|
-
* STRATEGIC REFACTOR
|
|
8
|
-
* 1.
|
|
9
|
-
* 2. Kept all A.D.A.P.T.
|
|
7
|
+
* STRATEGIC REFACTOR:
|
|
8
|
+
* 1. Cleaned up Ghost Bridge telemetry for production feel.
|
|
9
|
+
* 2. Kept all A.D.A.P.T. navigation and z-index fixes.
|
|
10
10
|
*/
|
|
11
11
|
import React from 'react';
|
|
12
12
|
import { Link } from 'react-router-dom';
|
|
@@ -25,7 +25,7 @@ import { useAuth } from '../features/auth/AuthContext';
|
|
|
25
25
|
import { appManifest } from '../config/navigation';
|
|
26
26
|
import type { ManifestLink } from '../engine/types/manifest-types';
|
|
27
27
|
import TemplateSwitcher from './TemplateSwitcher';
|
|
28
|
-
import rammeLogo from '../assets/orange.png';
|
|
28
|
+
import rammeLogo from '../assets/orange.png';
|
|
29
29
|
|
|
30
30
|
const AppHeader: React.FC = () => {
|
|
31
31
|
const { theme, availableThemes, setTheme } = useTheme();
|
|
@@ -47,13 +47,13 @@ const AppHeader: React.FC = () => {
|
|
|
47
47
|
// Z-INDEX FIX: Set to z-50 to render above the z-40 sidebar.
|
|
48
48
|
<header className="flex items-center justify-between p-4 bg-card border-b border-border shadow-sm sticky top-0 z-50">
|
|
49
49
|
|
|
50
|
-
{/* ---
|
|
51
|
-
{/* Logo - links to dashboard home */}
|
|
50
|
+
{/* --- 1. LOGO BLOCK --- */}
|
|
52
51
|
<Link to="/dashboard" className="flex items-center gap-2">
|
|
53
52
|
<img src={rammeLogo} alt="Ramme Logo" className="h-8 w-auto" />
|
|
54
53
|
<h1 className="text-2xl font-bold text-text">Ramme</h1>
|
|
55
54
|
</Link>
|
|
56
55
|
|
|
56
|
+
{/* --- 2. USER ACTIONS --- */}
|
|
57
57
|
<div className="flex items-center gap-4">
|
|
58
58
|
<TemplateSwitcher />
|
|
59
59
|
{user && (
|
|
@@ -64,14 +64,14 @@ const AppHeader: React.FC = () => {
|
|
|
64
64
|
</button>
|
|
65
65
|
}
|
|
66
66
|
>
|
|
67
|
-
{/*
|
|
67
|
+
{/* User Info */}
|
|
68
68
|
<div className="p-2 text-sm text-muted-foreground">
|
|
69
69
|
<p className="font-semibold text-text">{user.name}</p>
|
|
70
70
|
<p>{user.email}</p>
|
|
71
71
|
</div>
|
|
72
72
|
<MenuDivider />
|
|
73
73
|
|
|
74
|
-
{/*
|
|
74
|
+
{/* Navigation Links */}
|
|
75
75
|
{userNavLinks.map((link: ManifestLink) => (
|
|
76
76
|
<MenuItem
|
|
77
77
|
key={link.id}
|
|
@@ -83,7 +83,7 @@ const AppHeader: React.FC = () => {
|
|
|
83
83
|
))}
|
|
84
84
|
<MenuDivider />
|
|
85
85
|
|
|
86
|
-
{/*
|
|
86
|
+
{/* Theme Switcher */}
|
|
87
87
|
<div className="p-2 text-sm text-muted-foreground">
|
|
88
88
|
<p>
|
|
89
89
|
Active Theme:{' '}
|
|
@@ -100,7 +100,7 @@ const AppHeader: React.FC = () => {
|
|
|
100
100
|
))}
|
|
101
101
|
<MenuDivider />
|
|
102
102
|
|
|
103
|
-
{/*
|
|
103
|
+
{/* Actions */}
|
|
104
104
|
<MenuItem onClick={handleResetData} icon={<Icon name="refresh-cw" />}>
|
|
105
105
|
Reset Data
|
|
106
106
|
</MenuItem>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '@ramme-io/ui';
|
|
3
|
+
|
|
4
|
+
export const ChartLine: React.FC<{ title: string; description?: string }> = ({ title, description }) => {
|
|
5
|
+
return (
|
|
6
|
+
<Card className="p-6 h-full min-h-[300px] flex flex-col">
|
|
7
|
+
<div className="mb-6">
|
|
8
|
+
<h3 className="font-semibold text-lg">{title}</h3>
|
|
9
|
+
{description && <p className="text-sm text-muted-foreground">{description}</p>}
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
{/* Visual Mockup of a Chart */}
|
|
13
|
+
<div className="flex-1 flex items-end justify-between gap-2 px-2 pb-2 mt-4 border-b border-l border-border/50">
|
|
14
|
+
{[40, 70, 45, 90, 65, 85, 55, 95, 75, 60, 90, 100].map((h, i) => (
|
|
15
|
+
<div key={i} className="w-full flex flex-col items-center gap-2 group">
|
|
16
|
+
<div
|
|
17
|
+
className="w-full bg-primary/20 group-hover:bg-primary/80 transition-all rounded-t-sm"
|
|
18
|
+
style={{ height: `${h}%` }}
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
<div className="flex justify-between mt-2 text-xs text-muted-foreground px-2">
|
|
24
|
+
<span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span>
|
|
25
|
+
</div>
|
|
26
|
+
</Card>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '@ramme-io/ui';
|
|
3
|
+
import { ArrowUpRight, ArrowDownRight, Activity, Users, DollarSign, CreditCard } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
interface StatCardProps {
|
|
6
|
+
title: string;
|
|
7
|
+
value: string | number;
|
|
8
|
+
trend?: string;
|
|
9
|
+
status?: 'positive' | 'negative' | 'neutral' | 'offline';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const StatCard: React.FC<StatCardProps> = ({
|
|
13
|
+
title,
|
|
14
|
+
value,
|
|
15
|
+
trend,
|
|
16
|
+
status = 'neutral'
|
|
17
|
+
}) => {
|
|
18
|
+
const statusColors = {
|
|
19
|
+
positive: 'text-green-600 bg-green-100',
|
|
20
|
+
negative: 'text-red-600 bg-red-100',
|
|
21
|
+
neutral: 'text-blue-600 bg-blue-100',
|
|
22
|
+
offline: 'text-gray-400 bg-gray-100',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getIcon = () => {
|
|
26
|
+
switch(title?.toLowerCase()) {
|
|
27
|
+
case 'active users': return <Users size={20} />;
|
|
28
|
+
case 'revenue': return <DollarSign size={20} />;
|
|
29
|
+
case 'sales': return <CreditCard size={20} />;
|
|
30
|
+
default: return <Activity size={20} />;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Card className="p-6 flex flex-col justify-between h-full border-border/60 hover:shadow-md transition-all">
|
|
36
|
+
<div className="flex justify-between items-start mb-4">
|
|
37
|
+
<div>
|
|
38
|
+
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
39
|
+
<h3 className="text-2xl font-bold mt-1 tracking-tight">{value || '--'}</h3>
|
|
40
|
+
</div>
|
|
41
|
+
<div className={`p-2 rounded-lg ${statusColors[status] || statusColors.neutral}`}>
|
|
42
|
+
{getIcon()}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{trend && (
|
|
47
|
+
<div className="flex items-center gap-1 text-xs font-medium">
|
|
48
|
+
{trend.startsWith('+') ? (
|
|
49
|
+
<ArrowUpRight size={14} className="text-green-600" />
|
|
50
|
+
) : (
|
|
51
|
+
<ArrowDownRight size={14} className="text-red-600" />
|
|
52
|
+
)}
|
|
53
|
+
<span className={trend.startsWith('+') ? 'text-green-600' : 'text-red-600'}>
|
|
54
|
+
{trend}
|
|
55
|
+
</span>
|
|
56
|
+
<span className="text-muted-foreground ml-1">from last month</span>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</Card>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -1,56 +1,69 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
DataTable,
|
|
9
|
-
Card,
|
|
10
|
-
Alert,
|
|
11
|
-
EmptyState,
|
|
12
|
-
ToggleSwitch
|
|
13
|
-
} from '@ramme-io/ui';
|
|
14
|
-
|
|
15
|
-
// ✅ IMPORT YOUR CUSTOM COMPONENT
|
|
2
|
+
// ✅ 1. Import the Master Registry from the UI Library (Tier 1: Atoms)
|
|
3
|
+
// This instantly pulls in Button, Card, Inputs, Layouts, Navigation, etc.
|
|
4
|
+
import { AUTO_REGISTRY } from '@ramme-io/ui';
|
|
5
|
+
|
|
6
|
+
// ✅ 2. Import Local "Smart Blocks" (Tier 2: Logic)
|
|
7
|
+
// These exist only in the App (Starter), not the UI library.
|
|
16
8
|
import { SmartTable } from '../features/datagrid/SmartTable';
|
|
9
|
+
import { SmartChart } from '../features/visualizations/SmartChart';
|
|
17
10
|
|
|
11
|
+
// ✅ 3. MERGE EVERYTHING
|
|
18
12
|
export const COMPONENT_REGISTRY: Record<string, React.FC<any>> = {
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Data
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
SmartTable: SmartTable,
|
|
34
|
-
|
|
35
|
-
// Layout & Feedback
|
|
36
|
-
Card,
|
|
37
|
-
Alert,
|
|
38
|
-
EmptyState,
|
|
39
|
-
|
|
40
|
-
// Forms/Controls
|
|
41
|
-
ToggleSwitch
|
|
13
|
+
// A. The Entire UI Library (~50+ components)
|
|
14
|
+
...AUTO_REGISTRY,
|
|
15
|
+
|
|
16
|
+
// B. The Smart Blocks (Data-Connected)
|
|
17
|
+
SmartTable,
|
|
18
|
+
SmartChart,
|
|
19
|
+
|
|
20
|
+
// C. Legacy Aliases (For older AI prompts/manifests)
|
|
21
|
+
'smart_table': SmartTable,
|
|
22
|
+
'smart_chart': SmartChart,
|
|
23
|
+
'stat_card': AUTO_REGISTRY['StatCard'],
|
|
24
|
+
'chart_line': AUTO_REGISTRY['LineChart'],
|
|
25
|
+
'chart_bar': AUTO_REGISTRY['BarChart'],
|
|
26
|
+
'chart_pie': AUTO_REGISTRY['PieChart'],
|
|
42
27
|
};
|
|
43
28
|
|
|
29
|
+
// --- COMPONENT RESOLVER ENGINE ---
|
|
44
30
|
export const getComponent = (name: string) => {
|
|
45
|
-
|
|
31
|
+
// 1. Normalize the key to handle case-insensitivity and snake_case
|
|
32
|
+
// e.g. "page_header" -> "PageHeader", "button" -> "Button"
|
|
33
|
+
const normalizedName = name.replace(/_/g, '').toLowerCase();
|
|
46
34
|
|
|
35
|
+
// 2. Lookup Strategy
|
|
36
|
+
// We check the exact name, then the lowercase version, then the normalized version
|
|
37
|
+
const Component =
|
|
38
|
+
COMPONENT_REGISTRY[name] ||
|
|
39
|
+
COMPONENT_MAP_LOWER[name.toLowerCase()] ||
|
|
40
|
+
COMPONENT_MAP_NORMALIZED[normalizedName];
|
|
41
|
+
|
|
42
|
+
// 3. Safety Net (Ghost Placeholder)
|
|
47
43
|
if (!Component) {
|
|
48
|
-
console.warn(`[Registry] Unknown component
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
console.warn(`[Registry] ❌ Unknown component: "${name}"`);
|
|
45
|
+
|
|
46
|
+
return ({ ...props }) => (
|
|
47
|
+
<div className="p-4 border-2 border-dashed border-red-200 bg-red-50 rounded-lg flex flex-col items-center justify-center text-red-400 min-h-[100px] h-full w-full animate-in fade-in">
|
|
48
|
+
<span className="text-xs font-mono font-bold uppercase tracking-wider">{name}</span>
|
|
49
|
+
<span className="text-[10px] mt-1 opacity-75">Component Missing</span>
|
|
50
|
+
{/* Hidden debug data for developers */}
|
|
51
|
+
<div className="hidden">{JSON.stringify(props)}</div>
|
|
52
|
+
</div>
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
return Component;
|
|
56
|
-
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// --- OPTIMIZATION: PRE-CALCULATED LOOKUP MAPS ---
|
|
59
|
+
// We generate these once on startup so we don't map/reduce on every render.
|
|
60
|
+
|
|
61
|
+
const COMPONENT_MAP_LOWER = Object.keys(COMPONENT_REGISTRY).reduce((acc, key) => {
|
|
62
|
+
acc[key.toLowerCase()] = COMPONENT_REGISTRY[key];
|
|
63
|
+
return acc;
|
|
64
|
+
}, {} as Record<string, React.FC<any>>);
|
|
65
|
+
|
|
66
|
+
const COMPONENT_MAP_NORMALIZED = Object.keys(COMPONENT_REGISTRY).reduce((acc, key) => {
|
|
67
|
+
acc[key.replace(/_/g, '').toLowerCase()] = COMPONENT_REGISTRY[key];
|
|
68
|
+
return acc;
|
|
69
|
+
}, {} as Record<string, React.FC<any>>);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { getComponent } from '../../config/component-registry';
|
|
3
3
|
// @ts-ignore
|
|
4
|
-
import { useGeneratedSignals } from '../
|
|
4
|
+
import { useGeneratedSignals } from '../runtime/useSignalStore';
|
|
5
5
|
import { getMockData } from '../../data/mockData';
|
|
6
6
|
|
|
7
7
|
/**
|