Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/site/app/components/ComponentDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ export { InteractiveDemo as ComponentDemo, DemoGrid } from './InteractiveDemo';
// Legacy exports for backward compatibility
export { InteractiveDemo, InteractiveDemo as CodeDemo } from './InteractiveDemo';

// Export PluginLoader for use in MDX files
export { PluginLoader } from './PluginLoader';

// Export types for use in MDX files
export type { SchemaNode } from './InteractiveDemo';
70 changes: 17 additions & 53 deletions apps/site/app/components/InteractiveDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React, { useState, useEffect } from 'react';
import React from 'react';
import { SchemaRenderer } from '@object-ui/react';
import type { SchemaNode } from '@object-ui/core';
import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
Expand All @@ -9,26 +9,6 @@ import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock';
// Re-export SchemaNode type for use in MDX files
export type { SchemaNode } from '@object-ui/core';

// Load plugins promise that we can await
const pluginsLoading = typeof window !== 'undefined'
? Promise.all([
import('@object-ui/plugin-aggrid'),
import('@object-ui/plugin-editor'),
import('@object-ui/plugin-charts'),
import('@object-ui/plugin-dashboard'),
import('@object-ui/plugin-kanban'),
import('@object-ui/plugin-markdown'),
import('@object-ui/plugin-timeline'),
import('@object-ui/plugin-calendar'),
import('@object-ui/plugin-gantt'),
import('@object-ui/plugin-map'),
import('@object-ui/plugin-chatbot'),
import('@object-ui/plugin-form'),
import('@object-ui/plugin-grid'),
import('@object-ui/plugin-view'),
])
: Promise.resolve([]);

interface InteractiveDemoProps {
schema: SchemaNode;
title?: string;
Expand All @@ -49,14 +29,6 @@ export function InteractiveDemo({
description,
examples
}: InteractiveDemoProps) {
const [pluginsLoaded, setPluginsLoaded] = useState(false);

useEffect(() => {
// Wait for plugins to load before rendering
pluginsLoading.then(() => {
setPluginsLoaded(true);
});
}, []);

// If examples are provided, show a multi-example view
if (examples && examples.length > 0) {
Expand All @@ -70,27 +42,23 @@ export function InteractiveDemo({
)}
<Tabs items={['Preview', 'Code']} defaultIndex={0}>
<Tab value="Preview">
{!pluginsLoaded ? (
<div className="p-6 text-center text-muted-foreground">Loading plugins...</div>
) : (
<div className="space-y-6">
{examples.map((example, index) => (
<div key={index} className="border rounded-lg overflow-hidden">
{example.label && (
<div className="border-b bg-muted px-4 py-2">
<p className="text-sm font-medium">{example.label}</p>
{example.description && (
<p className="text-xs text-muted-foreground mt-0.5">{example.description}</p>
)}
</div>
)}
<div className="p-6 bg-background">
<SchemaRenderer schema={example.schema} />
<div className="space-y-6">
{examples.map((example, index) => (
<div key={index} className="border rounded-lg overflow-hidden">
{example.label && (
<div className="border-b bg-muted px-4 py-2">
<p className="text-sm font-medium">{example.label}</p>
{example.description && (
<p className="text-xs text-muted-foreground mt-0.5">{example.description}</p>
)}
</div>
)}
<div className="p-6 bg-background">
<SchemaRenderer schema={example.schema} />
</div>
))}
</div>
)}
</div>
))}
</div>
</Tab>
<Tab value="Code">
<div className="space-y-4">
Expand Down Expand Up @@ -125,11 +93,7 @@ export function InteractiveDemo({
<Tabs items={['Preview', 'Code']} defaultIndex={0}>
<Tab value="Preview">
<div className="border rounded-lg p-6 bg-background">
{!pluginsLoaded ? (
<div className="text-center text-muted-foreground">Loading plugins...</div>
) : (
<SchemaRenderer schema={schema} />
)}
<SchemaRenderer schema={schema} />
</div>
</Tab>
<Tab value="Code">
Expand Down
115 changes: 115 additions & 0 deletions apps/site/app/components/PluginLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use client';

import { useEffect, useState } from 'react';

type PluginName =
| 'aggrid'
| 'editor'
| 'charts'
| 'dashboard'
| 'kanban'
| 'markdown'
| 'timeline'
| 'calendar'
| 'gantt'
| 'map'
| 'chatbot'
| 'form'
| 'grid'
| 'view';

interface PluginLoaderProps {
plugins: PluginName[];
children: React.ReactNode;
}

/**
* PluginLoader component - Loads specific plugins on-demand
*
* Usage in MDX files:
* ```mdx
* import { PluginLoader } from '@/app/components/PluginLoader';
*
* <PluginLoader plugins={['aggrid']}>
* <InteractiveDemo schema={{...}} />
* </PluginLoader>
* ```
*/
export function PluginLoader({ plugins, children }: PluginLoaderProps) {
const [loaded, setLoaded] = useState(false);

useEffect(() => {
let cancelled = false;

const loadPlugins = async () => {
// On server side, skip actual imports but mark as loaded to avoid hydration mismatch
if (typeof window === 'undefined') {
if (!cancelled) {
setLoaded(true);
}
return;
}

Comment on lines +39 to +52
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check for typeof window === 'undefined' inside useEffect is unnecessary because useEffect never runs during server-side rendering in React. This code path (lines 46-50) will never execute on the server.

If the goal is to avoid showing a loading state on the server, you should initialize the state differently:
const [loaded, setLoaded] = useState(typeof window === 'undefined');

Then remove the server-side check from the useEffect. This ensures the server renders the children immediately while the client shows loading until plugins are loaded.

Suggested change
const [loaded, setLoaded] = useState(false);
useEffect(() => {
let cancelled = false;
const loadPlugins = async () => {
// On server side, skip actual imports but mark as loaded to avoid hydration mismatch
if (typeof window === 'undefined') {
if (!cancelled) {
setLoaded(true);
}
return;
}
const [loaded, setLoaded] = useState(typeof window === 'undefined');
useEffect(() => {
let cancelled = false;
const loadPlugins = async () => {

Copilot uses AI. Check for mistakes.
try {
// Dynamically import plugins based on the list
const imports = plugins.map(async (plugin) => {
switch (plugin) {
case 'aggrid':
return import('@object-ui/plugin-aggrid');
case 'editor':
return import('@object-ui/plugin-editor');
case 'charts':
return import('@object-ui/plugin-charts');
case 'dashboard':
return import('@object-ui/plugin-dashboard');
case 'kanban':
return import('@object-ui/plugin-kanban');
case 'markdown':
return import('@object-ui/plugin-markdown');
case 'timeline':
return import('@object-ui/plugin-timeline');
case 'calendar':
return import('@object-ui/plugin-calendar');
case 'gantt':
return import('@object-ui/plugin-gantt');
case 'map':
return import('@object-ui/plugin-map');
case 'chatbot':
return import('@object-ui/plugin-chatbot');
case 'form':
return import('@object-ui/plugin-form');
case 'grid':
return import('@object-ui/plugin-grid');
case 'view':
return import('@object-ui/plugin-view');
default:
console.warn(`Unknown plugin: ${plugin}`);
return Promise.resolve();
}
});

await Promise.all(imports);
if (!cancelled) {
setLoaded(true);
}
} catch (error) {
console.error('Failed to load plugins:', error);
if (!cancelled) {
setLoaded(true); // Still render children even if plugin loading fails
}
}
};

loadPlugins();

return () => {
cancelled = true;
};
}, [plugins]);
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugins array in the useEffect dependency will cause the effect to re-run on every render if the array is created inline in the parent component (e.g., plugins={['aggrid']}). Since arrays are compared by reference in React, a new array instance on each render will trigger the effect repeatedly, potentially loading plugins multiple times.

Consider using a stable reference or memoizing the plugins array, or convert plugins to a string for comparison. Alternatively, document that consumers should pass a stable array reference to avoid unnecessary reloads.

Copilot uses AI. Check for mistakes.

if (!loaded) {
return <div className="p-6 text-center text-muted-foreground">Loading plugins...</div>;
}

return <>{children}</>;
}
Comment on lines +1 to +115
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Rule #2 (Documentation Driven Development) in the coding guidelines, for EVERY feature implemented, you MUST update corresponding documentation including Package README.md and content/docs/guide/*.md.

This new PluginLoader component is a significant architectural feature that changes how plugins are loaded throughout the application, but there is no documentation added to explain:

  1. The new lazy-loading pattern for plugin pages
  2. How to use PluginLoader in MDX files
  3. The architectural decision and benefits

Consider adding or updating documentation in content/docs/guide/ to explain this new pattern, as it's a fundamental change to how the system works.

Copilot generated this review using guidance from repository custom instructions.
1 change: 1 addition & 0 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@object-ui/plugin-calendar": "workspace:*",
"@object-ui/plugin-charts": "workspace:*",
"@object-ui/plugin-chatbot": "workspace:*",
"@object-ui/plugin-dashboard": "workspace:*",
"@object-ui/plugin-editor": "workspace:*",
"@object-ui/plugin-gantt": "workspace:*",
"@object-ui/plugin-kanban": "workspace:*",
Expand Down
5 changes: 5 additions & 0 deletions content/docs/plugins/plugin-aggrid.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: "Plugin AgGrid"
---

import { InteractiveDemo } from '@/app/components/InteractiveDemo';
import { PluginLoader } from '@/app/components/PluginLoader';

Data grid component powered by AG Grid Community Edition with lazy loading for optimal performance.

Expand All @@ -14,6 +15,8 @@ npm install @object-ui/plugin-aggrid ag-grid-community ag-grid-react

Note: `ag-grid-community` and `ag-grid-react` are peer dependencies and must be installed separately.

<PluginLoader plugins={['aggrid']}>

## Interactive Examples

### Basic Data Grid
Expand Down Expand Up @@ -641,3 +644,5 @@ This results in significantly faster initial page loads for applications that do
- [AG Grid Community Documentation](https://www.ag-grid.com/documentation/)
- [Column Definitions Guide](https://www.ag-grid.com/documentation/javascript/column-definitions/)
- [Grid Options Reference](https://www.ag-grid.com/documentation/javascript/grid-options/)

</PluginLoader>
5 changes: 5 additions & 0 deletions content/docs/plugins/plugin-calendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: "Plugin Calendar"
---

import { InteractiveDemo } from '@/app/components/InteractiveDemo';
import { PluginLoader } from '@/app/components/PluginLoader';

Calendar view components for ObjectUI - includes both ObjectQL-integrated and standalone calendar components.

Expand All @@ -12,6 +13,8 @@ Calendar view components for ObjectUI - includes both ObjectQL-integrated and st
npm install @object-ui/plugin-calendar
```

<PluginLoader plugins={['calendar']}>

## Overview

The `@object-ui/plugin-calendar` plugin provides two calendar components:
Expand Down Expand Up @@ -511,3 +514,5 @@ All functionality remains the same - just update your imports and package depend

- [Plugin System Overview](/docs/concepts/plugins)
- [Package README](https://github.com/objectstack-ai/objectui/tree/main/packages/plugin-calendar)

</PluginLoader>
5 changes: 5 additions & 0 deletions content/docs/plugins/plugin-charts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: "Plugin Charts"
---

import { InteractiveDemo } from '@/app/components/InteractiveDemo';
import { PluginLoader } from '@/app/components/PluginLoader';

Data visualization components powered by Recharts.

Expand All @@ -12,6 +13,8 @@ Data visualization components powered by Recharts.
npm install @object-ui/plugin-charts
```

<PluginLoader plugins={['charts']}>

## Interactive Examples

<InteractiveDemo
Expand Down Expand Up @@ -341,3 +344,5 @@ const chartSchema: BarChartSchema = {
- [Plugin System Overview](/docs/concepts/plugins)
- [Lazy-Loaded Plugins Architecture](../concepts/lazy-loading)
- [Package README](https://github.com/objectstack-ai/objectui/tree/main/packages/plugin-charts)

</PluginLoader>
5 changes: 5 additions & 0 deletions content/docs/plugins/plugin-chatbot.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: "Plugin Chatbot"
---

import { InteractiveDemo } from '@/app/components/InteractiveDemo';
import { PluginLoader } from '@/app/components/PluginLoader';

Chat interface component with message history, typing indicators, and customizable avatars.

Expand All @@ -12,6 +13,8 @@ Chat interface component with message history, typing indicators, and customizab
npm install @object-ui/plugin-chatbot
```

<PluginLoader plugins={['chatbot']}>

## Interactive Examples

### Basic Chatbot
Expand Down Expand Up @@ -464,3 +467,5 @@ const chatbotSchema: ChatbotSchema = {

- [Plugin System Overview](/docs/concepts/plugins)
- [Package README](https://github.com/objectstack-ai/objectui/tree/main/packages/plugin-chatbot)

</PluginLoader>
5 changes: 5 additions & 0 deletions content/docs/plugins/plugin-dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: "Plugin Dashboard"
---

import { InteractiveDemo } from '@/app/components/InteractiveDemo';
import { PluginLoader } from '@/app/components/PluginLoader';

Dashboard layouts and metric widgets for creating beautiful dashboards with KPIs, charts, and statistics.

Expand All @@ -12,6 +13,8 @@ Dashboard layouts and metric widgets for creating beautiful dashboards with KPIs
npm install @object-ui/plugin-dashboard
```

<PluginLoader plugins={['dashboard']}>

## Interactive Examples

<InteractiveDemo
Expand Down Expand Up @@ -193,3 +196,5 @@ const dashboard: DashboardSchema = {
## License

MIT

</PluginLoader>
5 changes: 5 additions & 0 deletions content/docs/plugins/plugin-editor.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: "Plugin Editor"
---

import { InteractiveDemo } from '@/app/components/InteractiveDemo';
import { PluginLoader } from '@/app/components/PluginLoader';

Code editor component powered by Monaco Editor (VS Code's editor).

Expand All @@ -12,6 +13,8 @@ Code editor component powered by Monaco Editor (VS Code's editor).
npm install @object-ui/plugin-editor
```

<PluginLoader plugins={['editor']}>

## Interactive Examples

<InteractiveDemo
Expand Down Expand Up @@ -181,3 +184,5 @@ const editorSchema: CodeEditorSchema = {
- [Plugin System Overview](/docs/concepts/plugins)
- [Lazy-Loaded Plugins Architecture](../concepts/lazy-loading)
- [Package README](https://github.com/objectstack-ai/objectui/tree/main/packages/plugin-editor)

</PluginLoader>
5 changes: 5 additions & 0 deletions content/docs/plugins/plugin-form.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: "Plugin Form"
---

import { InteractiveDemo } from '@/app/components/InteractiveDemo';
import { PluginLoader } from '@/app/components/PluginLoader';

Advanced form components with validation, multi-step forms, and comprehensive field support.

Expand All @@ -12,6 +13,8 @@ Advanced form components with validation, multi-step forms, and comprehensive fi
npm install @object-ui/plugin-form
```

<PluginLoader plugins={['form']}>

## Interactive Examples

<InteractiveDemo
Expand Down Expand Up @@ -198,3 +201,5 @@ const loginForm: FormSchema = {
## License

MIT

</PluginLoader>
Loading
Loading