Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
621a8cf
add plugin configurations
AceTheCreator Oct 24, 2025
99544f0
add plugin slot renderer
AceTheCreator Oct 24, 2025
83dd359
passed props from asyncapi so standalone can access
AceTheCreator Oct 24, 2025
393abda
test plugin event reciever
AceTheCreator Nov 3, 2025
f7124f7
add support for ref plugin registration
AceTheCreator Nov 4, 2025
d44ea92
added test cases forslot and plugin manager
AceTheCreator Nov 4, 2025
9e98377
add plugin quick started docs
AceTheCreator Nov 12, 2025
b5511b5
Merge branch 'asyncapi:master' into extensibility
AceTheCreator Nov 12, 2025
14ec2e4
removed ccomment
AceTheCreator Nov 12, 2025
a6403c9
.
AceTheCreator Nov 12, 2025
3f661ff
Merge branch 'master' into extensibility
AceTheCreator Nov 26, 2025
7289cd6
removed unused console
AceTheCreator Nov 26, 2025
842de94
trying custom cypress timeout to fix test failure
AceTheCreator Nov 26, 2025
bb03e4c
revert cypress timeout
AceTheCreator Nov 26, 2025
4a2a8d5
Merge branch 'master' into extensibility
AceTheCreator Dec 3, 2025
e119e05
chore: trigger CI rebuild
AceTheCreator Dec 3, 2025
e2d2001
feat: add info level plugin slot
AceTheCreator Jan 22, 2026
665dc09
chore(docs): add missing slot to docs
AceTheCreator Jan 22, 2026
12a202b
fix: failing pluginManager test
AceTheCreator Jan 22, 2026
5542204
Merge branch 'master' into extensibility
AceTheCreator Jan 22, 2026
ab9a6e1
fix: made suggested sonarcloud changes
AceTheCreator Jan 22, 2026
a9e72da
Merge branch 'extensibility' of https://github.com/AceTheCreator/asyn…
AceTheCreator Jan 22, 2026
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
95 changes: 95 additions & 0 deletions docs/features/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Plugin System

The AsyncAPI React component supports a flexible plugin system to extend and customize your documentation.

## Usage

### Static Registration (via props)

Use this when you know all plugins upfront:

```typescript
import { AsyncApiPlugin, PluginAPI, PluginSlot } from '@asyncapi/react-component';

const myPlugin: AsyncApiPlugin = {
name: 'my-plugin',
version: '1.0.0',
install(api: PluginAPI) {
api.registerComponent(PluginSlot.OPERATION, MyComponent);
api.onSpecLoaded((spec) => console.log('Spec loaded:', spec));
}
};

<AsyncApi schema={mySchema} plugins={[myPlugin]} />
```

### Dynamic Registration

Use this when you need to add/remove plugins at runtime:

```typescript
import { useState } from 'react';

function MyApp() {
const [pluginManager, setPluginManager] = useState(null);

const handleEnablePlugin = () => {
pluginManager?.register(myPlugin);
};

const handleDisablePlugin = () => {
pluginManager?.unregister('my-plugin');
};

return (
<>
<button onClick={handleEnablePlugin}>Enable Plugin</button>
<button onClick={handleDisablePlugin}>Disable Plugin</button>
<AsyncApi
schema={mySchema}
onPluginManagerReady={(pm) => setPluginManager(pm)}
/>
</>
);
}
```

## Plugin Structure

```typescript
interface AsyncApiPlugin {
name: string; // Unique identifier
version: string; // Semantic version
description?: string; // Optional description
install(api: PluginAPI): void;
}
```

## PluginAPI Methods

| Method | Purpose |
|--------|---------|
| `registerComponent(slot, component, options?)` | Register a React component in a slot. `options`: `{ priority?: number; label?: string }` |
| `onSpecLoaded(callback)` | Called when AsyncAPI spec loads |
| `getContext()` | Get current plugin context with schema |
| `on(eventName, callback)` | Subscribe to events |
| `off(eventName, callback)` | Unsubscribe from events |
| `emit(eventName, data)` | Emit custom events |

## Component Props

```typescript
interface ComponentSlotProps {
context: PluginContext;
onClose?: () => void;
}

const MyComponent: React.FC<ComponentSlotProps> = ({ context, onClose }) => (
<div>Custom content here</div>
);
```

## Available Slots

- `PluginSlot.OPERATION` - Renders within operation sections
- `PluginSlot.INFO` - Renders within info section
1 change: 1 addition & 0 deletions library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"@types/node": "^18.0.0",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
"@types/testing-library__jest-dom": "^5.14.9",
"autoprefixer": "^10.2.5",
"cross-env": "^7.0.3",
"cssnano": "^4.1.11",
Expand Down
48 changes: 48 additions & 0 deletions library/src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import krakenMultipleChannels from './docs/v3/kraken-websocket-request-reply-mul
import streetlightsKafka from './docs/v3/streetlights-kafka.json';
import streetlightsMqtt from './docs/v3/streetlights-mqtt.json';
import websocketGemini from './docs/v3/websocket-gemini.json';
import { PluginAPI, PluginSlot } from '../types';

jest.mock('use-resize-observer', () => ({
__esModule: true,
Expand Down Expand Up @@ -266,4 +267,51 @@ describe('AsyncAPI component', () => {
expect(result.container.querySelector('#custom-extension')).toBeDefined();
});
});

test('should work with plugin registration', async () => {
const TestPluginComponent = () => (
<div data-testid="plugin-component">Test Plugin Rendered</div>
);

const testPlugin = {
name: 'test-plugin',
version: '1.0.0',
install: (api: PluginAPI) => {
api.registerComponent(PluginSlot.OPERATION, TestPluginComponent);
},
};

const schema = {
asyncapi: '2.0.0',
info: {
title: 'Test API with Plugins',
version: '1.0.0',
},
channels: {
'test/channel': {
subscribe: {
message: {
payload: {
type: 'object',
properties: {
id: { type: 'string' },
},
},
},
},
},
},
};

const result = render(
<AsyncApiComponent schema={schema} plugins={[testPlugin]} />,
);

await waitFor(() => {
expect(result.container.querySelector('#introduction')).toBeDefined();
const pluginComponent = result.getByTestId('plugin-component');
expect(pluginComponent).toBeDefined();
expect(pluginComponent.textContent).toContain('Test Plugin Rendered');
});
});
});
40 changes: 40 additions & 0 deletions library/src/components/PluginSlotRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { PluginManager } from '../helpers/pluginManager';
import { PluginContext, PluginSlot } from '../types';

interface SlotRendererProps {
slot: PluginSlot;
context: PluginContext;
pluginManager?: PluginManager;
}

const SlotRenderer: React.FC<SlotRendererProps> = ({
slot,
context,
pluginManager,
}) => {
if (!pluginManager) {
return null;
}

const components = pluginManager.getComponentsForSlot(slot);

if (!components || components.length === 0) {
return null;
}

return (
<div className={`asyncapi-react-plugin-slot-${slot}`} data-slot={slot}>
{components.map((Component, index) => (
<React.Suspense
key={`${slot}-${index}`}
fallback={<div>Loading plugin...</div>}
>
<Component context={context} />
</React.Suspense>
))}
</div>
);
};

export { SlotRenderer };
Loading