@principal-ade/panel-framework-core
v0.5.1
Published
A flexible panel system for building extensible web applications with support for internal and external panel extensions
Maintainers
Readme
@principal-ade/panel-framework-core
A flexible, extensible panel system for building modular web applications with React. Supports both internal panels (living in your codebase) and external panels (distributed via NPM).
Features
- 🎯 Type-safe - Full TypeScript support with comprehensive type definitions
- 🔌 Pluggable - Support for both internal and external panel extensions
- 🎨 Flexible - Minimal opinions, maximum flexibility
- 🚀 Modern - Built with React hooks and modern ES modules
- 📦 Zero dependencies - Only peer dependencies on React
- 🎪 Event-driven - Built-in event bus for inter-panel communication
- 🛡️ Error boundaries - Automatic error handling and recovery
- ⚡ Lazy loading - Built-in support for code splitting
Installation
npm install @principal-ade/panel-framework-core
# or
bun add @principal-ade/panel-framework-corePeer Dependencies
npm install react react-domQuick Start
1. Define a Panel
// src/panels/MyPanel.tsx
import type { PanelComponentProps } from '@principal-ade/panel-framework-core';
export const MyPanel: React.FC<PanelComponentProps> = ({ context, actions, events }) => {
return (
<div>
<h2>My Panel</h2>
<p>Repository: {context.repositoryPath}</p>
<button onClick={() => actions.openFile?.('README.md')}>
Open README
</button>
</div>
);
};
// Panel metadata
export const metadata = {
id: 'my-panel',
name: 'My Panel',
description: 'A simple example panel',
};
export default MyPanel;2. Set Up the Panel System
// src/App.tsx
import { PanelHarness, PanelWrapper, PanelEventBus, globalPanelRegistry } from '@principal-ade/panel-framework-core';
import { MyPanel, metadata } from './panels/MyPanel';
// Register panel
globalPanelRegistry.register({
metadata,
component: MyPanel,
});
function App() {
const context = {
repositoryPath: '/my/repo',
repository: null,
data: {},
loading: false,
refresh: async () => {},
hasSlice: () => false,
isSliceLoading: () => false,
};
const actions = {
openFile: (path: string) => console.log('Open file:', path),
};
const events = new PanelEventBus();
return (
<PanelHarness context={context} actions={actions} events={events}>
<PanelWrapper component={MyPanel} />
</PanelHarness>
);
}3. With Panel Registry
// src/App.tsx
import { globalPanelRegistry, PanelWrapper } from '@principal-ade/panel-framework-core';
function PanelContainer({ panelId }: { panelId: string }) {
const [panel, setPanel] = React.useState(null);
React.useEffect(() => {
globalPanelRegistry.get(panelId).then(setPanel);
}, [panelId]);
if (!panel) return <div>Loading...</div>;
return (
<PanelWrapper
component={panel.component}
lifecycle={panel.lifecycle}
/>
);
}API Reference
Components
Source:
src/components/PanelHarness.tsx,src/components/PanelWrapper.tsx
PanelHarness
The main context provider for the panel system. See src/components/PanelHarness.tsx.
<PanelHarness
context={contextValue}
actions={actionsObject}
events={eventBusInstance}
>
{children}
</PanelHarness>Props:
context: PanelContextValue- The context object available to all panelsactions?: PanelActions- Optional actions for inter-panel communicationevents?: PanelEventEmitter- Optional event bus for panel eventschildren: ReactNode- Child components (usually PanelWrapper components)
PanelWrapper
Wraps individual panels with lifecycle management and error boundaries. See src/components/PanelWrapper.tsx.
<PanelWrapper
component={PanelComponent}
lifecycle={lifecycleHooks}
fallback={<LoadingComponent />}
errorFallback={ErrorComponent}
/>Props:
component: ComponentType<PanelComponentProps>- The panel componentlifecycle?: PanelLifecycleHooks- Optional lifecycle hooksfallback?: ReactNode- Loading fallbackerrorFallback?: ComponentType<{ error: Error; reset: () => void }>- Error fallback
Hooks
Source:
src/components/PanelHarness.tsx(hooks are exported from the harness)
usePanelContext()
Access the panel context.
const context = usePanelContext();
console.log(context.repositoryPath);usePanelActions()
Access panel actions.
const actions = usePanelActions();
actions.openFile?.('README.md');usePanelEvents()
Access the event emitter.
const events = usePanelEvents();
useEffect(() => {
const unsubscribe = events.on('file:opened', (event) => {
console.log('File opened:', event.payload);
});
return unsubscribe;
}, [events]);Classes
PanelEventBus
Source:
src/events/PanelEventBus.ts
Browser-compatible event bus for inter-panel communication.
const eventBus = new PanelEventBus();
// Emit an event
eventBus.emit({
type: 'file:opened',
source: 'my-panel',
timestamp: Date.now(),
payload: { path: '/README.md' },
});
// Listen to events
const unsubscribe = eventBus.on('file:opened', (event) => {
console.log('File opened:', event.payload);
});
// Cleanup
unsubscribe();PanelRegistry
Source:
src/utils/panelRegistry.ts
Manages panel registration and loading.
import { globalPanelRegistry } from '@principal-ade/panel-framework-core';
// Register a panel
globalPanelRegistry.register({
metadata: { id: 'my-panel', name: 'My Panel' },
component: MyPanel,
onMount: async (context) => {
console.log('Panel mounted');
},
});
// Register with lazy loading
globalPanelRegistry.registerLazy('lazy-panel', async () => {
const module = await import('./panels/LazyPanel');
return module;
});
// Get a panel
const panel = await globalPanelRegistry.get('my-panel');
// Get all panel IDs
const ids = globalPanelRegistry.getAllIds();Type Definitions
Source:
src/types/panel.types.ts
PanelComponentProps
Props passed to every panel component.
interface PanelComponentProps {
context: PanelContextValue;
actions: PanelActions;
events: PanelEventEmitter;
}PanelContextValue
The context object available to all panels.
interface PanelContextValue {
repositoryPath: string | null;
repository: unknown | null;
data: Record<string, unknown>;
loading: boolean;
refresh: () => Promise<void>;
hasSlice: (slice: PanelDataSlice) => boolean;
isSliceLoading: (slice: PanelDataSlice) => boolean;
}PanelMetadata
Panel metadata structure.
interface PanelMetadata {
id: string;
name: string;
icon?: string;
version?: string;
author?: string;
description?: string;
surfaces?: string[];
slices?: PanelDataSlice[];
}PanelLifecycleHooks
Optional lifecycle hooks for panels.
interface PanelLifecycleHooks {
onMount?: (context: PanelContextValue) => void | Promise<void>;
onUnmount?: (context: PanelContextValue) => void | Promise<void>;
onDataChange?: (slice: PanelDataSlice, data: unknown) => void;
}Panel Development Guide
Internal Panels
For panels living in your codebase:
// src/panels/MyPanel/index.tsx
import type { PanelComponentProps, PanelMetadata } from '@principal-ade/panel-framework-core';
export const metadata: PanelMetadata = {
id: 'my-app.my-panel',
name: 'My Panel',
icon: '🎨',
version: '1.0.0',
};
export const MyPanel: React.FC<PanelComponentProps> = ({ context, actions, events }) => {
// Your panel implementation
return <div>My Panel</div>;
};
export default MyPanel;External Panels (Future)
For panels distributed via NPM, follow the complete specification in PANEL_EXTENSION_STORE_SPECIFICATION.md.
Event Types
Built-in event types for inter-panel communication:
file:opened- A file was openedfile:saved- A file was savedfile:deleted- A file was deletedgit:status-changed- Git status changedgit:commit- A commit was madegit:branch-changed- Git branch changedpanel:focus- A panel received focuspanel:blur- A panel lost focusdata:refresh- Data refresh requested
Examples
See the examples directory for complete working examples:
- Basic panel setup
- Multiple panels with communication
- Lazy-loaded panels
- Custom data slices
- Error handling
Project Structure
index.ts # Main entry point, re-exports all public APIs
src/
├── components/
│ ├── index.ts # Component exports
│ ├── PanelHarness.tsx # Context provider and hooks
│ └── PanelWrapper.tsx # Panel wrapper with error boundaries
├── events/
│ ├── index.ts # Event exports
│ └── PanelEventBus.ts # Browser-compatible event bus
├── types/
│ ├── index.ts # Type exports
│ └── panel.types.ts # All TypeScript interfaces and types
├── utils/
│ ├── index.ts # Utility exports
│ ├── panelRegistry.ts # Panel registration and discovery
│ └── dataSlice.ts # Data slice utilities
└── tools/
├── index.ts # Tools exports
└── PanelToolRegistry.ts # Agent tool registryArchitecture
This package is designed to support a phased approach to panel extensibility:
Phase 1 (Current):
- Panels live in the codebase
- Shared panel system and types
- Event-based communication
Phase 2 (Future):
- External NPM panel packages
- Dynamic panel discovery
- Panel marketplace/store
For detailed architecture information, see:
License
MIT
Contributing
Contributions welcome! Please read our contributing guidelines first.
