Skip to content

Commit deed6b3

Browse files
committed
docs: add comprehensive theme system documentation
- Update README with theme system overview and examples - Add complete THEME_SYSTEM.md guide covering: - Theme plugin lifecycle (onThemeEnable/onThemeDisable) - Layout slot overrides with createLayoutSlot() - Style context with StyleProvider and useStyles() - Static vs dynamic plugins comparison - Complete dark theme plugin example - Best practices and migration guide from v0.1.0 - API reference for theme-related functions - Clarify plugin types in README (dynamic, static, theme) - Add dark-theme-plugin to examples list
1 parent 52aa561 commit deed6b3

3 files changed

Lines changed: 650 additions & 21 deletions

File tree

README.md

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# React PKL
22

3-
A **typesafe plugin system for React applications** written in TypeScript. React PKL allows you to extend React applications from external sources through a robust, type-safe plugin architecture.
3+
A **typesafe plugin system for React applications** written in TypeScript. React PKL allows you to extend React applications from external sources through a robust, type-safe plugin architecture with advanced theming support.
44

55
[![TypeScript](https://img.shields.io/badge/TypeScript-5.5-blue.svg)](https://www.typescriptlang.org/)
66
[![React](https://img.shields.io/badge/React-18.0+-61dafb.svg)](https://reactjs.org/)
@@ -9,10 +9,13 @@ A **typesafe plugin system for React applications** written in TypeScript. React
99

1010
React PKL is designed for **SDK developers** who want to create extensible React applications. It provides the foundation for building plugin systems where:
1111

12-
- Plugins can extend the UI through defined **slots**
12+
- Plugins can extend the UI through defined **slots** and **layout overrides**
1313
- Plugins receive a **typesafe context** from the host application
14+
- **Theme plugins** can override entire layout components with custom styling
15+
- **Static plugins** work without lifecycle management (perfect for themes)
1416
- Resources are **automatically cleaned up** when plugins are disabled
1517
- Plugins can be managed **locally** or fetched from a **remote source**
18+
- **Style context** provides type-safe access to theme variables
1619

1720
## 📦 Packages
1821

@@ -292,6 +295,131 @@ Get all components registered for a slot:
292295
const toolbarComponents = useSlotComponents('toolbar');
293296
```
294297

298+
## 🎨 Theme System
299+
300+
### Creating Theme Plugins
301+
302+
Theme plugins use `onThemeEnable()` and `onThemeDisable()` to manage theme lifecycle:
303+
304+
```typescript
305+
import { definePlugin, AppHeader, AppSidebar, StyleProvider } from 'my-sdk';
306+
307+
const darkThemePlugin = definePlugin({
308+
meta: {
309+
id: 'com.example.dark-theme',
310+
name: 'Dark Theme',
311+
version: '1.0.0',
312+
},
313+
314+
// Theme plugins don't need activate/deactivate (static plugins)
315+
// They only activate when set as the active theme
316+
317+
onThemeEnable(slots) {
318+
// Apply CSS variables
319+
document.documentElement.style.setProperty('--bg-primary', '#1a1a1a');
320+
document.documentElement.style.setProperty('--text-primary', '#e4e4e7');
321+
322+
// Override layout slots with themed components
323+
slots.set(AppHeader, DarkHeader);
324+
slots.set(AppSidebar, DarkSidebar);
325+
326+
// Return cleanup function
327+
return () => {
328+
document.documentElement.style.removeProperty('--bg-primary');
329+
document.documentElement.style.removeProperty('--text-primary');
330+
};
331+
},
332+
333+
onThemeDisable() {
334+
console.log('Theme disabled - additional cleanup');
335+
},
336+
});
337+
338+
function DarkHeader({ toolbar }) {
339+
return (
340+
<StyleProvider variables={{
341+
bgPrimary: '#1a1a1a',
342+
textPrimary: '#e4e4e7',
343+
accentColor: '#60a5fa',
344+
}}>
345+
<header style={{ background: 'linear-gradient(135deg, #18181b 0%, #27272a 100%)' }}>
346+
{toolbar}
347+
</header>
348+
</StyleProvider>
349+
);
350+
}
351+
```
352+
353+
### Using StyleProvider
354+
355+
Provide type-safe style variables to components:
356+
357+
```tsx
358+
import { StyleProvider, useStyles } from 'my-sdk';
359+
360+
function MyComponent() {
361+
const styles = useStyles();
362+
363+
return (
364+
<div style={{
365+
background: styles.bgPrimary,
366+
color: styles.textPrimary,
367+
borderColor: styles.borderColor,
368+
}}>
369+
Themed content
370+
</div>
371+
);
372+
}
373+
```
374+
375+
### Setting Active Theme
376+
377+
```typescript
378+
import { isThemePlugin } from '@react-pkl/core';
379+
380+
// Check if a plugin is a theme plugin
381+
if (isThemePlugin(plugin)) {
382+
pluginHost.setThemePlugin(plugin);
383+
}
384+
385+
// Get current theme
386+
const currentTheme = pluginHost.getThemePlugin();
387+
388+
// Remove theme (back to default)
389+
pluginHost.setThemePlugin(null);
390+
391+
// Persist theme in localStorage
392+
localStorage.setItem('active-theme', plugin.meta.id);
393+
```
394+
395+
### Static vs Dynamic Plugins
396+
397+
React PKL supports two plugin types:
398+
399+
```typescript
400+
import { isStaticPlugin, isThemePlugin } from '@react-pkl/core';
401+
402+
// Static plugins - no activate/deactivate lifecycle
403+
// Perfect for theme plugins that only need theme lifecycle
404+
const themePlugin = {
405+
meta: { id: 'theme', name: 'Theme', version: '1.0.0' },
406+
onThemeEnable(slots) { /* ... */ },
407+
onThemeDisable() { /* ... */ },
408+
};
409+
410+
isStaticPlugin(themePlugin); // true
411+
isThemePlugin(themePlugin); // true
412+
413+
// Dynamic plugins - full lifecycle management
414+
const dataPlugin = {
415+
meta: { id: 'data', name: 'Data', version: '1.0.0' },
416+
async activate(context) { /* ... */ },
417+
async deactivate() { /* ... */ },
418+
};
419+
420+
isStaticPlugin(dataPlugin); // false
421+
```
422+
295423
## 🧩 Components
296424

297425
### `<PluginProvider>`
@@ -383,12 +511,22 @@ interface PluginModule<TContext> {
383511
// Optional lifecycle hooks
384512
activate?(context: TContext): void | Promise<void>;
385513
deactivate?(): void | Promise<void>;
514+
515+
// Optional React entrypoint
516+
entrypoint?(): ReactNode;
386517

387-
// Optional React components by slot name
388-
components?: Record<string, ComponentType<any>>;
518+
// Optional theme lifecycle hooks
519+
onThemeEnable?(slots: Map<Function, Function>): void | (() => void);
520+
onThemeDisable?(): void;
389521
}
390522
```
391523

524+
**Plugin Types:**
525+
- **Dynamic Plugins**: Have `activate`/`deactivate` - Full lifecycle management
526+
- **Static Plugins**: No `activate`/`deactivate` - Always available, perfect for themes
527+
- **Theme Plugins**: Have `onThemeEnable`/`onThemeDisable` - Can be set as active theme
528+
```
529+
392530
### TypeScript Plugin Helper
393531
394532
Create a helper for better type inference:
@@ -469,8 +607,9 @@ The repository includes complete examples:
469607
- **`examples/plugins`** - Sample plugins demonstrating various features:
470608
- `hello-plugin` - Basic plugin with notification
471609
- `user-greeting-plugin` - Accesses app context
472-
- `theme-toggle-plugin` - State management
610+
- `theme-toggle-plugin` - State management with toolbar button
473611
- `custom-page-plugin` - Route registration with cleanup
612+
- `dark-theme-plugin` - Complete theme with layout overrides and style context
474613

475614
## 🏛️ Design Philosophy
476615

0 commit comments

Comments
 (0)