Skip to content

Commit ab51c09

Browse files
committed
refactor(sdk,plugins): use context-driven layout slots and CSS variables
BREAKING CHANGE: Layout slots (AppHeader, AppSidebar, AppDashboard) no longer accept props - Layout slots now use context hooks (useAppContext, useAppLayout) directly - Add getRoutes() method to RouterService interface for accessing plugin routes - Navigation uses router.navigate() instead of React Router Link components - Remove prop dependencies from PageLayout, Shell, SettingsPage components feat(plugins): full theme support with CSS variables - Update all plugins to use CSS variables (--text-primary, --card-bg, etc.) - Plugins now properly respond to theme changes - Custom page plugin fully themed - User greeting, hello, and settings shortcut plugins use CSS variables Benefits: - Simpler component API - no prop drilling - Better separation of concerns - Plugins automatically respect active theme - More flexible for theme plugin implementations
1 parent de6480d commit ab51c09

File tree

10 files changed

+171
-143
lines changed

10 files changed

+171
-143
lines changed

examples/app/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ function AppWithRouter() {
161161

162162
{/* Routes */}
163163
<Routes key={routeVersion}>
164-
<Route path="/" element={<Shell host={host} pluginRoutes={pluginRoutes} />} />
165-
<Route path="/settings" element={<SettingsPage host={host} pluginRoutes={pluginRoutes} />} />
164+
<Route path="/" element={<Shell host={host} />} />
165+
<Route path="/settings" element={<SettingsPage host={host} />} />
166166
{/* Dynamic plugin routes */}
167167
{Array.from(pluginRoutes.values()).map((route) => (
168168
<Route key={route.path} path={route.path} element={<route.component />} />

examples/app/src/components/PageLayout.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,32 @@
11
import type { PluginHost } from '@react-pkl/core';
2-
import type { AppContext, PluginRoute } from 'example-sdk';
3-
import { useAppLayout, AppHeader, AppSidebar } from 'example-sdk';
4-
import { Link } from 'react-router-dom';
2+
import type { AppContext } from 'example-sdk';
3+
import { AppHeader, AppSidebar } from 'example-sdk';
54

65
/**
76
* PageLayout - Shared layout wrapper for all pages
87
*
98
* Provides consistent header and sidebar layout with themeable components.
109
* Used by both the home page (Shell) and settings page.
10+
*
11+
* Layout slots use context directly - no need to pass props!
1112
*/
1213
export function PageLayout({
1314
host,
14-
pluginRoutes,
1515
currentPath,
1616
children
1717
}: {
18-
host: PluginHost<AppContext>;
19-
pluginRoutes: Map<string, PluginRoute>;
18+
host: PluginHost<AppContext>;
2019
currentPath: string;
2120
children: React.ReactNode;
2221
}) {
23-
const layout = useAppLayout();
24-
2522
return (
2623
<div style={{ fontFamily: 'system-ui, sans-serif', padding: 24 }}>
27-
{/* Toolbar - themeable layout slot */}
28-
<AppHeader toolbar={layout.toolbar} />
24+
{/* Toolbar - themeable layout slot (uses context) */}
25+
<AppHeader />
2926

3027
<div style={{ display: 'flex', gap: 24 }}>
31-
{/* Sidebar - themeable layout slot */}
32-
<AppSidebar pluginRoutes={pluginRoutes} sidebarItems={layout.sidebar} Link={Link} />
28+
{/* Sidebar - themeable layout slot (uses context) */}
29+
<AppSidebar />
3330

3431
{/* Main content */}
3532
<main style={{ flex: 1 }}>

examples/app/src/mock-context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export function createMockAppContext(
5656
routes?.delete(path);
5757
onRouteChange?.();
5858
},
59+
getRoutes() {
60+
return routes || new Map();
61+
},
5962
},
6063

6164
user: {

examples/app/src/pages/SettingsPage.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { PluginHost } from '@react-pkl/core';
2-
import type { AppContext, PluginRoute } from 'example-sdk';
2+
import type { AppContext } from 'example-sdk';
33
import { useAppLayout } from 'example-sdk';
44
import { PageLayout } from '../components/PageLayout.js';
55
import { ThemeSelector } from '../components/ThemeSelector.js';
@@ -10,15 +10,13 @@ import { ThemeSelector } from '../components/ThemeSelector.js';
1010
* Displays application settings including theme selection and any plugin-contributed
1111
* settings sections.
1212
*/
13-
export function SettingsPage({ host, pluginRoutes }: { host: PluginHost<AppContext>; pluginRoutes: Map<string, PluginRoute> }) {
13+
export function SettingsPage({ host }: { host: PluginHost<AppContext> }) {
1414
const layout = useAppLayout();
1515

1616
return (
17-
<PageLayout host={host} pluginRoutes={pluginRoutes} currentPath="/settings">
17+
<PageLayout host={host} currentPath="/settings">
1818
<h2 style={{ marginTop: 0, color: 'var(--text-primary, inherit)' }}>Application Settings</h2>
19-
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
20-
{/* Theme Selection */}
21-
<ThemeSelector host={host} />
19+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>\n {/* Theme Selection */}\n <ThemeSelector host={host} />
2220

2321
<section style={{ padding: 16, background: 'var(--card-bg, #f8fafc)', borderRadius: 8 }}>
2422
<h3 style={{ marginTop: 0, fontSize: 16, color: 'var(--text-primary, inherit)' }}>General</h3>

examples/plugins/src/custom-page-plugin.tsx

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -48,41 +48,40 @@ export default definePlugin({
4848

4949
function CustomPage() {
5050
const { router, notifications } = useAppContext();
51-
const layout = useAppLayout();
5251

5352
return (
5453
<div style={{ fontFamily: 'system-ui, sans-serif', padding: 24 }}>
5554
{/* Toolbar - includes plugin components from other plugins! */}
56-
<AppHeader toolbar={layout.toolbar}/>
55+
<AppHeader />
5756

58-
<div style={{ padding: '16px 0', marginBottom: 24, borderBottom: '2px solid #e2e8f0' }}>
59-
<h1 style={{ margin: 0, fontSize: 28, color: '#1e293b' }}>
57+
<div style={{ padding: '16px 0', marginBottom: 24, borderBottom: '2px solid var(--border-color, #e2e8f0)' }}>
58+
<h1 style={{ margin: 0, fontSize: 28, color: 'var(--text-primary, #1e293b)' }}>
6059
📄 My Custom Page
6160
</h1>
62-
<p style={{ margin: '8px 0 0', color: '#64748b' }}>
61+
<p style={{ margin: '8px 0 0', color: 'var(--text-secondary, #64748b)' }}>
6362
This page was added by a plugin!
6463
</p>
6564
</div>
6665

6766
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
68-
<section style={{ padding: 24, background: '#f8fafc', borderRadius: 12 }}>
69-
<h2 style={{ marginTop: 0, fontSize: 20 }}>Welcome to the Custom Page</h2>
70-
<p style={{ color: '#475569', lineHeight: 1.6 }}>
67+
<section style={{ padding: 24, background: 'var(--card-bg, #f8fafc)', borderRadius: 12 }}>
68+
<h2 style={{ marginTop: 0, fontSize: 20, color: 'var(--text-primary, inherit)' }}>Welcome to the Custom Page</h2>
69+
<p style={{ color: 'var(--text-secondary, #475569)', lineHeight: 1.6 }}>
7170
This entire page was registered by the <strong>Custom Page Plugin</strong>.
7271
The plugin used the <code>context.router.registerRoute()</code> API during
7372
its activation phase.
7473
</p>
75-
<p style={{ color: '#475569', lineHeight: 1.6 }}>
74+
<p style={{ color: 'var(--text-secondary, #475569)', lineHeight: 1.6 }}>
7675
When the plugin is deactivated or removed, this route is automatically
7776
cleaned up and the page becomes inaccessible.
7877
</p>
7978
</section>
8079

81-
<section style={{ padding: 24, background: '#eff6ff', borderRadius: 12, border: '1px solid #bfdbfe' }}>
82-
<h3 style={{ marginTop: 0, fontSize: 18, color: '#1e40af' }}>
80+
<section style={{ padding: 24, background: 'var(--card-bg-secondary, #eff6ff)', borderRadius: 12, border: '1px solid var(--border-accent, #bfdbfe)' }}>
81+
<h3 style={{ marginTop: 0, fontSize: 18, color: 'var(--text-accent, #1e40af)' }}>
8382
🎯 Plugin Capabilities Demo
8483
</h3>
85-
<ul style={{ color: '#1e40af', lineHeight: 1.8 }}>
84+
<ul style={{ color: 'var(--text-accent, #1e40af)', lineHeight: 1.8 }}>
8685
<li>Custom routes with full React components</li>
8786
<li>Access to the application context (router, notifications, etc.)</li>
8887
<li>Proper lifecycle management (activate/deactivate)</li>
@@ -91,8 +90,8 @@ function CustomPage() {
9190
</ul>
9291
</section>
9392

94-
<section style={{ padding: 24, background: '#fff', borderRadius: 12, border: '1px solid #e2e8f0' }}>
95-
<h3 style={{ marginTop: 0, fontSize: 18 }}>Try These Actions</h3>
93+
<section style={{ padding: 24, background: 'var(--bg-primary, #fff)', borderRadius: 12, border: '1px solid var(--border-color, #e2e8f0)' }}>
94+
<h3 style={{ marginTop: 0, fontSize: 18, color: 'var(--text-primary, inherit)' }}>Try These Actions</h3>
9695
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
9796
<button
9897
onClick={() => {
@@ -102,11 +101,11 @@ function CustomPage() {
102101
style={{
103102
padding: '10px 20px',
104103
borderRadius: 6,
105-
border: '1px solid #cbd5e1',
106-
background: '#f8fafc',
104+
border: '1px solid var(--border-color, #cbd5e1)',
105+
background: 'var(--card-bg, #f8fafc)',
107106
cursor: 'pointer',
108107
fontSize: 14,
109-
color: '#334155',
108+
color: 'var(--text-primary, #334155)',
110109
}}
111110
>
112111
🏠 Go to Home
@@ -119,11 +118,11 @@ function CustomPage() {
119118
style={{
120119
padding: '10px 20px',
121120
borderRadius: 6,
122-
border: '1px solid #cbd5e1',
123-
background: '#f8fafc',
121+
border: '1px solid var(--border-color, #cbd5e1)',
122+
background: 'var(--card-bg, #f8fafc)',
124123
cursor: 'pointer',
125124
fontSize: 14,
126-
color: '#334155',
125+
color: 'var(--text-primary, #334155)',
127126
}}
128127
>
129128
⚙ Go to Settings
@@ -135,11 +134,11 @@ function CustomPage() {
135134
style={{
136135
padding: '10px 20px',
137136
borderRadius: 6,
138-
border: '1px solid #3b82f6',
139-
background: '#eff6ff',
137+
border: '1px solid var(--border-accent, #3b82f6)',
138+
background: 'var(--card-bg-secondary, #eff6ff)',
140139
cursor: 'pointer',
141140
fontSize: 14,
142-
color: '#1e40af',
141+
color: 'var(--text-accent, #1e40af)',
143142
}}
144143
>
145144
💬 Show Notification
@@ -182,21 +181,21 @@ function CustomPageSettings() {
182181
const { router } = useAppContext();
183182

184183
return (
185-
<section style={{ padding: 16, background: '#f8fafc', borderRadius: 8 }}>
186-
<h3 style={{ marginTop: 0, fontSize: 16 }}>Custom Page Plugin</h3>
187-
<p style={{ color: '#64748b', fontSize: 14, marginBottom: 12 }}>
184+
<section style={{ padding: 16, background: 'var(--card-bg, #f8fafc)', borderRadius: 8 }}>
185+
<h3 style={{ marginTop: 0, fontSize: 16, color: 'var(--text-primary, inherit)' }}>Custom Page Plugin</h3>
186+
<p style={{ color: 'var(--text-secondary, #64748b)', fontSize: 14, marginBottom: 12 }}>
188187
This plugin adds a custom page to the application.
189188
</p>
190189
<button
191190
onClick={() => router.navigate('/my-custom-page')}
192191
style={{
193192
padding: '8px 16px',
194193
borderRadius: 6,
195-
border: '1px solid #3b82f6',
196-
background: '#eff6ff',
194+
border: '1px solid var(--border-accent, #3b82f6)',
195+
background: 'var(--card-bg-secondary, #eff6ff)',
197196
cursor: 'pointer',
198197
fontSize: 13,
199-
color: '#1e40af',
198+
color: 'var(--text-accent, #1e40af)',
200199
}}
201200
>
202201
📄 Visit Custom Page

examples/plugins/src/hello-plugin.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ function HelloToolbarItem() {
4040
<span
4141
style={{
4242
padding: '4px 10px',
43-
background: '#e0f2fe',
43+
background: 'var(--card-bg-secondary, #e0f2fe)',
4444
borderRadius: 4,
4545
fontSize: 13,
46-
color: '#0369a1',
46+
color: 'var(--link-color, #0369a1)',
4747
}}
4848
>
4949
👋 Hello Plugin

examples/plugins/src/theme-toggle-plugin.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ function SettingsToolbarButton() {
7272
style={{
7373
padding: '4px 10px',
7474
borderRadius: 4,
75-
border: '1px solid #cbd5e1',
76-
background: '#f8fafc',
75+
border: '1px solid var(--border-color, #cbd5e1)',
76+
background: 'var(--card-bg, #f8fafc)',
7777
cursor: 'pointer',
7878
fontSize: 13,
79-
color: '#334155',
79+
color: 'var(--text-primary, #334155)',
8080
}}
8181
title="Open Settings (Alt+,)"
8282
>

examples/plugins/src/user-greeting-plugin.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function UserGreetingWidget() {
3434
if (!user) {
3535
return (
3636
<div style={cardStyle}>
37-
<p style={{ margin: 0, color: '#64748b' }}>
37+
<p style={{ margin: 0, color: 'var(--text-secondary, #64748b)' }}>
3838
Sign in to see your personalised greeting.
3939
</p>
4040
</div>
@@ -43,10 +43,10 @@ function UserGreetingWidget() {
4343

4444
return (
4545
<div style={cardStyle}>
46-
<h3 style={{ margin: '0 0 4px', fontSize: 16 }}>
46+
<h3 style={{ margin: '0 0 4px', fontSize: 16, color: 'var(--text-primary, inherit)' }}>
4747
Welcome back, {user.name}!
4848
</h3>
49-
<p style={{ margin: 0, fontSize: 13, color: '#64748b' }}>
49+
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-secondary, #64748b)' }}>
5050
{user.email} · {user.role}
5151
</p>
5252
</div>
@@ -56,6 +56,6 @@ function UserGreetingWidget() {
5656
const cardStyle: React.CSSProperties = {
5757
padding: '16px',
5858
borderRadius: 8,
59-
border: '1px solid #e2e8f0',
60-
background: '#fff',
59+
border: '1px solid var(--border-color, #e2e8f0)',
60+
background: 'var(--bg-primary, #fff)',
6161
};

examples/sdk/src/app-context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export interface RouterService {
3838
* Unregister a route by path (rarely needed - auto-cleanup handles this).
3939
*/
4040
unregisterRoute(path: string): void;
41+
/**
42+
* Get all registered plugin routes.
43+
*/
44+
getRoutes(): Map<string, PluginRoute>;
4145
}
4246

4347
export interface UserInfo {

0 commit comments

Comments
 (0)