@object-ui/runner 3.3.0 → 3.3.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.
@@ -1,320 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import React from 'react';
10
- import type { AppSchema } from '@object-ui/types';
11
- import * as LucideIcons from 'lucide-react';
12
- import {
13
- DropdownMenu,
14
- DropdownMenuContent,
15
- DropdownMenuGroup,
16
- DropdownMenuItem,
17
- DropdownMenuLabel,
18
- DropdownMenuSeparator,
19
- DropdownMenuTrigger,
20
- DropdownMenuShortcut,
21
- Avatar,
22
- AvatarImage,
23
- AvatarFallback,
24
- Collapsible,
25
- CollapsibleContent,
26
- CollapsibleTrigger
27
- } from '@object-ui/components';
28
-
29
- interface LayoutRendererProps {
30
- app: AppSchema;
31
- children: React.ReactNode;
32
- currentPath?: string;
33
- onNavigate?: (path: string) => void;
34
- }
35
-
36
- // Helper to resolve icon from string name (e.g. "bar-chart" -> "BarChart")
37
- const getIcon = (name?: string) => {
38
- if (!name) return null;
39
-
40
- // 1. Try direct match (e.g. "Home")
41
- if ((LucideIcons as any)[name]) return (LucideIcons as any)[name];
42
-
43
- // 2. Try PascalCase (e.g. "bar-chart" -> "BarChart")
44
- const pascalName = name.split('-').map(part => part.charAt(0).toUpperCase() + part.slice(1)).join('');
45
- if ((LucideIcons as any)[pascalName]) return (LucideIcons as any)[pascalName];
46
-
47
- return LucideIcons.Circle; // Fallback
48
- };
49
-
50
- const NavItem = ({ item, currentPath, isSidebarOpen, onNavigate, level = 0 }: any) => {
51
- const isActive = currentPath === item.path;
52
- const hasActiveChild = item.children?.some((child: any) => child.path === currentPath);
53
- const [isOpen, setIsOpen] = React.useState(hasActiveChild);
54
- const Icon = getIcon(item.icon);
55
-
56
- // Auto-expand if child is active
57
- React.useEffect(() => {
58
- if (hasActiveChild) setIsOpen(true);
59
- }, [hasActiveChild]);
60
-
61
- if (item.children && item.children.length > 0) {
62
- return (
63
- <Collapsible open={isOpen} onOpenChange={setIsOpen} className="w-full">
64
- <CollapsibleTrigger className={`flex w-full items-center justify-between py-2 text-sm font-medium rounded-md transition-colors text-muted-foreground hover:bg-muted hover:text-foreground ${isSidebarOpen ? 'px-3' : 'justify-center px-2 cursor-pointer'}`}>
65
- <div className="flex items-center overflow-hidden">
66
- {Icon && <Icon className={`h-4 w-4 flex-shrink-0 ${isSidebarOpen ? 'mr-3' : ''}`} />}
67
- <span className={`whitespace-nowrap overflow-hidden transition-all duration-300 ${isSidebarOpen ? 'opacity-100 w-auto' : 'opacity-0 w-0 hidden'}`}>
68
- {item.label}
69
- </span>
70
- </div>
71
- {isSidebarOpen && (
72
- <LucideIcons.ChevronDown className={`h-4 w-4 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
73
- )}
74
- </CollapsibleTrigger>
75
- <CollapsibleContent className="space-y-1 overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
76
- {isSidebarOpen && item.children.map((child: any, idx: number) => (
77
- <NavItem
78
- key={idx}
79
- item={child}
80
- currentPath={currentPath}
81
- isSidebarOpen={isSidebarOpen}
82
- onNavigate={onNavigate}
83
- level={level + 1}
84
- />
85
- ))}
86
- </CollapsibleContent>
87
- </Collapsible>
88
- );
89
- }
90
-
91
- return (
92
- <a
93
- href={item.path || '#'}
94
- onClick={(e) => item.path && onNavigate(e, item.path)}
95
- title={!isSidebarOpen ? item.label : undefined}
96
- className={`flex items-center py-2 text-sm font-medium rounded-md transition-colors ${
97
- isActive
98
- ? 'bg-primary text-primary-foreground'
99
- : 'text-muted-foreground hover:bg-muted hover:text-foreground'
100
- } ${isSidebarOpen ? 'px-3' : 'justify-center px-2'} ${level > 0 && isSidebarOpen ? 'pl-10' : ''}`}
101
- >
102
- {Icon && <Icon className={`h-4 w-4 flex-shrink-0 ${isSidebarOpen ? 'mr-3' : ''} ${isActive ? 'text-primary-foreground' : 'text-muted-foreground group-hover:text-foreground'}`} />}
103
- <span className={`whitespace-nowrap overflow-hidden transition-all duration-300 ${isSidebarOpen ? 'opacity-100 w-auto' : 'opacity-0 w-0 hidden'}`}>
104
- {item.label}
105
- </span>
106
- </a>
107
- );
108
- };
109
-
110
- export const LayoutRenderer = ({ app, children, currentPath, onNavigate }: LayoutRendererProps) => {
111
- const layout = app.layout || 'sidebar';
112
- const [isSidbarOpen, setSidebarOpen] = React.useState(true);
113
-
114
- // Theme management
115
- const [theme, setTheme] = React.useState<"light" | "dark">("light");
116
-
117
- React.useEffect(() => {
118
- const isDark = localStorage.getItem('theme') === 'dark' ||
119
- (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
120
- setTheme(isDark ? 'dark' : 'light');
121
- }, []);
122
-
123
- React.useEffect(() => {
124
- if (theme === 'dark') {
125
- document.documentElement.classList.add('dark');
126
- localStorage.setItem('theme', 'dark');
127
- } else {
128
- document.documentElement.classList.remove('dark');
129
- localStorage.setItem('theme', 'light');
130
- }
131
- }, [theme]);
132
-
133
- const toggleTheme = () => {
134
- setTheme(prev => prev === 'dark' ? 'light' : 'dark');
135
- };
136
-
137
- const handleNavClick = (e: React.MouseEvent<HTMLAnchorElement>, path: string) => {
138
- e.preventDefault();
139
- if (onNavigate) {
140
- onNavigate(path);
141
- } else {
142
- window.location.href = path;
143
- }
144
- };
145
-
146
- if (layout === 'empty') {
147
- return <main className={app.className}>{children}</main>;
148
- }
149
-
150
- const LogoIcon = app.logo && !app.logo.includes('/') && !app.logo.includes('.') ? getIcon(app.logo) : null;
151
-
152
- return (
153
- <div className={`flex min-h-screen w-full bg-background ${app.className || ''}`}>
154
- {/* Sidebar - Only if configured */}
155
- {layout === 'sidebar' && (
156
- <aside
157
- className={`
158
- flex-shrink-0 border-r bg-background hidden md:flex flex-col h-screen sticky top-0 z-30 transition-all duration-300 ease-in-out
159
- ${isSidbarOpen ? 'w-64' : 'w-[70px]'}
160
- `}
161
- >
162
- <div className={`h-14 flex items-center border-b font-semibold text-lg tracking-tight transition-all ${isSidbarOpen ? 'px-6' : 'justify-center px-0'}`}>
163
- {LogoIcon ? (
164
- <LogoIcon className="h-6 w-6" />
165
- ) : app.logo ? (
166
- <img src={app.logo} alt={app.title} className="h-6 w-auto" />
167
- ) : <LucideIcons.Box className="h-6 w-6" />}
168
-
169
- <span className={`ml-2 whitespace-nowrap overflow-hidden transition-all duration-300 ${isSidbarOpen ? 'opacity-100 w-auto' : 'opacity-0 w-0 hidden'}`}>
170
- {app.title || app.name || 'Object UI'}
171
- </span>
172
- </div>
173
- <nav className="flex-1 p-2 space-y-1 overflow-y-auto overflow-x-hidden">
174
- {app.menu?.map((item, index) => (
175
- <NavItem
176
- key={index}
177
- item={item}
178
- currentPath={currentPath}
179
- isSidebarOpen={isSidbarOpen}
180
- onNavigate={handleNavClick}
181
- />
182
- ))}
183
- </nav>
184
- {app.version && isSidbarOpen && (
185
- <div className="p-4 border-t text-xs text-muted-foreground">
186
- v{app.version}
187
- </div>
188
- )}
189
- </aside>
190
- )}
191
-
192
- {/* Main Content Area */}
193
- <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
194
- {/* Header - Always shown in sidebar/header layouts */}
195
- <header className="h-14 flex items-center justify-between px-4 md:px-6 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b z-20 sticky top-0">
196
- <div className="flex items-center gap-4">
197
- {/* Toggle Sidebar Button */}
198
- <button
199
- onClick={() => setSidebarOpen(!isSidbarOpen)}
200
- className="p-2 -ml-2 text-muted-foreground hover:bg-muted hover:text-foreground rounded-md transition-colors"
201
- >
202
- <LucideIcons.Menu className="h-5 w-5" />
203
- </button>
204
-
205
- {/* Breadcrumbs placeholder or Search */}
206
- <div className="relative hidden md:block w-96">
207
- <LucideIcons.Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
208
- <input
209
- type="text"
210
- placeholder="Search..."
211
- className="w-full h-9 pl-9 pr-4 rounded-md border border-input bg-background text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
212
- />
213
- </div>
214
- </div>
215
- <div className="flex items-center gap-2">
216
- {/* Theme Toggle */}
217
- <button
218
- onClick={toggleTheme}
219
- className="p-2 text-muted-foreground hover:text-foreground transition-colors rounded-md hover:bg-muted"
220
- title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
221
- >
222
- {theme === 'dark' ? <LucideIcons.Sun className="h-5 w-5" /> : <LucideIcons.Moon className="h-5 w-5" />}
223
- </button>
224
-
225
- {/* Global Actions */}
226
- {app.actions?.filter(a => a.type === 'button').map((action, i) => {
227
- const Icon = action.icon ? getIcon(action.icon) : null;
228
- return (
229
- <button
230
- key={i}
231
- className={action.variant === 'ghost' ? "relative p-2 text-muted-foreground hover:text-foreground transition-colors hover:bg-muted rounded-md" : "p-2"}
232
- title={action.label}
233
- >
234
- {Icon && <Icon className="h-5 w-5" />}
235
- {action.label && !action.icon && <span>{action.label}</span>}
236
- </button>
237
- );
238
- })}
239
-
240
- {/* Fallback Bell if no actions defined, or keep it as specific logic?
241
- The original code hardcoded a Bell button.
242
- The app.json defines a 'Bell' button action.
243
- So I should iterate app.actions for buttons as well.
244
- */}
245
-
246
- {/* Original Bell Logic (Hardcoded in user request? No, it was hardcoded in my previous edit, but app.json has it too)
247
- Let's check app.json. It has:
248
- { "type": "button", "variant": "ghost", "size": "icon", "icon": "Bell" }
249
-
250
- If I render actions generically, I don't need the hardcoded Bell.
251
- */}
252
-
253
- {(!app.actions || !app.actions.some(a => a.type === 'button')) && (
254
- <button className="relative p-2 text-muted-foreground hover:text-foreground transition-colors">
255
- <LucideIcons.Bell className="h-5 w-5" />
256
- <span className="absolute top-1.5 right-1.5 h-2 w-2 bg-red-600 rounded-full border-2 border-background"></span>
257
- </button>
258
- )}
259
-
260
- {app.actions?.filter(a => a.type === 'user').map((userAction, i) => (
261
- <DropdownMenu key={i}>
262
- <DropdownMenuTrigger asChild>
263
- <button className="relative h-8 w-8 rounded-full border bg-muted overflow-hidden focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 hover:opacity-90 transition-opacity">
264
- <Avatar className="h-full w-full">
265
- <AvatarImage
266
- src={userAction.avatar}
267
- alt={userAction.label || 'User'}
268
- />
269
- <AvatarFallback>
270
- {userAction.label?.substring(0, 2).toUpperCase() || 'JD'}
271
- </AvatarFallback>
272
- </Avatar>
273
- </button>
274
- </DropdownMenuTrigger>
275
- <DropdownMenuContent className="w-56" align="end" forceMount>
276
- <DropdownMenuLabel className="font-normal">
277
- <div className="flex flex-col space-y-1">
278
- <p className="text-sm font-medium leading-none">{userAction.label || 'User'}</p>
279
- <p className="text-xs leading-none text-muted-foreground">
280
- {userAction.description || 'user@example.com'}
281
- </p>
282
- </div>
283
- </DropdownMenuLabel>
284
- <DropdownMenuSeparator />
285
- <DropdownMenuGroup>
286
- {userAction.items?.map((item, idx) => {
287
- if (item.type === 'separator') {
288
- return <DropdownMenuSeparator key={idx} />;
289
- }
290
- return (
291
- <DropdownMenuItem key={idx} onSelect={() => {
292
- if ((item as any).onClick) {
293
- // Handle click logic
294
- console.log('Clicked', item.label);
295
- }
296
- }}>
297
- {item.label}
298
- {(item as any).shortcut && (
299
- <DropdownMenuShortcut>{(item as any).shortcut}</DropdownMenuShortcut>
300
- )}
301
- </DropdownMenuItem>
302
- );
303
- })}
304
- </DropdownMenuGroup>
305
- </DropdownMenuContent>
306
- </DropdownMenu>
307
- ))}
308
- </div>
309
- </header>
310
-
311
- {/* Page Content */}
312
- <main className="flex-1 overflow-auto p-4 md:p-8 scroll-smooth">
313
- <div className="mx-auto max-w-7xl animate-in fade-in slide-in-from-bottom-4 duration-500">
314
- {children}
315
- </div>
316
- </main>
317
- </div>
318
- </div>
319
- );
320
- };
package/src/index.css DELETED
@@ -1,111 +0,0 @@
1
- @import 'tailwindcss';
2
-
3
- /* Scan sources for Tailwind classes */
4
- @source './src/**/*.{ts,tsx}';
5
- @source '../../packages/components/src/**/*.{ts,tsx}';
6
- @source '../../packages/react/src/**/*.{ts,tsx}';
7
- @source '../../packages/plugin-kanban/src/**/*.{ts,tsx}';
8
- @source '../../packages/plugin-charts/src/**/*.{ts,tsx}';
9
-
10
- /* Tailwind plugin for animations */
11
- @plugin 'tailwindcss-animate';
12
-
13
- /* Define theme colors for Tailwind 4 */
14
- @theme {
15
- /* Border radius tokens */
16
- --radius-lg: var(--radius);
17
- --radius-md: calc(var(--radius) - 2px);
18
- --radius-sm: calc(var(--radius) - 4px);
19
-
20
- /* Color tokens mapped to CSS variables */
21
- --color-border: hsl(var(--border));
22
- --color-input: hsl(var(--input));
23
- --color-ring: hsl(var(--ring));
24
- --color-background: hsl(var(--background));
25
- --color-foreground: hsl(var(--foreground));
26
- --color-primary: hsl(var(--primary));
27
- --color-primary-foreground: hsl(var(--primary-foreground));
28
- --color-secondary: hsl(var(--secondary));
29
- --color-secondary-foreground: hsl(var(--secondary-foreground));
30
- --color-destructive: hsl(var(--destructive));
31
- --color-destructive-foreground: hsl(var(--destructive-foreground));
32
- --color-muted: hsl(var(--muted));
33
- --color-muted-foreground: hsl(var(--muted-foreground));
34
- --color-accent: hsl(var(--accent));
35
- --color-accent-foreground: hsl(var(--accent-foreground));
36
- --color-popover: hsl(var(--popover));
37
- --color-popover-foreground: hsl(var(--popover-foreground));
38
- --color-card: hsl(var(--card));
39
- --color-card-foreground: hsl(var(--card-foreground));
40
- }
41
-
42
- :root {
43
- --background: 0 0% 100%;
44
- --foreground: 222.2 84% 4.9%;
45
-
46
- --card: 0 0% 100%;
47
- --card-foreground: 222.2 84% 4.9%;
48
-
49
- --popover: 0 0% 100%;
50
- --popover-foreground: 222.2 84% 4.9%;
51
-
52
- --primary: 222.2 47.4% 11.2%;
53
- --primary-foreground: 210 40% 98%;
54
-
55
- --secondary: 210 40% 96.1%;
56
- --secondary-foreground: 222.2 47.4% 11.2%;
57
-
58
- --muted: 210 40% 96.1%;
59
- --muted-foreground: 215.4 16.3% 46.9%;
60
-
61
- --accent: 210 40% 96.1%;
62
- --accent-foreground: 222.2 47.4% 11.2%;
63
-
64
- --destructive: 0 84.2% 60.2%;
65
- --destructive-foreground: 210 40% 98%;
66
-
67
- --border: 214.3 31.8% 91.4%;
68
- --input: 214.3 31.8% 91.4%;
69
- --ring: 222.2 84% 4.9%;
70
-
71
- --radius: 0.5rem;
72
- }
73
-
74
- .dark {
75
- --background: 222.2 84% 4.9%;
76
- --foreground: 210 40% 98%;
77
-
78
- --card: 222.2 84% 4.9%;
79
- --card-foreground: 210 40% 98%;
80
-
81
- --popover: 222.2 84% 4.9%;
82
- --popover-foreground: 210 40% 98%;
83
-
84
- --primary: 210 40% 98%;
85
- --primary-foreground: 222.2 47.4% 11.2%;
86
-
87
- --secondary: 217.2 32.6% 17.5%;
88
- --secondary-foreground: 210 40% 98%;
89
-
90
- --muted: 217.2 32.6% 17.5%;
91
- --muted-foreground: 215 20.2% 65.1%;
92
-
93
- --accent: 217.2 32.6% 17.5%;
94
- --accent-foreground: 210 40% 98%;
95
-
96
- --destructive: 0 62.8% 30.6%;
97
- --destructive-foreground: 210 40% 98%;
98
-
99
- --border: 217.2 32.6% 17.5%;
100
- --input: 217.2 32.6% 17.5%;
101
- --ring: 212.7 26.8% 83.9%;
102
- }
103
-
104
- * {
105
- border-color: hsl(var(--border));
106
- }
107
-
108
- body {
109
- background-color: hsl(var(--background));
110
- color: hsl(var(--foreground));
111
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { AppSchema, PageSchema } from '@object-ui/types';
10
-
11
- export interface MetadataLoader {
12
- loadAppConfig(): Promise<AppSchema | null>;
13
- loadPage(path: string): Promise<PageSchema | null>;
14
- }
15
-
16
- /**
17
- * Strategy A: Local Bundle Loader (Vite Glob)
18
- * Used during local development via 'pnpm dev:crm'
19
- */
20
- export class LocalBundleLoader implements MetadataLoader {
21
- private appGlob = import.meta.glob('../app-data/app.json');
22
- private pagesGlob = import.meta.glob('../app-data/pages/**/*.json');
23
- private rootGlob = import.meta.glob('../app-data/*.json');
24
-
25
- async loadAppConfig(): Promise<AppSchema | null> {
26
- const key = '../app-data/app.json';
27
- if (this.appGlob[key]) {
28
- const mod: any = await this.appGlob[key]();
29
- return mod.default || mod;
30
- }
31
- return null;
32
- }
33
-
34
- async loadPage(path: string): Promise<PageSchema | null> {
35
- // 1. Normalize Path
36
- const normalizedPath = path.replace(/^\//, '') || 'index';
37
-
38
- // 2. Try Exact Match in Pages
39
- if (await this.tryLoad(`../app-data/pages/${normalizedPath}.json`)) {
40
- return await this.loadKey(`../app-data/pages/${normalizedPath}.json`);
41
- }
42
-
43
- // 3. Try Index Match
44
- if (await this.tryLoad(`../app-data/pages/${normalizedPath}/index.json`)) {
45
- return await this.loadKey(`../app-data/pages/${normalizedPath}/index.json`);
46
- }
47
-
48
- // 4. Try Root fallback
49
- if (normalizedPath === 'index' && await this.tryLoad(`../app-data/index.json`)) {
50
- return await this.loadKey(`../app-data/index.json`);
51
- }
52
-
53
- // 5. Dynamic Routing (Basic Mock)
54
- // TODO: Implement proper glob matching for dynamic routes if needed here,
55
- // but usually exact paths are enough for this loader demo.
56
- return null;
57
- }
58
-
59
- private async tryLoad(key: string) {
60
- return !!(this.pagesGlob[key] || this.rootGlob[key]);
61
- }
62
-
63
- private async loadKey(key: string) {
64
- const loader = this.pagesGlob[key] || this.rootGlob[key];
65
- if (!loader) return null;
66
- const mod: any = await loader();
67
- return mod.default || mod;
68
- }
69
- }
70
-
71
- /**
72
- * Strategy B: Network Loader (Fetch API)
73
- * Used in production to fetch JSONs from an API endpoint
74
- */
75
- export class NetworkLoader implements MetadataLoader {
76
- private baseUrl: string;
77
-
78
- constructor(baseUrl: string = '/api') {
79
- this.baseUrl = baseUrl;
80
- }
81
-
82
- async loadAppConfig(): Promise<AppSchema | null> {
83
- try {
84
- const res = await fetch(`${this.baseUrl}/app.json`);
85
- if (!res.ok) return null;
86
- return await res.json();
87
- } catch {
88
- return null;
89
- }
90
- }
91
-
92
- async loadPage(path: string): Promise<PageSchema | null> {
93
- try {
94
- // Maps /customers -> /api/pages/customers.json
95
- const jsonPath = path === '/' ? '/index' : path;
96
- const res = await fetch(`${this.baseUrl}/pages${jsonPath}.json`);
97
- if (!res.ok) return null;
98
- return await res.json();
99
- } catch {
100
- return null;
101
- }
102
- }
103
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import type { DataSource } from '@object-ui/core';
10
-
11
- /**
12
- * 模拟数据源 (Mock Adapter)
13
- * 在真实项目中,你会在这里使用 fetch/axios 调用你的 API。
14
- */
15
- export class MockDataSource implements DataSource {
16
- async find(resource: string, params?: any): Promise<any[]> {
17
- console.log(`[DataSource] Querying ${resource}`, params);
18
- return [];
19
- }
20
-
21
- async findOne(resource: string, id: string): Promise<any> {
22
- return null;
23
- }
24
-
25
- async create(resource: string, data: any): Promise<any> {
26
- // 模拟网络请求
27
- await new Promise(resolve => setTimeout(resolve, 800));
28
-
29
- console.log(`[DataSource] Created ${resource}:`, data);
30
- alert(`Success! Created record in "${resource}":\n${JSON.stringify(data, null, 2)}`);
31
-
32
- return { id: Math.random().toString(), ...data };
33
- }
34
-
35
- async update(resource: string, id: string, data: any): Promise<any> {
36
- return data;
37
- }
38
-
39
- async delete(resource: string, id: string): Promise<any> {
40
- return true;
41
- }
42
-
43
- async getObjectSchema(objectName: string): Promise<any> {
44
- if (!objectName || typeof objectName !== 'string') {
45
- throw new Error('Invalid object name');
46
- }
47
-
48
- console.log(`[DataSource] Getting schema for ${objectName}`);
49
- // Return a minimal schema for mock purposes
50
- return {
51
- name: objectName,
52
- label: objectName,
53
- fields: {}
54
- };
55
- }
56
- }
package/src/main.tsx DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import React from 'react'
10
- import ReactDOM from 'react-dom/client'
11
- import App from './App.tsx'
12
- import './index.css'
13
-
14
- ReactDOM.createRoot(document.getElementById('root')!).render(
15
- <React.StrictMode>
16
- <App />
17
- </React.StrictMode>,
18
- )
@@ -1,64 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
-
10
- import { describe, it, expect } from 'vitest';
11
- import { kanbanComponents } from '@object-ui/plugin-kanban';
12
- import { chartComponents } from '@object-ui/plugin-charts';
13
- import { ComponentRegistry } from '@object-ui/core';
14
-
15
- describe('Plugin Integration Protocol', () => {
16
- describe('Kanban Plugin', () => {
17
- it('should export components object for manual registration', () => {
18
- expect(kanbanComponents).toBeDefined();
19
- expect(kanbanComponents.kanban).toBeDefined();
20
- });
21
-
22
- it('should contain valid React component', () => {
23
- const Component = kanbanComponents.kanban;
24
- expect(typeof Component).toBe('function'); // React components are functions
25
- });
26
- });
27
-
28
- describe('Charts Plugin', () => {
29
- it('should export component objects for manual registration', () => {
30
- expect(chartComponents).toBeDefined();
31
- expect(chartComponents['bar-chart']).toBeDefined();
32
- expect(chartComponents['chart']).toBeDefined();
33
- });
34
-
35
- it('should export correctly named "bar-chart" key matching schema usage', () => {
36
- // Critical check for the bug we fixed (type mismatch)
37
- expect(Object.keys(chartComponents)).toContain('bar-chart');
38
- expect(chartComponents['bar-chart']).toBeDefined();
39
- });
40
- });
41
-
42
- describe('Manual Registration Simulation', () => {
43
- it('should successfully register kanban via manual components', () => {
44
- // Clear registry to simulate clean state
45
- // Note: In a real app we wouldn't clear, but here we want to prove registration works
46
-
47
- // Act: Manually register
48
- if (kanbanComponents?.kanban) {
49
- ComponentRegistry.register('test-kanban-manual', kanbanComponents.kanban);
50
- }
51
-
52
- // Assert
53
- expect(ComponentRegistry.get('test-kanban-manual')).toBeDefined();
54
- });
55
-
56
- it('should successfully register bar-chart via manual components', () => {
57
- if (chartComponents?.['bar-chart']) {
58
- ComponentRegistry.register('test-bar-chart-manual', chartComponents['bar-chart']);
59
- }
60
-
61
- expect(ComponentRegistry.get('test-bar-chart-manual')).toBeDefined();
62
- });
63
- });
64
- });