@@ -7,14 +7,12 @@ import {
77 SidebarSlotProvider ,
88 DashboardSlotProvider ,
99 SettingsSlotProvider ,
10- useAppLayout ,
11- AppHeader ,
12- AppSidebar ,
13- AppDashboard ,
1410} from 'example-sdk' ;
15- import { AppPluginProvider , useAppPluginMeta , PluginEntrypoints } from 'example-sdk/react' ;
11+ import { AppPluginProvider , PluginEntrypoints } from 'example-sdk/react' ;
1612import { createMockAppContext } from './mock-context.js' ;
1713import { LocalStoragePluginProvider } from './local-storage-adapter.js' ;
14+ import { Shell } from './pages/Shell.js' ;
15+ import { SettingsPage } from './pages/SettingsPage.js' ;
1816import type { PluginHost } from '@react-pkl/core' ;
1917import type { AppContext , PluginRoute } from 'example-sdk' ;
2018
@@ -207,249 +205,3 @@ function NotFoundPage() {
207205 </ div >
208206 ) ;
209207}
210-
211- // ---------------------------------------------------------------------------
212- // PageLayout – shared layout wrapper for all pages
213- // ---------------------------------------------------------------------------
214-
215- function PageLayout ( {
216- host,
217- pluginRoutes,
218- currentPath,
219- children
220- } : {
221- host : PluginHost < AppContext > ;
222- pluginRoutes : Map < string , PluginRoute > ;
223- currentPath : string ;
224- children : React . ReactNode ;
225- } ) {
226- const layout = useAppLayout ( ) ;
227-
228- return (
229- < div style = { { fontFamily : 'system-ui, sans-serif' , padding : 24 } } >
230- { /* Toolbar - themeable layout slot */ }
231- < AppHeader toolbar = { layout . toolbar } />
232-
233- < div style = { { display : 'flex' , gap : 24 } } >
234- { /* Sidebar - themeable layout slot */ }
235- < AppSidebar pluginRoutes = { pluginRoutes } sidebarItems = { layout . sidebar } Link = { Link } />
236-
237- { /* Main content */ }
238- < main style = { { flex : 1 } } >
239- { children }
240- </ main >
241- </ div >
242- </ div >
243- ) ;
244- }
245-
246- // ---------------------------------------------------------------------------
247- // Shell – home page
248- // ---------------------------------------------------------------------------
249-
250- function Shell ( { host, pluginRoutes } : { host : PluginHost < AppContext > ; pluginRoutes : Map < string , PluginRoute > } ) {
251- const layout = useAppLayout ( ) ;
252-
253- return (
254- < PageLayout host = { host } pluginRoutes = { pluginRoutes } currentPath = "/" >
255- < h2 style = { { marginTop : 0 , color : 'var(--text-primary, inherit)' } } > Dashboard</ h2 >
256-
257- { /* Dashboard - themeable layout slot */ }
258- < AppDashboard dashboardItems = { layout . dashboard } />
259-
260- < PluginDebugPanel host = { host } />
261- </ PageLayout >
262- ) ;
263- }
264-
265- // ---------------------------------------------------------------------------
266- // Debug panel – shows registered plugins and lets you toggle them
267- // ---------------------------------------------------------------------------
268-
269- function PluginDebugPanel ( {
270- host,
271- } : {
272- host : PluginHost < AppContext > ;
273- } ) {
274- const meta = useAppPluginMeta ( ) ;
275- const [ , forceUpdate ] = useState ( 0 ) ;
276- const manager = host . getManager ( ) ;
277-
278- return (
279- < section
280- style = { {
281- marginTop : 32 ,
282- padding : 16 ,
283- border : '1px dashed #cbd5e1' ,
284- borderRadius : 8 ,
285- } }
286- >
287- < h3 style = { { marginTop : 0 , fontSize : 14 , color : '#475569' } } >
288- Registered plugins ({ meta . length } )
289- </ h3 >
290- < ul style = { { margin : 0 , padding : 0 , listStyle : 'none' , display : 'flex' , flexDirection : 'column' , gap : 8 } } >
291- { meta . map ( ( m ) => (
292- < li key = { m . id } style = { { display : 'flex' , gap : 12 , alignItems : 'center' , fontSize : 13 } } >
293- < span style = { { fontWeight : 500 } } > { m . name } </ span >
294- < span style = { { color : '#94a3b8' } } > v{ m . version } </ span >
295- < button
296- style = { { marginLeft : 'auto' , fontSize : 12 , cursor : 'pointer' } }
297- onClick = { async ( ) => {
298- const entry = manager . getAll ( ) . find ( ( e : import ( '@react-pkl/core' ) . PluginEntry < AppContext > ) = > e . module . meta . id === m . id ) ;
299- if ( ! entry ) return ;
300- if ( entry . status === 'enabled' ) {
301- await manager . disable ( m . id ) ;
302- } else {
303- await manager . enable ( m . id ) ;
304- }
305- forceUpdate ( ( v ) => v + 1 ) ;
306- } }
307- >
308- { manager . getAll ( ) . find ( ( e : import ( '@react-pkl/core' ) . PluginEntry < AppContext > ) = > e . module . meta . id === m . id ) ?. status === 'enabled'
309- ? 'Disable'
310- : 'Enable' }
311- </ button >
312- </ li >
313- ) ) }
314- </ ul >
315- </ section >
316- ) ;
317- }
318-
319- // ---------------------------------------------------------------------------
320- // ThemeSelector – dropdown to select active theme plugin
321- // ---------------------------------------------------------------------------
322-
323- function ThemeSelector ( { host } : { host : PluginHost < AppContext > } ) {
324- const manager = host . getManager ( ) ;
325- const registry = host . getRegistry ( ) ;
326- const [ currentTheme , setCurrentTheme ] = useState ( ( ) => host . getThemePlugin ( ) ) ;
327-
328- // Subscribe to host changes (theme changes trigger re-render)
329- useEffect ( ( ) => {
330- const unsubscribe = host . subscribe ( ( ) => {
331- setCurrentTheme ( host . getThemePlugin ( ) ) ;
332- } ) ;
333- return unsubscribe ;
334- } , [ host ] ) ;
335-
336- // Get all plugins (enabled + static) that have onThemeEnable (theme plugins)
337- // Static plugins don't need to be "enabled" to be used as themes
338- const themePlugins = registry
339- . getAll ( )
340- . map ( entry => entry . module )
341- . filter ( plugin => typeof plugin . onThemeEnable === 'function' ) ;
342-
343- const handleThemeChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
344- const selectedId = e . target . value ;
345-
346- // Save theme preference to localStorage
347- localStorage . setItem ( 'react-pkl:active-theme' , selectedId ) ;
348-
349- if ( selectedId === 'default' ) {
350- host . setThemePlugin ( null ) ;
351- } else {
352- const plugin = themePlugins . find ( p => p . meta . id === selectedId ) ;
353- if ( plugin ) {
354- host . setThemePlugin ( plugin ) ;
355- }
356- }
357- } ;
358-
359- return (
360- < section
361- style = { {
362- padding : 16 ,
363- background : 'var(--card-bg, #f8fafc)' ,
364- borderRadius : 8 ,
365- border : '1px solid var(--border-color, #e2e8f0)' ,
366- } }
367- >
368- < h3 style = { { marginTop : 0 , fontSize : 16 , color : 'var(--text-primary, inherit)' } } >
369- 🎨 Theme
370- </ h3 >
371- < p style = { { color : 'var(--text-secondary, #64748b)' , fontSize : 14 , marginBottom : 12 } } >
372- Select a theme to customize the appearance of the application
373- </ p >
374-
375- < div style = { { display : 'flex' , alignItems : 'center' , gap : 12 } } >
376- < label
377- htmlFor = "theme-select"
378- style = { {
379- fontSize : 14 ,
380- fontWeight : 500 ,
381- color : 'var(--text-primary, inherit)' ,
382- } }
383- >
384- Active Theme:
385- </ label >
386- < select
387- id = "theme-select"
388- value = { currentTheme ?. meta . id || 'default' }
389- onChange = { handleThemeChange }
390- style = { {
391- padding : '8px 12px' ,
392- borderRadius : 6 ,
393- border : '1px solid var(--border-color, #cbd5e1)' ,
394- background : 'var(--bg-primary, white)' ,
395- color : 'var(--text-primary, inherit)' ,
396- fontSize : 14 ,
397- cursor : 'pointer' ,
398- minWidth : 200 ,
399- } }
400- >
401- < option value = "default" > Default (Light)</ option >
402- { themePlugins . map ( plugin => (
403- < option key = { plugin . meta . id } value = { plugin . meta . id } >
404- { plugin . meta . name }
405- </ option >
406- ) ) }
407- </ select >
408- </ div >
409-
410- { themePlugins . length === 0 && (
411- < p style = { { color : 'var(--text-muted, #94a3b8)' , fontSize : 13 , marginTop : 12 , marginBottom : 0 } } >
412- No theme plugins available. Enable a theme plugin to customize the appearance.
413- </ p >
414- ) }
415-
416- { currentTheme && (
417- < p style = { { color : 'var(--text-secondary, #64748b)' , fontSize : 13 , marginTop : 12 , marginBottom : 0 } } >
418- < strong > { currentTheme . meta . name } </ strong > - { currentTheme . meta . description }
419- </ p >
420- ) }
421- </ section >
422- ) ;
423- }
424-
425- // ---------------------------------------------------------------------------
426- // ---------------------------------------------------------------------------
427- // SettingsPage – accessible via /settings route
428- // ---------------------------------------------------------------------------
429-
430- function SettingsPage ( { host, pluginRoutes } : { host : PluginHost < AppContext > ; pluginRoutes : Map < string , PluginRoute > } ) {
431- const layout = useAppLayout ( ) ;
432-
433- return (
434- < PageLayout host = { host } pluginRoutes = { pluginRoutes } currentPath = "/settings" >
435- < h2 style = { { marginTop : 0 , color : 'var(--text-primary, inherit)' } } > Application Settings</ h2 >
436- < div style = { { display : 'flex' , flexDirection : 'column' , gap : 16 } } >
437- { /* Theme Selection */ }
438- < ThemeSelector host = { host } />
439-
440- < section style = { { padding : 16 , background : 'var(--card-bg, #f8fafc)' , borderRadius : 8 } } >
441- < h3 style = { { marginTop : 0 , fontSize : 16 , color : 'var(--text-primary, inherit)' } } > General</ h3 >
442- < p style = { { color : 'var(--text-secondary, #64748b)' , fontSize : 14 } } > Basic application settings</ p >
443- </ section >
444-
445- { /* Plugin settings sections */ }
446- { layout . settings . length === 0 ? (
447- < p style = { { color : 'var(--text-muted, #94a3b8)' , fontSize : 14 } } > No plugin settings available.</ p >
448- ) : (
449- layout . settings
450- ) }
451- </ div >
452- </ PageLayout >
453- ) ;
454- }
455-
0 commit comments